Skip to content

Commit 85b5e57

Browse files
authored
Plugin: support custom completions in command flags (nushell#16859)
Closes: nushell#15766 It's similar to nushell#16383, but allows to generate completion dynamically, which can be useful for plugin commands. To implement this, I added a new `get_dynamic_completion` method to `Command` trait, so nushell gets the ability to ask plugins for a completion. Here is how it goes when I type `plugin-cmd --flag1 <tab>`. 1. the completion logic detect it's behind a flag, and it should have a value, then it goes to `ArgValueDynamicCompletion` 2. inside `ArgValueDynamicCompletion` it calls `get_dynamic_completion(ArgType::Flag(flag1))` on the command, which can be plugin command, and it will issue a `get_dynamic_completion(ArgType::Flag(flag1))` call to the plugin 3. the plugin returns `Some(Vec<DynamicSemanticSuggestion>)` if there are available items. It also supports dynamic completion for positional arguments, here is how it goes when I type `plugin-cmd x<tab>`. 1. the completion logic detect it's behind a flag, and it should have a value, then it goes to `ArgValueDynamicCompletion` 2. inside `ArgValueDynamicCompletion` it calls `get_dynamic_completion(ArgType::Positional(0))` on the command, which can be plugin command, and it will issue a `get_dynamic_completion(ArgType::Positional(0))` call to the plugin 3. the plugin returns `Some(Vec<DynamicSemanticSuggestion>)` if there are available items. ## Release notes summary - What our users need to know ### Plugin developer: auto completion is available for plugin command arguments. User can define auto completions for plugin commands, here is the way to define this: ```rust impl PluginCommand for FlagCompletion { type Plugin = ExamplePlugin; ... fn get_dynamic_completion( &self, _plugin: &Self::Plugin, _engine: &EngineInterface, arg_type: ArgType, ) -> Option<Vec<DynamicSemanticSuggestion>> { match arg_type { // enable completion for flag. ArgType::Flag(flag_name) => match flag_name { "flag1" => Some(vec!["flag_val1".to_string().into(), "flag_val2".to_string().into()]), "flag2" => Some(vec!["flag_val3".to_string().into(), "flag_val4".to_string().into()]), // for other flags don't need to provide a completion _ => None, }, // enable completion for positional arguments. ArgType::Positional(index) => None, } } } ``` For the detailed example, you can refer to `get_dynamic_completion` in `crates/nu_plugin_example/src/commands/arg_completion.rs`. ## Tasks after submitting - [ ] Update the [documentation](https://github.com/nushell/nushell.github.io)
1 parent 99f0ef6 commit 85b5e57

31 files changed

+752
-83
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::completions::{
2+
Completer, CompletionOptions, SemanticSuggestion, completion_options::NuMatcher,
3+
};
4+
use nu_protocol::{
5+
DeclId, Span,
6+
engine::{ArgType, Stack, StateWorkingSet},
7+
};
8+
9+
pub struct ArgValueDynamicCompletion<'a> {
10+
pub decl_id: DeclId,
11+
pub arg_type: ArgType<'a>,
12+
pub need_fallback: &'a mut bool,
13+
}
14+
15+
impl<'a> Completer for ArgValueDynamicCompletion<'a> {
16+
// TODO: move the logic of `argument_completion_helper` here.
17+
fn fetch(
18+
&mut self,
19+
working_set: &StateWorkingSet,
20+
stack: &Stack,
21+
prefix: impl AsRef<str>,
22+
span: Span,
23+
offset: usize,
24+
options: &CompletionOptions,
25+
) -> Vec<SemanticSuggestion> {
26+
// the `prefix` is the value of a flag
27+
// if user input `--foo abc`, then the `prefix` here is abc.
28+
// the name of flag is saved in `self.flag_name`.
29+
let mut matcher = NuMatcher::new(prefix, options, true);
30+
31+
let decl = working_set.get_decl(self.decl_id);
32+
let mut stack = stack.to_owned();
33+
match decl.get_dynamic_completion(working_set.permanent_state, &mut stack, &self.arg_type) {
34+
Ok(Some(items)) => {
35+
for i in items {
36+
let suggestion = SemanticSuggestion::from_dynamic_suggestion(
37+
i,
38+
reedline::Span {
39+
start: span.start - offset,
40+
end: span.end - offset,
41+
},
42+
None,
43+
);
44+
matcher.add_semantic_suggestion(suggestion);
45+
}
46+
}
47+
Ok(None) => *self.need_fallback = true,
48+
Err(e) => {
49+
log::error!(
50+
"error on fetching dynamic suggestion on {} with {:?}: {e}",
51+
decl.name(),
52+
self.arg_type
53+
);
54+
}
55+
}
56+
matcher.suggestion_results()
57+
}
58+
}

