Improve completions with no starting characters (#4433)

* Improve completions with no starting characters

* Fix subexpressions, crashes, and differentiate externals
This commit is contained in:
JT 2022-02-12 10:04:10 -05:00 committed by GitHub
parent 0256e42e3b
commit cc171b6ad4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 60 deletions

View File

@ -1,5 +1,5 @@
use nu_engine::eval_block; use nu_engine::eval_block;
use nu_parser::{flatten_expression, parse, trim_quotes}; use nu_parser::{flatten_expression, parse, trim_quotes, FlatShape};
use nu_protocol::{ use nu_protocol::{
ast::{Expr, Statement}, ast::{Expr, Statement},
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
@ -121,7 +121,7 @@ impl NuCompleter {
) -> Vec<(reedline::Span, String)> { ) -> Vec<(reedline::Span, String)> {
let prefix = working_set.get_span_contents(span); let prefix = working_set.get_span_contents(span);
let results = working_set let mut results = working_set
.find_commands_by_prefix(prefix) .find_commands_by_prefix(prefix)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| {
@ -132,7 +132,8 @@ impl NuCompleter {
}, },
String::from_utf8_lossy(&x).to_string(), String::from_utf8_lossy(&x).to_string(),
) )
}); })
.collect::<Vec<_>>();
let prefix = working_set.get_span_contents(span); let prefix = working_set.get_span_contents(span);
let prefix = String::from_utf8_lossy(prefix).to_string(); let prefix = String::from_utf8_lossy(prefix).to_string();
@ -149,31 +150,45 @@ impl NuCompleter {
) )
}); });
for external in results_external {
if results.contains(&external) {
results.push((external.0, format!("^{}", external.1)))
} else {
results.push(external)
}
}
results results
.into_iter()
.chain(results_external.into_iter())
.collect()
} }
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start(); let offset = working_set.next_span_start();
let mut line = line.to_string();
line.insert(pos, 'a');
let pos = offset + pos; let pos = offset + pos;
let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
for stmt in output.stmts.into_iter() { for stmt in output.stmts.into_iter() {
if let Statement::Pipeline(pipeline) = stmt { if let Statement::Pipeline(pipeline) = stmt {
for expr in pipeline.expressions { for expr in pipeline.expressions {
let flattened = flatten_expression(&working_set, &expr); let flattened: Vec<_> = flatten_expression(&working_set, &expr);
for (flat_idx, flat) in flattened.into_iter().enumerate() {
if pos >= flat.0.start && pos <= flat.0.end { for (flat_idx, flat) in flattened.iter().enumerate() {
let prefix = working_set.get_span_contents(flat.0); if pos >= flat.0.start && pos < flat.0.end {
let new_span = Span {
start: flat.0.start,
end: flat.0.end - 1,
};
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - flat.0.start);
if prefix.starts_with(b"$") { if prefix.starts_with(b"$") {
return self.complete_variables( return self.complete_variables(
&working_set, &working_set,
prefix, &prefix,
flat.0, new_span,
offset, offset,
); );
} }
@ -189,11 +204,11 @@ impl NuCompleter {
let mut named = named.long.as_bytes().to_vec(); let mut named = named.long.as_bytes().to_vec();
named.insert(0, b'-'); named.insert(0, b'-');
named.insert(0, b'-'); named.insert(0, b'-');
if named.starts_with(prefix) { if named.starts_with(&prefix) {
output.push(( output.push((
reedline::Span { reedline::Span {
start: flat.0.start - offset, start: new_span.start - offset,
end: flat.0.end - offset, end: new_span.end - offset,
}, },
String::from_utf8_lossy(&named).to_string(), String::from_utf8_lossy(&named).to_string(),
)); ));
@ -205,7 +220,7 @@ impl NuCompleter {
match &flat.1 { match &flat.1 {
nu_parser::FlatShape::Custom(custom_completion) => { nu_parser::FlatShape::Custom(custom_completion) => {
let prefix = working_set.get_span_contents(flat.0).to_vec(); //let prefix = working_set.get_span_contents(flat.0).to_vec();
let (block, ..) = parse( let (block, ..) = parse(
&mut working_set, &mut working_set,
@ -228,24 +243,25 @@ impl NuCompleter {
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(flat.0), PipelineData::new(new_span),
); );
let v: Vec<_> = match result { let v: Vec<_> = match result {
Ok(pd) => pd Ok(pd) => pd
.into_iter() .into_iter()
.map(move |x| { .filter_map(move |x| {
let s = x.as_string().expect( let s = x.as_string();
"FIXME: better error handling for custom completions",
);
( match s {
reedline::Span { Ok(s) => Some((
start: flat.0.start - offset, reedline::Span {
end: flat.0.end - offset, start: new_span.start - offset,
}, end: new_span.end - offset,
s, },
) s,
)),
Err(_) => None,
}
}) })
.filter(|x| x.1.as_bytes().starts_with(&prefix)) .filter(|x| x.1.as_bytes().starts_with(&prefix))
.collect(), .collect(),
@ -254,15 +270,50 @@ impl NuCompleter {
return v; return v;
} }
_ => { flat_shape => {
let subcommands = self.complete_commands( let commands =
&working_set, if matches!(flat_shape, nu_parser::FlatShape::External)
Span { || matches!(
start: expr.span.start, flat_shape,
end: pos, nu_parser::FlatShape::InternalCall
}, )
offset, || ((new_span.end - new_span.start) == 0)
); {
// we're in a gap or at a command
self.complete_commands(&working_set, new_span, offset)
} else {
vec![]
};
let last = flattened
.iter()
.rev()
.skip_while(|x| x.0.end > pos)
.take_while(|x| {
matches!(
x.1,
FlatShape::InternalCall
| FlatShape::External
| FlatShape::ExternalArg
| FlatShape::Literal
| FlatShape::String
)
})
.last();
// The last item here would be the earliest shape that could possible by part of this subcommand
let subcommands = if let Some(last) = last {
self.complete_commands(
&working_set,
Span {
start: last.0.start,
end: pos,
},
offset,
)
} else {
vec![]
};
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD")
{ {
@ -274,19 +325,19 @@ impl NuCompleter {
"".to_string() "".to_string()
}; };
let preceding_byte = if flat.0.start > offset { let preceding_byte = if new_span.start > offset {
working_set working_set
.get_span_contents(Span { .get_span_contents(Span {
start: flat.0.start - 1, start: new_span.start - 1,
end: flat.0.start, end: new_span.start,
}) })
.to_vec() .to_vec()
} else { } else {
vec![] vec![]
}; };
let prefix = working_set.get_span_contents(flat.0); // let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(prefix).to_string(); let prefix = String::from_utf8_lossy(&prefix).to_string();
return file_path_completion(flat.0, &prefix, &cwd) let mut output = file_path_completion(new_span, &prefix, &cwd)
.into_iter() .into_iter()
.map(move |x| { .map(move |x| {
if flat_idx == 0 { if flat_idx == 0 {
@ -326,25 +377,14 @@ impl NuCompleter {
) )
}) })
.chain(subcommands.into_iter()) .chain(subcommands.into_iter())
.collect(); .chain(commands.into_iter())
.collect::<Vec<_>>();
output.dedup_by(|a, b| a.1 == b.1);
return output;
} }
} }
} }
// If we get here, let's just check to see if we can complete a subcommand
// Check for subcommands
let subcommands = self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
);
if !subcommands.is_empty() {
return subcommands;
}
} }
} }
} }

View File

@ -121,7 +121,7 @@ pub fn flatten_expression(
start: last.0.end, start: last.0.end,
end: outer_span.end, end: outer_span.end,
}, },
FlatShape::Table, FlatShape::Block,
)) ))
} else { } else {
None None