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_parser::{flatten_expression, parse, trim_quotes};
use nu_parser::{flatten_expression, parse, trim_quotes, FlatShape};
use nu_protocol::{
ast::{Expr, Statement},
engine::{EngineState, Stack, StateWorkingSet},
@ -121,7 +121,7 @@ impl NuCompleter {
) -> Vec<(reedline::Span, String)> {
let prefix = working_set.get_span_contents(span);
let results = working_set
let mut results = working_set
.find_commands_by_prefix(prefix)
.into_iter()
.map(move |x| {
@ -132,7 +132,8 @@ impl NuCompleter {
},
String::from_utf8_lossy(&x).to_string(),
)
});
})
.collect::<Vec<_>>();
let prefix = working_set.get_span_contents(span);
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
.into_iter()
.chain(results_external.into_iter())
.collect()
}
fn completion_helper(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start();
let mut line = line.to_string();
line.insert(pos, 'a');
let pos = offset + pos;
let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
for stmt in output.stmts.into_iter() {
if let Statement::Pipeline(pipeline) = stmt {
for expr in pipeline.expressions {
let flattened = flatten_expression(&working_set, &expr);
for (flat_idx, flat) in flattened.into_iter().enumerate() {
if pos >= flat.0.start && pos <= flat.0.end {
let prefix = working_set.get_span_contents(flat.0);
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
for (flat_idx, flat) in flattened.iter().enumerate() {
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"$") {
return self.complete_variables(
&working_set,
prefix,
flat.0,
&prefix,
new_span,
offset,
);
}
@ -189,11 +204,11 @@ impl NuCompleter {
let mut named = named.long.as_bytes().to_vec();
named.insert(0, b'-');
named.insert(0, b'-');
if named.starts_with(prefix) {
if named.starts_with(&prefix) {
output.push((
reedline::Span {
start: flat.0.start - offset,
end: flat.0.end - offset,
start: new_span.start - offset,
end: new_span.end - offset,
},
String::from_utf8_lossy(&named).to_string(),
));
@ -205,7 +220,7 @@ impl NuCompleter {
match &flat.1 {
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(
&mut working_set,
@ -228,24 +243,25 @@ impl NuCompleter {
&self.engine_state,
&mut stack,
&block,
PipelineData::new(flat.0),
PipelineData::new(new_span),
);
let v: Vec<_> = match result {
Ok(pd) => pd
.into_iter()
.map(move |x| {
let s = x.as_string().expect(
"FIXME: better error handling for custom completions",
);
.filter_map(move |x| {
let s = x.as_string();
(
reedline::Span {
start: flat.0.start - offset,
end: flat.0.end - offset,
},
s,
)
match s {
Ok(s) => Some((
reedline::Span {
start: new_span.start - offset,
end: new_span.end - offset,
},
s,
)),
Err(_) => None,
}
})
.filter(|x| x.1.as_bytes().starts_with(&prefix))
.collect(),
@ -254,15 +270,50 @@ impl NuCompleter {
return v;
}
_ => {
let subcommands = self.complete_commands(
&working_set,
Span {
start: expr.span.start,
end: pos,
},
offset,
);
flat_shape => {
let commands =
if matches!(flat_shape, nu_parser::FlatShape::External)
|| matches!(
flat_shape,
nu_parser::FlatShape::InternalCall
)
|| ((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")
{
@ -274,19 +325,19 @@ impl NuCompleter {
"".to_string()
};
let preceding_byte = if flat.0.start > offset {
let preceding_byte = if new_span.start > offset {
working_set
.get_span_contents(Span {
start: flat.0.start - 1,
end: flat.0.start,
start: new_span.start - 1,
end: new_span.start,
})
.to_vec()
} else {
vec![]
};
let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(prefix).to_string();
return file_path_completion(flat.0, &prefix, &cwd)
// let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(&prefix).to_string();
let mut output = file_path_completion(new_span, &prefix, &cwd)
.into_iter()
.map(move |x| {
if flat_idx == 0 {
@ -326,25 +377,14 @@ impl NuCompleter {
)
})
.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,
end: outer_span.end,
},
FlatShape::Table,
FlatShape::Block,
))
} else {
None