aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/fn_references.rs95
-rw-r--r--crates/ide/src/lib.rs6
-rw-r--r--crates/ide/src/runnables.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs21
-rw-r--r--crates/rust-analyzer/src/handlers.rs54
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/config.ts1
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
4use hir::Semantics;
5use ide_db::RootDatabase;
6use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode};
7
8use crate::{runnables::has_test_related_attribute, FileId, FileRange};
9
10pub(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
16fn 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)]
27mod 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;
38mod matching_brace; 38mod matching_brace;
39mod parent_module; 39mod parent_module;
40mod references; 40mod references;
41mod fn_references;
41mod runnables; 42mod runnables;
42mod status; 43mod status;
43mod syntax_highlighting; 44mod 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.
206fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { 206pub(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
79impl Default for LensConfig { 80impl 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
85impl LensConfig { 86impl 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};
14use itertools::Itertools;
14use lsp_server::ErrorCode; 15use lsp_server::ErrorCode;
15use lsp_types::{ 16use 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")]
960enum CodeLensResolveData { 977enum CodeLensResolveData {
961 Impls(lsp_types::request::GotoImplementationParams), 978 Impls(lsp_types::request::GotoImplementationParams),
979 References(lsp_types::TextDocumentPositionParams),
962} 980}
963 981
964pub(crate) fn handle_code_lens_resolve( 982pub(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
1297fn reference_title(count: usize) -> String {
1298 if count == 1 {
1299 "1 reference".into()
1300 } else {
1301 format!("{} references", count)
1302 }
1303}
1304
1251fn show_references_command( 1305fn 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