@@ -32,6 +32,8 @@ use std::iter::Peekable;
3232use std:: ops:: { ControlFlow , Range } ;
3333use std:: path:: PathBuf ;
3434use std:: str:: { self , CharIndices } ;
35+ use std:: sync:: atomic:: AtomicUsize ;
36+ use std:: sync:: { Arc , Weak } ;
3537
3638use pulldown_cmark:: {
3739 BrokenLink , CodeBlockKind , CowStr , Event , LinkType , Options , Parser , Tag , TagEnd , html,
@@ -1301,8 +1303,20 @@ impl LangString {
13011303 }
13021304}
13031305
1304- impl Markdown < ' _ > {
1306+ impl < ' a > Markdown < ' a > {
13051307 pub fn into_string ( self ) -> String {
1308+ // This is actually common enough to special-case
1309+ if self . content . is_empty ( ) {
1310+ return String :: new ( ) ;
1311+ }
1312+
1313+ let mut s = String :: with_capacity ( self . content . len ( ) * 3 / 2 ) ;
1314+ html:: push_html ( & mut s, self . into_iter ( ) ) ;
1315+
1316+ s
1317+ }
1318+
1319+ fn into_iter ( self ) -> CodeBlocks < ' a , ' a , impl Iterator < Item = Event < ' a > > > {
13061320 let Markdown {
13071321 content : md,
13081322 links,
@@ -1313,32 +1327,72 @@ impl Markdown<'_> {
13131327 heading_offset,
13141328 } = self ;
13151329
1316- // This is actually common enough to special-case
1317- if md. is_empty ( ) {
1318- return String :: new ( ) ;
1319- }
1320- let mut replacer = |broken_link : BrokenLink < ' _ > | {
1330+ let replacer = move |broken_link : BrokenLink < ' _ > | {
13211331 links
13221332 . iter ( )
13231333 . find ( |link| * link. original_text == * broken_link. reference )
13241334 . map ( |link| ( link. href . as_str ( ) . into ( ) , link. tooltip . as_str ( ) . into ( ) ) )
13251335 } ;
13261336
1327- let p = Parser :: new_with_broken_link_callback ( md, main_body_opts ( ) , Some ( & mut replacer) ) ;
1337+ let p = Parser :: new_with_broken_link_callback ( md, main_body_opts ( ) , Some ( replacer) ) ;
13281338 let p = p. into_offset_iter ( ) ;
13291339
1330- let mut s = String :: with_capacity ( md. len ( ) * 3 / 2 ) ;
1331-
13321340 ids. handle_footnotes ( |ids, existing_footnotes| {
13331341 let p = HeadingLinks :: new ( p, None , ids, heading_offset) ;
13341342 let p = footnotes:: Footnotes :: new ( p, existing_footnotes) ;
13351343 let p = LinkReplacer :: new ( p. map ( |( ev, _) | ev) , links) ;
13361344 let p = TableWrapper :: new ( p) ;
1337- let p = CodeBlocks :: new ( p, codes, edition, playground) ;
1338- html :: push_html ( & mut s , p ) ;
1339- } ) ;
1345+ CodeBlocks :: new ( p, codes, edition, playground)
1346+ } )
1347+ }
13401348
1341- s
1349+ /// Convert markdown to (summary, remaining) HTML.
1350+ ///
1351+ /// - The summary is the first top-level Markdown element (usually a paragraph, but potentially
1352+ /// any block).
1353+ /// - The remaining docs contain everything after the summary.
1354+ pub ( crate ) fn split_summary_and_content ( self ) -> ( Option < String > , Option < String > ) {
1355+ if self . content . is_empty ( ) {
1356+ return ( None , None ) ;
1357+ }
1358+ let mut p = self . into_iter ( ) ;
1359+
1360+ let mut event_level = 0 ;
1361+ let mut summary_events = Vec :: new ( ) ;
1362+ let mut get_next_tag = false ;
1363+
1364+ let mut end_of_summary = false ;
1365+ while let Some ( event) = p. next ( ) {
1366+ match event {
1367+ Event :: Start ( _) => event_level += 1 ,
1368+ Event :: End ( kind) => {
1369+ event_level -= 1 ;
1370+ if event_level == 0 {
1371+ // We're back at the "top" so it means we're done with the summary.
1372+ end_of_summary = true ;
1373+ // We surround tables with `<div>` HTML tags so this is a special case.
1374+ get_next_tag = kind == TagEnd :: Table ;
1375+ }
1376+ }
1377+ _ => { }
1378+ }
1379+ summary_events. push ( event) ;
1380+ if end_of_summary {
1381+ if get_next_tag && let Some ( event) = p. next ( ) {
1382+ summary_events. push ( event) ;
1383+ }
1384+ break ;
1385+ }
1386+ }
1387+ let mut summary = String :: new ( ) ;
1388+ html:: push_html ( & mut summary, summary_events. into_iter ( ) ) ;
1389+ if summary. is_empty ( ) {
1390+ return ( None , None ) ;
1391+ }
1392+ let mut content = String :: new ( ) ;
1393+ html:: push_html ( & mut content, p) ;
1394+
1395+ if content. is_empty ( ) { ( Some ( summary) , None ) } else { ( Some ( summary) , Some ( content) ) }
13421396 }
13431397}
13441398
@@ -1882,7 +1936,7 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<Rust
18821936#[ derive( Clone , Default , Debug ) ]
18831937pub struct IdMap {
18841938 map : FxHashMap < String , usize > ,
1885- existing_footnotes : usize ,
1939+ existing_footnotes : Arc < AtomicUsize > ,
18861940}
18871941
18881942fn is_default_id ( id : & str ) -> bool {
@@ -1942,7 +1996,7 @@ fn is_default_id(id: &str) -> bool {
19421996
19431997impl IdMap {
19441998 pub fn new ( ) -> Self {
1945- IdMap { map : FxHashMap :: default ( ) , existing_footnotes : 0 }
1999+ IdMap { map : FxHashMap :: default ( ) , existing_footnotes : Arc :: new ( AtomicUsize :: new ( 0 ) ) }
19462000 }
19472001
19482002 pub ( crate ) fn derive < S : AsRef < str > + ToString > ( & mut self , candidate : S ) -> String {
@@ -1970,15 +2024,17 @@ impl IdMap {
19702024
19712025 /// Method to handle `existing_footnotes` increment automatically (to prevent forgetting
19722026 /// about it).
1973- pub ( crate ) fn handle_footnotes < F : FnOnce ( & mut Self , & mut usize ) > ( & mut self , closure : F ) {
1974- let mut existing_footnotes = self . existing_footnotes ;
2027+ pub ( crate ) fn handle_footnotes < ' a , T , F : FnOnce ( & ' a mut Self , Weak < AtomicUsize > ) -> T > (
2028+ & ' a mut self ,
2029+ closure : F ,
2030+ ) -> T {
2031+ let existing_footnotes = Arc :: downgrade ( & self . existing_footnotes ) ;
19752032
1976- closure ( self , & mut existing_footnotes) ;
1977- self . existing_footnotes = existing_footnotes;
2033+ closure ( self , existing_footnotes)
19782034 }
19792035
19802036 pub ( crate ) fn clear ( & mut self ) {
19812037 self . map . clear ( ) ;
1982- self . existing_footnotes = 0 ;
2038+ self . existing_footnotes = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
19832039 }
19842040}
0 commit comments