diff options
| -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 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/config.rs | 2 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 2 |
5 files changed, 149 insertions, 13 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 37171cbef33..bb9f12cd37e 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -14,7 +14,7 @@ use test_utils::mark; use crate::{ display::{macro_label, ShortLabel, ToNav, TryToNav}, - link_rewrite::rewrite_links, + link_rewrite::{remove_links, rewrite_links}, markup::Markup, runnables::runnable, FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, @@ -26,17 +26,29 @@ pub struct HoverConfig { pub run: bool, pub debug: bool, pub goto_type_def: bool, + pub links_in_hover: bool, } impl Default for HoverConfig { fn default() -> Self { - Self { implementations: true, run: true, debug: true, goto_type_def: true } + Self { + implementations: true, + run: true, + debug: true, + goto_type_def: true, + links_in_hover: true, + } } } impl HoverConfig { - pub const NO_ACTIONS: Self = - Self { implementations: false, run: false, debug: false, goto_type_def: false }; + pub const NO_ACTIONS: Self = Self { + implementations: false, + run: false, + debug: false, + goto_type_def: false, + links_in_hover: true, + }; pub fn any(&self) -> bool { self.implementations || self.runnable() || self.goto_type_def @@ -75,7 +87,11 @@ pub struct HoverResult { // // Shows additional information, like type of an expression or documentation for definition when "focusing" code. // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. -pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { +pub(crate) fn hover( + db: &RootDatabase, + position: FilePosition, + links_in_hover: bool, +) -> Option<RangeInfo<HoverResult>> { let sema = Semantics::new(db); let file = sema.parse(position.file_id).syntax().clone(); let token = pick_best(file.token_at_offset(position.offset))?; @@ -93,7 +109,11 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn }; if let Some(definition) = definition { if let Some(markup) = hover_for_definition(db, definition) { - let markup = rewrite_links(db, &markup.as_str(), &definition); + let markup = if links_in_hover { + rewrite_links(db, &markup.as_str(), &definition) + } else { + remove_links(&markup.as_str()) + }; res.markup = Markup::from(markup); if let Some(action) = show_implementations_action(db, definition) { res.actions.push(action); @@ -363,12 +383,23 @@ mod tests { fn check_hover_no_result(ra_fixture: &str) { let (analysis, position) = analysis_and_position(ra_fixture); - assert!(analysis.hover(position).unwrap().is_none()); + assert!(analysis.hover(position, true).unwrap().is_none()); } fn check(ra_fixture: &str, expect: Expect) { let (analysis, position) = analysis_and_position(ra_fixture); - let hover = analysis.hover(position).unwrap().unwrap(); + let hover = analysis.hover(position, true).unwrap().unwrap(); + + let content = analysis.db.file_text(position.file_id); + let hovered_element = &content[hover.range]; + + let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup); + expect.assert_eq(&actual) + } + + fn check_hover_no_links(ra_fixture: &str, expect: Expect) { + let (analysis, position) = analysis_and_position(ra_fixture); + let hover = analysis.hover(position, false).unwrap().unwrap(); let content = analysis.db.file_text(position.file_id); let hovered_element = &content[hover.range]; @@ -379,7 +410,7 @@ mod tests { fn check_actions(ra_fixture: &str, expect: Expect) { let (analysis, position) = analysis_and_position(ra_fixture); - let hover = analysis.hover(position).unwrap().unwrap(); + let hover = analysis.hover(position, true).unwrap().unwrap(); expect.assert_debug_eq(&hover.info.actions) } @@ -1810,6 +1841,70 @@ struct S { } #[test] + fn test_hover_no_links() { + check_hover_no_links( + r#" +/// Test cases: +/// case 1. bare URL: https://www.example.com/ +/// case 2. inline URL with title: [example](https://www.example.com/) +/// case 3. code refrence: [`Result`] +/// case 4. code refrence but miss footnote: [`String`] +/// case 5. autolink: <http://www.example.com/> +/// case 6. email address: <test@example.com> +/// case 7. refrence: [example][example] +/// case 8. collapsed link: [example][] +/// case 9. shortcut link: [example] +/// case 10. inline without URL: [example]() +/// 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: \[foo] +/// case 17. inline link: [Foo](foo::Foo) +/// +/// [`Result`]: ../../std/result/enum.Result.html +/// [^example]: https://www.example.com/ +pub fn fo<|>o() {} +"#, + expect + case 3. code refrence: `Result` + case 4. code refrence but miss footnote: `String` + case 5. autolink: http://www.example.com/ + case 6. email address: test@example.com + case 7. refrence: example + case 8. collapsed link: example + case 9. shortcut link: example + case 10. inline without URL: example + case 11. refrence: foo + case 12. refrence: foo + case 13. collapsed link: foo + case 14. shortcut link: foo + case 15. inline without URL: foo + case 16. just escaped text: \[foo] + case 17. inline link: Foo + + [^example]: https://www.example.com/ + "#]], + ); + } + + #[test] fn test_hover_macro_generated_struct_fn_doc_comment() { mark::check!(hover_macro_generated_struct_fn_doc_comment); diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 3b97e087f88..4763c0aac3a 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -370,8 +370,12 @@ impl Analysis { } /// Returns a short text describing element at position. - pub fn hover(&self, position: FilePosition) -> Cancelable<Option<RangeInfo<HoverResult>>> { - self.with_db(|db| hover::hover(db, position)) + pub fn hover( + &self, + position: FilePosition, + links_in_hover: bool, + ) -> Cancelable<Option<RangeInfo<HoverResult>>> { + self.with_db(|db| hover::hover(db, position, links_in_hover)) } /// 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 acedea71bd5..107787bb91b 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs @@ -4,7 +4,7 @@ use hir::{Adt, Crate, HasAttrs, ModuleDef}; use ide_db::{defs::Definition, RootDatabase}; -use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; +use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; use url::Url; @@ -45,6 +45,41 @@ pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) out } +/// Remove all links in markdown documentation. +pub fn remove_links(markdown: &str) -> String { + let mut drop_link = false; + + let mut opts = Options::empty(); + opts.insert(Options::ENABLE_FOOTNOTES); + + let doc = Parser::new_with_broken_link_callback( + markdown, + opts, + Some(&|_, _| Some((String::new(), String::new()))), + ); + let doc = doc.filter_map(move |evt| match evt { + Event::Start(Tag::Link(link_type, ref target, ref title)) => { + if link_type == LinkType::Inline && target.contains("://") { + Some(Event::Start(Tag::Link(link_type, target.clone(), title.clone()))) + } else { + drop_link = true; + None + } + } + Event::End(_) if drop_link => { + drop_link = false; + None + } + _ => Some(evt), + }); + + let mut out = String::new(); + let mut options = CmarkOptions::default(); + options.code_block_backticks = 3; + cmark_with_options(doc, &mut out, None, options).ok(); + out +} + fn rewrite_intra_doc_link( db: &RootDatabase, def: Definition, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 69d05aed5ac..fab15f86009 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -307,6 +307,7 @@ impl Config { run: data.hoverActions_enable && data.hoverActions_run, debug: data.hoverActions_enable && data.hoverActions_debug, goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, + links_in_hover: data.hoverActions_linksInHover, }; log::info!("Config::update() = {:#?}", self); @@ -451,6 +452,7 @@ config_data! { hoverActions_gotoTypeDef: bool = true, hoverActions_implementations: bool = true, hoverActions_run: bool = true, + hoverActions_linksInHover: bool = true, inlayHints_chainingHints: bool = true, inlayHints_maxLength: Option<usize> = None, diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index c0943a54dc6..f7c7a378a14 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -597,7 +597,7 @@ pub(crate) fn handle_hover( ) -> Result<Option<lsp_ext::Hover>> { let _p = profile::span("handle_hover"); let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let info = match snap.analysis.hover(position)? { + let info = match snap.analysis.hover(position, snap.config.hover.links_in_hover)? { None => return Ok(None), Some(info) => info, }; |
