forked from extern/nushell
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.
This commit is contained in:
parent
ee26590011
commit
8f568f4fc5
@ -177,19 +177,35 @@ impl<'s> Flatten<'s> {
|
|||||||
const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';'];
|
const BEFORE_COMMAND_CHARS: &[char] = &['|', '(', ';'];
|
||||||
|
|
||||||
/// Determines the completion location for a given block at the given cursor position
|
/// Determines the completion location for a given block at the given cursor position
|
||||||
pub fn completion_location(line: &str, block: &Block, pos: usize) -> Option<CompletionLocation> {
|
pub fn completion_location(line: &str, block: &Block, pos: usize) -> Vec<CompletionLocation> {
|
||||||
let completion_engine = Flatten::new(line);
|
let completion_engine = Flatten::new(line);
|
||||||
let locations = completion_engine.completion_locations(block);
|
let locations = completion_engine.completion_locations(block);
|
||||||
|
|
||||||
if locations.is_empty() {
|
if locations.is_empty() {
|
||||||
Some(LocationType::Command.spanned(Span::unknown()))
|
vec![LocationType::Command.spanned(Span::unknown())]
|
||||||
} else {
|
} else {
|
||||||
let mut prev = None;
|
let mut prev = None;
|
||||||
for loc in locations {
|
for loc in locations {
|
||||||
// We don't use span.contains because we want to include the end. This handles the case
|
// 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)
|
// 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() {
|
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() {
|
} else if pos < loc.span.start() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -202,14 +218,14 @@ pub fn completion_location(line: &str, block: &Block, pos: usize) -> Option<Comp
|
|||||||
// is after some character that would imply we're in the command position.
|
// is after some character that would imply we're in the command position.
|
||||||
let start = prev.span.end();
|
let start = prev.span.end();
|
||||||
if line[start..pos].contains(BEFORE_COMMAND_CHARS) {
|
if line[start..pos].contains(BEFORE_COMMAND_CHARS) {
|
||||||
Some(LocationType::Command.spanned(Span::unknown()))
|
vec![LocationType::Command.spanned(Span::unknown())]
|
||||||
} else {
|
} else {
|
||||||
// TODO this should be able to be mapped to a command
|
// TODO this should be able to be mapped to a command
|
||||||
Some(LocationType::Argument(None, None).spanned(Span::unknown()))
|
vec![LocationType::Argument(None, None).spanned(Span::unknown())]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Cursor is before any possible completion location, so must be a command
|
// Cursor is before any possible completion location, so must be a command
|
||||||
Some(LocationType::Command.spanned(Span::unknown()))
|
vec![LocationType::Command.spanned(Span::unknown())]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,10 +269,13 @@ mod tests {
|
|||||||
line: &str,
|
line: &str,
|
||||||
registry: &dyn SignatureRegistry,
|
registry: &dyn SignatureRegistry,
|
||||||
pos: usize,
|
pos: usize,
|
||||||
) -> Option<LocationType> {
|
) -> Vec<LocationType> {
|
||||||
let lite_block = lite_parse(line, 0).expect("lite_parse");
|
let lite_block = lite_parse(line, 0).expect("lite_parse");
|
||||||
let block = classify_block(&lite_block, registry);
|
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]
|
#[test]
|
||||||
@ -267,7 +286,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 10),
|
completion_location(line, ®istry, 10),
|
||||||
Some(LocationType::Command),
|
vec![LocationType::Command],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +297,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 10),
|
completion_location(line, ®istry, 10),
|
||||||
Some(LocationType::Command),
|
vec![LocationType::Command],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +308,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 4),
|
completion_location(line, ®istry, 4),
|
||||||
Some(LocationType::Command),
|
vec![LocationType::Command],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +319,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 13),
|
completion_location(line, ®istry, 13),
|
||||||
Some(LocationType::Variable),
|
vec![LocationType::Variable],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +334,25 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 7),
|
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!(
|
assert_eq!(
|
||||||
completion_location(line, ®istry, 6),
|
completion_location(line, ®istry, 6),
|
||||||
Some(LocationType::Argument(Some("echo".to_string()), None)),
|
vec![LocationType::Argument(Some("echo".to_string()), None)],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,16 +20,20 @@ impl NuCompleter {
|
|||||||
Err(result) => result.partial,
|
Err(result) => result.partial,
|
||||||
};
|
};
|
||||||
|
|
||||||
let location = lite_block
|
let locations = lite_block
|
||||||
.map(|block| nu_parser::classify_block(&block, &nu_context.registry))
|
.map(|block| nu_parser::classify_block(&block, &nu_context.registry))
|
||||||
.and_then(|block| {
|
.map(|block| crate::completion::engine::completion_location(line, &block.block, pos))
|
||||||
crate::completion::engine::completion_location(line, &block.block, pos)
|
.unwrap_or_default();
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(location) = location {
|
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);
|
let partial = location.span.slice(line);
|
||||||
|
match location.item {
|
||||||
let suggestions = match location.item {
|
|
||||||
LocationType::Command => {
|
LocationType::Command => {
|
||||||
let command_completer = crate::completion::command::Completer {};
|
let command_completer = crate::completion::command::Completer {};
|
||||||
command_completer.complete(context, partial)
|
command_completer.complete(context, partial)
|
||||||
@ -50,11 +54,10 @@ impl NuCompleter {
|
|||||||
}
|
}
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(requote)
|
.map(requote)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
(location.span.start(), suggestions)
|
(pos, suggestions)
|
||||||
} else {
|
|
||||||
(pos, Vec::new())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user