Skip to content

Commit 0395fc2

Browse files
Move line numbers into the <code> directly
1 parent 8c04e39 commit 0395fc2

File tree

7 files changed

+168
-66
lines changed

7 files changed

+168
-66
lines changed

src/librustdoc/html/highlight.rs

+101-11
Original file line numberDiff line numberDiff line change
@@ -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

155156
impl<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

218232
impl<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

353430
impl 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
}

src/librustdoc/html/sources.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::cell::RefCell;
22
use std::ffi::OsStr;
3-
use std::ops::RangeInclusive;
43
use std::path::{Component, Path, PathBuf};
54
use std::{fmt, fs};
65

@@ -303,16 +302,16 @@ pub(crate) struct ScrapedInfo<'a> {
303302
#[template(path = "scraped_source.html")]
304303
struct ScrapedSource<'a, Code: std::fmt::Display> {
305304
info: ScrapedInfo<'a>,
306-
lines: RangeInclusive<usize>,
307305
code_html: Code,
306+
max_nb_digits: u32,
308307
}
309308

310309
#[derive(Template)]
311310
#[template(path = "source.html")]
312311
struct Source<Code: std::fmt::Display> {
313-
lines: RangeInclusive<usize>,
314312
code_html: Code,
315313
file_path: Option<(String, String)>,
314+
max_nb_digits: u32,
316315
}
317316

318317
pub(crate) enum SourceContext<'a> {
@@ -331,6 +330,15 @@ pub(crate) fn print_src(
331330
decoration_info: &highlight::DecorationInfo,
332331
source_context: SourceContext<'_>,
333332
) {
333+
let mut lines = s.lines().count();
334+
let line_info = if let SourceContext::Embedded(ref info) = source_context {
335+
highlight::LineInfo::new_scraped(lines as u32, info.offset as u32)
336+
} else {
337+
highlight::LineInfo::new(lines as u32)
338+
};
339+
if line_info.is_scraped_example {
340+
lines += line_info.start_line as usize;
341+
}
334342
let code = fmt::from_fn(move |fmt| {
335343
let current_href = context
336344
.href_from_span(clean::Span::new(file_span), false)
@@ -340,13 +348,13 @@ pub(crate) fn print_src(
340348
s,
341349
Some(highlight::HrefContext { context, file_span, root_path, current_href }),
342350
Some(decoration_info),
351+
Some(line_info),
343352
);
344353
Ok(())
345354
});
346-
let lines = s.lines().count();
355+
let max_nb_digits = if lines > 0 { lines.ilog(10) + 1 } else { 1 };
347356
match source_context {
348357
SourceContext::Standalone { file_path } => Source {
349-
lines: (1..=lines),
350358
code_html: code,
351359
file_path: if let Some(file_name) = file_path.file_name()
352360
&& let Some(file_path) = file_path.parent()
@@ -355,12 +363,14 @@ pub(crate) fn print_src(
355363
} else {
356364
None
357365
},
366+
max_nb_digits,
358367
}
359368
.render_into(&mut writer)
360369
.unwrap(),
361370
SourceContext::Embedded(info) => {
362-
let lines = (1 + info.offset)..=(lines + info.offset);
363-
ScrapedSource { info, lines, code_html: code }.render_into(&mut writer).unwrap();
371+
ScrapedSource { info, code_html: code, max_nb_digits }
372+
.render_into(&mut writer)
373+
.unwrap();
364374
}
365375
};
366376
}

0 commit comments

Comments
 (0)