From 8f568f4fc56aa9052a4b5a6e989379b99291e80d Mon Sep 17 00:00:00 2001 From: Jason Gedge Date: Fri, 21 Aug 2020 21:26:47 -0400 Subject: [PATCH] Support completions for a single hyphen argument. (#2388) The parser sees this as a positional argument, but when requesting completions this could be either a filename that starts with a hyphen, or it could be a flag. This expands the completion engine's interface to return a vec of possible completion locations instead of an optional one, because we want to show all possibilities instead of assuming one or the either. --- crates/nu-cli/src/completion/engine.rs | 65 +++++++++++++++++++------ crates/nu-cli/src/shell/completer.rs | 67 ++++++++++++++------------ 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/crates/nu-cli/src/completion/engine.rs b/crates/nu-cli/src/completion/engine.rs index a5e8021800..eb9a777139 100644 --- a/crates/nu-cli/src/completion/engine.rs +++ b/crates/nu-cli/src/completion/engine.rs @@ -177,19 +177,35 @@ impl<'s> Flatten<'s> { const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';']; /// Determines the completion location for a given block at the given cursor position -pub fn completion_location(line: &str, block: &Block, pos: usize) -> Option { +pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec { let completion_engine = Flatten::new(line); let locations = completion_engine.completion_locations(block); if locations.is_empty() { - Some(LocationType::Command.spanned(Span::unknown())) + vec![LocationType::Command.spanned(Span::unknown())] } else { let mut prev = None; for loc in locations { // We don't use span.contains because we want to include the end. This handles the case // where the cursor is just after the text (i.e., no space between cursor and text) if loc.span.start() <= pos && pos <= loc.span.end() { - return Some(loc); + // The parser sees the "-" in `cmd -` as an argument, but the user is likely + // expecting a flag. + return match loc.item { + LocationType::Argument(ref cmd, _) => { + if loc.span.slice(line) == "-" { + let cmd = cmd.clone(); + let span = loc.span; + vec![ + loc, + LocationType::Flag(cmd.unwrap_or_default()).spanned(span), + ] + } else { + vec![loc] + } + } + _ => vec![loc], + }; } else if pos < loc.span.start() { break; } @@ -202,14 +218,14 @@ pub fn completion_location(line: &str, block: &Block, pos: usize) -> Option Option { + ) -> Vec { let lite_block = lite_parse(line, 0).expect("lite_parse"); let block = classify_block(&lite_block, registry); - super::completion_location(line, &block.block, pos).map(|v| v.item) + super::completion_location(line, &block.block, pos) + .into_iter() + .map(|v| v.item) + .collect() } #[test] @@ -267,7 +286,7 @@ mod tests { assert_eq!( completion_location(line, ®istry, 10), - Some(LocationType::Command), + vec![LocationType::Command], ); } @@ -278,7 +297,7 @@ mod tests { assert_eq!( completion_location(line, ®istry, 10), - Some(LocationType::Command), + vec![LocationType::Command], ); } @@ -289,7 +308,7 @@ mod tests { assert_eq!( completion_location(line, ®istry, 4), - Some(LocationType::Command), + vec![LocationType::Command], ); } @@ -300,7 +319,7 @@ mod tests { assert_eq!( completion_location(line, ®istry, 13), - Some(LocationType::Variable), + vec![LocationType::Variable], ); } @@ -315,7 +334,25 @@ mod tests { assert_eq!( completion_location(line, ®istry, 7), - Some(LocationType::Flag("du".to_string())), + vec![LocationType::Flag("du".to_string())], + ); + } + + #[test] + fn completes_flags_with_just_a_single_hyphen() { + let registry: VecRegistry = vec![Signature::build("du") + .switch("recursive", "the values to echo", None) + .rest(SyntaxShape::Any, "blah")] + .into(); + + let line = "du -"; + + assert_eq!( + completion_location(line, ®istry, 3), + vec![ + LocationType::Argument(Some("du".to_string()), None), + LocationType::Flag("du".to_string()), + ], ); } @@ -327,7 +364,7 @@ mod tests { assert_eq!( completion_location(line, ®istry, 6), - Some(LocationType::Argument(Some("echo".to_string()), None)), + vec![LocationType::Argument(Some("echo".to_string()), None)], ); } } diff --git a/crates/nu-cli/src/shell/completer.rs b/crates/nu-cli/src/shell/completer.rs index 1988b247d2..1c5ce81666 100644 --- a/crates/nu-cli/src/shell/completer.rs +++ b/crates/nu-cli/src/shell/completer.rs @@ -20,41 +20,44 @@ impl NuCompleter { Err(result) => result.partial, }; - let location = lite_block + let locations = lite_block .map(|block| nu_parser::classify_block(&block, &nu_context.registry)) - .and_then(|block| { - crate::completion::engine::completion_location(line, &block.block, pos) - }); + .map(|block| crate::completion::engine::completion_location(line, &block.block, pos)) + .unwrap_or_default(); - if let Some(location) = location { - let partial = location.span.slice(line); - - let suggestions = match location.item { - LocationType::Command => { - let command_completer = crate::completion::command::Completer {}; - command_completer.complete(context, partial) - } - - LocationType::Flag(cmd) => { - let flag_completer = crate::completion::flag::Completer {}; - flag_completer.complete(context, cmd, partial) - } - - LocationType::Argument(_cmd, _arg_name) => { - // TODO use cmd and arg_name to narrow things down further - let path_completer = crate::completion::path::Completer::new(); - path_completer.complete(context, partial) - } - - LocationType::Variable => Vec::new(), - } - .into_iter() - .map(requote) - .collect(); - - (location.span.start(), suggestions) - } else { + if locations.is_empty() { (pos, Vec::new()) + } else { + let pos = locations[0].span.start(); + let suggestions = locations + .into_iter() + .flat_map(|location| { + let partial = location.span.slice(line); + match location.item { + LocationType::Command => { + let command_completer = crate::completion::command::Completer {}; + command_completer.complete(context, partial) + } + + LocationType::Flag(cmd) => { + let flag_completer = crate::completion::flag::Completer {}; + flag_completer.complete(context, cmd, partial) + } + + LocationType::Argument(_cmd, _arg_name) => { + // TODO use cmd and arg_name to narrow things down further + let path_completer = crate::completion::path::Completer::new(); + path_completer.complete(context, partial) + } + + LocationType::Variable => Vec::new(), + } + .into_iter() + .map(requote) + }) + .collect(); + + (pos, suggestions) } } }