@@ -34,6 +34,7 @@ use std::fmt::Write;
3434use std:: ops:: Range ;
3535use std:: str;
3636
37+ use crate :: clean:: RenderedLink ;
3738use crate :: doctest;
3839use crate :: html:: highlight;
3940use crate :: html:: toc:: TocBuilder ;
@@ -52,7 +53,7 @@ fn opts() -> Options {
5253pub struct Markdown < ' a > (
5354 pub & ' a str ,
5455 /// A list of link replacements.
55- pub & ' a [ ( String , String ) ] ,
56+ pub & ' a [ RenderedLink ] ,
5657 /// The current list of used header IDs.
5758 pub & ' a mut IdMap ,
5859 /// Whether to allow the use of explicit error codes in doctest lang strings.
@@ -78,7 +79,7 @@ pub struct MarkdownHtml<'a>(
7879 pub & ' a Option < Playground > ,
7980) ;
8081/// A tuple struct like `Markdown` that renders only the first paragraph.
81- pub struct MarkdownSummaryLine < ' a > ( pub & ' a str , pub & ' a [ ( String , String ) ] ) ;
82+ pub struct MarkdownSummaryLine < ' a > ( pub & ' a str , pub & ' a [ RenderedLink ] ) ;
8283
8384#[ derive( Copy , Clone , PartialEq , Debug ) ]
8485pub enum ErrorCodes {
@@ -337,31 +338,107 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
337338}
338339
339340/// Make headings links with anchor IDs and build up TOC.
340- struct LinkReplacer < ' a , ' b , I : Iterator < Item = Event < ' a > > > {
341+ struct LinkReplacer < ' a , I : Iterator < Item = Event < ' a > > > {
341342 inner : I ,
342- links : & ' b [ ( String , String ) ] ,
343+ links : & ' a [ RenderedLink ] ,
344+ shortcut_link : Option < & ' a RenderedLink > ,
343345}
344346
345- impl < ' a , ' b , I : Iterator < Item = Event < ' a > > > LinkReplacer < ' a , ' b , I > {
346- fn new ( iter : I , links : & ' b [ ( String , String ) ] ) -> Self {
347- LinkReplacer { inner : iter, links }
347+ impl < ' a , I : Iterator < Item = Event < ' a > > > LinkReplacer < ' a , I > {
348+ fn new ( iter : I , links : & ' a [ RenderedLink ] ) -> Self {
349+ LinkReplacer { inner : iter, links, shortcut_link : None }
348350 }
349351}
350352
351- impl < ' a , ' b , I : Iterator < Item = Event < ' a > > > Iterator for LinkReplacer < ' a , ' b , I > {
353+ impl < ' a , I : Iterator < Item = Event < ' a > > > Iterator for LinkReplacer < ' a , I > {
352354 type Item = Event < ' a > ;
353355
354356 fn next ( & mut self ) -> Option < Self :: Item > {
355- let event = self . inner . next ( ) ;
356- if let Some ( Event :: Start ( Tag :: Link ( kind, dest, text) ) ) = event {
357- if let Some ( & ( _, ref replace) ) = self . links . iter ( ) . find ( |link| link. 0 == * dest) {
358- Some ( Event :: Start ( Tag :: Link ( kind, replace. to_owned ( ) . into ( ) , text) ) )
359- } else {
360- Some ( Event :: Start ( Tag :: Link ( kind, dest, text) ) )
357+ use pulldown_cmark:: LinkType ;
358+
359+ let mut event = self . inner . next ( ) ;
360+
361+ // Replace intra-doc links and remove disambiguators from shortcut links (`[fn@f]`).
362+ match & mut event {
363+ // This is a shortcut link that was resolved by the broken_link_callback: `[fn@f]`
364+ // Remove any disambiguator.
365+ Some ( Event :: Start ( Tag :: Link (
366+ // [fn@f] or [fn@f][]
367+ LinkType :: ShortcutUnknown | LinkType :: CollapsedUnknown ,
368+ dest,
369+ title,
370+ ) ) ) => {
371+ debug ! ( "saw start of shortcut link to {} with title {}" , dest, title) ;
372+ // If this is a shortcut link, it was resolved by the broken_link_callback.
373+ // So the URL will already be updated properly.
374+ let link = self . links . iter ( ) . find ( |& link| * link. href == * * dest) ;
375+ // Since this is an external iterator, we can't replace the inner text just yet.
376+ // Store that we saw a link so we know to replace it later.
377+ if let Some ( link) = link {
378+ trace ! ( "it matched" ) ;
379+ assert ! ( self . shortcut_link. is_none( ) , "shortcut links cannot be nested" ) ;
380+ self . shortcut_link = Some ( link) ;
381+ }
361382 }
362- } else {
363- event
383+ // Now that we're done with the shortcut link, don't replace any more text.
384+ Some ( Event :: End ( Tag :: Link (
385+ LinkType :: ShortcutUnknown | LinkType :: CollapsedUnknown ,
386+ dest,
387+ _,
388+ ) ) ) => {
389+ debug ! ( "saw end of shortcut link to {}" , dest) ;
390+ if self . links . iter ( ) . find ( |& link| * link. href == * * dest) . is_some ( ) {
391+ assert ! ( self . shortcut_link. is_some( ) , "saw closing link without opening tag" ) ;
392+ self . shortcut_link = None ;
393+ }
394+ }
395+ // Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link.
396+ // [`fn@f`]
397+ Some ( Event :: Code ( text) ) => {
398+ trace ! ( "saw code {}" , text) ;
399+ if let Some ( link) = self . shortcut_link {
400+ trace ! ( "original text was {}" , link. original_text) ;
401+ // NOTE: this only replaces if the code block is the *entire* text.
402+ // If only part of the link has code highlighting, the disambiguator will not be removed.
403+ // e.g. [fn@`f`]
404+ // This is a limitation from `collect_intra_doc_links`: it passes a full link,
405+ // and does not distinguish at all between code blocks.
406+ // So we could never be sure we weren't replacing too much:
407+ // [fn@my_`f`unc] is treated the same as [my_func()] in that pass.
408+ //
409+ // NOTE: &[1..len() - 1] is to strip the backticks
410+ if * * text == link. original_text [ 1 ..link. original_text . len ( ) - 1 ] {
411+ debug ! ( "replacing {} with {}" , text, link. new_text) ;
412+ * text = CowStr :: Borrowed ( & link. new_text ) ;
413+ }
414+ }
415+ }
416+ // Replace plain text in links, but only in the middle of a shortcut link.
417+ // [fn@f]
418+ Some ( Event :: Text ( text) ) => {
419+ trace ! ( "saw text {}" , text) ;
420+ if let Some ( link) = self . shortcut_link {
421+ trace ! ( "original text was {}" , link. original_text) ;
422+ // NOTE: same limitations as `Event::Code`
423+ if * * text == * link. original_text {
424+ debug ! ( "replacing {} with {}" , text, link. new_text) ;
425+ * text = CowStr :: Borrowed ( & link. new_text ) ;
426+ }
427+ }
428+ }
429+ // If this is a link, but not a shortcut link,
430+ // replace the URL, since the broken_link_callback was not called.
431+ Some ( Event :: Start ( Tag :: Link ( _, dest, _) ) ) => {
432+ if let Some ( link) = self . links . iter ( ) . find ( |& link| * link. original_text == * * dest) {
433+ * dest = CowStr :: Borrowed ( link. href . as_ref ( ) ) ;
434+ }
435+ }
436+ // Anything else couldn't have been a valid Rust path, so no need to replace the text.
437+ _ => { }
364438 }
439+
440+ // Yield the modified event
441+ event
365442 }
366443}
367444
@@ -855,8 +932,8 @@ impl Markdown<'_> {
855932 return String :: new ( ) ;
856933 }
857934 let replacer = |_: & str , s : & str | {
858- if let Some ( & ( _ , ref replace ) ) = links. iter ( ) . find ( |link| & * link. 0 == s) {
859- Some ( ( replace . clone ( ) , s . to_owned ( ) ) )
935+ if let Some ( link ) = links. iter ( ) . find ( |link| & * link. original_text == s) {
936+ Some ( ( link . href . clone ( ) , link . new_text . clone ( ) ) )
860937 } else {
861938 None
862939 }
@@ -933,8 +1010,8 @@ impl MarkdownSummaryLine<'_> {
9331010 }
9341011
9351012 let replacer = |_: & str , s : & str | {
936- if let Some ( & ( _ , ref replace ) ) = links. iter ( ) . find ( |link| & * link. 0 == s) {
937- Some ( ( replace . clone ( ) , s . to_owned ( ) ) )
1013+ if let Some ( link ) = links. iter ( ) . find ( |link| & * link. original_text == s) {
1014+ Some ( ( link . href . clone ( ) , link . new_text . clone ( ) ) )
9381015 } else {
9391016 None
9401017 }
0 commit comments