- let mut links = vec![];
- let mut shortcut_links = vec![];
-
- {
- let locate = |s: &str| unsafe {
- let s_start = s.as_ptr();
- let s_end = s_start.add(s.len());
- let md_start = md.as_ptr();
- let md_end = md_start.add(md.len());
- if md_start <= s_start && s_end <= md_end {
- let start = s_start.offset_from(md_start) as usize;
- let end = s_end.offset_from(md_start) as usize;
- Some(start..end)
- } else {
- None
- }
- };
-
- let mut push = |link: BrokenLink<'_>| {
- // FIXME: use `link.span` instead of `locate`
- // (doing it now includes the `[]` as well as the text)
- shortcut_links.push((link.reference.to_owned(), locate(link.reference)));
- None
- };
- let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut push));
-
- // There's no need to thread an IdMap through to here because
- // the IDs generated aren't going to be emitted anywhere.
- let mut ids = IdMap::new();
- let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
-
- for ev in iter {
- if let Event::Start(Tag::Link(_, dest, _)) = ev {
- debug!("found link: {}", dest);
- links.push(match dest {
- CowStr::Borrowed(s) => (s.to_owned(), locate(s)),
- s @ (CowStr::Boxed(..) | CowStr::Inlined(..)) => (s.into_string(), None),
- });
- }
+ let links = RefCell::new(vec![]);
+
+ // FIXME: remove this function once pulldown_cmark can provide spans for link definitions.
+ let locate = |s: &str, fallback: Range<usize>| unsafe {
+ let s_start = s.as_ptr();
+ let s_end = s_start.add(s.len());
+ let md_start = md.as_ptr();
+ let md_end = md_start.add(md.len());
+ if md_start <= s_start && s_end <= md_end {
+ let start = s_start.offset_from(md_start) as usize;
+ let end = s_end.offset_from(md_start) as usize;
+ start..end
+ } else {
+ fallback
+ }
+ };
+
+ let span_for_link = |link: &CowStr<'_>, span: Range<usize>| {
+ // For diagnostics, we want to underline the link's definition but `span` will point at
+ // where the link is used. This is a problem for reference-style links, where the definition
+ // is separate from the usage.
+ match link {
+ // `Borrowed` variant means the string (the link's destination) may come directly from
+ // the markdown text and we can locate the original link destination.
+ // NOTE: LinkReplacer also provides `Borrowed` but possibly from other sources,
+ // so `locate()` can fall back to use `span`.
+ CowStr::Borrowed(s) => locate(s, span),
+
+ // For anything else, we can only use the provided range.
+ CowStr::Boxed(_) | CowStr::Inlined(_) => span,
+ }
+ };
+
+ let mut push = |link: BrokenLink<'_>| {
+ let span = span_for_link(&CowStr::Borrowed(link.reference), link.span);
+ links.borrow_mut().push(MarkdownLink {
+ kind: LinkType::ShortcutUnknown,
+ link: link.reference.to_owned(),
+ range: span,
+ });
+ None
+ };
+ let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut push)).into_offset_iter();
+
+ // There's no need to thread an IdMap through to here because
+ // the IDs generated aren't going to be emitted anywhere.
+ let mut ids = IdMap::new();
+ let iter = Footnotes::new(HeadingLinks::new(p, None, &mut ids));
+
+ for ev in iter {
+ if let Event::Start(Tag::Link(kind, dest, _)) = ev.0 {
+ debug!("found link: {}", dest);
+ let span = span_for_link(&dest, ev.1);
+ links.borrow_mut().push(MarkdownLink { kind, link: dest.into_string(), range: span });