about summary refs log tree commit diff
diff options
context:
space:
mode:
-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
-rw-r--r--crates/rust-analyzer/src/config.rs2
-rw-r--r--crates/rust-analyzer/src/handlers.rs2
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![[r#"
+                *foo*
+
+                ```rust
+                test
+                ```
+
+                ```rust
+                pub fn foo()
+                ```
+
+                ---
+
+                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
+                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,
     };