Skip to content

Commit e813de6

Browse files
bors[bot]flw-cn
andauthored
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]>
2 parents 18c62c8 + e73ee9d commit e813de6

File tree

5 files changed

+149
-13
lines changed

5 files changed

+149
-13
lines changed

crates/ide/src/hover.rs

Lines changed: 104 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use test_utils::mark;
1414

1515
use crate::{
1616
display::{macro_label, ShortLabel, ToNav, TryToNav},
17-
link_rewrite::rewrite_links,
17+
link_rewrite::{remove_links, rewrite_links},
1818
markup::Markup,
1919
runnables::runnable,
2020
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable,
@@ -26,17 +26,29 @@ pub struct HoverConfig {
2626
pub run: bool,
2727
pub debug: bool,
2828
pub goto_type_def: bool,
29+
pub links_in_hover: bool,
2930
}
3031

3132
impl Default for HoverConfig {
3233
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+
}
3441
}
3542
}
3643

3744
impl HoverConfig {
38-
pub const NO_ACTIONS: Self =
39-
Self { implementations: false, run: false, debug: false, goto_type_def: false };
45+
pub const NO_ACTIONS: Self = Self {
46+
implementations: false,
47+
run: false,
48+
debug: false,
49+
goto_type_def: false,
50+
links_in_hover: true,
51+
};
4052

4153
pub fn any(&self) -> bool {
4254
self.implementations || self.runnable() || self.goto_type_def
@@ -75,7 +87,11 @@ pub struct HoverResult {
7587
//
7688
// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
7789
// 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>> {
7995
let sema = Semantics::new(db);
8096
let file = sema.parse(position.file_id).syntax().clone();
8197
let token = pick_best(file.token_at_offset(position.offset))?;
@@ -93,7 +109,11 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
93109
};
94110
if let Some(definition) = definition {
95111
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+
};
97117
res.markup = Markup::from(markup);
98118
if let Some(action) = show_implementations_action(db, definition) {
99119
res.actions.push(action);
@@ -363,12 +383,23 @@ mod tests {
363383

364384
fn check_hover_no_result(ra_fixture: &str) {
365385
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());
367387
}
368388

369389
fn check(ra_fixture: &str, expect: Expect) {
370390
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();
372403

373404
let content = analysis.db.file_text(position.file_id);
374405
let hovered_element = &content[hover.range];
@@ -379,7 +410,7 @@ mod tests {
379410

380411
fn check_actions(ra_fixture: &str, expect: Expect) {
381412
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();
383414
expect.assert_debug_eq(&hover.info.actions)
384415
}
385416

@@ -1809,6 +1840,70 @@ struct S {
18091840
);
18101841
}
18111842

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+
18121907
#[test]
18131908
fn test_hover_macro_generated_struct_fn_doc_comment() {
18141909
mark::check!(hover_macro_generated_struct_fn_doc_comment);

crates/ide/src/lib.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,12 @@ impl Analysis {
370370
}
371371

372372
/// Returns a short text describing element at position.
373-
pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> {
374-
self.with_db(|db| hover::hover(db, position))
373+
pub fn hover(
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))
375379
}
376380

377381
/// Computes parameter information for the given call expression.

crates/ide/src/link_rewrite.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use hir::{Adt, Crate, HasAttrs, ModuleDef};
66
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};
88
use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
99
use url::Url;
1010

@@ -45,6 +45,41 @@ pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition)
4545
out
4646
}
4747

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+
4883
fn rewrite_intra_doc_link(
4984
db: &RootDatabase,
5085
def: Definition,

crates/rust-analyzer/src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ impl Config {
307307
run: data.hoverActions_enable && data.hoverActions_run,
308308
debug: data.hoverActions_enable && data.hoverActions_debug,
309309
goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
310+
links_in_hover: data.hoverActions_linksInHover,
310311
};
311312

312313
log::info!("Config::update() = {:#?}", self);
@@ -451,6 +452,7 @@ config_data! {
451452
hoverActions_gotoTypeDef: bool = true,
452453
hoverActions_implementations: bool = true,
453454
hoverActions_run: bool = true,
455+
hoverActions_linksInHover: bool = true,
454456

455457
inlayHints_chainingHints: bool = true,
456458
inlayHints_maxLength: Option<usize> = None,

crates/rust-analyzer/src/handlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ pub(crate) fn handle_hover(
597597
) -> Result<Option<lsp_ext::Hover>> {
598598
let _p = profile::span("handle_hover");
599599
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
600-
let info = match snap.analysis.hover(position)? {
600+
let info = match snap.analysis.hover(position, snap.config.hover.links_in_hover)? {
601601
None => return Ok(None),
602602
Some(info) => info,
603603
};

0 commit comments

Comments
 (0)