diff options
26 files changed, 454 insertions, 242 deletions
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index f86f98be7..82e10bc7e 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -75,7 +75,7 @@ pub use crate::{ | |||
75 | runnables::{Runnable, RunnableKind, TestId}, | 75 | runnables::{Runnable, RunnableKind, TestId}, |
76 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, | 76 | source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, |
77 | ssr::SsrError, | 77 | ssr::SsrError, |
78 | syntax_highlighting::HighlightedRange, | 78 | syntax_highlighting::{tags, HighlightedRange}, |
79 | }; | 79 | }; |
80 | 80 | ||
81 | pub use hir::Documentation; | 81 | pub use hir::Documentation; |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index d873f153e..812229b4e 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -17,32 +17,32 @@ use crate::{ | |||
17 | }; | 17 | }; |
18 | 18 | ||
19 | pub mod tags { | 19 | pub mod tags { |
20 | pub(crate) const FIELD: &str = "field"; | 20 | pub const FIELD: &str = "field"; |
21 | pub(crate) const FUNCTION: &str = "function"; | 21 | pub const FUNCTION: &str = "function"; |
22 | pub(crate) const MODULE: &str = "module"; | 22 | pub const MODULE: &str = "module"; |
23 | pub(crate) const CONSTANT: &str = "constant"; | 23 | pub const CONSTANT: &str = "constant"; |
24 | pub(crate) const MACRO: &str = "macro"; | 24 | pub const MACRO: &str = "macro"; |
25 | 25 | ||
26 | pub(crate) const VARIABLE: &str = "variable"; | 26 | pub const VARIABLE: &str = "variable"; |
27 | pub(crate) const VARIABLE_MUT: &str = "variable.mut"; | 27 | pub const VARIABLE_MUT: &str = "variable.mut"; |
28 | 28 | ||
29 | pub(crate) const TYPE: &str = "type"; | 29 | pub const TYPE: &str = "type"; |
30 | pub(crate) const TYPE_BUILTIN: &str = "type.builtin"; | 30 | pub const TYPE_BUILTIN: &str = "type.builtin"; |
31 | pub(crate) const TYPE_SELF: &str = "type.self"; | 31 | pub const TYPE_SELF: &str = "type.self"; |
32 | pub(crate) const TYPE_PARAM: &str = "type.param"; | 32 | pub const TYPE_PARAM: &str = "type.param"; |
33 | pub(crate) const TYPE_LIFETIME: &str = "type.lifetime"; | 33 | pub const TYPE_LIFETIME: &str = "type.lifetime"; |
34 | 34 | ||
35 | pub(crate) const LITERAL_BYTE: &str = "literal.byte"; | 35 | pub const LITERAL_BYTE: &str = "literal.byte"; |
36 | pub(crate) const LITERAL_NUMERIC: &str = "literal.numeric"; | 36 | pub const LITERAL_NUMERIC: &str = "literal.numeric"; |
37 | pub(crate) const LITERAL_CHAR: &str = "literal.char"; | 37 | pub const LITERAL_CHAR: &str = "literal.char"; |
38 | 38 | ||
39 | pub(crate) const LITERAL_COMMENT: &str = "comment"; | 39 | pub const LITERAL_COMMENT: &str = "comment"; |
40 | pub(crate) const LITERAL_STRING: &str = "string"; | 40 | pub const LITERAL_STRING: &str = "string"; |
41 | pub(crate) const LITERAL_ATTRIBUTE: &str = "attribute"; | 41 | pub const LITERAL_ATTRIBUTE: &str = "attribute"; |
42 | 42 | ||
43 | pub(crate) const KEYWORD: &str = "keyword"; | 43 | pub const KEYWORD: &str = "keyword"; |
44 | pub(crate) const KEYWORD_UNSAFE: &str = "keyword.unsafe"; | 44 | pub const KEYWORD_UNSAFE: &str = "keyword.unsafe"; |
45 | pub(crate) const KEYWORD_CONTROL: &str = "keyword.control"; | 45 | pub const KEYWORD_CONTROL: &str = "keyword.control"; |
46 | } | 46 | } |
47 | 47 | ||
48 | #[derive(Debug)] | 48 | #[derive(Debug)] |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index c9fd645f1..638987ee8 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | //! Advertizes the capabilities of the LSP Server. | 1 | //! Advertizes the capabilities of the LSP Server. |
2 | 2 | ||
3 | use crate::semantic_tokens; | ||
4 | |||
3 | use lsp_types::{ | 5 | use lsp_types::{ |
4 | CallHierarchyServerCapability, CodeActionProviderCapability, CodeLensOptions, | 6 | CallHierarchyServerCapability, CodeActionProviderCapability, CodeLensOptions, |
5 | CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, | 7 | CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, |
6 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, | 8 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, |
7 | SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, | 9 | SelectionRangeProviderCapability, SemanticTokensDocumentProvider, SemanticTokensLegend, |
8 | TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, | 10 | SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, |
9 | TypeDefinitionProviderCapability, WorkDoneProgressOptions, | 11 | SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, |
12 | TextDocumentSyncOptions, TypeDefinitionProviderCapability, WorkDoneProgressOptions, | ||
10 | }; | 13 | }; |
11 | 14 | ||
12 | pub fn server_capabilities() -> ServerCapabilities { | 15 | pub fn server_capabilities() -> ServerCapabilities { |
@@ -57,7 +60,20 @@ pub fn server_capabilities() -> ServerCapabilities { | |||
57 | execute_command_provider: None, | 60 | execute_command_provider: None, |
58 | workspace: None, | 61 | workspace: None, |
59 | call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)), | 62 | call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)), |
60 | semantic_tokens_provider: None, | 63 | semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensOptions( |
64 | SemanticTokensOptions { | ||
65 | legend: SemanticTokensLegend { | ||
66 | token_types: semantic_tokens::supported_token_types().iter().cloned().collect(), | ||
67 | token_modifiers: semantic_tokens::supported_token_modifiers() | ||
68 | .iter() | ||
69 | .cloned() | ||
70 | .collect(), | ||
71 | }, | ||
72 | |||
73 | document_provider: Some(SemanticTokensDocumentProvider::Bool(true)), | ||
74 | ..SemanticTokensOptions::default() | ||
75 | }, | ||
76 | )), | ||
61 | experimental: Default::default(), | 77 | experimental: Default::default(), |
62 | } | 78 | } |
63 | } | 79 | } |
diff --git a/crates/rust-analyzer/src/conv.rs b/crates/rust-analyzer/src/conv.rs index 90ef74056..5fcb46b61 100644 --- a/crates/rust-analyzer/src/conv.rs +++ b/crates/rust-analyzer/src/conv.rs | |||
@@ -4,11 +4,12 @@ | |||
4 | use lsp_types::{ | 4 | use lsp_types::{ |
5 | self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation, | 5 | self, CreateFile, DiagnosticSeverity, DocumentChangeOperation, DocumentChanges, Documentation, |
6 | Location, LocationLink, MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp, | 6 | Location, LocationLink, MarkupContent, MarkupKind, Position, Range, RenameFile, ResourceOp, |
7 | SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem, | 7 | SemanticTokenModifier, SemanticTokenType, SymbolKind, TextDocumentEdit, TextDocumentIdentifier, |
8 | TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit, | 8 | TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, |
9 | WorkspaceEdit, | ||
9 | }; | 10 | }; |
10 | use ra_ide::{ | 11 | use ra_ide::{ |
11 | translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition, | 12 | tags, translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition, |
12 | FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex, | 13 | FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex, |
13 | NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit, | 14 | NavigationTarget, RangeInfo, ReferenceAccess, Severity, SourceChange, SourceFileEdit, |
14 | }; | 15 | }; |
@@ -16,7 +17,7 @@ use ra_syntax::{SyntaxKind, TextRange, TextUnit}; | |||
16 | use ra_text_edit::{AtomTextEdit, TextEdit}; | 17 | use ra_text_edit::{AtomTextEdit, TextEdit}; |
17 | use ra_vfs::LineEndings; | 18 | use ra_vfs::LineEndings; |
18 | 19 | ||
19 | use crate::{req, world::WorldSnapshot, Result}; | 20 | use crate::{req, semantic_tokens, world::WorldSnapshot, Result}; |
20 | 21 | ||
21 | pub trait Conv { | 22 | pub trait Conv { |
22 | type Output; | 23 | type Output; |
@@ -302,6 +303,76 @@ impl ConvWith<&FoldConvCtx<'_>> for Fold { | |||
302 | } | 303 | } |
303 | } | 304 | } |
304 | 305 | ||
306 | impl Conv for &'static str { | ||
307 | type Output = (SemanticTokenType, Vec<SemanticTokenModifier>); | ||
308 | |||
309 | fn conv(self) -> (SemanticTokenType, Vec<SemanticTokenModifier>) { | ||
310 | let token_type: SemanticTokenType = match self { | ||
311 | tags::FIELD => SemanticTokenType::MEMBER, | ||
312 | tags::FUNCTION => SemanticTokenType::FUNCTION, | ||
313 | tags::MODULE => SemanticTokenType::NAMESPACE, | ||
314 | tags::CONSTANT => { | ||
315 | return ( | ||
316 | SemanticTokenType::VARIABLE, | ||
317 | vec![SemanticTokenModifier::STATIC, SemanticTokenModifier::READONLY], | ||
318 | ) | ||
319 | } | ||
320 | tags::MACRO => SemanticTokenType::MACRO, | ||
321 | |||
322 | tags::VARIABLE => { | ||
323 | return (SemanticTokenType::VARIABLE, vec![SemanticTokenModifier::READONLY]) | ||
324 | } | ||
325 | tags::VARIABLE_MUT => SemanticTokenType::VARIABLE, | ||
326 | |||
327 | tags::TYPE => SemanticTokenType::TYPE, | ||
328 | tags::TYPE_BUILTIN => SemanticTokenType::TYPE, | ||
329 | tags::TYPE_SELF => { | ||
330 | return (SemanticTokenType::TYPE, vec![SemanticTokenModifier::REFERENCE]) | ||
331 | } | ||
332 | tags::TYPE_PARAM => SemanticTokenType::TYPE_PARAMETER, | ||
333 | tags::TYPE_LIFETIME => { | ||
334 | return (SemanticTokenType::LABEL, vec![SemanticTokenModifier::REFERENCE]) | ||
335 | } | ||
336 | |||
337 | tags::LITERAL_BYTE => SemanticTokenType::NUMBER, | ||
338 | tags::LITERAL_NUMERIC => SemanticTokenType::NUMBER, | ||
339 | tags::LITERAL_CHAR => SemanticTokenType::NUMBER, | ||
340 | |||
341 | tags::LITERAL_COMMENT => { | ||
342 | return (SemanticTokenType::COMMENT, vec![SemanticTokenModifier::DOCUMENTATION]) | ||
343 | } | ||
344 | |||
345 | tags::LITERAL_STRING => SemanticTokenType::STRING, | ||
346 | tags::LITERAL_ATTRIBUTE => SemanticTokenType::KEYWORD, | ||
347 | |||
348 | tags::KEYWORD => SemanticTokenType::KEYWORD, | ||
349 | tags::KEYWORD_UNSAFE => SemanticTokenType::KEYWORD, | ||
350 | tags::KEYWORD_CONTROL => SemanticTokenType::KEYWORD, | ||
351 | unknown => panic!("Unknown semantic token: {}", unknown), | ||
352 | }; | ||
353 | |||
354 | (token_type, vec![]) | ||
355 | } | ||
356 | } | ||
357 | |||
358 | impl Conv for (SemanticTokenType, Vec<SemanticTokenModifier>) { | ||
359 | type Output = (u32, u32); | ||
360 | |||
361 | fn conv(self) -> Self::Output { | ||
362 | let token_index = | ||
363 | semantic_tokens::supported_token_types().iter().position(|it| *it == self.0).unwrap(); | ||
364 | let mut token_modifier_bitset = 0; | ||
365 | for modifier in self.1.iter() { | ||
366 | token_modifier_bitset |= semantic_tokens::supported_token_modifiers() | ||
367 | .iter() | ||
368 | .position(|it| it == modifier) | ||
369 | .unwrap(); | ||
370 | } | ||
371 | |||
372 | (token_index as u32, token_modifier_bitset as u32) | ||
373 | } | ||
374 | } | ||
375 | |||
305 | impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> { | 376 | impl<T: ConvWith<CTX>, CTX> ConvWith<CTX> for Option<T> { |
306 | type Output = Option<T::Output>; | 377 | type Output = Option<T::Output>; |
307 | 378 | ||
diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 0dae30e46..a0f968823 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs | |||
@@ -36,6 +36,7 @@ pub mod req; | |||
36 | mod config; | 36 | mod config; |
37 | mod world; | 37 | mod world; |
38 | mod diagnostics; | 38 | mod diagnostics; |
39 | mod semantic_tokens; | ||
39 | 40 | ||
40 | use serde::de::DeserializeOwned; | 41 | use serde::de::DeserializeOwned; |
41 | 42 | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 98306986b..6e9e604a6 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -528,6 +528,7 @@ fn on_request( | |||
528 | .on::<req::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)? | 528 | .on::<req::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)? |
529 | .on::<req::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)? | 529 | .on::<req::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)? |
530 | .on::<req::Ssr>(handlers::handle_ssr)? | 530 | .on::<req::Ssr>(handlers::handle_ssr)? |
531 | .on::<req::SemanticTokensRequest>(handlers::handle_semantic_tokens)? | ||
531 | .finish(); | 532 | .finish(); |
532 | Ok(()) | 533 | Ok(()) |
533 | } | 534 | } |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index bb7bab372..e13e7c95a 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -16,8 +16,9 @@ use lsp_types::{ | |||
16 | CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem, | 16 | CodeAction, CodeActionOrCommand, CodeActionResponse, CodeLens, Command, CompletionItem, |
17 | Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, | 17 | Diagnostic, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, |
18 | FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, | 18 | FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, |
19 | PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier, | 19 | PrepareRenameResponse, Range, RenameParams, SemanticTokenModifier, SemanticTokenType, |
20 | TextEdit, WorkspaceEdit, | 20 | SemanticTokens, SemanticTokensParams, SemanticTokensResult, SymbolInformation, |
21 | TextDocumentIdentifier, TextEdit, WorkspaceEdit, | ||
21 | }; | 22 | }; |
22 | use ra_ide::{ | 23 | use ra_ide::{ |
23 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, | 24 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, |
@@ -38,6 +39,7 @@ use crate::{ | |||
38 | diagnostics::DiagnosticTask, | 39 | diagnostics::DiagnosticTask, |
39 | from_json, | 40 | from_json, |
40 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, | 41 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, |
42 | semantic_tokens::SemanticTokensBuilder, | ||
41 | world::WorldSnapshot, | 43 | world::WorldSnapshot, |
42 | LspError, Result, | 44 | LspError, Result, |
43 | }; | 45 | }; |
@@ -1068,3 +1070,25 @@ pub fn handle_call_hierarchy_outgoing( | |||
1068 | 1070 | ||
1069 | Ok(Some(res)) | 1071 | Ok(Some(res)) |
1070 | } | 1072 | } |
1073 | |||
1074 | pub fn handle_semantic_tokens( | ||
1075 | world: WorldSnapshot, | ||
1076 | params: SemanticTokensParams, | ||
1077 | ) -> Result<Option<SemanticTokensResult>> { | ||
1078 | let _p = profile("handle_semantic_tokens"); | ||
1079 | |||
1080 | let file_id = params.text_document.try_conv_with(&world)?; | ||
1081 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1082 | |||
1083 | let mut builder = SemanticTokensBuilder::default(); | ||
1084 | |||
1085 | for h in world.analysis().highlight(file_id)?.into_iter() { | ||
1086 | let type_and_modifiers: (SemanticTokenType, Vec<SemanticTokenModifier>) = h.tag.conv(); | ||
1087 | let (token_type, token_modifiers) = type_and_modifiers.conv(); | ||
1088 | builder.push(h.range.conv_with(&line_index), token_type, token_modifiers); | ||
1089 | } | ||
1090 | |||
1091 | let tokens = SemanticTokens { data: builder.build(), ..Default::default() }; | ||
1092 | |||
1093 | Ok(Some(tokens.into())) | ||
1094 | } | ||
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs index 7ff7f60b3..3734899bc 100644 --- a/crates/rust-analyzer/src/req.rs +++ b/crates/rust-analyzer/src/req.rs | |||
@@ -12,9 +12,9 @@ pub use lsp_types::{ | |||
12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, | 12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, |
13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, | 13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, |
14 | PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, SelectionRange, | 14 | PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, SelectionRange, |
15 | SelectionRangeParams, ServerCapabilities, ShowMessageParams, SignatureHelp, SymbolKind, | 15 | SelectionRangeParams, SemanticTokensParams, SemanticTokensResult, ServerCapabilities, |
16 | TextDocumentEdit, TextDocumentPositionParams, TextEdit, WorkDoneProgressParams, WorkspaceEdit, | 16 | ShowMessageParams, SignatureHelp, SymbolKind, TextDocumentEdit, TextDocumentPositionParams, |
17 | WorkspaceSymbolParams, | 17 | TextEdit, WorkDoneProgressParams, WorkspaceEdit, WorkspaceSymbolParams, |
18 | }; | 18 | }; |
19 | 19 | ||
20 | pub enum AnalyzerStatus {} | 20 | pub enum AnalyzerStatus {} |
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs new file mode 100644 index 000000000..ad000a3ce --- /dev/null +++ b/crates/rust-analyzer/src/semantic_tokens.rs | |||
@@ -0,0 +1,94 @@ | |||
1 | //! Semantic Tokens helpers | ||
2 | |||
3 | use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType}; | ||
4 | |||
5 | const SUPPORTED_TYPES: &[SemanticTokenType] = &[ | ||
6 | SemanticTokenType::COMMENT, | ||
7 | SemanticTokenType::KEYWORD, | ||
8 | SemanticTokenType::STRING, | ||
9 | SemanticTokenType::NUMBER, | ||
10 | SemanticTokenType::REGEXP, | ||
11 | SemanticTokenType::OPERATOR, | ||
12 | SemanticTokenType::NAMESPACE, | ||
13 | SemanticTokenType::TYPE, | ||
14 | SemanticTokenType::STRUCT, | ||
15 | SemanticTokenType::CLASS, | ||
16 | SemanticTokenType::INTERFACE, | ||
17 | SemanticTokenType::ENUM, | ||
18 | SemanticTokenType::TYPE_PARAMETER, | ||
19 | SemanticTokenType::FUNCTION, | ||
20 | SemanticTokenType::MEMBER, | ||
21 | SemanticTokenType::PROPERTY, | ||
22 | SemanticTokenType::MACRO, | ||
23 | SemanticTokenType::VARIABLE, | ||
24 | SemanticTokenType::PARAMETER, | ||
25 | SemanticTokenType::LABEL, | ||
26 | ]; | ||
27 | |||
28 | const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[ | ||
29 | SemanticTokenModifier::DOCUMENTATION, | ||
30 | SemanticTokenModifier::DECLARATION, | ||
31 | SemanticTokenModifier::DEFINITION, | ||
32 | SemanticTokenModifier::REFERENCE, | ||
33 | SemanticTokenModifier::STATIC, | ||
34 | SemanticTokenModifier::ABSTRACT, | ||
35 | SemanticTokenModifier::DEPRECATED, | ||
36 | SemanticTokenModifier::ASYNC, | ||
37 | SemanticTokenModifier::VOLATILE, | ||
38 | SemanticTokenModifier::READONLY, | ||
39 | ]; | ||
40 | |||
41 | /// Token types that the server supports | ||
42 | pub(crate) fn supported_token_types() -> &'static [SemanticTokenType] { | ||
43 | SUPPORTED_TYPES | ||
44 | } | ||
45 | |||
46 | /// Token modifiers that the server supports | ||
47 | pub(crate) fn supported_token_modifiers() -> &'static [SemanticTokenModifier] { | ||
48 | SUPPORTED_MODIFIERS | ||
49 | } | ||
50 | |||
51 | /// Tokens are encoded relative to each other. | ||
52 | /// | ||
53 | /// This is a direct port of https://github.com/microsoft/vscode-languageserver-node/blob/f425af9de46a0187adb78ec8a46b9b2ce80c5412/server/src/sematicTokens.proposed.ts#L45 | ||
54 | #[derive(Default)] | ||
55 | pub(crate) struct SemanticTokensBuilder { | ||
56 | prev_line: u32, | ||
57 | prev_char: u32, | ||
58 | data: Vec<SemanticToken>, | ||
59 | } | ||
60 | |||
61 | impl SemanticTokensBuilder { | ||
62 | /// Push a new token onto the builder | ||
63 | pub fn push(&mut self, range: Range, token_index: u32, modifier_bitset: u32) { | ||
64 | let mut push_line = range.start.line as u32; | ||
65 | let mut push_char = range.start.character as u32; | ||
66 | |||
67 | if !self.data.is_empty() { | ||
68 | push_line -= self.prev_line; | ||
69 | if push_line == 0 { | ||
70 | push_char -= self.prev_char; | ||
71 | } | ||
72 | } | ||
73 | |||
74 | // A token cannot be multiline | ||
75 | let token_len = range.end.character - range.start.character; | ||
76 | |||
77 | let token = SemanticToken { | ||
78 | delta_line: push_line, | ||
79 | delta_start: push_char, | ||
80 | length: token_len as u32, | ||
81 | token_type: token_index, | ||
82 | token_modifiers_bitset: modifier_bitset, | ||
83 | }; | ||
84 | |||
85 | self.data.push(token); | ||
86 | |||
87 | self.prev_line = range.start.line as u32; | ||
88 | self.prev_char = range.start.character as u32; | ||
89 | } | ||
90 | |||
91 | pub fn build(self) -> Vec<SemanticToken> { | ||
92 | self.data | ||
93 | } | ||
94 | } | ||
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json index 76ef2aae4..0288a468e 100644 --- a/editors/code/package-lock.json +++ b/editors/code/package-lock.json | |||
@@ -1575,9 +1575,9 @@ | |||
1575 | } | 1575 | } |
1576 | }, | 1576 | }, |
1577 | "typescript": { | 1577 | "typescript": { |
1578 | "version": "3.7.5", | 1578 | "version": "3.8.2", |
1579 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", | 1579 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.2.tgz", |
1580 | "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", | 1580 | "integrity": "sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==", |
1581 | "dev": true | 1581 | "dev": true |
1582 | }, | 1582 | }, |
1583 | "typescript-formatter": { | 1583 | "typescript-formatter": { |
diff --git a/editors/code/package.json b/editors/code/package.json index 9ef6c6983..dff535fcd 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -42,7 +42,7 @@ | |||
42 | "eslint": "^6.8.0", | 42 | "eslint": "^6.8.0", |
43 | "rollup": "^1.31.1", | 43 | "rollup": "^1.31.1", |
44 | "tslib": "^1.10.0", | 44 | "tslib": "^1.10.0", |
45 | "typescript": "^3.7.5", | 45 | "typescript": "^3.8.2", |
46 | "typescript-formatter": "^7.2.2", | 46 | "typescript-formatter": "^7.2.2", |
47 | "vsce": "^1.73.0" | 47 | "vsce": "^1.73.0" |
48 | }, | 48 | }, |
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts index 6631e8db7..1c6ea399b 100644 --- a/editors/code/src/commands/analyzer_status.ts +++ b/editors/code/src/commands/analyzer_status.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | 2 | ||
3 | import * as ra from '../rust-analyzer-api'; | ||
3 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
4 | 5 | ||
5 | // Shows status of rust-analyzer (for debugging) | 6 | // Shows status of rust-analyzer (for debugging) |
@@ -50,10 +51,7 @@ class TextDocumentContentProvider | |||
50 | const client = this.ctx.client; | 51 | const client = this.ctx.client; |
51 | if (!editor || !client) return ''; | 52 | if (!editor || !client) return ''; |
52 | 53 | ||
53 | return client.sendRequest<string>( | 54 | return client.sendRequest(ra.analyzerStatus, null); |
54 | 'rust-analyzer/analyzerStatus', | ||
55 | null, | ||
56 | ); | ||
57 | } | 55 | } |
58 | 56 | ||
59 | get onDidChange(): vscode.Event<vscode.Uri> { | 57 | get onDidChange(): vscode.Event<vscode.Uri> { |
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts index edec9bbc1..23f2ef1d5 100644 --- a/editors/code/src/commands/expand_macro.ts +++ b/editors/code/src/commands/expand_macro.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | 5 | ||
@@ -26,12 +26,7 @@ export function expandMacro(ctx: Ctx): Cmd { | |||
26 | }; | 26 | }; |
27 | } | 27 | } |
28 | 28 | ||
29 | interface ExpandedMacro { | 29 | function codeFormat(expanded: ra.ExpandedMacro): string { |
30 | name: string; | ||
31 | expansion: string; | ||
32 | } | ||
33 | |||
34 | function codeFormat(expanded: ExpandedMacro): string { | ||
35 | let result = `// Recursive expansion of ${expanded.name}! macro\n`; | 30 | let result = `// Recursive expansion of ${expanded.name}! macro\n`; |
36 | result += '// ' + '='.repeat(result.length - 3); | 31 | result += '// ' + '='.repeat(result.length - 3); |
37 | result += '\n\n'; | 32 | result += '\n\n'; |
@@ -54,14 +49,11 @@ class TextDocumentContentProvider | |||
54 | if (!editor || !client) return ''; | 49 | if (!editor || !client) return ''; |
55 | 50 | ||
56 | const position = editor.selection.active; | 51 | const position = editor.selection.active; |
57 | const request: lc.TextDocumentPositionParams = { | 52 | |
53 | const expanded = await client.sendRequest(ra.expandMacro, { | ||
58 | textDocument: { uri: editor.document.uri.toString() }, | 54 | textDocument: { uri: editor.document.uri.toString() }, |
59 | position, | 55 | position, |
60 | }; | 56 | }); |
61 | const expanded = await client.sendRequest<ExpandedMacro>( | ||
62 | 'rust-analyzer/expandMacro', | ||
63 | request, | ||
64 | ); | ||
65 | 57 | ||
66 | if (expanded == null) return 'Not available'; | 58 | if (expanded == null) return 'Not available'; |
67 | 59 | ||
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts index 839245f48..bdb7fc3b0 100644 --- a/editors/code/src/commands/index.ts +++ b/editors/code/src/commands/index.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from '../rust-analyzer-api'; | ||
3 | 4 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 5 | import { Ctx, Cmd } from '../ctx'; |
5 | import * as sourceChange from '../source_change'; | 6 | import * as sourceChange from '../source_change'; |
@@ -16,9 +17,7 @@ export * from './ssr'; | |||
16 | export * from './server_version'; | 17 | export * from './server_version'; |
17 | 18 | ||
18 | export function collectGarbage(ctx: Ctx): Cmd { | 19 | export function collectGarbage(ctx: Ctx): Cmd { |
19 | return async () => { | 20 | return async () => ctx.client.sendRequest(ra.collectGarbage, null); |
20 | await ctx.client?.sendRequest<null>('rust-analyzer/collectGarbage', null); | ||
21 | }; | ||
22 | } | 21 | } |
23 | 22 | ||
24 | export function showReferences(ctx: Ctx): Cmd { | 23 | export function showReferences(ctx: Ctx): Cmd { |
@@ -36,13 +35,13 @@ export function showReferences(ctx: Ctx): Cmd { | |||
36 | } | 35 | } |
37 | 36 | ||
38 | export function applySourceChange(ctx: Ctx): Cmd { | 37 | export function applySourceChange(ctx: Ctx): Cmd { |
39 | return async (change: sourceChange.SourceChange) => { | 38 | return async (change: ra.SourceChange) => { |
40 | await sourceChange.applySourceChange(ctx, change); | 39 | await sourceChange.applySourceChange(ctx, change); |
41 | }; | 40 | }; |
42 | } | 41 | } |
43 | 42 | ||
44 | export function selectAndApplySourceChange(ctx: Ctx): Cmd { | 43 | export function selectAndApplySourceChange(ctx: Ctx): Cmd { |
45 | return async (changes: sourceChange.SourceChange[]) => { | 44 | return async (changes: ra.SourceChange[]) => { |
46 | if (changes.length === 1) { | 45 | if (changes.length === 1) { |
47 | await sourceChange.applySourceChange(ctx, changes[0]); | 46 | await sourceChange.applySourceChange(ctx, changes[0]); |
48 | } else if (changes.length > 0) { | 47 | } else if (changes.length > 0) { |
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts index 7b08c3255..de0614653 100644 --- a/editors/code/src/commands/join_lines.ts +++ b/editors/code/src/commands/join_lines.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as lc from 'vscode-languageclient'; | 1 | import * as ra from '../rust-analyzer-api'; |
2 | 2 | ||
3 | import { Ctx, Cmd } from '../ctx'; | 3 | import { Ctx, Cmd } from '../ctx'; |
4 | import { applySourceChange, SourceChange } from '../source_change'; | 4 | import { applySourceChange } from '../source_change'; |
5 | 5 | ||
6 | export function joinLines(ctx: Ctx): Cmd { | 6 | export function joinLines(ctx: Ctx): Cmd { |
7 | return async () => { | 7 | return async () => { |
@@ -9,19 +9,10 @@ export function joinLines(ctx: Ctx): Cmd { | |||
9 | const client = ctx.client; | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | 10 | if (!editor || !client) return; |
11 | 11 | ||
12 | const request: JoinLinesParams = { | 12 | const change = await client.sendRequest(ra.joinLines, { |
13 | range: client.code2ProtocolConverter.asRange(editor.selection), | 13 | range: client.code2ProtocolConverter.asRange(editor.selection), |
14 | textDocument: { uri: editor.document.uri.toString() }, | 14 | textDocument: { uri: editor.document.uri.toString() }, |
15 | }; | 15 | }); |
16 | const change = await client.sendRequest<SourceChange>( | ||
17 | 'rust-analyzer/joinLines', | ||
18 | request, | ||
19 | ); | ||
20 | await applySourceChange(ctx, change); | 16 | await applySourceChange(ctx, change); |
21 | }; | 17 | }; |
22 | } | 18 | } |
23 | |||
24 | interface JoinLinesParams { | ||
25 | textDocument: lc.TextDocumentIdentifier; | ||
26 | range: lc.Range; | ||
27 | } | ||
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts index 7c58bb7e7..a60776e2d 100644 --- a/editors/code/src/commands/matching_brace.ts +++ b/editors/code/src/commands/matching_brace.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | 5 | ||
@@ -9,16 +9,12 @@ export function matchingBrace(ctx: Ctx): Cmd { | |||
9 | const client = ctx.client; | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | 10 | if (!editor || !client) return; |
11 | 11 | ||
12 | const request: FindMatchingBraceParams = { | 12 | const response = await client.sendRequest(ra.findMatchingBrace, { |
13 | textDocument: { uri: editor.document.uri.toString() }, | 13 | textDocument: { uri: editor.document.uri.toString() }, |
14 | offsets: editor.selections.map(s => | 14 | offsets: editor.selections.map(s => |
15 | client.code2ProtocolConverter.asPosition(s.active), | 15 | client.code2ProtocolConverter.asPosition(s.active), |
16 | ), | 16 | ), |
17 | }; | 17 | }); |
18 | const response = await client.sendRequest<lc.Position[]>( | ||
19 | 'rust-analyzer/findMatchingBrace', | ||
20 | request, | ||
21 | ); | ||
22 | editor.selections = editor.selections.map((sel, idx) => { | 18 | editor.selections = editor.selections.map((sel, idx) => { |
23 | const active = client.protocol2CodeConverter.asPosition( | 19 | const active = client.protocol2CodeConverter.asPosition( |
24 | response[idx], | 20 | response[idx], |
@@ -29,8 +25,3 @@ export function matchingBrace(ctx: Ctx): Cmd { | |||
29 | editor.revealRange(editor.selection); | 25 | editor.revealRange(editor.selection); |
30 | }; | 26 | }; |
31 | } | 27 | } |
32 | |||
33 | interface FindMatchingBraceParams { | ||
34 | textDocument: lc.TextDocumentIdentifier; | ||
35 | offsets: lc.Position[]; | ||
36 | } | ||
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts index 27ae8ec23..285849db7 100644 --- a/editors/code/src/commands/on_enter.ts +++ b/editors/code/src/commands/on_enter.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { applySourceChange, SourceChange } from '../source_change'; | 4 | import { applySourceChange } from '../source_change'; |
5 | import { Cmd, Ctx } from '../ctx'; | 5 | import { Cmd, Ctx } from '../ctx'; |
6 | 6 | ||
7 | async function handleKeypress(ctx: Ctx) { | 7 | async function handleKeypress(ctx: Ctx) { |
@@ -10,22 +10,15 @@ async function handleKeypress(ctx: Ctx) { | |||
10 | 10 | ||
11 | if (!editor || !client) return false; | 11 | if (!editor || !client) return false; |
12 | 12 | ||
13 | const request: lc.TextDocumentPositionParams = { | 13 | const change = await client.sendRequest(ra.onEnter, { |
14 | textDocument: { uri: editor.document.uri.toString() }, | 14 | textDocument: { uri: editor.document.uri.toString() }, |
15 | position: client.code2ProtocolConverter.asPosition( | 15 | position: client.code2ProtocolConverter.asPosition( |
16 | editor.selection.active, | 16 | editor.selection.active, |
17 | ), | 17 | ), |
18 | }; | 18 | }).catch(_error => { |
19 | const change = await client.sendRequest<undefined | SourceChange>( | 19 | // client.logFailedRequest(OnEnterRequest.type, error); |
20 | 'rust-analyzer/onEnter', | 20 | return null; |
21 | request, | 21 | }); |
22 | ).catch( | ||
23 | (_error: any) => { | ||
24 | // FIXME: switch to the more modern (?) typed request infrastructure | ||
25 | // client.logFailedRequest(OnEnterRequest.type, error); | ||
26 | return Promise.resolve(null); | ||
27 | } | ||
28 | ); | ||
29 | if (!change) return false; | 22 | if (!change) return false; |
30 | 23 | ||
31 | await applySourceChange(ctx, change); | 24 | await applySourceChange(ctx, change); |
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts index bf40b4021..8f78ddd71 100644 --- a/editors/code/src/commands/parent_module.ts +++ b/editors/code/src/commands/parent_module.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | 5 | ||
@@ -9,16 +9,12 @@ export function parentModule(ctx: Ctx): Cmd { | |||
9 | const client = ctx.client; | 9 | const client = ctx.client; |
10 | if (!editor || !client) return; | 10 | if (!editor || !client) return; |
11 | 11 | ||
12 | const request: lc.TextDocumentPositionParams = { | 12 | const response = await client.sendRequest(ra.parentModule, { |
13 | textDocument: { uri: editor.document.uri.toString() }, | 13 | textDocument: { uri: editor.document.uri.toString() }, |
14 | position: client.code2ProtocolConverter.asPosition( | 14 | position: client.code2ProtocolConverter.asPosition( |
15 | editor.selection.active, | 15 | editor.selection.active, |
16 | ), | 16 | ), |
17 | }; | 17 | }); |
18 | const response = await client.sendRequest<lc.Location[]>( | ||
19 | 'rust-analyzer/parentModule', | ||
20 | request, | ||
21 | ); | ||
22 | const loc = response[0]; | 18 | const loc = response[0]; |
23 | if (loc == null) return; | 19 | if (loc == null) return; |
24 | 20 | ||
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts index 7919997ce..06b513466 100644 --- a/editors/code/src/commands/runnables.ts +++ b/editors/code/src/commands/runnables.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from '../rust-analyzer-api'; | ||
3 | 4 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 5 | import { Ctx, Cmd } from '../ctx'; |
5 | 6 | ||
@@ -14,16 +15,13 @@ export function run(ctx: Ctx): Cmd { | |||
14 | const textDocument: lc.TextDocumentIdentifier = { | 15 | const textDocument: lc.TextDocumentIdentifier = { |
15 | uri: editor.document.uri.toString(), | 16 | uri: editor.document.uri.toString(), |
16 | }; | 17 | }; |
17 | const params: RunnablesParams = { | 18 | |
19 | const runnables = await client.sendRequest(ra.runnables, { | ||
18 | textDocument, | 20 | textDocument, |
19 | position: client.code2ProtocolConverter.asPosition( | 21 | position: client.code2ProtocolConverter.asPosition( |
20 | editor.selection.active, | 22 | editor.selection.active, |
21 | ), | 23 | ), |
22 | }; | 24 | }); |
23 | const runnables = await client.sendRequest<Runnable[]>( | ||
24 | 'rust-analyzer/runnables', | ||
25 | params, | ||
26 | ); | ||
27 | const items: RunnableQuickPick[] = []; | 25 | const items: RunnableQuickPick[] = []; |
28 | if (prevRunnable) { | 26 | if (prevRunnable) { |
29 | items.push(prevRunnable); | 27 | items.push(prevRunnable); |
@@ -48,7 +46,7 @@ export function run(ctx: Ctx): Cmd { | |||
48 | } | 46 | } |
49 | 47 | ||
50 | export function runSingle(ctx: Ctx): Cmd { | 48 | export function runSingle(ctx: Ctx): Cmd { |
51 | return async (runnable: Runnable) => { | 49 | return async (runnable: ra.Runnable) => { |
52 | const editor = ctx.activeRustEditor; | 50 | const editor = ctx.activeRustEditor; |
53 | if (!editor) return; | 51 | if (!editor) return; |
54 | 52 | ||
@@ -64,26 +62,13 @@ export function runSingle(ctx: Ctx): Cmd { | |||
64 | }; | 62 | }; |
65 | } | 63 | } |
66 | 64 | ||
67 | interface RunnablesParams { | ||
68 | textDocument: lc.TextDocumentIdentifier; | ||
69 | position?: lc.Position; | ||
70 | } | ||
71 | |||
72 | interface Runnable { | ||
73 | label: string; | ||
74 | bin: string; | ||
75 | args: string[]; | ||
76 | env: { [index: string]: string }; | ||
77 | cwd?: string; | ||
78 | } | ||
79 | |||
80 | class RunnableQuickPick implements vscode.QuickPickItem { | 65 | class RunnableQuickPick implements vscode.QuickPickItem { |
81 | public label: string; | 66 | public label: string; |
82 | public description?: string | undefined; | 67 | public description?: string | undefined; |
83 | public detail?: string | undefined; | 68 | public detail?: string | undefined; |
84 | public picked?: boolean | undefined; | 69 | public picked?: boolean | undefined; |
85 | 70 | ||
86 | constructor(public runnable: Runnable) { | 71 | constructor(public runnable: ra.Runnable) { |
87 | this.label = runnable.label; | 72 | this.label = runnable.label; |
88 | } | 73 | } |
89 | } | 74 | } |
@@ -96,7 +81,7 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { | |||
96 | env?: { [key: string]: string }; | 81 | env?: { [key: string]: string }; |
97 | } | 82 | } |
98 | 83 | ||
99 | function createTask(spec: Runnable): vscode.Task { | 84 | function createTask(spec: ra.Runnable): vscode.Task { |
100 | const TASK_SOURCE = 'Rust'; | 85 | const TASK_SOURCE = 'Rust'; |
101 | const definition: CargoTaskDefinition = { | 86 | const definition: CargoTaskDefinition = { |
102 | type: 'cargo', | 87 | type: 'cargo', |
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts index 9b814612a..eee48c693 100644 --- a/editors/code/src/commands/ssr.ts +++ b/editors/code/src/commands/ssr.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import { Ctx, Cmd } from '../ctx'; | ||
2 | import { applySourceChange, SourceChange } from '../source_change'; | ||
3 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as ra from "../rust-analyzer-api"; | ||
3 | |||
4 | import { Ctx, Cmd } from '../ctx'; | ||
5 | import { applySourceChange } from '../source_change'; | ||
4 | 6 | ||
5 | export function ssr(ctx: Ctx): Cmd { | 7 | export function ssr(ctx: Ctx): Cmd { |
6 | return async () => { | 8 | return async () => { |
@@ -21,16 +23,8 @@ export function ssr(ctx: Ctx): Cmd { | |||
21 | 23 | ||
22 | if (!request) return; | 24 | if (!request) return; |
23 | 25 | ||
24 | const ssrRequest: SsrRequest = { arg: request }; | 26 | const change = await client.sendRequest(ra.ssr, { arg: request }); |
25 | const change = await client.sendRequest<SourceChange>( | ||
26 | 'rust-analyzer/ssr', | ||
27 | ssrRequest, | ||
28 | ); | ||
29 | 27 | ||
30 | await applySourceChange(ctx, change); | 28 | await applySourceChange(ctx, change); |
31 | }; | 29 | }; |
32 | } | 30 | } |
33 | |||
34 | interface SsrRequest { | ||
35 | arg: string; | ||
36 | } | ||
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 2887c96c8..7218bfb90 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from '../rust-analyzer-api'; |
3 | 3 | ||
4 | import { Ctx, Cmd } from '../ctx'; | 4 | import { Ctx, Cmd } from '../ctx'; |
5 | 5 | ||
@@ -61,13 +61,8 @@ function afterLs(f: () => void) { | |||
61 | setTimeout(f, 10); | 61 | setTimeout(f, 10); |
62 | } | 62 | } |
63 | 63 | ||
64 | interface SyntaxTreeParams { | ||
65 | textDocument: lc.TextDocumentIdentifier; | ||
66 | range?: lc.Range; | ||
67 | } | ||
68 | 64 | ||
69 | class TextDocumentContentProvider | 65 | class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { |
70 | implements vscode.TextDocumentContentProvider { | ||
71 | uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); | 66 | uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); |
72 | eventEmitter = new vscode.EventEmitter<vscode.Uri>(); | 67 | eventEmitter = new vscode.EventEmitter<vscode.Uri>(); |
73 | 68 | ||
@@ -79,23 +74,15 @@ class TextDocumentContentProvider | |||
79 | const client = this.ctx.client; | 74 | const client = this.ctx.client; |
80 | if (!editor || !client) return ''; | 75 | if (!editor || !client) return ''; |
81 | 76 | ||
82 | let range: lc.Range | undefined; | ||
83 | |||
84 | // When the range based query is enabled we take the range of the selection | 77 | // When the range based query is enabled we take the range of the selection |
85 | if (uri.query === 'range=true') { | 78 | const range = uri.query === 'range=true' && !editor.selection.isEmpty |
86 | range = editor.selection.isEmpty | 79 | ? client.code2ProtocolConverter.asRange(editor.selection) |
87 | ? undefined | 80 | : null; |
88 | : client.code2ProtocolConverter.asRange(editor.selection); | ||
89 | } | ||
90 | 81 | ||
91 | const request: SyntaxTreeParams = { | 82 | return client.sendRequest(ra.syntaxTree, { |
92 | textDocument: { uri: editor.document.uri.toString() }, | 83 | textDocument: { uri: editor.document.uri.toString() }, |
93 | range, | 84 | range, |
94 | }; | 85 | }); |
95 | return client.sendRequest<string>( | ||
96 | 'rust-analyzer/syntaxTree', | ||
97 | request, | ||
98 | ); | ||
99 | } | 86 | } |
100 | 87 | ||
101 | get onDidChange(): vscode.Event<vscode.Uri> { | 88 | get onDidChange(): vscode.Event<vscode.Uri> { |
diff --git a/editors/code/src/highlighting.ts b/editors/code/src/highlighting.ts index 77b4a1a68..3e0cbdc56 100644 --- a/editors/code/src/highlighting.ts +++ b/editors/code/src/highlighting.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from './rust-analyzer-api'; |
3 | 3 | ||
4 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; | 4 | import { ColorTheme, TextMateRuleSettings } from './color_theme'; |
5 | 5 | ||
@@ -8,29 +8,25 @@ import { sendRequestWithRetry } from './util'; | |||
8 | 8 | ||
9 | export function activateHighlighting(ctx: Ctx) { | 9 | export function activateHighlighting(ctx: Ctx) { |
10 | const highlighter = new Highlighter(ctx); | 10 | const highlighter = new Highlighter(ctx); |
11 | const client = ctx.client; | ||
12 | if (client != null) { | ||
13 | client.onNotification( | ||
14 | 'rust-analyzer/publishDecorations', | ||
15 | (params: PublishDecorationsParams) => { | ||
16 | if (!ctx.config.highlightingOn) return; | ||
17 | |||
18 | const targetEditor = vscode.window.visibleTextEditors.find( | ||
19 | editor => { | ||
20 | const unescapedUri = unescape( | ||
21 | editor.document.uri.toString(), | ||
22 | ); | ||
23 | // Unescaped URI looks like: | ||
24 | // file:///c:/Workspace/ra-test/src/main.rs | ||
25 | return unescapedUri === params.uri; | ||
26 | }, | ||
27 | ); | ||
28 | if (!targetEditor) return; | ||
29 | 11 | ||
30 | highlighter.setHighlights(targetEditor, params.decorations); | 12 | ctx.client.onNotification(ra.publishDecorations, params => { |
13 | if (!ctx.config.highlightingOn) return; | ||
14 | |||
15 | const targetEditor = vscode.window.visibleTextEditors.find( | ||
16 | editor => { | ||
17 | const unescapedUri = unescape( | ||
18 | editor.document.uri.toString(), | ||
19 | ); | ||
20 | // Unescaped URI looks like: | ||
21 | // file:///c:/Workspace/ra-test/src/main.rs | ||
22 | return unescapedUri === params.uri; | ||
31 | }, | 23 | }, |
32 | ); | 24 | ); |
33 | } | 25 | if (!targetEditor) return; |
26 | |||
27 | highlighter.setHighlights(targetEditor, params.decorations); | ||
28 | }); | ||
29 | |||
34 | 30 | ||
35 | vscode.workspace.onDidChangeConfiguration( | 31 | vscode.workspace.onDidChangeConfiguration( |
36 | _ => highlighter.removeHighlights(), | 32 | _ => highlighter.removeHighlights(), |
@@ -45,13 +41,10 @@ export function activateHighlighting(ctx: Ctx) { | |||
45 | const client = ctx.client; | 41 | const client = ctx.client; |
46 | if (!client) return; | 42 | if (!client) return; |
47 | 43 | ||
48 | const params: lc.TextDocumentIdentifier = { | 44 | const decorations = await sendRequestWithRetry( |
49 | uri: editor.document.uri.toString(), | ||
50 | }; | ||
51 | const decorations = await sendRequestWithRetry<Decoration[]>( | ||
52 | client, | 45 | client, |
53 | 'rust-analyzer/decorationsRequest', | 46 | ra.decorationsRequest, |
54 | params, | 47 | { uri: editor.document.uri.toString() }, |
55 | ); | 48 | ); |
56 | highlighter.setHighlights(editor, decorations); | 49 | highlighter.setHighlights(editor, decorations); |
57 | }, | 50 | }, |
@@ -60,17 +53,6 @@ export function activateHighlighting(ctx: Ctx) { | |||
60 | ); | 53 | ); |
61 | } | 54 | } |
62 | 55 | ||
63 | interface PublishDecorationsParams { | ||
64 | uri: string; | ||
65 | decorations: Decoration[]; | ||
66 | } | ||
67 | |||
68 | interface Decoration { | ||
69 | range: lc.Range; | ||
70 | tag: string; | ||
71 | bindingHash?: string; | ||
72 | } | ||
73 | |||
74 | // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 | 56 | // Based on this HSL-based color generator: https://gist.github.com/bendc/76c48ce53299e6078a76 |
75 | function fancify(seed: string, shade: 'light' | 'dark') { | 57 | function fancify(seed: string, shade: 'light' | 'dark') { |
76 | const random = randomU32Numbers(hashString(seed)); | 58 | const random = randomU32Numbers(hashString(seed)); |
@@ -108,7 +90,7 @@ class Highlighter { | |||
108 | this.decorations = null; | 90 | this.decorations = null; |
109 | } | 91 | } |
110 | 92 | ||
111 | public setHighlights(editor: vscode.TextEditor, highlights: Decoration[]) { | 93 | public setHighlights(editor: vscode.TextEditor, highlights: ra.Decoration[]) { |
112 | const client = this.ctx.client; | 94 | const client = this.ctx.client; |
113 | if (!client) return; | 95 | if (!client) return; |
114 | // Initialize decorations if necessary | 96 | // Initialize decorations if necessary |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 5f9229efb..5951cf1b4 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as ra from './rust-analyzer-api'; |
3 | 3 | ||
4 | import { Ctx } from './ctx'; | 4 | import { Ctx } from './ctx'; |
5 | import { log, sendRequestWithRetry } from './util'; | 5 | import { log, sendRequestWithRetry } from './util'; |
@@ -39,16 +39,6 @@ export function activateInlayHints(ctx: Ctx) { | |||
39 | void hintsUpdater.setEnabled(ctx.config.displayInlayHints); | 39 | void hintsUpdater.setEnabled(ctx.config.displayInlayHints); |
40 | } | 40 | } |
41 | 41 | ||
42 | interface InlayHintsParams { | ||
43 | textDocument: lc.TextDocumentIdentifier; | ||
44 | } | ||
45 | |||
46 | interface InlayHint { | ||
47 | range: vscode.Range; | ||
48 | kind: "TypeHint" | "ParameterHint"; | ||
49 | label: string; | ||
50 | } | ||
51 | |||
52 | const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ | 42 | const typeHintDecorationType = vscode.window.createTextEditorDecorationType({ |
53 | after: { | 43 | after: { |
54 | color: new vscode.ThemeColor('rust_analyzer.inlayHint'), | 44 | color: new vscode.ThemeColor('rust_analyzer.inlayHint'), |
@@ -107,9 +97,9 @@ class HintsUpdater { | |||
107 | if (newHints == null) return; | 97 | if (newHints == null) return; |
108 | 98 | ||
109 | const newTypeDecorations = newHints | 99 | const newTypeDecorations = newHints |
110 | .filter(hint => hint.kind === 'TypeHint') | 100 | .filter(hint => hint.kind === ra.InlayKind.TypeHint) |
111 | .map(hint => ({ | 101 | .map(hint => ({ |
112 | range: hint.range, | 102 | range: this.ctx.client.protocol2CodeConverter.asRange(hint.range), |
113 | renderOptions: { | 103 | renderOptions: { |
114 | after: { | 104 | after: { |
115 | contentText: `: ${hint.label}`, | 105 | contentText: `: ${hint.label}`, |
@@ -119,9 +109,9 @@ class HintsUpdater { | |||
119 | this.setTypeDecorations(editor, newTypeDecorations); | 109 | this.setTypeDecorations(editor, newTypeDecorations); |
120 | 110 | ||
121 | const newParameterDecorations = newHints | 111 | const newParameterDecorations = newHints |
122 | .filter(hint => hint.kind === 'ParameterHint') | 112 | .filter(hint => hint.kind === ra.InlayKind.ParameterHint) |
123 | .map(hint => ({ | 113 | .map(hint => ({ |
124 | range: hint.range, | 114 | range: this.ctx.client.protocol2CodeConverter.asRange(hint.range), |
125 | renderOptions: { | 115 | renderOptions: { |
126 | before: { | 116 | before: { |
127 | contentText: `${hint.label}: `, | 117 | contentText: `${hint.label}: `, |
@@ -151,20 +141,15 @@ class HintsUpdater { | |||
151 | ); | 141 | ); |
152 | } | 142 | } |
153 | 143 | ||
154 | private async queryHints(documentUri: string): Promise<InlayHint[] | null> { | 144 | private async queryHints(documentUri: string): Promise<ra.InlayHint[] | null> { |
155 | this.pending.get(documentUri)?.cancel(); | 145 | this.pending.get(documentUri)?.cancel(); |
156 | 146 | ||
157 | const tokenSource = new vscode.CancellationTokenSource(); | 147 | const tokenSource = new vscode.CancellationTokenSource(); |
158 | this.pending.set(documentUri, tokenSource); | 148 | this.pending.set(documentUri, tokenSource); |
159 | 149 | ||
160 | const request: InlayHintsParams = { textDocument: { uri: documentUri } }; | 150 | const request = { textDocument: { uri: documentUri } }; |
161 | 151 | ||
162 | return sendRequestWithRetry<InlayHint[]>( | 152 | return sendRequestWithRetry(this.ctx.client, ra.inlayHints, request, tokenSource.token) |
163 | this.ctx.client, | ||
164 | 'rust-analyzer/inlayHints', | ||
165 | request, | ||
166 | tokenSource.token | ||
167 | ) | ||
168 | .catch(_ => null) | 153 | .catch(_ => null) |
169 | .finally(() => { | 154 | .finally(() => { |
170 | if (!tokenSource.token.isCancellationRequested) { | 155 | if (!tokenSource.token.isCancellationRequested) { |
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts new file mode 100644 index 000000000..c5a010e94 --- /dev/null +++ b/editors/code/src/rust-analyzer-api.ts | |||
@@ -0,0 +1,117 @@ | |||
1 | /** | ||
2 | * This file mirrors `crates/rust-analyzer/src/req.rs` declarations. | ||
3 | */ | ||
4 | |||
5 | import * as lc from "vscode-languageclient"; | ||
6 | |||
7 | type Option<T> = null | T; | ||
8 | type Vec<T> = T[]; | ||
9 | type FxHashMap<K extends PropertyKey, V> = Record<K, V>; | ||
10 | |||
11 | function request<TParams, TResult>(method: string) { | ||
12 | return new lc.RequestType<TParams, TResult, unknown>(`rust-analyzer/${method}`); | ||
13 | } | ||
14 | function notification<TParam>(method: string) { | ||
15 | return new lc.NotificationType<TParam>(method); | ||
16 | } | ||
17 | |||
18 | |||
19 | export const analyzerStatus = request<null, string>("analyzerStatus"); | ||
20 | |||
21 | |||
22 | export const collectGarbage = request<null, null>("collectGarbage"); | ||
23 | |||
24 | |||
25 | export interface SyntaxTreeParams { | ||
26 | textDocument: lc.TextDocumentIdentifier; | ||
27 | range: Option<lc.Range>; | ||
28 | } | ||
29 | export const syntaxTree = request<SyntaxTreeParams, string>("syntaxTree"); | ||
30 | |||
31 | |||
32 | export interface ExpandMacroParams { | ||
33 | textDocument: lc.TextDocumentIdentifier; | ||
34 | position: Option<lc.Position>; | ||
35 | } | ||
36 | export interface ExpandedMacro { | ||
37 | name: string; | ||
38 | expansion: string; | ||
39 | } | ||
40 | export const expandMacro = request<ExpandMacroParams, Option<ExpandedMacro>>("expandMacro"); | ||
41 | |||
42 | |||
43 | export interface FindMatchingBraceParams { | ||
44 | textDocument: lc.TextDocumentIdentifier; | ||
45 | offsets: Vec<lc.Position>; | ||
46 | } | ||
47 | export const findMatchingBrace = request<FindMatchingBraceParams, Vec<lc.Position>>("findMatchingBrace"); | ||
48 | |||
49 | |||
50 | export interface PublishDecorationsParams { | ||
51 | uri: string; | ||
52 | decorations: Vec<Decoration>; | ||
53 | } | ||
54 | export interface Decoration { | ||
55 | range: lc.Range; | ||
56 | tag: string; | ||
57 | bindingHash: Option<string>; | ||
58 | } | ||
59 | export const decorationsRequest = request<lc.TextDocumentIdentifier, Vec<Decoration>>("decorationsRequest"); | ||
60 | |||
61 | |||
62 | export const parentModule = request<lc.TextDocumentPositionParams, Vec<lc.Location>>("parentModule"); | ||
63 | |||
64 | |||
65 | export interface JoinLinesParams { | ||
66 | textDocument: lc.TextDocumentIdentifier; | ||
67 | range: lc.Range; | ||
68 | } | ||
69 | export const joinLines = request<JoinLinesParams, SourceChange>("joinLines"); | ||
70 | |||
71 | |||
72 | export const onEnter = request<lc.TextDocumentPositionParams, Option<SourceChange>>("onEnter"); | ||
73 | |||
74 | export interface RunnablesParams { | ||
75 | textDocument: lc.TextDocumentIdentifier; | ||
76 | position: Option<lc.Position>; | ||
77 | } | ||
78 | export interface Runnable { | ||
79 | range: lc.Range; | ||
80 | label: string; | ||
81 | bin: string; | ||
82 | args: Vec<string>; | ||
83 | env: FxHashMap<string, string>; | ||
84 | cwd: Option<string>; | ||
85 | } | ||
86 | export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables"); | ||
87 | |||
88 | |||
89 | export const enum InlayKind { | ||
90 | TypeHint = "TypeHint", | ||
91 | ParameterHint = "ParameterHint", | ||
92 | } | ||
93 | export interface InlayHint { | ||
94 | range: lc.Range; | ||
95 | kind: InlayKind; | ||
96 | label: string; | ||
97 | } | ||
98 | export interface InlayHintsParams { | ||
99 | textDocument: lc.TextDocumentIdentifier; | ||
100 | } | ||
101 | export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints"); | ||
102 | |||
103 | |||
104 | export interface SsrParams { | ||
105 | arg: string; | ||
106 | } | ||
107 | export const ssr = request<SsrParams, SourceChange>("ssr"); | ||
108 | |||
109 | |||
110 | export const publishDecorations = notification<PublishDecorationsParams>("publishDecorations"); | ||
111 | |||
112 | |||
113 | export interface SourceChange { | ||
114 | label: string; | ||
115 | workspaceEdit: lc.WorkspaceEdit; | ||
116 | cursorPosition: Option<lc.TextDocumentPositionParams>; | ||
117 | } | ||
diff --git a/editors/code/src/source_change.ts b/editors/code/src/source_change.ts index a336269ba..399a150c6 100644 --- a/editors/code/src/source_change.ts +++ b/editors/code/src/source_change.ts | |||
@@ -1,15 +1,10 @@ | |||
1 | import * as vscode from 'vscode'; | 1 | import * as vscode from 'vscode'; |
2 | import * as lc from 'vscode-languageclient'; | 2 | import * as lc from 'vscode-languageclient'; |
3 | import * as ra from './rust-analyzer-api'; | ||
3 | 4 | ||
4 | import { Ctx } from './ctx'; | 5 | import { Ctx } from './ctx'; |
5 | 6 | ||
6 | export interface SourceChange { | 7 | export async function applySourceChange(ctx: Ctx, change: ra.SourceChange) { |
7 | label: string; | ||
8 | workspaceEdit: lc.WorkspaceEdit; | ||
9 | cursorPosition?: lc.TextDocumentPositionParams; | ||
10 | } | ||
11 | |||
12 | export async function applySourceChange(ctx: Ctx, change: SourceChange) { | ||
13 | const client = ctx.client; | 8 | const client = ctx.client; |
14 | if (!client) return; | 9 | if (!client) return; |
15 | 10 | ||
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 2f18f85a3..68c2a94d0 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts | |||
@@ -20,21 +20,21 @@ export const log = { | |||
20 | } | 20 | } |
21 | }; | 21 | }; |
22 | 22 | ||
23 | export async function sendRequestWithRetry<R>( | 23 | export async function sendRequestWithRetry<TParam, TRet>( |
24 | client: lc.LanguageClient, | 24 | client: lc.LanguageClient, |
25 | method: string, | 25 | reqType: lc.RequestType<TParam, TRet, unknown>, |
26 | param: unknown, | 26 | param: TParam, |
27 | token?: vscode.CancellationToken, | 27 | token?: vscode.CancellationToken, |
28 | ): Promise<R> { | 28 | ): Promise<TRet> { |
29 | for (const delay of [2, 4, 6, 8, 10, null]) { | 29 | for (const delay of [2, 4, 6, 8, 10, null]) { |
30 | try { | 30 | try { |
31 | return await (token | 31 | return await (token |
32 | ? client.sendRequest(method, param, token) | 32 | ? client.sendRequest(reqType, param, token) |
33 | : client.sendRequest(method, param) | 33 | : client.sendRequest(reqType, param) |
34 | ); | 34 | ); |
35 | } catch (error) { | 35 | } catch (error) { |
36 | if (delay === null) { | 36 | if (delay === null) { |
37 | log.error("LSP request timed out", { method, param, error }); | 37 | log.error("LSP request timed out", { method: reqType.method, param, error }); |
38 | throw error; | 38 | throw error; |
39 | } | 39 | } |
40 | 40 | ||
@@ -43,7 +43,7 @@ export async function sendRequestWithRetry<R>( | |||
43 | } | 43 | } |
44 | 44 | ||
45 | if (error.code !== lc.ErrorCodes.ContentModified) { | 45 | if (error.code !== lc.ErrorCodes.ContentModified) { |
46 | log.error("LSP request failed", { method, param, error }); | 46 | log.error("LSP request failed", { method: reqType.method, param, error }); |
47 | throw error; | 47 | throw error; |
48 | } | 48 | } |
49 | 49 | ||