@@ -176,11 +176,17 @@ impl Element {
176176 other. content . iter ( ) . all ( |c| can_merge ( self . class , other. class , & c. text ) )
177177 }
178178
179- fn write_elem_to < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > , parent_class : Option < Class > ) {
179+ fn write_elem_to < W : Write > (
180+ & self ,
181+ out : & mut W ,
182+ href_context : & Option < HrefContext < ' _ , ' _ > > ,
183+ parent_class : Option < Class > ,
184+ ) {
180185 let mut prev = parent_class;
181186 let mut closing_tag = None ;
182187 for part in & self . content {
183- let text: & dyn Display = if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
188+ let text: & dyn Display =
189+ if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
184190 if part. class . is_some ( ) {
185191 // We only try to generate links as the `<span>` should have already be generated
186192 // by the caller of `write_elem_to`.
@@ -215,11 +221,18 @@ enum ElementOrStack {
215221 Stack ( ElementStack ) ,
216222}
217223
224+ /// This represents the stack of HTML elements. For example a macro expansion
225+ /// will contain other elements which might themselves contain other elements
226+ /// (like macros).
227+ ///
228+ /// This allows to easily handle HTML tags instead of having a more complicated
229+ /// state machine to keep track of which tags are open.
218230#[ derive( Debug ) ]
219231struct ElementStack {
220232 elements : Vec < ElementOrStack > ,
221233 parent : Option < Box < ElementStack > > ,
222234 class : Option < Class > ,
235+ pending_exit : bool ,
223236}
224237
225238impl ElementStack {
@@ -228,10 +241,15 @@ impl ElementStack {
228241 }
229242
230243 fn new_with_class ( class : Option < Class > ) -> Self {
231- Self { elements : Vec :: new ( ) , parent : None , class }
244+ Self { elements : Vec :: new ( ) , parent : None , class, pending_exit : false }
232245 }
233246
234247 fn push_element ( & mut self , mut elem : Element ) {
248+ if self . pending_exit
249+ && !can_merge ( self . class , elem. class , elem. content . first ( ) . map_or ( "" , |c| & c. text ) )
250+ {
251+ self . exit_current_stack ( ) ;
252+ }
235253 if let Some ( ElementOrStack :: Element ( last) ) = self . elements . last_mut ( )
236254 && last. can_merge ( & elem)
237255 {
@@ -254,41 +272,61 @@ impl ElementStack {
254272 }
255273 }
256274
257- fn enter_stack ( & mut self , ElementStack { elements, parent, class } : ElementStack ) {
275+ fn enter_stack (
276+ & mut self ,
277+ ElementStack { elements, parent, class, pending_exit } : ElementStack ,
278+ ) {
279+ if self . pending_exit {
280+ if can_merge ( self . class , class, "" ) {
281+ self . pending_exit = false ;
282+ for elem in elements {
283+ self . elements . push ( elem) ;
284+ }
285+ // Compatible stacks, nothing to be done here!
286+ return ;
287+ }
288+ self . exit_current_stack ( ) ;
289+ }
258290 assert ! ( parent. is_none( ) , "`enter_stack` used with a non empty parent" ) ;
259291 let parent_elements = std:: mem:: take ( & mut self . elements ) ;
260292 let parent_parent = std:: mem:: take ( & mut self . parent ) ;
261293 self . parent = Some ( Box :: new ( ElementStack {
262294 elements : parent_elements,
263295 parent : parent_parent,
264296 class : self . class ,
297+ pending_exit : self . pending_exit ,
265298 } ) ) ;
266299 self . class = class;
267300 self . elements = elements;
301+ self . pending_exit = pending_exit;
268302 }
269303
270- fn enter_elem ( & mut self , class : Class ) {
271- let elements = std :: mem :: take ( & mut self . elements ) ;
272- let parent = std :: mem :: take ( & mut self . parent ) ;
273- self . parent = Some ( Box :: new ( ElementStack { elements , parent , class : self . class } ) ) ;
274- self . class = Some ( class ) ;
304+ /// This sets the `pending_exit` field to `true`. Meaning that if we try to push another stack
305+ /// which is not compatible with this one, it will exit the current one before adding the new
306+ /// one.
307+ fn exit_elem ( & mut self ) {
308+ self . pending_exit = true ;
275309 }
276310
277- fn exit_elem ( & mut self ) {
311+ /// Unlike `exit_elem`, this method directly exits the current stack. It is called when the
312+ /// current stack is not compatible with a new one pushed or if an expansion was ended.
313+ fn exit_current_stack ( & mut self ) {
278314 let Some ( element) = std:: mem:: take ( & mut self . parent ) else {
279315 panic ! ( "exiting an element where there is no parent" ) ;
280316 } ;
281- let ElementStack { elements, parent, class } = Box :: into_inner ( element) ;
317+ let ElementStack { elements, parent, class, pending_exit } = Box :: into_inner ( element) ;
282318
283319 let old_elements = std:: mem:: take ( & mut self . elements ) ;
284320 self . elements = elements;
285321 self . elements . push ( ElementOrStack :: Stack ( ElementStack {
286322 elements : old_elements,
287323 class : self . class ,
288324 parent : None ,
325+ pending_exit : false ,
289326 } ) ) ;
290327 self . parent = parent;
291328 self . class = class;
329+ self . pending_exit = pending_exit;
292330 }
293331
294332 fn write_content < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > ) {
@@ -323,16 +361,18 @@ impl ElementStack {
323361 // we generate the `<a>` directly here.
324362 //
325363 // For other elements, the links will be generated in `write_elem_to`.
326- let href_context = if matches ! ( class, Class :: Macro ( _) ) {
327- href_context
328- } else {
329- & None
330- } ;
331- string_without_closing_tag ( out, "" , Some ( class) , href_context, self . class != parent_class)
332- . expect (
333- "internal error: enter_span was called with Some(class) but did not \
364+ let href_context = if matches ! ( class, Class :: Macro ( _) ) { href_context } else { & None } ;
365+ string_without_closing_tag (
366+ out,
367+ "" ,
368+ Some ( class) ,
369+ href_context,
370+ self . class != parent_class,
371+ )
372+ . expect (
373+ "internal error: enter_span was called with Some(class) but did not \
334374 return a closing HTML tag",
335- )
375+ )
336376 } else {
337377 ""
338378 } ;
@@ -444,7 +484,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
444484
445485 // We inline everything into the top-most element.
446486 while self . element_stack . parent . is_some ( ) {
447- self . element_stack . exit_elem ( ) ;
487+ self . element_stack . exit_current_stack ( ) ;
448488 if let Some ( ElementOrStack :: Stack ( stack) ) = self . element_stack . elements . last ( )
449489 && let Some ( class) = stack. class
450490 && class != Class :: Original
@@ -467,6 +507,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
467507impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
468508 /// When leaving, we need to flush all pending data to not have missing content.
469509 fn drop ( & mut self ) {
510+ // We need to clean the hierarchy before displaying it, otherwise the parents won't see
511+ // the last child.
512+ while self . element_stack . parent . is_some ( ) {
513+ self . element_stack . exit_current_stack ( ) ;
514+ }
470515 self . element_stack . write_content ( self . out , & self . href_context ) ;
471516 }
472517}
@@ -510,7 +555,7 @@ fn end_expansion<'a, W: Write>(
510555 expanded_codes : & ' a [ ExpandedCode ] ,
511556 span : Span ,
512557) -> Option < & ' a ExpandedCode > {
513- token_handler. element_stack . exit_elem ( ) ;
558+ token_handler. element_stack . exit_current_stack ( ) ;
514559 let expansion = get_next_expansion ( expanded_codes, token_handler. line , span) ;
515560 if expansion. is_none ( ) {
516561 token_handler. close_expansion ( ) ;
@@ -628,7 +673,9 @@ pub(super) fn write_code(
628673 }
629674 }
630675 }
631- Highlight :: EnterSpan { class } => token_handler. element_stack . enter_elem ( class) ,
676+ Highlight :: EnterSpan { class } => {
677+ token_handler. element_stack . enter_stack ( ElementStack :: new_with_class ( Some ( class) ) )
678+ }
632679 Highlight :: ExitSpan => token_handler. element_stack . exit_elem ( ) ,
633680 } ) ;
634681}
0 commit comments