crates/nu-cli/src/completions/attribute_completions.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
use super::{SemanticSuggestion, completion_options::NuMatcher};
2-
use crate::{
3-
SuggestionKind,
4-
completions::{Completer, CompletionOptions},
5-
};
2+
use crate::completions::{Completer, CompletionOptions};
63
use nu_protocol::{
7-
Span,
4+
Span, SuggestionKind,
85
engine::{Stack, StateWorkingSet},
96
};
107
use reedline::Suggestion;

crates/nu-cli/src/completions/base.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::completions::CompletionOptions;
22
use nu_protocol::{
3-
DeclId, Span,
3+
DynamicSuggestion, Span, SuggestionKind,
44
engine::{Stack, StateWorkingSet},
55
};
66
use reedline::Suggestion;
@@ -25,18 +25,25 @@ pub struct SemanticSuggestion {
2525
pub kind: Option<SuggestionKind>,
2626
}
2727

28-
// TODO: think about name: maybe suggestion context?
29-
#[derive(Clone, Debug, PartialEq)]
30-
pub enum SuggestionKind {
31-
Command(nu_protocol::engine::CommandType, Option<DeclId>),
32-
Value(nu_protocol::Type),
33-
CellPath,
34-
Directory,
35-
File,
36-
Flag,
37-
Module,
38-
Operator,
39-
Variable,
28+
impl SemanticSuggestion {
29+
pub fn from_dynamic_suggestion(
30+
suggestion: DynamicSuggestion,
31+
span: reedline::Span,
32+
style: Option<nu_ansi_term::Style>,
33+
) -> Self {
34+
SemanticSuggestion {
35+
suggestion: Suggestion {
36+
value: suggestion.value,
37+
description: suggestion.description,
38+
extra: suggestion.extra,
39+
append_whitespace: suggestion.append_whitespace,
40+
match_indices: suggestion.match_indices,
41+
style,
42+
span,
43+
},
44+
kind: suggestion.kind,
45+
}
46+
}
4047
}
4148

4249
impl From<Suggestion> for SemanticSuggestion {

crates/nu-cli/src/completions/cell_path_completions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use std::borrow::Cow;
22

3-
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion, SuggestionKind};
3+
use crate::completions::{Completer, CompletionOptions, SemanticSuggestion};
44
use nu_engine::{column::get_columns, eval_variable};
55
use nu_protocol::{
6-
ShellError, Span, Value,
6+
ShellError, Span, SuggestionKind, Value,
77
ast::{Expr, Expression, FullCellPath, PathMember},
88
engine::{Stack, StateWorkingSet},
99
eval_const::eval_constant,

crates/nu-cli/src/completions/command_completions.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
use std::collections::HashSet;
22

3-
use crate::{
4-
SuggestionKind,
5-
completions::{Completer, CompletionOptions},
6-
};
3+
use crate::completions::{Completer, CompletionOptions};
74
use nu_protocol::{
8-
Category, Span,
5+
Category, Span, SuggestionKind,
96
engine::{CommandType, Stack, StateWorkingSet},
107
};
118
use reedline::Suggestion;

crates/nu-cli/src/completions/completer.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
use crate::completions::{
2-
AttributableCompletion, AttributeCompletion, CellPathCompletion, CommandCompletion, Completer,
3-
CompletionOptions, CustomCompletion, DirectoryCompletion, DotNuCompletion,
4-
ExportableCompletion, FileCompletion, FlagCompletion, OperatorCompletion, VariableCompletion,
5-
base::{SemanticSuggestion, SuggestionKind},
2+
ArgValueDynamicCompletion, AttributableCompletion, AttributeCompletion, CellPathCompletion,
3+
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
4+
DotNuCompletion, ExportableCompletion, FileCompletion, FlagCompletion, OperatorCompletion,
5+
VariableCompletion, base::SemanticSuggestion,
66
};
77
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
88
use nu_parser::{parse, parse_module_file_or_dir};
99
use nu_protocol::{
10-
CommandWideCompleter, Completion, GetSpan, Span, Type, Value,
10+
CommandWideCompleter, Completion, GetSpan, Span, SuggestionKind, Type, Value,
1111
ast::{
1212
Argument, Block, Expr, Expression, FindMapResult, ListItem, PipelineRedirection,
1313
RedirectionTarget, Traverse,
1414
},
15-
engine::{EngineState, Stack, StateWorkingSet},
15+
engine::{ArgType, EngineState, Stack, StateWorkingSet},
1616
};
1717
use reedline::{Completer as ReedlineCompleter, Suggestion};
18+
use std::borrow::Cow;
1819
use std::sync::Arc;
1920

2021
use super::{StaticCompletion, custom_completions::CommandWideCompletion};
@@ -571,9 +572,42 @@ impl NuCompleter {
571572
0..0,
572573
match arg {
573574
// flags
574-
Argument::Named(_) | Argument::Unknown(_)
575-
if prefix.starts_with(b"-") =>
576-
{
575+
Argument::Named((name, _, val)) if prefix.starts_with(b"-") => {
576+
match val {
577+
None => flag_completion_helper(),
578+
// It's required because it's edge case of lsp completion for
579+
// flag name itself.
580+
Some(val) if !val.span.contains(pos) => {
581+
flag_completion_helper()
582+
}
583+
Some(_) => {
584+
// TODO: add a test to flag value completion in nu-lsp/src/completion.rs
585+
//
586+
// Completed flag value.
587+
// strip from `--foo ..a|` and `--foo=..a|` to `..a`, and also remove the place holder.
588+
// to make a user friendly completion items.
589+
let (new_span, prefix) = strip_placeholder_with_rsplit(
590+
working_set,
591+
&span,
592+
|b| *b == b'=' || *b == b' ',
593+
strip,
594+
);
595+
let ctx =
596+
Context::new(working_set, new_span, prefix, offset);
597+
let mut flag_value_completion = ArgValueDynamicCompletion {
598+
decl_id: call.decl_id,
599+
arg_type: ArgType::Flag(Cow::from(
600+
name.as_ref().item.as_str(),
601+
)),
602+
// flag value doesn't need to fallback, just fill a
603+
// temp value false.
604+
need_fallback: &mut false,
605+
};
606+
self.process_completion(&mut flag_value_completion, &ctx)
607+
}
608+
}
609+
}
610+
Argument::Unknown(_) if prefix.starts_with(b"-") => {
577611
flag_completion_helper()
578612
}
579613
// only when `strip` == false
@@ -582,7 +616,21 @@ impl NuCompleter {
582616
Argument::Positional(expr) => {
583617
let command_head = working_set.get_decl(call.decl_id).name();
584618
positional_arg_indices.push(arg_idx);
585-
let mut need_fallback = suggestions.is_empty();
619+
let mut dynamic_completion_need_fallback = false;
620+
let mut positional_value_completion = ArgValueDynamicCompletion {
621+
decl_id: call.decl_id,
622+
arg_type: ArgType::Positional(arg_idx),
623+
need_fallback: &mut dynamic_completion_need_fallback,
624+
};
625+
626+
// try argument dynamic completion defined by Command first.
627+
let value_completion_result =
628+
self.process_completion(&mut positional_value_completion, &ctx);
629+
if !value_completion_result.is_empty() {
630+
return value_completion_result;
631+
}
632+
let mut need_fallback =
633+
suggestions.is_empty() && dynamic_completion_need_fallback;
586634
let results = self.argument_completion_helper(
587635
PositionalArguments {
588636
command_head,

crates/nu-cli/src/completions/directory_completions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use crate::completions::{
33
completion_common::{AdjustView, adjust_if_intermediate, complete_item},
44
};
55
use nu_protocol::{
6-
Span,
6+
Span, SuggestionKind,
77
engine::{EngineState, Stack, StateWorkingSet},
88
};
99
use reedline::Suggestion;
1010
use std::path::Path;
1111

12-
use super::{SemanticSuggestion, SuggestionKind, completion_common::FileSuggestion};
12+
use super::{SemanticSuggestion, completion_common::FileSuggestion};
1313

1414
pub struct DirectoryCompletion;
1515

crates/nu-cli/src/completions/dotnu_completions.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::completions::{
2-
Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
3-
completion_common::FileSuggestion, completion_options::NuMatcher,
2+
Completer, CompletionOptions, SemanticSuggestion, completion_common::FileSuggestion,
3+
completion_options::NuMatcher,
44
};
55
use nu_path::expand_tilde;
66
use nu_protocol::{
7-
Span,
7+
Span, SuggestionKind,
88
engine::{Stack, StateWorkingSet, VirtualPath},
99
};
1010
use reedline::Suggestion;

crates/nu-cli/src/completions/exportable_completions.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use crate::completions::{
2-
Completer, CompletionOptions, SemanticSuggestion, SuggestionKind,
3-
completion_common::surround_remove, completion_options::NuMatcher,
2+
Completer, CompletionOptions, SemanticSuggestion, completion_common::surround_remove,
3+
completion_options::NuMatcher,
44
};
55
use nu_protocol::{
6-
ModuleId, Span,
6+
ModuleId, Span, SuggestionKind,
77
engine::{Stack, StateWorkingSet},
88
};
99
use reedline::Suggestion;

crates/nu-cli/src/completions/file_completions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ use crate::completions::{
33
completion_common::{AdjustView, adjust_if_intermediate, complete_item},
44
};
55
use nu_protocol::{
6-
Span,
6+
Span, SuggestionKind,
77
engine::{Stack, StateWorkingSet},
88
};
99
use reedline::Suggestion;
1010
use std::path::Path;
1111

12-
use super::{SemanticSuggestion, SuggestionKind};
12+
use super::SemanticSuggestion;
1313

1414
pub struct FileCompletion;
1515

0 commit comments

Comments
 (0)