2019-11-21 15:33:14 +01:00
|
|
|
use crate::context::CommandRegistry;
|
|
|
|
|
2019-05-26 08:54:41 +02:00
|
|
|
use derive_new::new;
|
2019-12-08 07:42:43 +01:00
|
|
|
use nu_source::{HasSpan, Text};
|
2019-08-09 22:49:43 +02:00
|
|
|
use rustyline::completion::{Completer, FilenameCompleter};
|
2019-05-16 23:43:36 +02:00
|
|
|
|
2019-05-26 08:54:41 +02:00
|
|
|
#[derive(new)]
|
2019-08-29 13:08:28 +02:00
|
|
|
pub(crate) struct NuCompleter {
|
2019-05-18 04:30:57 +02:00
|
|
|
pub file_completer: FilenameCompleter,
|
2019-08-10 07:02:15 +02:00
|
|
|
pub commands: CommandRegistry,
|
2019-05-18 04:30:57 +02:00
|
|
|
}
|
2019-05-16 23:43:36 +02:00
|
|
|
|
2019-08-09 06:51:21 +02:00
|
|
|
impl NuCompleter {
|
|
|
|
pub fn complete(
|
2019-05-16 23:43:36 +02:00
|
|
|
&self,
|
2019-05-18 04:30:57 +02:00
|
|
|
line: &str,
|
|
|
|
pos: usize,
|
|
|
|
context: &rustyline::Context,
|
2019-08-09 21:42:23 +02:00
|
|
|
) -> rustyline::Result<(usize, Vec<rustyline::completion::Pair>)> {
|
2019-12-08 06:58:53 +01:00
|
|
|
let commands: Vec<String> = self.commands.names();
|
2019-07-15 07:40:27 +02:00
|
|
|
|
2019-09-02 20:06:25 +02:00
|
|
|
let line_chars: Vec<_> = line[..pos].chars().collect();
|
2019-12-08 07:23:31 +01:00
|
|
|
|
2019-09-02 20:06:25 +02:00
|
|
|
let mut replace_pos = line_chars.len();
|
2019-05-18 04:30:57 +02:00
|
|
|
while replace_pos > 0 {
|
|
|
|
if line_chars[replace_pos - 1] == ' ' {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
replace_pos -= 1;
|
|
|
|
}
|
|
|
|
|
2019-12-08 07:42:43 +01:00
|
|
|
let mut completions;
|
2019-12-08 06:58:53 +01:00
|
|
|
|
2019-12-08 07:23:31 +01:00
|
|
|
// See if we're a flag
|
2019-12-08 07:04:23 +01:00
|
|
|
if pos > 0 && line_chars[replace_pos] == '-' {
|
2019-12-08 07:42:43 +01:00
|
|
|
completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos);
|
2019-12-08 06:58:53 +01:00
|
|
|
} else {
|
|
|
|
completions = self.file_completer.complete(line, pos, context)?.1;
|
|
|
|
|
|
|
|
for completion in &mut completions {
|
|
|
|
if completion.replacement.contains("\\ ") {
|
|
|
|
completion.replacement = completion.replacement.replace("\\ ", " ");
|
|
|
|
}
|
|
|
|
if completion.replacement.contains("\\(") {
|
|
|
|
completion.replacement = completion.replacement.replace("\\(", "(");
|
|
|
|
}
|
|
|
|
|
|
|
|
if completion.replacement.contains(' ') || completion.replacement.contains('(') {
|
|
|
|
if !completion.replacement.starts_with('\"') {
|
|
|
|
completion.replacement = format!("\"{}", completion.replacement);
|
|
|
|
}
|
|
|
|
if !completion.replacement.ends_with('\"') {
|
|
|
|
completion.replacement = format!("{}\"", completion.replacement);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-18 16:06:01 +02:00
|
|
|
for command in commands.iter() {
|
|
|
|
let mut pos = replace_pos;
|
|
|
|
let mut matched = true;
|
|
|
|
if pos < line_chars.len() {
|
|
|
|
for chr in command.chars() {
|
|
|
|
if line_chars[pos] != chr {
|
|
|
|
matched = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
pos += 1;
|
|
|
|
if pos == line_chars.len() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if matched {
|
2019-08-10 07:02:15 +02:00
|
|
|
completions.push(rustyline::completion::Pair {
|
2019-06-09 19:52:56 +02:00
|
|
|
display: command.clone(),
|
|
|
|
replacement: command.clone(),
|
2019-05-18 16:06:01 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-18 04:30:57 +02:00
|
|
|
Ok((replace_pos, completions))
|
2019-05-16 23:43:36 +02:00
|
|
|
}
|
2019-12-08 07:42:43 +01:00
|
|
|
|
|
|
|
fn get_matching_arguments(
|
|
|
|
&self,
|
|
|
|
line_chars: &[char],
|
|
|
|
line: &str,
|
|
|
|
replace_pos: usize,
|
|
|
|
pos: usize,
|
|
|
|
) -> Vec<rustyline::completion::Pair> {
|
|
|
|
let mut matching_arguments = vec![];
|
|
|
|
|
|
|
|
let mut line_copy = line.to_string();
|
|
|
|
let substring = line_chars[replace_pos..pos].iter().collect::<String>();
|
|
|
|
let replace_string = (replace_pos..pos).map(|_| " ").collect::<String>();
|
|
|
|
line_copy.replace_range(replace_pos..pos, &replace_string);
|
|
|
|
|
|
|
|
match nu_parser::parse(&line_copy) {
|
|
|
|
Ok(val) => {
|
|
|
|
let source = Text::from(line);
|
|
|
|
let pipeline_list = vec![val.clone()];
|
|
|
|
let mut iterator =
|
|
|
|
nu_parser::TokensIterator::all(&pipeline_list, source.clone(), val.span());
|
|
|
|
|
|
|
|
let expand_context = nu_parser::ExpandContext {
|
|
|
|
homedir: None,
|
|
|
|
registry: Box::new(self.commands.clone()),
|
|
|
|
source: &source,
|
|
|
|
};
|
|
|
|
|
|
|
|
let result = nu_parser::expand_syntax(
|
|
|
|
&nu_parser::PipelineShape,
|
|
|
|
&mut iterator,
|
|
|
|
&expand_context,
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Ok(result) = result {
|
|
|
|
for command in result.commands.list {
|
|
|
|
match command {
|
|
|
|
nu_parser::ClassifiedCommand::Internal(
|
|
|
|
nu_parser::InternalCommand { args, .. },
|
|
|
|
) => {
|
|
|
|
if replace_pos >= args.span.start()
|
|
|
|
&& replace_pos <= args.span.end()
|
|
|
|
{
|
|
|
|
if let Some(named) = args.named {
|
|
|
|
for (name, _) in named.iter() {
|
|
|
|
let full_flag = format!("--{}", name);
|
|
|
|
|
|
|
|
if full_flag.starts_with(&substring) {
|
|
|
|
matching_arguments.push(
|
|
|
|
rustyline::completion::Pair {
|
|
|
|
display: full_flag.clone(),
|
|
|
|
replacement: full_flag,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
|
|
|
|
matching_arguments
|
|
|
|
}
|
2019-05-16 23:43:36 +02:00
|
|
|
}
|