diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-09-29 13:36:11 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-09-29 13:36:11 +0100 |
commit | bdc1f76cbda7478f39c190cc6ba296bc0030928f (patch) | |
tree | 91d3312e41626d40387d59e9243ae32b099eecb1 | |
parent | e813de6cdd53e542bce8d4a554288dc2f17bbf5e (diff) | |
parent | 91da41b3b13129007920943d7300459921faf3c5 (diff) |
Merge #5928
5928: Add method references CodeLens r=vsrs a=vsrs
The PR adds CodeLens for methods and free-standing functions:
![method_refs](https://user-images.githubusercontent.com/62505555/91858244-95fbfb00-ec71-11ea-90c7-5b3ee067e305.png)
Relates to #5836
Co-authored-by: vsrs <[email protected]>
-rw-r--r-- | crates/ide/src/fn_references.rs | 95 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 2 | ||||
-rw-r--r-- | crates/rust-analyzer/src/config.rs | 21 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 54 | ||||
-rw-r--r-- | editors/code/package.json | 5 | ||||
-rw-r--r-- | editors/code/src/config.ts | 1 |
7 files changed, 175 insertions, 9 deletions
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs new file mode 100644 index 000000000..1989a562b --- /dev/null +++ b/crates/ide/src/fn_references.rs | |||
@@ -0,0 +1,95 @@ | |||
1 | //! This module implements a methods and free functions search in the specified file. | ||
2 | //! We have to skip tests, so cannot reuse file_structure module. | ||
3 | |||
4 | use hir::Semantics; | ||
5 | use ide_db::RootDatabase; | ||
6 | use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; | ||
7 | |||
8 | use crate::{runnables::has_test_related_attribute, FileId, FileRange}; | ||
9 | |||
10 | pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec<FileRange> { | ||
11 | let sema = Semantics::new(db); | ||
12 | let source_file = sema.parse(file_id); | ||
13 | source_file.syntax().descendants().filter_map(|it| method_range(it, file_id)).collect() | ||
14 | } | ||
15 | |||
16 | fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> { | ||
17 | ast::Fn::cast(item).and_then(|fn_def| { | ||
18 | if has_test_related_attribute(&fn_def) { | ||
19 | None | ||
20 | } else { | ||
21 | fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() }) | ||
22 | } | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | #[cfg(test)] | ||
27 | mod tests { | ||
28 | use crate::mock_analysis::analysis_and_position; | ||
29 | use crate::{FileRange, TextSize}; | ||
30 | use std::ops::RangeInclusive; | ||
31 | |||
32 | #[test] | ||
33 | fn test_find_all_methods() { | ||
34 | let (analysis, pos) = analysis_and_position( | ||
35 | r#" | ||
36 | //- /lib.rs | ||
37 | fn private_fn() {<|>} | ||
38 | |||
39 | pub fn pub_fn() {} | ||
40 | |||
41 | pub fn generic_fn<T>(arg: T) {} | ||
42 | "#, | ||
43 | ); | ||
44 | |||
45 | let refs = analysis.find_all_methods(pos.file_id).unwrap(); | ||
46 | check_result(&refs, &[3..=13, 27..=33, 47..=57]); | ||
47 | } | ||
48 | |||
49 | #[test] | ||
50 | fn test_find_trait_methods() { | ||
51 | let (analysis, pos) = analysis_and_position( | ||
52 | r#" | ||
53 | //- /lib.rs | ||
54 | trait Foo { | ||
55 | fn bar() {<|>} | ||
56 | fn baz() {} | ||
57 | } | ||
58 | "#, | ||
59 | ); | ||
60 | |||
61 | let refs = analysis.find_all_methods(pos.file_id).unwrap(); | ||
62 | check_result(&refs, &[19..=22, 35..=38]); | ||
63 | } | ||
64 | |||
65 | #[test] | ||
66 | fn test_skip_tests() { | ||
67 | let (analysis, pos) = analysis_and_position( | ||
68 | r#" | ||
69 | //- /lib.rs | ||
70 | #[test] | ||
71 | fn foo() {<|>} | ||
72 | |||
73 | pub fn pub_fn() {} | ||
74 | |||
75 | mod tests { | ||
76 | #[test] | ||
77 | fn bar() {} | ||
78 | } | ||
79 | "#, | ||
80 | ); | ||
81 | |||
82 | let refs = analysis.find_all_methods(pos.file_id).unwrap(); | ||
83 | check_result(&refs, &[28..=34]); | ||
84 | } | ||
85 | |||
86 | fn check_result(refs: &[FileRange], expected: &[RangeInclusive<u32>]) { | ||
87 | assert_eq!(refs.len(), expected.len()); | ||
88 | |||
89 | for (i, item) in refs.iter().enumerate() { | ||
90 | let range = &expected[i]; | ||
91 | assert_eq!(TextSize::from(*range.start()), item.range.start()); | ||
92 | assert_eq!(TextSize::from(*range.end()), item.range.end()); | ||
93 | } | ||
94 | } | ||
95 | } | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 4763c0aac..31f2bcba3 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -38,6 +38,7 @@ mod join_lines; | |||
38 | mod matching_brace; | 38 | mod matching_brace; |
39 | mod parent_module; | 39 | mod parent_module; |
40 | mod references; | 40 | mod references; |
41 | mod fn_references; | ||
41 | mod runnables; | 42 | mod runnables; |
42 | mod status; | 43 | mod status; |
43 | mod syntax_highlighting; | 44 | mod syntax_highlighting; |
@@ -369,6 +370,11 @@ impl Analysis { | |||
369 | }) | 370 | }) |
370 | } | 371 | } |
371 | 372 | ||
373 | /// Finds all methods and free functions for the file. Does not return tests! | ||
374 | pub fn find_all_methods(&self, file_id: FileId) -> Cancelable<Vec<FileRange>> { | ||
375 | self.with_db(|db| fn_references::find_all_methods(db, file_id)) | ||
376 | } | ||
377 | |||
372 | /// Returns a short text describing element at position. | 378 | /// Returns a short text describing element at position. |
373 | pub fn hover( | 379 | pub fn hover( |
374 | &self, | 380 | &self, |
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 989a63c09..cfeff40c1 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs | |||
@@ -203,7 +203,7 @@ impl TestAttr { | |||
203 | /// | 203 | /// |
204 | /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, | 204 | /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, |
205 | /// but it's better than not to have the runnables for the tests at all. | 205 | /// but it's better than not to have the runnables for the tests at all. |
206 | fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { | 206 | pub(crate) fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { |
207 | fn_def | 207 | fn_def |
208 | .attrs() | 208 | .attrs() |
209 | .filter_map(|attr| attr.path()) | 209 | .filter_map(|attr| attr.path()) |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index fab15f860..42e1ad376 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -74,19 +74,18 @@ pub struct LensConfig { | |||
74 | pub run: bool, | 74 | pub run: bool, |
75 | pub debug: bool, | 75 | pub debug: bool, |
76 | pub implementations: bool, | 76 | pub implementations: bool, |
77 | pub method_refs: bool, | ||
77 | } | 78 | } |
78 | 79 | ||
79 | impl Default for LensConfig { | 80 | impl Default for LensConfig { |
80 | fn default() -> Self { | 81 | fn default() -> Self { |
81 | Self { run: true, debug: true, implementations: true } | 82 | Self { run: true, debug: true, implementations: true, method_refs: false } |
82 | } | 83 | } |
83 | } | 84 | } |
84 | 85 | ||
85 | impl LensConfig { | 86 | impl LensConfig { |
86 | pub const NO_LENS: LensConfig = Self { run: false, debug: false, implementations: false }; | ||
87 | |||
88 | pub fn any(&self) -> bool { | 87 | pub fn any(&self) -> bool { |
89 | self.implementations || self.runnable() | 88 | self.implementations || self.runnable() || self.references() |
90 | } | 89 | } |
91 | 90 | ||
92 | pub fn none(&self) -> bool { | 91 | pub fn none(&self) -> bool { |
@@ -96,6 +95,10 @@ impl LensConfig { | |||
96 | pub fn runnable(&self) -> bool { | 95 | pub fn runnable(&self) -> bool { |
97 | self.run || self.debug | 96 | self.run || self.debug |
98 | } | 97 | } |
98 | |||
99 | pub fn references(&self) -> bool { | ||
100 | self.method_refs | ||
101 | } | ||
99 | } | 102 | } |
100 | 103 | ||
101 | #[derive(Debug, Clone)] | 104 | #[derive(Debug, Clone)] |
@@ -278,6 +281,7 @@ impl Config { | |||
278 | run: data.lens_enable && data.lens_run, | 281 | run: data.lens_enable && data.lens_run, |
279 | debug: data.lens_enable && data.lens_debug, | 282 | debug: data.lens_enable && data.lens_debug, |
280 | implementations: data.lens_enable && data.lens_implementations, | 283 | implementations: data.lens_enable && data.lens_implementations, |
284 | method_refs: data.lens_enable && data.lens_methodReferences, | ||
281 | }; | 285 | }; |
282 | 286 | ||
283 | if !data.linkedProjects.is_empty() { | 287 | if !data.linkedProjects.is_empty() { |
@@ -459,10 +463,11 @@ config_data! { | |||
459 | inlayHints_parameterHints: bool = true, | 463 | inlayHints_parameterHints: bool = true, |
460 | inlayHints_typeHints: bool = true, | 464 | inlayHints_typeHints: bool = true, |
461 | 465 | ||
462 | lens_debug: bool = true, | 466 | lens_debug: bool = true, |
463 | lens_enable: bool = true, | 467 | lens_enable: bool = true, |
464 | lens_implementations: bool = true, | 468 | lens_implementations: bool = true, |
465 | lens_run: bool = true, | 469 | lens_run: bool = true, |
470 | lens_methodReferences: bool = false, | ||
466 | 471 | ||
467 | linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(), | 472 | linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(), |
468 | lruCapacity: Option<usize> = None, | 473 | lruCapacity: Option<usize> = None, |
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f7c7a378a..7ac1a30f6 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -11,6 +11,7 @@ use ide::{ | |||
11 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, | 11 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, |
12 | RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, | 12 | RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, |
13 | }; | 13 | }; |
14 | use itertools::Itertools; | ||
14 | use lsp_server::ErrorCode; | 15 | use lsp_server::ErrorCode; |
15 | use lsp_types::{ | 16 | use lsp_types::{ |
16 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | 17 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, |
@@ -952,6 +953,22 @@ pub(crate) fn handle_code_lens( | |||
952 | }), | 953 | }), |
953 | ); | 954 | ); |
954 | } | 955 | } |
956 | |||
957 | if snap.config.lens.references() { | ||
958 | lenses.extend(snap.analysis.find_all_methods(file_id)?.into_iter().map(|it| { | ||
959 | let range = to_proto::range(&line_index, it.range); | ||
960 | let position = to_proto::position(&line_index, it.range.start()); | ||
961 | let lens_params = | ||
962 | lsp_types::TextDocumentPositionParams::new(params.text_document.clone(), position); | ||
963 | |||
964 | CodeLens { | ||
965 | range, | ||
966 | command: None, | ||
967 | data: Some(to_value(CodeLensResolveData::References(lens_params)).unwrap()), | ||
968 | } | ||
969 | })); | ||
970 | } | ||
971 | |||
955 | Ok(Some(lenses)) | 972 | Ok(Some(lenses)) |
956 | } | 973 | } |
957 | 974 | ||
@@ -959,6 +976,7 @@ pub(crate) fn handle_code_lens( | |||
959 | #[serde(rename_all = "camelCase")] | 976 | #[serde(rename_all = "camelCase")] |
960 | enum CodeLensResolveData { | 977 | enum CodeLensResolveData { |
961 | Impls(lsp_types::request::GotoImplementationParams), | 978 | Impls(lsp_types::request::GotoImplementationParams), |
979 | References(lsp_types::TextDocumentPositionParams), | ||
962 | } | 980 | } |
963 | 981 | ||
964 | pub(crate) fn handle_code_lens_resolve( | 982 | pub(crate) fn handle_code_lens_resolve( |
@@ -990,6 +1008,34 @@ pub(crate) fn handle_code_lens_resolve( | |||
990 | ); | 1008 | ); |
991 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) | 1009 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) |
992 | } | 1010 | } |
1011 | Some(CodeLensResolveData::References(doc_position)) => { | ||
1012 | let position = from_proto::file_position(&snap, doc_position.clone())?; | ||
1013 | let locations = snap | ||
1014 | .analysis | ||
1015 | .find_all_refs(position, None) | ||
1016 | .unwrap_or(None) | ||
1017 | .map(|r| { | ||
1018 | r.references() | ||
1019 | .iter() | ||
1020 | .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) | ||
1021 | .collect_vec() | ||
1022 | }) | ||
1023 | .unwrap_or_default(); | ||
1024 | |||
1025 | let title = reference_title(locations.len()); | ||
1026 | let cmd = if locations.is_empty() { | ||
1027 | Command { title, command: "".into(), arguments: None } | ||
1028 | } else { | ||
1029 | show_references_command( | ||
1030 | title, | ||
1031 | &doc_position.text_document.uri, | ||
1032 | code_lens.range.start, | ||
1033 | locations, | ||
1034 | ) | ||
1035 | }; | ||
1036 | |||
1037 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) | ||
1038 | } | ||
993 | None => Ok(CodeLens { | 1039 | None => Ok(CodeLens { |
994 | range: code_lens.range, | 1040 | range: code_lens.range, |
995 | command: Some(Command { title: "Error".into(), ..Default::default() }), | 1041 | command: Some(Command { title: "Error".into(), ..Default::default() }), |
@@ -1248,6 +1294,14 @@ fn implementation_title(count: usize) -> String { | |||
1248 | } | 1294 | } |
1249 | } | 1295 | } |
1250 | 1296 | ||
1297 | fn reference_title(count: usize) -> String { | ||
1298 | if count == 1 { | ||
1299 | "1 reference".into() | ||
1300 | } else { | ||
1301 | format!("{} references", count) | ||
1302 | } | ||
1303 | } | ||
1304 | |||
1251 | fn show_references_command( | 1305 | fn show_references_command( |
1252 | title: String, | 1306 | title: String, |
1253 | uri: &lsp_types::Url, | 1307 | uri: &lsp_types::Url, |
diff --git a/editors/code/package.json b/editors/code/package.json index 132664926..bdd8a0c29 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -554,6 +554,11 @@ | |||
554 | "type": "boolean", | 554 | "type": "boolean", |
555 | "default": true | 555 | "default": true |
556 | }, | 556 | }, |
557 | "rust-analyzer.lens.methodReferences": { | ||
558 | "markdownDescription": "Whether to show `Method References` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", | ||
559 | "type": "boolean", | ||
560 | "default": false | ||
561 | }, | ||
557 | "rust-analyzer.hoverActions.enable": { | 562 | "rust-analyzer.hoverActions.enable": { |
558 | "description": "Whether to show HoverActions in Rust files.", | 563 | "description": "Whether to show HoverActions in Rust files.", |
559 | "type": "boolean", | 564 | "type": "boolean", |
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 033b04b60..848e92af9 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts | |||
@@ -138,6 +138,7 @@ export class Config { | |||
138 | run: this.get<boolean>("lens.run"), | 138 | run: this.get<boolean>("lens.run"), |
139 | debug: this.get<boolean>("lens.debug"), | 139 | debug: this.get<boolean>("lens.debug"), |
140 | implementations: this.get<boolean>("lens.implementations"), | 140 | implementations: this.get<boolean>("lens.implementations"), |
141 | methodReferences: this.get<boolean>("lens.methodReferences"), | ||
141 | }; | 142 | }; |
142 | } | 143 | } |
143 | 144 | ||