summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuki Tanaka <uki00a@gmail.com>2021-02-16 11:34:09 +0900
committerGitHub <noreply@github.com>2021-02-16 13:34:09 +1100
commitccd6ee5c2394418c078f1a1be9e5cc1012829cbc (patch)
tree34d289fd504a89493de295ae9cd9a1cc771fede6
parent3f5265b21ec578e543d09cdc9d8b19d9655aebd9 (diff)
feat(lsp): Implement `textDocument/signatureHelp` (#9330)
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
-rw-r--r--cli/lsp/capabilities.rs13
-rw-r--r--cli/lsp/language_server.rs143
-rw-r--r--cli/lsp/tsc.rs151
-rw-r--r--cli/tests/lsp/signature_help_did_change_notification.json25
-rw-r--r--cli/tests/lsp/signature_help_did_open_notification.json12
-rw-r--r--cli/tests/lsp/signature_help_request_01.json19
-rw-r--r--cli/tests/lsp/signature_help_request_02.json14
-rw-r--r--cli/tsc/99_main_compiler.js10
-rw-r--r--cli/tsc/compiler.d.ts8
9 files changed, 382 insertions, 13 deletions
diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs
index 9eed85b73..fb16db88b 100644
--- a/cli/lsp/capabilities.rs
+++ b/cli/lsp/capabilities.rs
@@ -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,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 6594492a4..1501249e8 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -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)]
@@ -2539,6 +2608,80 @@ mod tests {
}
#[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![
("initialize_request.json", LspResponse::RequestAny),
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index ef57682f1..5dc7200b7 100644
--- a/cli/lsp/tsc.rs
+++ b/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",
diff --git a/cli/tests/lsp/signature_help_did_change_notification.json b/cli/tests/lsp/signature_help_did_change_notification.json
new file mode 100644
index 000000000..f88eaa9ff
--- /dev/null
+++ b/cli/tests/lsp/signature_help_did_change_notification.json
@@ -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, "
+ }
+ ]
+ }
+}
diff --git a/cli/tests/lsp/signature_help_did_open_notification.json b/cli/tests/lsp/signature_help_did_open_notification.json
new file mode 100644
index 000000000..1ba1f7586
--- /dev/null
+++ b/cli/tests/lsp/signature_help_did_open_notification.json
@@ -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("
+ }
+ }
+}
diff --git a/cli/tests/lsp/signature_help_request_01.json b/cli/tests/lsp/signature_help_request_01.json
new file mode 100644
index 000000000..c3e185e08
--- /dev/null
+++ b/cli/tests/lsp/signature_help_request_01.json
@@ -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
+ }
+ }
+}
diff --git a/cli/tests/lsp/signature_help_request_02.json b/cli/tests/lsp/signature_help_request_02.json
new file mode 100644
index 000000000..c2a6e0abb
--- /dev/null
+++ b/cli/tests/lsp/signature_help_request_02.json
@@ -0,0 +1,14 @@
+{
+ "id": 2,
+ "jsonrpc": "2.0",
+ "method": "textDocument/signatureHelp",
+ "params": {
+ "textDocument": {
+ "uri": "file:///a/file.ts"
+ },
+ "position": {
+ "character": 8,
+ "line": 9
+ }
+ }
+}
diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js
index 50631e83f..f8eabc890 100644
--- a/cli/tsc/99_main_compiler.js
+++ b/cli/tsc/99_main_compiler.js
@@ -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,
diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts
index 4e5dcdb96..7d102eb56 100644
--- a/cli/tsc/compiler.d.ts
+++ b/cli/tsc/compiler.d.ts
@@ -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";
}