diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/lsp/capabilities.rs | 11 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 45 | ||||
-rw-r--r-- | cli/lsp/semantic_tokens.rs | 14 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 113 | ||||
-rw-r--r-- | cli/tests/integration/lsp_tests.rs | 114 | ||||
-rw-r--r-- | cli/tests/testdata/lsp/document_symbol_response.json | 12 | ||||
-rw-r--r-- | cli/tsc/99_main_compiler.js | 10 | ||||
-rw-r--r-- | cli/tsc/compiler.d.ts | 8 |
8 files changed, 303 insertions, 24 deletions
diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index a664c296d..e05cc27b8 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -12,6 +12,7 @@ use lspower::lsp::CodeActionOptions; use lspower::lsp::CodeActionProviderCapability; use lspower::lsp::CodeLensOptions; use lspower::lsp::CompletionOptions; +use lspower::lsp::DocumentSymbolOptions; use lspower::lsp::FoldingRangeProviderCapability; use lspower::lsp::HoverProviderCapability; use lspower::lsp::ImplementationProviderCapability; @@ -114,9 +115,13 @@ pub fn server_capabilities( )), references_provider: Some(OneOf::Left(true)), document_highlight_provider: Some(OneOf::Left(true)), - // TODO: Provide a label once https://github.com/gluon-lang/lsp-types/pull/207 is merged - document_symbol_provider: Some(OneOf::Left(true)), - workspace_symbol_provider: None, + document_symbol_provider: Some(OneOf::Right(DocumentSymbolOptions { + label: Some("Deno".to_string()), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: None, + }, + })), + workspace_symbol_provider: Some(OneOf::Left(true)), code_action_provider: Some(code_action_provider), code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true), diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 29138c762..fb2b04214 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -2270,6 +2270,44 @@ impl Inner { Ok(None) } } + + async fn symbol( + &mut self, + params: WorkspaceSymbolParams, + ) -> LspResult<Option<Vec<SymbolInformation>>> { + let mark = self.performance.mark("symbol", Some(¶ms)); + + let req = tsc::RequestMethod::GetNavigateToItems { + search: params.query, + // this matches vscode's hard coded result count + max_result_count: Some(256), + file: None, + }; + + let navigate_to_items: Vec<tsc::NavigateToItem> = self + .ts_server + .request(self.snapshot()?, req) + .await + .map_err(|err| { + error!("Failed request to tsserver: {}", err); + LspError::invalid_request() + })?; + + let maybe_symbol_information = if navigate_to_items.is_empty() { + None + } else { + let mut symbol_information = Vec::new(); + for item in navigate_to_items { + if let Some(info) = item.to_symbol_information(self).await { + symbol_information.push(info); + } + } + Some(symbol_information) + }; + + self.performance.measure(mark); + Ok(maybe_symbol_information) + } } #[lspower::async_trait] @@ -2481,6 +2519,13 @@ impl lspower::LanguageServer for LanguageServer { ) -> LspResult<Option<SignatureHelp>> { self.0.lock().await.signature_help(params).await } + + async fn symbol( + &self, + params: WorkspaceSymbolParams, + ) -> LspResult<Option<Vec<SymbolInformation>>> { + self.0.lock().await.symbol(params).await + } } // These are implementations of custom commands supported by the LSP diff --git a/cli/lsp/semantic_tokens.rs b/cli/lsp/semantic_tokens.rs index fe95a7377..326eb7dde 100644 --- a/cli/lsp/semantic_tokens.rs +++ b/cli/lsp/semantic_tokens.rs @@ -12,6 +12,9 @@ use lspower::lsp::SemanticTokens; use lspower::lsp::SemanticTokensLegend; use std::ops::{Index, IndexMut}; +pub(crate) const MODIFIER_MASK: u32 = 255; +pub(crate) const TYPE_OFFSET: u32 = 8; + enum TokenType { Class = 0, Enum = 1, @@ -78,12 +81,12 @@ pub fn get_legend() -> SemanticTokensLegend { token_types[TokenType::Method] = "method".into(); let mut token_modifiers = vec![SemanticTokenModifier::from(""); 6]; - token_modifiers[TokenModifier::Declaration] = "declaration".into(); - token_modifiers[TokenModifier::Static] = "static".into(); token_modifiers[TokenModifier::Async] = "async".into(); + token_modifiers[TokenModifier::Declaration] = "declaration".into(); token_modifiers[TokenModifier::Readonly] = "readonly".into(); - token_modifiers[TokenModifier::DefaultLibrary] = "defaultLibrary".into(); + token_modifiers[TokenModifier::Static] = "static".into(); token_modifiers[TokenModifier::Local] = "local".into(); + token_modifiers[TokenModifier::DefaultLibrary] = "defaultLibrary".into(); SemanticTokensLegend { token_types, @@ -91,11 +94,6 @@ pub fn get_legend() -> SemanticTokensLegend { } } -pub enum TsTokenEncodingConsts { - TypeOffset = 8, - ModifierMask = 255, -} - pub struct SemanticTokensBuilder { prev_line: u32, prev_char: u32, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 7829a2fda..e56b7ab68 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -9,8 +9,8 @@ use super::refactor::ALL_KNOWN_REFACTOR_ACTION_KINDS; use super::refactor::EXTRACT_CONSTANT; use super::refactor::EXTRACT_INTERFACE; use super::refactor::EXTRACT_TYPE; +use super::semantic_tokens; use super::semantic_tokens::SemanticTokensBuilder; -use super::semantic_tokens::TsTokenEncodingConsts; use super::text; use super::text::LineIndex; use super::urls::INVALID_SPECIFIER; @@ -507,6 +507,9 @@ impl From<ScriptElementKind> for lsp::SymbolKind { fn from(kind: ScriptElementKind) -> Self { match kind { ScriptElementKind::ModuleElement => Self::Module, + // this is only present in `getSymbolKind` in `workspaceSymbols` in + // vscode, but seems strange it isn't consistent. + ScriptElementKind::TypeElement => Self::Class, ScriptElementKind::ClassElement => Self::Class, ScriptElementKind::EnumElement => Self::Enum, ScriptElementKind::EnumMemberElement => Self::EnumMember, @@ -514,9 +517,12 @@ impl From<ScriptElementKind> for lsp::SymbolKind { ScriptElementKind::IndexSignatureElement => Self::Method, ScriptElementKind::CallSignatureElement => Self::Method, ScriptElementKind::MemberFunctionElement => Self::Method, - ScriptElementKind::MemberVariableElement => Self::Property, - ScriptElementKind::MemberGetAccessorElement => Self::Property, - ScriptElementKind::MemberSetAccessorElement => Self::Property, + // workspaceSymbols in vscode treats them as fields, which does seem more + // semantically correct while `fromProtocolScriptElementKind` treats them + // as properties. + ScriptElementKind::MemberVariableElement => Self::Field, + ScriptElementKind::MemberGetAccessorElement => Self::Field, + ScriptElementKind::MemberSetAccessorElement => Self::Field, ScriptElementKind::VariableElement => Self::Variable, ScriptElementKind::LetElement => Self::Variable, ScriptElementKind::ConstElement => Self::Variable, @@ -673,6 +679,71 @@ impl DocumentSpan { } #[derive(Debug, Clone, Deserialize)] +pub enum MatchKind { + #[serde(rename = "exact")] + Exact, + #[serde(rename = "prefix")] + Prefix, + #[serde(rename = "substring")] + Substring, + #[serde(rename = "camelCase")] + CamelCase, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NavigateToItem { + name: String, + kind: ScriptElementKind, + kind_modifiers: String, + match_kind: MatchKind, + is_case_sensitive: bool, + file_name: String, + text_span: TextSpan, + container_name: Option<String>, + container_kind: ScriptElementKind, +} + +impl NavigateToItem { + pub(crate) async fn to_symbol_information( + &self, + language_server: &mut language_server::Inner, + ) -> Option<lsp::SymbolInformation> { + let specifier = normalize_specifier(&self.file_name).ok()?; + let asset_or_doc = language_server + .get_asset_or_document(&specifier) + .await + .ok()?; + let line_index = asset_or_doc.line_index(); + let uri = language_server + .url_map + .normalize_specifier(&specifier) + .ok()?; + let range = self.text_span.to_range(line_index); + let location = lsp::Location { uri, range }; + + let mut tags: Option<Vec<lsp::SymbolTag>> = None; + let kind_modifiers = parse_kind_modifier(&self.kind_modifiers); + if kind_modifiers.contains("deprecated") { + tags = Some(vec![lsp::SymbolTag::Deprecated]); + } + + // The field `deprecated` is deprecated but SymbolInformation does not have + // a default, therefore we have to supply the deprecated deprecated + // field. It is like a bad version of Inception. + #[allow(deprecated)] + Some(lsp::SymbolInformation { + name: self.name.clone(), + kind: self.kind.clone().into(), + tags, + deprecated: None, + location, + container_name: self.container_name.clone(), + }) + } +} + +#[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NavigationTree { pub text: String, @@ -752,6 +823,16 @@ impl NavigationTree { } } + let name = match self.kind { + ScriptElementKind::MemberGetAccessorElement => { + format!("(get) {}", self.text) + } + ScriptElementKind::MemberSetAccessorElement => { + format!("(set) {}", self.text) + } + _ => self.text.clone(), + }; + let mut tags: Option<Vec<lsp::SymbolTag>> = None; let kind_modifiers = parse_kind_modifier(&self.kind_modifiers); if kind_modifiers.contains("deprecated") { @@ -769,7 +850,7 @@ impl NavigationTree { // field. It is like a bad version of Inception. #[allow(deprecated)] document_symbols.push(lsp::DocumentSymbol { - name: self.text.clone(), + name, kind: self.kind.clone().into(), range: span.to_range(line_index.clone()), selection_range: selection_span.to_range(line_index.clone()), @@ -1166,11 +1247,12 @@ impl Classifications { } fn get_token_type_from_classification(ts_classification: u32) -> u32 { - (ts_classification >> (TsTokenEncodingConsts::TypeOffset as u32)) - 1 + assert!(ts_classification > semantic_tokens::MODIFIER_MASK); + (ts_classification >> semantic_tokens::TYPE_OFFSET) - 1 } fn get_token_modifier_from_classification(ts_classification: u32) -> u32 { - ts_classification & (TsTokenEncodingConsts::ModifierMask as u32) + ts_classification & semantic_tokens::MODIFIER_MASK } } @@ -2667,6 +2749,12 @@ pub enum RequestMethod { GetEncodedSemanticClassifications((ModuleSpecifier, TextSpan)), /// Get implementation information for a specific position. GetImplementation((ModuleSpecifier, u32)), + /// Get "navigate to" items, which are converted to workspace symbols + GetNavigateToItems { + search: String, + max_result_count: Option<u32>, + file: Option<String>, + }, /// Get a "navigation tree" for a specifier. GetNavigationTree(ModuleSpecifier), /// Get outlining spans for a specifier. @@ -2808,6 +2896,17 @@ impl RequestMethod { "specifier": state.denormalize_specifier(specifier), "position": position, }), + RequestMethod::GetNavigateToItems { + search, + max_result_count, + file, + } => json!({ + "id": id, + "method": "getNavigateToItems", + "search": search, + "maxResultCount": max_result_count, + "file": file, + }), RequestMethod::GetNavigationTree(specifier) => json!({ "id": id, "method": "getNavigationTree", diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index c22bb3bdb..8e7de0286 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -3776,6 +3776,120 @@ fn lsp_configuration_did_change() { } #[test] +fn lsp_workspace_symbol() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export class A {\n fieldA: string;\n fieldB: string;\n}\n", + } + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export class B {\n fieldC: string;\n fieldD: string;\n}\n", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "workspace/symbol", + json!({ + "query": "field" + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "name": "fieldA", + "kind": 8, + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 17 + } + } + }, + "containerName": "A" + }, + { + "name": "fieldB", + "kind": 8, + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 17 + } + } + }, + "containerName": "A" + }, + { + "name": "fieldC", + "kind": 8, + "location": { + "uri": "file:///a/file_01.ts", + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 17 + } + } + }, + "containerName": "B" + }, + { + "name": "fieldD", + "kind": 8, + "location": { + "uri": "file:///a/file_01.ts", + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 17 + } + } + }, + "containerName": "B" + } + ])) + ); + shutdown(&mut client); +} + +#[test] fn lsp_code_actions_ignore_lint() { let mut client = init("initialize_params.json"); did_open( diff --git a/cli/tests/testdata/lsp/document_symbol_response.json b/cli/tests/testdata/lsp/document_symbol_response.json index 54c89cc09..90dd76411 100644 --- a/cli/tests/testdata/lsp/document_symbol_response.json +++ b/cli/tests/testdata/lsp/document_symbol_response.json @@ -148,7 +148,7 @@ }, { "name": "staticBar", - "kind": 7, + "kind": 8, "range": { "start": { "line": 11, @@ -171,8 +171,8 @@ } }, { - "name": "value", - "kind": 7, + "name": "(get) value", + "kind": 8, "range": { "start": { "line": 9, @@ -195,8 +195,8 @@ } }, { - "name": "value", - "kind": 7, + "name": "(set) value", + "kind": 8, "range": { "start": { "line": 10, @@ -220,7 +220,7 @@ }, { "name": "x", - "kind": 7, + "kind": 8, "range": { "start": { "line": 5, diff --git a/cli/tsc/99_main_compiler.js b/cli/tsc/99_main_compiler.js index ddc9f3c78..1b3999bf9 100644 --- a/cli/tsc/99_main_compiler.js +++ b/cli/tsc/99_main_compiler.js @@ -743,6 +743,16 @@ delete Object.prototype.__proto__; ), ); } + case "getNavigateToItems": { + return respond( + id, + languageService.getNavigateToItems( + request.search, + request.maxResultCount, + request.fileName, + ), + ); + } case "getNavigationTree": { return respond( id, diff --git a/cli/tsc/compiler.d.ts b/cli/tsc/compiler.d.ts index ff2e59e8e..8e6b6d417 100644 --- a/cli/tsc/compiler.d.ts +++ b/cli/tsc/compiler.d.ts @@ -58,6 +58,7 @@ declare global { | GetDocumentHighlightsRequest | GetEncodedSemanticClassifications | GetImplementationRequest + | GetNavigateToItems | GetNavigationTree | GetOutliningSpans | GetQuickInfoRequest @@ -173,6 +174,13 @@ declare global { position: number; } + interface GetNavigateToItems extends BaseLanguageServerRequest { + method: "getNavigateToItems"; + search: string; + maxResultCount?: number; + fileName?: string; + } + interface GetNavigationTree extends BaseLanguageServerRequest { method: "getNavigationTree"; specifier: string; |