aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/auto_import.rs8
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs8
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs7
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs26
-rw-r--r--crates/hir_def/src/path.rs26
-rw-r--r--crates/ide/src/fn_references.rs95
-rw-r--r--crates/ide/src/hover.rs113
-rw-r--r--crates/ide/src/lib.rs14
-rw-r--r--crates/ide/src/link_rewrite.rs37
-rw-r--r--crates/ide/src/runnables.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs23
-rw-r--r--crates/rust-analyzer/src/handlers.rs56
-rw-r--r--crates/syntax/src/ast/make.rs33
-rw-r--r--editors/code/package.json5
-rw-r--r--editors/code/src/config.ts1
15 files changed, 385 insertions, 69 deletions
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index ee7277c04..fa524ffd9 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -1,6 +1,5 @@
1use std::collections::BTreeSet; 1use std::collections::BTreeSet;
2 2
3use ast::make;
4use either::Either; 3use either::Either;
5use hir::{ 4use hir::{
6 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, 5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
@@ -54,11 +53,8 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
54 format!("Import `{}`", &import), 53 format!("Import `{}`", &import),
55 range, 54 range,
56 |builder| { 55 |builder| {
57 let new_syntax = insert_use( 56 let new_syntax =
58 &scope, 57 insert_use(&scope, import.to_ast_path(), ctx.config.insert_use.merge);
59 make::path_from_text(&import.to_string()),
60 ctx.config.insert_use.merge,
61 );
62 builder.replace(syntax.text_range(), new_syntax.to_string()) 58 builder.replace(syntax.text_range(), new_syntax.to_string())
63 }, 59 },
64 ); 60 );
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index e14ac7f65..d1adff972 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -264,12 +264,8 @@ fn replace_ast(
264 match use_trees.as_slice() { 264 match use_trees.as_slice() {
265 [name] => { 265 [name] => {
266 if let Some(end_path) = name.path() { 266 if let Some(end_path) = name.path() {
267 let replacement = make::use_tree( 267 let replacement =
268 make::path_from_text(&format!("{}::{}", path, end_path)), 268 make::use_tree(make::path_concat(path, end_path), None, None, false);
269 None,
270 None,
271 false,
272 );
273 269
274 algo::diff( 270 algo::diff(
275 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()), 271 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index 3ea50f375..d1eadaa99 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -12,7 +12,6 @@ use syntax::{
12use crate::{ 12use crate::{
13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists, 13 assist_context::AssistBuilder, utils::insert_use, AssistContext, AssistId, AssistKind, Assists,
14}; 14};
15use ast::make;
16use insert_use::ImportScope; 15use insert_use::ImportScope;
17 16
18// Assist: extract_struct_from_enum_variant 17// Assist: extract_struct_from_enum_variant
@@ -112,11 +111,7 @@ fn insert_import(
112 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 111 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
113 let syntax = scope.as_syntax_node(); 112 let syntax = scope.as_syntax_node();
114 113
115 let new_syntax = insert_use( 114 let new_syntax = insert_use(&scope, mod_path.to_ast_path(), ctx.config.insert_use.merge);
116 &scope,
117 make::path_from_text(&mod_path.to_string()),
118 ctx.config.insert_use.merge,
119 );
120 // FIXME: this will currently panic as multiple imports will have overlapping text ranges 115 // FIXME: this will currently panic as multiple imports will have overlapping text ranges
121 builder.replace(syntax.text_range(), new_syntax.to_string()) 116 builder.replace(syntax.text_range(), new_syntax.to_string())
122 } 117 }
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index 8ac907707..74afc123b 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,11 +1,10 @@
1use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRange}; 1use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
2use test_utils::mark; 2use test_utils::mark;
3 3
4use crate::{ 4use crate::{
5 utils::{insert_use, ImportScope}, 5 utils::{insert_use, ImportScope},
6 AssistContext, AssistId, AssistKind, Assists, 6 AssistContext, AssistId, AssistKind, Assists,
7}; 7};
8use ast::make;
9 8
10// Assist: replace_qualified_name_with_use 9// Assist: replace_qualified_name_with_use
11// 10//
@@ -33,15 +32,6 @@ pub(crate) fn replace_qualified_name_with_use(
33 mark::hit!(dont_import_trivial_paths); 32 mark::hit!(dont_import_trivial_paths);
34 return None; 33 return None;
35 } 34 }
36 let path_to_import = path.to_string();
37 let path_to_import = match path.segment()?.generic_arg_list() {
38 Some(generic_args) => {
39 let generic_args_start =
40 generic_args.syntax().text_range().start() - path.syntax().text_range().start();
41 &path_to_import[TextRange::up_to(generic_args_start)]
42 }
43 None => path_to_import.as_str(),
44 };
45 35
46 let target = path.syntax().text_range(); 36 let target = path.syntax().text_range();
47 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?; 37 let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
@@ -54,14 +44,10 @@ pub(crate) fn replace_qualified_name_with_use(
54 // Now that we've brought the name into scope, re-qualify all paths that could be 44 // Now that we've brought the name into scope, re-qualify all paths that could be
55 // affected (that is, all paths inside the node we added the `use` to). 45 // affected (that is, all paths inside the node we added the `use` to).
56 let mut rewriter = SyntaxRewriter::default(); 46 let mut rewriter = SyntaxRewriter::default();
57 shorten_paths(&mut rewriter, syntax.clone(), path); 47 shorten_paths(&mut rewriter, syntax.clone(), &path);
58 let rewritten_syntax = rewriter.rewrite(&syntax); 48 let rewritten_syntax = rewriter.rewrite(&syntax);
59 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) { 49 if let Some(ref import_scope) = ImportScope::from(rewritten_syntax) {
60 let new_syntax = insert_use( 50 let new_syntax = insert_use(import_scope, path, ctx.config.insert_use.merge);
61 import_scope,
62 make::path_from_text(path_to_import),
63 ctx.config.insert_use.merge,
64 );
65 builder.replace(syntax.text_range(), new_syntax.to_string()) 51 builder.replace(syntax.text_range(), new_syntax.to_string())
66 } 52 }
67 }, 53 },
@@ -69,7 +55,7 @@ pub(crate) fn replace_qualified_name_with_use(
69} 55}
70 56
71/// Adds replacements to `re` that shorten `path` in all descendants of `node`. 57/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
72fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) { 58fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
73 for child in node.children() { 59 for child in node.children() {
74 match_ast! { 60 match_ast! {
75 match child { 61 match child {
@@ -82,10 +68,10 @@ fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path:
82 ast::Path(p) => { 68 ast::Path(p) => {
83 match maybe_replace_path(rewriter, p.clone(), path.clone()) { 69 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
84 Some(()) => {}, 70 Some(()) => {},
85 None => shorten_paths(rewriter, p.syntax().clone(), path.clone()), 71 None => shorten_paths(rewriter, p.syntax().clone(), path),
86 } 72 }
87 }, 73 },
88 _ => shorten_paths(rewriter, child, path.clone()), 74 _ => shorten_paths(rewriter, child, path),
89 } 75 }
90 } 76 }
91 } 77 }
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 734310458..209b18e78 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -13,7 +13,7 @@ use hir_expand::{
13 hygiene::Hygiene, 13 hygiene::Hygiene,
14 name::{AsName, Name}, 14 name::{AsName, Name},
15}; 15};
16use syntax::ast; 16use syntax::ast::{self, make};
17 17
18use crate::{ 18use crate::{
19 type_ref::{TypeBound, TypeRef}, 19 type_ref::{TypeBound, TypeRef},
@@ -100,6 +100,26 @@ impl ModPath {
100 } 100 }
101 self.segments.first() 101 self.segments.first()
102 } 102 }
103
104 pub fn to_ast_path(&self) -> ast::Path {
105 let mut segments = Vec::new();
106 let mut is_abs = false;
107 match self.kind {
108 PathKind::Plain => {}
109 PathKind::Super(0) => segments.push(make::path_segment_self()),
110 PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
111 PathKind::Crate => segments.push(make::path_segment_crate()),
112 PathKind::Abs => is_abs = true,
113 PathKind::DollarCrate(_) => (),
114 }
115
116 segments.extend(
117 self.segments
118 .iter()
119 .map(|segment| make::path_segment(make::name_ref(&segment.to_string()))),
120 );
121 make::path_from_segments(segments, is_abs)
122 }
103} 123}
104 124
105#[derive(Debug, Clone, PartialEq, Eq, Hash)] 125#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -286,10 +306,8 @@ impl Display for ModPath {
286 }; 306 };
287 match self.kind { 307 match self.kind {
288 PathKind::Plain => {} 308 PathKind::Plain => {}
309 PathKind::Super(0) => add_segment("self")?,
289 PathKind::Super(n) => { 310 PathKind::Super(n) => {
290 if n == 0 {
291 add_segment("self")?;
292 }
293 for _ in 0..n { 311 for _ in 0..n {
294 add_segment("super")?; 312 add_segment("super")?;
295 } 313 }
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/hover.rs b/crates/ide/src/hover.rs
index 37171cbef..bb9f12cd3 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::rewrite_links, 17 link_rewrite::{remove_links, rewrite_links},
18 markup::Markup, 18 markup::Markup,
19 runnables::runnable, 19 runnables::runnable,
20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, 20 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
@@ -26,17 +26,29 @@ pub struct HoverConfig {
26 pub run: bool, 26 pub run: bool,
27 pub debug: bool, 27 pub debug: bool,
28 pub goto_type_def: bool, 28 pub goto_type_def: bool,
29 pub links_in_hover: bool,
29} 30}
30 31
31impl Default for HoverConfig { 32impl Default for HoverConfig {
32 fn default() -> Self { 33 fn default() -> Self {
33 Self { implementations: true, run: true, debug: true, goto_type_def: true } 34 Self {
35 implementations: true,
36 run: true,
37 debug: true,
38 goto_type_def: true,
39 links_in_hover: true,
40 }
34 } 41 }
35} 42}
36 43
37impl HoverConfig { 44impl HoverConfig {
38 pub const NO_ACTIONS: Self = 45 pub const NO_ACTIONS: Self = Self {
39 Self { implementations: false, run: false, debug: false, goto_type_def: false }; 46 implementations: false,
47 run: false,
48 debug: false,
49 goto_type_def: false,
50 links_in_hover: true,
51 };
40 52
41 pub fn any(&self) -> bool { 53 pub fn any(&self) -> bool {
42 self.implementations || self.runnable() || self.goto_type_def 54 self.implementations || self.runnable() || self.goto_type_def
@@ -75,7 +87,11 @@ pub struct HoverResult {
75// 87//
76// Shows additional information, like type of an expression or documentation for definition when "focusing" code. 88// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
77// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. 89// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
78pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { 90pub(crate) fn hover(
91 db: &RootDatabase,
92 position: FilePosition,
93 links_in_hover: bool,
94) -> Option<RangeInfo<HoverResult>> {
79 let sema = Semantics::new(db); 95 let sema = Semantics::new(db);
80 let file = sema.parse(position.file_id).syntax().clone(); 96 let file = sema.parse(position.file_id).syntax().clone();
81 let token = pick_best(file.token_at_offset(position.offset))?; 97 let token = pick_best(file.token_at_offset(position.offset))?;
@@ -93,7 +109,11 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
93 }; 109 };
94 if let Some(definition) = definition { 110 if let Some(definition) = definition {
95 if let Some(markup) = hover_for_definition(db, definition) { 111 if let Some(markup) = hover_for_definition(db, definition) {
96 let markup = rewrite_links(db, &markup.as_str(), &definition); 112 let markup = if links_in_hover {
113 rewrite_links(db, &markup.as_str(), &definition)
114 } else {
115 remove_links(&markup.as_str())
116 };
97 res.markup = Markup::from(markup); 117 res.markup = Markup::from(markup);
98 if let Some(action) = show_implementations_action(db, definition) { 118 if let Some(action) = show_implementations_action(db, definition) {
99 res.actions.push(action); 119 res.actions.push(action);
@@ -363,12 +383,23 @@ mod tests {
363 383
364 fn check_hover_no_result(ra_fixture: &str) { 384 fn check_hover_no_result(ra_fixture: &str) {
365 let (analysis, position) = analysis_and_position(ra_fixture); 385 let (analysis, position) = analysis_and_position(ra_fixture);
366 assert!(analysis.hover(position).unwrap().is_none()); 386 assert!(analysis.hover(position, true).unwrap().is_none());
367 } 387 }
368 388
369 fn check(ra_fixture: &str, expect: Expect) { 389 fn check(ra_fixture: &str, expect: Expect) {
370 let (analysis, position) = analysis_and_position(ra_fixture); 390 let (analysis, position) = analysis_and_position(ra_fixture);
371 let hover = analysis.hover(position).unwrap().unwrap(); 391 let hover = analysis.hover(position, true).unwrap().unwrap();
392
393 let content = analysis.db.file_text(position.file_id);
394 let hovered_element = &content[hover.range];
395
396 let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
397 expect.assert_eq(&actual)
398 }
399
400 fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
401 let (analysis, position) = analysis_and_position(ra_fixture);
402 let hover = analysis.hover(position, false).unwrap().unwrap();
372 403
373 let content = analysis.db.file_text(position.file_id); 404 let content = analysis.db.file_text(position.file_id);
374 let hovered_element = &content[hover.range]; 405 let hovered_element = &content[hover.range];
@@ -379,7 +410,7 @@ mod tests {
379 410
380 fn check_actions(ra_fixture: &str, expect: Expect) { 411 fn check_actions(ra_fixture: &str, expect: Expect) {
381 let (analysis, position) = analysis_and_position(ra_fixture); 412 let (analysis, position) = analysis_and_position(ra_fixture);
382 let hover = analysis.hover(position).unwrap().unwrap(); 413 let hover = analysis.hover(position, true).unwrap().unwrap();
383 expect.assert_debug_eq(&hover.info.actions) 414 expect.assert_debug_eq(&hover.info.actions)
384 } 415 }
385 416
@@ -1810,6 +1841,70 @@ struct S {
1810 } 1841 }
1811 1842
1812 #[test] 1843 #[test]
1844 fn test_hover_no_links() {
1845 check_hover_no_links(
1846 r#"
1847/// Test cases:
1848/// case 1. bare URL: https://www.example.com/
1849/// case 2. inline URL with title: [example](https://www.example.com/)
1850/// case 3. code refrence: [`Result`]
1851/// case 4. code refrence but miss footnote: [`String`]
1852/// case 5. autolink: <http://www.example.com/>
1853/// case 6. email address: <[email protected]>
1854/// case 7. refrence: [example][example]
1855/// case 8. collapsed link: [example][]
1856/// case 9. shortcut link: [example]
1857/// case 10. inline without URL: [example]()
1858/// case 11. refrence: [foo][foo]
1859/// case 12. refrence: [foo][bar]
1860/// case 13. collapsed link: [foo][]
1861/// case 14. shortcut link: [foo]
1862/// case 15. inline without URL: [foo]()
1863/// case 16. just escaped text: \[foo]
1864/// case 17. inline link: [Foo](foo::Foo)
1865///
1866/// [`Result`]: ../../std/result/enum.Result.html
1867/// [^example]: https://www.example.com/
1868pub fn fo<|>o() {}
1869"#,
1870 expect![[r#"
1871 *foo*
1872
1873 ```rust
1874 test
1875 ```
1876
1877 ```rust
1878 pub fn foo()
1879 ```
1880
1881 ---
1882
1883 Test cases:
1884 case 1. bare URL: https://www.example.com/
1885 case 2. inline URL with title: [example](https://www.example.com/)
1886 case 3. code refrence: `Result`
1887 case 4. code refrence but miss footnote: `String`
1888 case 5. autolink: http://www.example.com/
1889 case 6. email address: [email protected]
1890 case 7. refrence: example
1891 case 8. collapsed link: example
1892 case 9. shortcut link: example
1893 case 10. inline without URL: example
1894 case 11. refrence: foo
1895 case 12. refrence: foo
1896 case 13. collapsed link: foo
1897 case 14. shortcut link: foo
1898 case 15. inline without URL: foo
1899 case 16. just escaped text: \[foo]
1900 case 17. inline link: Foo
1901
1902 [^example]: https://www.example.com/
1903 "#]],
1904 );
1905 }
1906
1907 #[test]
1813 fn test_hover_macro_generated_struct_fn_doc_comment() { 1908 fn test_hover_macro_generated_struct_fn_doc_comment() {
1814 mark::check!(hover_macro_generated_struct_fn_doc_comment); 1909 mark::check!(hover_macro_generated_struct_fn_doc_comment);
1815 1910
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 3b97e087f..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,9 +370,18 @@ 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(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> { 379 pub fn hover(
374 self.with_db(|db| hover::hover(db, position)) 380 &self,
381 position: FilePosition,
382 links_in_hover: bool,
383 ) -> Cancelable<Option<RangeInfo<HoverResult>>> {
384 self.with_db(|db| hover::hover(db, position, links_in_hover))
375 } 385 }
376 386
377 /// Computes parameter information for the given call expression. 387 /// Computes parameter information for the given call expression.
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
index acedea71b..107787bb9 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/link_rewrite.rs
@@ -4,7 +4,7 @@
4 4
5use hir::{Adt, Crate, HasAttrs, ModuleDef}; 5use hir::{Adt, Crate, HasAttrs, ModuleDef};
6use ide_db::{defs::Definition, RootDatabase}; 6use ide_db::{defs::Definition, RootDatabase};
7use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; 7use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 9use url::Url;
10 10
@@ -45,6 +45,41 @@ pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition)
45 out 45 out
46} 46}
47 47
48/// Remove all links in markdown documentation.
49pub fn remove_links(markdown: &str) -> String {
50 let mut drop_link = false;
51
52 let mut opts = Options::empty();
53 opts.insert(Options::ENABLE_FOOTNOTES);
54
55 let doc = Parser::new_with_broken_link_callback(
56 markdown,
57 opts,
58 Some(&|_, _| Some((String::new(), String::new()))),
59 );
60 let doc = doc.filter_map(move |evt| match evt {
61 Event::Start(Tag::Link(link_type, ref target, ref title)) => {
62 if link_type == LinkType::Inline && target.contains("://") {
63 Some(Event::Start(Tag::Link(link_type, target.clone(), title.clone())))
64 } else {
65 drop_link = true;
66 None
67 }
68 }
69 Event::End(_) if drop_link => {
70 drop_link = false;
71 None
72 }
73 _ => Some(evt),
74 });
75
76 let mut out = String::new();
77 let mut options = CmarkOptions::default();
78 options.code_block_backticks = 3;
79 cmark_with_options(doc, &mut out, None, options).ok();
80 out
81}
82
48fn rewrite_intra_doc_link( 83fn rewrite_intra_doc_link(
49 db: &RootDatabase, 84 db: &RootDatabase,
50 def: Definition, 85 def: Definition,
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 69d05aed5..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() {
@@ -307,6 +311,7 @@ impl Config {
307 run: data.hoverActions_enable && data.hoverActions_run, 311 run: data.hoverActions_enable && data.hoverActions_run,
308 debug: data.hoverActions_enable && data.hoverActions_debug, 312 debug: data.hoverActions_enable && data.hoverActions_debug,
309 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 313 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
314 links_in_hover: data.hoverActions_linksInHover,
310 }; 315 };
311 316
312 log::info!("Config::update() = {:#?}", self); 317 log::info!("Config::update() = {:#?}", self);
@@ -451,16 +456,18 @@ config_data! {
451 hoverActions_gotoTypeDef: bool = true, 456 hoverActions_gotoTypeDef: bool = true,
452 hoverActions_implementations: bool = true, 457 hoverActions_implementations: bool = true,
453 hoverActions_run: bool = true, 458 hoverActions_run: bool = true,
459 hoverActions_linksInHover: bool = true,
454 460
455 inlayHints_chainingHints: bool = true, 461 inlayHints_chainingHints: bool = true,
456 inlayHints_maxLength: Option<usize> = None, 462 inlayHints_maxLength: Option<usize> = None,
457 inlayHints_parameterHints: bool = true, 463 inlayHints_parameterHints: bool = true,
458 inlayHints_typeHints: bool = true, 464 inlayHints_typeHints: bool = true,
459 465
460 lens_debug: bool = true, 466 lens_debug: bool = true,
461 lens_enable: bool = true, 467 lens_enable: bool = true,
462 lens_implementations: bool = true, 468 lens_implementations: bool = true,
463 lens_run: bool = true, 469 lens_run: bool = true,
470 lens_methodReferences: bool = false,
464 471
465 linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(), 472 linkedProjects: Vec<ManifestOrProjectJson> = Vec::new(),
466 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 c0943a54d..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,
@@ -597,7 +598,7 @@ pub(crate) fn handle_hover(
597) -> Result<Option<lsp_ext::Hover>> { 598) -> Result<Option<lsp_ext::Hover>> {
598 let _p = profile::span("handle_hover"); 599 let _p = profile::span("handle_hover");
599 let position = from_proto::file_position(&snap, params.text_document_position_params)?; 600 let position = from_proto::file_position(&snap, params.text_document_position_params)?;
600 let info = match snap.analysis.hover(position)? { 601 let info = match snap.analysis.hover(position, snap.config.hover.links_in_hover)? {
601 None => return Ok(None), 602 None => return Ok(None),
602 Some(info) => info, 603 Some(info) => info,
603 }; 604 };
@@ -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/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 4a0ffcbb0..3a184094c 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -28,18 +28,41 @@ pub fn assoc_item_list() -> ast::AssocItemList {
28pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment { 28pub fn path_segment(name_ref: ast::NameRef) -> ast::PathSegment {
29 ast_from_text(&format!("use {};", name_ref)) 29 ast_from_text(&format!("use {};", name_ref))
30} 30}
31
31pub fn path_segment_self() -> ast::PathSegment { 32pub fn path_segment_self() -> ast::PathSegment {
32 ast_from_text("use self;") 33 ast_from_text("use self;")
33} 34}
35
36pub fn path_segment_super() -> ast::PathSegment {
37 ast_from_text("use super;")
38}
39
40pub fn path_segment_crate() -> ast::PathSegment {
41 ast_from_text("use crate;")
42}
43
34pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { 44pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
35 path_from_text(&format!("use {}", segment)) 45 ast_from_text(&format!("use {}", segment))
36} 46}
47
37pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 48pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
38 path_from_text(&format!("{}::{}", qual, segment)) 49 ast_from_text(&format!("{}::{}", qual, segment))
39} 50}
40// FIXME: make this private 51
41pub fn path_from_text(text: &str) -> ast::Path { 52pub fn path_concat(first: ast::Path, second: ast::Path) -> ast::Path {
42 ast_from_text(text) 53 ast_from_text(&format!("{}::{}", first, second))
54}
55
56pub fn path_from_segments(
57 segments: impl IntoIterator<Item = ast::PathSegment>,
58 is_abs: bool,
59) -> ast::Path {
60 let segments = segments.into_iter().map(|it| it.syntax().clone()).join("::");
61 ast_from_text(&if is_abs {
62 format!("use ::{};", segments)
63 } else {
64 format!("use {};", segments)
65 })
43} 66}
44 67
45pub fn glob_use_tree() -> ast::UseTree { 68pub fn glob_use_tree() -> ast::UseTree {
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