aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src')
-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
5 files changed, 248 insertions, 13 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/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())