Send LSP Completion Item Kind (#11443)

# Description

This commit fills in the completion item kind into the
`textDocument/completion` response so that LSP client can present more
information to the user.

It is an improvement in the context of #10794

# User-Facing Changes

Improved information display in editor's intelli-sense menu


![output](https://github.com/nushell/nushell/assets/16558417/991dc0a9-45d1-4718-8f22-29002d687b93)
This commit is contained in:
Marc Schreiber
2024-03-25 02:14:12 +01:00
committed by GitHub
parent d1a8992590
commit e7bdd08a04
16 changed files with 384 additions and 199 deletions

View File

@ -11,18 +11,18 @@ use std::{
use lsp_server::{Connection, IoThreads, Message, Response, ResponseError};
use lsp_types::{
request::{Completion, GotoDefinition, HoverRequest, Request},
CompletionItem, CompletionParams, CompletionResponse, CompletionTextEdit, GotoDefinitionParams,
GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location, MarkupContent, MarkupKind,
OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit, Url,
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, CompletionTextEdit,
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, Location,
MarkupContent, MarkupKind, OneOf, Range, ServerCapabilities, TextDocumentSyncKind, TextEdit,
Url,
};
use miette::{IntoDiagnostic, Result};
use nu_cli::NuCompleter;
use nu_cli::{NuCompleter, SuggestionKind};
use nu_parser::{flatten_block, parse, FlatShape};
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
DeclId, Span, Value, VarId,
};
use reedline::Completer;
use ropey::Rope;
mod diagnostics;
@ -559,7 +559,8 @@ impl LanguageServer {
let location =
Self::lsp_position_to_location(&params.text_document_position.position, rope_of_file);
let results = completer.complete(&rope_of_file.to_string()[..location], location);
let results =
completer.fetch_completions_at(&rope_of_file.to_string()[..location], location);
if results.is_empty() {
None
} else {
@ -568,17 +569,18 @@ impl LanguageServer {
.into_iter()
.map(|r| {
let mut start = params.text_document_position.position;
start.character -= (r.span.end - r.span.start) as u32;
start.character -= (r.suggestion.span.end - r.suggestion.span.start) as u32;
CompletionItem {
label: r.value.clone(),
detail: r.description,
label: r.suggestion.value.clone(),
detail: r.suggestion.description,
kind: Self::lsp_completion_item_kind(r.kind),
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
range: Range {
start,
end: params.text_document_position.position,
},
new_text: r.value,
new_text: r.suggestion.value,
})),
..Default::default()
}
@ -587,12 +589,28 @@ impl LanguageServer {
))
}
}
fn lsp_completion_item_kind(
suggestion_kind: Option<SuggestionKind>,
) -> Option<CompletionItemKind> {
suggestion_kind.and_then(|suggestion_kind| match suggestion_kind {
SuggestionKind::Type(t) => match t {
nu_protocol::Type::String => Some(CompletionItemKind::VARIABLE),
_ => None,
},
SuggestionKind::Command(c) => match c {
nu_protocol::engine::CommandType::Keyword => Some(CompletionItemKind::KEYWORD),
nu_protocol::engine::CommandType::Builtin => Some(CompletionItemKind::FUNCTION),
_ => None,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_json_diff::assert_json_eq;
use assert_json_diff::{assert_json_eq, assert_json_include};
use lsp_types::{
notification::{
DidChangeTextDocument, DidOpenTextDocument, Exit, Initialized, Notification,
@ -1078,7 +1096,8 @@ mod tests {
"start": { "character": 5, "line": 2 },
"end": { "character": 9, "line": 2 }
}
}
},
"kind": 6
}
])
);
@ -1115,7 +1134,8 @@ mod tests {
"end": { "line": 0, "character": 8 },
},
"newText": "config nu"
}
},
"kind": 3
}
])
);
@ -1152,7 +1172,45 @@ mod tests {
"end": { "line": 0, "character": 14 },
},
"newText": "str trim"
}
},
"kind": 3
}
])
);
}
#[test]
fn complete_keyword() {
let (client_connection, _recv) = initialize_language_server();
let mut script = fixtures();
script.push("lsp");
script.push("completion");
script.push("keyword.nu");
let script = Url::from_file_path(script).unwrap();
open_unchecked(&client_connection, script.clone());
let resp = complete(&client_connection, script, 0, 2);
let result = if let Message::Response(response) = resp {
response.result
} else {
panic!()
};
assert_json_include!(
actual: result,
expected: serde_json::json!([
{
"label": "def",
"textEdit": {
"newText": "def",
"range": {
"start": { "character": 0, "line": 0 },
"end": { "character": 2, "line": 0 }
}
},
"kind": 14
}
])
);