1
Fork 0

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 https://github.com/rust-lang/rust/issues/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``
This commit is contained in:
Guillaume Gomez 2025-02-12 10:46:39 +01:00 committed by GitHub
commit ba32d8bdee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 280 additions and 222 deletions

View file

@ -54,7 +54,7 @@ pub(crate) fn render_example_with_highlighting(
extra_classes: &[String],
) {
write_header(out, "rust-example-rendered", None, tooltip, extra_classes);
write_code(out, src, None, None);
write_code(out, src, None, None, None);
write_footer(out, playground_button);
}
@ -150,6 +150,7 @@ struct TokenHandler<'a, 'tcx, F: Write> {
/// used to generate links.
pending_elems: Vec<(&'a str, Option<Class>)>,
href_context: Option<HrefContext<'a, 'tcx>>,
write_line_number: fn(&mut F, u32, &'static str),
}
impl<F: Write> TokenHandler<'_, '_, F> {
@ -182,7 +183,14 @@ impl<F: Write> TokenHandler<'_, '_, F> {
&& can_merge(current_class, Some(*parent_class), "")
{
for (text, class) in self.pending_elems.iter() {
string(self.out, EscapeBodyText(text), *class, &self.href_context, false);
string(
self.out,
EscapeBodyText(text),
*class,
&self.href_context,
false,
self.write_line_number,
);
}
} else {
// 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> {
*class,
&self.href_context,
close_tag.is_none(),
self.write_line_number,
);
}
if let Some(close_tag) = close_tag {
@ -213,6 +222,11 @@ impl<F: Write> TokenHandler<'_, '_, F> {
self.pending_elems.clear();
true
}
#[inline]
fn write_line_number(&mut self, line: u32, extra: &'static str) {
(self.write_line_number)(&mut self.out, line, extra);
}
}
impl<F: Write> Drop for TokenHandler<'_, '_, F> {
@ -226,6 +240,43 @@ impl<F: Write> Drop for TokenHandler<'_, '_, F> {
}
}
fn write_scraped_line_number(out: &mut impl Write, line: u32, extra: &'static str) {
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
// Do not show "1 2 3 4 5 ..." in web search results.
write!(out, "{extra}<span data-nosnippet>{line}</span>",).unwrap();
}
fn write_line_number(out: &mut impl Write, line: u32, extra: &'static str) {
// https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
// Do not show "1 2 3 4 5 ..." in web search results.
write!(out, "{extra}<a href=#{line} id={line} data-nosnippet>{line}</a>",).unwrap();
}
fn empty_line_number(out: &mut impl Write, _: u32, extra: &'static str) {
out.write_str(extra).unwrap();
}
#[derive(Clone, Copy)]
pub(super) struct LineInfo {
pub(super) start_line: u32,
max_lines: u32,
pub(super) is_scraped_example: bool,
}
impl LineInfo {
pub(super) fn new(max_lines: u32) -> Self {
Self { start_line: 1, max_lines: max_lines + 1, is_scraped_example: false }
}
pub(super) fn new_scraped(max_lines: u32, start_line: u32) -> Self {
Self {
start_line: start_line + 1,
max_lines: max_lines + start_line + 1,
is_scraped_example: true,
}
}
}
/// Convert the given `src` source code into HTML by adding classes for highlighting.
///
/// 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(
src: &str,
href_context: Option<HrefContext<'_, '_>>,
decoration_info: Option<&DecorationInfo>,
line_info: Option<LineInfo>,
) {
// This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n");
@ -252,6 +304,23 @@ pub(super) fn write_code(
current_class: None,
pending_elems: Vec::new(),
href_context,
write_line_number: match line_info {
Some(line_info) => {
if line_info.is_scraped_example {
write_scraped_line_number
} else {
write_line_number
}
}
None => empty_line_number,
},
};
let (mut line, max_lines) = if let Some(line_info) = line_info {
token_handler.write_line_number(line_info.start_line, "");
(line_info.start_line, line_info.max_lines)
} else {
(0, u32::MAX)
};
Classifier::new(
@ -282,7 +351,14 @@ pub(super) fn write_code(
if need_current_class_update {
token_handler.current_class = class.map(Class::dummy);
}
token_handler.pending_elems.push((text, class));
if text == "\n" {
line += 1;
if line < max_lines {
token_handler.pending_elems.push((text, Some(Class::Backline(line))));
}
} else {
token_handler.pending_elems.push((text, class));
}
}
Highlight::EnterSpan { class } => {
let mut should_add = true;
@ -348,6 +424,7 @@ enum Class {
PreludeVal(Span),
QuestionMark,
Decoration(&'static str),
Backline(u32),
}
impl Class {
@ -396,6 +473,7 @@ impl Class {
Class::PreludeVal(_) => "prelude-val",
Class::QuestionMark => "question-mark",
Class::Decoration(kind) => kind,
Class::Backline(_) => "",
}
}
@ -419,7 +497,8 @@ impl Class {
| Self::Bool
| Self::Lifetime
| Self::QuestionMark
| Self::Decoration(_) => None,
| Self::Decoration(_)
| Self::Backline(_) => None,
}
}
}
@ -694,8 +773,13 @@ impl<'src> Classifier<'src> {
) {
let lookahead = self.peek();
let no_highlight = |sink: &mut dyn FnMut(_)| sink(Highlight::Token { text, class: None });
let whitespace = |sink: &mut dyn FnMut(_)| {
for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
sink(Highlight::Token { text: part, class: None });
}
};
let class = match token {
TokenKind::Whitespace => return no_highlight(sink),
TokenKind::Whitespace => return whitespace(sink),
TokenKind::LineComment { doc_style } | TokenKind::BlockComment { doc_style, .. } => {
if doc_style.is_some() {
Class::DocComment
@ -716,7 +800,7 @@ impl<'src> Classifier<'src> {
// or a reference or pointer type. Unless, of course, it looks like
// a logical and or a multiplication operator: `&&` or `* `.
TokenKind::Star => match self.tokens.peek() {
Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
Some((TokenKind::Whitespace, _)) => return whitespace(sink),
Some((TokenKind::Ident, "mut")) => {
self.next();
sink(Highlight::Token { text: "*mut", class: Some(Class::RefKeyWord) });
@ -740,7 +824,7 @@ impl<'src> Classifier<'src> {
sink(Highlight::Token { text: "&=", class: None });
return;
}
Some((TokenKind::Whitespace, _)) => return no_highlight(sink),
Some((TokenKind::Whitespace, _)) => return whitespace(sink),
Some((TokenKind::Ident, "mut")) => {
self.next();
sink(Highlight::Token { text: "&mut", class: Some(Class::RefKeyWord) });
@ -887,7 +971,9 @@ impl<'src> Classifier<'src> {
};
// Anything that didn't return above is the simple case where we the
// class just spans a single token, so we can use the `string` method.
sink(Highlight::Token { text, class: Some(class) });
for part in text.split('\n').intersperse("\n").filter(|s| !s.is_empty()) {
sink(Highlight::Token { text: part, class: Some(class) });
}
}
fn peek(&mut self) -> Option<TokenKind> {
@ -939,14 +1025,18 @@ fn exit_span(out: &mut impl Write, closing_tag: &str) {
/// Note that if `context` is not `None` and that the given `klass` contains a `Span`, the function
/// will then try to find this `span` in the `span_correspondence_map`. If found, it'll then
/// generate a link for this element (which corresponds to where its definition is located).
fn string<T: Display>(
out: &mut impl Write,
fn string<T: Display, W: Write>(
out: &mut W,
text: T,
klass: Option<Class>,
href_context: &Option<HrefContext<'_, '_>>,
open_tag: bool,
write_line_number_callback: fn(&mut W, u32, &'static str),
) {
if let Some(closing_tag) = string_without_closing_tag(out, text, klass, href_context, open_tag)
if let Some(Class::Backline(line)) = klass {
write_line_number_callback(out, line, "\n");
} else if let Some(closing_tag) =
string_without_closing_tag(out, text, klass, href_context, open_tag)
{
out.write_str(closing_tag).unwrap();
}

View file

@ -23,7 +23,7 @@ fn test_html_highlighting() {
let src = include_str!("fixtures/sample.rs");
let html = {
let mut out = Buffer::new();
write_code(&mut out, src, None, None);
write_code(&mut out, src, None, None, None);
format!("{STYLE}<pre><code>{}</code></pre>\n", out.into_inner())
};
expect_file!["fixtures/sample.html"].assert_eq(&html);
@ -37,7 +37,7 @@ fn test_dos_backline() {
println!(\"foo\");\r\n\
}\r\n";
let mut html = Buffer::new();
write_code(&mut html, src, None, None);
write_code(&mut html, src, None, None, None);
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
});
}
@ -51,7 +51,7 @@ let x = super::b::foo;
let y = Self::whatever;";
let mut html = Buffer::new();
write_code(&mut html, src, None, None);
write_code(&mut html, src, None, None, None);
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
});
}
@ -61,7 +61,7 @@ fn test_union_highlighting() {
create_default_session_globals_then(|| {
let src = include_str!("fixtures/union.rs");
let mut html = Buffer::new();
write_code(&mut html, src, None, None);
write_code(&mut html, src, None, None, None);
expect_file!["fixtures/union.html"].assert_eq(&html.into_inner());
});
}
@ -78,7 +78,7 @@ let a = 4;";
decorations.insert("example2", vec![(22, 32)]);
let mut html = Buffer::new();
write_code(&mut html, src, None, Some(&DecorationInfo(decorations)));
write_code(&mut html, src, None, Some(&DecorationInfo(decorations)), None);
expect_file!["fixtures/decorations.html"].assert_eq(&html.into_inner());
});
}

View file

@ -1,6 +1,5 @@
use std::cell::RefCell;
use std::ffi::OsStr;
use std::ops::RangeInclusive;
use std::path::{Component, Path, PathBuf};
use std::{fmt, fs};
@ -303,16 +302,16 @@ pub(crate) struct ScrapedInfo<'a> {
#[template(path = "scraped_source.html")]
struct ScrapedSource<'a, Code: std::fmt::Display> {
info: ScrapedInfo<'a>,
lines: RangeInclusive<usize>,
code_html: Code,
max_nb_digits: u32,
}
#[derive(Template)]
#[template(path = "source.html")]
struct Source<Code: std::fmt::Display> {
lines: RangeInclusive<usize>,
code_html: Code,
file_path: Option<(String, String)>,
max_nb_digits: u32,
}
pub(crate) enum SourceContext<'a> {
@ -331,6 +330,15 @@ pub(crate) fn print_src(
decoration_info: &highlight::DecorationInfo,
source_context: SourceContext<'_>,
) {
let mut lines = s.lines().count();
let line_info = if let SourceContext::Embedded(ref info) = source_context {
highlight::LineInfo::new_scraped(lines as u32, info.offset as u32)
} else {
highlight::LineInfo::new(lines as u32)
};
if line_info.is_scraped_example {
lines += line_info.start_line as usize;
}
let code = fmt::from_fn(move |fmt| {
let current_href = context
.href_from_span(clean::Span::new(file_span), false)
@ -340,13 +348,13 @@ pub(crate) fn print_src(
s,
Some(highlight::HrefContext { context, file_span, root_path, current_href }),
Some(decoration_info),
Some(line_info),
);
Ok(())
});
let lines = s.lines().count();
let max_nb_digits = if lines > 0 { lines.ilog(10) + 1 } else { 1 };
match source_context {
SourceContext::Standalone { file_path } => Source {
lines: (1..=lines),
code_html: code,
file_path: if let Some(file_name) = file_path.file_name()
&& let Some(file_path) = file_path.parent()
@ -355,12 +363,14 @@ pub(crate) fn print_src(
} else {
None
},
max_nb_digits,
}
.render_into(&mut writer)
.unwrap(),
SourceContext::Embedded(info) => {
let lines = (1 + info.offset)..=(lines + info.offset);
ScrapedSource { info, lines, code_html: code }.render_into(&mut writer).unwrap();
ScrapedSource { info, code_html: code, max_nb_digits }
.render_into(&mut writer)
.unwrap();
}
};
}

View file

@ -40,6 +40,7 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\
--docblock-indent: 24px;
--font-family: "Source Serif 4", NanumBarunGothic, serif;
--font-family-code: "Source Code Pro", monospace;
--line-number-padding: 4px;
}
:root.sans-serif {
@ -450,9 +451,7 @@ pre.item-decl {
.src .content pre {
padding: 20px;
}
.rustdoc.src .example-wrap .src-line-numbers {
padding: 20px 0 20px 4px;
padding-left: 16px;
}
img {
@ -901,29 +900,58 @@ both the code example and the line numbers, so we need to remove the radius in t
min-width: fit-content; /* prevent collapsing into nothing in truncated scraped examples */
flex-grow: 0;
text-align: right;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
padding: 14px 8px;
padding-right: 2px;
color: var(--src-line-numbers-span-color);
}
.rustdoc .scraped-example .example-wrap .src-line-numbers {
padding: 0;
.example-wrap.digits-1 [data-nosnippet] {
width: calc(1ch + var(--line-number-padding) * 2);
}
.rustdoc .src-line-numbers pre {
padding: 14px 0;
.example-wrap.digits-2 [data-nosnippet] {
width: calc(2ch + var(--line-number-padding) * 2);
}
.src-line-numbers a, .src-line-numbers span {
.example-wrap.digits-3 [data-nosnippet] {
width: calc(3ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-4 [data-nosnippet] {
width: calc(4ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-5 [data-nosnippet] {
width: calc(5ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-6 [data-nosnippet] {
width: calc(6ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-7 [data-nosnippet] {
width: calc(7ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-8 [data-nosnippet] {
width: calc(8ch + var(--line-number-padding) * 2);
}
.example-wrap.digits-9 [data-nosnippet] {
width: calc(9ch + var(--line-number-padding) * 2);
}
.example-wrap [data-nosnippet] {
color: var(--src-line-numbers-span-color);
padding: 0 8px;
text-align: right;
display: inline-block;
margin-right: 20px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
padding: 0 4px;
}
.src-line-numbers :target {
background-color: transparent;
.example-wrap [data-nosnippet]:target {
border-right: none;
padding: 0 8px;
}
.src-line-numbers .line-highlighted {
.example-wrap .line-highlighted[data-nosnippet] {
background-color: var(--src-line-number-highlighted-background-color);
}
@ -1110,7 +1138,7 @@ because of the `[-]` element which would overlap with it. */
}
.main-heading a:hover,
.example-wrap .rust a:hover,
.example-wrap .rust a:hover:not([data-nosnippet]),
.all-items a:hover,
.docblock a:not(.scrape-help):not(.tooltip):hover:not(.doc-anchor),
.item-table dd a:not(.scrape-help):not(.tooltip):hover,
@ -1568,7 +1596,7 @@ pre.rust .doccomment {
color: var(--code-highlight-doc-comment-color);
}
.rustdoc.src .example-wrap pre.rust a {
.rustdoc.src .example-wrap pre.rust a:not([data-nosnippet]) {
background: var(--codeblock-link-background);
}
@ -1759,8 +1787,7 @@ instead, we check that it's not a "finger" cursor.
}
}
:target {
padding-right: 3px;
:target:not([data-nosnippet]) {
background-color: var(--target-background-color);
border-right: 3px solid var(--target-border-color);
}
@ -3153,7 +3180,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
color: #ff7733;
}
:root[data-theme="ayu"] .src-line-numbers .line-highlighted {
:root[data-theme="ayu"] a[data-nosnippet].line-highlighted {
color: #708090;
padding-right: 7px;
border-right: 1px solid #ffb44c;

View file

@ -16,7 +16,7 @@
// Scroll code block to the given code location
function scrollToLoc(elt, loc, isHidden) {
const lines = elt.querySelector(".src-line-numbers > pre");
const lines = elt.querySelectorAll("[data-nosnippet]");
let scrollOffset;
// If the block is greater than the size of the viewer,
@ -25,17 +25,17 @@
const maxLines = isHidden ? HIDDEN_MAX_LINES : DEFAULT_MAX_LINES;
if (loc[1] - loc[0] > maxLines) {
const line = Math.max(0, loc[0] - 1);
scrollOffset = lines.children[line].offsetTop;
scrollOffset = lines[line].offsetTop;
} else {
const halfHeight = elt.offsetHeight / 2;
const offsetTop = lines.children[loc[0]].offsetTop;
const lastLine = lines.children[loc[1]];
const offsetTop = lines[loc[0]].offsetTop;
const lastLine = lines[loc[1]];
const offsetBot = lastLine.offsetTop + lastLine.offsetHeight;
const offsetMid = (offsetTop + offsetBot) / 2;
scrollOffset = offsetMid - halfHeight;
}
lines.parentElement.scrollTo(0, scrollOffset);
lines[0].parentElement.scrollTo(0, scrollOffset);
elt.querySelector(".rust").scrollTo(0, scrollOffset);
}

View file

@ -138,10 +138,8 @@ function highlightSrcLines() {
if (x) {
x.scrollIntoView();
}
onEachLazy(document.getElementsByClassName("src-line-numbers"), e => {
onEachLazy(e.getElementsByTagName("a"), i_e => {
removeClass(i_e, "line-highlighted");
});
onEachLazy(document.querySelectorAll("a[data-nosnippet]"), e => {
removeClass(e, "line-highlighted");
});
for (let i = from; i <= to; ++i) {
elem = document.getElementById(i);
@ -200,7 +198,7 @@ const handleSrcHighlight = (function() {
window.addEventListener("hashchange", highlightSrcLines);
onEachLazy(document.getElementsByClassName("src-line-numbers"), el => {
onEachLazy(document.querySelectorAll("a[data-nosnippet]"), el => {
el.addEventListener("click", handleSrcHighlight);
});

View file

@ -2,17 +2,7 @@
<div class="scraped-example-title">
{{info.name +}} (<a href="{{info.url}}">{{info.title}}</a>) {# #}
</div> {# #}
<div class="example-wrap">
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
Do not show "1 2 3 4 5 ..." in web search results. #}
<div class="src-line-numbers" data-nosnippet> {# #}
<pre>
{% for line in lines.clone() %}
{# ~#}
<span>{{line|safe}}</span>
{% endfor %}
</pre> {# #}
</div> {# #}
<div class="example-wrap digits-{{max_nb_digits}}"> {# #}
<pre class="rust"> {# #}
<code>
{{code_html|safe}}

View file

@ -9,15 +9,7 @@
</div>
{% else %}
{% endmatch %}
<div class="example-wrap">
{# https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#data-nosnippet-attr
Do not show "1 2 3 4 5 ..." in web search results. #}
<div data-nosnippet><pre class="src-line-numbers">
{% for line in lines.clone() %}
{# ~#}
<a href="#{{line|safe}}" id="{{line|safe}}">{{line|safe}}</a>
{% endfor %}
</pre></div> {# #}
<div class="example-wrap digits-{{max_nb_digits}}"> {# #}
<pre class="rust"> {# #}
<code>
{{code_html|safe}}

View file

@ -1,6 +0,0 @@
// Small test to ensure the "src-line-numbers" element is only present once on
// the page.
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
click: "a.src"
wait-for: ".src-line-numbers"
assert-count: (".src-line-numbers", 1)

View file

@ -111,28 +111,6 @@ wait-for: "pre.example-line-numbers"
// Same check with scraped examples line numbers.
go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
assert-css: (
".scraped-example .src-line-numbers > pre",
{
// There should not be a radius on the right of the line numbers.
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "0px",
"border-bottom-right-radius": "0px",
},
ALL,
)
assert-css: (
".scraped-example .src-line-numbers",
{
// There should not be a radius on the right of the line numbers.
"border-top-left-radius": "6px",
"border-bottom-left-radius": "6px",
"border-top-right-radius": "0px",
"border-bottom-right-radius": "0px",
},
ALL,
)
assert-css: (
".scraped-example .rust",
{
@ -149,23 +127,15 @@ define-function: (
"check-padding",
[path, padding_bottom],
block {
assert-css: (|path| + " .src-line-numbers", {
assert-css: (|path| + " span[data-nosnippet]", {
"padding-top": "0px",
"padding-bottom": "0px",
"padding-left": "0px",
"padding-right": "0px",
}, ALL)
assert-css: (|path| + " .src-line-numbers > pre", {
"padding-top": "14px",
"padding-bottom": |padding_bottom|,
"padding-left": "0px",
"padding-right": "0px",
}, ALL)
assert-css: (|path| + " .src-line-numbers > pre > span", {
"padding-top": "0px",
"padding-bottom": "0px",
"padding-left": "8px",
"padding-right": "8px",
"padding-left": "4px",
"padding-right": "4px",
"margin-right": "20px",
"margin-left": "0px",
"margin-top": "0px",
"margin-bottom": "0px",
}, ALL)
},
)
@ -196,13 +166,13 @@ define-function: ("check-line-numbers-existence", [], block {
wait-for-local-storage-false: {"rustdoc-line-numbers": "true" }
assert-false: ".example-line-numbers"
// Line numbers should still be there.
assert: ".src-line-numbers"
assert-css: ("[data-nosnippet]", { "display": "inline-block"})
// Now disabling the setting.
click: "input#line-numbers"
wait-for-local-storage: {"rustdoc-line-numbers": "true" }
assert-false: ".example-line-numbers"
// Line numbers should still be there.
assert: ".src-line-numbers"
assert-css: ("[data-nosnippet]", { "display": "inline-block"})
// Closing settings menu.
click: "#settings-menu"
wait-for-css: ("#settings", {"display": "none"})
@ -214,18 +184,16 @@ call-function: ("check-line-numbers-existence", {})
// Now checking the line numbers in the source code page.
click: ".src"
assert-css: (".src-line-numbers", {
"padding-top": "20px",
"padding-bottom": "20px",
"padding-left": "4px",
"padding-right": "0px",
})
assert-css: (".src-line-numbers > a", {
assert-css: ("a[data-nosnippet]", {
"padding-top": "0px",
"padding-bottom": "0px",
"padding-left": "8px",
"padding-right": "8px",
})
"padding-left": "4px",
"padding-right": "4px",
"margin-top": "0px",
"margin-bottom": "0px",
"margin-left": "0px",
"margin-right": "20px",
}, ALL)
// Checking that turning off the line numbers setting won't remove line numbers.
call-function: ("check-line-numbers-existence", {})

View file

@ -8,7 +8,7 @@ define-function: (
block {
call-function: ("switch-theme", {"theme": |theme|})
assert-css: (
"body.src .example-wrap pre.rust a",
"body.src .example-wrap pre.rust a:not([data-nosnippet])",
{"background-color": |background_color|},
ALL,
)

View file

@ -4,52 +4,18 @@ go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test.html"
// The next/prev buttons vertically scroll the code viewport between examples
move-cursor-to: ".scraped-example-list > .scraped-example"
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": initialScrollTop,
})
wait-for: ".scraped-example-list > .scraped-example .next"
store-value: (initialScrollTop, 250)
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|,
})
}, NEAR)
focus: ".scraped-example-list > .scraped-example .next"
press-key: "Enter"
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": |initialScrollTop|
}, NEAR)
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|
}, NEAR)
focus: ".scraped-example-list > .scraped-example .prev"
press-key: "Enter"
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollTop": |initialScrollTop|
}, NEAR)
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollTop": |initialScrollTop|
}, NEAR)
// The expand button increases the scrollHeight of the minimized code viewport
store-property: (".scraped-example-list > .scraped-example pre", {"offsetHeight": smallOffsetHeight})
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollHeight": |smallOffsetHeight|
}, NEAR)
assert-property: (".scraped-example-list > .scraped-example .rust", {
"scrollHeight": |smallOffsetHeight|
}, NEAR)
focus: ".scraped-example-list > .scraped-example .expand"
press-key: "Enter"
assert-property-false: (".scraped-example-list > .scraped-example .src-line-numbers", {
"offsetHeight": |smallOffsetHeight|
}, NEAR)
assert-property-false: (".scraped-example-list > .scraped-example .rust", {
"offsetHeight": |smallOffsetHeight|
}, NEAR)
store-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"offsetHeight": fullOffsetHeight,
})
assert-property: (".scraped-example-list > .scraped-example .rust", {
"offsetHeight": |fullOffsetHeight|,
"scrollHeight": |fullOffsetHeight|,
})
assert-property: (".scraped-example-list > .scraped-example .src-line-numbers", {
"scrollHeight": |fullOffsetHeight|
}, NEAR)

View file

@ -3,35 +3,38 @@ go-to: "file://" + |DOC_PATH| + "/scrape_examples/fn.test_many.html"
set-window-size: (1000, 1000)
// We move the mouse over the scraped example for the prev button to be generated.
move-cursor-to: ".scraped-example"
// Check that it's not zero.
assert-property-false: (
".more-scraped-examples .scraped-example .src-line-numbers",
".more-scraped-examples .scraped-example span[data-nosnippet]",
{"clientWidth": "0"}
)
// Check that examples with very long lines have the same width as ones that don't.
store-property: (
".more-scraped-examples .scraped-example:nth-child(2) .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(2) span[data-nosnippet]",
{"clientWidth": clientWidth},
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(3) .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(3) span[data-nosnippet]",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(4) .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(4) span[data-nosnippet]",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(5) .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(5) span[data-nosnippet]",
{"clientWidth": |clientWidth|}
)
assert-property: (
".more-scraped-examples .scraped-example:nth-child(6) .src-line-numbers",
".more-scraped-examples .scraped-example:nth-child(6) span[data-nosnippet]",
{"clientWidth": |clientWidth|}
)
@ -55,25 +58,6 @@ assert-size: (".more-scraped-examples .scraped-example .example-wrap", {
"width": |width|,
})
// Check that the expand button works and also that line number aligns with code.
move-cursor-to: ".scraped-example .rust"
click: ".scraped-example .button-holder .expand"
wait-for: ".scraped-example.expanded"
// They should have the same y position.
compare-elements-position: (
".scraped-example.expanded .src-line-numbers pre span",
".scraped-example.expanded .rust code",
["y"],
)
// And they should have the same height.
compare-elements-size: (
".scraped-example.expanded .src-line-numbers",
".scraped-example.expanded .rust",
["height"],
)
// Collapse code again.
click: ".scraped-example .button-holder .expand"
// Check that for both mobile and desktop sizes, the buttons in scraped examples are displayed
// correctly.
@ -98,7 +82,7 @@ define-function: (
[],
block {
// Title should be above the code.
store-position: (".scraped-example .example-wrap .src-line-numbers", {"x": x, "y": y})
store-position: (".scraped-example .example-wrap", {"x": x, "y": y})
store-size: (".scraped-example .scraped-example-title", { "height": title_height })
assert-position: (".scraped-example .scraped-example-title", {
@ -107,10 +91,13 @@ define-function: (
})
// Line numbers should be right beside the code.
compare-elements-position: (
".scraped-example .example-wrap .src-line-numbers",
".scraped-example .example-wrap .rust",
["y"],
compare-elements-position-near: (
".scraped-example .example-wrap span[data-nosnippet]",
// On the first line, the code starts with `fn main` so we have a keyword.
".scraped-example .example-wrap .rust span.kw",
// They're not exactly the same size but since they're on the same line,
// it's kinda the same.
{"y": 2},
)
}
)

View file

@ -8,13 +8,13 @@ set-window-size: (600, 800)
assert-property: ("html", {"scrollTop": "0"})
click: '//a[text() = "barbar" and @href="#5-7"]'
assert-property: ("html", {"scrollTop": "208"})
assert-property: ("html", {"scrollTop": "206"})
click: '//a[text() = "bar" and @href="#28-36"]'
assert-property: ("html", {"scrollTop": "239"})
click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]'
assert-property: ("html", {"scrollTop": "136"})
assert-property: ("html", {"scrollTop": "134"})
// We now check that clicking on lines doesn't change the scroll
// Extra information: the "sub_fn" function header is on line 1.
click: '//*[@id="6"]'
assert-property: ("html", {"scrollTop": "136"})
assert-property: ("html", {"scrollTop": "134"})

View file

@ -2,7 +2,7 @@
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
set-window-size: (800, 1000)
// "scrollWidth" should be superior than "clientWidth".
assert-property: ("body", {"scrollWidth": 1776, "clientWidth": 800})
assert-property: ("body", {"scrollWidth": 1780, "clientWidth": 800})
// Both properties should be equal (ie, no scroll on the code block).
assert-property: (".example-wrap .rust", {"scrollWidth": 1662, "clientWidth": 1662})
assert-property: (".example-wrap .rust", {"scrollWidth": 1715, "clientWidth": 1715})

View file

@ -3,7 +3,7 @@ include: "utils.goml"
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
show-text: true
// Check that we can click on the line number.
click: ".src-line-numbers > a:nth-child(4)" // This is the anchor for line 4.
click: "//a[@data-nosnippet and text()='4']" // This is the anchor for line 4.
// Ensure that the page URL was updated.
assert-document-property: ({"URL": "lib.rs.html#4"}, ENDS_WITH)
assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
@ -14,11 +14,11 @@ assert-attribute: ("//*[@id='4']", {"class": "line-highlighted"})
assert-css: ("//*[@id='4']", {"border-right-width": "0px"})
// We now check that the good anchors are highlighted
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html#4-6"
assert-attribute-false: (".src-line-numbers > a:nth-child(3)", {"class": "line-highlighted"})
assert-attribute: (".src-line-numbers > a:nth-child(4)", {"class": "line-highlighted"})
assert-attribute: (".src-line-numbers > a:nth-child(5)", {"class": "line-highlighted"})
assert-attribute: (".src-line-numbers > a:nth-child(6)", {"class": "line-highlighted"})
assert-attribute-false: (".src-line-numbers > a:nth-child(7)", {"class": "line-highlighted"})
assert-attribute-false: ("//a[@data-nosnippet and text()='3']", {"class": "line-highlighted"})
assert-attribute: ("//a[@data-nosnippet and text()='4']", {"class": "line-highlighted"})
assert-attribute: ("//a[@data-nosnippet and text()='5']", {"class": "line-highlighted"})
assert-attribute: ("//a[@data-nosnippet and text()='6']", {"class": "line-highlighted"})
assert-attribute-false: ("//a[@data-nosnippet and text()='7']", {"class": "line-highlighted"})
define-function: (
"check-colors",
@ -26,12 +26,12 @@ define-function: (
block {
call-function: ("switch-theme", {"theme": |theme|})
assert-css: (
".src-line-numbers > a:not(.line-highlighted)",
"a[data-nosnippet]:not(.line-highlighted)",
{"color": |color|, "background-color": |background_color|},
ALL,
)
assert-css: (
".src-line-numbers > a.line-highlighted",
"a[data-nosnippet].line-highlighted",
{"color": |highlight_color|, "background-color": |highlight_background_color|},
ALL,
)
@ -61,37 +61,37 @@ call-function: ("check-colors", {
})
// This is to ensure that the content is correctly align with the line numbers.
compare-elements-position: ("//*[@id='1']", ".rust > code > span", ["y"])
compare-elements-position-near: ("//*[@id='1']", ".rust > code > span", {"y": 2})
// Check the `href` property so that users can treat anchors as links.
assert-property: (".src-line-numbers > a:nth-child(1)", {
assert-property: ("//a[@data-nosnippet and text()='1']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#1"
}, ENDS_WITH)
assert-property: (".src-line-numbers > a:nth-child(2)", {
assert-property: ("//a[@data-nosnippet and text()='2']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#2"
}, ENDS_WITH)
assert-property: (".src-line-numbers > a:nth-child(3)", {
assert-property: ("//a[@data-nosnippet and text()='3']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#3"
}, ENDS_WITH)
assert-property: (".src-line-numbers > a:nth-child(4)", {
assert-property: ("//a[@data-nosnippet and text()='4']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#4"
}, ENDS_WITH)
assert-property: (".src-line-numbers > a:nth-child(5)", {
assert-property: ("//a[@data-nosnippet and text()='5']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#5"
}, ENDS_WITH)
assert-property: (".src-line-numbers > a:nth-child(6)", {
assert-property: ("//a[@data-nosnippet and text()='6']", {
"href": |DOC_PATH| + "/src/test_docs/lib.rs.html#6"
}, ENDS_WITH)
// Assert that the line numbers text is aligned to the right.
assert-css: (".src-line-numbers", {"text-align": "right"})
assert-css: ("a[data-nosnippet]", {"text-align": "right"}, ALL)
// Now let's check that clicking on something else than the line number doesn't
// do anything (and certainly not add a `#NaN` to the URL!).
go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
// We use this assert-position to know where we will click.
assert-position: ("//*[@id='1']", {"x": 88, "y": 171})
// We click on the left of the "1" anchor but still in the "src-line-number" `<pre>`.
click: (163, 77)
assert-position: ("//*[@id='1']", {"x": 81, "y": 169})
// We click on the left of the "1" anchor but still in the `a[data-nosnippet]`.
click: (77, 163)
assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
// Checking the source code sidebar.

View file

@ -31,7 +31,8 @@ fn babar() {}
//@ has - '//pre[@class="rust"]//a/@href' '/struct.String.html'
//@ has - '//pre[@class="rust"]//a/@href' '/primitive.u32.html'
//@ has - '//pre[@class="rust"]//a/@href' '/primitive.str.html'
//@ count - '//pre[@class="rust"]//a[@href="#23"]' 5
// The 5 links to line 23 and the line 23 itself.
//@ count - '//pre[@class="rust"]//a[@href="#23"]' 6
//@ has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \
// 'source_code::SourceCode'
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) {}
pub trait AnotherTrait {}
pub trait WhyNot {}
//@ has - '//pre[@class="rust"]//a[@href="#50"]' 'AnotherTrait'
//@ has - '//pre[@class="rust"]//a[@href="#51"]' 'WhyNot'
//@ has - '//pre[@class="rust"]//a[@href="#51"]' 'AnotherTrait'
//@ has - '//pre[@class="rust"]//a[@href="#52"]' 'WhyNot'
pub fn foo3<T, V>(t: &T, v: &V)
where
T: AnotherTrait,
@ -60,7 +61,7 @@ where
pub trait AnotherTrait2 {}
//@ has - '//pre[@class="rust"]//a[@href="#61"]' 'AnotherTrait2'
//@ has - '//pre[@class="rust"]//a[@href="#62"]' 'AnotherTrait2'
pub fn foo4() {
let x: Vec<&dyn AnotherTrait2> = Vec::new();
}

View file

@ -0,0 +1,35 @@
// This test ensures that we have the expected number of line generated.
#![crate_name = "foo"]
//@ has 'src/foo/source-line-numbers.rs.html'
//@ count - '//a[@data-nosnippet]' 35
//@ has - '//a[@id="35"]' '35'
#[
macro_export
]
macro_rules! bar {
($x:ident) => {{
$x += 2;
$x *= 2;
}}
}
/*
multi line
comment
*/
fn x(_: u8, _: u8) {}
fn foo() {
let mut y = 0;
bar!(y);
println!("
{y}
");
x(
1,
2,
);
}