mirror of
https://github.com/nushell/nushell.git
synced 2025-04-29 15:44:28 +02:00
fix(lsp): a panic caused by completion with decl_id out of range (#15576)
Fixes a bug caused by #15536 Sorry about that, @fdncred # Description I've made the panic reproducible in the test case. TLDR: completer will sometimes return new decl_ids outside of the range of the engine_state passed in. # User-Facing Changes bug fix # Tests + Formatting +1 # After Submitting
This commit is contained in:
parent
24cc2f9d87
commit
cd4560e97a
@ -69,7 +69,8 @@ impl Completer for ExportableCompletion<'_> {
|
|||||||
wrapped_name(name),
|
wrapped_name(name),
|
||||||
Some(cmd.description().to_string()),
|
Some(cmd.description().to_string()),
|
||||||
None,
|
None,
|
||||||
SuggestionKind::Command(cmd.command_type(), Some(*decl_id)),
|
// `None` here avoids arguments being expanded by snippet edit style for lsp
|
||||||
|
SuggestionKind::Command(cmd.command_type(), None),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,13 +68,19 @@ impl LanguageServer {
|
|||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
// use snippet as `insert_text_format` for command argument completion
|
// use snippet as `insert_text_format` for command argument completion
|
||||||
if let Some(SuggestionKind::Command(_, Some(decl_id))) = suggestion.kind {
|
if let Some(SuggestionKind::Command(_, Some(decl_id))) = suggestion.kind {
|
||||||
|
// NOTE: for new commands defined in current context,
|
||||||
|
// which are not present in the engine state, skip the documentation and snippet.
|
||||||
|
if engine_state.num_decls() > decl_id.get() {
|
||||||
let cmd = engine_state.get_decl(decl_id);
|
let cmd = engine_state.get_decl(decl_id);
|
||||||
doc_string = Some(Self::get_decl_description(cmd, true));
|
doc_string = Some(Self::get_decl_description(cmd, true));
|
||||||
insert_text_format = Some(InsertTextFormat::SNIPPET);
|
insert_text_format = Some(InsertTextFormat::SNIPPET);
|
||||||
let signature = cmd.signature();
|
let signature = cmd.signature();
|
||||||
// add curly brackets around block arguments
|
// add curly brackets around block arguments
|
||||||
// and keywords, e.g. `=` in `alias foo = bar`
|
// and keywords, e.g. `=` in `alias foo = bar`
|
||||||
let mut arg_wrapper = |arg: &PositionalArg, text: String, optional: bool| -> String {
|
let mut arg_wrapper = |arg: &PositionalArg,
|
||||||
|
text: String,
|
||||||
|
optional: bool|
|
||||||
|
-> String {
|
||||||
idx += 1;
|
idx += 1;
|
||||||
match &arg.shape {
|
match &arg.shape {
|
||||||
SyntaxShape::Block | SyntaxShape::MatchBlock => {
|
SyntaxShape::Block | SyntaxShape::MatchBlock => {
|
||||||
@ -108,14 +114,16 @@ impl LanguageServer {
|
|||||||
}
|
}
|
||||||
for optional in signature.optional_positional {
|
for optional in signature.optional_positional {
|
||||||
snippet_text.push(' ');
|
snippet_text.push(' ');
|
||||||
snippet_text
|
snippet_text.push_str(
|
||||||
.push_str(arg_wrapper(&optional, format!("{}?", optional.name), true).as_str());
|
arg_wrapper(&optional, format!("{}?", optional.name), true).as_str(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(rest) = signature.rest_positional {
|
if let Some(rest) = signature.rest_positional {
|
||||||
idx += 1;
|
idx += 1;
|
||||||
snippet_text.push_str(format!(" ${{{}:...{}}}", idx, rest.name).as_str());
|
snippet_text.push_str(format!(" ${{{}:...{}}}", idx, rest.name).as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// no extra space for a command with args expanded in the snippet
|
// no extra space for a command with args expanded in the snippet
|
||||||
if idx == 0 && suggestion.suggestion.append_whitespace {
|
if idx == 0 && suggestion.suggestion.append_whitespace {
|
||||||
snippet_text.push(' ');
|
snippet_text.push(' ');
|
||||||
@ -329,6 +337,17 @@ mod tests {
|
|||||||
})
|
})
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// fallback completion on a newly defined command,
|
||||||
|
// the decl_id is missing in the engine state, this shouldn't cause any panic.
|
||||||
|
let resp = send_complete_request(&client_connection, script.clone(), 13, 9);
|
||||||
|
assert_json_include!(
|
||||||
|
actual: result_from_message(resp),
|
||||||
|
expected: serde_json::json!([
|
||||||
|
// defined before the cursor
|
||||||
|
{ "label": "config n foo bar", "detail": detail_str, "kind": 2 },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
// inside parenthesis
|
// inside parenthesis
|
||||||
let resp = send_complete_request(&client_connection, script, 10, 34);
|
let resp = send_complete_request(&client_connection, script, 10, 34);
|
||||||
assert!(result_from_message(resp).as_array().unwrap().contains(
|
assert!(result_from_message(resp).as_array().unwrap().contains(
|
||||||
|
2
tests/fixtures/lsp/completion/command.nu
vendored
2
tests/fixtures/lsp/completion/command.nu
vendored
@ -10,3 +10,5 @@ def "config n foo bar" [
|
|||||||
echo "🤔🐘"
|
echo "🤔🐘"
|
||||||
| str substring (str substring -)
|
| str substring (str substring -)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config n # don't panic!
|
||||||
|
Loading…
Reference in New Issue
Block a user