@@ -138,32 +138,30 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
138138#[ derive( Debug ) ]
139139struct ClassInfo {
140140 class : Class ,
141- /// Set to true only when an item was written inside this tag.
142- open : bool ,
143- /// Set to true when leaving the current item. The closing tag will be
144- /// written if:
141+ /// If `Some`, then it means the tag was opened and needs to be closed.
142+ closing_tag : Option < & ' static str > ,
143+ /// Set to `true` by `exit_elem` to signal that all the elements of this class have been pushed.
145144 ///
146- /// 1. `self.open` is true
147- /// 2. Only when the first non-mergeable item is pushed.
145+ /// The class will be closed and removed from the stack when the next non-mergeable item is
146+ /// pushed. When it is removed, the closing tag will be written if (and only if)
147+ /// `self.closing_tag` is `Some`.
148148 pending_exit : bool ,
149- /// If `true`, it means it's `</a>`, otherwise it's `</span>`.
150- link_closing_tag : bool ,
151149}
152150
153151impl ClassInfo {
154- fn new ( class : Class , pending_exit : bool ) -> Self {
155- Self { class, open : pending_exit , pending_exit, link_closing_tag : false }
152+ fn new ( class : Class , closing_tag : Option < & ' static str > ) -> Self {
153+ Self { class, closing_tag , pending_exit : closing_tag . is_some ( ) }
156154 }
157155
158156 fn close_tag < W : Write > ( & self , out : & mut W ) {
159- if self . open {
160- if self . link_closing_tag {
161- out. write_str ( "</a>" ) . unwrap ( ) ;
162- } else {
163- out. write_str ( "</span>" ) . unwrap ( ) ;
164- }
157+ if let Some ( closing_tag) = self . closing_tag {
158+ out. write_str ( closing_tag) . unwrap ( ) ;
165159 }
166160 }
161+
162+ fn is_open ( & self ) -> bool {
163+ self . closing_tag . is_some ( )
164+ }
167165}
168166
169167/// This represents the stack of HTML elements. For example a macro expansion
@@ -187,7 +185,7 @@ impl ClassStack {
187185 out : & mut W ,
188186 href_context : & Option < HrefContext < ' _ , ' _ > > ,
189187 new_class : Class ,
190- pending_exit : bool ,
188+ closing_tag : Option < & ' static str > ,
191189 ) {
192190 if let Some ( current_class) = self . open_classes . last_mut ( ) {
193191 if can_merge ( Some ( current_class. class ) , Some ( new_class) , "" ) {
@@ -198,22 +196,22 @@ impl ClassStack {
198196 self . open_classes . pop ( ) ;
199197 }
200198 }
201- let mut class_info = ClassInfo :: new ( new_class, pending_exit ) ;
202- if pending_exit {
203- class_info . open = true ;
204- } else if matches ! ( new_class , Class :: Decoration ( _ ) | Class :: Original ) {
205- // We open it right away to ensure it always come at the expected location .
206- // FIXME: Should we instead add a new boolean field to `ClassInfo` to force a non-open
207- // tags to be added if another one comes before it's open?
208- write ! ( out, "<span class=\" {}\" >" , new_class. as_html( ) ) . unwrap ( ) ;
209- class_info. open = true ;
210- } else if new_class. get_span ( ) . is_some ( )
211- && let Some ( closing_tag) =
212- string_without_closing_tag ( out, "" , Some ( class_info. class ) , href_context, false )
213- && !closing_tag. is_empty ( )
214- {
215- class_info. open = true ;
216- class_info . link_closing_tag = closing_tag == "</a>" ;
199+ let mut class_info = ClassInfo :: new ( new_class, closing_tag ) ;
200+ if closing_tag . is_none ( ) {
201+ if matches ! ( new_class , Class :: Decoration ( _ ) | Class :: Original ) {
202+ // Even if a whitespace characters follows, we need to open the class right away
203+ // as these characters are part of the element .
204+ // FIXME: Should we instead add a new boolean field to `ClassInfo` to force a
205+ // non-open tag to be added if another one comes before it's open?
206+ write ! ( out, "<span class=\" {}\" >" , new_class. as_html( ) ) . unwrap ( ) ;
207+ class_info. closing_tag = Some ( "</span>" ) ;
208+ } else if new_class. get_span ( ) . is_some ( )
209+ && let Some ( closing_tag) =
210+ string_without_closing_tag ( out, "" , Some ( class_info. class ) , href_context, false )
211+ && !closing_tag. is_empty ( )
212+ {
213+ class_info. closing_tag = Some ( closing_tag ) ;
214+ }
217215 }
218216
219217 self . open_classes . push ( class_info) ;
@@ -242,20 +240,17 @@ impl ClassStack {
242240
243241 fn last_class_is_open ( & self ) -> bool {
244242 if let Some ( last) = self . open_classes . last ( ) {
245- last. open
243+ last. is_open ( )
246244 } else {
247245 // If there is no class, then it's already open.
248246 true
249247 }
250248 }
251249
252250 fn close_last_if_needed < W : Write > ( & mut self , out : & mut W ) {
253- if let Some ( last) = self . open_classes . last ( )
254- && last. pending_exit
255- && last. open
251+ if let Some ( last) = self . open_classes . pop_if ( |class| class. pending_exit && class. is_open ( ) )
256252 {
257253 last. close_tag ( out) ;
258- self . open_classes . pop ( ) ;
259254 }
260255 }
261256
@@ -284,11 +279,11 @@ impl ClassStack {
284279 if !class_s. is_empty ( ) {
285280 write ! ( out, "<span class=\" {class_s}\" >" ) . unwrap ( ) ;
286281 }
287- current_class_info. open = true ;
282+ current_class_info. closing_tag = Some ( "</span>" ) ;
288283 }
289284 }
290285
291- let current_class_is_open = self . open_classes . last ( ) . is_some_and ( |c| c. open ) ;
286+ let current_class_is_open = self . open_classes . last ( ) . is_some_and ( |c| c. is_open ( ) ) ;
292287 let can_merge = can_merge ( class, current_class, & text) ;
293288 let should_open_tag = !current_class_is_open || !can_merge;
294289
@@ -312,14 +307,20 @@ impl ClassStack {
312307 } else if let Some ( class) = class
313308 && !can_merge
314309 {
315- self . enter_elem ( out, href_context, class, true ) ;
310+ self . enter_elem ( out, href_context, class, Some ( "</span>" ) ) ;
316311 // Otherwise, we consider the actual `Class` to have been open.
317312 } else if let Some ( current_class_info) = self . open_classes . last_mut ( ) {
318- current_class_info. open = true ;
313+ current_class_info. closing_tag = Some ( "</span>" ) ;
319314 }
320315 }
321316 }
322317
318+ /// This method closes all open tags and returns the list of `Class` which were not already
319+ /// closed (ie `pending_exit` set to `true`).
320+ ///
321+ /// It is used when starting a macro expansion: we need to close all HTML tags and then to
322+ /// reopen them inside the newly created expansion HTML tag. Same goes when we close the
323+ /// expansion.
323324 fn empty_stack < W : Write > ( & mut self , out : & mut W ) -> Vec < Class > {
324325 let mut classes = Vec :: with_capacity ( self . open_classes . len ( ) ) ;
325326
@@ -387,7 +388,7 @@ impl<'a, F: Write> TokenHandler<'a, '_, F> {
387388 let classes = self . class_stack . empty_stack ( self . out ) ;
388389
389390 // We start the expansion tag.
390- self . class_stack . enter_elem ( self . out , & self . href_context , Class :: Expansion , false ) ;
391+ self . class_stack . enter_elem ( self . out , & self . href_context , Class :: Expansion , None ) ;
391392 self . push_token_without_backline_check (
392393 Some ( Class :: Expansion ) ,
393394 Cow :: Owned ( format ! (
@@ -401,9 +402,9 @@ impl<'a, F: Write> TokenHandler<'a, '_, F> {
401402 false ,
402403 ) ;
403404
404- // We re-open all tags.
405+ // We re-open all tags that didn't have `pending_exit` set to `true` .
405406 for class in classes. into_iter ( ) . rev ( ) {
406- self . class_stack . enter_elem ( self . out , & self . href_context , class, false ) ;
407+ self . class_stack . enter_elem ( self . out , & self . href_context , class, None ) ;
407408 }
408409 }
409410
@@ -413,7 +414,7 @@ impl<'a, F: Write> TokenHandler<'a, '_, F> {
413414 Cow :: Owned ( format ! ( "<span class=expanded>{}</span>" , expanded_code. code) ) ,
414415 false ,
415416 ) ;
416- self . class_stack . enter_elem ( self . out , & self . href_context , Class :: Original , false ) ;
417+ self . class_stack . enter_elem ( self . out , & self . href_context , Class :: Original , None ) ;
417418 }
418419
419420 fn close_expansion ( & mut self ) {
@@ -423,7 +424,7 @@ impl<'a, F: Write> TokenHandler<'a, '_, F> {
423424 // We re-open all tags without expansion-related ones.
424425 for class in classes. into_iter ( ) . rev ( ) {
425426 if !matches ! ( class, Class :: Expansion | Class :: Original ) {
426- self . class_stack . enter_elem ( self . out , & self . href_context , class, false ) ;
427+ self . class_stack . enter_elem ( self . out , & self . href_context , class, None ) ;
427428 }
428429 }
429430 }
@@ -604,7 +605,7 @@ pub(super) fn write_code(
604605 token_handler. out ,
605606 & token_handler. href_context ,
606607 class,
607- false ,
608+ None ,
608609 ) ;
609610 }
610611 Highlight :: ExitSpan => {
@@ -706,7 +707,6 @@ impl Class {
706707 | Self :: Lifetime
707708 | Self :: QuestionMark
708709 | Self :: Decoration ( _)
709- // | Self::Backline(_)
710710 | Self :: Original
711711 | Self :: Expansion => None ,
712712 }
0 commit comments