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 <jonathandturner@users.noreply.github.com>
This commit is contained in:
Jason Gedge 2020-07-17 22:55:10 -04:00 committed by GitHub
parent d8594a62c2
commit 9d24b440bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 72 deletions

View file

@ -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<rustyline::Context<'a>> 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<Suggestion>), ShellError>;
fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option<String>;
}

View file

@ -15,6 +15,7 @@ extern crate quickcheck_macros;
mod cli; mod cli;
mod commands; mod commands;
mod completion;
mod context; mod context;
pub mod data; pub mod data;
mod deserializer; mod deserializer;

View file

@ -5,6 +5,7 @@ use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs; use crate::commands::mkdir::MkdirArgs;
use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::move_::mv::Arguments as MvArgs;
use crate::commands::rm::RemoveArgs; use crate::commands::rm::RemoveArgs;
use crate::completion;
use crate::data::dir_entry_dict; use crate::data::dir_entry_dict;
use crate::path::canonicalize; use crate::path::canonicalize;
use crate::prelude::*; use crate::prelude::*;
@ -737,13 +738,15 @@ impl Shell for FilesystemShell {
)), )),
} }
} }
}
impl completion::Completer for FilesystemShell {
fn complete( fn complete(
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
ctx: &rustyline::Context<'_>, ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> { ) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let expanded = expand_ndots(&line); let expanded = expand_ndots(&line);
// Find the first not-matching char position, if there is one // Find the first not-matching char position, if there is one
@ -764,11 +767,26 @@ impl Shell for FilesystemShell {
pos 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<String> { fn hint(&self, line: &str, pos: usize, ctx: &completion::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx) self.hinter.hint(line, pos, ctx.as_ref())
} }
} }
@ -860,28 +878,23 @@ fn is_hidden_dir(dir: impl AsRef<Path>) -> bool {
} }
fn requote( fn requote(
completions: Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError>, items: (usize, Vec<rustyline::completion::Pair>),
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> { ) -> (usize, Vec<rustyline::completion::Pair>) {
match completions { let mut new_items = Vec::with_capacity(items.1.len());
Ok(items) => {
let mut new_items = Vec::with_capacity(items.0);
for item in items.1 { for item in items.1 {
let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\')); let unescaped = rustyline::completion::unescape(&item.replacement, Some('\\'));
let maybe_quote = if unescaped != item.replacement { let maybe_quote = if unescaped != item.replacement {
"\"" "\""
} else { } else {
"" ""
}; };
new_items.push(rustyline::completion::Pair { new_items.push(rustyline::completion::Pair {
display: item.display, display: item.display,
replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote), replacement: format!("{}{}{}", maybe_quote, unescaped, maybe_quote),
}); });
}
Ok((items.0, new_items))
}
Err(err) => Err(err),
} }
(items.0, new_items)
} }

View file

