From eeb40dbece3421d685c75bf2860610e6fd3b7b73 Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 1 Sep 2020 16:33:02 +0300 Subject: Add method references CodeLens --- crates/rust-analyzer/src/config.rs | 21 ++++++++------ crates/rust-analyzer/src/handlers.rs | 55 ++++++++++++++++++++++++++++++++++++ editors/code/package.json | 5 ++++ editors/code/src/config.ts | 1 + 4 files changed, 74 insertions(+), 8 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index fab15f860..fa8472e62 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -74,19 +74,18 @@ pub struct LensConfig { pub run: bool, pub debug: bool, pub implementations: bool, + pub method_refs: bool, } impl Default for LensConfig { fn default() -> Self { - Self { run: true, debug: true, implementations: true } + Self { run: true, debug: true, implementations: true, method_refs: true } } } impl LensConfig { - pub const NO_LENS: LensConfig = Self { run: false, debug: false, implementations: false }; - pub fn any(&self) -> bool { - self.implementations || self.runnable() + self.implementations || self.runnable() || self.references() } pub fn none(&self) -> bool { @@ -96,6 +95,10 @@ impl LensConfig { pub fn runnable(&self) -> bool { self.run || self.debug } + + pub fn references(&self) -> bool { + self.method_refs + } } #[derive(Debug, Clone)] @@ -278,6 +281,7 @@ impl Config { run: data.lens_enable && data.lens_run, debug: data.lens_enable && data.lens_debug, implementations: data.lens_enable && data.lens_implementations, + method_refs: data.lens_enable && data.lens_methodReferences, }; if !data.linkedProjects.is_empty() { @@ -459,10 +463,11 @@ config_data! { inlayHints_parameterHints: bool = true, inlayHints_typeHints: bool = true, - lens_debug: bool = true, - lens_enable: bool = true, - lens_implementations: bool = true, - lens_run: bool = true, + lens_debug: bool = true, + lens_enable: bool = true, + lens_implementations: bool = true, + lens_run: bool = true, + lens_methodReferences: bool = true, linkedProjects: Vec = Vec::new(), lruCapacity: Option = None, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index f7c7a378a..06afb8148 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -11,6 +11,7 @@ use ide::{ FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, }; +use itertools::Itertools; use lsp_server::ErrorCode; use lsp_types::{ CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, @@ -952,6 +953,52 @@ pub(crate) fn handle_code_lens( }), ); } + + if snap.config.lens.references() { + let ref_lenses = snap + .analysis + .file_structure(file_id)? + .into_iter() + .filter(|it| match it.kind { + SyntaxKind::FN => true, + _ => false, + }) + .filter_map(|it| { + let position = FilePosition { file_id, offset: it.navigation_range.start() }; + let scope = None; // all references + + snap.analysis.find_all_refs(position, scope).unwrap_or(None).map(|r| { + let mut lenses = Vec::new(); + if r.len() == 1 { + // Only a declaration + return lenses; + } + + let uri = to_proto::url(&snap, file_id); + let range = to_proto::range(&line_index, it.node_range); + let position = to_proto::position(&line_index, position.offset); + + if snap.config.lens.method_refs { + let all_locations: Vec<_> = r + .references() + .iter() + .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) + .collect(); + let title = reference_title(all_locations.len()); + let all_refs = + show_references_command(title, &uri, position, all_locations); + lenses.push(CodeLens { range, command: Some(all_refs), data: None }); + } + + lenses + }) + }) + .flatten() + .collect_vec(); + + lenses.extend(ref_lenses); + } + Ok(Some(lenses)) } @@ -1248,6 +1295,14 @@ fn implementation_title(count: usize) -> String { } } +fn reference_title(count: usize) -> String { + if count == 1 { + "1 reference".into() + } else { + format!("{} references", count) + } +} + fn show_references_command( title: String, uri: &lsp_types::Url, diff --git a/editors/code/package.json b/editors/code/package.json index 132664926..4414b3e66 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -554,6 +554,11 @@ "type": "boolean", "default": true }, + "rust-analyzer.lens.methodReferences": { + "markdownDescription": "Whether to show `Method References` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, "rust-analyzer.hoverActions.enable": { "description": "Whether to show HoverActions in Rust files.", "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 { run: this.get("lens.run"), debug: this.get("lens.debug"), implementations: this.get("lens.implementations"), + methodReferences: this.get("lens.methodReferences"), }; } -- cgit v1.2.3 From b7fda5f936737aa1111599f93cb3133fa7f65ee4 Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 2 Sep 2020 13:25:33 +0300 Subject: Make method references CodeLens off by default. --- crates/rust-analyzer/src/config.rs | 4 ++-- editors/code/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index fa8472e62..42e1ad376 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -79,7 +79,7 @@ pub struct LensConfig { impl Default for LensConfig { fn default() -> Self { - Self { run: true, debug: true, implementations: true, method_refs: true } + Self { run: true, debug: true, implementations: true, method_refs: false } } } @@ -467,7 +467,7 @@ config_data! { lens_enable: bool = true, lens_implementations: bool = true, lens_run: bool = true, - lens_methodReferences: bool = true, + lens_methodReferences: bool = false, linkedProjects: Vec = Vec::new(), lruCapacity: Option = None, diff --git a/editors/code/package.json b/editors/code/package.json index 4414b3e66..bdd8a0c29 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -557,7 +557,7 @@ "rust-analyzer.lens.methodReferences": { "markdownDescription": "Whether to show `Method References` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", - "default": true + "default": false }, "rust-analyzer.hoverActions.enable": { "description": "Whether to show HoverActions in Rust files.", -- cgit v1.2.3 From 06fbd6905014b90aa2efc1f67b92f31845011d76 Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 2 Sep 2020 16:03:05 +0300 Subject: Make method references CodeLens lazy. --- crates/ide/src/lib.rs | 17 +++++++- crates/rust-analyzer/src/handlers.rs | 82 ++++++++++++++++++------------------ 2 files changed, 56 insertions(+), 43 deletions(-) diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 4763c0aac..286a6a110 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -56,7 +56,7 @@ use ide_db::{ symbol_index::{self, FileSymbol}, LineIndexDatabase, }; -use syntax::{SourceFile, TextRange, TextSize}; +use syntax::{SourceFile, SyntaxKind, TextRange, TextSize}; use crate::display::ToNav; @@ -369,6 +369,21 @@ impl Analysis { }) } + /// Finds all methods and free functions for the file. + pub fn find_all_methods(&self, file_id: FileId) -> Cancelable> { + let res = self + .file_structure(file_id)? + .into_iter() + .filter(|it| match it.kind { + SyntaxKind::FN => true, + _ => false, + }) + .filter_map(|it| Some(FileRange { file_id, range: it.navigation_range })) + .collect(); + + Ok(res) + } + /// Returns a short text describing element at position. pub fn hover( &self, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 06afb8148..1a0bee5e4 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -955,48 +955,18 @@ pub(crate) fn handle_code_lens( } if snap.config.lens.references() { - let ref_lenses = snap - .analysis - .file_structure(file_id)? - .into_iter() - .filter(|it| match it.kind { - SyntaxKind::FN => true, - _ => false, - }) - .filter_map(|it| { - let position = FilePosition { file_id, offset: it.navigation_range.start() }; - let scope = None; // all references - - snap.analysis.find_all_refs(position, scope).unwrap_or(None).map(|r| { - let mut lenses = Vec::new(); - if r.len() == 1 { - // Only a declaration - return lenses; - } - - let uri = to_proto::url(&snap, file_id); - let range = to_proto::range(&line_index, it.node_range); - let position = to_proto::position(&line_index, position.offset); - - if snap.config.lens.method_refs { - let all_locations: Vec<_> = r - .references() - .iter() - .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) - .collect(); - let title = reference_title(all_locations.len()); - let all_refs = - show_references_command(title, &uri, position, all_locations); - lenses.push(CodeLens { range, command: Some(all_refs), data: None }); - } - - lenses - }) - }) - .flatten() - .collect_vec(); - - lenses.extend(ref_lenses); + lenses.extend(snap.analysis.find_all_methods(file_id)?.into_iter().map(|it| { + let range = to_proto::range(&line_index, it.range); + let position = to_proto::position(&line_index, it.range.start()); + let lens_params = + lsp_types::TextDocumentPositionParams::new(params.text_document.clone(), position); + + CodeLens { + range, + command: None, + data: Some(to_value(CodeLensResolveData::References(lens_params)).unwrap()), + } + })); } Ok(Some(lenses)) @@ -1006,6 +976,7 @@ pub(crate) fn handle_code_lens( #[serde(rename_all = "camelCase")] enum CodeLensResolveData { Impls(lsp_types::request::GotoImplementationParams), + References(lsp_types::TextDocumentPositionParams), } pub(crate) fn handle_code_lens_resolve( @@ -1037,6 +1008,33 @@ pub(crate) fn handle_code_lens_resolve( ); Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) } + Some(CodeLensResolveData::References(doc_position)) => { + let position = from_proto::file_position(&snap, doc_position.clone())?; + let locations = snap + .analysis + .find_all_refs(position, None) + .unwrap_or(None) + .map(|r| { + r.references() + .iter() + .filter_map(|it| to_proto::location(&snap, it.file_range).ok()) + .collect_vec() + }) + .unwrap_or_default(); + + let cmd = if locations.is_empty() { + Command { title: "No references".into(), command: "".into(), arguments: None } + } else { + show_references_command( + reference_title(locations.len()), + &doc_position.text_document.uri, + code_lens.range.start, + locations, + ) + }; + + Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) + } None => Ok(CodeLens { range: code_lens.range, command: Some(Command { title: "Error".into(), ..Default::default() }), -- cgit v1.2.3 From 1895716c885eba9aae710f80f4c29eb2b424c6f0 Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 2 Sep 2020 17:27:57 +0300 Subject: Do not show references CodeLens for tests. --- crates/ide/src/fn_references.rs | 21 +++++++++++++++++++++ crates/ide/src/lib.rs | 17 ++++------------- crates/ide/src/runnables.rs | 2 +- 3 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 crates/ide/src/fn_references.rs diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs new file mode 100644 index 000000000..ca91b98cf --- /dev/null +++ b/crates/ide/src/fn_references.rs @@ -0,0 +1,21 @@ +use hir::Semantics; +use ide_db::RootDatabase; +use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; + +use crate::{runnables::has_test_related_attribute, FileId, FileRange}; + +pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec { + let sema = Semantics::new(db); + let source_file = sema.parse(file_id); + source_file.syntax().descendants().filter_map(|it| method_range(it, file_id)).collect() +} + +pub(crate) fn method_range(item: SyntaxNode, file_id: FileId) -> Option { + ast::Fn::cast(item).and_then(|fn_def|{ + if has_test_related_attribute(&fn_def) { + None + } else { + fn_def.name().map(|name| FileRange{ file_id, range: name.syntax().text_range() }) + } + }) +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 286a6a110..31f2bcba3 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -38,6 +38,7 @@ mod join_lines; mod matching_brace; mod parent_module; mod references; +mod fn_references; mod runnables; mod status; mod syntax_highlighting; @@ -56,7 +57,7 @@ use ide_db::{ symbol_index::{self, FileSymbol}, LineIndexDatabase, }; -use syntax::{SourceFile, SyntaxKind, TextRange, TextSize}; +use syntax::{SourceFile, TextRange, TextSize}; use crate::display::ToNav; @@ -369,19 +370,9 @@ impl Analysis { }) } - /// Finds all methods and free functions for the file. + /// Finds all methods and free functions for the file. Does not return tests! pub fn find_all_methods(&self, file_id: FileId) -> Cancelable> { - let res = self - .file_structure(file_id)? - .into_iter() - .filter(|it| match it.kind { - SyntaxKind::FN => true, - _ => false, - }) - .filter_map(|it| Some(FileRange { file_id, range: it.navigation_range })) - .collect(); - - Ok(res) + self.with_db(|db| fn_references::find_all_methods(db, file_id)) } /// Returns a short text describing element at position. 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 { /// /// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test, /// but it's better than not to have the runnables for the tests at all. -fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { +pub(crate) fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { fn_def .attrs() .filter_map(|attr| attr.path()) -- cgit v1.2.3 From cd5eeb904e6f5096bb1f8b76fba7e568cdbbdc8c Mon Sep 17 00:00:00 2001 From: vsrs Date: Wed, 2 Sep 2020 18:21:20 +0300 Subject: Add tests --- crates/ide/src/fn_references.rs | 80 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs index ca91b98cf..1989a562b 100644 --- a/crates/ide/src/fn_references.rs +++ b/crates/ide/src/fn_references.rs @@ -1,3 +1,6 @@ +//! This module implements a methods and free functions search in the specified file. +//! We have to skip tests, so cannot reuse file_structure module. + use hir::Semantics; use ide_db::RootDatabase; use syntax::{ast, ast::NameOwner, AstNode, SyntaxNode}; @@ -10,12 +13,83 @@ pub(crate) fn find_all_methods(db: &RootDatabase, file_id: FileId) -> Vec Option { - ast::Fn::cast(item).and_then(|fn_def|{ +fn method_range(item: SyntaxNode, file_id: FileId) -> Option { + ast::Fn::cast(item).and_then(|fn_def| { if has_test_related_attribute(&fn_def) { None } else { - fn_def.name().map(|name| FileRange{ file_id, range: name.syntax().text_range() }) + fn_def.name().map(|name| FileRange { file_id, range: name.syntax().text_range() }) } }) } + +#[cfg(test)] +mod tests { + use crate::mock_analysis::analysis_and_position; + use crate::{FileRange, TextSize}; + use std::ops::RangeInclusive; + + #[test] + fn test_find_all_methods() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + fn private_fn() {<|>} + + pub fn pub_fn() {} + + pub fn generic_fn(arg: T) {} + "#, + ); + + let refs = analysis.find_all_methods(pos.file_id).unwrap(); + check_result(&refs, &[3..=13, 27..=33, 47..=57]); + } + + #[test] + fn test_find_trait_methods() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + trait Foo { + fn bar() {<|>} + fn baz() {} + } + "#, + ); + + let refs = analysis.find_all_methods(pos.file_id).unwrap(); + check_result(&refs, &[19..=22, 35..=38]); + } + + #[test] + fn test_skip_tests() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + #[test] + fn foo() {<|>} + + pub fn pub_fn() {} + + mod tests { + #[test] + fn bar() {} + } + "#, + ); + + let refs = analysis.find_all_methods(pos.file_id).unwrap(); + check_result(&refs, &[28..=34]); + } + + fn check_result(refs: &[FileRange], expected: &[RangeInclusive]) { + assert_eq!(refs.len(), expected.len()); + + for (i, item) in refs.iter().enumerate() { + let range = &expected[i]; + assert_eq!(TextSize::from(*range.start()), item.range.start()); + assert_eq!(TextSize::from(*range.end()), item.range.end()); + } + } +} -- cgit v1.2.3 From 91da41b3b13129007920943d7300459921faf3c5 Mon Sep 17 00:00:00 2001 From: vsrs Date: Tue, 29 Sep 2020 15:20:07 +0300 Subject: Replace "No references" with "0 references". --- crates/rust-analyzer/src/handlers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 1a0bee5e4..7ac1a30f6 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -1022,11 +1022,12 @@ pub(crate) fn handle_code_lens_resolve( }) .unwrap_or_default(); + let title = reference_title(locations.len()); let cmd = if locations.is_empty() { - Command { title: "No references".into(), command: "".into(), arguments: None } + Command { title, command: "".into(), arguments: None } } else { show_references_command( - reference_title(locations.len()), + title, &doc_position.text_document.uri, code_lens.range.start, locations, -- cgit v1.2.3