Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ba32d8b

Browse files
authoredFeb 12, 2025
Rollup merge of #136829 - GuillaumeGomez:move-line-numbers-into-code, r=notriddle
[rustdoc] Move line numbers into the `<code>` directly Fixes #84242. This is the first for adding support for #127334 and also for another feature I'm working on. A side-effect of this change is that it also fixes source code pages display in lynx since they're not directly in the source code. To allow having code wrapping, the grid approach doesn't work as the line numbers are in their own container, so we need to move them into the code. Now with this, it becomes much simpler to do what we want (with CSS mostly). One downside: the highlighting became more complex and slow as we need to generate some extra HTML tags directly into the highlighting process. However that also allows to not have a huge HTML size increase. You can test the result [here](https://rustdoc.crud.net/imperio/move-line-numbers-into-code/scrape_examples/fn.test_many.html) and [here](https://rustdoc.crud.net/imperio/move-line-numbers-into-code/src/scrape_examples/lib.rs.html#10). The appearance should have close to no changes. r? ``@notriddle``
2 parents 1073aea + b594b9f commit ba32d8b

18 files changed

+280
-222
lines changed
 

‎src/librustdoc/html/highlight.rs

Lines changed: 101 additions & 11 deletions
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/highlight/tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fn test_html_highlighting() {
2323
let src = include_str!("fixtures/sample.rs");
2424
let html = {
2525
let mut out = Buffer::new();
26-
write_code(&mut out, src, None, None);
26+
write_code(&mut out, src, None, None, None);
2727
format!("{STYLE}<pre><code>{}</code></pre>\n", out.into_inner())
2828
};
2929
expect_file!["fixtures/sample.html"].assert_eq(&html);
@@ -37,7 +37,7 @@ fn test_dos_backline() {
3737
println!(\"foo\");\r\n\
3838
}\r\n";
3939
let mut html = Buffer::new();
40-
write_code(&mut html, src, None, None);
40+
write_code(&mut html, src, None, None, None);
4141
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
4242
});
4343
}
@@ -51,7 +51,7 @@ let x = super::b::foo;
5151
let y = Self::whatever;";
5252

5353
let mut html = Buffer::new();
54-
write_code(&mut html, src, None, None);
54+
write_code(&mut html, src, None, None, None);
5555
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
5656
});
5757
}
@@ -61,7 +61,7 @@ fn test_union_highlighting() {
6161
create_default_session_globals_then(|| {
6262
let src = include_str!("fixtures/union.rs");
6363
let mut html = Buffer::new();
64-
write_code(&mut html, src, None, None);
64+
write_code(&mut html, src, None, None, None);
6565
expect_file!["fixtures/union.html"].assert_eq(&html.into_inner());
6666
});
6767
}
@@ -78,7 +78,7 @@ let a = 4;";
7878
decorations.insert("example2", vec![(22, 32)]);
7979

8080
let mut html = Buffer::new();
81-
write_code(&mut html, src, None, Some(&DecorationInfo(decorations)));
81+
write_code(&mut html, src, None, Some(&DecorationInfo(decorations)), None);
8282
expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner());
8383
});
8484
}

‎src/librustdoc/html/sources.rs

Lines changed: 17 additions & 7 deletions
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
}

‎src/librustdoc/html/static/css/rustdoc.css

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\
4040
--docblock-indent: 24px;
4141
--font-family: "Source Serif 4", NanumBarunGothic, serif;
4242
--font-family-code: "Source Code Pro", monospace;
43+
--line-number-padding: 4px;
4344
}
4445

