summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/lsp/capabilities.rs11
-rw-r--r--cli/lsp/language_server.rs45
-rw-r--r--cli/lsp/semantic_tokens.rs14
-rw-r--r--cli/lsp/tsc.rs113
-rw-r--r--cli/tests/integration/lsp_tests.rs114
-rw-r--r--cli/tests/testdata/lsp/document_symbol_response.json12
-rw-r--r--cli/tsc/99_main_compiler.js10
-rw-r--r--cli/tsc/compiler.d.ts8
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(&params));
+
+ 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;