diff --git a/crates/nu-parser/src/hir/syntax_shape.rs b/crates/nu-parser/src/hir/syntax_shape.rs index 98483eb1ad..04d89e93aa 100644 --- a/crates/nu-parser/src/hir/syntax_shape.rs +++ b/crates/nu-parser/src/hir/syntax_shape.rs @@ -182,10 +182,10 @@ pub trait SignatureRegistry { #[derive(Getters, new)] pub struct ExpandContext<'context> { #[get = "pub(crate)"] - registry: Box, + pub registry: Box, #[get = "pub(crate)"] - source: &'context Text, - homedir: Option, + pub source: &'context Text, + pub homedir: Option, } impl<'context> ExpandContext<'context> { diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 41c7cde5f5..05194a67eb 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -6,7 +6,9 @@ pub mod parse_command; pub use crate::commands::classified::{ClassifiedCommand, ClassifiedPipeline, InternalCommand}; pub use crate::commands::ExternalCommand; pub use crate::hir::syntax_shape::flat_shape::FlatShape; -pub use crate::hir::syntax_shape::{expand_syntax, ExpandSyntax, PipelineShape, SignatureRegistry}; +pub use crate::hir::syntax_shape::{ + expand_syntax, ExpandContext, ExpandSyntax, PipelineShape, SignatureRegistry, +}; pub use crate::hir::tokens_iterator::TokensIterator; pub use crate::parse::files::Files; pub use crate::parse::flag::Flag; diff --git a/src/cli.rs b/src/cli.rs index dbfd8641d0..4e0bcfc0e3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -587,7 +587,7 @@ async fn process_line(readline: Result, ctx: &mut Context } } -fn classify_pipeline( +pub fn classify_pipeline( pipeline: &TokenNode, context: &Context, source: &Text, diff --git a/src/shell/completer.rs b/src/shell/completer.rs index 27aeaed155..fac2af1c4a 100644 --- a/src/shell/completer.rs +++ b/src/shell/completer.rs @@ -1,6 +1,7 @@ use crate::context::CommandRegistry; use derive_new::new; +use nu_source::{HasSpan, Text}; use rustyline::completion::{Completer, FilenameCompleter}; #[derive(new)] @@ -18,27 +19,8 @@ impl NuCompleter { ) -> rustyline::Result<(usize, Vec)> { let commands: Vec = self.commands.names(); - let mut 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); - } - } - } - let line_chars: Vec<_> = line[..pos].chars().collect(); + let mut replace_pos = line_chars.len(); while replace_pos > 0 { if line_chars[replace_pos - 1] == ' ' { @@ -47,6 +29,33 @@ impl NuCompleter { replace_pos -= 1; } + let mut completions; + + // See if we're a flag + if pos > 0 && replace_pos < line_chars.len() && line_chars[replace_pos] == '-' { + completions = self.get_matching_arguments(&line_chars, line, replace_pos, pos); + } 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); + } + } + } + }; + for command in commands.iter() { let mut pos = replace_pos; let mut matched = true; @@ -73,4 +82,73 @@ impl NuCompleter { Ok((replace_pos, completions)) } + + fn get_matching_arguments( + &self, + line_chars: &[char], + line: &str, + replace_pos: usize, + pos: usize, + ) -> Vec { + let mut matching_arguments = vec![]; + + let mut line_copy = line.to_string(); + let substring = line_chars[replace_pos..pos].iter().collect::(); + let replace_string = (replace_pos..pos).map(|_| " ").collect::(); + 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 + } }