1 //! Markdown formatting for rustdoc.
3 //! This module implements markdown formatting through the pulldown-cmark library.
6 //! #![feature(rustc_private)]
8 //! extern crate rustc_span;
10 //! use rustc_span::edition::Edition;
11 //! use rustdoc::html::markdown::{IdMap, Markdown, ErrorCodes};
13 //! let s = "My *markdown* _text_";
14 //! let mut id_map = IdMap::new();
15 //! let md = Markdown(s, &[], &mut id_map, ErrorCodes::Yes, Edition::Edition2015, &None);
16 //! let html = md.to_string();
17 //! // ... something using html
20 #![allow(non_camel_case_types)]
22 use rustc_data_structures
::fx
::FxHashMap
;
23 use rustc_span
::edition
::Edition
;
25 use std
::cell
::RefCell
;
26 use std
::collections
::VecDeque
;
27 use std
::default::Default
;
32 use crate::html
::highlight
;
33 use crate::html
::toc
::TocBuilder
;
36 use pulldown_cmark
::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag}
;
41 fn opts() -> Options
{
42 Options
::ENABLE_TABLES
| Options
::ENABLE_FOOTNOTES
45 /// When `to_string` is called, this struct will emit the HTML corresponding to
46 /// the rendered version of the contained markdown string.
47 pub struct Markdown
<'a
>(
49 /// A list of link replacements.
50 pub &'a
[(String
, String
)],
51 /// The current list of used header IDs.
53 /// Whether to allow the use of explicit error codes in doctest lang strings.
55 /// Default edition to use when parsing doctests (to add a `fn main`).
57 pub &'a Option
<Playground
>,
59 /// A tuple struct like `Markdown` that renders the markdown with a table of contents.
60 pub struct MarkdownWithToc
<'a
>(
65 pub &'a Option
<Playground
>,
67 /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags.
68 pub struct MarkdownHtml
<'a
>(
73 pub &'a Option
<Playground
>,
75 /// A tuple struct like `Markdown` that renders only the first paragraph.
76 pub struct MarkdownSummaryLine
<'a
>(pub &'a
str, pub &'a
[(String
, String
)]);
78 #[derive(Copy, Clone, PartialEq, Debug)]
85 pub fn from(b
: bool
) -> Self {
87 true => ErrorCodes
::Yes
,
88 false => ErrorCodes
::No
,
92 pub fn as_bool(self) -> bool
{
94 ErrorCodes
::Yes
=> true,
95 ErrorCodes
::No
=> false,
100 /// Controls whether a line will be hidden or shown in HTML output.
102 /// All lines are used in documentation tests.
109 fn for_html(self) -> Option
<Cow
<'a
, str>> {
111 Line
::Shown(l
) => Some(l
),
112 Line
::Hidden(_
) => None
,
116 fn for_code(self) -> Cow
<'a
, str> {
119 Line
::Hidden(l
) => Cow
::Borrowed(l
),
124 // FIXME: There is a minor inconsistency here. For lines that start with ##, we
125 // have no easy way of removing a potential single space after the hashes, which
126 // is done in the single # case. This inconsistency seems okay, if non-ideal. In
127 // order to fix it we'd have to iterate to find the first non-# character, and
128 // then reallocate to remove it; which would make us return a String.
129 fn map_line(s
: &str) -> Line
<'_
> {
130 let trimmed
= s
.trim();
131 if trimmed
.starts_with("##") {
132 Line
::Shown(Cow
::Owned(s
.replacen("##", "#", 1)))
133 } else if trimmed
.starts_with("# ") {
135 Line
::Hidden(&trimmed
[2..])
136 } else if trimmed
== "#" {
137 // We cannot handle '#text' because it could be #[attr].
140 Line
::Shown(Cow
::Borrowed(s
))
144 /// Convert chars from a title for an id.
146 /// "Hello, world!" -> "hello-world"
147 fn slugify(c
: char) -> Option
<char> {
148 if c
.is_alphanumeric() || c
== '
-'
|| c
== '_'
{
149 if c
.is_ascii() { Some(c.to_ascii_lowercase()) }
else { Some(c) }
150 } else if c
.is_whitespace() && c
.is_ascii() {
157 #[derive(Clone, Debug)]
158 pub struct Playground
{
159 pub crate_name
: Option
<String
>,
163 /// Adds syntax highlighting and playground Run buttons to Rust code blocks.
164 struct CodeBlocks
<'p
, 'a
, I
: Iterator
<Item
= Event
<'a
>>> {
166 check_error_codes
: ErrorCodes
,
168 // Information about the playground if a URL has been specified, containing an
169 // optional crate name and the URL.
170 playground
: &'p Option
<Playground
>,
173 impl<'p
, 'a
, I
: Iterator
<Item
= Event
<'a
>>> CodeBlocks
<'p
, 'a
, I
> {
176 error_codes
: ErrorCodes
,
178 playground
: &'p Option
<Playground
>,
180 CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground }
184 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for CodeBlocks
<'_
, 'a
, I
> {
185 type Item
= Event
<'a
>;
187 fn next(&mut self) -> Option
<Self::Item
> {
188 let event
= self.inner
.next();
192 if let Some(Event
::Start(Tag
::CodeBlock(kind
))) = event
{
193 let parse_result
= match kind
{
194 CodeBlockKind
::Fenced(ref lang
) => {
195 LangString
::parse(&lang
, self.check_error_codes
, false)
197 CodeBlockKind
::Indented
=> LangString
::all_false(),
199 if !parse_result
.rust
{
200 return Some(Event
::Start(Tag
::CodeBlock(kind
)));
202 compile_fail
= parse_result
.compile_fail
;
203 ignore
= parse_result
.ignore
;
204 edition
= parse_result
.edition
;
209 let explicit_edition
= edition
.is_some();
210 let edition
= edition
.unwrap_or(self.edition
);
212 let mut origtext
= String
::new();
213 for event
in &mut self.inner
{
215 Event
::End(Tag
::CodeBlock(..)) => break,
216 Event
::Text(ref s
) => {
217 origtext
.push_str(s
);
222 let lines
= origtext
.lines().filter_map(|l
| map_line(l
).for_html());
223 let text
= lines
.collect
::<Vec
<Cow
<'_
, str>>>().join("\n");
224 // insert newline to clearly separate it from the
225 // previous block so we can shorten the html output
226 let mut s
= String
::from("\n");
227 let playground_button
= self.playground
.as_ref().and_then(|playground
| {
228 let krate
= &playground
.crate_name
;
229 let url
= &playground
.url
;
235 .map(|l
| map_line(l
).for_code())
236 .collect
::<Vec
<Cow
<'_
, str>>>()
238 let krate
= krate
.as_ref().map(|s
| &**s
);
239 let (test
, _
) = test
::make_test(&test
, krate
, false, &Default
::default(), edition
);
240 let channel
= if test
.contains("#![feature(") { "&version=nightly" }
else { "" }
;
242 let edition_string
= format
!("&edition={}", edition
);
244 // These characters don't need to be escaped in a URI.
245 // FIXME: use a library function for percent encoding.
246 fn dont_escape(c
: u8) -> bool
{
247 (b'a'
<= c
&& c
<= b'z'
)
248 || (b'A'
<= c
&& c
<= b'Z'
)
249 || (b'
0'
<= c
&& c
<= b'
9'
)
260 let mut test_escaped
= String
::new();
261 for b
in test
.bytes() {
263 test_escaped
.push(char::from(b
));
265 write
!(test_escaped
, "%{:02X}", b
).unwrap();
269 r
#"<a class="test-arrow" target="_blank" href="{}?code={}{}{}">Run</a>"#,
270 url
, test_escaped
, channel
, edition_string
274 let tooltip
= if ignore
!= Ignore
::None
{
275 Some(("This example is not tested".to_owned(), "ignore"))
276 } else if compile_fail
{
277 Some(("This example deliberately fails to compile".to_owned(), "compile_fail"))
278 } else if explicit_edition
{
279 Some((format
!("This code runs with edition {}", edition
), "edition"))
284 if let Some((s1
, s2
)) = tooltip
{
285 s
.push_str(&highlight
::render_with_highlighting(
288 "rust-example-rendered{}",
289 if ignore
!= Ignore
::None
{
291 } else if compile_fail
{
293 } else if explicit_edition
{
299 playground_button
.as_deref(),
300 Some((s1
.as_str(), s2
)),
302 Some(Event
::Html(s
.into()))
304 s
.push_str(&highlight
::render_with_highlighting(
307 "rust-example-rendered{}",
308 if ignore
!= Ignore
::None
{
310 } else if compile_fail
{
312 } else if explicit_edition
{
318 playground_button
.as_deref(),
321 Some(Event
::Html(s
.into()))
326 /// Make headings links with anchor IDs and build up TOC.
327 struct LinkReplacer
<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> {
329 links
: &'b
[(String
, String
)],
332 impl<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> LinkReplacer
<'a
, 'b
, I
> {
333 fn new(iter
: I
, links
: &'b
[(String
, String
)]) -> Self {
334 LinkReplacer { inner: iter, links }
338 impl<'a
, 'b
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for LinkReplacer
<'a
, 'b
, I
> {
339 type Item
= Event
<'a
>;
341 fn next(&mut self) -> Option
<Self::Item
> {
342 let event
= self.inner
.next();
343 if let Some(Event
::Start(Tag
::Link(kind
, dest
, text
))) = event
{
344 if let Some(&(_
, ref replace
)) = self.links
.iter().find(|link
| link
.0 == *dest
) {
345 Some(Event
::Start(Tag
::Link(kind
, replace
.to_owned().into(), text
)))
347 Some(Event
::Start(Tag
::Link(kind
, dest
, text
)))
355 /// Make headings links with anchor IDs and build up TOC.
356 struct HeadingLinks
<'a
, 'b
, 'ids
, I
: Iterator
<Item
= Event
<'a
>>> {
358 toc
: Option
<&'b
mut TocBuilder
>,
359 buf
: VecDeque
<Event
<'a
>>,
360 id_map
: &'ids
mut IdMap
,
363 impl<'a
, 'b
, 'ids
, I
: Iterator
<Item
= Event
<'a
>>> HeadingLinks
<'a
, 'b
, 'ids
, I
> {
364 fn new(iter
: I
, toc
: Option
<&'b
mut TocBuilder
>, ids
: &'ids
mut IdMap
) -> Self {
365 HeadingLinks { inner: iter, toc, buf: VecDeque::new(), id_map: ids }
369 impl<'a
, 'b
, 'ids
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for HeadingLinks
<'a
, 'b
, 'ids
, I
> {
370 type Item
= Event
<'a
>;
372 fn next(&mut self) -> Option
<Self::Item
> {
373 if let Some(e
) = self.buf
.pop_front() {
377 let event
= self.inner
.next();
378 if let Some(Event
::Start(Tag
::Heading(level
))) = event
{
379 let mut id
= String
::new();
380 for event
in &mut self.inner
{
382 Event
::End(Tag
::Heading(..)) => break,
383 Event
::Text(text
) | Event
::Code(text
) => {
384 id
.extend(text
.chars().filter_map(slugify
));
389 Event
::Start(Tag
::Link(_
, _
, _
)) | Event
::End(Tag
::Link(..)) => {}
390 event
=> self.buf
.push_back(event
),
393 let id
= self.id_map
.derive(id
);
395 if let Some(ref mut builder
) = self.toc
{
396 let mut html_header
= String
::new();
397 html
::push_html(&mut html_header
, self.buf
.iter().cloned());
398 let sec
= builder
.push(level
as u32, html_header
, id
.clone());
399 self.buf
.push_front(Event
::Html(format
!("{} ", sec
).into()));
402 self.buf
.push_back(Event
::Html(format
!("</a></h{}>", level
).into()));
404 let start_tags
= format
!(
405 "<h{level} id=\"{id}\" class=\"section-header\">\
410 return Some(Event
::Html(start_tags
.into()));
416 /// Extracts just the first paragraph.
417 struct SummaryLine
<'a
, I
: Iterator
<Item
= Event
<'a
>>> {
423 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> SummaryLine
<'a
, I
> {
424 fn new(iter
: I
) -> Self {
425 SummaryLine { inner: iter, started: false, depth: 0 }
429 fn check_if_allowed_tag(t
: &Tag
<'_
>) -> bool
{
436 | Tag
::BlockQuote
=> true,
441 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for SummaryLine
<'a
, I
> {
442 type Item
= Event
<'a
>;
444 fn next(&mut self) -> Option
<Self::Item
> {
445 if self.started
&& self.depth
== 0 {
451 if let Some(event
) = self.inner
.next() {
452 let mut is_start
= true;
453 let is_allowed_tag
= match event
{
454 Event
::Start(Tag
::CodeBlock(_
)) | Event
::End(Tag
::CodeBlock(_
)) => {
457 Event
::Start(ref c
) => {
459 check_if_allowed_tag(c
)
461 Event
::End(ref c
) => {
464 check_if_allowed_tag(c
)
468 return if !is_allowed_tag
{
470 Some(Event
::Start(Tag
::Paragraph
))
472 Some(Event
::End(Tag
::Paragraph
))
482 /// Moves all footnote definitions to the end and add back links to the
484 struct Footnotes
<'a
, I
: Iterator
<Item
= Event
<'a
>>> {
486 footnotes
: FxHashMap
<String
, (Vec
<Event
<'a
>>, u16)>,
489 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Footnotes
<'a
, I
> {
490 fn new(iter
: I
) -> Self {
491 Footnotes { inner: iter, footnotes: FxHashMap::default() }
493 fn get_entry(&mut self, key
: &str) -> &mut (Vec
<Event
<'a
>>, u16) {
494 let new_id
= self.footnotes
.keys().count() + 1;
495 let key
= key
.to_owned();
496 self.footnotes
.entry(key
).or_insert((Vec
::new(), new_id
as u16))
500 impl<'a
, I
: Iterator
<Item
= Event
<'a
>>> Iterator
for Footnotes
<'a
, I
> {
501 type Item
= Event
<'a
>;
503 fn next(&mut self) -> Option
<Self::Item
> {
505 match self.inner
.next() {
506 Some(Event
::FootnoteReference(ref reference
)) => {
507 let entry
= self.get_entry(&reference
);
508 let reference
= format
!(
509 "<sup id=\"fnref{0}\"><a href=\"#fn{0}\">{0}\
513 return Some(Event
::Html(reference
.into()));
515 Some(Event
::Start(Tag
::FootnoteDefinition(def
))) => {
516 let mut content
= Vec
::new();
517 for event
in &mut self.inner
{
518 if let Event
::End(Tag
::FootnoteDefinition(..)) = event
{
523 let entry
= self.get_entry(&def
);
524 (*entry
).0 = content
;
526 Some(e
) => return Some(e
),
528 if !self.footnotes
.is_empty() {
529 let mut v
: Vec
<_
> = self.footnotes
.drain().map(|(_
, x
)| x
).collect();
530 v
.sort_by(|a
, b
| a
.1.cmp(&b
.1));
531 let mut ret
= String
::from("<div class=\"footnotes\"><hr><ol>");
532 for (mut content
, id
) in v
{
533 write
!(ret
, "<li id=\"fn{}\">", id
).unwrap();
534 let mut is_paragraph
= false;
535 if let Some(&Event
::End(Tag
::Paragraph
)) = content
.last() {
539 html
::push_html(&mut ret
, content
.into_iter());
540 write
!(ret
, " <a href=\"#fnref{}\" rev=\"footnote\">↩</a>", id
)
543 ret
.push_str("</p>");
545 ret
.push_str("</li>");
547 ret
.push_str("</ol></div>");
548 return Some(Event
::Html(ret
.into()));
558 pub fn find_testable_code
<T
: test
::Tester
>(
561 error_codes
: ErrorCodes
,
562 enable_per_target_ignores
: bool
,
564 let mut parser
= Parser
::new(doc
).into_offset_iter();
565 let mut prev_offset
= 0;
566 let mut nb_lines
= 0;
567 let mut register_header
= None
;
568 while let Some((event
, offset
)) = parser
.next() {
570 Event
::Start(Tag
::CodeBlock(kind
)) => {
571 let block_info
= match kind
{
572 CodeBlockKind
::Fenced(ref lang
) => {
574 LangString
::all_false()
576 LangString
::parse(lang
, error_codes
, enable_per_target_ignores
)
579 CodeBlockKind
::Indented
=> LangString
::all_false(),
581 if !block_info
.rust
{
585 let mut test_s
= String
::new();
587 while let Some((Event
::Text(s
), _
)) = parser
.next() {
592 .map(|l
| map_line(l
).for_code())
593 .collect
::<Vec
<Cow
<'_
, str>>>()
596 nb_lines
+= doc
[prev_offset
..offset
.start
].lines().count();
597 let line
= tests
.get_line() + nb_lines
+ 1;
598 tests
.add_test(text
, block_info
, line
);
599 prev_offset
= offset
.start
;
601 Event
::Start(Tag
::Heading(level
)) => {
602 register_header
= Some(level
as u32);
604 Event
::Text(ref s
) if register_header
.is_some() => {
605 let level
= register_header
.unwrap();
607 tests
.register_header("", level
);
609 tests
.register_header(s
, level
);
611 register_header
= None
;
618 #[derive(Eq, PartialEq, Clone, Debug)]
619 pub struct LangString
{
621 pub should_panic
: bool
,
625 pub test_harness
: bool
,
626 pub compile_fail
: bool
,
627 pub error_codes
: Vec
<String
>,
628 pub allow_fail
: bool
,
629 pub edition
: Option
<Edition
>,
632 #[derive(Eq, PartialEq, Clone, Debug)]
640 fn all_false() -> LangString
{
642 original
: String
::new(),
645 ignore
: Ignore
::None
,
646 rust
: true, // NB This used to be `notrust = false`
649 error_codes
: Vec
::new(),
657 allow_error_code_check
: ErrorCodes
,
658 enable_per_target_ignores
: bool
,
660 let allow_error_code_check
= allow_error_code_check
.as_bool();
661 let mut seen_rust_tags
= false;
662 let mut seen_other_tags
= false;
663 let mut data
= LangString
::all_false();
664 let mut ignores
= vec
![];
666 data
.original
= string
.to_owned();
667 let tokens
= string
.split(|c
: char| !(c
== '_'
|| c
== '
-'
|| c
.is_alphanumeric()));
669 for token
in tokens
{
673 data
.should_panic
= true;
674 seen_rust_tags
= !seen_other_tags
;
678 seen_rust_tags
= !seen_other_tags
;
681 data
.ignore
= Ignore
::All
;
682 seen_rust_tags
= !seen_other_tags
;
684 x
if x
.starts_with("ignore-") => {
685 if enable_per_target_ignores
{
686 ignores
.push(x
.trim_start_matches("ignore-").to_owned());
687 seen_rust_tags
= !seen_other_tags
;
691 data
.allow_fail
= true;
692 seen_rust_tags
= !seen_other_tags
;
696 seen_rust_tags
= true;
699 data
.test_harness
= true;
700 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
703 data
.compile_fail
= true;
704 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
707 x
if x
.starts_with("edition") => {
708 data
.edition
= x
[7..].parse
::<Edition
>().ok();
710 x
if allow_error_code_check
&& x
.starts_with('E'
) && x
.len() == 5 => {
711 if x
[1..].parse
::<u32>().is_ok() {
712 data
.error_codes
.push(x
.to_owned());
713 seen_rust_tags
= !seen_other_tags
|| seen_rust_tags
;
715 seen_other_tags
= true;
718 _
=> seen_other_tags
= true,
721 // ignore-foo overrides ignore
722 if !ignores
.is_empty() {
723 data
.ignore
= Ignore
::Some(ignores
);
726 data
.rust
&= !seen_other_tags
|| seen_rust_tags
;
733 pub fn to_string(self) -> String
{
734 let Markdown(md
, links
, mut ids
, codes
, edition
, playground
) = self;
736 // This is actually common enough to special-case
738 return String
::new();
740 let replacer
= |_
: &str, s
: &str| {
741 if let Some(&(_
, ref replace
)) = links
.iter().find(|link
| &*link
.0 == s
) {
742 Some((replace
.clone(), s
.to_owned()))
748 let p
= Parser
::new_with_broken_link_callback(md
, opts(), Some(&replacer
));
750 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
752 let p
= HeadingLinks
::new(p
, None
, &mut ids
);
753 let p
= LinkReplacer
::new(p
, links
);
754 let p
= CodeBlocks
::new(p
, codes
, edition
, playground
);
755 let p
= Footnotes
::new(p
);
756 html
::push_html(&mut s
, p
);
762 impl MarkdownWithToc
<'_
> {
763 pub fn to_string(self) -> String
{
764 let MarkdownWithToc(md
, mut ids
, codes
, edition
, playground
) = self;
766 let p
= Parser
::new_ext(md
, opts());
768 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
770 let mut toc
= TocBuilder
::new();
773 let p
= HeadingLinks
::new(p
, Some(&mut toc
), &mut ids
);
774 let p
= CodeBlocks
::new(p
, codes
, edition
, playground
);
775 let p
= Footnotes
::new(p
);
776 html
::push_html(&mut s
, p
);
779 format
!("<nav id=\"TOC\">{}</nav>{}", toc
.into_toc().print(), s
)
783 impl MarkdownHtml
<'_
> {
784 pub fn to_string(self) -> String
{
785 let MarkdownHtml(md
, mut ids
, codes
, edition
, playground
) = self;
787 // This is actually common enough to special-case
789 return String
::new();
791 let p
= Parser
::new_ext(md
, opts());
793 // Treat inline HTML as plain text.
794 let p
= p
.map(|event
| match event
{
795 Event
::Html(text
) => Event
::Text(text
),
799 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
801 let p
= HeadingLinks
::new(p
, None
, &mut ids
);
802 let p
= CodeBlocks
::new(p
, codes
, edition
, playground
);
803 let p
= Footnotes
::new(p
);
804 html
::push_html(&mut s
, p
);
810 impl MarkdownSummaryLine
<'_
> {
811 pub fn to_string(self) -> String
{
812 let MarkdownSummaryLine(md
, links
) = self;
813 // This is actually common enough to special-case
815 return String
::new();
818 let replacer
= |_
: &str, s
: &str| {
819 if let Some(&(_
, ref replace
)) = links
.iter().find(|link
| &*link
.0 == s
) {
820 Some((replace
.clone(), s
.to_owned()))
826 let p
= Parser
::new_with_broken_link_callback(md
, Options
::empty(), Some(&replacer
));
828 let mut s
= String
::new();
830 html
::push_html(&mut s
, LinkReplacer
::new(SummaryLine
::new(p
), links
));
836 pub fn plain_summary_line(md
: &str) -> String
{
837 struct ParserWrapper
<'a
> {
843 impl<'a
> Iterator
for ParserWrapper
<'a
> {
846 fn next(&mut self) -> Option
<String
> {
847 let next_event
= self.inner
.next()?
;
848 let (ret
, is_in
) = match next_event
{
849 Event
::Start(Tag
::Paragraph
) => (None
, 1),
850 Event
::Start(Tag
::Heading(_
)) => (None
, 1),
851 Event
::Code(code
) => (Some(format
!("`{}`", code
)), 0),
852 Event
::Text(ref s
) if self.is_in
> 0 => (Some(s
.as_ref().to_owned()), 0),
853 Event
::End(Tag
::Paragraph
| Tag
::Heading(_
)) => (None
, -1),
856 if is_in
> 0 || (is_in
< 0 && self.is_in
> 0) {
860 self.is_first
= false;
867 let mut s
= String
::with_capacity(md
.len() * 3 / 2);
868 let p
= ParserWrapper { inner: Parser::new(md), is_in: 0, is_first: true }
;
869 p
.filter(|t
| !t
.is_empty()).for_each(|i
| s
.push_str(&i
));
873 pub fn markdown_links(md
: &str) -> Vec
<(String
, Option
<Range
<usize>>)> {
878 let mut links
= vec
![];
879 let shortcut_links
= RefCell
::new(vec
![]);
882 let locate
= |s
: &str| unsafe {
883 let s_start
= s
.as_ptr();
884 let s_end
= s_start
.add(s
.len());
885 let md_start
= md
.as_ptr();
886 let md_end
= md_start
.add(md
.len());
887 if md_start
<= s_start
&& s_end
<= md_end
{
888 let start
= s_start
.offset_from(md_start
) as usize;
889 let end
= s_end
.offset_from(md_start
) as usize;
896 let push
= |_
: &str, s
: &str| {
897 shortcut_links
.borrow_mut().push((s
.to_owned(), locate(s
)));
900 let p
= Parser
::new_with_broken_link_callback(md
, opts(), Some(&push
));
902 // There's no need to thread an IdMap through to here because
903 // the IDs generated aren't going to be emitted anywhere.
904 let mut ids
= IdMap
::new();
905 let iter
= Footnotes
::new(HeadingLinks
::new(p
, None
, &mut ids
));
908 if let Event
::Start(Tag
::Link(_
, dest
, _
)) = ev
{
909 debug
!("found link: {}", dest
);
910 links
.push(match dest
{
911 CowStr
::Borrowed(s
) => (s
.to_owned(), locate(s
)),
912 s @
(CowStr
::Boxed(..) | CowStr
::Inlined(..)) => (s
.into_string(), None
),
918 let mut shortcut_links
= shortcut_links
.into_inner();
919 links
.extend(shortcut_links
.drain(..));
925 crate struct RustCodeBlock
{
926 /// The range in the markdown that the code block occupies. Note that this includes the fences
927 /// for fenced code blocks.
928 pub range
: Range
<usize>,
929 /// The range in the markdown that the code within the code block occupies.
930 pub code
: Range
<usize>,
932 pub syntax
: Option
<String
>,
935 /// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
936 /// untagged (and assumed to be rust).
937 crate fn rust_code_blocks(md
: &str) -> Vec
<RustCodeBlock
> {
938 let mut code_blocks
= vec
![];
944 let mut p
= Parser
::new_ext(md
, opts()).into_offset_iter();
946 while let Some((event
, offset
)) = p
.next() {
947 if let Event
::Start(Tag
::CodeBlock(syntax
)) = event
{
948 let (syntax
, code_start
, code_end
, range
, is_fenced
) = match syntax
{
949 CodeBlockKind
::Fenced(syntax
) => {
950 let syntax
= syntax
.as_ref();
951 let lang_string
= if syntax
.is_empty() {
952 LangString
::all_false()
954 LangString
::parse(&*syntax
, ErrorCodes
::Yes
, false)
956 if !lang_string
.rust
{
959 let syntax
= if syntax
.is_empty() { None }
else { Some(syntax.to_owned()) }
;
960 let (code_start
, mut code_end
) = match p
.next() {
961 Some((Event
::Text(_
), offset
)) => (offset
.start
, offset
.end
),
962 Some((_
, sub_offset
)) => {
963 let code
= Range { start: sub_offset.start, end: sub_offset.start }
;
964 code_blocks
.push(RustCodeBlock
{
973 let code
= Range { start: offset.end, end: offset.end }
;
974 code_blocks
.push(RustCodeBlock
{
983 while let Some((Event
::Text(_
), offset
)) = p
.next() {
984 code_end
= offset
.end
;
986 (syntax
, code_start
, code_end
, offset
, true)
988 CodeBlockKind
::Indented
=> {
989 // The ending of the offset goes too far sometime so we reduce it by one in
991 if offset
.end
> offset
.start
&& md
.get(offset
.end
..=offset
.end
) == Some(&"\n") {
996 Range { start: offset.start, end: offset.end - 1 }
,
1000 (None
, offset
.start
, offset
.end
, offset
, false)
1005 code_blocks
.push(RustCodeBlock
{
1008 code
: Range { start: code_start, end: code_end }
,
1017 #[derive(Clone, Default, Debug)]
1019 map
: FxHashMap
<String
, usize>,
1023 pub fn new() -> Self {
1027 pub fn populate
<I
: IntoIterator
<Item
= String
>>(&mut self, ids
: I
) {
1029 let _
= self.derive(id
);
1033 pub fn reset(&mut self) {
1034 self.map
= FxHashMap
::default();
1037 pub fn derive(&mut self, candidate
: String
) -> String
{
1038 let id
= match self.map
.get_mut(&candidate
) {
1041 let id
= format
!("{}-{}", candidate
, *a
);
1047 self.map
.insert(id
.clone(), 1);