@@ -34,6 +34,7 @@ use std::fmt::Write;
34
34
use std:: ops:: Range ;
35
35
use std:: str;
36
36
37
+ use crate :: clean:: RenderedLink ;
37
38
use crate :: doctest;
38
39
use crate :: html:: highlight;
39
40
use crate :: html:: toc:: TocBuilder ;
@@ -52,7 +53,7 @@ fn opts() -> Options {
52
53
pub struct Markdown < ' a > (
53
54
pub & ' a str ,
54
55
/// A list of link replacements.
55
- pub & ' a [ ( String , String ) ] ,
56
+ pub & ' a [ RenderedLink ] ,
56
57
/// The current list of used header IDs.
57
58
pub & ' a mut IdMap ,
58
59
/// Whether to allow the use of explicit error codes in doctest lang strings.
@@ -78,7 +79,7 @@ pub struct MarkdownHtml<'a>(
78
79
pub & ' a Option < Playground > ,
79
80
) ;
80
81
/// 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 ] ) ;
82
83
83
84
#[ derive( Copy , Clone , PartialEq , Debug ) ]
84
85
pub enum ErrorCodes {
@@ -337,31 +338,107 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
337
338
}
338
339
339
340
/// 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 > > > {
341
342
inner : I ,
342
- links : & ' b [ ( String , String ) ] ,
343
+ links : & ' a [ RenderedLink ] ,
344
+ shortcut_link : Option < & ' a RenderedLink > ,
343
345
}
344
346
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 }
348
350
}
349
351
}
350
352
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 > {
352
354
type Item = Event < ' a > ;
353
355
354
356
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
+ }
361
382
}
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
+ _ => { }
364
438
}
439
+
440
+ // Yield the modified event
441
+ event
365
442
}
366
443
}
367
444
@@ -855,8 +932,8 @@ impl Markdown<'_> {
855
932
return String :: new ( ) ;
856
933
}
857
934
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 ( ) ) )
860
937
} else {
861
938
None
862
939
}
@@ -933,8 +1010,8 @@ impl MarkdownSummaryLine<'_> {
933
1010
}
934
1011
935
1012
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 ( ) ) )
938
1015
} else {
939
1016
None
940
1017
}
0 commit comments