@ -5,6 +5,7 @@ use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs; use crate::commands::mkdir::MkdirArgs;
use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::move_::mv::Arguments as MvArgs;
use crate::commands::rm::RemoveArgs; use crate::commands::rm::RemoveArgs;
use crate::completion;
use crate::data::command_dict; use crate::data::command_dict;
use crate::prelude::*; use crate::prelude::*;
use crate::shell::shell::Shell; use crate::shell::shell::Shell;
@ -228,15 +229,15 @@ impl Shell for HelpShell {
"save on help shell is not supported", "save on help shell is not supported",
)) ))
} }
}
impl completion::Completer for HelpShell {
fn complete( fn complete(
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
_ctx: &rustyline::Context<'_>, _ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> { ) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let mut completions = vec![];
let mut possible_completion = vec![]; let mut possible_completion = vec![];
let commands = self.commands(); let commands = self.commands();
for cmd in commands { for cmd in commands {
@ -255,6 +256,7 @@ impl Shell for HelpShell {
replace_pos -= 1; replace_pos -= 1;
} }
let mut completions = vec![];
for command in possible_completion.iter() { for command in possible_completion.iter() {
let mut pos = replace_pos; let mut pos = replace_pos;
let mut matched = true; let mut matched = true;
@ -272,7 +274,7 @@ impl Shell for HelpShell {
} }
if matched { if matched {
completions.push(rustyline::completion::Pair { completions.push(completion::Suggestion {
display: command.to_string(), display: command.to_string(),
replacement: command.to_string(), replacement: command.to_string(),
}); });
@ -281,7 +283,7 @@ impl Shell for HelpShell {
Ok((replace_pos, completions)) Ok((replace_pos, completions))
} }
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> { fn hint(&self, _line: &str, _pos: usize, _ctx: &completion::Context<'_>) -> Option<String> {
None None
} }
} }

View file

@ -1,11 +1,12 @@
use crate::completion::{self, Completer as _};
use crate::context::Context; use crate::context::Context;
use crate::shell::palette::{DefaultPalette, Palette}; use crate::shell::palette::{DefaultPalette, Palette};
use ansi_term::{Color, Style}; use ansi_term::{Color, Style};
use nu_parser::SignatureRegistry; use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape; use nu_protocol::hir::FlatShape;
use nu_source::{Spanned, Tag, Tagged}; use nu_source::{Spanned, Tag, Tagged};
use rustyline::completion::Completer; use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter; use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter; use rustyline::hint::Hinter;
use std::borrow::Cow::{self, Owned}; 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 { impl Completer for Helper {
type Candidate = rustyline::completion::Pair; type Candidate = completion::Suggestion;
fn complete( fn complete(
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
ctx: &rustyline::Context<'_>, ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), ReadlineError> { ) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
self.context.shell_manager.complete(line, pos, ctx) let ctx = completion::Context(ctx);
self.context
.shell_manager
.complete(line, pos, &ctx)
.map_err(|_| rustyline::error::ReadlineError::Eof)
} }
} }
impl Hinter for Helper { impl Hinter for Helper {
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> { fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.context.shell_manager.hint(line, pos, ctx) let ctx = completion::Context(ctx);
self.context.shell_manager.hint(line, pos, &ctx)
} }
} }

View file

@ -6,13 +6,15 @@ use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs; use crate::commands::mkdir::MkdirArgs;
use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::move_::mv::Arguments as MvArgs;
use crate::commands::rm::RemoveArgs; use crate::commands::rm::RemoveArgs;
use crate::completion;
use crate::prelude::*; use crate::prelude::*;
use crate::stream::OutputStream; use crate::stream::OutputStream;
use encoding_rs::Encoding; use encoding_rs::Encoding;
use nu_errors::ShellError; use nu_errors::ShellError;
use std::path::PathBuf; use std::path::PathBuf;
pub trait Shell: std::fmt::Debug { pub trait Shell: completion::Completer + std::fmt::Debug {
fn name(&self) -> String; fn name(&self) -> String;
fn homedir(&self) -> Option<PathBuf>; fn homedir(&self) -> Option<PathBuf>;
@ -42,13 +44,4 @@ pub trait Shell: std::fmt::Debug {
contents: &[u8], contents: &[u8],
name: Span, name: Span,
) -> Result<OutputStream, ShellError>; ) -> Result<OutputStream, ShellError>;
fn complete(
&self,
line: &str,
pos: usize,
ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError>;
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String>;
} }

View file

@ -6,10 +6,12 @@ use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs; use crate::commands::mkdir::MkdirArgs;
use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::move_::mv::Arguments as MvArgs;
use crate::commands::rm::RemoveArgs; use crate::commands::rm::RemoveArgs;
use crate::completion::{self, Completer};
use crate::prelude::*; use crate::prelude::*;
use crate::shell::filesystem_shell::FilesystemShell; use crate::shell::filesystem_shell::FilesystemShell;
use crate::shell::shell::Shell; use crate::shell::shell::Shell;
use crate::stream::OutputStream; use crate::stream::OutputStream;
use encoding_rs::Encoding; use encoding_rs::Encoding;
use nu_errors::ShellError; use nu_errors::ShellError;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -102,25 +104,6 @@ impl ShellManager {
self.shells.lock()[self.current_shell()].save(full_path, save_data, name) 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::completion::Pair>), 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<String> {
self.shells.lock()[self.current_shell()].hint(line, pos, ctx)
}
pub fn next(&mut self) { pub fn next(&mut self) {
{ {
let shell_len = self.shells.lock().len(); let shell_len = self.shells.lock().len();
@ -198,3 +181,24 @@ impl ShellManager {
shells[self.current_shell()].mv(args, name, &path) 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<completion::Suggestion>), ShellError> {
self.shells.lock()[self.current_shell()].complete(line, pos, ctx)
}
fn hint(
&self,
line: &str,
pos: usize,
ctx: &completion::Context<'_>,
//context: ExpandContext,
) -> Option<String> {
self.shells.lock()[self.current_shell()].hint(line, pos, ctx)
}
}

View file

@ -5,6 +5,7 @@ use crate::commands::ls::LsArgs;
use crate::commands::mkdir::MkdirArgs; use crate::commands::mkdir::MkdirArgs;
use crate::commands::move_::mv::Arguments as MvArgs; use crate::commands::move_::mv::Arguments as MvArgs;
use crate::commands::rm::RemoveArgs; use crate::commands::rm::RemoveArgs;
use crate::completion;
use crate::prelude::*; use crate::prelude::*;
use crate::shell::shell::Shell; use crate::shell::shell::Shell;
use crate::utils::ValueStructure; use crate::utils::ValueStructure;
@ -253,15 +254,15 @@ impl Shell for ValueShell {
"save on help shell is not supported", "save on help shell is not supported",
)) ))
} }
}
impl completion::Completer for ValueShell {
fn complete( fn complete(
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
_ctx: &rustyline::Context<'_>, _ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> { ) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let mut completions = vec![];
let mut possible_completion = vec![]; let mut possible_completion = vec![];
let members = self.members(); let members = self.members();
for member in members { for member in members {
@ -280,6 +281,7 @@ impl Shell for ValueShell {
replace_pos -= 1; replace_pos -= 1;
} }
let mut completions = vec![];
for command in possible_completion.iter() { for command in possible_completion.iter() {
let mut pos = replace_pos; let mut pos = replace_pos;
let mut matched = true; let mut matched = true;
@ -297,7 +299,7 @@ impl Shell for ValueShell {
} }
if matched { if matched {
completions.push(rustyline::completion::Pair { completions.push(completion::Suggestion {
display: command.to_string(), display: command.to_string(),
replacement: command.to_string(), replacement: command.to_string(),
}); });
@ -306,7 +308,7 @@ impl Shell for ValueShell {
Ok((replace_pos, completions)) Ok((replace_pos, completions))
} }
fn hint(&self, _line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<String> { fn hint(&self, _line: &str, _pos: usize, _context: &completion::Context<'_>) -> Option<String> {
None None
} }
} }