Skip to content

Commit e73ee9d

Browse files
committed
Add hover config linksInHover to suppress links
1 parent 18c62c8 commit e73ee9d

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)