@@ -54,7 +54,7 @@ pub(crate) fn render_example_with_highlighting(
5454 extra_classes : & [ String ] ,
5555) {
5656 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 ) ;
5858 write_footer ( out, playground_button) ;
5959}
6060
@@ -150,6 +150,7 @@ struct TokenHandler<'a, 'tcx, F: Write> {
150150 /// used to generate links.
151151 pending_elems : Vec < ( & ' a str , Option < Class > ) > ,
152152 href_context : Option < HrefContext < ' a , ' tcx > > ,
153+ write_line_number : fn ( & mut F , u32 , & ' static str ) ,
153154}
154155
155156impl < F : Write > TokenHandler < ' _ , ' _ , F > {
@@ -182,7 +183,14 @@ impl<F: Write> TokenHandler<'_, '_, F> {
182183 && can_merge ( current_class, Some ( * parent_class) , "" )
183184 {
184185 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+ ) ;
186194 }
187195 } else {
188196 // 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> {
204212 * class,
205213 & self . href_context ,
206214 close_tag. is_none ( ) ,
215+ self . write_line_number ,
207216 ) ;
208217 }
209218 if let Some ( close_tag) = close_tag {
@@ -213,6 +222,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
213222 self . pending_elems . clear ( ) ;
214223 true
215224 }
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+ }
216230}
217231
218232impl < F : Write > Drop for TokenHandler < ' _ , ' _ , F > {
@@ -226,6 +240,43 @@ impl<F: Write> Drop for TokenHandler<'_, '_, F> {
226240 }
227241}
228242
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+
229280/// Convert the given `src` source code into HTML by adding classes for highlighting.
230281///
231282/// 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(
242293 src : & str ,
243294 href_context : Option < HrefContext < ' _ , ' _ > > ,
244295 decoration_info : Option < & DecorationInfo > ,
296+ line_info : Option < LineInfo > ,
245297) {
246298 // This replace allows to fix how the code source with DOS backline characters is displayed.
247299 let src = src. replace ( "\r \n " , "\n " ) ;
@@ -252,6 +304,23 @@ pub(super) fn write_code(
252304 current_class : None ,
253305 pending_elems : Vec :: new ( ) ,
254306 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 )
255324 } ;
256325
257326 Classifier :: new (
@@ -282,7 +351,14 @@ pub(super) fn write_code(
282351 if need_current_class_update {
283352 token_handler. current_class = class. map ( Class :: dummy) ;
284353 }
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+ }
286362 }
287363 Highlight :: EnterSpan { class } => {
288364 let mut should_add = true ;
@@ -348,6 +424,7 @@ enum Class {
348424 PreludeVal ( Span ) ,
349425 QuestionMark ,
350426 Decoration ( & ' static str ) ,
427+ Backline ( u32 ) ,
351428}
352429
353430impl Class {
@@ -396,6 +473,7 @@ impl Class {
396473 Class :: PreludeVal ( _) => "prelude-val" ,
397474 Class :: QuestionMark => "question-mark" ,
398475 Class :: Decoration ( kind) => kind,
476+ Class :: Backline ( _) => "" ,
399477 }
400478 }
401479
@@ -419,7 +497,8 @@ impl Class {
419497 | Self :: Bool
420498 | Self :: Lifetime
421499 | Self :: QuestionMark
422- | Self :: Decoration ( _) => None ,
500+ | Self :: Decoration ( _)
501+ | Self :: Backline ( _) => None ,
423502 }
424503 }
425504}
@@ -694,8 +773,13 @@ impl<'src> Classifier<'src> {
694773 ) {
695774 let lookahead = self . peek ( ) ;
696775 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+ } ;
697781 let class = match token {
698- TokenKind :: Whitespace => return no_highlight ( sink) ,
782+ TokenKind :: Whitespace => return whitespace ( sink) ,
699783 TokenKind :: LineComment { doc_style } | TokenKind :: BlockComment { doc_style, .. } => {
700784 if doc_style. is_some ( ) {
701785 Class :: DocComment
@@ -716,7 +800,7 @@ impl<'src> Classifier<'src> {
716800 // or a reference or pointer type. Unless, of course, it looks like
717801 // a logical and or a multiplication operator: `&&` or `* `.
718802 TokenKind :: Star => match self . tokens . peek ( ) {
719- Some ( ( TokenKind :: Whitespace , _) ) => return no_highlight ( sink) ,
803+ Some ( ( TokenKind :: Whitespace , _) ) => return whitespace ( sink) ,
720804 Some ( ( TokenKind :: Ident , "mut" ) ) => {
721805 self . next ( ) ;
722806 sink ( Highlight :: Token { text : "*mut" , class : Some ( Class :: RefKeyWord ) } ) ;
@@ -740,7 +824,7 @@ impl<'src> Classifier<'src> {
740824 sink ( Highlight :: Token { text : "&=" , class : None } ) ;
741825 return ;
742826 }
743- Some ( ( TokenKind :: Whitespace , _) ) => return no_highlight ( sink) ,
827+ Some ( ( TokenKind :: Whitespace , _) ) => return whitespace ( sink) ,
744828 Some ( ( TokenKind :: Ident , "mut" ) ) => {
745829 self . next ( ) ;
746830 sink ( Highlight :: Token { text : "&mut" , class : Some ( Class :: RefKeyWord ) } ) ;
@@ -887,7 +971,9 @@ impl<'src> Classifier<'src> {
887971 } ;
888972 // Anything that didn't return above is the simple case where we the
889973 // 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+ }
891977 }
892978
893979 fn peek ( & mut self ) -> Option < TokenKind > {
@@ -939,14 +1025,18 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) {
9391025/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
9401026/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then
9411027/// 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 ,
9441030 text : T ,
9451031 klass : Option < Class > ,
9461032 href_context : & Option < HrefContext < ' _ , ' _ > > ,
9471033 open_tag : bool ,
1034+ write_line_number_callback : fn ( & mut W , u32 , & ' static str ) ,
9481035) {
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)
9501040 {
9511041 out. write_str ( closing_tag) . unwrap ( ) ;
9521042 }
0 commit comments