@@ -158,11 +158,17 @@ impl Element {
158
158
other. content . iter ( ) . all ( |c| can_merge ( self . class , other. class , & c. text ) )
159
159
}
160
160
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
+ ) {
162
167
let mut prev = parent_class;
163
168
let mut closing_tag = None ;
164
169
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 } ;
166
172
if part. class . is_some ( ) {
167
173
// We only try to generate links as the `<span>` should have already be generated
168
174
// by the caller of `write_elem_to`.
@@ -197,11 +203,18 @@ enum ElementOrStack {
197
203
Stack ( ElementStack ) ,
198
204
}
199
205
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.
200
212
#[ derive( Debug ) ]
201
213
struct ElementStack {
202
214
elements : Vec < ElementOrStack > ,
203
215
parent : Option < Box < ElementStack > > ,
204
216
class : Option < Class > ,
217
+ pending_exit : bool ,
205
218
}
206
219
207
220
impl ElementStack {
@@ -210,10 +223,15 @@ impl ElementStack {
210
223
}
211
224
212
225
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 }
214
227
}
215
228
216
229
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
+ }
217
235
if let Some ( ElementOrStack :: Element ( last) ) = self . elements . last_mut ( )
218
236
&& last. can_merge ( & elem)
219
237
{
@@ -236,41 +254,61 @@ impl ElementStack {
236
254
}
237
255
}
238
256
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
+ }
240
272
assert ! ( parent. is_none( ) , "`enter_stack` used with a non empty parent" ) ;
241
273
let parent_elements = std:: mem:: take ( & mut self . elements ) ;
242
274
let parent_parent = std:: mem:: take ( & mut self . parent ) ;
243
275
self . parent = Some ( Box :: new ( ElementStack {
244
276
elements : parent_elements,
245
277
parent : parent_parent,
246
278
class : self . class ,
279
+ pending_exit : self . pending_exit ,
247
280
} ) ) ;
248
281
self . class = class;
249
282
self . elements = elements;
283
+ self . pending_exit = pending_exit;
250
284
}
251
285
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 ;
257
291
}
258
292
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 ) {
260
296
let Some ( element) = std:: mem:: take ( & mut self . parent ) else {
261
297
panic ! ( "exiting an element where there is no parent" ) ;
262
298
} ;
263
- let ElementStack { elements, parent, class } = Box :: into_inner ( element) ;
299
+ let ElementStack { elements, parent, class, pending_exit } = Box :: into_inner ( element) ;
264
300
265
301
let old_elements = std:: mem:: take ( & mut self . elements ) ;
266
302
self . elements = elements;
267
303
self . elements . push ( ElementOrStack :: Stack ( ElementStack {
268
304
elements : old_elements,
269
305
class : self . class ,
270
306
parent : None ,
307
+ pending_exit : false ,
271
308
} ) ) ;
272
309
self . parent = parent;
273
310
self . class = class;
311
+ self . pending_exit = pending_exit;
274
312
}
275
313
276
314
fn write_content < W : Write > ( & self , out : & mut W , href_context : & Option < HrefContext < ' _ , ' _ > > ) {
@@ -305,16 +343,18 @@ impl ElementStack {
305
343
// we generate the `<a>` directly here.
306
344
//
307
345
// 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 \
316
356
return a closing HTML tag",
317
- )
357
+ )
318
358
} else {
319
359
""
320
360
} ;
@@ -426,7 +466,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
426
466
427
467
// We inline everything into the top-most element.
428
468
while self . element_stack . parent . is_some ( ) {
429
- self . element_stack . exit_elem ( ) ;
469
+ self . element_stack . exit_current_stack ( ) ;
430
470
if let Some ( ElementOrStack :: Stack ( stack) ) = self . element_stack . elements . last ( )
431
471
&& let Some ( class) = stack. class
432
472
&& class != Class :: Original
@@ -449,6 +489,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
449
489
impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
450
490
/// When leaving, we need to flush all pending data to not have missing content.
451
491
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
+ }
452
497
self . element_stack . write_content ( self . out , & self . href_context ) ;
453
498
}
454
499
}
@@ -492,7 +537,7 @@ fn end_expansion<'a, W: Write>(
492
537
expanded_codes : & ' a [ ExpandedCode ] ,
493
538
span : Span ,
494
539
) -> Option < & ' a ExpandedCode > {
495
- token_handler. element_stack . exit_elem ( ) ;
540
+ token_handler. element_stack . exit_current_stack ( ) ;
496
541
let expansion = get_next_expansion ( expanded_codes, token_handler. line , span) ;
497
542
if expansion. is_none ( ) {
498
543
token_handler. close_expansion ( ) ;
@@ -610,7 +655,9 @@ pub(super) fn write_code(
610
655
}
611
656
}
612
657
}
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
+ }
614
661
Highlight :: ExitSpan => token_handler. element_stack . exit_elem ( ) ,
615
662
} ) ;
616
663
}
0 commit comments