@@ -111,65 +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 && current_class. is_some ( ) ;
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-
161- fn handle_exit_span (
162- out : & mut Buffer ,
163- href_context : & Option < HrefContext < ' _ , ' _ , ' _ > > ,
164- pending_elems : & mut Vec < ( & str , Option < Class > ) > ,
165- closing_tags : & mut Vec < ( & str , Class ) > ,
166- ) {
167- let class = closing_tags. last ( ) . expect ( "ExitSpan without EnterSpan" ) . 1 ;
168- // We flush everything just in case...
169- write_pending_elems ( out, href_context, pending_elems, & mut Some ( class) , closing_tags) ;
170- exit_span ( out, closing_tags. pop ( ) . expect ( "ExitSpan without EnterSpan" ) . 0 ) ;
171- }
172-
173114/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
174115/// basically (since it's `Option<Class>`). The following rules apply:
175116///
@@ -187,6 +128,87 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
187128 }
188129}
189130
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+ }
209+ }
210+ }
211+
190212/// Convert the given `src` source code into HTML by adding classes for highlighting.
191213///
192214/// This code is used to render code blocks (in the documentation) as well as the source code pages.
@@ -206,97 +228,72 @@ fn write_code(
206228) {
207229 // This replace allows to fix how the code source with DOS backline characters is displayed.
208230 let src = src. replace ( "\r \n " , "\n " ) ;
209- // It contains the closing tag and the associated `Class`.
210- let mut closing_tags: Vec < ( & ' static str , Class ) > = Vec :: new ( ) ;
211- // This is used because we don't automatically generate the closing tag on `ExitSpan` in
212- // case an `EnterSpan` event with the same class follows.
213- let mut pending_exit_span: Option < Class > = None ;
214- // The following two variables are used to group HTML elements with same `class` attributes
215- // to reduce the DOM size.
216- let mut current_class: Option < Class > = None ;
217- // We need to keep the `Class` for each element because it could contain a `Span` which is
218- // used to generate links.
219- 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+ } ;
220239
221240 Classifier :: new (
222241 & src,
223- 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 ) ,
224243 decoration_info,
225244 )
226245 . highlight ( & mut |highlight| {
227246 match highlight {
228247 Highlight :: Token { text, class } => {
229248 // If we received a `ExitSpan` event and then have a non-compatible `Class`, we
230249 // need to close the `<span>`.
231- if let Some ( pending) = pending_exit_span &&
250+ let need_current_class_update = if let Some ( pending) = token_handler . pending_exit_span &&
232251 !can_merge ( Some ( pending) , class, text) {
233- handle_exit_span (
234- out,
235- & href_context,
236- & mut pending_elems,
237- & mut closing_tags,
238- ) ;
239- pending_exit_span = None ;
240- current_class = class. map ( Class :: dummy) ;
252+ token_handler. handle_exit_span ( ) ;
253+ true
241254 // If the two `Class` are different, time to flush the current content and start
242255 // a new one.
243- } else if !can_merge ( current_class, class, text) {
244- write_pending_elems (
245- out,
246- & href_context,
247- & mut pending_elems,
248- & mut current_class,
249- & closing_tags,
250- ) ;
251- current_class = class. map ( Class :: dummy) ;
252- } else if current_class. is_none ( ) {
253- 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) ;
254265 }
255- pending_elems. push ( ( text, class) ) ;
266+ token_handler . pending_elems . push ( ( text, class) ) ;
256267 }
257268 Highlight :: EnterSpan { class } => {
258269 let mut should_add = true ;
259- if pending_exit_span. is_some ( ) {
260- if !can_merge ( Some ( class) , pending_exit_span, "" ) {
261- handle_exit_span ( out, & href_context, & mut pending_elems, & mut closing_tags) ;
262- } else {
270+ if let Some ( pending_exit_span) = token_handler. pending_exit_span {
271+ if class. is_equal_to ( pending_exit_span) {
263272 should_add = false ;
273+ } else {
274+ token_handler. handle_exit_span ( ) ;
264275 }
265276 } else {
266277 // We flush everything just in case...
267- write_pending_elems (
268- out,
269- & href_context,
270- & mut pending_elems,
271- & mut current_class,
272- & closing_tags,
273- ) ;
278+ if token_handler. write_pending_elems ( token_handler. current_class ) {
279+ token_handler. current_class = None ;
280+ }
274281 }
275- current_class = None ;
276- pending_exit_span = None ;
277282 if should_add {
278- let closing_tag = enter_span ( out, class, & href_context) ;
279- closing_tags. push ( ( closing_tag, class) ) ;
283+ let closing_tag = enter_span ( token_handler . out , class, & token_handler . href_context ) ;
284+ token_handler . closing_tags . push ( ( closing_tag, class) ) ;
280285 }
286+
287+ token_handler. current_class = None ;
288+ token_handler. pending_exit_span = None ;
281289 }
282290 Highlight :: ExitSpan => {
283- current_class = None ;
284- pending_exit_span =
285- Some ( closing_tags. last ( ) . as_ref ( ) . expect ( "ExitSpan without EnterSpan" ) . 1 ) ;
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 ) ;
286294 }
287295 } ;
288296 } ) ;
289- if pending_exit_span. is_some ( ) {
290- handle_exit_span ( out, & href_context, & mut pending_elems, & mut closing_tags) ;
291- } else {
292- write_pending_elems (
293- out,
294- & href_context,
295- & mut pending_elems,
296- & mut current_class,
297- & closing_tags,
298- ) ;
299- }
300297}
301298
302299fn write_footer ( out : & mut Buffer , playground_button : Option < & str > ) {
0 commit comments