Refactor completion trait (#2555)

* Remove completion code from help/value shells

* Tweak the `Completer` trait for nushell.

Previously, this trait was built around rustyline's completion traits, and for
`Shell` instances. Now it is built for individual completers inside of nushell
that will complete a specific location based on a partial string. For example,
for completing a partially typed command in the command position.
This commit is contained in:
Jason Gedge 2020-09-16 00:37:43 -04:00 committed by GitHub
parent d19a5f4c2f
commit f5fad393d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 40 additions and 140 deletions

View File

@ -3,13 +3,13 @@ use std::path::Path;
use indexmap::set::IndexSet;
use crate::completion::{Context, Suggestion};
use crate::completion::{Completer, Context, Suggestion};
use crate::context;
pub struct Completer;
pub struct CommandCompleter;
impl Completer {
pub fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
impl Completer for CommandCompleter {
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
let context: &context::Context = ctx.as_ref();
let mut commands: IndexSet<String> = IndexSet::from_iter(context.registry.names());
@ -33,8 +33,8 @@ impl Completer {
.collect();
if partial != "" {
let path_completer = crate::completion::path::Completer;
let path_results = path_completer.path_suggestions(ctx, partial);
let path_completer = crate::completion::path::PathCompleter;
let path_results = path_completer.path_suggestions(partial);
let iter = path_results.into_iter().filter_map(|path_suggestion| {
let path = path_suggestion.path;
if path.is_dir() || is_executable(&path) {

View File

@ -1,13 +1,15 @@
use crate::completion::{Context, Suggestion};
use crate::completion::{Completer, Context, Suggestion};
use crate::context;
pub struct Completer;
pub struct FlagCompleter {
pub(crate) cmd: String,
}
impl Completer {
pub fn complete(&self, ctx: &Context<'_>, cmd: String, partial: &str) -> Vec<Suggestion> {
impl Completer for FlagCompleter {
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
let context: &context::Context = ctx.as_ref();
if let Some(cmd) = context.registry.get_command(&cmd) {
if let Some(cmd) = context.registry.get_command(&self.cmd) {
let sig = cmd.signature();
let mut suggestions = Vec::new();
for (name, (named_type, _desc)) in sig.named.iter() {

View File

@ -3,8 +3,6 @@ pub(crate) mod engine;
pub(crate) mod flag;
pub(crate) mod path;
use nu_errors::ShellError;
use crate::context;
#[derive(Debug, Eq, PartialEq)]
@ -28,10 +26,5 @@ impl<'a> AsRef<context::Context> for Context<'a> {
}
pub trait Completer {
fn complete(
&self,
line: &str,
pos: usize,
ctx: &Context<'_>,
) -> Result<(usize, Vec<Suggestion>), ShellError>;
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion>;
}

View File

@ -1,18 +1,18 @@
use std::path::PathBuf;
use crate::completion::{Context, Suggestion};
use crate::completion::{Completer, Context, Suggestion};
const SEP: char = std::path::MAIN_SEPARATOR;
pub struct Completer;
pub struct PathCompleter;
pub struct PathSuggestion {
pub(crate) path: PathBuf,
pub(crate) suggestion: Suggestion,
}
impl Completer {
pub fn path_suggestions(&self, _ctx: &Context<'_>, partial: &str) -> Vec<PathSuggestion> {
impl PathCompleter {
pub fn path_suggestions(&self, partial: &str) -> Vec<PathSuggestion> {
let expanded = nu_parser::expand_ndots(partial);
let expanded = expanded.as_ref();
@ -71,3 +71,12 @@ impl Completer {
}
}
}
impl Completer for PathCompleter {
fn complete(&self, _ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
self.path_suggestions(partial)
.into_iter()
.map(|ps| ps.suggestion)
.collect()
}
}

View File

@ -1,5 +1,7 @@
use crate::completion::path::PathSuggestion;
use crate::completion::{self, Suggestion};
use crate::completion::command::CommandCompleter;
use crate::completion::flag::FlagCompleter;
use crate::completion::path::{PathCompleter, PathSuggestion};
use crate::completion::{self, Completer, Suggestion};
use crate::context;
pub(crate) struct NuCompleter {}
@ -13,7 +15,7 @@ impl NuCompleter {
pos: usize,
context: &completion::Context,
) -> (usize, Vec<Suggestion>) {
use crate::completion::engine::LocationType;
use completion::engine::LocationType;
let nu_context: &context::Context = context.as_ref();
let lite_block = match nu_parser::lite_parse(line, 0) {
@ -23,7 +25,7 @@ impl NuCompleter {
let locations = lite_block
.map(|block| nu_parser::classify_block(&block, &nu_context.registry))
.map(|block| crate::completion::engine::completion_location(line, &block.block, pos))
.map(|block| completion::engine::completion_location(line, &block.block, pos))
.unwrap_or_default();
if locations.is_empty() {
@ -36,17 +38,17 @@ impl NuCompleter {
let partial = location.span.slice(line);
match location.item {
LocationType::Command => {
let command_completer = crate::completion::command::Completer;
let command_completer = CommandCompleter;
command_completer.complete(context, partial)
}
LocationType::Flag(cmd) => {
let flag_completer = crate::completion::flag::Completer;
flag_completer.complete(context, cmd, partial)
let flag_completer = FlagCompleter { cmd };
flag_completer.complete(context, partial)
}
LocationType::Argument(cmd, _arg_name) => {
let path_completer = crate::completion::path::Completer;
let path_completer = PathCompleter;
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
@ -71,7 +73,7 @@ impl NuCompleter {
partial
};
let completed_paths = path_completer.path_suggestions(context, partial);
let completed_paths = path_completer.path_suggestions(partial);
match cmd.as_deref().unwrap_or("") {
"cd" => select_directory_suggestions(completed_paths),
_ => completed_paths,

View File

@ -6,7 +6,6 @@ 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;
@ -230,56 +229,3 @@ impl Shell for HelpShell {
))
}
}
impl completion::Completer for HelpShell {
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let mut possible_completion = vec![];
let commands = self.commands();
for cmd in commands {
let Value { value, .. } = cmd;
for desc in value.data_descriptors() {
possible_completion.push(desc);
}
}
let line_chars: Vec<_> = line.chars().collect();
let mut replace_pos = pos;
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
let mut completions = vec![];
for command in possible_completion.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(completion::Suggestion {
display: command.to_string(),
replacement: command.to_string(),
});
}
}
Ok((replace_pos, completions))
}
}

View File

@ -5,7 +5,6 @@ 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;
@ -76,6 +75,8 @@ impl ValueShell {
shell_entries
}
// TODO make use of this in the new completion engine
#[allow(dead_code)]
fn members(&self) -> VecDeque<Value> {
self.members_under(Path::new("."))
}
@ -255,56 +256,3 @@ impl Shell for ValueShell {
))
}
}
impl completion::Completer for ValueShell {
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &completion::Context<'_>,
) -> Result<(usize, Vec<completion::Suggestion>), ShellError> {
let mut possible_completion = vec![];
let members = self.members();
for member in members {
let Value { value, .. } = member;
for desc in value.data_descriptors() {
possible_completion.push(desc);
}
}
let line_chars: Vec<_> = line.chars().collect();
let mut replace_pos = pos;
while replace_pos > 0 {
if line_chars[replace_pos - 1] == ' ' {
break;
}
replace_pos -= 1;
}
let mut completions = vec![];
for command in possible_completion.iter() {
let mut pos = replace_pos;
let mut matched = true;
if pos < line_chars.len() {
for chr in command.chars() {
if line_chars[pos] != chr {
matched = false;
break;
}
pos += 1;
if pos == line_chars.len() {
break;
}
}
}
if matched {
completions.push(completion::Suggestion {
display: command.to_string(),
replacement: command.to_string(),
});
}
}
Ok((replace_pos, completions))
}
}