mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat(lsp): Implement textDocument/signatureHelp
(#9330)
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
This commit is contained in:
parent
3f5265b21e
commit
ccd6ee5c23
9 changed files with 382 additions and 13 deletions
|
@ -16,6 +16,7 @@ use lspower::lsp::ImplementationProviderCapability;
|
|||
use lspower::lsp::OneOf;
|
||||
use lspower::lsp::SaveOptions;
|
||||
use lspower::lsp::ServerCapabilities;
|
||||
use lspower::lsp::SignatureHelpOptions;
|
||||
use lspower::lsp::TextDocumentSyncCapability;
|
||||
use lspower::lsp::TextDocumentSyncKind;
|
||||
use lspower::lsp::TextDocumentSyncOptions;
|
||||
|
@ -69,7 +70,17 @@ pub fn server_capabilities(
|
|||
work_done_progress: None,
|
||||
},
|
||||
}),
|
||||
signature_help_provider: None,
|
||||
signature_help_provider: Some(SignatureHelpOptions {
|
||||
trigger_characters: Some(vec![
|
||||
",".to_string(),
|
||||
"(".to_string(),
|
||||
"<".to_string(),
|
||||
]),
|
||||
retrigger_characters: None,
|
||||
work_done_progress_options: WorkDoneProgressOptions {
|
||||
work_done_progress: None,
|
||||
},
|
||||
}),
|
||||
declaration_provider: None,
|
||||
definition_provider: Some(OneOf::Left(true)),
|
||||
type_definition_provider: None,
|
||||
|
|
|
@ -1705,6 +1705,68 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn signature_help(
|
||||
&self,
|
||||
params: SignatureHelpParams,
|
||||
) -> LspResult<Option<SignatureHelp>> {
|
||||
if !self.enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mark = self.performance.mark("signature_help");
|
||||
let specifier = utils::normalize_url(
|
||||
params.text_document_position_params.text_document.uri,
|
||||
);
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
} else {
|
||||
return Err(LspError::invalid_params(format!(
|
||||
"An unexpected specifier ({}) was provided.",
|
||||
specifier
|
||||
)));
|
||||
};
|
||||
let options = if let Some(context) = params.context {
|
||||
tsc::SignatureHelpItemsOptions {
|
||||
trigger_reason: Some(tsc::SignatureHelpTriggerReason {
|
||||
kind: context.trigger_kind.into(),
|
||||
trigger_character: context.trigger_character,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
tsc::SignatureHelpItemsOptions {
|
||||
trigger_reason: None,
|
||||
}
|
||||
};
|
||||
let req = tsc::RequestMethod::GetSignatureHelpItems((
|
||||
specifier,
|
||||
line_index.offset_tsc(params.text_document_position_params.position)?,
|
||||
options,
|
||||
));
|
||||
let res =
|
||||
self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to request to tsserver: {}", err);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
let maybe_signature_help_items: Option<tsc::SignatureHelpItems> =
|
||||
serde_json::from_value(res).map_err(|err| {
|
||||
error!("Failed to deserialize tsserver response: {}", err);
|
||||
LspError::internal_error()
|
||||
})?;
|
||||
|
||||
if let Some(signature_help_items) = maybe_signature_help_items {
|
||||
let signature_help = signature_help_items.into_signature_help();
|
||||
self.performance.measure(mark);
|
||||
Ok(Some(signature_help))
|
||||
} else {
|
||||
self.performance.measure(mark);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[lspower::async_trait]
|
||||
|
@ -1840,6 +1902,13 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
) -> LspResult<Option<Value>> {
|
||||
self.0.lock().await.request_else(method, params).await
|
||||
}
|
||||
|
||||
async fn signature_help(
|
||||
&self,
|
||||
params: SignatureHelpParams,
|
||||
) -> LspResult<Option<SignatureHelp>> {
|
||||
self.0.lock().await.signature_help(params).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
|
@ -2538,6 +2607,80 @@ mod tests {
|
|||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_signature_help() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
("initialize_request.json", LspResponse::RequestAny),
|
||||
("initialized_notification.json", LspResponse::None),
|
||||
(
|
||||
"signature_help_did_open_notification.json",
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
"signature_help_request_01.json",
|
||||
LspResponse::Request(
|
||||
1,
|
||||
json!({
|
||||
"signatures": [
|
||||
{
|
||||
"label": "add(a: number, b: number): number",
|
||||
"documentation": "Adds two numbers.",
|
||||
"parameters": [
|
||||
{
|
||||
"label": "a: number",
|
||||
"documentation": "This is a first number."
|
||||
},
|
||||
{
|
||||
"label": "b: number",
|
||||
"documentation": "This is a second number."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"activeSignature": 0,
|
||||
"activeParameter": 0
|
||||
}),
|
||||
),
|
||||
),
|
||||
(
|
||||
"signature_help_did_change_notification.json",
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
"signature_help_request_02.json",
|
||||
LspResponse::Request(
|
||||
2,
|
||||
json!({
|
||||
"signatures": [
|
||||
{
|
||||
"label": "add(a: number, b: number): number",
|
||||
"documentation": "Adds two numbers.",
|
||||
"parameters": [
|
||||
{
|
||||
"label": "a: number",
|
||||
"documentation": "This is a first number."
|
||||
},
|
||||
{
|
||||
"label": "b: number",
|
||||
"documentation": "This is a second number."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"activeSignature": 0,
|
||||
"activeParameter": 1
|
||||
}),
|
||||
),
|
||||
),
|
||||
(
|
||||
"shutdown_request.json",
|
||||
LspResponse::Request(3, json!(null)),
|
||||
),
|
||||
("exit_notification.json", LspResponse::None),
|
||||
]);
|
||||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_code_lens_impl_request() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
|
|
151
cli/lsp/tsc.rs
151
cli/lsp/tsc.rs
|
@ -166,16 +166,12 @@ pub async fn get_asset(
|
|||
}
|
||||
}
|
||||
|
||||
fn display_parts_to_string(
|
||||
maybe_parts: Option<Vec<SymbolDisplayPart>>,
|
||||
) -> Option<String> {
|
||||
maybe_parts.map(|parts| {
|
||||
parts
|
||||
.into_iter()
|
||||
.map(|p| p.text)
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
})
|
||||
fn display_parts_to_string(parts: Vec<SymbolDisplayPart>) -> String {
|
||||
parts
|
||||
.into_iter()
|
||||
.map(|p| p.text)
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
}
|
||||
|
||||
fn get_tag_body_text(tag: &JSDocTagInfo) -> Option<String> {
|
||||
|
@ -433,7 +429,7 @@ impl QuickInfo {
|
|||
pub fn to_hover(&self, line_index: &LineIndex) -> lsp::Hover {
|
||||
let mut contents = Vec::<lsp::MarkedString>::new();
|
||||
if let Some(display_string) =
|
||||
display_parts_to_string(self.display_parts.clone())
|
||||
self.display_parts.clone().map(display_parts_to_string)
|
||||
{
|
||||
contents.push(lsp::MarkedString::from_language_code(
|
||||
"typescript".to_string(),
|
||||
|
@ -441,7 +437,7 @@ impl QuickInfo {
|
|||
));
|
||||
}
|
||||
if let Some(documentation) =
|
||||
display_parts_to_string(self.documentation.clone())
|
||||
self.documentation.clone().map(display_parts_to_string)
|
||||
{
|
||||
contents.push(lsp::MarkedString::from_markdown(documentation));
|
||||
}
|
||||
|
@ -946,6 +942,91 @@ impl CompletionEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpItems {
|
||||
items: Vec<SignatureHelpItem>,
|
||||
applicable_span: TextSpan,
|
||||
selected_item_index: u32,
|
||||
argument_index: u32,
|
||||
argument_count: u32,
|
||||
}
|
||||
|
||||
impl SignatureHelpItems {
|
||||
pub fn into_signature_help(self) -> lsp::SignatureHelp {
|
||||
lsp::SignatureHelp {
|
||||
signatures: self
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| item.into_signature_information())
|
||||
.collect(),
|
||||
active_parameter: Some(self.argument_index),
|
||||
active_signature: Some(self.selected_item_index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpItem {
|
||||
is_variadic: bool,
|
||||
prefix_display_parts: Vec<SymbolDisplayPart>,
|
||||
suffix_display_parts: Vec<SymbolDisplayPart>,
|
||||
separator_display_parts: Vec<SymbolDisplayPart>,
|
||||
parameters: Vec<SignatureHelpParameter>,
|
||||
documentation: Vec<SymbolDisplayPart>,
|
||||
tags: Vec<JSDocTagInfo>,
|
||||
}
|
||||
|
||||
impl SignatureHelpItem {
|
||||
pub fn into_signature_information(self) -> lsp::SignatureInformation {
|
||||
let prefix_text = display_parts_to_string(self.prefix_display_parts);
|
||||
let params_text = self
|
||||
.parameters
|
||||
.iter()
|
||||
.map(|param| display_parts_to_string(param.display_parts.clone()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
let suffix_text = display_parts_to_string(self.suffix_display_parts);
|
||||
lsp::SignatureInformation {
|
||||
label: format!("{}{}{}", prefix_text, params_text, suffix_text),
|
||||
documentation: Some(lsp::Documentation::String(display_parts_to_string(
|
||||
self.documentation,
|
||||
))),
|
||||
parameters: Some(
|
||||
self
|
||||
.parameters
|
||||
.into_iter()
|
||||
.map(|param| param.into_parameter_information())
|
||||
.collect(),
|
||||
),
|
||||
active_parameter: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpParameter {
|
||||
name: String,
|
||||
documentation: Vec<SymbolDisplayPart>,
|
||||
display_parts: Vec<SymbolDisplayPart>,
|
||||
is_optional: bool,
|
||||
}
|
||||
|
||||
impl SignatureHelpParameter {
|
||||
pub fn into_parameter_information(self) -> lsp::ParameterInformation {
|
||||
lsp::ParameterInformation {
|
||||
label: lsp::ParameterLabel::Simple(display_parts_to_string(
|
||||
self.display_parts,
|
||||
)),
|
||||
documentation: Some(lsp::Documentation::String(display_parts_to_string(
|
||||
self.documentation,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
struct Response {
|
||||
id: usize,
|
||||
|
@ -1361,6 +1442,41 @@ pub struct UserPreferences {
|
|||
pub provide_refactor_not_applicable_reason: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpItemsOptions {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_reason: Option<SignatureHelpTriggerReason>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum SignatureHelpTriggerKind {
|
||||
#[serde(rename = "characterTyped")]
|
||||
CharacterTyped,
|
||||
#[serde(rename = "invoked")]
|
||||
Invoked,
|
||||
#[serde(rename = "retrigger")]
|
||||
Retrigger,
|
||||
}
|
||||
|
||||
impl From<lsp::SignatureHelpTriggerKind> for SignatureHelpTriggerKind {
|
||||
fn from(kind: lsp::SignatureHelpTriggerKind) -> Self {
|
||||
match kind {
|
||||
lsp::SignatureHelpTriggerKind::Invoked => Self::Invoked,
|
||||
lsp::SignatureHelpTriggerKind::TriggerCharacter => Self::CharacterTyped,
|
||||
lsp::SignatureHelpTriggerKind::ContentChange => Self::Retrigger,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SignatureHelpTriggerReason {
|
||||
pub kind: SignatureHelpTriggerKind,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub trigger_character: Option<String>,
|
||||
}
|
||||
|
||||
/// Methods that are supported by the Language Service in the compiler isolate.
|
||||
#[derive(Debug)]
|
||||
pub enum RequestMethod {
|
||||
|
@ -1390,6 +1506,8 @@ pub enum RequestMethod {
|
|||
GetQuickInfo((ModuleSpecifier, u32)),
|
||||
/// Get document references for a specific position.
|
||||
GetReferences((ModuleSpecifier, u32)),
|
||||
/// Get signature help items for a specific position.
|
||||
GetSignatureHelpItems((ModuleSpecifier, u32, SignatureHelpItemsOptions)),
|
||||
/// Get the diagnostic codes that support some form of code fix.
|
||||
GetSupportedCodeFixes,
|
||||
}
|
||||
|
@ -1497,6 +1615,15 @@ impl RequestMethod {
|
|||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetSignatureHelpItems((specifier, position, options)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "getSignatureHelpItems",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
"options": options,
|
||||
})
|
||||
}
|
||||
RequestMethod::GetSupportedCodeFixes => json!({
|
||||
"id": id,
|
||||
"method": "getSupportedCodeFixes",
|
||||
|
|
25
cli/tests/lsp/signature_help_did_change_notification.json
Normal file
25
cli/tests/lsp/signature_help_did_change_notification.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didChange",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"version": 2
|
||||
},
|
||||
"contentChanges": [
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"character": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"character": 4
|
||||
}
|
||||
},
|
||||
"text": "123, "
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
12
cli/tests/lsp/signature_help_did_open_notification.json
Normal file
12
cli/tests/lsp/signature_help_did_open_notification.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didOpen",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "/**\n * Adds two numbers.\n * @param a This is a first number.\n * @param b This is a second number.\n */\nfunction add(a: number, b: number) {\n return a + b;\n}\n\nadd("
|
||||
}
|
||||
}
|
||||
}
|
19
cli/tests/lsp/signature_help_request_01.json
Normal file
19
cli/tests/lsp/signature_help_request_01.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "textDocument/signatureHelp",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
},
|
||||
"position": {
|
||||
"character": 4,
|
||||
"line": 9
|
||||
},
|
||||
"context": {
|
||||
"triggerKind": 2,
|
||||
"triggerCharacter": "(",
|
||||
"isRetrigger": false
|
||||
}
|
||||
}
|
||||
}
|
14
cli/tests/lsp/signature_help_request_02.json
Normal file
14
cli/tests/lsp/signature_help_request_02.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"id": 2,
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/signatureHelp",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
},
|
||||
"position": {
|
||||
"character": 8,
|
||||
"line": 9
|
||||
}
|
||||
}
|
||||
}
|
|
@ -677,6 +677,16 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
);
|
||||
}
|
||||
case "getSignatureHelpItems": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getSignatureHelpItems(
|
||||
request.specifier,
|
||||
request.position,
|
||||
request.options,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getSupportedCodeFixes": {
|
||||
return respond(
|
||||
id,
|
||||
|
|
8
cli/tsc/compiler.d.ts
vendored
8
cli/tsc/compiler.d.ts
vendored
|
@ -54,6 +54,7 @@ declare global {
|
|||
| GetNavigationTree
|
||||
| GetQuickInfoRequest
|
||||
| GetReferencesRequest
|
||||
| GetSignatureHelpItemsRequest
|
||||
| GetSupportedCodeFixes;
|
||||
|
||||
interface BaseLanguageServerRequest {
|
||||
|
@ -144,6 +145,13 @@ declare global {
|
|||
position: number;
|
||||
}
|
||||
|
||||
interface GetSignatureHelpItemsRequest extends BaseLanguageServerRequest {
|
||||
method: "getSignatureHelpItems";
|
||||
specifier: string;
|
||||
position: number;
|
||||
options: ts.SignatureHelpItemsOptions;
|
||||
}
|
||||
|
||||
interface GetSupportedCodeFixes extends BaseLanguageServerRequest {
|
||||
method: "getSupportedCodeFixes";
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue