diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-09-29 13:10:47 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-09-29 13:10:47 +0100 |
commit | e813de6cdd53e542bce8d4a554288dc2f17bbf5e (patch) | |
tree | 633e533ce5aa3c10337bafc240e1f4911e4ecb65 /crates/ide | |
parent | 18c62c8a39d95ce3bb10ff5446bb589b1128a090 (diff) | |
parent | e73ee9dfa28e2c093cc79e0e8d729945c43f3c81 (diff) |
Merge #6080
6080: Add hover config `linksInHover` to suppress links r=flw-cn a=flw-cn
This PR solves the problem of using RA under vim8. It should close #6014.
Since vim8's popup-window doesn't capture focus, the URL given by RA is effectively useless. links are neither displayed correctly nor can they be clicked. This makes the hover window ugly and inefficient.
I'm providing this patch so that people who share my confusion (which I'm almost certain vim8 users do) will have a way to remove links from markdown.
I noticed that [gopls has an option](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#linksinhover-bool) for a similar purpose. So I added an option `linksInHover` to enable this behavior. This is a bool value and defaults to `true` to keep the behavior consistent with the master version. But you can suppress the links in the hover text by setting it to `false`.
The name of my option, `linksInHover`, is borrowed from gopls.
Before applying this patch:
<img width="1280" alt="image" src="https://user-images.githubusercontent.com/5546718/93285021-85698a00-f806-11ea-911d-e77fea4a47f0.png">
After applying this patch(with `"rust-analyzer.hoverActions.linksInHover": false,`):
<img width="1280" alt="image" src="https://user-images.githubusercontent.com/5546718/94332256-2e359780-0006-11eb-9724-1aed14130d0d.png">
This is the full test cases:
```
fn main() {
let args: Vec<String> = std::env::args().collect();
test();
println!("args: {:?}", args);
}
/// Test cases:
/// case 1. bare URL: https://rust-lang.org/
/// case 2. inline URL with title: [foo](https://rust-lang.org/)
/// case 3. code refrence: [`Result`]
/// case 4. code refrence but miss footnote: [`String`]
/// case 5. autolink: <http://rust-lang.org/>
/// case 6. email address: <[email protected]>
/// case 7. refrence: [bing][google]
/// case 8. collapsed link: [bing][]
/// case 9. shortcut link: [bing]
/// case 10. inline without URL: [bing]()
/// case 11. refrence: [foo][foo]
/// case 12. refrence: [foo][bar]
/// case 13. collapsed link: [foo][]
/// case 14. shortcut link: [foo]
/// case 15. inline without URL: [foo]()
/// case 16. just escaped text: \[hello]
/// case 17. inline link: [Foo](foo::Foo)
///
/// [`Result`]: ../../std/result/enum.Result.html
/// [^bing]: https://www.bing.com/
/// [^google]: https://www.google.com/
pub fn test() {
println!("Hello");
}
```
screenshot:
<img width="1278" alt="image" src="https://user-images.githubusercontent.com/5546718/94332055-45738580-0004-11eb-9153-707f508d0c4b.png">
Co-authored-by: flw <[email protected]>
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/hover.rs | 113 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 37 |
3 files changed, 146 insertions, 12 deletions
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..4763c0aac 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -370,8 +370,12 @@ impl Analysis { | |||
370 | } | 370 | } |
371 | 371 | ||
372 | /// Returns a short text describing element at position. | 372 | /// Returns a short text describing element at position. |
373 | pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> { | 373 | pub fn hover( |
374 | self.with_db(|db| hover::hover(db, position)) | 374 | &self, |
375 | position: FilePosition, | ||
376 | links_in_hover: bool, | ||
377 | ) -> Cancelable<Option<RangeInfo<HoverResult>>> { | ||
378 | self.with_db(|db| hover::hover(db, position, links_in_hover)) | ||
375 | } | 379 | } |
376 | 380 | ||
377 | /// Computes parameter information for the given call expression. | 381 | /// 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, |