forked from extern/nushell
Extract completions into subcrate. (#3631)
This commit is contained in:
parent
04c0e94349
commit
7c8fb060f1
19
Cargo.lock
generated
19
Cargo.lock
generated
@ -3233,6 +3233,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"nu-cli",
|
||||
"nu-command",
|
||||
"nu-completion",
|
||||
"nu-data",
|
||||
"nu-engine",
|
||||
"nu-errors",
|
||||
@ -3318,6 +3319,7 @@ dependencies = [
|
||||
"meval",
|
||||
"nu-ansi-term",
|
||||
"nu-command",
|
||||
"nu-completion",
|
||||
"nu-data",
|
||||
"nu-engine",
|
||||
"nu-errors",
|
||||
@ -3476,6 +3478,23 @@ dependencies = [
|
||||
"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]]
|
||||
name = "nu-data"
|
||||
version = "0.32.1"
|
||||
|
@ -20,6 +20,7 @@ members = ["crates/*/"]
|
||||
[dependencies]
|
||||
nu-cli = { version = "0.32.1", path = "./crates/nu-cli", default-features = false }
|
||||
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-engine = { version = "0.32.1", path = "./crates/nu-engine" }
|
||||
nu-errors = { version = "0.32.1", path = "./crates/nu-errors" }
|
||||
|
@ -11,6 +11,7 @@ version = "0.32.1"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
nu-completion = { version = "0.32.1", path = "../nu-completion" }
|
||||
nu-command = { version = "0.32.1", path = "../nu-command" }
|
||||
nu-data = { version = "0.32.1", path = "../nu-data" }
|
||||
nu-engine = { version = "0.32.1", path = "../nu-engine" }
|
||||
|
@ -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>;
|
||||
}
|
@ -11,8 +11,6 @@ extern crate quickcheck_macros;
|
||||
|
||||
mod app;
|
||||
mod cli;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod completion;
|
||||
mod format;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
mod keybinding;
|
||||
|
@ -1,7 +1,5 @@
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod completer;
|
||||
#[cfg(feature = "rustyline-support")]
|
||||
pub(crate) mod helper;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::completion;
|
||||
use crate::shell::completer::NuCompleter;
|
||||
use nu_ansi_term::Color;
|
||||
use nu_completion::NuCompleter;
|
||||
use nu_engine::{DefaultPalette, EvaluationContext, Painter};
|
||||
use nu_source::{Tag, Tagged};
|
||||
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 {
|
||||
&self.display
|
||||
&self.0.display
|
||||
}
|
||||
|
||||
fn replacement(&self) -> &str {
|
||||
&self.replacement
|
||||
&self.0.replacement
|
||||
}
|
||||
}
|
||||
|
||||
impl rustyline::completion::Completer for Helper {
|
||||
type Candidate = completion::Suggestion;
|
||||
type Candidate = CompletionSuggestion;
|
||||
|
||||
fn complete(
|
||||
&self,
|
||||
@ -47,8 +56,10 @@ impl rustyline::completion::Completer for Helper {
|
||||
pos: usize,
|
||||
_ctx: &rustyline::Context<'_>,
|
||||
) -> Result<(usize, Vec<Self::Candidate>), rustyline::error::ReadlineError> {
|
||||
let ctx = completion::CompletionContext::new(&self.context);
|
||||
Ok(self.completer.complete(line, pos, &ctx))
|
||||
let ctx = CompletionContext(&self.context);
|
||||
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) {
|
||||
|
29
crates/nu-completion/Cargo.toml
Normal file
29
crates/nu-completion/Cargo.toml
Normal 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"]
|
@ -1,24 +1,22 @@
|
||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||
|
||||
use std::iter::FromIterator;
|
||||
use std::path::Path;
|
||||
|
||||
use indexmap::set::IndexSet;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
use nu_test_support::NATIVE_PATH_ENV_VAR;
|
||||
use crate::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
pub struct CommandCompleter;
|
||||
|
||||
impl Completer for CommandCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(context.scope.get_command_names());
|
||||
impl<Context> Completer<Context> for CommandCompleter
|
||||
where
|
||||
Context: CompletionContext,
|
||||
{
|
||||
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
||||
let registry = ctx.signature_registry();
|
||||
let mut commands: IndexSet<String> = IndexSet::from_iter(registry.get_names());
|
||||
|
||||
// Command suggestions can come from three possible sets:
|
||||
// 1. internal command names,
|
||||
@ -40,7 +38,7 @@ impl Completer for CommandCompleter {
|
||||
.collect();
|
||||
|
||||
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 iter = path_results.into_iter().filter_map(|path_suggestion| {
|
||||
let path = path_suggestion.path;
|
@ -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;
|
||||
|
||||
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 {
|
||||
pub fn complete(
|
||||
pub fn complete<Context: CompletionContext>(
|
||||
&self,
|
||||
line: &str,
|
||||
pos: usize,
|
||||
context: &completion::CompletionContext,
|
||||
context: &Context,
|
||||
) -> (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 (block, _) = nu_parser::parse(line, 0, &nu_context.scope);
|
||||
nu_context.scope.exit_scope();
|
||||
|
||||
let locations = completion::engine::completion_location(line, &block, pos);
|
||||
let locations = Some(nu_parser::parse_block(tokens).0)
|
||||
.map(|block| nu_parser::classify_block(&block, context.signature_registry()))
|
||||
.map(|(block, _)| engine::completion_location(line, &block, pos))
|
||||
.unwrap_or_default();
|
||||
|
||||
let matcher = nu_data::config::config(Tag::unknown())
|
||||
.ok()
|
||||
@ -61,7 +60,6 @@ impl NuCompleter {
|
||||
pos = location.span.start();
|
||||
}
|
||||
}
|
||||
|
||||
let suggestions = locations
|
||||
.into_iter()
|
||||
.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 {
|
||||
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 should_quote = false;
|
@ -301,6 +301,10 @@ mod tests {
|
||||
}
|
||||
|
||||
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 {
|
||||
self.0.iter().any(|v| v.name == name)
|
||||
}
|
||||
@ -331,8 +335,6 @@ mod tests {
|
||||
mod completion_location {
|
||||
use super::*;
|
||||
|
||||
use nu_parser::ParserScope;
|
||||
|
||||
fn completion_location(
|
||||
line: &str,
|
||||
scope: &dyn ParserScope,
|
@ -1,22 +1,16 @@
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use nu_engine::EvaluationContext;
|
||||
use crate::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
pub struct FlagCompleter {
|
||||
pub(crate) cmd: String,
|
||||
}
|
||||
|
||||
impl Completer for FlagCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
let context: &EvaluationContext = ctx.as_ref();
|
||||
|
||||
if let Some(cmd) = context.scope.get_command(&self.cmd) {
|
||||
let sig = cmd.signature();
|
||||
impl<Context> Completer<Context> for FlagCompleter
|
||||
where
|
||||
Context: CompletionContext,
|
||||
{
|
||||
fn complete(&self, ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
||||
if let Some(sig) = ctx.signature_registry().get_signature(&self.cmd) {
|
||||
let mut suggestions = Vec::new();
|
||||
for (name, (named_type, _desc)) in sig.named.iter() {
|
||||
suggestions.push(format!("--{}", name));
|
24
crates/nu-completion/src/lib.rs
Normal file
24
crates/nu-completion/src/lib.rs
Normal 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>;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
use crate::completion::matchers;
|
||||
use crate::matchers;
|
||||
|
||||
pub struct Matcher;
|
||||
|
||||
impl matchers::Matcher for Matcher {
|
||||
@ -12,7 +13,7 @@ impl matchers::Matcher for Matcher {
|
||||
mod tests {
|
||||
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
|
||||
#[test]
|
@ -1,4 +1,4 @@
|
||||
use crate::completion::matchers;
|
||||
use crate::matchers;
|
||||
|
||||
pub struct Matcher;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::matchers::Matcher;
|
||||
use crate::completion::{Completer, CompletionContext, Suggestion};
|
||||
use crate::{Completer, CompletionContext, Suggestion};
|
||||
|
||||
const SEP: char = std::path::MAIN_SEPARATOR;
|
||||
|
||||
@ -74,13 +74,11 @@ impl PathCompleter {
|
||||
}
|
||||
}
|
||||
|
||||
impl Completer for PathCompleter {
|
||||
fn complete(
|
||||
&self,
|
||||
_ctx: &CompletionContext<'_>,
|
||||
partial: &str,
|
||||
matcher: &dyn Matcher,
|
||||
) -> Vec<Suggestion> {
|
||||
impl<Context> Completer<Context> for PathCompleter
|
||||
where
|
||||
Context: CompletionContext,
|
||||
{
|
||||
fn complete(&self, _ctx: &Context, partial: &str, matcher: &dyn Matcher) -> Vec<Suggestion> {
|
||||
self.path_suggestions(partial, matcher)
|
||||
.into_iter()
|
||||
.map(|ps| ps.suggestion)
|
@ -319,6 +319,10 @@ impl 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> {
|
||||
self.get_command(name).map(|x| x.signature())
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ use nu_source::Spanned;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
pub trait ParserScope: Debug {
|
||||
fn get_names(&self) -> Vec<String>;
|
||||
|
||||
fn get_signature(&self, name: &str) -> Option<nu_protocol::Signature>;
|
||||
|
||||
fn has_signature(&self, name: &str) -> bool;
|
||||
|
Loading…
Reference in New Issue
Block a user