use crate::completion::command::CommandCompleter; use crate::completion::flag::FlagCompleter; use crate::completion::path::{PathCompleter, PathSuggestion}; use crate::completion::{self, Completer, Suggestion}; use crate::context; use std::borrow::Cow; pub(crate) struct NuCompleter {} impl NuCompleter {} impl NuCompleter { pub fn complete( &self, line: &str, pos: usize, context: &completion::Context, ) -> (usize, Vec) { use completion::engine::LocationType; let nu_context: &context::Context = context.as_ref(); let lite_block = match nu_parser::lite_parse(line, 0) { Ok(block) => Some(block), Err(result) => result.partial, }; let locations = lite_block .map(|block| nu_parser::classify_block(&block, &nu_context.registry)) .map(|block| completion::engine::completion_location(line, &block.block, pos)) .unwrap_or_default(); 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); match location.item { LocationType::Command => { let command_completer = CommandCompleter; command_completer.complete(context, partial) } LocationType::Flag(cmd) => { let flag_completer = FlagCompleter { cmd }; flag_completer.complete(context, partial) } LocationType::Argument(cmd, _arg_name) => { let path_completer = PathCompleter; const QUOTE_CHARS: &[char] = &['\'', '"', '`']; // TODO Find a better way to deal with quote chars. Can the completion // engine relay this back to us? Maybe have two spans: inner and // outer. The former is what we want to complete, the latter what // we'd need to replace. let (quote_char, partial) = if partial.starts_with(QUOTE_CHARS) { let (head, tail) = partial.split_at(1); (Some(head), tail) } else { (None, partial) }; let partial = if let Some(quote_char) = quote_char { if partial.ends_with(quote_char) { &partial[..partial.len() - 1] } else { partial } } else { partial }; let completed_paths = path_completer.path_suggestions(partial); match cmd.as_deref().unwrap_or("") { "cd" => select_directory_suggestions(completed_paths), _ => completed_paths, } .into_iter() .map(|s| Suggestion { replacement: requote(s.suggestion.replacement), display: s.suggestion.display, }) .collect() } LocationType::Variable => Vec::new(), } }) .collect(); (pos, suggestions) } } } fn select_directory_suggestions(completed_paths: Vec) -> Vec { completed_paths .into_iter() .filter(|suggestion| { suggestion .path .metadata() .map(|md| md.is_dir()) .unwrap_or(false) }) .collect() } fn requote(orig_value: String) -> String { let value: Cow = rustyline::completion::unescape(&orig_value, Some('\\')); let mut quotes = vec!['"', '\'', '`']; let mut should_quote = false; for c in value.chars() { if c.is_whitespace() { should_quote = true; } else if let Some(index) = quotes.iter().position(|q| *q == c) { should_quote = true; quotes.swap_remove(index); } } if should_quote { if quotes.is_empty() { // TODO we don't really have an escape character, so there isn't a great option right // now. One possibility is `{{$(char backtick)}}` value.to_string() } else { let quote = quotes[0]; format!("{}{}{}", quote, value, quote) } } else { value.to_string() } }