4546
:root.sans-serif {
@@ -450,9 +451,7 @@ pre.item-decl {
450451

451452
.src .content pre {
452453
padding: 20px;
453-
}
454-
.rustdoc.src .example-wrap .src-line-numbers {
455-
padding: 20px 0 20px 4px;
454+
padding-left: 16px;
456455
}
457456

458457
img {
@@ -901,29 +900,58 @@ both the code example and the line numbers, so we need to remove the radius in t
901900
min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */
902901
flex-grow: 0;
903902
text-align: right;
903+
-moz-user-select: none;
904904
-webkit-user-select: none;
905+
-ms-user-select: none;
905906
user-select: none;
906907
padding: 14px 8px;
907908
padding-right: 2px;
908909
color: var(--src-line-numbers-span-color);
909910
}
910911

911-
.rustdoc .scraped-example .example-wrap .src-line-numbers {
912-
padding: 0;
912+
.example-wrap.digits-1 [data-nosnippet] {
913+
width: calc(1ch + var(--line-number-padding) * 2);
914+
}
915+
.example-wrap.digits-2 [data-nosnippet] {
916+
width: calc(2ch + var(--line-number-padding) * 2);
917+
}
918+
.example-wrap.digits-3 [data-nosnippet] {
919+
width: calc(3ch + var(--line-number-padding) * 2);
920+
}
921+
.example-wrap.digits-4 [data-nosnippet] {
922+
width: calc(4ch + var(--line-number-padding) * 2);
923+
}
924+
.example-wrap.digits-5 [data-nosnippet] {
925+
width: calc(5ch + var(--line-number-padding) * 2);
926+
}
927+
.example-wrap.digits-6 [data-nosnippet] {
928+
width: calc(6ch + var(--line-number-padding) * 2);
929+
}
930+
.example-wrap.digits-7 [data-nosnippet] {
931+
width: calc(7ch + var(--line-number-padding) * 2);
913932
}
914-
.rustdoc .src-line-numbers pre {
915-
padding: 14px 0;
933+
.example-wrap.digits-8 [data-nosnippet] {
934+
width: calc(8ch + var(--line-number-padding) * 2);
916935
}
917-
.src-line-numbers a, .src-line-numbers span {
936+
.example-wrap.digits-9 [data-nosnippet] {
937+
width: calc(9ch + var(--line-number-padding) * 2);
938+
}
939+
940+
.example-wrap [data-nosnippet] {
918941
color: var(--src-line-numbers-span-color);
919-
padding: 0 8px;
942+
text-align: right;
943+
display: inline-block;
944+
margin-right: 20px;
945+
-moz-user-select: none;
946+
-webkit-user-select: none;
947+
-ms-user-select: none;
948+
user-select: none;
949+
padding: 0 4px;
920950
}
921-
.src-line-numbers :target {
922-
background-color: transparent;
951+
.example-wrap [data-nosnippet]:target {
923952
border-right: none;
924-
padding: 0 8px;
925953
}
926-
.src-line-numbers .line-highlighted {
954+
.example-wrap .line-highlighted[data-nosnippet] {
927955
background-color: var(--src-line-number-highlighted-background-color);
928956
}
929957

@@ -1110,7 +1138,7 @@ because of the `[-]` element which would overlap with it. */
11101138
}
11111139

11121140
.main-heading a:hover,
1113-
.example-wrap .rust a:hover,
1141+
.example-wrap .rust a:hover:not([data-nosnippet]),
11141142
.all-items a:hover,
11151143
.docblock a:not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
11161144
.item-table dd a:not(.scrape-help):not(.tooltip):hover,
@@ -1568,7 +1596,7 @@ pre.rust .doccomment {
15681596
color: var(--code-highlight-doc-comment-color);
15691597
}
15701598

1571-
.rustdoc.src .example-wrap pre.rust a {
1599+
.rustdoc.src .example-wrap pre.rust a:not([data-nosnippet]) {
15721600
background: var(--codeblock-link-background);
15731601
}
15741602

@@ -1759,8 +1787,7 @@ instead, we check that it's not a "finger" cursor.
17591787
}
17601788
}
17611789

1762-
:target {
1763-
padding-right: 3px;
1790+
:target:not([data-nosnippet]) {
17641791
background-color: var(--target-background-color);
17651792
border-right: 3px solid var(--target-border-color);
17661793
}
@@ -3153,7 +3180,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
31533180
color: #ff7733;
31543181
}
31553182

3156-
:root[data-theme="ayu"] .src-line-numbers .line-highlighted {
3183+
:root[data-theme="ayu"] a[data-nosnippet].line-highlighted {
31573184
color: #708090;
31583185
padding-right: 7px;
31593186
border-right: 1px solid #ffb44c;

‎src/librustdoc/html/static/js/scrape-examples.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
// Scroll code block to the given code location
1818
function scrollToLoc(elt, loc, isHidden) {
19-
const lines = elt.querySelector(".src-line-numbers > pre");
19+
const lines = elt.querySelectorAll("[data-nosnippet]");
2020
let scrollOffset;
2121

2222
// If the block is greater than the size of the viewer,
@@ -25,17 +25,17 @@
2525
const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
2626
if (loc[1] - loc[0] > maxLines) {
2727
const line = Math.max(0, loc[0] - 1);
28-
scrollOffset = lines.children[line].offsetTop;
28+
scrollOffset = lines[line].offsetTop;
2929
} else {
3030
const halfHeight = elt.offsetHeight / 2;
31-
const offsetTop = lines.children[loc[0]].offsetTop;
32-
const lastLine = lines.children[loc[1]];
31+
const offsetTop = lines[loc[0]].offsetTop;
32+
const lastLine = lines[loc[1]];
3333
const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
3434
const offsetMid = (offsetTop + offsetBot) / 2;
3535
scrollOffset = offsetMid - halfHeight;
3636
}
3737

38-
lines.parentElement.scrollTo(0, scrollOffset);
38+
lines[0].parentElement.scrollTo(0, scrollOffset);
3939
elt.querySelector(".rust").scrollTo(0, scrollOffset);
4040
}
4141

‎src/librustdoc/html/static/js/src-script.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,8 @@ function highlightSrcLines() {
138138
if (x) {
139139
x.scrollIntoView();
140140
}
141-
onEachLazy(document.getElementsByClassName("src-line-numbers"), e => {
142-
onEachLazy(e.getElementsByTagName("a"), i_e => {
143-
removeClass(i_e, "line-highlighted");
144-
});
141+
onEachLazy(document.querySelectorAll("a[data-nosnippet]"), e => {
142+
removeClass(e, "line-highlighted");
145143
});
146144
for (let i = from; i <= to; ++i) {
147145
elem = document.getElementById(i);
@@ -200,7 +198,7 @@ const handleSrcHighlight = (function() {
200198

201199
window.addEventListener("hashchange", highlightSrcLines);
202200

203-
onEachLazy(document.getElementsByClassName("src-line-numbers"), el => {
201+
onEachLazy(document.querySelectorAll("a[data-nosnippet]"), el => {
204202
el.addEventListener("click", handleSrcHighlight);
205203
});
206204

‎src/librustdoc/html/templates/scraped_source.html

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@
22
<div class="scraped-example-title">
33
{{info.name +}} (<a href="{{info.url}}">{{info.title}}</a>) {# #}
44
</div> {# #}
5-
<div class="example-wrap">
6-
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
7-
Do not show "1 2 3 4 5 ..." in web search results. #}
8-
<div class="src-line-numbers" data-nosnippet> {# #}
9-
<pre>
10-
{% for line in lines.clone() %}
11-
{# ~#}
12-
<span>{{line|safe}}</span>
13-
{% endfor %}
14-
</pre> {# #}
15-
</div> {# #}
5+
<div class="example-wrap digits-{{max_nb_digits}}"> {# #}
166
<pre class="rust"> {# #}
177
<code>
188
{{code_html|safe}}

‎src/librustdoc/html/templates/source.html

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,7 @@ <h1> {# #}
99
</div>
1010
{% else %}
1111
{% endmatch %}
12-
<div class="example-wrap">
13-
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
14-
Do not show "1 2 3 4 5 ..." in web search results. #}
15-
<div data-nosnippet><pre class="src-line-numbers">
16-
{% for line in lines.clone() %}
17-
{# ~#}
18-
<a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
19-
{% endfor %}
20-
</pre></div> {# #}
12+
<div class="example-wrap digits-{{max_nb_digits}}"> {# #}
2113
<pre class="rust"> {# #}
2214
<code>
2315
{{code_html|safe}}

‎tests/rustdoc-gui/basic-code.goml

Lines changed: 0 additions & 6 deletions
This file was deleted.

‎tests/rustdoc-gui/docblock-code-block-line-number.goml

Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -111,28 +111,6 @@ wait-for: "pre.example-line-numbers"
111111
// Same check with scraped examples line numbers.
112112
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
113113

114-
assert-css: (
115-
".scraped-example .src-line-numbers > pre",
116-
{
117-
// There should not be a radius on the right of the line numbers.
118-
"border-top-left-radius": "6px",
119-
"border-bottom-left-radius": "6px",
120-
"border-top-right-radius": "0px",
121-
"border-bottom-right-radius": "0px",
122-
},
123-
ALL,
124-
)
125-
assert-css: (
126-
".scraped-example .src-line-numbers",
127-
{
128-
// There should not be a radius on the right of the line numbers.
129-
"border-top-left-radius": "6px",
130-
"border-bottom-left-radius": "6px",
131-
"border-top-right-radius": "0px",
132-
"border-bottom-right-radius": "0px",
133-
},
134-
ALL,
135-
)
136114
assert-css: (
137115
".scraped-example .rust",
138116
{
@@ -149,23 +127,15 @@ define-function: (
149127
"check-padding",
150128
[path, padding_bottom],
151129
block {
152-
assert-css: (|path| + " .src-line-numbers", {
130+
assert-css: (|path| + " span[data-nosnippet]", {
153131
"padding-top": "0px",
154132
"padding-bottom": "0px",
155-
"padding-left": "0px",
156-
"padding-right": "0px",
157-
}, ALL)
158-
assert-css: (|path| + " .src-line-numbers > pre", {
159-
"padding-top": "14px",
160-
"padding-bottom": |padding_bottom|,
161-
"padding-left": "0px",
162-
"padding-right": "0px",
163-
}, ALL)
164-
assert-css: (|path| + " .src-line-numbers > pre > span", {
165-
"padding-top": "0px",
166-
"padding-bottom": "0px",
167-
"padding-left": "8px",
168-
"padding-right": "8px",
133+
"padding-left": "4px",
134+
"padding-right": "4px",
135+
"margin-right": "20px",
136+
"margin-left": "0px",
137+
"margin-top": "0px",
138+
"margin-bottom": "0px",
169139
}, ALL)
170140
},
171141
)
@@ -196,13 +166,13 @@ define-function: ("check-line-numbers-existence", [], block {
196166
wait-for-local-storage-false: {"rustdoc-line-numbers": "true" }
197167
assert-false: ".example-line-numbers"
198168
// Line numbers should still be there.
199-
assert: ".src-line-numbers"
169+
assert-css: ("[data-nosnippet]", { "display": "inline-block"})
200170
// Now disabling the setting.
201171
click: "input#line-numbers"
202172
wait-for-local-storage: {"rustdoc-line-numbers": "true" }
203173
assert-false: ".example-line-numbers"
204174
// Line numbers should still be there.
205-
assert: ".src-line-numbers"
175+
assert-css: ("[data-nosnippet]", { "display": "inline-block"})
206176
// Closing settings menu.
207177
click: "#settings-menu"
208178
wait-for-css: ("#settings", {"display": "none"})
@@ -214,18 +184,16 @@ call-function: ("check-line-numbers-existence", {})
214184

215185
// Now checking the line numbers in the source code page.
216186
click: ".src"
217-
assert-css: (".src-line-numbers", {
218-
"padding-top": "20px",
219-
"padding-bottom": "20px",
220-
"padding-left": "4px",
221-
"padding-right": "0px",
222-
})
223-
assert-css: (".src-line-numbers > a", {
187+
assert-css: ("a[data-nosnippet]", {
224188
"padding-top": "0px",
225189
"padding-bottom": "0px",
226-
"padding-left": "8px",
227-
"padding-right": "8px",
228-
})
190+
"padding-left": "4px",
191+
"padding-right": "4px",
192+
"margin-top": "0px",
193+
"margin-bottom": "0px",
194+
"margin-left": "0px",
195+
"margin-right": "20px",
196+
}, ALL)
229197
// Checking that turning off the line numbers setting won't remove line numbers.
230198
call-function: ("check-line-numbers-existence", {})
231199

‎tests/rustdoc-gui/jump-to-def-background.goml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ define-function: (
88
block {
99
call-function: ("switch-theme", {"theme": |theme|})
1010
assert-css: (
11-
"body.src .example-wrap pre.rust a",
11+
"body.src .example-wrap pre.rust a:not([data-nosnippet])",
1212
{"background-color": |background_color|},
1313
ALL,
1414
)

‎tests/rustdoc-gui/scrape-examples-button-focus.goml

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,52 +4,18 @@ go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html"
44

55
// The next/prev buttons vertically scroll the code viewport between examples
66
move-cursor-to: ".scraped-example-list > .scraped-example"
7-
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
8-
"scrollTop": initialScrollTop,
9-
})
7+
wait-for: ".scraped-example-list > .scraped-example .next"
8+
store-value: (initialScrollTop, 250)
109
assert-property: (".scraped-example-list > .scraped-example .rust", {
1110
"scrollTop": |initialScrollTop|,
12-
})
11+
}, NEAR)
1312
focus: ".scraped-example-list > .scraped-example .next"
1413
press-key: "Enter"
15-
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
16-
"scrollTop": |initialScrollTop|
17-
}, NEAR)
1814
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
1915
"scrollTop": |initialScrollTop|
2016
}, NEAR)
2117
focus: ".scraped-example-list > .scraped-example .prev"
2218
press-key: "Enter"
23-
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
24-
"scrollTop": |initialScrollTop|
25-
}, NEAR)
2619
assert-property: (".scraped-example-list > .scraped-example .rust", {
2720
"scrollTop": |initialScrollTop|
2821
}, NEAR)
29-
30-
// The expand button increases the scrollHeight of the minimized code viewport
31-
store-property: (".scraped-example-list > .scraped-example pre", {"offsetHeight": smallOffsetHeight})
32-
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
33-
"scrollHeight": |smallOffsetHeight|
34-
}, NEAR)
35-
assert-property: (".scraped-example-list > .scraped-example .rust", {
36-
"scrollHeight": |smallOffsetHeight|
37-
}, NEAR)
38-
focus: ".scraped-example-list > .scraped-example .expand"
39-
press-key: "Enter"
40-
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
41-
"offsetHeight": |smallOffsetHeight|
42-
}, NEAR)
43-
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
44-
"offsetHeight": |smallOffsetHeight|
45-
}, NEAR)
46-
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
47-
"offsetHeight": fullOffsetHeight,
48-
})
49-
assert-property: (".scraped-example-list > .scraped-example .rust", {
50-
"offsetHeight": |fullOffsetHeight|,
51-
"scrollHeight": |fullOffsetHeight|,
52-
})
53-
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
54-
"scrollHeight": |fullOffsetHeight|
55-
}, NEAR)

‎tests/rustdoc-gui/scrape-examples-layout.goml

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,38 @@ go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
33

44
set-window-size: (1000, 1000)
55

6+
// We move the mouse over the scraped example for the prev button to be generated.
7+
move-cursor-to: ".scraped-example"
8+
69
// Check that it's not zero.
710
assert-property-false: (
8-
".more-scraped-examples .scraped-example .src-line-numbers",
11+
".more-scraped-examples .scraped-example span[data-nosnippet]",
912
{"clientWidth": "0"}
1013
)
1114

1215
// Check that examples with very long lines have the same width as ones that don't.
1316
store-property: (
14-
".more-scraped-examples .scraped-example:nth-child(2) .src-line-numbers",
17+
".more-scraped-examples .scraped-example:nth-child(2) span[data-nosnippet]",
1518
{"clientWidth": clientWidth},
1619
)
1720

1821
assert-property: (
19-
".more-scraped-examples .scraped-example:nth-child(3) .src-line-numbers",
22+
".more-scraped-examples .scraped-example:nth-child(3) span[data-nosnippet]",
2023
{"clientWidth": |clientWidth|}
2124
)
2225

2326
assert-property: (
24-
".more-scraped-examples .scraped-example:nth-child(4) .src-line-numbers",
27+
".more-scraped-examples .scraped-example:nth-child(4) span[data-nosnippet]",
2528
{"clientWidth": |clientWidth|}
2629
)
2730

2831
assert-property: (
29-
".more-scraped-examples .scraped-example:nth-child(5) .src-line-numbers",
32+
".more-scraped-examples .scraped-example:nth-child(5) span[data-nosnippet]",
3033
{"clientWidth": |clientWidth|}
3134
)
3235

3336
assert-property: (
34-
".more-scraped-examples .scraped-example:nth-child(6) .src-line-numbers",
37+
".more-scraped-examples .scraped-example:nth-child(6) span[data-nosnippet]",
3538
{"clientWidth": |clientWidth|}
3639
)
3740

@@ -55,25 +58,6 @@ assert-size: (".more-scraped-examples .scraped-example .example-wrap", {
5558
"width": |width|,
5659
})
5760

58-
// Check that the expand button works and also that line number aligns with code.
59-
move-cursor-to: ".scraped-example .rust"
60-
click: ".scraped-example .button-holder .expand"
61-
wait-for: ".scraped-example.expanded"
62-
// They should have the same y position.
63-
compare-elements-position: (
64-
".scraped-example.expanded .src-line-numbers pre span",
65-
".scraped-example.expanded .rust code",
66-
["y"],
67-
)
68-
// And they should have the same height.
69-
compare-elements-size: (
70-
".scraped-example.expanded .src-line-numbers",
71-
".scraped-example.expanded .rust",
72-
["height"],
73-
)
74-
// Collapse code again.
75-
click: ".scraped-example .button-holder .expand"
76-
7761
// Check that for both mobile and desktop sizes, the buttons in scraped examples are displayed
7862
// correctly.
7963

@@ -98,7 +82,7 @@ define-function: (
9882
[],
9983
block {
10084
// Title should be above the code.
101-
store-position: (".scraped-example .example-wrap .src-line-numbers", {"x": x, "y": y})
85+
store-position: (".scraped-example .example-wrap", {"x": x, "y": y})
10286
store-size: (".scraped-example .scraped-example-title", { "height": title_height })
10387

10488
assert-position: (".scraped-example .scraped-example-title", {
@@ -107,10 +91,13 @@ define-function: (
10791
})
10892

10993
// Line numbers should be right beside the code.
110-
compare-elements-position: (
111-
".scraped-example .example-wrap .src-line-numbers",
112-
".scraped-example .example-wrap .rust",
113-
["y"],
94+
compare-elements-position-near: (
95+
".scraped-example .example-wrap span[data-nosnippet]",
96+
// On the first line, the code starts with `fn main` so we have a keyword.
97+
".scraped-example .example-wrap .rust span.kw",
98+
// They're not exactly the same size but since they're on the same line,
99+
// it's kinda the same.
100+
{"y": 2},
114101
)
115102
}
116103
)

‎tests/rustdoc-gui/source-anchor-scroll.goml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ set-window-size: (600, 800)
88
assert-property: ("html", {"scrollTop": "0"})
99

1010
click: '//a[text() = "barbar" and @href="#5-7"]'
11-
assert-property: ("html", {"scrollTop": "208"})
11+
assert-property: ("html", {"scrollTop": "206"})
1212
click: '//a[text() = "bar" and @href="#28-36"]'
1313
assert-property: ("html", {"scrollTop": "239"})
1414
click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]'
15-
assert-property: ("html", {"scrollTop": "136"})
15+
assert-property: ("html", {"scrollTop": "134"})
1616

1717
// We now check that clicking on lines doesn't change the scroll
1818
// Extra information: the "sub_fn" function header is on line 1.
1919
click: '//*[@id="6"]'
20-
assert-property: ("html", {"scrollTop": "136"})
20+
assert-property: ("html", {"scrollTop": "134"})

‎tests/rustdoc-gui/source-code-page-code-scroll.goml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
33
set-window-size: (800, 1000)
44
// "scrollWidth" should be superior than "clientWidth".
5-
assert-property: ("body", {"scrollWidth": 1776, "clientWidth": 800})
5+
assert-property: ("body", {"scrollWidth": 1780, "clientWidth": 800})
66

77
// Both properties should be equal (ie, no scroll on the code block).
8-
assert-property: (".example-wrap .rust", {"scrollWidth": 1662, "clientWidth": 1662})
8+
assert-property: (".example-wrap .rust", {"scrollWidth": 1715, "clientWidth": 1715})

‎tests/rustdoc-gui/source-code-page.goml

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ include: "utils.goml"
33
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
44
show-text: true
55
// Check that we can click on the line number.
6-
click: ".src-line-numbers > a:nth-child(4)" // This is the anchor for line 4.
6+
click: "//a[@data-nosnippet and text()='4']" // This is the anchor for line 4.
77
// Ensure that the page URL was updated.
88
assert-document-property: ({"URL": "lib.rs.html#4"}, ENDS_WITH)
99
assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
@@ -14,24 +14,24 @@ assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
1414
assert-css: ("//*[@id='4']", {"border-right-width": "0px"})
1515
// We now check that the good anchors are highlighted
1616
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#4-6"
17-
assert-attribute-false: (".src-line-numbers > a:nth-child(3)", {"class": "line-highlighted"})
18-
assert-attribute: (".src-line-numbers > a:nth-child(4)", {"class": "line-highlighted"})
19-
assert-attribute: (".src-line-numbers > a:nth-child(5)", {"class": "line-highlighted"})
20-
assert-attribute: (".src-line-numbers > a:nth-child(6)", {"class": "line-highlighted"})
21-
assert-attribute-false: (".src-line-numbers > a:nth-child(7)", {"class": "line-highlighted"})
17+
assert-attribute-false: ("//a[@data-nosnippet and text()='3']", {"class": "line-highlighted"})
18+
assert-attribute: ("//a[@data-nosnippet and text()='4']", {"class": "line-highlighted"})
19+
assert-attribute: ("//a[@data-nosnippet and text()='5']", {"class": "line-highlighted"})
20+
assert-attribute: ("//a[@data-nosnippet and text()='6']", {"class": "line-highlighted"})
21+
assert-attribute-false: ("//a[@data-nosnippet and text()='7']", {"class": "line-highlighted"})
2222

2323
define-function: (
2424
"check-colors",
2525
[theme, color, background_color, highlight_color, highlight_background_color],
2626
block {
2727
call-function: ("switch-theme", {"theme": |theme|})
2828
assert-css: (
29-
".src-line-numbers > a:not(.line-highlighted)",
29+
"a[data-nosnippet]:not(.line-highlighted)",
3030
{"color": |color|, "background-color": |background_color|},
3131
ALL,
3232
)
3333
assert-css: (
34-
".src-line-numbers > a.line-highlighted",
34+
"a[data-nosnippet].line-highlighted",
3535
{"color": |highlight_color|, "background-color": |highlight_background_color|},
3636
ALL,
3737
)
@@ -61,37 +61,37 @@ call-function: ("check-colors", {
6161
})
6262

6363
// This is to ensure that the content is correctly align with the line numbers.
64-
compare-elements-position: ("//*[@id='1']", ".rust > code > span", ["y"])
64+
compare-elements-position-near: ("//*[@id='1']", ".rust > code > span", {"y": 2})
6565
// Check the `href` property so that users can treat anchors as links.
66-
assert-property: (".src-line-numbers > a:nth-child(1)", {
66+
assert-property: ("//a[@data-nosnippet and text()='1']", {
6767
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#1"
6868
}, ENDS_WITH)
69-
assert-property: (".src-line-numbers > a:nth-child(2)", {
69+
assert-property: ("//a[@data-nosnippet and text()='2']", {
7070
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#2"
7171
}, ENDS_WITH)
72-
assert-property: (".src-line-numbers > a:nth-child(3)", {
72+
assert-property: ("//a[@data-nosnippet and text()='3']", {
7373
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#3"
7474
}, ENDS_WITH)
75-
assert-property: (".src-line-numbers > a:nth-child(4)", {
75+
assert-property: ("//a[@data-nosnippet and text()='4']", {
7676
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#4"
7777
}, ENDS_WITH)
78-
assert-property: (".src-line-numbers > a:nth-child(5)", {
78+
assert-property: ("//a[@data-nosnippet and text()='5']", {
7979
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#5"
8080
}, ENDS_WITH)
81-
assert-property: (".src-line-numbers > a:nth-child(6)", {
81+
assert-property: ("//a[@data-nosnippet and text()='6']", {
8282
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#6"
8383
}, ENDS_WITH)
8484

8585
// Assert that the line numbers text is aligned to the right.
86-
assert-css: (".src-line-numbers", {"text-align": "right"})
86+
assert-css: ("a[data-nosnippet]", {"text-align": "right"}, ALL)
8787

8888
// Now let's check that clicking on something else than the line number doesn't
8989
// do anything (and certainly not add a `#NaN` to the URL!).
9090
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
9191
// We use this assert-position to know where we will click.
92-
assert-position: ("//*[@id='1']", {"x": 88, "y": 171})
93-
// We click on the left of the "1" anchor but still in the "src-line-number" `<pre>`.
94-
click: (163, 77)
92+
assert-position: ("//*[@id='1']", {"x": 81, "y": 169})
93+
// We click on the left of the "1" anchor but still in the `a[data-nosnippet]`.
94+
click: (77, 163)
9595
assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
9696

9797
// Checking the source code sidebar.

‎tests/rustdoc/check-source-code-urls-to-def.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ fn babar() {}
3131
//@ has - '//pre[@class="rust"]//a/@href' '/struct.String.html'
3232
//@ has - '//pre[@class="rust"]//a/@href' '/primitive.u32.html'
3333
//@ has - '//pre[@class="rust"]//a/@href' '/primitive.str.html'
34-
//@ count - '//pre[@class="rust"]//a[@href="#23"]' 5
34+
// The 5 links to line 23 and the line 23 itself.
35+
//@ count - '//pre[@class="rust"]//a[@href="#23"]' 6
3536
//@ has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
3637
// 'source_code::SourceCode'
3738
pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) {
@@ -50,8 +51,8 @@ pub fn foo2<T: bar::sub::Trait, V: Trait>(t: &T, v: &V, b: bool) {}
5051
pub trait AnotherTrait {}
5152
pub trait WhyNot {}
5253

53-
//@ has - '//pre[@class="rust"]//a[@href="#50"]' 'AnotherTrait'
54-
//@ has - '//pre[@class="rust"]//a[@href="#51"]' 'WhyNot'
54+
//@ has - '//pre[@class="rust"]//a[@href="#51"]' 'AnotherTrait'
55+
//@ has - '//pre[@class="rust"]//a[@href="#52"]' 'WhyNot'
5556
pub fn foo3<T, V>(t: &T, v: &V)
5657
where
5758
T: AnotherTrait,
@@ -60,7 +61,7 @@ where
6061

6162
pub trait AnotherTrait2 {}
6263

63-
//@ has - '//pre[@class="rust"]//a[@href="#61"]' 'AnotherTrait2'
64+
//@ has - '//pre[@class="rust"]//a[@href="#62"]' 'AnotherTrait2'
6465
pub fn foo4() {
6566
let x: Vec<&dyn AnotherTrait2> = Vec::new();
6667
}

‎tests/rustdoc/source-line-numbers.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This test ensures that we have the expected number of line generated.
2+
3+
#![crate_name = "foo"]
4+
5+
//@ has 'src/foo/source-line-numbers.rs.html'
6+
//@ count - '//a[@data-nosnippet]' 35
7+
//@ has - '//a[@id="35"]' '35'
8+
9+
#[
10+
macro_export
11+
]
12+
macro_rules! bar {
13+
($x:ident) => {{
14+
$x += 2;
15+
$x *= 2;
16+
}}
17+
}
18+
19+
/*
20+
multi line
21+
comment
22+
*/
23+
fn x(_: u8, _: u8) {}
24+
25+
fn foo() {
26+
let mut y = 0;
27+
bar!(y);
28+
println!("
29+
{y}
30+
");
31+
x(
32+
1,
33+
2,
34+
);
35+
}

0 commit comments

Comments
 (0)
Please sign in to comment.