Extract completions into subcrate. (#3631)

This commit is contained in:
Andrés N. Robalino 2021-06-16 15:20:01 -05:00 committed by GitHub
parent 04c0e94349
commit 7c8fb060f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 160 additions and 110 deletions

19
Cargo.lock generated
View file

@ -3233,6 +3233,7 @@ dependencies = [
"itertools", "itertools",
"nu-cli", "nu-cli",
"nu-command", "nu-command",
"nu-completion",
"nu-data", "nu-data",
"nu-engine", "nu-engine",
"nu-errors", "nu-errors",
@ -3318,6 +3319,7 @@ dependencies = [
"meval", "meval",
"nu-ansi-term", "nu-ansi-term",
"nu-command", "nu-command",
"nu-completion",
"nu-data", "nu-data",
"nu-engine", "nu-engine",
"nu-errors", "nu-errors",
@ -3476,6 +3478,23 @@ dependencies = [
"zip", "zip",
] ]
[[package]]
name = "nu-completion"
version = "0.32.1"
dependencies = [
"directories-next",
"dirs-next",
"indexmap",
"nu-data",
"nu-engine",
"nu-errors",
"nu-parser",
"nu-protocol",
"nu-source",
"nu-test-support",
"rustyline",
]
[[package]] [[package]]
name = "nu-data" name = "nu-data"
version = "0.32.1" version = "0.32.1"

View file

@ -20,6 +20,7 @@ members = ["crates/*/"]
[dependencies] [dependencies]
nu-cli = { version = "0.32.1", path = "./crates/nu-cli", default-features = false } nu-cli = { version = "0.32.1", path = "./crates/nu-cli", default-features = false }
nu-command = { version = "0.32.1", path = "./crates/nu-command" } nu-command = { version = "0.32.1", path = "./crates/nu-command" }
nu-completion = { version = "0.32.1", path = "./crates/nu-completion" }
nu-data = { version = "0.32.1", path = "./crates/nu-data" } nu-data = { version = "0.32.1", path = "./crates/nu-data" }
nu-engine = { version = "0.32.1", path = "./crates/nu-engine" } nu-engine = { version = "0.32.1", path = "./crates/nu-engine" }
nu-errors = { version = "0.32.1", path = "./crates/nu-errors" } nu-errors = { version = "0.32.1", path = "./crates/nu-errors" }

View file

@ -11,6 +11,7 @@ version = "0.32.1"
doctest = false doctest = false
[dependencies] [dependencies]
nu-completion = { version = "0.32.1", path = "../nu-completion" }
nu-command = { version = "0.32.1", path = "../nu-command" } nu-command = { version = "0.32.1", path = "../nu-command" }
nu-data = { version = "0.32.1", path = "../nu-data" } nu-data = { version = "0.32.1", path = "../nu-data" }
nu-engine = { version = "0.32.1", path = "../nu-engine" } nu-engine = { version = "0.32.1", path = "../nu-engine" }

View file

@ -1,37 +0,0 @@
pub(crate) mod command;
pub(crate) mod engine;
pub(crate) mod flag;
pub(crate) mod matchers;
pub(crate) mod path;
use matchers::Matcher;
use nu_engine::EvaluationContext;
#[derive(Debug, Eq, PartialEq)]
pub struct Suggestion {
pub display: String,
pub replacement: String,
}
pub struct CompletionContext<'a>(&'a EvaluationContext);
impl<'a> CompletionContext<'a> {
pub fn new(a: &'a EvaluationContext) -> CompletionContext<'a> {
CompletionContext(a)
}
}
impl<'a> AsRef<EvaluationContext> for CompletionContext<'a> {
fn as_ref(&self) -> &EvaluationContext {
self.0
}
}
pub trait Completer {
fn complete(
&self,
ctx: &CompletionContext<'_>,
partial: &str,
matcher: &dyn Matcher,
) -> Vec<Suggestion>;
}

View file

@ -11,8 +11,6 @@ extern crate quickcheck_macros;
mod app; mod app;
mod cli; mod cli;
#[cfg(feature = "rustyline-support")]
mod completion;
mod format; mod format;
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
mod keybinding; mod keybinding;

View file

@ -1,7 +1,5 @@
#![allow(clippy::module_inception)] #![allow(clippy::module_inception)]
#[cfg(feature = "rustyline-support")]
pub(crate) mod completer;
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
pub(crate) mod helper; pub(crate) mod helper;

View file

@ -1,6 +1,5 @@
use crate::completion;
use crate::shell::completer::NuCompleter;
use nu_ansi_term::Color; use nu_ansi_term::Color;
use nu_completion::NuCompleter;
use nu_engine::{DefaultPalette, EvaluationContext, Painter}; use nu_engine::{DefaultPalette, EvaluationContext, Painter};
use nu_source::{Tag, Tagged}; use nu_source::{Tag, Tagged};
use std::borrow::Cow::{self, Owned}; use std::borrow::Cow::{self, Owned};
@ -28,18 +27,28 @@ impl Helper {
} }
} }
impl rustyline::completion::Candidate for completion::Suggestion { struct CompletionContext<'a>(&'a EvaluationContext);
impl<'a> nu_completion::CompletionContext for CompletionContext<'a> {
fn signature_registry(&self) -> &dyn nu_parser::ParserScope {
&self.0.scope
}
}
pub struct CompletionSuggestion(nu_completion::Suggestion);
impl rustyline::completion::Candidate for CompletionSuggestion {
fn display(&self) -> &str { fn display(&self) -> &str {
&self.display &self.0.display
} }
fn replacement(&self) -> &str { fn replacement(&self) -> &str {
&self.replacement &self.0.replacement
} }
} }
impl rustyline::completion::Completer for Helper { impl rustyline::completion::Completer for Helper {
type Candidate = completion::Suggestion; type Candidate = CompletionSuggestion;
fn complete( fn complete(
&self, &self,
@ -47,8 +56,10 @@ impl rustyline::completion::Completer for Helper {
pos: usize, pos: usize,
_ctx: &rustyline::Context<'_>, _ctx: &rustyline::Context<'_>,
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> { ) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
let ctx = completion::CompletionContext::new(&self.context); let ctx = CompletionContext(&self.context);
Ok(self.completer.complete(line, pos, &ctx)) let (position, suggestions) = self.completer.complete(line, pos, &ctx);
let suggestions = suggestions.into_iter().map(CompletionSuggestion).collect();
Ok((position, suggestions))
} }
fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) { fn update(&self, line: &mut rustyline::line_buffer::LineBuffer, start: usize, elected: &str) {

View file

@ -0,0 +1,29 @@
[package]
authors = ["The Nu Project Contributors"]
description = "Completions for nushell"
edition = "2018"
license = "MIT"
name = "nu-completion"
version = "0.32.1"
[lib]
doctest = false
[dependencies]
nu-data = { version = "0.32.1", path = "../nu-data" }
nu-engine = { version = "0.32.1", path = "../nu-engine" }
nu-errors = { version = "0.32.1", path = "../nu-errors" }
nu-parser = { version = "0.32.1", path = "../nu-parser" }
nu-protocol = { version = "0.32.1", path = "../nu-protocol" }
nu-source = { version = "0.32.1", path = "../nu-source" }
nu-test-support = { version = "0.32.1", path = "../nu-test-support" }
directories-next = { version = "2.0.0", optional = true }
dirs-next = { version = "2.0.0", optional = true }
indexmap = { version = "1.6.1", features = ["serde-1"] }
rustyline = { version = "8.1.0", optional = true }
[features]
rustyline-support = ["rustyline", "nu-engine/rustyline-support"]
dirs = ["dirs-next"]
directories = ["directories-next"]

View file

@ -1,24 +1,22 @@
use nu_test_support::NATIVE_PATH_ENV_VAR;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::path::Path; use std::path::Path;
use indexmap::set::IndexSet; use indexmap::set::IndexSet;
use super::matchers::Matcher; use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion}; use crate::{Completer, CompletionContext, Suggestion};
use nu_engine::EvaluationContext;
use nu_test_support::NATIVE_PATH_ENV_VAR;
pub struct CommandCompleter; pub struct CommandCompleter;
impl Completer for CommandCompleter { impl<Context> Completer<Context> for CommandCompleter
fn complete( where
&self, Context: CompletionContext,
ctx: &CompletionContext<'_>, {
partial: &str, fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
matcher: &dyn Matcher, let registry = ctx.signature_registry();
) -> Vec<Suggestion> { let mut commands: IndexSet<String> = IndexSet::from_iter(registry.get_names());
let context: &EvaluationContext = ctx.as_ref();
let mut commands: IndexSet<String> = IndexSet::from_iter(context.scope.get_command_names());
// Command suggestions can come from three possible sets: // Command suggestions can come from three possible sets:
// 1. internal command names, // 1. internal command names,
@ -40,7 +38,7 @@ impl Completer for CommandCompleter {
.collect(); .collect();
if !partial.is_empty() { if !partial.is_empty() {
let path_completer = crate::completion::path::PathCompleter; let path_completer = crate::path::PathCompleter;
let path_results = path_completer.path_suggestions(partial, matcher); let path_results = path_completer.path_suggestions(partial, matcher);
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;

View file

@ -1,35 +1,34 @@
use crate::completion::command::CommandCompleter;
use crate::completion::flag::FlagCompleter;
use crate::completion::matchers;
use crate::completion::matchers::Matcher;
use crate::completion::path::{PathCompleter, PathSuggestion};
use crate::completion::{self, Completer, Suggestion};
use nu_engine::EvaluationContext;
use nu_parser::ParserScope;
use nu_source::{Span, Tag};
use std::borrow::Cow; use std::borrow::Cow;
pub(crate) struct NuCompleter {} use nu_source::{Span, Tag};
use crate::command::CommandCompleter;
use crate::engine;
use crate::flag::FlagCompleter;
use crate::matchers;
use crate::matchers::Matcher;
use crate::path::{PathCompleter, PathSuggestion};
use crate::{Completer, CompletionContext, Suggestion};
pub struct NuCompleter {}
impl NuCompleter {} impl NuCompleter {}
impl NuCompleter { impl NuCompleter {
pub fn complete( pub fn complete<Context: CompletionContext>(
&self, &self,
line: &str, line: &str,
pos: usize, pos: usize,
context: &completion::CompletionContext, context: &Context,
) -> (usize, Vec<Suggestion>) { ) -> (usize, Vec<Suggestion>) {
use completion::engine::LocationType; use engine::LocationType;
let nu_context: &EvaluationContext = context.as_ref(); let tokens = nu_parser::lex(line, 0).0;
nu_context.scope.enter_scope(); let locations = Some(nu_parser::parse_block(tokens).0)
let (block, _) = nu_parser::parse(line, 0, &nu_context.scope); .map(|block| nu_parser::classify_block(&block, context.signature_registry()))
nu_context.scope.exit_scope(); .map(|(block, _)| engine::completion_location(line, &block, pos))
.unwrap_or_default();
let locations = completion::engine::completion_location(line, &block, pos);
let matcher = nu_data::config::config(Tag::unknown()) let matcher = nu_data::config::config(Tag::unknown())
.ok() .ok()
@ -61,7 +60,6 @@ impl NuCompleter {
pos = location.span.start(); pos = location.span.start();
} }
} }
let suggestions = locations let suggestions = locations
.into_iter() .into_iter()
.flat_map(|location| { .flat_map(|location| {
@ -147,7 +145,16 @@ fn select_directory_suggestions(completed_paths: Vec<PathSuggestion>) -> Vec<Pat
} }
fn requote(orig_value: String, previously_quoted: bool) -> String { fn requote(orig_value: String, previously_quoted: bool) -> String {
let value: Cow<str> = rustyline::completion::unescape(&orig_value, Some('\\')); let value: Cow<str> = {
#[cfg(feature = "rustyline-support")]
{
rustyline::completion::unescape(&orig_value, Some('\\'))
}
#[cfg(not(feature = "rustyline-support"))]
{
orig_value.into()
}
};
let mut quotes = vec!['"', '\'']; let mut quotes = vec!['"', '\''];
let mut should_quote = false; let mut should_quote = false;

View file

@ -301,6 +301,10 @@ mod tests {
} }
impl ParserScope for VecRegistry { impl ParserScope for VecRegistry {
fn get_names(&self) -> Vec<String> {
self.0.iter().cloned().map(|s| s.name).collect()
}
fn has_signature(&self, name: &str) -> bool { fn has_signature(&self, name: &str) -> bool {
self.0.iter().any(|v| v.name == name) self.0.iter().any(|v| v.name == name)
} }
@ -331,8 +335,6 @@ mod tests {
mod completion_location { mod completion_location {
use super::*; use super::*;
use nu_parser::ParserScope;
fn completion_location( fn completion_location(
line: &str, line: &str,
scope: &dyn ParserScope, scope: &dyn ParserScope,

View file

@ -1,22 +1,16 @@
use super::matchers::Matcher; use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion}; use crate::{Completer, CompletionContext, Suggestion};
use nu_engine::EvaluationContext;
pub struct FlagCompleter { pub struct FlagCompleter {
pub(crate) cmd: String, pub(crate) cmd: String,
} }
impl Completer for FlagCompleter { impl<Context> Completer<Context> for FlagCompleter
fn complete( where
&self, Context: CompletionContext,
ctx: &CompletionContext<'_>, {
partial: &str, fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
matcher: &dyn Matcher, if let Some(sig) = ctx.signature_registry().get_signature(&self.cmd) {
) -> Vec<Suggestion> {
let context: &EvaluationContext = ctx.as_ref();
if let Some(cmd) = context.scope.get_command(&self.cmd) {
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() {
suggestions.push(format!("--{}", name)); suggestions.push(format!("--{}", name));

View file

@ -0,0 +1,24 @@
pub(crate) mod command;
pub(crate) mod completer;
pub(crate) mod engine;
pub(crate) mod flag;
pub(crate) mod matchers;
pub(crate) mod path;
use matchers::Matcher;
pub use completer::NuCompleter;
#[derive(Debug, Eq, PartialEq)]
pub struct Suggestion {
pub display: String,
pub replacement: String,
}
pub trait CompletionContext {
fn signature_registry(&self) -> &dyn nu_parser::ParserScope;
}
pub trait Completer<Context: CompletionContext> {
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion>;
}

View file

@ -1,4 +1,5 @@
use crate::completion::matchers; use crate::matchers;
pub struct Matcher; pub struct Matcher;
impl matchers::Matcher for Matcher { impl matchers::Matcher for Matcher {
@ -12,7 +13,7 @@ impl matchers::Matcher for Matcher {
mod tests { mod tests {
use super::*; use super::*;
// TODO: check some Unicode matches if this becomes relevant // TODO: check some unicode matches if this becomes relevant
// FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test // FIXME: could work exhaustively through ['-', '--'. ''] in a loop for each test
#[test] #[test]

View file

@ -1,4 +1,4 @@
use crate::completion::matchers; use crate::matchers;
pub struct Matcher; pub struct Matcher;

View file

@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use super::matchers::Matcher; use super::matchers::Matcher;
use crate::completion::{Completer, CompletionContext, Suggestion}; use crate::{Completer, CompletionContext, Suggestion};
const SEP: char = std::path::MAIN_SEPARATOR; const SEP: char = std::path::MAIN_SEPARATOR;
@ -74,13 +74,11 @@ impl PathCompleter {
} }
} }
impl Completer for PathCompleter { impl<Context> Completer<Context> for PathCompleter
fn complete( where
&self, Context: CompletionContext,
_ctx: &CompletionContext<'_>, {
partial: &str, fn complete(&self, _ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
matcher: &dyn Matcher,
) -> Vec<Suggestion> {
self.path_suggestions(partial, matcher) self.path_suggestions(partial, matcher)
.into_iter() .into_iter()
.map(|ps| ps.suggestion) .map(|ps| ps.suggestion)

View file

@ -319,6 +319,10 @@ impl Scope {
} }
impl ParserScope for Scope { impl ParserScope for Scope {
fn get_names(&self) -> Vec<String> {
self.get_command_names()
}
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> { fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature> {
self.get_command(name).map(|x| x.signature()) self.get_command(name).map(|x| x.signature())
} }

View file

@ -3,6 +3,8 @@ use nu_source::Spanned;
use std::{fmt::Debug, sync::Arc}; use std::{fmt::Debug, sync::Arc};
pub trait ParserScope: Debug { pub trait ParserScope: Debug {
fn get_names(&self) -> Vec<String>;
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature>; fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature>;
fn has_signature(&self, name: &str) -> bool; fn has_signature(&self, name: &str) -> bool;