diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/ide/src/link_rewrite.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/ide/src/link_rewrite.rs')
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs new file mode 100644 index 000000000..a826220e3 --- /dev/null +++ b/crates/ide/src/link_rewrite.rs | |||
@@ -0,0 +1,79 @@ | |||
1 | //! This is a wrapper around [`hir::link_rewrite`] connecting it to the markdown parser. | ||
2 | |||
3 | use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; | ||
4 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | ||
5 | |||
6 | use hir::resolve_doc_link; | ||
7 | use ide_db::{defs::Definition, RootDatabase}; | ||
8 | |||
9 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) | ||
10 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | ||
11 | let doc = Parser::new_with_broken_link_callback( | ||
12 | markdown, | ||
13 | Options::empty(), | ||
14 | Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), | ||
15 | ); | ||
16 | |||
17 | let doc = map_links(doc, |target, title: &str| { | ||
18 | // This check is imperfect, there's some overlap between valid intra-doc links | ||
19 | // and valid URLs so we choose to be too eager to try to resolve what might be | ||
20 | // a URL. | ||
21 | if target.contains("://") { | ||
22 | (target.to_string(), title.to_string()) | ||
23 | } else { | ||
24 | // Two posibilities: | ||
25 | // * path-based links: `../../module/struct.MyStruct.html` | ||
26 | // * module-based links (AKA intra-doc links): `super::super::module::MyStruct` | ||
27 | let resolved = match definition { | ||
28 | Definition::ModuleDef(t) => resolve_doc_link(db, t, title, target), | ||
29 | Definition::Macro(t) => resolve_doc_link(db, t, title, target), | ||
30 | Definition::Field(t) => resolve_doc_link(db, t, title, target), | ||
31 | Definition::SelfType(t) => resolve_doc_link(db, t, title, target), | ||
32 | Definition::Local(t) => resolve_doc_link(db, t, title, target), | ||
33 | Definition::TypeParam(t) => resolve_doc_link(db, t, title, target), | ||
34 | }; | ||
35 | |||
36 | match resolved { | ||
37 | Some((target, title)) => (target, title), | ||
38 | None => (target.to_string(), title.to_string()), | ||
39 | } | ||
40 | } | ||
41 | }); | ||
42 | let mut out = String::new(); | ||
43 | let mut options = CmarkOptions::default(); | ||
44 | options.code_block_backticks = 3; | ||
45 | cmark_with_options(doc, &mut out, None, options).ok(); | ||
46 | out | ||
47 | } | ||
48 | |||
49 | // Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. | ||
50 | fn map_links<'e>( | ||
51 | events: impl Iterator<Item = Event<'e>>, | ||
52 | callback: impl Fn(&str, &str) -> (String, String), | ||
53 | ) -> impl Iterator<Item = Event<'e>> { | ||
54 | let mut in_link = false; | ||
55 | let mut link_target: Option<CowStr> = None; | ||
56 | |||
57 | events.map(move |evt| match evt { | ||
58 | Event::Start(Tag::Link(_link_type, ref target, _)) => { | ||
59 | in_link = true; | ||
60 | link_target = Some(target.clone()); | ||
61 | evt | ||
62 | } | ||
63 | Event::End(Tag::Link(link_type, _target, _)) => { | ||
64 | in_link = false; | ||
65 | Event::End(Tag::Link(link_type, link_target.take().unwrap(), CowStr::Borrowed(""))) | ||
66 | } | ||
67 | Event::Text(s) if in_link => { | ||
68 | let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); | ||
69 | link_target = Some(CowStr::Boxed(link_target_s.into())); | ||
70 | Event::Text(CowStr::Boxed(link_name.into())) | ||
71 | } | ||
72 | Event::Code(s) if in_link => { | ||
73 | let (link_target_s, link_name) = callback(&link_target.take().unwrap(), &s); | ||
74 | link_target = Some(CowStr::Boxed(link_target_s.into())); | ||
75 | Event::Code(CowStr::Boxed(link_name.into())) | ||
76 | } | ||
77 | _ => evt, | ||
78 | }) | ||
79 | } | ||