@@ -158,11 +158,17 @@ impl Element {
158158 other. content . iter ( ) . all ( |c| can_merge ( self . class , other. class , & c. text ) )
159159 }
160160
161- fn write_elem_to < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > , parent_class : Option < Class > ) {
161+ fn write_elem_to < W : Write > (
162+ & self ,
163+ out : & mut W ,
164+ href_context : & Option < HrefContext < ' _ , ' _ > > ,
165+ parent_class : Option < Class > ,
166+ ) {
162167 let mut prev = parent_class;
163168 let mut closing_tag = None ;
164169 for part in & self . content {
165- let text: & dyn Display = if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
170+ let text: & dyn Display =
171+ if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
166172 if part. class . is_some ( ) {
167173 // We only try to generate links as the `<span>` should have already be generated
168174 // by the caller of `write_elem_to`.
@@ -197,11 +203,18 @@ enum ElementOrStack {
197203 Stack ( ElementStack ) ,
198204}
199205
206+ /// This represents the stack of HTML elements. For example a macro expansion
207+ /// will contain other elements which might themselves contain other elements
208+ /// (like macros).
209+ ///
210+ /// This allows to easily handle HTML tags instead of having a more complicated
211+ /// state machine to keep track of which tags are open.
200212#[ derive( Debug ) ]
201213struct ElementStack {
202214 elements : Vec < ElementOrStack > ,
203215 parent : Option < Box < ElementStack > > ,
204216 class : Option < Class > ,
217+ pending_exit : bool ,
205218}
206219
207220impl ElementStack {
@@ -210,10 +223,15 @@ impl ElementStack {
210223 }
211224
212225 fn new_with_class ( class : Option < Class > ) -> Self {
213- Self { elements : Vec :: new ( ) , parent : None , class }
226+ Self { elements : Vec :: new ( ) , parent : None , class, pending_exit : false }
214227 }
215228
216229 fn push_element ( & mut self , mut elem : Element ) {
230+ if self . pending_exit
231+ && !can_merge ( self . class , elem. class , elem. content . first ( ) . map_or ( "" , |c| & c. text ) )
232+ {
233+ self . exit_current_stack ( ) ;
234+ }
217235 if let Some ( ElementOrStack :: Element ( last) ) = self . elements . last_mut ( )
218236 && last. can_merge ( & elem)
219237 {
@@ -236,41 +254,61 @@ impl ElementStack {
236254 }
237255 }
238256
239- fn enter_stack ( & mut self , ElementStack { elements, parent, class } : ElementStack ) {
257+ fn enter_stack (
258+ & mut self ,
259+ ElementStack { elements, parent, class, pending_exit } : ElementStack ,
260+ ) {
261+ if self . pending_exit {
262+ if can_merge ( self . class , class, "" ) {
263+ self . pending_exit = false ;
264+ for elem in elements {
265+ self . elements . push ( elem) ;
266+ }
267+ // Compatible stacks, nothing to be done here!
268+ return ;
269+ }
270+ self . exit_current_stack ( ) ;
271+ }
240272 assert ! ( parent. is_none( ) , "`enter_stack` used with a non empty parent" ) ;
241273 let parent_elements = std:: mem:: take ( & mut self . elements ) ;
242274 let parent_parent = std:: mem:: take ( & mut self . parent ) ;
243275 self . parent = Some ( Box :: new ( ElementStack {
244276 elements : parent_elements,
245277 parent : parent_parent,
246278 class : self . class ,
279+ pending_exit : self . pending_exit ,
247280 } ) ) ;
248281 self . class = class;
249282 self . elements = elements;
283+ self . pending_exit = pending_exit;
250284 }
251285
252- fn enter_elem ( & mut self , class : Class ) {
253- let elements = std :: mem :: take ( & mut self . elements ) ;
254- let parent = std :: mem :: take ( & mut self . parent ) ;
255- self . parent = Some ( Box :: new ( ElementStack { elements , parent , class : self . class } ) ) ;
256- self . class = Some ( class ) ;
286+ /// This sets the `pending_exit` field to `true`. Meaning that if we try to push another stack
287+ /// which is not compatible with this one, it will exit the current one before adding the new
288+ /// one.
289+ fn exit_elem ( & mut self ) {
290+ self . pending_exit = true ;
257291 }
258292
259- fn exit_elem ( & mut self ) {
293+ /// Unlike `exit_elem`, this method directly exits the current stack. It is called when the
294+ /// current stack is not compatible with a new one pushed or if an expansion was ended.
295+ fn exit_current_stack ( & mut self ) {
260296 let Some ( element) = std:: mem:: take ( & mut self . parent ) else {
261297 panic ! ( "exiting an element where there is no parent" ) ;
262298 } ;
263- let ElementStack { elements, parent, class } = Box :: into_inner ( element) ;
299+ let ElementStack { elements, parent, class, pending_exit } = Box :: into_inner ( element) ;
264300
265301 let old_elements = std:: mem:: take ( & mut self . elements ) ;
266302 self . elements = elements;
267303 self . elements . push ( ElementOrStack :: Stack ( ElementStack {
268304 elements : old_elements,
269305 class : self . class ,
270306 parent : None ,
307+ pending_exit : false ,
271308 } ) ) ;
272309 self . parent = parent;
273310 self . class = class;
311+ self . pending_exit = pending_exit;
274312 }
275313
276314 fn write_content < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > ) {
@@ -305,16 +343,18 @@ impl ElementStack {
305343 // we generate the `<a>` directly here.
306344 //
307345 // For other elements, the links will be generated in `write_elem_to`.
308- let href_context = if matches ! ( class, Class :: Macro ( _) ) {
309- href_context
310- } else {
311- & None
312- } ;
313- string_without_closing_tag ( out, "" , Some ( class) , href_context, self . class != parent_class)
314- . expect (
315- "internal error: enter_span was called with Some(class) but did not \
346+ let href_context = if matches ! ( class, Class :: Macro ( _) ) { href_context } else { & None } ;
347+ string_without_closing_tag (
348+ out,
349+ "" ,
350+ Some ( class) ,
351+ href_context,
352+ self . class != parent_class,
353+ )
354+ . expect (
355+ "internal error: enter_span was called with Some(class) but did not \
316356 return a closing HTML tag",
317- )
357+ )
318358 } else {
319359 ""
320360 } ;
@@ -426,7 +466,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
426466
427467 // We inline everything into the top-most element.
428468 while self . element_stack . parent . is_some ( ) {
429- self . element_stack . exit_elem ( ) ;
469+ self . element_stack . exit_current_stack ( ) ;
430470 if let Some ( ElementOrStack :: Stack ( stack) ) = self . element_stack . elements . last ( )
431471 && let Some ( class) = stack. class
432472 && class != Class :: Original
@@ -449,6 +489,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
449489impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
450490 /// When leaving, we need to flush all pending data to not have missing content.
451491 fn drop ( & mut self ) {
492+ // We need to clean the hierarchy before displaying it, otherwise the parents won't see
493+ // the last child.
494+ while self . element_stack . parent . is_some ( ) {
495+ self . element_stack . exit_current_stack ( ) ;
496+ }
452497 self . element_stack . write_content ( self . out , & self . href_context ) ;
453498 }
454499}
@@ -492,7 +537,7 @@ fn end_expansion<'a, W: Write>(
492537 expanded_codes : & ' a [ ExpandedCode ] ,
493538 span : Span ,
494539) -> Option < & ' a ExpandedCode > {
495- token_handler. element_stack . exit_elem ( ) ;
540+ token_handler. element_stack . exit_current_stack ( ) ;
496541 let expansion = get_next_expansion ( expanded_codes, token_handler. line , span) ;
497542 if expansion. is_none ( ) {
498543 token_handler. close_expansion ( ) ;
@@ -610,7 +655,9 @@ pub(super) fn write_code(
610655 }
611656 }
612657 }
613- Highlight :: EnterSpan { class } => token_handler. element_stack . enter_elem ( class) ,
658+ Highlight :: EnterSpan { class } => {
659+ token_handler. element_stack . enter_stack ( ElementStack :: new_with_class ( Some ( class) ) )
660+ }
614661 Highlight :: ExitSpan => token_handler. element_stack . exit_elem ( ) ,
615662 } ) ;
616663}
0 commit comments