mirror of
https://github.com/nushell/nushell.git
synced 2025-04-02 20:27:11 +02:00
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:
parent
d19a5f4c2f
commit
f5fad393d0
@ -3,13 +3,13 @@ use std::path::Path;
|
|||||||
|
|
||||||
use indexmap::set::IndexSet;
|
use indexmap::set::IndexSet;
|
||||||
|
|
||||||
use crate::completion::{Context, Suggestion};
|
use crate::completion::{Completer, Context, Suggestion};
|
||||||
use crate::context;
|
use crate::context;
|
||||||
|
|
||||||
pub struct Completer;
|
pub struct CommandCompleter;
|
||||||
|
|
||||||
impl Completer {
|
impl Completer for CommandCompleter {
|
||||||
pub fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
|
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
|
||||||
let context: &context::Context = ctx.as_ref();
|
let context: &context::Context = ctx.as_ref();
|
||||||
let mut commands: IndexSet<String> = IndexSet::from_iter(context.registry.names());
|
let mut commands: IndexSet<String> = IndexSet::from_iter(context.registry.names());
|
||||||
|
|
||||||
@ -33,8 +33,8 @@ impl Completer {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if partial != "" {
|
if partial != "" {
|
||||||
let path_completer = crate::completion::path::Completer;
|
let path_completer = crate::completion::path::PathCompleter;
|
||||||
let path_results = path_completer.path_suggestions(ctx, partial);
|
let path_results = path_completer.path_suggestions(partial);
|
||||||
let iter = path_results.into_iter().filter_map(|path_suggestion| {
|
let iter = path_results.into_iter().filter_map(|path_suggestion| {
|
||||||
let path = path_suggestion.path;
|
let path = path_suggestion.path;
|
||||||
if path.is_dir() || is_executable(&path) {
|
if path.is_dir() || is_executable(&path) {
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
use crate::completion::{Context, Suggestion};
|
use crate::completion::{Completer, Context, Suggestion};
|
||||||
use crate::context;
|
use crate::context;
|
||||||
|
|
||||||
pub struct Completer;
|
pub struct FlagCompleter {
|
||||||
|
pub(crate) cmd: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Completer {
|
impl Completer for FlagCompleter {
|
||||||
pub fn complete(&self, ctx: &Context<'_>, cmd: String, partial: &str) -> Vec<Suggestion> {
|
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion> {
|
||||||
let context: &context::Context = ctx.as_ref();
|
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 sig = cmd.signature();
|
||||||
let mut suggestions = Vec::new();
|
let mut suggestions = Vec::new();
|
||||||
for (name, (named_type, _desc)) in sig.named.iter() {
|
for (name, (named_type, _desc)) in sig.named.iter() {
|
||||||
|
@ -3,8 +3,6 @@ pub(crate) mod engine;
|
|||||||
pub(crate) mod flag;
|
pub(crate) mod flag;
|
||||||
pub(crate) mod path;
|
pub(crate) mod path;
|
||||||
|
|
||||||
use nu_errors::ShellError;
|
|
||||||
|
|
||||||
use crate::context;
|
use crate::context;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
@ -28,10 +26,5 @@ impl<'a> AsRef<context::Context> for Context<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Completer {
|
pub trait Completer {
|
||||||
fn complete(
|
fn complete(&self, ctx: &Context<'_>, partial: &str) -> Vec<Suggestion>;
|
||||||
&self,
|
|
||||||
line: &str,
|
|
||||||
pos: usize,
|
|
||||||
ctx: &Context<'_>,
|
|
||||||
) -> Result<(usize, Vec<Suggestion>), ShellError>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::completion::{Context, Suggestion};
|
use crate::completion::{Completer, Context, Suggestion};
|
||||||
|
|
||||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
pub struct Completer;
|
pub struct PathCompleter;
|
||||||
|
|
||||||
pub struct PathSuggestion {
|
pub struct PathSuggestion {
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
pub(crate) suggestion: Suggestion,
|
pub(crate) suggestion: Suggestion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Completer {
|
impl PathCompleter {
|
||||||
pub fn path_suggestions(&self, _ctx: &Context<'_>, partial: &str) -> Vec<PathSuggestion> {
|
pub fn path_suggestions(&self, partial: &str) -> Vec<PathSuggestion> {
|
||||||
let expanded = nu_parser::expand_ndots(partial);
|
let expanded = nu_parser::expand_ndots(partial);
|
||||||
let expanded = expanded.as_ref();
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use crate::completion::path::PathSuggestion;
|
use crate::completion::command::CommandCompleter;
|
||||||
use crate::completion::{self, Suggestion};
|
use crate::completion::flag::FlagCompleter;
|
||||||
|
use crate::completion::path::{PathCompleter, PathSuggestion};
|
||||||
|
use crate::completion::{self, Completer, Suggestion};
|
||||||
use crate::context;
|
use crate::context;
|
||||||
|
|
||||||
pub(crate) struct NuCompleter {}
|
pub(crate) struct NuCompleter {}
|
||||||
@ -13,7 +15,7 @@ impl NuCompleter {
|
|||||||
pos: usize,
|
pos: usize,
|
||||||
context: &completion::Context,
|
context: &completion::Context,
|
||||||
) -> (usize, Vec<Suggestion>) {
|
) -> (usize, Vec<Suggestion>) {
|
||||||
use crate::completion::engine::LocationType;
|
use completion::engine::LocationType;
|
||||||
|
|
||||||
let nu_context: &context::Context = context.as_ref();
|
let nu_context: &context::Context = context.as_ref();
|
||||||
let lite_block = match nu_parser::lite_parse(line, 0) {
|
let lite_block = match nu_parser::lite_parse(line, 0) {
|
||||||
@ -23,7 +25,7 @@ impl NuCompleter {
|
|||||||
|
|
||||||
let locations = lite_block
|
let locations = lite_block
|
||||||
.map(|block| nu_parser::classify_block(&block, &nu_context.registry))
|
.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();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if locations.is_empty() {
|
if locations.is_empty() {
|
||||||
@ -36,17 +38,17 @@ impl NuCompleter {
|
|||||||
let partial = location.span.slice(line);
|
let partial = location.span.slice(line);
|
||||||
match location.item {
|
match location.item {
|
||||||
LocationType::Command => {
|
LocationType::Command => {
|
||||||
let command_completer = crate::completion::command::Completer;
|
let command_completer = CommandCompleter;
|
||||||
command_completer.complete(context, partial)
|
command_completer.complete(context, partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationType::Flag(cmd) => {
|
LocationType::Flag(cmd) => {
|
||||||
let flag_completer = crate::completion::flag::Completer;
|
let flag_completer = FlagCompleter { cmd };
|
||||||
flag_completer.complete(context, cmd, partial)
|
flag_completer.complete(context, partial)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationType::Argument(cmd, _arg_name) => {
|
LocationType::Argument(cmd, _arg_name) => {
|
||||||
let path_completer = crate::completion::path::Completer;
|
let path_completer = PathCompleter;
|
||||||
|
|
||||||
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
|
const QUOTE_CHARS: &[char] = &['\'', '"', '`'];
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ impl NuCompleter {
|
|||||||
partial
|
partial
|
||||||
};
|
};
|
||||||
|
|
||||||
let completed_paths = path_completer.path_suggestions(context, partial);
|
let completed_paths = path_completer.path_suggestions(partial);
|
||||||
match cmd.as_deref().unwrap_or("") {
|
match cmd.as_deref().unwrap_or("") {
|
||||||
"cd" => select_directory_suggestions(completed_paths),
|
"cd" => select_directory_suggestions(completed_paths),
|
||||||
_ => completed_paths,
|
_ => completed_paths,
|
||||||
|
@ -6,7 +6,6 @@ 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;
|
||||||
|
|
||||||
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,6 @@ 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;
|
||||||
@ -76,6 +75,8 @@ impl ValueShell {
|
|||||||
shell_entries
|
shell_entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO make use of this in the new completion engine
|
||||||
|
#[allow(dead_code)]
|
||||||
fn members(&self) -> VecDeque<Value> {
|
fn members(&self) -> VecDeque<Value> {
|
||||||
self.members_under(Path::new("."))
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user