@@ -111,65 +111,6 @@ fn write_header(out: &mut Buffer, class: &str, extra_content: Option<Buffer>) {
111
111
write ! ( out, "<code>" ) ;
112
112
}
113
113
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
-
173
114
/// Check if two `Class` can be merged together. In the following rules, "unclassified" means `None`
174
115
/// basically (since it's `Option<Class>`). The following rules apply:
175
116
///
@@ -187,6 +128,87 @@ fn can_merge(class1: Option<Class>, class2: Option<Class>, text: &str) -> bool {
187
128
}
188
129
}
189
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
+ }
209
+ }
210
+ }
211
+
190
212
/// Convert the given `src` source code into HTML by adding classes for highlighting.
191
213
///
192
214
/// 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(
206
228
) {
207
229
// This replace allows to fix how the code source with DOS backline characters is displayed.
208
230
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
+ } ;
220
239
221
240
Classifier :: new (
222
241
& 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 ) ,
224
243
decoration_info,
225
244
)
226
245
. highlight ( & mut |highlight| {
227
246
match highlight {
228
247
Highlight :: Token { text, class } => {
229
248
// If we received a `ExitSpan` event and then have a non-compatible `Class`, we
230
249
// 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 &&
232
251
!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
241
254
// If the two `Class` are different, time to flush the current content and start
242
255
// 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) ;
254
265
}
255
- pending_elems. push ( ( text, class) ) ;
266
+ token_handler . pending_elems . push ( ( text, class) ) ;
256
267
}
257
268
Highlight :: EnterSpan { class } => {
258
269
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) {
263
272
should_add = false ;
273
+ } else {
274
+ token_handler. handle_exit_span ( ) ;
264
275
}
265
276
} else {
266
277
// 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
+ }
274
281
}
275
- current_class = None ;
276
- pending_exit_span = None ;
277
282
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) ) ;
280
285
}
286
+
287
+ token_handler. current_class = None ;
288
+ token_handler. pending_exit_span = None ;
281
289
}
282
290
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 ) ;
286
294
}
287
295
} ;
288
296
} ) ;
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
- }
300
297
}
301
298
302
299
fn write_footer ( out : & mut Buffer , playground_button : Option < & str > ) {
0 commit comments