@@ -159,11 +159,17 @@ impl Element {
159159 other. content . iter ( ) . all ( |c| can_merge ( self . class , other. class , & c. text ) )
160160 }
161161
162- fn write_elem_to < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > , parent_class : Option < Class > ) {
162+ fn write_elem_to < W : Write > (
163+ & self ,
164+ out : & mut W ,
165+ href_context : & Option < HrefContext < ' _ , ' _ > > ,
166+ parent_class : Option < Class > ,
167+ ) {
163168 let mut prev = parent_class;
164169 let mut closing_tag = None ;
165170 for part in & self . content {
166- let text: & dyn Display = if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
171+ let text: & dyn Display =
172+ if part. needs_escape { & EscapeBodyText ( & part. text ) } else { & part. text } ;
167173 if part. class . is_some ( ) {
168174 // We only try to generate links as the `<span>` should have already be generated
169175 // by the caller of `write_elem_to`.
@@ -198,11 +204,18 @@ enum ElementOrStack {
198204 Stack ( ElementStack ) ,
199205}
200206
207+ /// This represents the stack of HTML elements. For example a macro expansion
208+ /// will contain other elements which might themselves contain other elements
209+ /// (like macros).
210+ ///
211+ /// This allows to easily handle HTML tags instead of having a more complicated
212+ /// state machine to keep track of which tags are open.
201213#[ derive( Debug ) ]
202214struct ElementStack {
203215 elements : Vec < ElementOrStack > ,
204216 parent : Option < Box < ElementStack > > ,
205217 class : Option < Class > ,
218+ pending_exit : bool ,
206219}
207220
208221impl ElementStack {
@@ -211,10 +224,15 @@ impl ElementStack {
211224 }
212225
213226 fn new_with_class ( class : Option < Class > ) -> Self {
214- Self { elements : Vec :: new ( ) , parent : None , class }
227+ Self { elements : Vec :: new ( ) , parent : None , class, pending_exit : false }
215228 }
216229
217230 fn push_element ( & mut self , mut elem : Element ) {
231+ if self . pending_exit
232+ && !can_merge ( self . class , elem. class , elem. content . first ( ) . map_or ( "" , |c| & c. text ) )
233+ {
234+ self . exit_current_stack ( ) ;
235+ }
218236 if let Some ( ElementOrStack :: Element ( last) ) = self . elements . last_mut ( )
219237 && last. can_merge ( & elem)
220238 {
@@ -237,41 +255,61 @@ impl ElementStack {
237255 }
238256 }
239257
240- fn enter_stack ( & mut self , ElementStack { elements, parent, class } : ElementStack ) {
258+ fn enter_stack (
259+ & mut self ,
260+ ElementStack { elements, parent, class, pending_exit } : ElementStack ,
261+ ) {
262+ if self . pending_exit {
263+ if can_merge ( self . class , class, "" ) {
264+ self . pending_exit = false ;
265+ for elem in elements {
266+ self . elements . push ( elem) ;
267+ }
268+ // Compatible stacks, nothing to be done here!
269+ return ;
270+ }
271+ self . exit_current_stack ( ) ;
272+ }
241273 assert ! ( parent. is_none( ) , "`enter_stack` used with a non empty parent" ) ;
242274 let parent_elements = std:: mem:: take ( & mut self . elements ) ;
243275 let parent_parent = std:: mem:: take ( & mut self . parent ) ;
244276 self . parent = Some ( Box :: new ( ElementStack {
245277 elements : parent_elements,
246278 parent : parent_parent,
247279 class : self . class ,
280+ pending_exit : self . pending_exit ,
248281 } ) ) ;
249282 self . class = class;
250283 self . elements = elements;
284+ self . pending_exit = pending_exit;
251285 }
252286
253- fn enter_elem ( & mut self , class : Class ) {
254- let elements = std :: mem :: take ( & mut self . elements ) ;
255- let parent = std :: mem :: take ( & mut self . parent ) ;
256- self . parent = Some ( Box :: new ( ElementStack { elements , parent , class : self . class } ) ) ;
257- self . class = Some ( class ) ;
287+ /// This sets the `pending_exit` field to `true`. Meaning that if we try to push another stack
288+ /// which is not compatible with this one, it will exit the current one before adding the new
289+ /// one.
290+ fn exit_elem ( & mut self ) {
291+ self . pending_exit = true ;
258292 }
259293
260- fn exit_elem ( & mut self ) {
294+ /// Unlike `exit_elem`, this method directly exits the current stack. It is called when the
295+ /// current stack is not compatible with a new one pushed or if an expansion was ended.
296+ fn exit_current_stack ( & mut self ) {
261297 let Some ( element) = std:: mem:: take ( & mut self . parent ) else {
262298 panic ! ( "exiting an element where there is no parent" ) ;
263299 } ;
264- let ElementStack { elements, parent, class } = Box :: into_inner ( element) ;
300+ let ElementStack { elements, parent, class, pending_exit } = Box :: into_inner ( element) ;
265301
266302 let old_elements = std:: mem:: take ( & mut self . elements ) ;
267303 self . elements = elements;
268304 self . elements . push ( ElementOrStack :: Stack ( ElementStack {
269305 elements : old_elements,
270306 class : self . class ,
271307 parent : None ,
308+ pending_exit : false ,
272309 } ) ) ;
273310 self . parent = parent;
274311 self . class = class;
312+ self . pending_exit = pending_exit;
275313 }
276314
277315 fn write_content < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > ) {
@@ -306,16 +344,18 @@ impl ElementStack {
306344 // we generate the `<a>` directly here.
307345 //
308346 // For other elements, the links will be generated in `write_elem_to`.
309- let href_context = if matches ! ( class, Class :: Macro ( _) ) {
310- href_context
311- } else {
312- & None
313- } ;
314- string_without_closing_tag ( out, "" , Some ( class) , href_context, self . class != parent_class)
315- . expect (
316- "internal error: enter_span was called with Some(class) but did not \
347+ let href_context = if matches ! ( class, Class :: Macro ( _) ) { href_context } else { & None } ;
348+ string_without_closing_tag (
349+ out,
350+ "" ,
351+ Some ( class) ,
352+ href_context,
353+ self . class != parent_class,
354+ )
355+ . expect (
356+ "internal error: enter_span was called with Some(class) but did not \
317357 return a closing HTML tag",
318- )
358+ )
319359 } else {
320360 ""
321361 } ;
@@ -427,7 +467,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
427467
428468 // We inline everything into the top-most element.
429469 while self . element_stack . parent . is_some ( ) {
430- self . element_stack . exit_elem ( ) ;
470+ self . element_stack . exit_current_stack ( ) ;
431471 if let Some ( ElementOrStack :: Stack ( stack) ) = self . element_stack . elements . last ( )
432472 && let Some ( class) = stack. class
433473 && class != Class :: Original
@@ -450,6 +490,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
450490impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
451491 /// When leaving, we need to flush all pending data to not have missing content.
452492 fn drop ( & mut self ) {
493+ // We need to clean the hierarchy before displaying it, otherwise the parents won't see
494+ // the last child.
495+ while self . element_stack . parent . is_some ( ) {
496+ self . element_stack . exit_current_stack ( ) ;
497+ }
453498 self . element_stack . write_content ( self . out , & self . href_context ) ;
454499 }
455500}
@@ -493,7 +538,7 @@ fn end_expansion<'a, W: Write>(
493538 expanded_codes : & ' a [ ExpandedCode ] ,
494539 span : Span ,
495540) -> Option < & ' a ExpandedCode > {
496- token_handler. element_stack . exit_elem ( ) ;
541+ token_handler. element_stack . exit_current_stack ( ) ;
497542 let expansion = get_next_expansion ( expanded_codes, token_handler. line , span) ;
498543 if expansion. is_none ( ) {
499544 token_handler. close_expansion ( ) ;
@@ -617,7 +662,9 @@ pub(super) fn write_code(
617662 }
618663 }
619664 }
620- Highlight :: EnterSpan { class } => token_handler. element_stack . enter_elem ( class) ,
665+ Highlight :: EnterSpan { class } => {
666+ token_handler. element_stack . enter_stack ( ElementStack :: new_with_class ( Some ( class) ) )
667+ }
621668 Highlight :: ExitSpan => token_handler. element_stack . exit_elem ( ) ,
622669 } ) ;
623670}
0 commit comments