aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/hover.rs134
-rw-r--r--crates/ra_ide/src/lib.rs2
-rw-r--r--crates/ra_ide_db/src/defs.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs28
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs34
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs119
-rw-r--r--editors/code/package.json16
-rw-r--r--editors/code/src/client.ts45
-rw-r--r--editors/code/src/config.ts13
-rw-r--r--editors/code/src/lsp_ext.ts12
11 files changed, 351 insertions, 56 deletions
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 9636cd0d6..baa9fc8a8 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -13,14 +13,43 @@ use ra_ide_db::{
13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; 13use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset};
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, 16 display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav},
17 FilePosition, RangeInfo, 17 FilePosition, RangeInfo, NavigationTarget,
18}; 18};
19 19
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct HoverConfig {
22 pub implementations: bool,
23}
24
25impl Default for HoverConfig {
26 fn default() -> Self {
27 Self { implementations: true }
28 }
29}
30
31impl HoverConfig {
32 pub const NO_ACTIONS: Self = Self { implementations: false };
33
34 pub fn any(&self) -> bool {
35 self.implementations
36 }
37
38 pub fn none(&self) -> bool {
39 !self.any()
40 }
41}
42
43#[derive(Debug, Clone)]
44pub enum HoverAction {
45 Implementaion(FilePosition),
46}
47
20/// Contains the results when hovering over an item 48/// Contains the results when hovering over an item
21#[derive(Debug, Default)] 49#[derive(Debug, Default)]
22pub struct HoverResult { 50pub struct HoverResult {
23 results: Vec<String>, 51 results: Vec<String>,
52 actions: Vec<HoverAction>,
24} 53}
25 54
26impl HoverResult { 55impl HoverResult {
@@ -48,10 +77,20 @@ impl HoverResult {
48 &self.results 77 &self.results
49 } 78 }
50 79
80 pub fn actions(&self) -> &[HoverAction] {
81 &self.actions
82 }
83
84 pub fn push_action(&mut self, action: HoverAction) {
85 self.actions.push(action);
86 }
87
51 /// Returns the results converted into markup 88 /// Returns the results converted into markup
52 /// for displaying in a UI 89 /// for displaying in a UI
90 ///
91 /// Does not process actions!
53 pub fn to_markup(&self) -> String { 92 pub fn to_markup(&self) -> String {
54 self.results.join("\n\n---\n") 93 self.results.join("\n\n___\n")
55 } 94 }
56} 95}
57 96
@@ -82,6 +121,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
82 res.extend(hover_text_from_name_kind(db, name_kind)); 121 res.extend(hover_text_from_name_kind(db, name_kind));
83 122
84 if !res.is_empty() { 123 if !res.is_empty() {
124 if let Some(action) = show_implementations_action(db, name_kind) {
125 res.push_action(action);
126 }
127
85 return Some(RangeInfo::new(range, res)); 128 return Some(RangeInfo::new(range, res));
86 } 129 }
87 } 130 }
@@ -112,6 +155,26 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
112 Some(RangeInfo::new(range, res)) 155 Some(RangeInfo::new(range, res))
113} 156}
114 157
158fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
159 fn to_action(nav_target: NavigationTarget) -> HoverAction {
160 HoverAction::Implementaion(FilePosition {
161 file_id: nav_target.file_id(),
162 offset: nav_target.range().start(),
163 })
164 }
165
166 match def {
167 Definition::ModuleDef(it) => match it {
168 ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))),
169 ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))),
170 ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))),
171 ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))),
172 _ => None,
173 },
174 _ => None,
175 }
176}
177
115fn hover_text( 178fn hover_text(
116 docs: Option<String>, 179 docs: Option<String>,
117 desc: Option<String>, 180 desc: Option<String>,
@@ -228,6 +291,8 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
228 291
229#[cfg(test)] 292#[cfg(test)]
230mod tests { 293mod tests {
294 use super::*;
295
231 use ra_db::FileLoader; 296 use ra_db::FileLoader;
232 use ra_syntax::TextRange; 297 use ra_syntax::TextRange;
233 298
@@ -241,7 +306,14 @@ mod tests {
241 s.map(trim_markup) 306 s.map(trim_markup)
242 } 307 }
243 308
244 fn check_hover_result(fixture: &str, expected: &[&str]) -> String { 309 fn assert_impl_action(action: &HoverAction, position: u32) {
310 let offset = match action {
311 HoverAction::Implementaion(pos) => pos.offset
312 };
313 assert_eq!(offset, position.into());
314 }
315
316 fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec<HoverAction>) {
245 let (analysis, position) = analysis_and_position(fixture); 317 let (analysis, position) = analysis_and_position(fixture);
246 let hover = analysis.hover(position).unwrap().unwrap(); 318 let hover = analysis.hover(position).unwrap().unwrap();
247 let mut results = Vec::from(hover.info.results()); 319 let mut results = Vec::from(hover.info.results());
@@ -256,7 +328,7 @@ mod tests {
256 assert_eq!(hover.info.len(), expected.len()); 328 assert_eq!(hover.info.len(), expected.len());
257 329
258 let content = analysis.db.file_text(position.file_id); 330 let content = analysis.db.file_text(position.file_id);
259 content[hover.range].to_string() 331 (content[hover.range].to_string(), hover.info.actions().to_vec())
260 } 332 }
261 333
262 fn check_hover_no_result(fixture: &str) { 334 fn check_hover_no_result(fixture: &str) {
@@ -746,7 +818,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
746 818
747 #[test] 819 #[test]
748 fn test_hover_through_macro() { 820 fn test_hover_through_macro() {
749 let hover_on = check_hover_result( 821 let (hover_on, _) = check_hover_result(
750 " 822 "
751 //- /lib.rs 823 //- /lib.rs
752 macro_rules! id { 824 macro_rules! id {
@@ -767,7 +839,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
767 839
768 #[test] 840 #[test]
769 fn test_hover_through_expr_in_macro() { 841 fn test_hover_through_expr_in_macro() {
770 let hover_on = check_hover_result( 842 let (hover_on, _) = check_hover_result(
771 " 843 "
772 //- /lib.rs 844 //- /lib.rs
773 macro_rules! id { 845 macro_rules! id {
@@ -785,7 +857,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
785 857
786 #[test] 858 #[test]
787 fn test_hover_through_expr_in_macro_recursive() { 859 fn test_hover_through_expr_in_macro_recursive() {
788 let hover_on = check_hover_result( 860 let (hover_on, _) = check_hover_result(
789 " 861 "
790 //- /lib.rs 862 //- /lib.rs
791 macro_rules! id_deep { 863 macro_rules! id_deep {
@@ -806,7 +878,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
806 878
807 #[test] 879 #[test]
808 fn test_hover_through_func_in_macro_recursive() { 880 fn test_hover_through_func_in_macro_recursive() {
809 let hover_on = check_hover_result( 881 let (hover_on, _) = check_hover_result(
810 " 882 "
811 //- /lib.rs 883 //- /lib.rs
812 macro_rules! id_deep { 884 macro_rules! id_deep {
@@ -830,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
830 902
831 #[test] 903 #[test]
832 fn test_hover_through_literal_string_in_macro() { 904 fn test_hover_through_literal_string_in_macro() {
833 let hover_on = check_hover_result( 905 let (hover_on, _) = check_hover_result(
834 r#" 906 r#"
835 //- /lib.rs 907 //- /lib.rs
836 macro_rules! arr { 908 macro_rules! arr {
@@ -849,7 +921,7 @@ fn func(foo: i32) { if true { <|>foo; }; }
849 921
850 #[test] 922 #[test]
851 fn test_hover_through_assert_macro() { 923 fn test_hover_through_assert_macro() {
852 let hover_on = check_hover_result( 924 let (hover_on, _) = check_hover_result(
853 r#" 925 r#"
854 //- /lib.rs 926 //- /lib.rs
855 #[rustc_builtin_macro] 927 #[rustc_builtin_macro]
@@ -925,13 +997,14 @@ fn func(foo: i32) { if true { <|>foo; }; }
925 997
926 #[test] 998 #[test]
927 fn test_hover_trait_show_qualifiers() { 999 fn test_hover_trait_show_qualifiers() {
928 check_hover_result( 1000 let (_, actions) = check_hover_result(
929 " 1001 "
930 //- /lib.rs 1002 //- /lib.rs
931 unsafe trait foo<|>() {} 1003 unsafe trait foo<|>() {}
932 ", 1004 ",
933 &["unsafe trait foo"], 1005 &["unsafe trait foo"],
934 ); 1006 );
1007 assert_impl_action(&actions[0], 13);
935 } 1008 }
936 1009
937 #[test] 1010 #[test]
@@ -1052,4 +1125,41 @@ fn func(foo: i32) { if true { <|>foo; }; }
1052 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], 1125 &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"],
1053 ); 1126 );
1054 } 1127 }
1128
1129 #[test]
1130 fn test_hover_trait_hash_impl_action() {
1131 let (_, actions) = check_hover_result(
1132 "
1133 //- /lib.rs
1134 trait foo<|>() {}
1135 ",
1136 &["trait foo"],
1137 );
1138 assert_impl_action(&actions[0], 6);
1139 }
1140
1141 #[test]
1142 fn test_hover_struct_hash_impl_action() {
1143 let (_, actions) = check_hover_result(
1144 "
1145 //- /lib.rs
1146 struct foo<|>() {}
1147 ",
1148 &["struct foo"],
1149 );
1150 assert_impl_action(&actions[0], 7);
1151 }
1152
1153 #[test]
1154 fn test_hover_union_hash_impl_action() {
1155 let (_, actions) = check_hover_result(
1156 "
1157 //- /lib.rs
1158 union foo<|>() {}
1159 ",
1160 &["union foo"],
1161 );
1162 assert_impl_action(&actions[0], 6);
1163 }
1164
1055} 1165}
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 34c2d75fe..a9601400f 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -66,7 +66,7 @@ pub use crate::{
66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 66 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
67 expand_macro::ExpandedMacro, 67 expand_macro::ExpandedMacro,
68 folding_ranges::{Fold, FoldKind}, 68 folding_ranges::{Fold, FoldKind},
69 hover::HoverResult, 69 hover::{HoverResult, HoverAction, HoverConfig},
70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 70 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 71 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
72 runnables::{Runnable, RunnableKind, TestId}, 72 runnables::{Runnable, RunnableKind, TestId},
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs
index 8b06cbfc5..1db60b87f 100644
--- a/crates/ra_ide_db/src/defs.rs
+++ b/crates/ra_ide_db/src/defs.rs
@@ -18,7 +18,7 @@ use ra_syntax::{
18use crate::RootDatabase; 18use crate::RootDatabase;
19 19
20// FIXME: a more precise name would probably be `Symbol`? 20// FIXME: a more precise name would probably be `Symbol`?
21#[derive(Debug, PartialEq, Eq)] 21#[derive(Debug, PartialEq, Eq, Copy, Clone)]
22pub enum Definition { 22pub enum Definition {
23 Macro(MacroDef), 23 Macro(MacroDef),
24 Field(Field), 24 Field(Field),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 23168c3ae..e7c859577 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -11,7 +11,7 @@ use std::{ffi::OsString, path::PathBuf};
11 11
12use lsp_types::ClientCapabilities; 12use lsp_types::ClientCapabilities;
13use ra_flycheck::FlycheckConfig; 13use ra_flycheck::FlycheckConfig;
14use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; 14use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig, HoverConfig};
15use ra_project_model::{CargoConfig, JsonProject, ProjectManifest}; 15use ra_project_model::{CargoConfig, JsonProject, ProjectManifest};
16use serde::Deserialize; 16use serde::Deserialize;
17 17
@@ -34,6 +34,7 @@ pub struct Config {
34 pub assist: AssistConfig, 34 pub assist: AssistConfig,
35 pub call_info_full: bool, 35 pub call_info_full: bool,
36 pub lens: LensConfig, 36 pub lens: LensConfig,
37 pub hover: HoverConfig,
37 38
38 pub with_sysroot: bool, 39 pub with_sysroot: bool,
39 pub linked_projects: Vec<LinkedProject>, 40 pub linked_projects: Vec<LinkedProject>,
@@ -124,6 +125,7 @@ pub struct ClientCapsConfig {
124 pub work_done_progress: bool, 125 pub work_done_progress: bool,
125 pub code_action_group: bool, 126 pub code_action_group: bool,
126 pub resolve_code_action: bool, 127 pub resolve_code_action: bool,
128 pub hover_actions: bool,
127} 129}
128 130
129impl Default for Config { 131impl Default for Config {
@@ -162,6 +164,7 @@ impl Default for Config {
162 assist: AssistConfig::default(), 164 assist: AssistConfig::default(),
163 call_info_full: true, 165 call_info_full: true,
164 lens: LensConfig::default(), 166 lens: LensConfig::default(),
167 hover: HoverConfig::default(),
165 linked_projects: Vec::new(), 168 linked_projects: Vec::new(),
166 } 169 }
167 } 170 }
@@ -278,6 +281,14 @@ impl Config {
278 } 281 }
279 } 282 }
280 283
284 let mut use_hover_actions = false;
285 set(value, "/hoverActions/enable", &mut use_hover_actions);
286 if use_hover_actions {
287 set(value, "/hoverActions/implementations", &mut self.hover.implementations);
288 } else {
289 self.hover = HoverConfig::NO_ACTIONS;
290 }
291
281 log::info!("Config::update() = {:#?}", self); 292 log::info!("Config::update() = {:#?}", self);
282 293
283 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> { 294 fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option<T> {
@@ -331,17 +342,14 @@ impl Config {
331 342
332 self.assist.allow_snippets(false); 343 self.assist.allow_snippets(false);
333 if let Some(experimental) = &caps.experimental { 344 if let Some(experimental) = &caps.experimental {
334 let snippet_text_edit = 345 let get_bool = |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
335 experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true);
336 self.assist.allow_snippets(snippet_text_edit);
337 346
338 let code_action_group = 347 let snippet_text_edit = get_bool("snippetTextEdit");
339 experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true); 348 self.assist.allow_snippets(snippet_text_edit);
340 self.client_caps.code_action_group = code_action_group;
341 349
342 let resolve_code_action = 350 self.client_caps.code_action_group = get_bool("codeActionGroup");
343 experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true); 351 self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
344 self.client_caps.resolve_code_action = resolve_code_action; 352 self.client_caps.hover_actions = get_bool("hoverActions");
345 } 353 }
346 } 354 }
347} 355}
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 3b957534d..145a389ce 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -260,3 +260,37 @@ pub struct SnippetTextEdit {
260 #[serde(skip_serializing_if = "Option::is_none")] 260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub insert_text_format: Option<lsp_types::InsertTextFormat>, 261 pub insert_text_format: Option<lsp_types::InsertTextFormat>,
262} 262}
263
264pub enum HoverRequest {}
265
266impl Request for HoverRequest {
267 type Params = lsp_types::HoverParams;
268 type Result = Option<Hover>;
269 const METHOD: &'static str = "textDocument/hover";
270}
271
272#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
273pub struct Hover {
274 pub contents: lsp_types::HoverContents,
275 #[serde(skip_serializing_if = "Option::is_none")]
276 pub range: Option<Range>,
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub actions: Option<Vec<CommandLinkGroup>>,
279}
280
281#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
282pub struct CommandLinkGroup {
283 pub title: Option<String>,
284 pub commands: Vec<CommandLink>,
285}
286
287// LSP v3.15 Command does not have a `tooltip` field, vscode supports one.
288#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)]
289pub struct CommandLink {
290 pub title: String,
291 pub command: String,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub tooltip: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
295 pub arguments: Option<Vec<serde_json::Value>>,
296}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index e60337b8e..752dbf145 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -510,6 +510,7 @@ fn on_request(
510 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)? 510 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)?
511 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? 511 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
512 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)? 512 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
513 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
513 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 514 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
514 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 515 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
515 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 516 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
@@ -521,7 +522,6 @@ fn on_request(
521 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)? 522 .on::<lsp_types::request::CodeLensResolve>(handlers::handle_code_lens_resolve)?
522 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)? 523 .on::<lsp_types::request::FoldingRangeRequest>(handlers::handle_folding_range)?
523 .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)? 524 .on::<lsp_types::request::SignatureHelpRequest>(handlers::handle_signature_help)?
524 .on::<lsp_types::request::HoverRequest>(handlers::handle_hover)?
525 .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)? 525 .on::<lsp_types::request::PrepareRenameRequest>(handlers::handle_prepare_rename)?
526 .on::<lsp_types::request::Rename>(handlers::handle_rename)? 526 .on::<lsp_types::request::Rename>(handlers::handle_rename)?
527 .on::<lsp_types::request::References>(handlers::handle_references)? 527 .on::<lsp_types::request::References>(handlers::handle_references)?
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 6acf80c58..0958a231f 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -12,13 +12,14 @@ use lsp_types::{
12 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, 12 CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
13 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, 13 CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
14 CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight, 14 CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight,
15 DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, 15 DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent,
16 MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, 16 MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams,
17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, 17 SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation,
18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, 18 TextDocumentIdentifier, Url, WorkspaceEdit,
19}; 19};
20use ra_ide::{ 20use ra_ide::{
21 FileId, FilePosition, FileRange, Query, RangeInfo, RunnableKind, SearchScope, TextEdit, 21 FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, RunnableKind, SearchScope,
22 TextEdit,
22}; 23};
23use ra_prof::profile; 24use ra_prof::profile;
24use ra_project_model::TargetKind; 25use ra_project_model::TargetKind;
@@ -537,7 +538,7 @@ pub fn handle_signature_help(
537pub fn handle_hover( 538pub fn handle_hover(
538 snap: GlobalStateSnapshot, 539 snap: GlobalStateSnapshot,
539 params: lsp_types::HoverParams, 540 params: lsp_types::HoverParams,
540) -> Result<Option<Hover>> { 541) -> Result<Option<lsp_ext::Hover>> {
541 let _p = profile("handle_hover"); 542 let _p = profile("handle_hover");
542 let position = from_proto::file_position(&snap, params.text_document_position_params)?; 543 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
543 let info = match snap.analysis().hover(position)? { 544 let info = match snap.analysis().hover(position)? {
@@ -546,12 +547,13 @@ pub fn handle_hover(
546 }; 547 };
547 let line_index = snap.analysis.file_line_index(position.file_id)?; 548 let line_index = snap.analysis.file_line_index(position.file_id)?;
548 let range = to_proto::range(&line_index, info.range); 549 let range = to_proto::range(&line_index, info.range);
549 let res = Hover { 550 let res = lsp_ext::Hover {
550 contents: HoverContents::Markup(MarkupContent { 551 contents: HoverContents::Markup(MarkupContent {
551 kind: MarkupKind::Markdown, 552 kind: MarkupKind::Markdown,
552 value: crate::markdown::format_docs(&info.info.to_markup()), 553 value: crate::markdown::format_docs(&info.info.to_markup()),
553 }), 554 }),
554 range: Some(range), 555 range: Some(range),
556 actions: Some(prepare_hover_actions(&world, info.info.actions())),
555 }; 557 };
556 Ok(Some(res)) 558 Ok(Some(res))
557} 559}
@@ -924,24 +926,13 @@ pub fn handle_code_lens_resolve(
924 _ => vec![], 926 _ => vec![],
925 }; 927 };
926 928
927 let title = if locations.len() == 1 { 929 let title = implementation_title(locations.len());
928 "1 implementation".into() 930 let cmd = show_references_command(
929 } else {
930 format!("{} implementations", locations.len())
931 };
932
933 // We cannot use the 'editor.action.showReferences' command directly
934 // because that command requires vscode types which we convert in the handler
935 // on the client side.
936 let cmd = Command {
937 title, 931 title,
938 command: "rust-analyzer.showReferences".into(), 932 &lens_params.text_document_position_params.text_document.uri,
939 arguments: Some(vec![ 933 code_lens.range.start,
940 to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(), 934 locations,
941 to_value(code_lens.range.start).unwrap(), 935 );
942 to_value(locations).unwrap(),
943 ]),
944 };
945 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) 936 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
946 } 937 }
947 None => Ok(CodeLens { 938 None => Ok(CodeLens {
@@ -1145,3 +1136,83 @@ pub fn handle_semantic_tokens_range(
1145 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1136 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1146 Ok(Some(semantic_tokens.into())) 1137 Ok(Some(semantic_tokens.into()))
1147} 1138}
1139
1140fn implementation_title(count: usize) -> String {
1141 if count == 1 {
1142 "1 implementation".into()
1143 } else {
1144 format!("{} implementations", count)
1145 }
1146}
1147
1148fn show_references_command(
1149 title: String,
1150 uri: &lsp_types::Url,
1151 position: lsp_types::Position,
1152 locations: Vec<lsp_types::Location>,
1153) -> Command {
1154 // We cannot use the 'editor.action.showReferences' command directly
1155 // because that command requires vscode types which we convert in the handler
1156 // on the client side.
1157
1158 Command {
1159 title,
1160 command: "rust-analyzer.showReferences".into(),
1161 arguments: Some(vec![
1162 to_value(uri).unwrap(),
1163 to_value(position).unwrap(),
1164 to_value(locations).unwrap(),
1165 ]),
1166 }
1167}
1168
1169fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
1170 lsp_ext::CommandLink {
1171 tooltip: Some(tooltip),
1172 title: command.title,
1173 command: command.command,
1174 arguments: command.arguments,
1175 }
1176}
1177
1178fn show_impl_command_link(
1179 world: &WorldSnapshot,
1180 position: &FilePosition,
1181) -> Option<lsp_ext::CommandLinkGroup> {
1182 if world.config.hover.implementations {
1183 if let Some(nav_data) = world.analysis().goto_implementation(*position).unwrap_or(None) {
1184 let uri = to_proto::url(world, position.file_id).ok()?;
1185 let line_index = world.analysis().file_line_index(position.file_id).ok()?;
1186 let position = to_proto::position(&line_index, position.offset);
1187 let locations: Vec<_> = nav_data
1188 .info
1189 .iter()
1190 .filter_map(|it| to_proto::location(world, it.file_range()).ok())
1191 .collect();
1192 let title = implementation_title(locations.len());
1193 let command = show_references_command(title, &uri, position, locations);
1194
1195 return Some(lsp_ext::CommandLinkGroup {
1196 commands: vec![to_command_link(command, "Go to implementations".into())],
1197 ..Default::default()
1198 });
1199 }
1200 }
1201 None
1202}
1203
1204fn prepare_hover_actions(
1205 world: &WorldSnapshot,
1206 actions: &[HoverAction],
1207) -> Vec<lsp_ext::CommandLinkGroup> {
1208 if world.config.hover.none() || !world.config.client_caps.hover_actions {
1209 return Vec::new();
1210 }
1211
1212 actions
1213 .iter()
1214 .filter_map(|it| match it {
1215 HoverAction::Implementaion(position) => show_impl_command_link(world, position),
1216 })
1217 .collect()
1218}
diff --git a/editors/code/package.json b/editors/code/package.json
index 30ab7ba4a..b9c57db3b 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -462,17 +462,27 @@
462 "default": true 462 "default": true
463 }, 463 },
464 "rust-analyzer.lens.run": { 464 "rust-analyzer.lens.run": {
465 "markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.", 465 "markdownDescription": "Whether to show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
466 "type": "boolean", 466 "type": "boolean",
467 "default": true 467 "default": true
468 }, 468 },
469 "rust-analyzer.lens.debug": { 469 "rust-analyzer.lens.debug": {
470 "markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.", 470 "markdownDescription": "Whether to show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
471 "type": "boolean", 471 "type": "boolean",
472 "default": true 472 "default": true
473 }, 473 },
474 "rust-analyzer.lens.implementations": { 474 "rust-analyzer.lens.implementations": {
475 "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", 475 "markdownDescription": "Whether to show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.",
476 "type": "boolean",
477 "default": true
478 },
479 "rust-analyzer.hoverActions.enable": {
480 "description": "Whether to show HoverActions in Rust files.",
481 "type": "boolean",
482 "default": true
483 },
484 "rust-analyzer.hoverActions.implementations": {
485 "markdownDescription": "Whether to show `Implementations` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
476 "type": "boolean", 486 "type": "boolean",
477 "default": true 487 "default": true
478 }, 488 },
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 40ad1e3cd..9df670283 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -7,6 +7,29 @@ import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.pr
7import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; 7import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed';
8import { assert } from './util'; 8import { assert } from './util';
9 9
10function toTrusted(obj: vscode.MarkedString): vscode.MarkedString {
11 const md = <vscode.MarkdownString>obj;
12 if (md && md.value.includes("```rust")) {
13 md.isTrusted = true;
14 return md;
15 }
16 return obj;
17}
18
19function renderCommand(cmd: CommandLink) {
20 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
21}
22
23function renderHoverActions(actions: CommandLinkGroup[]): vscode.MarkdownString {
24 const text = actions.map(group =>
25 (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ')
26 ).join('___');
27
28 const result = new vscode.MarkdownString(text);
29 result.isTrusted = true;
30 return result;
31}
32
10export function createClient(serverPath: string, cwd: string): lc.LanguageClient { 33export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
11 // '.' Is the fallback if no folder is open 34 // '.' Is the fallback if no folder is open
12 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 35 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
@@ -35,6 +58,27 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
35 if (res === undefined) throw new Error('busy'); 58 if (res === undefined) throw new Error('busy');
36 return res; 59 return res;
37 }, 60 },
61 async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
62 return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
63 (result) => {
64 const hover = client.protocol2CodeConverter.asHover(result);
65 if (hover) {
66 // Workaround to support command links (trusted vscode.MarkdownString) in hovers
67 // https://github.com/microsoft/vscode/issues/33577
68 hover.contents = hover.contents.map(toTrusted);
69
70 const actions = (<any>result).actions;
71 if (actions) {
72 hover.contents.push(renderHoverActions(actions));
73 }
74 }
75 return hover;
76 },
77 (error) => {
78 client.logFailedRequest(lc.HoverRequest.type, error);
79 return Promise.resolve(null);
80 });
81 },
38 // Using custom handling of CodeActions where each code action is resloved lazily 82 // Using custom handling of CodeActions where each code action is resloved lazily
39 // That's why we are not waiting for any command or edits 83 // That's why we are not waiting for any command or edits
40 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { 84 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
@@ -129,6 +173,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
129 caps.snippetTextEdit = true; 173 caps.snippetTextEdit = true;
130 caps.codeActionGroup = true; 174 caps.codeActionGroup = true;
131 caps.resolveCodeAction = true; 175 caps.resolveCodeAction = true;
176 caps.hoverActions = true;
132 capabilities.experimental = caps; 177 capabilities.experimental = caps;
133 } 178 }
134 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 179 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index e8abf8284..d8f0037d4 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -16,10 +16,8 @@ export class Config {
16 "files", 16 "files",
17 "highlighting", 17 "highlighting",
18 "updates.channel", 18 "updates.channel",
19 "lens.enable", 19 "lens", // works as lens.*
20 "lens.run", 20 "hoverActions", // works as hoverActions.*
21 "lens.debug",
22 "lens.implementations",
23 ] 21 ]
24 .map(opt => `${this.rootSection}.${opt}`); 22 .map(opt => `${this.rootSection}.${opt}`);
25 23
@@ -132,4 +130,11 @@ export class Config {
132 implementations: this.get<boolean>("lens.implementations"), 130 implementations: this.get<boolean>("lens.implementations"),
133 }; 131 };
134 } 132 }
133
134 get hoverActions() {
135 return {
136 enable: this.get<boolean>("hoverActions.enable"),
137 implementations: this.get<boolean>("hoverActions.implementations"),
138 };
139 }
135} 140}
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 9793b926c..e16ea799c 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -90,3 +90,15 @@ export interface SsrParams {
90 parseOnly: boolean; 90 parseOnly: boolean;
91} 91}
92export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr'); 92export const ssr = new lc.RequestType<SsrParams, lc.WorkspaceEdit, void>('experimental/ssr');
93
94export interface CommandLink extends lc.Command {
95 /**
96 * A tooltip for the command, when represented in the UI.
97 */
98 tooltip?: string;
99}
100
101export interface CommandLinkGroup {
102 title?: string;
103 commands: CommandLink[];
104}