From 9d24b440bb5fade5ff1ebc7e620eb4bfe01bccb5 Mon Sep 17 00:00:00 2001 From: Jason Gedge Date: Fri, 17 Jul 2020 22:55:10 -0400 Subject: [PATCH] Introduce completion abstractions to nushell. (#2198) * Introduce completion abstractions to nushell. Currently, we rely on rustyline's completion structures. By abstracting this away, we are more flexible to introduce someone elses completion engine, or our own. * Update value_shell.rs Co-authored-by: Jonathan Turner --- crates/nu-cli/src/completion.rs | 26 +++++++++ crates/nu-cli/src/lib.rs | 1 + crates/nu-cli/src/shell/filesystem_shell.rs | 65 ++++++++++++--------- crates/nu-cli/src/shell/help_shell.rs | 14 +++-- crates/nu-cli/src/shell/helper.rs | 27 +++++++-- crates/nu-cli/src/shell/shell.rs | 13 +---- crates/nu-cli/src/shell/shell_manager.rs | 42 +++++++------ crates/nu-cli/src/shell/value_shell.rs | 14 +++-- 8 files changed, 130 insertions(+), 72 deletions(-) create mode 100644 crates/nu-cli/src/completion.rs diff --git a/crates/nu-cli/src/completion.rs b/crates/nu-cli/src/completion.rs new file mode 100644 index 0000000000..bde18576b0 --- /dev/null +++ b/crates/nu-cli/src/completion.rs @@ -0,0 +1,26 @@ +use nu_errors::ShellError; + +#[derive(Debug, Eq, PartialEq)] +pub struct Suggestion { + pub display: String, + pub replacement: String, +} + +pub struct Context<'a>(pub &'a rustyline::Context<'a>); + +impl<'a> AsRef> for Context<'a> { + fn as_ref(&self) -> &rustyline::Context<'a> { + self.0 + } +} + +pub trait Completer { + fn complete( + &self, + line: &str, + pos: usize, + ctx: &Context<'_>, + ) -> Result<(usize, Vec), ShellError>; + + fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option; +} diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index 7b32571033..543fceace6 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -15,6 +15,7 @@ extern crate quickcheck_macros; mod cli; mod commands; +mod completion; mod context; pub mod data; mod deserializer; diff --git a/crates/nu-cli/src/shell/filesystem_shell.rs b/crates/nu-cli/src/shell/filesystem_shell.rs index 343da5db33..59f1e1a7d3 100644 --- a/crates/nu-cli/src/shell/filesystem_shell.rs +++ b/crates/nu-cli/src/shell/filesystem_shell.rs @@ -5,6 +5,7 @@ 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::data::dir_entry_dict; use crate::path::canonicalize; use crate::prelude::*; @@ -737,13 +738,15 @@ impl Shell for FilesystemShell { )), } } +} +impl completion::Completer for FilesystemShell { fn complete( &self, line: &str, pos: usize, - ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), rustyline::error::ReadlineError> { + ctx: &completion::Context<'_>, + ) -> Result<(usize, Vec), ShellError> { let expanded = expand_ndots(&line); // Find the first not-matching char position, if there is one @@ -764,11 +767,26 @@ impl Shell for FilesystemShell { pos }; - requote(self.completer.complete(&expanded, pos, ctx)) + self.completer + .complete(&expanded, pos, ctx.as_ref()) + .map_err(|e| ShellError::untagged_runtime_error(format!("{}", e))) + .map(requote) + .map(|(pos, completions)| { + ( + pos, + completions + .into_iter() + .map(|pair| completion::Suggestion { + display: pair.display, + replacement: pair.replacement, + }) + .collect(), + ) + }) } - fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { - self.hinter.hint(line, pos, ctx) + fn hint(&self, line: &str, pos: usize, ctx: &completion::Context<'_>) -> Option { + self.hinter.hint(line, pos, ctx.as_ref()) } } @@ -860,28 +878,23 @@ fn is_hidden_dir(dir: impl AsRef) -> bool { } fn requote( - completions: Result<(usize, Vec), rustyline::error::ReadlineError>, -) -> Result<(usize, Vec), rustyline::error::ReadlineError> { - match completions { - Ok(items) => { - let mut new_items = Vec::with_capacity(items.0); + items: (usize, Vec), +) -> (usize, Vec) { + let mut new_items = Vec::with_capacity(items.1.len()); - for item in items.1 { - let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\')); - let maybe_quote = if unescaped != item.replacement { - "\"" - } else { - "" - }; + for item in items.1 { + let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\')); + let maybe_quote = if unescaped != item.replacement { + "\"" + } else { + "" + }; - new_items.push(rustyline::completion::Pair { - display: item.display, - replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote), - }); - } - - Ok((items.0, new_items)) - } - Err(err) => Err(err), + new_items.push(rustyline::completion::Pair { + display: item.display, + replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote), + }); } + + (items.0, new_items) } diff --git a/crates/nu-cli/src/shell/help_shell.rs b/crates/nu-cli/src/shell/help_shell.rs index 5795962517..5ed55220ba 100644 --- a/crates/nu-cli/src/shell/help_shell.rs +++ b/crates/nu-cli/src/shell/help_shell.rs @@ -5,6 +5,7 @@ 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::data::command_dict; use crate::prelude::*; use crate::shell::shell::Shell; @@ -228,15 +229,15 @@ impl Shell for HelpShell { "save on help shell is not supported", )) } +} +impl completion::Completer for HelpShell { fn complete( &self, line: &str, pos: usize, - _ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), rustyline::error::ReadlineError> { - let mut completions = vec![]; - + _ctx: &completion::Context<'_>, + ) -> Result<(usize, Vec), ShellError> { let mut possible_completion = vec![]; let commands = self.commands(); for cmd in commands { @@ -255,6 +256,7 @@ impl Shell for HelpShell { replace_pos -= 1; } + let mut completions = vec![]; for command in possible_completion.iter() { let mut pos = replace_pos; let mut matched = true; @@ -272,7 +274,7 @@ impl Shell for HelpShell { } if matched { - completions.push(rustyline::completion::Pair { + completions.push(completion::Suggestion { display: command.to_string(), replacement: command.to_string(), }); @@ -281,7 +283,7 @@ impl Shell for HelpShell { Ok((replace_pos, completions)) } - fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option { + fn hint(&self, _line: &str, _pos: usize, _ctx: &completion::Context<'_>) -> Option { None } } diff --git a/crates/nu-cli/src/shell/helper.rs b/crates/nu-cli/src/shell/helper.rs index c838313724..d6534bb575 100644 --- a/crates/nu-cli/src/shell/helper.rs +++ b/crates/nu-cli/src/shell/helper.rs @@ -1,11 +1,12 @@ +use crate::completion::{self, Completer as _}; use crate::context::Context; use crate::shell::palette::{DefaultPalette, Palette}; + use ansi_term::{Color, Style}; use nu_parser::SignatureRegistry; use nu_protocol::hir::FlatShape; use nu_source::{Spanned, Tag, Tagged}; use rustyline::completion::Completer; -use rustyline::error::ReadlineError; use rustyline::highlight::Highlighter; use rustyline::hint::Hinter; use std::borrow::Cow::{self, Owned}; @@ -24,21 +25,37 @@ impl Helper { } } +impl rustyline::completion::Candidate for completion::Suggestion { + fn display(&self) -> &str { + &self.display + } + + fn replacement(&self) -> &str { + &self.replacement + } +} + impl Completer for Helper { - type Candidate = rustyline::completion::Pair; + type Candidate = completion::Suggestion; + fn complete( &self, line: &str, pos: usize, ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), ReadlineError> { - self.context.shell_manager.complete(line, pos, ctx) + ) -> Result<(usize, Vec), rustyline::error::ReadlineError> { + let ctx = completion::Context(ctx); + self.context + .shell_manager + .complete(line, pos, &ctx) + .map_err(|_| rustyline::error::ReadlineError::Eof) } } impl Hinter for Helper { fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option { - self.context.shell_manager.hint(line, pos, ctx) + let ctx = completion::Context(ctx); + self.context.shell_manager.hint(line, pos, &ctx) } } diff --git a/crates/nu-cli/src/shell/shell.rs b/crates/nu-cli/src/shell/shell.rs index 36a3e337f7..82dc3d6780 100644 --- a/crates/nu-cli/src/shell/shell.rs +++ b/crates/nu-cli/src/shell/shell.rs @@ -6,13 +6,15 @@ 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::stream::OutputStream; + use encoding_rs::Encoding; use nu_errors::ShellError; use std::path::PathBuf; -pub trait Shell: std::fmt::Debug { +pub trait Shell: completion::Completer + std::fmt::Debug { fn name(&self) -> String; fn homedir(&self) -> Option; @@ -42,13 +44,4 @@ pub trait Shell: std::fmt::Debug { contents: &[u8], name: Span, ) -> Result; - - fn complete( - &self, - line: &str, - pos: usize, - ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), rustyline::error::ReadlineError>; - - fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option; } diff --git a/crates/nu-cli/src/shell/shell_manager.rs b/crates/nu-cli/src/shell/shell_manager.rs index 325540fd95..c5c29bb852 100644 --- a/crates/nu-cli/src/shell/shell_manager.rs +++ b/crates/nu-cli/src/shell/shell_manager.rs @@ -6,10 +6,12 @@ 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::{self, Completer}; use crate::prelude::*; use crate::shell::filesystem_shell::FilesystemShell; use crate::shell::shell::Shell; use crate::stream::OutputStream; + use encoding_rs::Encoding; use nu_errors::ShellError; use parking_lot::Mutex; @@ -102,25 +104,6 @@ impl ShellManager { self.shells.lock()[self.current_shell()].save(full_path, save_data, name) } - pub fn complete( - &self, - line: &str, - pos: usize, - ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), rustyline::error::ReadlineError> { - self.shells.lock()[self.current_shell()].complete(line, pos, ctx) - } - - pub fn hint( - &self, - line: &str, - pos: usize, - ctx: &rustyline::Context<'_>, - //context: ExpandContext, - ) -> Option { - self.shells.lock()[self.current_shell()].hint(line, pos, ctx) - } - pub fn next(&mut self) { { let shell_len = self.shells.lock().len(); @@ -198,3 +181,24 @@ impl ShellManager { shells[self.current_shell()].mv(args, name, &path) } } + +impl Completer for ShellManager { + fn complete( + &self, + line: &str, + pos: usize, + ctx: &completion::Context<'_>, + ) -> Result<(usize, Vec), ShellError> { + self.shells.lock()[self.current_shell()].complete(line, pos, ctx) + } + + fn hint( + &self, + line: &str, + pos: usize, + ctx: &completion::Context<'_>, + //context: ExpandContext, + ) -> Option { + self.shells.lock()[self.current_shell()].hint(line, pos, ctx) + } +} diff --git a/crates/nu-cli/src/shell/value_shell.rs b/crates/nu-cli/src/shell/value_shell.rs index 08ad6461a7..993aec70e6 100644 --- a/crates/nu-cli/src/shell/value_shell.rs +++ b/crates/nu-cli/src/shell/value_shell.rs @@ -5,6 +5,7 @@ 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; @@ -253,15 +254,15 @@ impl Shell for ValueShell { "save on help shell is not supported", )) } +} +impl completion::Completer for ValueShell { fn complete( &self, line: &str, pos: usize, - _ctx: &rustyline::Context<'_>, - ) -> Result<(usize, Vec), rustyline::error::ReadlineError> { - let mut completions = vec![]; - + _ctx: &completion::Context<'_>, + ) -> Result<(usize, Vec), ShellError> { let mut possible_completion = vec![]; let members = self.members(); for member in members { @@ -280,6 +281,7 @@ impl Shell for ValueShell { replace_pos -= 1; } + let mut completions = vec![]; for command in possible_completion.iter() { let mut pos = replace_pos; let mut matched = true; @@ -297,7 +299,7 @@ impl Shell for ValueShell { } if matched { - completions.push(rustyline::completion::Pair { + completions.push(completion::Suggestion { display: command.to_string(), replacement: command.to_string(), }); @@ -306,7 +308,7 @@ impl Shell for ValueShell { Ok((replace_pos, completions)) } - fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option { + fn hint(&self, _line: &str, _pos: usize, _context: &completion::Context<'_>) -> Option { None } }