aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-09-29 13:10:47 +0100
committerGitHub <[email protected]>2020-09-29 13:10:47 +0100
commite813de6cdd53e542bce8d4a554288dc2f17bbf5e (patch)
tree633e533ce5aa3c10337bafc240e1f4911e4ecb65 /crates/ide
parent18c62c8a39d95ce3bb10ff5446bb589b1128a090 (diff)
parente73ee9dfa28e2c093cc79e0e8d729945c43f3c81 (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.rs113
-rw-r--r--crates/ide/src/lib.rs8
-rw-r--r--crates/ide/src/link_rewrite.rs37
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
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..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
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,