use rustc_span::edition::Edition;
use rustc_span::Span;
use std::borrow::Cow;
+use std::cell::RefCell;
use std::collections::VecDeque;
use std::default::Default;
use std::fmt::Write;
use crate::html::highlight;
use crate::html::toc::TocBuilder;
-use pulldown_cmark::{html, BrokenLink, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
+use pulldown_cmark::{
+ html, BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag,
+};
#[cfg(test)]
mod tests;
type Item = Event<'a>;
fn next(&mut self) -> Option<Self::Item> {
- use pulldown_cmark::LinkType;
-
let mut event = self.inner.next();
// Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
}
}
+type SpannedEvent<'a> = (Event<'a>, Range<usize>);
+
/// Make headings links with anchor IDs and build up TOC.
struct HeadingLinks<'a, 'b, 'ids, I> {
inner: I,
toc: Option<&'b mut TocBuilder>,
- buf: VecDeque<Event<'a>>,
+ buf: VecDeque<SpannedEvent<'a>>,
id_map: &'ids mut IdMap,
}
}
}
-impl<'a, 'b, 'ids, I: Iterator<Item = Event<'a>>> Iterator for HeadingLinks<'a, 'b, 'ids, I> {
- type Item = Event<'a>;
+impl<'a, 'b, 'ids, I: Iterator<Item = SpannedEvent<'a>>> Iterator
+ for HeadingLinks<'a, 'b, 'ids, I>
+{
+ type Item = SpannedEvent<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(e) = self.buf.pop_front() {
}
let event = self.inner.next();
- if let Some(Event::Start(Tag::Heading(level))) = event {
+ if let Some((Event::Start(Tag::Heading(level)), _)) = event {
let mut id = String::new();
for event in &mut self.inner {
- match &event {
+ match &event.0 {
Event::End(Tag::Heading(..)) => break,
+ Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
Event::Text(text) | Event::Code(text) => {
id.extend(text.chars().filter_map(slugify));
+ self.buf.push_back(event);
}
- _ => {}
- }
- match event {
- Event::Start(Tag::Link(_, _, _)) | Event::End(Tag::Link(..)) => {}
- event => self.buf.push_back(event),
+ _ => self.buf.push_back(event),
}
}
let id = self.id_map.derive(id);
if let Some(ref mut builder) = self.toc {
let mut html_header = String::new();
- html::push_html(&mut html_header, self.buf.iter().cloned());
+ html::push_html(&mut html_header, self.buf.iter().map(|(ev, _)| ev.clone()));
let sec = builder.push(level as u32, html_header, id.clone());
- self.buf.push_front(Event::Html(format!("{} ", sec).into()));
+ self.buf.push_front((Event::Html(format!("{} ", sec).into()), 0..0));
}
- self.buf.push_back(Event::Html(format!("</a></h{}>", level).into()));
+ self.buf.push_back((Event::Html(format!("</a></h{}>", level).into()), 0..0));
let start_tags = format!(
"<h{level} id=\"{id}\" class=\"section-header\">\
id = id,
level = level
);
- return Some(Event::Html(start_tags.into()));
+ return Some((Event::Html(start_tags.into()), 0..0));
}
event
}
}
}
-impl<'a, I: Iterator<Item = Event<'a>>> Iterator for Footnotes<'a, I> {
- type Item = Event<'a>;
+impl<'a, I: Iterator<Item = SpannedEvent<'a>>> Iterator for Footnotes<'a, I> {
+ type Item = SpannedEvent<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.inner.next() {
- Some(Event::FootnoteReference(ref reference)) => {
+ Some((Event::FootnoteReference(ref reference), range)) => {
let entry = self.get_entry(&reference);
let reference = format!(
"<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}</a></sup>",
(*entry).1
);
- return Some(Event::Html(reference.into()));
+ return Some((Event::Html(reference.into()), range));
}
- Some(Event::Start(Tag::FootnoteDefinition(def))) => {
+ Some((Event::Start(Tag::FootnoteDefinition(def)), _)) => {
let mut content = Vec::new();
- for event in &mut self.inner {
+ for (event, _) in &mut self.inner {
if let Event::End(Tag::FootnoteDefinition(..)) = event {
break;
}
ret.push_str("</li>");
}
ret.push_str("</ol></div>");
- return Some(Event::Html(ret.into()));
+ return Some((Event::Html(ret.into()), 0..0));
} else {
return None;
}
};
let p = Parser::new_with_broken_link_callback(md, opts(), Some(&mut replacer));
+ let p = p.into_offset_iter();
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
- let p = LinkReplacer::new(p, links);
- let p = CodeBlocks::new(p, codes, edition, playground);
let p = Footnotes::new(p);
+ let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
+ let p = CodeBlocks::new(p, codes, edition, playground);
html::push_html(&mut s, p);
s
crate fn into_string(self) -> String {
let MarkdownWithToc(md, mut ids, codes, edition, playground) = self;
- let p = Parser::new_ext(md, opts());
+ let p = Parser::new_ext(md, opts()).into_offset_iter();
let mut s = String::with_capacity(md.len() * 3 / 2);
{
let p = HeadingLinks::new(p, Some(&mut toc), &mut ids);
- let p = CodeBlocks::new(p, codes, edition, playground);
let p = Footnotes::new(p);
+ let p = CodeBlocks::new(p.map(|(ev, _)| ev), codes, edition, playground);
html::push_html(&mut s, p);
}
if md.is_empty() {
return String::new();
}
- let p = Parser::new_ext(md, opts());
+ let p = Parser::new_ext(md, opts()).into_offset_iter();
// Treat inline HTML as plain text.
- let p = p.map(|event| match event {
- Event::Html(text) => Event::Text(text),
+ let p = p.map(|event| match event.0 {
+ Event::Html(text) => (Event::Text(text), event.1),
_ => event,
});
let mut s = String::with_capacity(md.len() * 3 / 2);
let p = HeadingLinks::new(p, None, &mut ids);
- let p = CodeBlocks::new(p, codes, edition, playground);
let p = Footnotes::new(p);
+ let p = CodeBlocks::new(p.map(|(ev, _)| ev), codes, edition, playground);
html::push_html(&mut s, p);
s
s
}
-crate fn markdown_links(md: &str) -> Vec<(String, Option<Range<usize>>)> {
+crate struct MarkdownLink {
+ pub kind: LinkType,
+ pub link: String,
+ pub range: Range<usize>,
+}
+
+crate fn markdown_links(md: &str) -> Vec<MarkdownLink> {
if md.is_empty() {
return vec![];
}
- 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 });
}
}
- links.append(&mut shortcut_links);
-
- links
+ links.into_inner()
}
#[derive(Debug)]
crate code: Range<usize>,
crate is_fenced: bool,
crate syntax: Option<String>,
+ crate is_ignore: bool,
}
/// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
while let Some((event, offset)) = p.next() {
if let Event::Start(Tag::CodeBlock(syntax)) = event {
- let (syntax, code_start, code_end, range, is_fenced) = match syntax {
+ let (syntax, code_start, code_end, range, is_fenced, is_ignore) = match syntax {
CodeBlockKind::Fenced(syntax) => {
let syntax = syntax.as_ref();
let lang_string = if syntax.is_empty() {
if !lang_string.rust {
continue;
}
+ let is_ignore = lang_string.ignore != Ignore::None;
let syntax = if syntax.is_empty() { None } else { Some(syntax.to_owned()) };
let (code_start, mut code_end) = match p.next() {
Some((Event::Text(_), offset)) => (offset.start, offset.end),
range: offset,
code,
syntax,
+ is_ignore,
});
continue;
}
range: offset,
code,
syntax,
+ is_ignore,
});
continue;
}
while let Some((Event::Text(_), offset)) = p.next() {
code_end = offset.end;
}
- (syntax, code_start, code_end, offset, true)
+ (syntax, code_start, code_end, offset, true, is_ignore)
}
CodeBlockKind::Indented => {
// The ending of the offset goes too far sometime so we reduce it by one in
offset.end,
Range { start: offset.start, end: offset.end - 1 },
false,
+ false,
)
} else {
- (None, offset.start, offset.end, offset, false)
+ (None, offset.start, offset.end, offset, false, false)
}
}
};
range,
code: Range { start: code_start, end: code_end },
syntax,
+ is_ignore,
});
}
}
map.insert("toggle-all-docs".to_owned(), 1);
map.insert("all-types".to_owned(), 1);
map.insert("default-settings".to_owned(), 1);
+ map.insert("rustdoc-vars".to_owned(), 1);
+ map.insert("sidebar-vars".to_owned(), 1);
// This is the list of IDs used by rustdoc sections.
map.insert("fields".to_owned(), 1);
map.insert("variants".to_owned(), 1);
map.insert("trait-implementations".to_owned(), 1);
map.insert("synthetic-implementations".to_owned(), 1);
map.insert("blanket-implementations".to_owned(), 1);
- map.insert("deref-methods".to_owned(), 1);
map
}