mirror of
https://github.com/nushell/nushell
synced 2024-12-28 22:13:10 +00:00
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:
parent
d8594a62c2
commit
9d24b440bb
8 changed files with 130 additions and 72 deletions
26
crates/nu-cli/src/completion.rs
Normal file
26
crates/nu-cli/src/completion.rs
Normal 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>;
|
||||
}
|
|
@ -15,6 +15,7 @@ extern crate quickcheck_macros;
|
|||
|
||||
mod cli;
|
||||
mod commands;
|
||||
mod completion;
|
||||
mod context;
|
||||
pub mod data;
|
||||
mod deserializer;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue