Skip to content

Commit bdc1f76

Browse files
bors[bot]vsrs
andauthored
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]>
2 parents e813de6 + 91da41b commit bdc1f76

File tree

7 files changed

+175
-9
lines changed

7 files changed

+175
-9
lines changed

crates/ide/src/fn_references.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ide/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mod join_lines;
3838
mod matching_brace;
3939
mod parent_module;
4040
mod references;
41+
mod fn_references;
4142
mod runnables;
4243
mod status;
4344
mod syntax_highlighting;
@@ -369,6 +370,11 @@ impl Analysis {
369370
})
370371
}
371372

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+
372378
/// Returns a short text describing element at position.
373379
pub fn hover(
374380
&self,

crates/ide/src/runnables.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ impl TestAttr {
203203
///
204204
/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
205205
/// 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 {
207207
fn_def
208208
.attrs()
209209
.filter_map(|attr| attr.path())

crates/rust-analyzer/src/config.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,18 @@ pub struct LensConfig {
7474
pub run: bool,
7575
pub debug: bool,
7676
pub implementations: bool,
77+
pub method_refs: bool,
7778
}
7879

7980
impl Default for LensConfig {
8081
fn default() -> Self {
81-
Self { run: true, debug: true, implementations: true }
82+
Self { run: true, debug: true, implementations: true, method_refs: false }
8283
}
8384
}
8485

8586
impl LensConfig {
86-
pub const NO_LENS: LensConfig = Self { run: false, debug: false, implementations: false };
87-
8887
pub fn any(&self) -> bool {
89-
self.implementations || self.runnable()
88+
self.implementations || self.runnable() || self.references()
9089
}
9190

9291
pub fn none(&self) -> bool {
@@ -96,6 +95,10 @@ impl LensConfig {
9695
pub fn runnable(&self) -> bool {
9796
self.run || self.debug
9897
}
98+
99+
pub fn references(&self) -> bool {
100+
self.method_refs
101+
}
99102
}
100103

101104
#[derive(Debug, Clone)]
@@ -278,6 +281,7 @@ impl Config {
278281
run: data.lens_enable && data.lens_run,
279282
debug: data.lens_enable && data.lens_debug,
280283
implementations: data.lens_enable && data.lens_implementations,
284+
method_refs: data.lens_enable && data.lens_methodReferences,
281285
};
282286

283287
if !data.linkedProjects.is_empty() {
@@ -459,10 +463,11 @@ config_data! {
459463
inlayHints_parameterHints: bool = true,
460464
inlayHints_typeHints: bool = true,
461465

462-
lens_debug: bool = true,
463-
lens_enable: bool = true,
464-
lens_implementations: bool = true,
465-
lens_run: bool = true,
466+
lens_debug: bool = true,
467+
lens_enable: bool = true,
468+
lens_implementations: bool = true,
469+
lens_run: bool = true,
470+
lens_methodReferences: bool = false,
466471

467472
linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(),
468473
lruCapacity: Option<usize> = None,

crates/rust-analyzer/src/handlers.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ide::{
1111
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
1212
RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
1313
};
14+
use itertools::Itertools;
1415
use lsp_server::ErrorCode;
1516
use lsp_types::{
1617
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
@@ -952,13 +953,30 @@ pub(crate) fn handle_code_lens(
952953
}),
953954
);
954955
}
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+
955972
Ok(Some(lenses))
956973
}
957974

958975
#[derive(Debug, Serialize, Deserialize)]
959976
#[serde(rename_all = "camelCase")]
960977
enum CodeLensResolveData {
961978
Impls(lsp_types::request::GotoImplementationParams),
979+
References(lsp_types::TextDocumentPositionParams),
962980
}
963981

964982
pub(crate) fn handle_code_lens_resolve(
@@ -990,6 +1008,34 @@ pub(crate) fn handle_code_lens_resolve(
9901008
);
9911009
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
9921010
}
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+
}
9931039
None => Ok(CodeLens {
9941040
range: code_lens.range,
9951041
command: Some(Command { title: "Error".into(), ..Default::default() }),
@@ -1248,6 +1294,14 @@ fn implementation_title(count: usize) -> String {
12481294
}
12491295
}
12501296

1297+
fn reference_title(count: usize) -> String {
1298+
if count == 1 {
1299+
"1 reference".into()
1300+
} else {
1301+
format!("{} references", count)
1302+
}
1303+
}
1304+
12511305
fn show_references_command(
12521306
title: String,
12531307
uri: &lsp_types::Url,

editors/code/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,11 @@
554554
"type": "boolean",
555555
"default": true
556556
},
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+
},
557562
"rust-analyzer.hoverActions.enable": {
558563
"description": "Whether to show HoverActions in Rust files.",
559564
"type": "boolean",

editors/code/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class Config {
138138
run: this.get<boolean>("lens.run"),
139139
debug: this.get<boolean>("lens.debug"),
140140
implementations: this.get<boolean>("lens.implementations"),
141+
methodReferences: this.get<boolean>("lens.methodReferences"),
141142
};
142143
}
143144

0 commit comments

Comments
 (0)