mirror of
https://github.com/nushell/nushell
synced 2024-12-29 14:33:13 +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 cli;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod completion;
|
||||||
mod context;
|
mod context;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
mod deserializer;
|
mod deserializer;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue