diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/fn_references.rs | 95 | ||||
-rw-r--r-- | crates/ide/src/hover.rs | 113 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 14 | ||||
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 37 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 2 |
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 | |||
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 | } | ||
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 | ||
15 | use crate::{ | 15 | use 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 | ||
31 | impl Default for HoverConfig { | 32 | impl 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 | ||
37 | impl HoverConfig { | 44 | impl 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. |
78 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | 90 | pub(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/ | ||
1868 | pub 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; | |||
38 | mod matching_brace; | 38 | mod matching_brace; |
39 | mod parent_module; | 39 | mod parent_module; |
40 | mod references; | 40 | mod references; |
41 | mod fn_references; | ||
41 | mod runnables; | 42 | mod runnables; |
42 | mod status; | 43 | mod status; |
43 | mod syntax_highlighting; | 44 | mod 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 | ||
5 | use hir::{Adt, Crate, HasAttrs, ModuleDef}; | 5 | use hir::{Adt, Crate, HasAttrs, ModuleDef}; |
6 | use ide_db::{defs::Definition, RootDatabase}; | 6 | use ide_db::{defs::Definition, RootDatabase}; |
7 | use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; | 7 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; |
8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
9 | use url::Url; | 9 | use 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. | ||
49 | pub 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 | |||
48 | fn rewrite_intra_doc_link( | 83 | fn 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. |
206 | fn has_test_related_attribute(fn_def: &ast::Fn) -> bool { | 206 | pub(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()) |