about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/librustdoc/html/highlight.rs112
-rw-r--r--src/librustdoc/html/sources.rs24
-rw-r--r--src/librustdoc/html/static/css/rustdoc.css58
-rw-r--r--src/librustdoc/html/static/js/scrape-examples.js10
-rw-r--r--src/librustdoc/html/static/js/src-script.js8
-rw-r--r--src/librustdoc/html/templates/scraped_source.html12
-rw-r--r--src/librustdoc/html/templates/source.html10
7 files changed, 168 insertions, 66 deletions
diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs
index 8c91cae4931..fbe3a8d5ba4 100644
--- a/src/librustdoc/html/highlight.rs
+++ b/src/librustdoc/html/highlight.rs
@@ -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();
     }
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 1ac0c10c612..7839ba9ac83 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -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();
         }
     };
 }
diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css
index d0612e997fd..613407cbe1f 100644
--- a/src/librustdoc/html/static/css/rustdoc.css
+++ b/src/librustdoc/html/static/css/rustdoc.css
@@ -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 {
@@ -908,22 +907,46 @@ both the code example and the line numbers, so we need to remove the radius in t
 	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);
+}
+.example-wrap.digits-2 [data-nosnippet] {
+	width: calc(2ch + var(--line-number-padding) * 2);
+}
+.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);
 }
-.rustdoc .src-line-numbers pre {
-	padding: 14px 0;
+.example-wrap.digits-8 [data-nosnippet] {
+	width: calc(8ch + var(--line-number-padding) * 2);
 }
-.src-line-numbers a, .src-line-numbers span {
+.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;
+	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 +1133,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 +1591,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 +1782,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 +3175,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;
diff --git a/src/librustdoc/html/static/js/scrape-examples.js b/src/librustdoc/html/static/js/scrape-examples.js
index d08f15a5bfa..99cbe6daf31 100644
--- a/src/librustdoc/html/static/js/scrape-examples.js
+++ b/src/librustdoc/html/static/js/scrape-examples.js
@@ -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);
     }
 
diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js
index 8f712f4c20c..fc27241334b 100644
--- a/src/librustdoc/html/static/js/src-script.js
+++ b/src/librustdoc/html/static/js/src-script.js
@@ -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);
 });
 
diff --git a/src/librustdoc/html/templates/scraped_source.html b/src/librustdoc/html/templates/scraped_source.html
index bd54bbf58d5..3e69f1c8cad 100644
--- a/src/librustdoc/html/templates/scraped_source.html
+++ b/src/librustdoc/html/templates/scraped_source.html
@@ -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}}
diff --git a/src/librustdoc/html/templates/source.html b/src/librustdoc/html/templates/source.html
index ea530087e6f..454d4c27f1a 100644
--- a/src/librustdoc/html/templates/source.html
+++ b/src/librustdoc/html/templates/source.html
@@ -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}}