@@ -54,7 +54,7 @@ pub(crate) fn render_example_with_highlighting(
54
54
extra_classes : & [ String ] ,
55
55
) {
56
56
write_header ( out, "rust-example-rendered" , None , tooltip, extra_classes) ;
57
- write_code ( out, src, None , None ) ;
57
+ write_code ( out, src, None , None , None ) ;
58
58
write_footer ( out, playground_button) ;
59
59
}
60
60
@@ -150,6 +150,7 @@ struct TokenHandler<'a, 'tcx, F: Write> {
150
150
/// used to generate links.
151
151
pending_elems : Vec < ( & ' a str , Option < Class > ) > ,
152
152
href_context : Option < HrefContext < ' a , ' tcx > > ,
153
+ write_line_number : fn ( & mut F , u32 , & ' static str ) ,
153
154
}
154
155
155
156
impl < F : Write > TokenHandler < ' _ , ' _ , F > {
@@ -182,7 +183,14 @@ impl<F: Write> TokenHandler<'_, '_, F> {
182
183
&& can_merge ( current_class, Some ( * parent_class) , "" )
183
184
{
184
185
for ( text, class) in self . pending_elems . iter ( ) {
185
- string ( self . out , EscapeBodyText ( text) , * class, & self . href_context , false ) ;
186
+ string (
187
+ self . out ,
188
+ EscapeBodyText ( text) ,
189
+ * class,
190
+ & self . href_context ,
191
+ false ,
192
+ self . write_line_number ,
193
+ ) ;
186
194
}
187
195
} else {
188
196
// We only want to "open" the tag ourselves if we have more than one pending and if the
@@ -204,6 +212,7 @@ impl<F: Write> TokenHandler<'_, '_, F> {
204
212
* class,
205
213
& self . href_context ,
206
214
close_tag. is_none ( ) ,
215
+ self . write_line_number ,
207
216
) ;
208
217
}
209
218
if let Some ( close_tag) = close_tag {
@@ -213,6 +222,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
213
222
self . pending_elems . clear ( ) ;
214
223
true
215
224
}
225
+
226
+ #[ inline]
227
+ fn write_line_number ( & mut self , line : u32 , extra : & ' static str ) {
228
+ ( self . write_line_number ) ( & mut self . out , line, extra) ;
229
+ }
216
230
}
217
231
218
232
impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
@@ -226,6 +240,43 @@ impl<F: Write> Drop for TokenHandler<'_, '_, F> {
226
240
}
227
241
}
228
242
243
+ fn write_scraped_line_number ( out : & mut impl Write , line : u32 , extra : & ' static str ) {
244
+ // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
245
+ // Do not show "1 2 3 4 5 ..." in web search results.
246
+ write ! ( out, "{extra}<span data-nosnippet>{line}</span>" , ) . unwrap ( ) ;
247
+ }
248
+
249
+ fn write_line_number ( out : & mut impl Write , line : u32 , extra : & ' static str ) {
250
+ // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
251
+ // Do not show "1 2 3 4 5 ..." in web search results.
252
+ write ! ( out, "{extra}<a href=#{line} id={line} data-nosnippet>{line}</a>" , ) . unwrap ( ) ;
253
+ }
254
+
255
+ fn empty_line_number ( out : & mut impl Write , _: u32 , extra : & ' static str ) {
256
+ out. write_str ( extra) . unwrap ( ) ;
257
+ }
258
+
259
+ #[ derive( Clone , Copy ) ]
260
+ pub ( super ) struct LineInfo {
261
+ pub ( super ) start_line : u32 ,
262
+ max_lines : u32 ,
263
+ pub ( super ) is_scraped_example : bool ,
264
+ }
265
+
266
+ impl LineInfo {
267
+ pub ( super ) fn new ( max_lines : u32 ) -> Self {
268
+ Self { start_line : 1 , max_lines : max_lines + 1 , is_scraped_example : false }
269
+ }
270
+
271
+ pub ( super ) fn new_scraped ( max_lines : u32 , start_line : u32 ) -> Self {
272
+ Self {
273
+ start_line : start_line + 1 ,
274
+ max_lines : max_lines + start_line + 1 ,
275
+ is_scraped_example : true ,
276
+ }
277
+ }
278
+ }
279
+
229
280
/// Convert the given `src` source code into HTML by adding classes for highlighting.
230
281
///
231
282
/// This code is used to render code blocks (in the documentation) as well as the source code pages.
@@ -242,6 +293,7 @@ pub(super) fn write_code(
242
293
src : & str ,
243
294
href_context : Option < HrefContext < ' _ , ' _ > > ,
244
295
decoration_info : Option < & DecorationInfo > ,
296
+ line_info : Option < LineInfo > ,
245
297
) {
246
298
// This replace allows to fix how the code source with DOS backline characters is displayed.
247
299
let src = src. replace ( "\r \n " , "\n " ) ;
@@ -252,6 +304,23 @@ pub(super) fn write_code(
252
304
current_class : None ,
253
305
pending_elems : Vec :: new ( ) ,
254
306
href_context,
307
+ write_line_number : match line_info {
308
+ Some ( line_info) => {
309
+ if line_info. is_scraped_example {
310
+ write_scraped_line_number
311
+ } else {
312
+ write_line_number
313
+ }
314
+ }
315
+ None => empty_line_number,
316
+ } ,
317
+ } ;
318
+
319
+ let ( mut line, max_lines) = if let Some ( line_info) = line_info {
320
+ token_handler. write_line_number ( line_info. start_line , "" ) ;
321
+ ( line_info. start_line , line_info. max_lines )
322
+ } else {
323
+ ( 0 , u32:: MAX )
255
324
} ;
256
325
257
326
Classifier :: new (
@@ -282,7 +351,14 @@ pub(super) fn write_code(
282
351
if need_current_class_update {
283
352
token_handler. current_class = class. map ( Class :: dummy) ;
284
353
}
285
- token_handler. pending_elems . push ( ( text, class) ) ;
354
+ if text == "\n " {
355
+ line += 1 ;
356
+ if line < max_lines {
357
+ token_handler. pending_elems . push ( ( text, Some ( Class :: Backline ( line) ) ) ) ;
358
+ }
359
+ } else {
360
+ token_handler. pending_elems . push ( ( text, class) ) ;
361
+ }
286
362
}
287
363
Highlight :: EnterSpan { class } => {
288
364
let mut should_add = true ;
@@ -348,6 +424,7 @@ enum Class {
348
424
PreludeVal ( Span ) ,
349
425
QuestionMark ,
350
426
Decoration ( & ' static str ) ,
427
+ Backline ( u32 ) ,
351
428
}
352
429
353
430
impl Class {
@@ -396,6 +473,7 @@ impl Class {
396
473
Class :: PreludeVal ( _) => "prelude-val" ,
397
474
Class :: QuestionMark => "question-mark" ,
398
475
Class :: Decoration ( kind) => kind,
476
+ Class :: Backline ( _) => "" ,
399
477
}
400
478
}
401
479
@@ -419,7 +497,8 @@ impl Class {
419
497
| Self :: Bool
420
498
| Self :: Lifetime
421
499
| Self :: QuestionMark
422
- | Self :: Decoration ( _) => None ,
500
+ | Self :: Decoration ( _)
501
+ | Self :: Backline ( _) => None ,
423
502
}
424
503
}
425
504
}
@@ -694,8 +773,13 @@ impl<'src> Classifier<'src> {
694
773
) {
695
774
let lookahead = self . peek ( ) ;
696
775
let no_highlight = |sink : & mut dyn FnMut ( _) | sink ( Highlight :: Token { text, class : None } ) ;
776
+ let whitespace = |sink : & mut dyn FnMut ( _) | {
777
+ for part in text. split ( '\n' ) . intersperse ( "\n " ) . filter ( |s| !s. is_empty ( ) ) {
778
+ sink ( Highlight :: Token { text : part, class : None } ) ;
779
+ }
780
+ } ;
697
781
let class = match token {
698
- TokenKind :: Whitespace => return no_highlight ( sink) ,
782
+ TokenKind :: Whitespace => return whitespace ( sink) ,
699
783
TokenKind :: LineComment { doc_style } | TokenKind :: BlockComment { doc_style, .. } => {
700
784
if doc_style. is_some ( ) {
701
785
Class :: DocComment
@@ -716,7 +800,7 @@ impl<'src> Classifier<'src> {
716
800
// or a reference or pointer type. Unless, of course, it looks like
717
801
// a logical and or a multiplication operator: `&&` or `* `.
718
802
TokenKind :: Star => match self . tokens . peek ( ) {
719
- Some ( ( TokenKind :: Whitespace , _) ) => return no_highlight ( sink) ,
803
+ Some ( ( TokenKind :: Whitespace , _) ) => return whitespace ( sink) ,
720
804
Some ( ( TokenKind :: Ident , "mut" ) ) => {
721
805
self . next ( ) ;
722
806
sink ( Highlight :: Token { text : "*mut" , class : Some ( Class :: RefKeyWord ) } ) ;
@@ -740,7 +824,7 @@ impl<'src> Classifier<'src> {
740
824
sink ( Highlight :: Token { text : "&=" , class : None } ) ;
741
825
return ;
742
826
}
743
- Some ( ( TokenKind :: Whitespace , _) ) => return no_highlight ( sink) ,
827
+ Some ( ( TokenKind :: Whitespace , _) ) => return whitespace ( sink) ,
744
828
Some ( ( TokenKind :: Ident , "mut" ) ) => {
745
829
self . next ( ) ;
746
830
sink ( Highlight :: Token { text : "&mut" , class : Some ( Class :: RefKeyWord ) } ) ;
@@ -887,7 +971,9 @@ impl<'src> Classifier<'src> {
887
971
} ;
888
972
// Anything that didn't return above is the simple case where we the
889
973
// class just spans a single token, so we can use the `string` method.
890
- sink ( Highlight :: Token { text, class : Some ( class) } ) ;
974
+ for part in text. split ( '\n' ) . intersperse ( "\n " ) . filter ( |s| !s. is_empty ( ) ) {
975
+ sink ( Highlight :: Token { text : part, class : Some ( class) } ) ;
976
+ }
891
977
}
892
978
893
979
fn peek ( & mut self ) -> Option < TokenKind > {
@@ -939,14 +1025,18 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) {
939
1025
/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
940
1026
/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then
941
1027
/// generate a link for this element (which corresponds to where its definition is located).
942
- fn string < T : Display > (
943
- out : & mut impl Write ,
1028
+ fn string < T : Display , W : Write > (
1029
+ out : & mut W ,
944
1030
text : T ,
945
1031
klass : Option < Class > ,
946
1032
href_context : & Option < HrefContext < ' _ , ' _ > > ,
947
1033
open_tag : bool ,
1034
+ write_line_number_callback : fn ( & mut W , u32 , & ' static str ) ,
948
1035
) {
949
- if let Some ( closing_tag) = string_without_closing_tag ( out, text, klass, href_context, open_tag)
1036
+ if let Some ( Class :: Backline ( line) ) = klass {
1037
+ write_line_number_callback ( out, line, "\n " ) ;
1038
+ } else if let Some ( closing_tag) =
1039
+ string_without_closing_tag ( out, text, klass, href_context, open_tag)
950
1040
{
951
1041
out. write_str ( closing_tag) . unwrap ( ) ;
952
1042
}
0 commit comments