From f5fad393d0f07eeb0c3c2e9bd8d151dac8b1b8f0 Mon Sep 17 00:00:00 2001 From: Jason Gedge Date: Wed, 16 Sep 2020 00:37:43 -0400 Subject: [PATCH] Refactor completion trait (#2555) * Remove completion code from help/value shells * Tweak the `Completer` trait for nushell. Previously, this trait was built around rustyline's completion traits, and for `Shell` instances. Now it is built for individual completers inside of nushell that will complete a specific location based on a partial string. For example, for completing a partially typed command in the command position. --- crates/nu-cli/src/completion/command.rs | 12 +++--- crates/nu-cli/src/completion/flag.rs | 12 +++--- crates/nu-cli/src/completion/mod.rs | 9 +--- crates/nu-cli/src/completion/path.rs | 17 ++++++-- crates/nu-cli/src/shell/completer.rs | 20 +++++---- crates/nu-cli/src/shell/help_shell.rs | 54 ------------------------ crates/nu-cli/src/shell/value_shell.rs | 56 +------------------------ 7 files changed, 40 insertions(+), 140 deletions(-) diff --git a/crates/nu-cli/src/completion/command.rs b/crates/nu-cli/src/completion/command.rs index b2f95426b..855148d50 100644 --- a/crates/nu-cli/src/completion/command.rs +++ b/crates/nu-cli/src/completion/command.rs @@ -3,13 +3,13 @@ use std::path::Path; use indexmap::set::IndexSet; -use crate::completion::{Context, Suggestion}; +use crate::completion::{Completer, Context, Suggestion}; use crate::context; -pub struct Completer; +pub struct CommandCompleter; -impl Completer { - pub fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec { +impl Completer for CommandCompleter { + fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec { let context: &context::Context = ctx.as_ref(); let mut commands: IndexSet = IndexSet::from_iter(context.registry.names()); @@ -33,8 +33,8 @@ impl Completer { .collect(); if partial != "" { - let path_completer = crate::completion::path::Completer; - let path_results = path_completer.path_suggestions(ctx, partial); + let path_completer = crate::completion::path::PathCompleter; + let path_results = path_completer.path_suggestions(partial); let iter = path_results.into_iter().filter_map(|path_suggestion| { let path = path_suggestion.path; if path.is_dir() || is_executable(&path) { diff --git a/crates/nu-cli/src/completion/flag.rs b/crates/nu-cli/src/completion/flag.rs index 65763e68c..8b2ffd183 100644 --- a/crates/nu-cli/src/completion/flag.rs +++ b/crates/nu-cli/src/completion/flag.rs @@ -1,13 +1,15 @@ -use crate::completion::{Context, Suggestion}; +use crate::completion::{Completer, Context, Suggestion}; use crate::context; -pub struct Completer; +pub struct FlagCompleter { + pub(crate) cmd: String, +} -impl Completer { - pub fn complete(&self, ctx: &Context<'_>, cmd: String, partial: &str) -> Vec { +impl Completer for FlagCompleter { + fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec { let context: &context::Context = ctx.as_ref(); - if let Some(cmd) = context.registry.get_command(&cmd) { + if let Some(cmd) = context.registry.get_command(&self.cmd) { let sig = cmd.signature(); let mut suggestions = Vec::new(); for (name, (named_type, _desc)) in sig.named.iter() { diff --git a/crates/nu-cli/src/completion/mod.rs b/crates/nu-cli/src/completion/mod.rs index 74953df6d..6b0131ac5 100644 --- a/crates/nu-cli/src/completion/mod.rs +++ b/crates/nu-cli/src/completion/mod.rs @@ -3,8 +3,6 @@ pub(crate) mod engine; pub(crate) mod flag; pub(crate) mod path; -use nu_errors::ShellError; - use crate::context; #[derive(Debug, Eq, PartialEq)] @@ -28,10 +26,5 @@ impl<'a> AsRef for Context<'a> { } pub trait Completer { - fn complete( - &self, - line: &str, - pos: usize, - ctx: &Context<'_>, - ) -> Result<(usize, Vec), ShellError>; + fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec; } diff --git a/crates/nu-cli/src/completion/path.rs b/crates/nu-cli/src/completion/path.rs index 9df28b6c3..ab58151df 100644 --- a/crates/nu-cli/src/completion/path.rs +++ b/crates/nu-cli/src/completion/path.rs @@ -1,18 +1,18 @@ use std::path::PathBuf; -use crate::completion::{Context, Suggestion}; +use crate::completion::{Completer, Context, Suggestion}; const SEP: char = std::path::MAIN_SEPARATOR; -pub struct Completer; +pub struct PathCompleter; pub struct PathSuggestion { pub(crate) path: PathBuf, pub(crate) suggestion: Suggestion, } -impl Completer { - pub fn path_suggestions(&self, _ctx: &Context<'_>, partial: &str) -> Vec { +impl PathCompleter { + pub fn path_suggestions(&self, partial: &str) -> Vec { let expanded = nu_parser::expand_ndots(partial); let expanded = expanded.as_ref(); @@ -71,3 +71,12 @@ impl Completer { } } } + +impl Completer for PathCompleter { + fn complete(&self, _ctx: &Context<'_>, partial: &str) -> Vec { + self.path_suggestions(partial) + .into_iter() + .map(|ps| ps.suggestion) + .collect() + } +} diff --git a/crates/nu-cli/src/shell/completer.rs b/crates/nu-cli/src/shell/completer.rs index 1e368a6b4..9b7566f75 100644 --- a/crates/nu-cli/src/shell/completer.rs +++ b/crates/nu-cli/src/shell/completer.rs @@ -1,5 +1,7 @@ -use crate::completion::path::PathSuggestion; -use crate::completion::{self, Suggestion}; +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; pub(crate) struct NuCompleter {} @@ -13,7 +15,7 @@ impl NuCompleter { pos: usize, context: &completion::Context, ) -> (usize, Vec) { - use crate::completion::engine::LocationType; + use completion::engine::LocationType; let nu_context: &context::Context = context.as_ref(); let lite_block = match nu_parser::lite_parse(line, 0) { @@ -23,7 +25,7 @@ impl NuCompleter { let locations = lite_block .map(|block| nu_parser::classify_block(&block, &nu_context.registry)) - .map(|block| crate::completion::engine::completion_location(line, &block.block, pos)) + .map(|block| completion::engine::completion_location(line, &block.block, pos)) .unwrap_or_default(); if locations.is_empty() { @@ -36,17 +38,17 @@ impl NuCompleter { let partial = location.span.slice(line); match location.item { LocationType::Command => { - let command_completer = crate::completion::command::Completer; + let command_completer = CommandCompleter; command_completer.complete(context, partial) } LocationType::Flag(cmd) => { - let flag_completer = crate::completion::flag::Completer; - flag_completer.complete(context, cmd, partial) + let flag_completer = FlagCompleter { cmd }; + flag_completer.complete(context, partial) } LocationType::Argument(cmd, _arg_name) => { - let path_completer = crate::completion::path::Completer; + let path_completer = PathCompleter; const QUOTE_CHARS: &[char] = &['\'', '"', '`']; @@ -71,7 +73,7 @@ impl NuCompleter { partial }; - let completed_paths = path_completer.path_suggestions(context, partial); + let completed_paths = path_completer.path_suggestions(partial); match cmd.as_deref().unwrap_or("") { "cd" => select_directory_suggestions(completed_paths), _ => completed_paths, diff --git a/crates/nu-cli/src/shell/help_shell.rs b/crates/nu-cli/src/shell/help_shell.rs index c7bffed72..317409f7b 100644 --- a/crates/nu-cli/src/shell/help_shell.rs +++ b/crates/nu-cli/src/shell/help_shell.rs @@ -6,7 +6,6 @@ use crate::commands::ls::LsArgs; use crate::commands::mkdir::MkdirArgs; use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::rm::RemoveArgs; -use crate::completion; use crate::prelude::*; use crate::shell::shell::Shell; @@ -230,56 +229,3 @@ impl Shell for HelpShell { )) } } - -impl completion::Completer for HelpShell { - fn complete( - &self, - line: &str, - pos: usize, - _ctx: &completion::Context<'_>, - ) -> Result<(usize, Vec), ShellError> { - let mut possible_completion = vec![]; - let commands = self.commands(); - for cmd in commands { - let Value { value, .. } = cmd; - for desc in value.data_descriptors() { - possible_completion.push(desc); - } - } - - let line_chars: Vec<_> = line.chars().collect(); - let mut replace_pos = pos; - while replace_pos > 0 { - if line_chars[replace_pos - 1] == ' ' { - break; - } - replace_pos -= 1; - } - - let mut completions = vec![]; - for command in possible_completion.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 { - completions.push(completion::Suggestion { - display: command.to_string(), - replacement: command.to_string(), - }); - } - } - Ok((replace_pos, completions)) - } -} diff --git a/crates/nu-cli/src/shell/value_shell.rs b/crates/nu-cli/src/shell/value_shell.rs index 1e62e560d..0604bc8ff 100644 --- a/crates/nu-cli/src/shell/value_shell.rs +++ b/crates/nu-cli/src/shell/value_shell.rs @@ -5,7 +5,6 @@ use crate::commands::ls::LsArgs; use crate::commands::mkdir::MkdirArgs; use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::rm::RemoveArgs; -use crate::completion; use crate::prelude::*; use crate::shell::shell::Shell; use crate::utils::ValueStructure; @@ -76,6 +75,8 @@ impl ValueShell { shell_entries } + // TODO make use of this in the new completion engine + #[allow(dead_code)] fn members(&self) -> VecDeque { self.members_under(Path::new(".")) } @@ -255,56 +256,3 @@ impl Shell for ValueShell { )) } } - -impl completion::Completer for ValueShell { - fn complete( - &self, - line: &str, - pos: usize, - _ctx: &completion::Context<'_>, - ) -> Result<(usize, Vec), ShellError> { - let mut possible_completion = vec![]; - let members = self.members(); - for member in members { - let Value { value, .. } = member; - for desc in value.data_descriptors() { - possible_completion.push(desc); - } - } - - let line_chars: Vec<_> = line.chars().collect(); - let mut replace_pos = pos; - while replace_pos > 0 { - if line_chars[replace_pos - 1] == ' ' { - break; - } - replace_pos -= 1; - } - - let mut completions = vec![]; - for command in possible_completion.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 { - completions.push(completion::Suggestion { - display: command.to_string(), - replacement: command.to_string(), - }); - } - } - Ok((replace_pos, completions)) - } -}