aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/link_rewrite.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/link_rewrite.rs')
-rw-r--r--crates/ide/src/link_rewrite.rs79
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
3use pulldown_cmark::{CowStr, Event, Options, Parser, Tag};
4use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
5
6use hir::resolve_doc_link;
7use ide_db::{defs::Definition, RootDatabase};
8
9/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
10pub 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.
50fn 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}