aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock4
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs19
-rw-r--r--crates/ra_db/src/lib.rs26
-rw-r--r--crates/ra_hir_def/src/nameres/mod_resolution.rs2
-rw-r--r--crates/ra_hir_def/src/test_db.rs8
-rw-r--r--crates/ra_hir_expand/src/builtin_macro.rs4
-rw-r--r--crates/ra_hir_expand/src/test_db.rs8
-rw-r--r--crates/ra_hir_ty/src/test_db.rs8
-rw-r--r--crates/ra_ide/src/hover.rs148
-rw-r--r--crates/ra_ide/src/inlay_hints.rs3
-rw-r--r--crates/ra_ide/src/lib.rs2
-rw-r--r--crates/ra_ide_db/src/defs.rs2
-rw-r--r--crates/ra_ide_db/src/lib.rs8
-rw-r--r--crates/ra_parser/src/grammar/items.rs15
-rw-r--r--crates/ra_syntax/test_data/parser/err/0043_default_const.rast40
-rw-r--r--crates/ra_syntax/test_data/parser/err/0043_default_const.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast34
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast18
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0066_default_const.rast44
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0066_default_const.rs3
-rw-r--r--crates/rust-analyzer/src/config.rs29
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs32
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs129
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs83
-rw-r--r--docs/dev/lsp-extensions.md38
-rw-r--r--editors/code/package.json16
-rw-r--r--editors/code/src/client.ts32
-rw-r--r--editors/code/src/config.ts13
-rw-r--r--editors/code/src/lsp_ext.ts12
31 files changed, 603 insertions, 183 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5f88ad0c4..15c6a4c11 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -471,9 +471,9 @@ dependencies = [
471 471
472[[package]] 472[[package]]
473name = "inotify" 473name = "inotify"
474version = "0.7.0" 474version = "0.7.1"
475source = "registry+https://github.com/rust-lang/crates.io-index" 475source = "registry+https://github.com/rust-lang/crates.io-index"
476checksum = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8" 476checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
477dependencies = [ 477dependencies = [
478 "bitflags", 478 "bitflags",
479 "inotify-sys", 479 "inotify-sys",
diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
index beb5b7366..28fcbc9ba 100644
--- a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
+++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
@@ -41,8 +41,6 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { 41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) 42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { 43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) {
44 // only allow naming the last anonymous lifetime
45 lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?;
46 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) 44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
47 } else { 45 } else {
48 None 46 None
@@ -191,6 +189,23 @@ mod tests {
191 } 189 }
192 190
193 #[test] 191 #[test]
192 fn test_impl_with_other_type_param() {
193 check_assist(
194 introduce_named_lifetime,
195 "impl<I> fmt::Display for SepByBuilder<'_<|>, I>
196 where
197 I: Iterator,
198 I::Item: fmt::Display,
199 {",
200 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
201 where
202 I: Iterator,
203 I::Item: fmt::Display,
204 {",
205 )
206 }
207
208 #[test]
194 fn test_example_case_cursor_before_tick() { 209 fn test_example_case_cursor_before_tick() {
195 check_assist( 210 check_assist(
196 introduce_named_lifetime, 211 introduce_named_lifetime,
diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs
index e7868268b..fcfa2788c 100644
--- a/crates/ra_db/src/lib.rs
+++ b/crates/ra_db/src/lib.rs
@@ -89,8 +89,7 @@ pub const DEFAULT_LRU_CAP: usize = 128;
89pub trait FileLoader { 89pub trait FileLoader {
90 /// Text of the file. 90 /// Text of the file.
91 fn file_text(&self, file_id: FileId) -> Arc<String>; 91 fn file_text(&self, file_id: FileId) -> Arc<String>;
92 fn resolve_relative_path(&self, anchor: FileId, relative_path: &RelativePath) 92 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId>;
93 -> Option<FileId>;
94 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>>; 93 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>>;
95 94
96 fn resolve_extern_path( 95 fn resolve_extern_path(
@@ -155,20 +154,21 @@ impl<T: SourceDatabaseExt> FileLoader for FileLoaderDelegate<&'_ T> {
155 fn file_text(&self, file_id: FileId) -> Arc<String> { 154 fn file_text(&self, file_id: FileId) -> Arc<String> {
156 SourceDatabaseExt::file_text(self.0, file_id) 155 SourceDatabaseExt::file_text(self.0, file_id)
157 } 156 }
158 fn resolve_relative_path( 157 /// Note that we intentionally accept a `&str` and not a `&Path` here. This
159 &self, 158 /// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such,
160 anchor: FileId, 159 /// so the input is guaranteed to be utf-8 string. We might introduce
161 relative_path: &RelativePath, 160 /// `struct StrPath(str)` for clarity some day, but it's a bit messy, so we
162 ) -> Option<FileId> { 161 /// get by with a `&str` for the time being.
163 let path = { 162 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
164 let mut path = self.0.file_relative_path(anchor); 163 let rel_path = {
165 assert!(path.pop()); 164 let mut rel_path = self.0.file_relative_path(anchor);
166 path.push(relative_path); 165 assert!(rel_path.pop());
167 path.normalize() 166 rel_path.push(path);
167 rel_path.normalize()
168 }; 168 };
169 let source_root = self.0.file_source_root(anchor); 169 let source_root = self.0.file_source_root(anchor);
170 let source_root = self.0.source_root(source_root); 170 let source_root = self.0.source_root(source_root);
171 source_root.file_by_relative_path(&path) 171 source_root.file_by_relative_path(&rel_path)
172 } 172 }
173 173
174 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { 174 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs
index 386c5cade..cede4a6fc 100644
--- a/crates/ra_hir_def/src/nameres/mod_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs
@@ -61,7 +61,7 @@ impl ModDir {
61 }; 61 };
62 62
63 for candidate in candidate_files.iter() { 63 for candidate in candidate_files.iter() {
64 if let Some(file_id) = db.resolve_relative_path(file_id, candidate) { 64 if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) {
65 let mut root_non_dir_owner = false; 65 let mut root_non_dir_owner = false;
66 let mut mod_path = RelativePathBuf::new(); 66 let mut mod_path = RelativePathBuf::new();
67 if !(candidate.ends_with("mod.rs") || attr_path.is_some()) { 67 if !(candidate.ends_with("mod.rs") || attr_path.is_some()) {
diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs
index eb83dee79..e7a5182f0 100644
--- a/crates/ra_hir_def/src/test_db.rs
+++ b/crates/ra_hir_def/src/test_db.rs
@@ -58,12 +58,8 @@ impl FileLoader for TestDB {
58 fn file_text(&self, file_id: FileId) -> Arc<String> { 58 fn file_text(&self, file_id: FileId) -> Arc<String> {
59 FileLoaderDelegate(self).file_text(file_id) 59 FileLoaderDelegate(self).file_text(file_id)
60 } 60 }
61 fn resolve_relative_path( 61 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
62 &self, 62 FileLoaderDelegate(self).resolve_path(anchor, path)
63 anchor: FileId,
64 relative_path: &RelativePath,
65 ) -> Option<FileId> {
66 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
67 } 63 }
68 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { 64 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
69 FileLoaderDelegate(self).relevant_crates(file_id) 65 FileLoaderDelegate(self).relevant_crates(file_id)
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
index 3bce8f673..eec5fb8eb 100644
--- a/crates/ra_hir_expand/src/builtin_macro.rs
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -8,7 +8,7 @@ use crate::{
8use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId}; 8use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId};
9use either::Either; 9use either::Either;
10use mbe::parse_to_token_tree; 10use mbe::parse_to_token_tree;
11use ra_db::{FileId, RelativePath}; 11use ra_db::FileId;
12use ra_parser::FragmentKind; 12use ra_parser::FragmentKind;
13 13
14macro_rules! register_builtin { 14macro_rules! register_builtin {
@@ -297,7 +297,7 @@ fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Opti
297 let call_site = call_id.as_file().original_file(db); 297 let call_site = call_id.as_file().original_file(db);
298 298
299 // Handle trivial case 299 // Handle trivial case
300 if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { 300 if let Some(res) = db.resolve_path(call_site, path) {
301 // Prevent include itself 301 // Prevent include itself
302 return if res == call_site { None } else { Some(res) }; 302 return if res == call_site { None } else { Some(res) };
303 } 303 }
diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs
index c1fb762de..765a2f6d1 100644
--- a/crates/ra_hir_expand/src/test_db.rs
+++ b/crates/ra_hir_expand/src/test_db.rs
@@ -41,12 +41,8 @@ impl FileLoader for TestDB {
41 fn file_text(&self, file_id: FileId) -> Arc<String> { 41 fn file_text(&self, file_id: FileId) -> Arc<String> {
42 FileLoaderDelegate(self).file_text(file_id) 42 FileLoaderDelegate(self).file_text(file_id)
43 } 43 }
44 fn resolve_relative_path( 44 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
45 &self, 45 FileLoaderDelegate(self).resolve_path(anchor, path)
46 anchor: FileId,
47 relative_path: &RelativePath,
48 ) -> Option<FileId> {
49 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
50 } 46 }
51 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { 47 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
52 FileLoaderDelegate(self).relevant_crates(file_id) 48 FileLoaderDelegate(self).relevant_crates(file_id)
diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs
index 8498d3d96..21a3bdfd1 100644
--- a/crates/ra_hir_ty/src/test_db.rs
+++ b/crates/ra_hir_ty/src/test_db.rs
@@ -72,12 +72,8 @@ impl FileLoader for TestDB {
72 fn file_text(&self, file_id: FileId) -> Arc<String> { 72 fn file_text(&self, file_id: FileId) -> Arc<String> {
73 FileLoaderDelegate(self).file_text(file_id) 73 FileLoaderDelegate(self).file_text(file_id)
74 } 74 }
75 fn resolve_relative_path( 75 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
76 &self, 76 FileLoaderDelegate(self).resolve_path(anchor, path)
77 anchor: FileId,
78 relative_path: &RelativePath,
79 ) -> Option<FileId> {
80 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
81 } 77 }
82 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { 78 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
83 FileLoaderDelegate(self).relevant_crates(file_id) 79 FileLoaderDelegate(self).relevant_crates(file_id)
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 9636cd0d6..62df07459 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, NavigationTarget, RangeInfo,
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,55 @@ 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_has_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_has_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_has_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
1165 #[test]
1166 fn test_hover_enum_has_impl_action() {
1167 let (_, actions) = check_hover_result(
1168 "
1169 //- /lib.rs
1170 enum foo<|>() {
1171 A,
1172 B
1173 }
1174 ",
1175 &["enum foo"],
1176 );
1177 assert_impl_action(&actions[0], 5);
1178 }
1055} 1179}
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 75bd3c96b..49366de98 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -149,11 +149,10 @@ fn get_param_name_hints(
149 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), 149 ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(),
150 _ => return None, 150 _ => return None,
151 }; 151 };
152 let args_count = args.clone().count();
153 152
154 let fn_signature = get_fn_signature(sema, &expr)?; 153 let fn_signature = get_fn_signature(sema, &expr)?;
155 let n_params_to_skip = 154 let n_params_to_skip =
156 if fn_signature.has_self_param && fn_signature.parameter_names.len() > args_count { 155 if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) {
157 1 156 1
158 } else { 157 } else {
159 0 158 0
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 34c2d75fe..a56718d3f 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::{HoverAction, HoverConfig, HoverResult},
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/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs
index 1b74e6558..93d5891a0 100644
--- a/crates/ra_ide_db/src/lib.rs
+++ b/crates/ra_ide_db/src/lib.rs
@@ -57,12 +57,8 @@ impl FileLoader for RootDatabase {
57 fn file_text(&self, file_id: FileId) -> Arc<String> { 57 fn file_text(&self, file_id: FileId) -> Arc<String> {
58 FileLoaderDelegate(self).file_text(file_id) 58 FileLoaderDelegate(self).file_text(file_id)
59 } 59 }
60 fn resolve_relative_path( 60 fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
61 &self, 61 FileLoaderDelegate(self).resolve_path(anchor, path)
62 anchor: FileId,
63 relative_path: &RelativePath,
64 ) -> Option<FileId> {
65 FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path)
66 } 62 }
67 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> { 63 fn relevant_crates(&self, file_id: FileId) -> Arc<Vec<CrateId>> {
68 FileLoaderDelegate(self).relevant_crates(file_id) 64 FileLoaderDelegate(self).relevant_crates(file_id)
diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs
index 67a924de5..9c14b954a 100644
--- a/crates/ra_parser/src/grammar/items.rs
+++ b/crates/ra_parser/src/grammar/items.rs
@@ -118,7 +118,17 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
118 && p.at_contextual_kw("default") 118 && p.at_contextual_kw("default")
119 && (match p.nth(1) { 119 && (match p.nth(1) {
120 T![impl] => true, 120 T![impl] => true,
121 T![fn] | T![type] => { 121 T![unsafe] => {
122 // test default_unsafe_impl
123 // default unsafe impl Foo {}
124 if p.nth(2) == T![impl] {
125 p.bump_remap(T![default]);
126 p.bump(T![unsafe]);
127 has_mods = true;
128 }
129 false
130 }
131 T![fn] | T![type] | T![const] => {
122 if let ItemFlavor::Mod = flavor { 132 if let ItemFlavor::Mod = flavor {
123 true 133 true
124 } else { 134 } else {
@@ -198,6 +208,9 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul
198 // default type T = Bar; 208 // default type T = Bar;
199 // default fn foo() {} 209 // default fn foo() {}
200 // } 210 // }
211 T![const] => {
212 consts::const_def(p, m);
213 }
201 214
202 // test unsafe_default_impl 215 // test unsafe_default_impl
203 // unsafe default impl Foo {} 216 // unsafe default impl Foo {}
diff --git a/crates/ra_syntax/test_data/parser/err/0043_default_const.rast b/crates/ra_syntax/test_data/parser/err/0043_default_const.rast
new file mode 100644
index 000000000..8eb583ef8
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/err/0043_default_const.rast
@@ -0,0 +1,40 @@
1[email protected]
2 [email protected]
3 [email protected] "trait"
4 [email protected] " "
5 [email protected]
6 [email protected] "T"
7 [email protected] " "
8 [email protected]
9 [email protected] "{"
10 [email protected] "\n "
11 [email protected]
12 [email protected]
13 [email protected]
14 [email protected]
15 [email protected] "default"
16 [email protected] " "
17 [email protected]
18 [email protected] "const"
19 [email protected] " "
20 [email protected]
21 [email protected] "f"
22 [email protected] ":"
23 [email protected] " "
24 [email protected]
25 [email protected]
26 [email protected]
27 [email protected]
28 [email protected] "u8"
29 [email protected] " "
30 [email protected] "="
31 [email protected] " "
32 [email protected]
33 [email protected] "0"
34 [email protected] ";"
35 [email protected] "\n"
36 [email protected] "}"
37 [email protected] "\n"
38error 19..19: expected BANG
39error 19..19: expected `{`, `[`, `(`
40error 19..19: expected SEMICOLON
diff --git a/crates/ra_syntax/test_data/parser/err/0043_default_const.rs b/crates/ra_syntax/test_data/parser/err/0043_default_const.rs
new file mode 100644
index 000000000..80f15474a
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/err/0043_default_const.rs
@@ -0,0 +1,3 @@
1trait T {
2 default const f: u8 = 0;
3}
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast
index 9be441110..53f7ebaf9 100644
--- a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast
+++ b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast
@@ -17,23 +17,29 @@ [email protected]
17 [email protected] "{" 17 [email protected] "{"
18 [email protected] "}" 18 [email protected] "}"
19 [email protected] "\n" 19 [email protected] "\n"
20 ERROR@25..31 20 CONST_DEF@25..46
21 [email protected] "unsafe" 21 [email protected] "unsafe"
22 [email protected] " " 22 [email protected] " "
23 [email protected]
24 [email protected] "const" 23 [email protected] "const"
25 [email protected] " " 24 [email protected] " "
26 [email protected] "fn" 25 [email protected]
26 [email protected] "fn"
27 [email protected] " " 27 [email protected] " "
28 [email protected] 28 [email protected]
29 [email protected] "bar" 29 [email protected]
30 [email protected] 30 [email protected]
31 [email protected] "(" 31 [email protected]
32 [email protected] ")" 32 [email protected] "bar"
33 [email protected] " " 33 [email protected]
34 [email protected] 34 [email protected] "("
35 [email protected] "{" 35 [email protected] ")"
36 [email protected] "}" 36 [email protected] " "
37 [email protected]
38 [email protected] "{"
39 [email protected] "}"
37 [email protected] "\n" 40 [email protected] "\n"
38error 6..6: expected existential, fn, trait or impl 41error 6..6: expected existential, fn, trait or impl
39error 31..31: expected existential, fn, trait or impl 42error 38..38: expected a name
43error 40..40: expected COLON
44error 46..46: expected SEMICOLON
45error 47..47: expected an item
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast
new file mode 100644
index 000000000..a9eda5668
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast
@@ -0,0 +1,18 @@
1[email protected]
2 [email protected]
3 [email protected] "default"
4 [email protected] " "
5 [email protected] "unsafe"
6 [email protected] " "
7 [email protected] "impl"
8 [email protected] " "
9 [email protected]
10 [email protected]
11 [email protected]
12 [email protected]
13 [email protected] "Foo"
14 [email protected] " "
15 [email protected]
16 [email protected] "{"
17 [email protected] "}"
18 [email protected] "\n"
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs
new file mode 100644
index 000000000..ba0998ff4
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs
@@ -0,0 +1 @@
default unsafe impl Foo {}
diff --git a/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast
new file mode 100644
index 000000000..dab0247ee
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast
@@ -0,0 +1,44 @@
1[email protected]
2 [email protected]
3 [email protected] "impl"
4 [email protected] " "
5 [email protected]
6 [email protected]
7 [email protected]
8 [email protected]
9 [email protected] "T"
10 [email protected] " "
11 [email protected] "for"
12 [email protected] " "
13 [email protected]
14 [email protected]
15 [email protected]
16 [email protected]
17 [email protected] "Foo"
18 [email protected] " "
19 [email protected]
20 [email protected] "{"
21 [email protected] "\n "
22 [email protected]
23 [email protected] "default"
24 [email protected] " "
25 [email protected] "const"
26 [email protected] " "
27 [email protected]
28 [email protected] "f"
29 [email protected] ":"
30 [email protected] " "
31 [email protected]
32 [email protected]
33 [email protected]
34 [email protected]
35 [email protected] "u8"
36 [email protected] " "
37 [email protected] "="
38 [email protected] " "
39 [email protected]
40 [email protected] "0"
41 [email protected] ";"
42 [email protected] "\n"
43 [email protected] "}"
44 [email protected] "\n"
diff --git a/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs
new file mode 100644
index 000000000..dfb3b92dc
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs
@@ -0,0 +1,3 @@
1impl T for Foo {
2 default const f: u8 = 0;
3}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 23168c3ae..8d6efdbe8 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, HoverConfig, InlayHintsConfig};
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,15 @@ 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 =
335 experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); 346 |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true);
336 self.assist.allow_snippets(snippet_text_edit);
337 347
338 let code_action_group = 348 let snippet_text_edit = get_bool("snippetTextEdit");
339 experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true); 349 self.assist.allow_snippets(snippet_text_edit);
340 self.client_caps.code_action_group = code_action_group;
341 350
342 let resolve_code_action = 351 self.client_caps.code_action_group = get_bool("codeActionGroup");
343 experimental.get("resolveCodeAction").and_then(|it| it.as_bool()) == Some(true); 352 self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
344 self.client_caps.resolve_code_action = resolve_code_action; 353 self.client_caps.hover_actions = get_bool("hoverActions");
345 } 354 }
346 } 355 }
347} 356}
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 3b957534d..1371f6cb4 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -260,3 +260,35 @@ 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 #[serde(flatten)]
275 pub hover: lsp_types::Hover,
276 #[serde(skip_serializing_if = "Vec::is_empty")]
277 pub actions: Vec<CommandLinkGroup>,
278}
279
280#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)]
281pub struct CommandLinkGroup {
282 #[serde(skip_serializing_if = "Option::is_none")]
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, Clone, Default, Deserialize, Serialize)]
289pub struct CommandLink {
290 #[serde(flatten)]
291 pub command: lsp_types::Command,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub tooltip: Option<String>,
294}
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..3ff779702 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,14 +547,18 @@ 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 hover = lsp_ext::Hover {
550 contents: HoverContents::Markup(MarkupContent { 551 hover: lsp_types::Hover {
551 kind: MarkupKind::Markdown, 552 contents: HoverContents::Markup(MarkupContent {
552 value: crate::markdown::format_docs(&info.info.to_markup()), 553 kind: MarkupKind::Markdown,
553 }), 554 value: crate::markdown::format_docs(&info.info.to_markup()),
554 range: Some(range), 555 }),
556 range: Some(range),
557 },
558 actions: prepare_hover_actions(&snap, info.info.actions()),
555 }; 559 };
556 Ok(Some(res)) 560
561 Ok(Some(hover))
557} 562}
558 563
559pub fn handle_prepare_rename( 564pub fn handle_prepare_rename(
@@ -924,24 +929,13 @@ pub fn handle_code_lens_resolve(
924 _ => vec![], 929 _ => vec![],
925 }; 930 };
926 931
927 let title = if locations.len() == 1 { 932 let title = implementation_title(locations.len());
928 "1 implementation".into() 933 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, 934 title,
938 command: "rust-analyzer.showReferences".into(), 935 &lens_params.text_document_position_params.text_document.uri,
939 arguments: Some(vec![ 936 code_lens.range.start,
940 to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(), 937 locations,
941 to_value(code_lens.range.start).unwrap(), 938 );
942 to_value(locations).unwrap(),
943 ]),
944 };
945 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) 939 Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
946 } 940 }
947 None => Ok(CodeLens { 941 None => Ok(CodeLens {
@@ -1145,3 +1139,78 @@ pub fn handle_semantic_tokens_range(
1145 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1139 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1146 Ok(Some(semantic_tokens.into())) 1140 Ok(Some(semantic_tokens.into()))
1147} 1141}
1142
1143fn implementation_title(count: usize) -> String {
1144 if count == 1 {
1145 "1 implementation".into()
1146 } else {
1147 format!("{} implementations", count)
1148 }
1149}
1150
1151fn show_references_command(
1152 title: String,
1153 uri: &lsp_types::Url,
1154 position: lsp_types::Position,
1155 locations: Vec<lsp_types::Location>,
1156) -> Command {
1157 // We cannot use the 'editor.action.showReferences' command directly
1158 // because that command requires vscode types which we convert in the handler
1159 // on the client side.
1160
1161 Command {
1162 title,
1163 command: "rust-analyzer.showReferences".into(),
1164 arguments: Some(vec![
1165 to_value(uri).unwrap(),
1166 to_value(position).unwrap(),
1167 to_value(locations).unwrap(),
1168 ]),
1169 }
1170}
1171
1172fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
1173 lsp_ext::CommandLink { tooltip: Some(tooltip), command }
1174}
1175
1176fn show_impl_command_link(
1177 snap: &GlobalStateSnapshot,
1178 position: &FilePosition,
1179) -> Option<lsp_ext::CommandLinkGroup> {
1180 if snap.config.hover.implementations {
1181 if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) {
1182 let uri = to_proto::url(snap, position.file_id).ok()?;
1183 let line_index = snap.analysis().file_line_index(position.file_id).ok()?;
1184 let position = to_proto::position(&line_index, position.offset);
1185 let locations: Vec<_> = nav_data
1186 .info
1187 .iter()
1188 .filter_map(|it| to_proto::location(snap, it.file_range()).ok())
1189 .collect();
1190 let title = implementation_title(locations.len());
1191 let command = show_references_command(title, &uri, position, locations);
1192
1193 return Some(lsp_ext::CommandLinkGroup {
1194 commands: vec![to_command_link(command, "Go to implementations".into())],
1195 ..Default::default()
1196 });
1197 }
1198 }
1199 None
1200}
1201
1202fn prepare_hover_actions(
1203 snap: &GlobalStateSnapshot,
1204 actions: &[HoverAction],
1205) -> Vec<lsp_ext::CommandLinkGroup> {
1206 if snap.config.hover.none() || !snap.config.client_caps.hover_actions {
1207 return Vec::new();
1208 }
1209
1210 actions
1211 .iter()
1212 .filter_map(|it| match it {
1213 HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
1214 })
1215 .collect()
1216}
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index ad3476310..0e2a83c6a 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -4,9 +4,7 @@ use std::{collections::HashMap, path::PathBuf, time::Instant};
4 4
5use lsp_types::{ 5use lsp_types::{
6 notification::DidOpenTextDocument, 6 notification::DidOpenTextDocument,
7 request::{ 7 request::{CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest},
8 CodeActionRequest, Completion, Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest,
9 },
10 CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, 8 CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
11 DocumentFormattingParams, FormattingOptions, GotoDefinitionParams, HoverParams, 9 DocumentFormattingParams, FormattingOptions, GotoDefinitionParams, HoverParams,
12 PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams, 10 PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams,
@@ -507,6 +505,10 @@ fn main() {
507 println!("cargo:rerun-if-changed=build.rs"); 505 println!("cargo:rerun-if-changed=build.rs");
508} 506}
509//- src/main.rs 507//- src/main.rs
508#[rustc_builtin_macro] macro_rules! include {}
509#[rustc_builtin_macro] macro_rules! concat {}
510#[rustc_builtin_macro] macro_rules! env {}
511
510include!(concat!(env!("OUT_DIR"), "/hello.rs")); 512include!(concat!(env!("OUT_DIR"), "/hello.rs"));
511 513
512#[cfg(atom_cfg)] 514#[cfg(atom_cfg)]
@@ -521,10 +523,8 @@ struct B;
521fn main() { 523fn main() {
522 let va = A; 524 let va = A;
523 let vb = B; 525 let vb = B;
524 message(); 526 let should_be_str = message();
525} 527}
526
527fn main() { message(); }
528"###, 528"###,
529 ) 529 )
530 .with_config(|config| { 530 .with_config(|config| {
@@ -532,54 +532,35 @@ fn main() { message(); }
532 }) 532 })
533 .server(); 533 .server();
534 server.wait_until_workspace_is_loaded(); 534 server.wait_until_workspace_is_loaded();
535 let res = server.send_request::<GotoDefinition>(GotoDefinitionParams { 535 let res = server.send_request::<HoverRequest>(HoverParams {
536 text_document_position_params: TextDocumentPositionParams::new( 536 text_document_position_params: TextDocumentPositionParams::new(
537 server.doc_id("src/main.rs"), 537 server.doc_id("src/main.rs"),
538 Position::new(14, 8), 538 Position::new(18, 10),
539 ), 539 ),
540 work_done_progress_params: Default::default(), 540 work_done_progress_params: Default::default(),
541 partial_result_params: Default::default(),
542 }); 541 });
543 assert!(format!("{}", res).contains("hello.rs")); 542 assert!(res.to_string().contains("&str"));
544 server.request::<GotoTypeDefinition>( 543 server.request::<GotoTypeDefinition>(
545 GotoDefinitionParams { 544 GotoDefinitionParams {
546 text_document_position_params: TextDocumentPositionParams::new( 545 text_document_position_params: TextDocumentPositionParams::new(
547 server.doc_id("src/main.rs"), 546 server.doc_id("src/main.rs"),
548 Position::new(12, 9), 547 Position::new(16, 9),
549 ), 548 ),
550 work_done_progress_params: Default::default(), 549 work_done_progress_params: Default::default(),
551 partial_result_params: Default::default(), 550 partial_result_params: Default::default(),
552 }, 551 },
553 json!([{ 552 json!([{
554 "originSelectionRange": { 553 "originSelectionRange": {
555 "end": { 554 "end": { "character": 10, "line": 16 },
556 "character": 10, 555 "start": { "character": 8, "line": 16 }
557 "line": 12
558 },
559 "start": {
560 "character": 8,
561 "line": 12
562 }
563 }, 556 },
564 "targetRange": { 557 "targetRange": {
565 "end": { 558 "end": { "character": 9, "line": 7 },
566 "character": 9, 559 "start": { "character": 0, "line": 6 }
567 "line": 3
568 },
569 "start": {
570 "character": 0,
571 "line": 2
572 }
573 }, 560 },
574 "targetSelectionRange": { 561 "targetSelectionRange": {
575 "end": { 562 "end": { "character": 8, "line": 7 },
576 "character": 8, 563 "start": { "character": 7, "line": 7 }
577 "line": 3
578 },
579 "start": {
580 "character": 7,
581 "line": 3
582 }
583 }, 564 },
584 "targetUri": "file:///[..]src/main.rs" 565 "targetUri": "file:///[..]src/main.rs"
585 }]), 566 }]),
@@ -588,41 +569,23 @@ fn main() { message(); }
588 GotoDefinitionParams { 569 GotoDefinitionParams {
589 text_document_position_params: TextDocumentPositionParams::new( 570 text_document_position_params: TextDocumentPositionParams::new(
590 server.doc_id("src/main.rs"), 571 server.doc_id("src/main.rs"),
591 Position::new(13, 9), 572 Position::new(17, 9),
592 ), 573 ),
593 work_done_progress_params: Default::default(), 574 work_done_progress_params: Default::default(),
594 partial_result_params: Default::default(), 575 partial_result_params: Default::default(),
595 }, 576 },
596 json!([{ 577 json!([{
597 "originSelectionRange": { 578 "originSelectionRange": {
598 "end": { 579 "end": { "character": 10, "line": 17 },
599 "character": 10, 580 "start": { "character": 8, "line": 17 }
600 "line": 13
601 },
602 "start": {
603 "character": 8,
604 "line":13
605 }
606 }, 581 },
607 "targetRange": { 582 "targetRange": {
608 "end": { 583 "end": { "character": 9, "line": 11 },
609 "character": 9, 584 "start": { "character": 0, "line":10 }
610 "line": 7
611 },
612 "start": {
613 "character": 0,
614 "line":6
615 }
616 }, 585 },
617 "targetSelectionRange": { 586 "targetSelectionRange": {
618 "end": { 587 "end": { "character": 8, "line": 11 },
619 "character": 8, 588 "start": { "character": 7, "line": 11 }
620 "line": 7
621 },
622 "start": {
623 "character": 7,
624 "line": 7
625 }
626 }, 589 },
627 "targetUri": "file:///[..]src/main.rs" 590 "targetUri": "file:///[..]src/main.rs"
628 }]), 591 }]),
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 7f7940d0b..a0847dad3 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -467,3 +467,41 @@ interface InlayHint {
467 label: string, 467 label: string,
468} 468}
469``` 469```
470
471## Hover Actions
472
473**Client Capability:** `{ "hoverActions": boolean }`
474
475If this capability is set, `Hover` request returned from the server might contain an additional field, `actions`:
476
477```typescript
478interface Hover {
479 ...
480 actions?: CommandLinkGroup[];
481}
482
483interface CommandLink extends Command {
484 /**
485 * A tooltip for the command, when represented in the UI.
486 */
487 tooltip?: string;
488}
489
490interface CommandLinkGroup {
491 title?: string;
492 commands: CommandLink[];
493}
494```
495
496Such actions on the client side are appended to a hover bottom as command links:
497```
498 +-----------------------------+
499 | Hover content |
500 | |
501 +-----------------------------+
502 | _Action1_ | _Action2_ | <- first group, no TITLE
503 +-----------------------------+
504 | TITLE _Action1_ | _Action2_ | <- second group
505 +-----------------------------+
506 ...
507``` \ No newline at end of file
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..65ad573d8 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -7,6 +7,20 @@ 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 renderCommand(cmd: ra.CommandLink) {
11 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`;
12}
13
14function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString {
15 const text = actions.map(group =>
16 (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ')
17 ).join('___');
18
19 const result = new vscode.MarkdownString(text);
20 result.isTrusted = true;
21 return result;
22}
23
10export function createClient(serverPath: string, cwd: string): lc.LanguageClient { 24export function createClient(serverPath: string, cwd: string): lc.LanguageClient {
11 // '.' Is the fallback if no folder is open 25 // '.' Is the fallback if no folder is open
12 // TODO?: Workspace folders support Uri's (eg: file://test.txt). 26 // TODO?: Workspace folders support Uri's (eg: file://test.txt).
@@ -35,6 +49,23 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient
35 if (res === undefined) throw new Error('busy'); 49 if (res === undefined) throw new Error('busy');
36 return res; 50 return res;
37 }, 51 },
52 async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
53 return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
54 (result) => {
55 const hover = client.protocol2CodeConverter.asHover(result);
56 if (hover) {
57 const actions = (<any>result).actions;
58 if (actions) {
59 hover.contents.push(renderHoverActions(actions));
60 }
61 }
62 return hover;
63 },
64 (error) => {
65 client.logFailedRequest(lc.HoverRequest.type, error);
66 return Promise.resolve(null);
67 });
68 },
38 // Using custom handling of CodeActions where each code action is resloved lazily 69 // 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 70 // 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) { 71 async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) {
@@ -129,6 +160,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
129 caps.snippetTextEdit = true; 160 caps.snippetTextEdit = true;
130 caps.codeActionGroup = true; 161 caps.codeActionGroup = true;
131 caps.resolveCodeAction = true; 162 caps.resolveCodeAction = true;
163 caps.hoverActions = true;
132 capabilities.experimental = caps; 164 capabilities.experimental = caps;
133 } 165 }
134 initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void { 166 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}