@@ -111,53 +111,6 @@ fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>) {
111111 write ! ( out, "<code>" ) ;
112112}
113113
114- /// Write all the pending elements sharing a same (or at mergeable) `Class`.
115- ///
116- /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
117- /// with the elements' class, then we simply write the elements since the `ExitSpan` event will
118- /// close the tag.
119- ///
120- /// Otherwise, if there is only one pending element, we let the `string` function handle both
121- /// opening and closing the tag, otherwise we do it into this function.
122- fn write_pending_elems (
123- out : & mut Buffer ,
124- href_context : & Option < HrefContext < ' _ , ' _ , ' _ > > ,
125- pending_elems : & mut Vec < ( & str , Option < Class > ) > ,
126- current_class : & mut Option < Class > ,
127- closing_tags : & [ ( & str , Class ) ] ,
128- ) {
129- if pending_elems. is_empty ( ) {
130- return ;
131- }
132- let mut done = false ;
133- if let Some ( ( _, parent_class) ) = closing_tags. last ( ) {
134- if can_merge ( * current_class, Some ( * parent_class) , "" ) {
135- for ( text, class) in pending_elems. iter ( ) {
136- string ( out, Escape ( text) , * class, & href_context, false ) ;
137- }
138- done = true ;
139- }
140- }
141- if !done {
142- // We only want to "open" the tag ourselves if we have more than one pending and if the current
143- // parent tag is not the same as our pending content.
144- let open_tag_ourselves = pending_elems. len ( ) > 1 ;
145- let close_tag = if open_tag_ourselves {
146- enter_span ( out, current_class. unwrap ( ) , & href_context)
147- } else {
148- ""
149- } ;
150- for ( text, class) in pending_elems. iter ( ) {
151- string ( out, Escape ( text) , * class, & href_context, !open_tag_ourselves) ;
152- }
153- if open_tag_ourselves {
154- exit_span ( out, close_tag) ;
155- }
156- }
157- pending_elems. clear ( ) ;
158- * current_class = None ;
159- }
160-
161114/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
162115/// basically (since it's `Option<Class>`). The following rules apply:
163116///
@@ -171,7 +124,88 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
171124 ( Some ( c1) , Some ( c2) ) => c1. is_equal_to ( c2) ,
172125 ( Some ( Class :: Ident ( _) ) , None ) | ( None , Some ( Class :: Ident ( _) ) ) => true ,
173126 ( Some ( _) , None ) | ( None , Some ( _) ) => text. trim ( ) . is_empty ( ) ,
174- _ => false ,
127+ ( None , None ) => true ,
128+ }
129+ }
130+
131+ /// This type is used as a conveniency to prevent having to pass all its fields as arguments into
132+ /// the various functions (which became its methods).
133+ struct TokenHandler < ' a , ' b , ' c , ' d , ' e > {
134+ out : & ' a mut Buffer ,
135+ /// It contains the closing tag and the associated `Class`.
136+ closing_tags : Vec < ( & ' static str , Class ) > ,
137+ /// This is used because we don't automatically generate the closing tag on `ExitSpan` in
138+ /// case an `EnterSpan` event with the same class follows.
139+ pending_exit_span : Option < Class > ,
140+ /// `current_class` and `pending_elems` are used to group HTML elements with same `class`
141+ /// attributes to reduce the DOM size.
142+ current_class : Option < Class > ,
143+ /// We need to keep the `Class` for each element because it could contain a `Span` which is
144+ /// used to generate links.
145+ pending_elems : Vec < ( & ' b str , Option < Class > ) > ,
146+ href_context : Option < HrefContext < ' c , ' d , ' e > > ,
147+ }
148+
149+ impl < ' a , ' b , ' c , ' d , ' e > TokenHandler < ' a , ' b , ' c , ' d , ' e > {
150+ fn handle_exit_span ( & mut self ) {
151+ // We can't get the last `closing_tags` element using `pop()` because `closing_tags` is
152+ // being used in `write_pending_elems`.
153+ let class = self . closing_tags . last ( ) . expect ( "ExitSpan without EnterSpan" ) . 1 ;
154+ // We flush everything just in case...
155+ self . write_pending_elems ( Some ( class) ) ;
156+
157+ exit_span ( self . out , self . closing_tags . pop ( ) . expect ( "ExitSpan without EnterSpan" ) . 0 ) ;
158+ self . pending_exit_span = None ;
159+ }
160+
161+ /// Write all the pending elements sharing a same (or at mergeable) `Class`.
162+ ///
163+ /// If there is a "parent" (if a `EnterSpan` event was encountered) and the parent can be merged
164+ /// with the elements' class, then we simply write the elements since the `ExitSpan` event will
165+ /// close the tag.
166+ ///
167+ /// Otherwise, if there is only one pending element, we let the `string` function handle both
168+ /// opening and closing the tag, otherwise we do it into this function.
169+ ///
170+ /// It returns `true` if `current_class` must be set to `None` afterwards.
171+ fn write_pending_elems ( & mut self , current_class : Option < Class > ) -> bool {
172+ if self . pending_elems . is_empty ( ) {
173+ return false ;
174+ }
175+ if let Some ( ( _, parent_class) ) = self . closing_tags . last ( ) &&
176+ can_merge ( current_class, Some ( * parent_class) , "" )
177+ {
178+ for ( text, class) in self . pending_elems . iter ( ) {
179+ string ( self . out , Escape ( text) , * class, & self . href_context , false ) ;
180+ }
181+ } else {
182+ // We only want to "open" the tag ourselves if we have more than one pending and if the
183+ // current parent tag is not the same as our pending content.
184+ let close_tag = if self . pending_elems . len ( ) > 1 && current_class. is_some ( ) {
185+ Some ( enter_span ( self . out , current_class. unwrap ( ) , & self . href_context ) )
186+ } else {
187+ None
188+ } ;
189+ for ( text, class) in self . pending_elems . iter ( ) {
190+ string ( self . out , Escape ( text) , * class, & self . href_context , close_tag. is_none ( ) ) ;
191+ }
192+ if let Some ( close_tag) = close_tag {
193+ exit_span ( self . out , close_tag) ;
194+ }
195+ }
196+ self . pending_elems . clear ( ) ;
197+ true
198+ }
199+ }
200+
201+ impl < ' a , ' b , ' c , ' d , ' e > Drop for TokenHandler < ' a , ' b , ' c , ' d , ' e > {
202+ /// When leaving, we need to flush all pending data to not have missing content.
203+ fn drop ( & mut self ) {
204+ if self . pending_exit_span . is_some ( ) {
205+ self . handle_exit_span ( ) ;
206+ } else {
207+ self . write_pending_elems ( self . current_class ) ;
208+ }
175209 }
176210}
177211
@@ -194,64 +228,72 @@ fn write_code(
194228) {
195229 // This replace allows to fix how the code source with DOS backline characters is displayed.
196230 let src = src. replace ( "\r \n " , "\n " ) ;
197- // It contains the closing tag and the associated `Class`.
198- let mut closing_tags : Vec < ( & ' static str , Class ) > = Vec :: new ( ) ;
199- // The following two variables are used to group HTML elements with same `class` attributes
200- // to reduce the DOM size.
201- let mut current_class: Option < Class > = None ;
202- // We need to keep the `Class` for each element because it could contain a `Span` which is
203- // used to generate links.
204- let mut pending_elems : Vec < ( & str , Option < Class > ) > = Vec :: new ( ) ;
231+ let mut token_handler = TokenHandler {
232+ out ,
233+ closing_tags : Vec :: new ( ) ,
234+ pending_exit_span : None ,
235+ current_class : None ,
236+ pending_elems : Vec :: new ( ) ,
237+ href_context ,
238+ } ;
205239
206240 Classifier :: new (
207241 & src,
208- href_context. as_ref ( ) . map ( |c| c. file_span ) . unwrap_or ( DUMMY_SP ) ,
242+ token_handler . href_context . as_ref ( ) . map ( |c| c. file_span ) . unwrap_or ( DUMMY_SP ) ,
209243 decoration_info,
210244 )
211245 . highlight ( & mut |highlight| {
212246 match highlight {
213247 Highlight :: Token { text, class } => {
248+ // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
249+ // need to close the `<span>`.
250+ let need_current_class_update = if let Some ( pending) = token_handler. pending_exit_span &&
251+ !can_merge ( Some ( pending) , class, text) {
252+ token_handler. handle_exit_span ( ) ;
253+ true
214254 // If the two `Class` are different, time to flush the current content and start
215255 // a new one.
216- if !can_merge ( current_class, class, text) {
217- write_pending_elems (
218- out,
219- & href_context,
220- & mut pending_elems,
221- & mut current_class,
222- & closing_tags,
223- ) ;
224- current_class = class. map ( Class :: dummy) ;
225- } else if current_class. is_none ( ) {
226- current_class = class. map ( Class :: dummy) ;
256+ } else if !can_merge ( token_handler. current_class , class, text) {
257+ token_handler. write_pending_elems ( token_handler. current_class ) ;
258+ true
259+ } else {
260+ token_handler. current_class . is_none ( )
261+ } ;
262+
263+ if need_current_class_update {
264+ token_handler. current_class = class. map ( Class :: dummy) ;
227265 }
228- pending_elems. push ( ( text, class) ) ;
266+ token_handler . pending_elems . push ( ( text, class) ) ;
229267 }
230268 Highlight :: EnterSpan { class } => {
231- // We flush everything just in case...
232- write_pending_elems (
233- out,
234- & href_context,
235- & mut pending_elems,
236- & mut current_class,
237- & closing_tags,
238- ) ;
239- closing_tags. push ( ( enter_span ( out, class, & href_context) , class) )
269+ let mut should_add = true ;
270+ if let Some ( pending_exit_span) = token_handler. pending_exit_span {
271+ if class. is_equal_to ( pending_exit_span) {
272+ should_add = false ;
273+ } else {
274+ token_handler. handle_exit_span ( ) ;
275+ }
276+ } else {
277+ // We flush everything just in case...
278+ if token_handler. write_pending_elems ( token_handler. current_class ) {
279+ token_handler. current_class = None ;
280+ }
281+ }
282+ if should_add {
283+ let closing_tag = enter_span ( token_handler. out , class, & token_handler. href_context ) ;
284+ token_handler. closing_tags . push ( ( closing_tag, class) ) ;
285+ }
286+
287+ token_handler. current_class = None ;
288+ token_handler. pending_exit_span = None ;
240289 }
241290 Highlight :: ExitSpan => {
242- // We flush everything just in case...
243- write_pending_elems (
244- out,
245- & href_context,
246- & mut pending_elems,
247- & mut current_class,
248- & closing_tags,
249- ) ;
250- exit_span ( out, closing_tags. pop ( ) . expect ( "ExitSpan without EnterSpan" ) . 0 )
291+ token_handler. current_class = None ;
292+ token_handler. pending_exit_span =
293+ Some ( token_handler. closing_tags . last ( ) . as_ref ( ) . expect ( "ExitSpan without EnterSpan" ) . 1 ) ;
251294 }
252295 } ;
253296 } ) ;
254- write_pending_elems ( out, & href_context, & mut pending_elems, & mut current_class, & closing_tags) ;
255297}
256298
257299fn write_footer ( out : & mut Buffer , playground_button : Option < & str > ) {
@@ -291,8 +333,8 @@ impl Class {
291333 match ( self , other) {
292334 ( Self :: Self_ ( _) , Self :: Self_ ( _) )
293335 | ( Self :: Macro ( _) , Self :: Macro ( _) )
294- | ( Self :: Ident ( _) , Self :: Ident ( _) )
295- | ( Self :: Decoration ( _ ) , Self :: Decoration ( _ ) ) => true ,
336+ | ( Self :: Ident ( _) , Self :: Ident ( _) ) => true ,
337+ ( Self :: Decoration ( c1 ) , Self :: Decoration ( c2 ) ) => c1 == c2 ,
296338 ( x, y) => x == y,
297339 }
298340 }
@@ -761,7 +803,7 @@ impl<'a> Classifier<'a> {
761803 TokenKind :: CloseBracket => {
762804 if self . in_attribute {
763805 self . in_attribute = false ;
764- sink ( Highlight :: Token { text : "]" , class : Some ( Class :: Attribute ) } ) ;
806+ sink ( Highlight :: Token { text : "]" , class : None } ) ;
765807 sink ( Highlight :: ExitSpan ) ;
766808 return ;
767809 }
0 commit comments