mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 09:15:42 +02:00
refactor(completion, parser): move custom_completion info from Expression to Signature (#15613)
Restricts custom completion from universal to internal arguments only. Pros: 1. Less memory 2. More flexible for later customizations, e.g. #14923 Cons: 1. limited customization capabilities, but at least covers all currently existing features in nushell. # Description Mostly vibe coded by [Zed AI](https://zed.dev/ai) with a single prompt. LGTM, but I'm not so sure @ysthakur # User-Facing Changes Hopefully none. # Tests + Formatting +3 # After Submitting --------- Co-authored-by: Yash Thakur <45539777+ysthakur@users.noreply.github.com>
This commit is contained in:
@ -183,11 +183,6 @@ fn flatten_expression_into(
|
||||
expr: &Expression,
|
||||
output: &mut Vec<(Span, FlatShape)>,
|
||||
) {
|
||||
if let Some(custom_completion) = &expr.custom_completion {
|
||||
output.push((expr.span, FlatShape::Custom(*custom_completion)));
|
||||
return;
|
||||
}
|
||||
|
||||
match &expr.expr {
|
||||
Expr::AttributeBlock(ab) => {
|
||||
for attr in &ab.attributes {
|
||||
|
@ -350,6 +350,7 @@ pub fn parse_for(working_set: &mut StateWorkingSet, lite_command: &LiteCommand)
|
||||
shape: var_type.to_shape(),
|
||||
var_id: Some(*var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -2,40 +2,21 @@
|
||||
|
||||
use crate::{TokenContents, lex::lex_signature, parser::parse_value, trim_quotes};
|
||||
use nu_protocol::{
|
||||
IntoSpanned, ParseError, Span, Spanned, SyntaxShape, Type, engine::StateWorkingSet,
|
||||
DeclId, IntoSpanned, ParseError, Span, Spanned, SyntaxShape, Type, engine::StateWorkingSet,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum ShapeDescriptorUse {
|
||||
/// Used in an argument position allowing the addition of custom completion
|
||||
Argument,
|
||||
/// Used to define the type of a variable or input/output types
|
||||
Type,
|
||||
}
|
||||
|
||||
/// equivalent to [`parse_shape_name`] with [`ShapeDescriptorUse::Type`] converting the
|
||||
/// [`SyntaxShape`] to its [`Type`]
|
||||
/// [`parse_shape_name`] then convert to Type
|
||||
pub fn parse_type(working_set: &mut StateWorkingSet, bytes: &[u8], span: Span) -> Type {
|
||||
parse_shape_name(working_set, bytes, span, ShapeDescriptorUse::Type).to_type()
|
||||
parse_shape_name(working_set, bytes, span).to_type()
|
||||
}
|
||||
|
||||
/// Parse the literals of [`Type`]-like [`SyntaxShape`]s including inner types.
|
||||
/// Also handles the specification of custom completions with `type@completer`.
|
||||
///
|
||||
/// Restrict the parsing with `use_loc`
|
||||
/// Used in:
|
||||
/// - [`ShapeDescriptorUse::Argument`]
|
||||
/// - `: ` argument type (+completer) positions in signatures
|
||||
/// - [`ShapeDescriptorUse::Type`]
|
||||
/// - `type->type` input/output type pairs
|
||||
/// - `let name: type` variable type infos
|
||||
///
|
||||
/// NOTE: Does not provide a mapping to every [`SyntaxShape`]
|
||||
pub fn parse_shape_name(
|
||||
working_set: &mut StateWorkingSet,
|
||||
bytes: &[u8],
|
||||
span: Span,
|
||||
use_loc: ShapeDescriptorUse,
|
||||
) -> SyntaxShape {
|
||||
match bytes {
|
||||
b"any" => SyntaxShape::Any,
|
||||
@ -70,71 +51,54 @@ pub fn parse_shape_name(
|
||||
|| bytes.starts_with(b"record")
|
||||
|| bytes.starts_with(b"table") =>
|
||||
{
|
||||
parse_generic_shape(working_set, bytes, span, use_loc)
|
||||
parse_generic_shape(working_set, bytes, span)
|
||||
}
|
||||
_ => {
|
||||
if bytes.contains(&b'@') {
|
||||
let mut split = bytes.splitn(2, |b| b == &b'@');
|
||||
|
||||
let shape_name = split
|
||||
.next()
|
||||
.expect("If `bytes` contains `@` splitn returns 2 slices");
|
||||
let shape_span = Span::new(span.start, span.start + shape_name.len());
|
||||
let shape = parse_shape_name(working_set, shape_name, shape_span, use_loc);
|
||||
if use_loc != ShapeDescriptorUse::Argument {
|
||||
let illegal_span = Span::new(span.start + shape_name.len(), span.end);
|
||||
working_set.error(ParseError::LabeledError(
|
||||
"Unexpected custom completer in type spec".into(),
|
||||
"Type specifications do not support custom completers".into(),
|
||||
illegal_span,
|
||||
));
|
||||
return shape;
|
||||
}
|
||||
|
||||
let cmd_span = Span::new(span.start + shape_name.len() + 1, span.end);
|
||||
let cmd_name = split
|
||||
.next()
|
||||
.expect("If `bytes` contains `@` splitn returns 2 slices");
|
||||
|
||||
let cmd_name = trim_quotes(cmd_name);
|
||||
if cmd_name.is_empty() {
|
||||
working_set.error(ParseError::Expected(
|
||||
"the command name of a completion function",
|
||||
cmd_span,
|
||||
));
|
||||
return shape;
|
||||
}
|
||||
|
||||
if let Some(decl_id) = working_set.find_decl(cmd_name) {
|
||||
SyntaxShape::CompleterWrapper(Box::new(shape), decl_id)
|
||||
} else {
|
||||
working_set.error(ParseError::UnknownCommand(cmd_span));
|
||||
shape
|
||||
}
|
||||
} else {
|
||||
//TODO: Handle error case for unknown shapes
|
||||
working_set.error(ParseError::UnknownType(span));
|
||||
SyntaxShape::Any
|
||||
working_set.error(ParseError::LabeledError(
|
||||
"Unexpected custom completer in type spec".into(),
|
||||
"Type specifications do not support custom completers".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
//TODO: Handle error case for unknown shapes
|
||||
working_set.error(ParseError::UnknownType(span));
|
||||
SyntaxShape::Any
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the specification of custom completions with `type@completer`.
|
||||
pub fn parse_completer(
|
||||
working_set: &mut StateWorkingSet,
|
||||
bytes: &[u8],
|
||||
span: Span,
|
||||
) -> Option<DeclId> {
|
||||
let cmd_name = trim_quotes(bytes);
|
||||
if cmd_name.is_empty() {
|
||||
working_set.error(ParseError::Expected(
|
||||
"the command name of a completion function",
|
||||
span,
|
||||
));
|
||||
return None;
|
||||
}
|
||||
working_set.find_decl(cmd_name)
|
||||
}
|
||||
|
||||
fn parse_generic_shape(
|
||||
working_set: &mut StateWorkingSet<'_>,
|
||||
bytes: &[u8],
|
||||
span: Span,
|
||||
use_loc: ShapeDescriptorUse,
|
||||
) -> SyntaxShape {
|
||||
let (type_name, type_params) = split_generic_params(working_set, bytes, span);
|
||||
match type_name {
|
||||
b"oneof" => SyntaxShape::OneOf(match type_params {
|
||||
Some(params) => parse_type_params(working_set, params, use_loc),
|
||||
Some(params) => parse_type_params(working_set, params),
|
||||
None => vec![],
|
||||
}),
|
||||
b"list" => SyntaxShape::List(Box::new(match type_params {
|
||||
Some(params) => {
|
||||
let mut parsed_params = parse_type_params(working_set, params, use_loc);
|
||||
let mut parsed_params = parse_type_params(working_set, params);
|
||||
if parsed_params.len() > 1 {
|
||||
working_set.error(ParseError::LabeledError(
|
||||
"expected a single type parameter".into(),
|
||||
@ -149,11 +113,11 @@ fn parse_generic_shape(
|
||||
None => SyntaxShape::Any,
|
||||
})),
|
||||
b"record" => SyntaxShape::Record(match type_params {
|
||||
Some(params) => parse_named_type_params(working_set, params, use_loc),
|
||||
Some(params) => parse_named_type_params(working_set, params),
|
||||
None => vec![],
|
||||
}),
|
||||
b"table" => SyntaxShape::Table(match type_params {
|
||||
Some(params) => parse_named_type_params(working_set, params, use_loc),
|
||||
Some(params) => parse_named_type_params(working_set, params),
|
||||
None => vec![],
|
||||
}),
|
||||
_ => {
|
||||
@ -204,7 +168,6 @@ fn split_generic_params<'a>(
|
||||
fn parse_named_type_params(
|
||||
working_set: &mut StateWorkingSet,
|
||||
Spanned { item: source, span }: Spanned<&[u8]>,
|
||||
use_loc: ShapeDescriptorUse,
|
||||
) -> Vec<(String, SyntaxShape)> {
|
||||
let (tokens, err) = lex_signature(source, span.start, &[b'\n', b'\r'], &[b':', b','], true);
|
||||
|
||||
@ -279,7 +242,7 @@ fn parse_named_type_params(
|
||||
}
|
||||
|
||||
let shape_bytes = working_set.get_span_contents(tokens[idx].span).to_vec();
|
||||
let shape = parse_shape_name(working_set, &shape_bytes, tokens[idx].span, use_loc);
|
||||
let shape = parse_shape_name(working_set, &shape_bytes, tokens[idx].span);
|
||||
sig.push((key, shape));
|
||||
idx += 1;
|
||||
}
|
||||
@ -290,7 +253,6 @@ fn parse_named_type_params(
|
||||
fn parse_type_params(
|
||||
working_set: &mut StateWorkingSet,
|
||||
Spanned { item: source, span }: Spanned<&[u8]>,
|
||||
use_loc: ShapeDescriptorUse,
|
||||
) -> Vec<SyntaxShape> {
|
||||
let (tokens, err) = lex_signature(source, span.start, &[b'\n', b'\r'], &[b':', b','], true);
|
||||
|
||||
@ -312,7 +274,7 @@ fn parse_type_params(
|
||||
}
|
||||
|
||||
let shape_bytes = working_set.get_span_contents(tokens[idx].span).to_vec();
|
||||
let shape = parse_shape_name(working_set, &shape_bytes, tokens[idx].span, use_loc);
|
||||
let shape = parse_shape_name(working_set, &shape_bytes, tokens[idx].span);
|
||||
sig.push(shape);
|
||||
idx += 1;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use crate::{
|
||||
lite_parser::{LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget, lite_parse},
|
||||
parse_keywords::*,
|
||||
parse_patterns::parse_pattern,
|
||||
parse_shape_specs::{ShapeDescriptorUse, parse_shape_name, parse_type},
|
||||
parse_shape_specs::{parse_completer, parse_shape_name, parse_type},
|
||||
type_check::{self, check_range_types, math_result_type, type_compatible},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@ -1071,6 +1071,7 @@ pub fn parse_internal_call(
|
||||
desc: "".to_string(),
|
||||
var_id: None,
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1324,7 +1325,6 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
||||
span: _,
|
||||
span_id: _,
|
||||
ty,
|
||||
custom_completion,
|
||||
} = &alias.clone().wrapped_call
|
||||
{
|
||||
trace!("parsing: alias of external call");
|
||||
@ -1338,14 +1338,13 @@ pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span)
|
||||
final_args.push(arg);
|
||||
}
|
||||
|
||||
let mut expression = Expression::new(
|
||||
let expression = Expression::new(
|
||||
working_set,
|
||||
Expr::ExternalCall(head, final_args.into()),
|
||||
Span::concat(spans),
|
||||
ty.clone(),
|
||||
);
|
||||
|
||||
expression.custom_completion = *custom_completion;
|
||||
return expression;
|
||||
} else {
|
||||
trace!("parsing: alias of internal call");
|
||||
@ -3756,6 +3755,7 @@ pub fn parse_row_condition(working_set: &mut StateWorkingSet, spans: &[Span]) ->
|
||||
shape: SyntaxShape::Any,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
});
|
||||
|
||||
compile_block(working_set, &mut block);
|
||||
@ -3940,6 +3940,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
required: false,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
type_annotated: false,
|
||||
});
|
||||
@ -4001,6 +4002,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
required: false,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
type_annotated: false,
|
||||
});
|
||||
@ -4043,6 +4045,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
required: false,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
type_annotated: false,
|
||||
});
|
||||
@ -4110,6 +4113,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
shape: SyntaxShape::Any,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
required: false,
|
||||
type_annotated: false,
|
||||
@ -4137,6 +4141,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
shape: SyntaxShape::Any,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
}));
|
||||
parse_mode = ParseMode::Arg;
|
||||
}
|
||||
@ -4163,6 +4168,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
shape: SyntaxShape::Any,
|
||||
var_id: Some(var_id),
|
||||
default_value: None,
|
||||
custom_completion: None,
|
||||
},
|
||||
required: true,
|
||||
type_annotated: false,
|
||||
@ -4172,31 +4178,62 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
}
|
||||
ParseMode::Type => {
|
||||
if let Some(last) = args.last_mut() {
|
||||
let syntax_shape = parse_shape_name(
|
||||
working_set,
|
||||
&contents,
|
||||
span,
|
||||
ShapeDescriptorUse::Argument,
|
||||
);
|
||||
let (syntax_shape, completer) = if contents.contains(&b'@') {
|
||||
let mut split = contents.splitn(2, |b| b == &b'@');
|
||||
|
||||
let shape_name = split
|
||||
.next()
|
||||
.expect("If `bytes` contains `@` splitn returns 2 slices");
|
||||
let shape_span =
|
||||
Span::new(span.start, span.start + shape_name.len());
|
||||
let cmd_span =
|
||||
Span::new(span.start + shape_name.len() + 1, span.end);
|
||||
let cmd_name = split
|
||||
.next()
|
||||
.expect("If `bytes` contains `@` splitn returns 2 slices");
|
||||
(
|
||||
parse_shape_name(working_set, shape_name, shape_span),
|
||||
parse_completer(working_set, cmd_name, cmd_span),
|
||||
)
|
||||
} else {
|
||||
(parse_shape_name(working_set, &contents, span), None)
|
||||
};
|
||||
//TODO check if we're replacing a custom parameter already
|
||||
match last {
|
||||
Arg::Positional {
|
||||
arg: PositionalArg { shape, var_id, .. },
|
||||
arg:
|
||||
PositionalArg {
|
||||
shape,
|
||||
var_id,
|
||||
custom_completion,
|
||||
..
|
||||
},
|
||||
required: _,
|
||||
type_annotated,
|
||||
} => {
|
||||
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
|
||||
*custom_completion = completer;
|
||||
*shape = syntax_shape;
|
||||
*type_annotated = true;
|
||||
}
|
||||
Arg::RestPositional(PositionalArg {
|
||||
shape, var_id, ..
|
||||
shape,
|
||||
var_id,
|
||||
custom_completion,
|
||||
..
|
||||
}) => {
|
||||
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), Type::List(Box::new(syntax_shape.to_type())));
|
||||
*custom_completion = completer;
|
||||
*shape = syntax_shape;
|
||||
}
|
||||
Arg::Flag {
|
||||
flag: Flag { arg, var_id, .. },
|
||||
flag:
|
||||
Flag {
|
||||
arg,
|
||||
var_id,
|
||||
custom_completion,
|
||||
..
|
||||
},
|
||||
type_annotated,
|
||||
} => {
|
||||
working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
|
||||
@ -4207,6 +4244,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
|
||||
span,
|
||||
));
|
||||
}
|
||||
*custom_completion = completer;
|
||||
*arg = Some(syntax_shape);
|
||||
*type_annotated = true;
|
||||
}
|
||||
@ -5163,11 +5201,6 @@ pub fn parse_value(
|
||||
}
|
||||
|
||||
match shape {
|
||||
SyntaxShape::CompleterWrapper(shape, custom_completion) => {
|
||||
let mut expression = parse_value(working_set, span, shape);
|
||||
expression.custom_completion = Some(*custom_completion);
|
||||
expression
|
||||
}
|
||||
SyntaxShape::Number => parse_number(working_set, span),
|
||||
SyntaxShape::Float => parse_float(working_set, span),
|
||||
SyntaxShape::Int => parse_int(working_set, span),
|
||||
|
Reference in New Issue
Block a user