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 commands;
mod completion;
mod context;
pub mod data;
mod deserializer;

View file

@ -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::completion::Pair>), rustyline::error::ReadlineError> {
ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<completion::Suggestion>), 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<String> {
self.hinter.hint(line, pos, ctx)
fn hint(&self, line: &str, pos: usize, ctx: &completion::Context<'_>) -> Option<String> {
self.hinter.hint(line, pos, ctx.as_ref())
}
}
@ -860,28 +878,23 @@ fn is_hidden_dir(dir: impl AsRef<Path>) -> bool {
}
fn requote(
completions: Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError>,
) -> Result<(usize, Vec<rustyline::completion::Pair>), rustyline::error::ReadlineError> {
match completions {
Ok(items) => {
let mut new_items = Vec::with_capacity(items.0);
items: (usize, Vec<rustyline::completion::Pair>),
) -> (usize, Vec<rustyline::completion::Pair>) {
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)
}

View file

@ -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::completion::Pair>), rustyline::error::ReadlineError> {
let mut completions = vec![];
_ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<completion::Suggestion>), 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<String> {
fn hint(&self, _line: &str, _pos: usize, _ctx: &completion::Context<'_>) -> Option<String> {
None
}
}

View file

@ -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<rustyline::completion::Pair>), ReadlineError> {
self.context.shell_manager.complete(line, pos, ctx)
) -> Result<(usize, Vec<Self::Candidate>), 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<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::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<PathBuf>;
@ -42,13 +44,4 @@ pub trait Shell: std::fmt::Debug {
contents: &[u8],
name: Span,
) -> 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::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::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) {
{
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<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::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::completion::Pair>), rustyline::error::ReadlineError> {
let mut completions = vec![];
_ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<completion::Suggestion>), 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<String> {
fn hint(&self, _line: &str, _pos: usize, _context: &completion::Context<'_>) -> Option<String> {
None
}
}