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