Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "copy to clipboard" for all code blocks and "expand" buttons #86892

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ use crate::clean::PrimitiveType;
use crate::html::escape::Escape;
use crate::html::render::Context;

use std::fmt::{Display, Write};
use std::iter::Peekable;
use std::borrow::Cow;
use std::fmt::{Debug, Display, Write};
use std::iter::{once, Peekable};

use rustc_lexer::{LiteralKind, TokenKind};
use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol;
use rustc_span::{BytePos, Span, DUMMY_SP};

use super::format::{self, Buffer};
use super::markdown::Line;
use super::render::LinkFromSrc;

/// This type is needed in case we want to render links on items to allow to go to their definition.
Expand All @@ -31,7 +33,7 @@ crate struct ContextInfo<'a, 'b, 'c> {
}

/// Highlights `src`, returning the HTML output.
crate fn render_with_highlighting(
crate fn render_source_with_highlighting(
src: &str,
out: &mut Buffer,
class: Option<&str>,
Expand All @@ -41,7 +43,31 @@ crate fn render_with_highlighting(
extra_content: Option<Buffer>,
context_info: Option<ContextInfo<'_, '_, '_>>,
) {
debug!("highlighting: ================\n{}\n==============", src);
render_with_highlighting(
once(Line::Shown(Cow::Borrowed(src))),
out,
class,
playground_button,
tooltip,
edition,
extra_content,
context_info,
)
}

/// Highlights `src` containing potential hidden lines, returning the HTML output. If you don't have
/// hidden lines, use [`render_source_with_highlighting`] instead.
crate fn render_with_highlighting<'a>(
src: impl Iterator<Item = Line<'a>> + Debug,
out: &mut Buffer,
class: Option<&str>,
playground_button: Option<&str>,
tooltip: Option<(Option<Edition>, &str)>,
edition: Edition,
extra_content: Option<Buffer>,
context_info: Option<ContextInfo<'_, '_, '_>>,
) {
debug!("highlighting: ================\n{:?}\n==============", src);
if let Some((edition_info, class)) = tooltip {
write!(
out,
Expand All @@ -56,8 +82,8 @@ crate fn render_with_highlighting(
}

write_header(out, class, extra_content);
write_code(out, &src, edition, context_info);
write_footer(out, playground_button);
let expand = write_code(out, src, edition, context_info);
write_footer(out, playground_button, expand);
}

fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buffer>) {
Expand Down Expand Up @@ -86,24 +112,59 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
/// More explanations about spans and how we use them here are provided in the
fn write_code(
out: &mut Buffer,
src: &str,
src: impl Iterator<Item = Line<'a>>,
edition: Edition,
context_info: Option<ContextInfo<'_, '_, '_>>,
) {
// This replace allows to fix how the code source with DOS backline characters is displayed.
let src = src.replace("\r\n", "\n");
Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP))
) -> bool {
let mut iter = src.peekable();
let mut expand = false;

// For each `Line`, we replace DOS backlines with '\n'. This replace allows to fix how the code
// source with DOS backline characters is displayed.
while let Some(line) = iter.next() {
let (before, text, after) = match line {
Line::Hidden(text) => {
expand = true;
("<span class=\"hidden\">", text.replace("\r\n", "\n"), "</span>")
GuillaumeGomez marked this conversation as resolved.
Show resolved Hide resolved
}
Line::Shown(text) => ("", text.replace("\r\n", "\n"), ""),
};
if !before.is_empty() {
out.push_str(before);
}
Classifier::new(
&text.replace("\r\n", "\n"),
edition,
context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP),
)
.highlight(&mut |highlight| {
match highlight {
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
Highlight::EnterSpan { class } => enter_span(out, class),
Highlight::ExitSpan => exit_span(out),
};
});
if iter.peek().is_some() && !text.ends_with('\n') {
out.push_str("\n");
}
if !after.is_empty() {
out.push_str(after);
}
}
expand
}

fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default());
fn write_footer(out: &mut Buffer, playground_button: Option<&str>, expand: bool) {
writeln!(
out,
"</code></pre>\
<div class=\"code-buttons\">\
{}{}<button class=\"copy-code\" onclick=\"copyCode(this)\"></button>\
</div>\
</div>",
playground_button.unwrap_or_default(),
if expand { "<button class=\"expand\" onclick=\"expandCode(this)\"></button>" } else { "" },
);
}

/// How a span of text is classified. Mostly corresponds to token kinds.
Expand Down
7 changes: 7 additions & 0 deletions src/librustdoc/html/highlight/tests.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use super::write_code;
use crate::html::format::Buffer;
use crate::html::markdown::Line;
use expect_test::expect_file;
use rustc_span::create_default_session_globals_then;
use rustc_span::edition::Edition;

use std::borrow::Cow;
use std::iter::once;

const STYLE: &str = r#"
<style>
.kw { color: #8959A8; }
Expand All @@ -20,6 +24,7 @@ const STYLE: &str = r#"
fn test_html_highlighting() {
create_default_session_globals_then(|| {
let src = include_str!("fixtures/sample.rs");
let src = once(Line::Shown(Cow::Borrowed(src)));
let html = {
let mut out = Buffer::new();
write_code(&mut out, src, Edition::Edition2018, None);
Expand All @@ -35,6 +40,7 @@ fn test_dos_backline() {
let src = "pub fn foo() {\r\n\
println!(\"foo\");\r\n\
}\r\n";
let src = once(Line::Shown(Cow::Borrowed(src)));
let mut html = Buffer::new();
write_code(&mut html, src, Edition::Edition2018, None);
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());
Expand All @@ -49,6 +55,7 @@ use self::whatever;
let x = super::b::foo;
let y = Self::whatever;";

let src = once(Line::Shown(Cow::Borrowed(src)));
let mut html = Buffer::new();
write_code(&mut html, src, Edition::Edition2018, None);
expect_file!["fixtures/highlight.html"].assert_eq(&html.into_inner());
Expand Down
19 changes: 6 additions & 13 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,20 +123,14 @@ impl ErrorCodes {
/// Controls whether a line will be hidden or shown in HTML output.
///
/// All lines are used in documentation tests.
enum Line<'a> {
#[derive(Debug)]
crate enum Line<'a> {
Hidden(&'a str),
Shown(Cow<'a, str>),
}

impl<'a> Line<'a> {
fn for_html(self) -> Option<Cow<'a, str>> {
match self {
Line::Shown(l) => Some(l),
Line::Hidden(_) => None,
}
}

fn for_code(self) -> Cow<'a, str> {
crate fn for_code(self) -> Cow<'a, str> {
match self {
Line::Shown(l) => l,
Line::Hidden(l) => Cow::Borrowed(l),
Expand Down Expand Up @@ -229,8 +223,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
_ => {}
}
}
let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
let text = lines.collect::<Vec<Cow<'_, str>>>().join("\n");
let lines = origtext.lines().map(map_line);

let parse_result = match kind {
CodeBlockKind::Fenced(ref lang) => {
Expand All @@ -243,7 +236,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
<pre class=\"language-{}\"><code>{}</code></pre>\
</div>",
lang,
Escape(&text),
Escape(&origtext),
)
.into(),
));
Expand Down Expand Up @@ -326,7 +319,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
let mut s = Buffer::new();
s.push_str("\n");
highlight::render_with_highlighting(
&text,
lines,
&mut s,
Some(&format!(
"rust-example-rendered{}",
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/render/print_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1115,7 +1115,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum

fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) {
wrap_into_docblock(w, |w| {
highlight::render_with_highlighting(
highlight::render_source_with_highlighting(
&t.source,
w,
Some("macro"),
Expand Down
9 changes: 9 additions & 0 deletions src/librustdoc/html/render/write_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ pub(super) fn write_shared(
cx.write_shared(SharedResource::InvocationSpecific { basename: p }, content, &options.emit)
};

// This is required because the name of the image changes based on the current toolchain.
fn add_background_image_to_css(
cx: &Context<'_>,
css: &mut String,
Expand Down Expand Up @@ -218,6 +219,12 @@ pub(super) fn write_shared(

// Add all the static files. These may already exist, but we just
// overwrite them anyway to make sure that they're fresh and up-to-date.
let mut rustdoc_css = static_files::RUSTDOC_CSS.to_owned();
add_background_image_to_css(cx, &mut rustdoc_css, ".copy-code", "clipboard.svg");
add_background_image_to_css(cx, &mut rustdoc_css, ".expand", "eye.svg");
add_background_image_to_css(cx, &mut rustdoc_css, ".collapse", "eye-slash.svg");
write_minify("rustdoc.css", rustdoc_css, cx, options)?;

write_minify("settings.css", static_files::SETTINGS_CSS, cx, options)?;
write_minify("noscript.css", static_files::NOSCRIPT_CSS, cx, options)?;

Expand Down Expand Up @@ -259,6 +266,8 @@ pub(super) fn write_shared(
write_toolchain("down-arrow.svg", static_files::DOWN_ARROW_SVG)?;
write_toolchain("toggle-minus.svg", static_files::TOGGLE_MINUS_PNG)?;
write_toolchain("toggle-plus.svg", static_files::TOGGLE_PLUS_PNG)?;
write_toolchain("eye.svg", static_files::EXPAND_SVG)?;
write_toolchain("eye-slash.svg", static_files::COLLAPSE_SVG)?;

let mut themes: Vec<&String> = themes.iter().collect();
themes.sort();
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ fn print_src(
writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
}
line_numbers.write_str("</pre>");
highlight::render_with_highlighting(
highlight::render_source_with_highlighting(
s,
buf,
None,
Expand Down
2 changes: 1 addition & 1 deletion src/librustdoc/html/static/css/noscript.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ rules.
margin-left: 0 !important;
}

#copy-path {
#copy-path, .copy-code, .expand, .collapse {
/* It requires JS to work so no need to display it in this case. */
display: none;
}
34 changes: 27 additions & 7 deletions src/librustdoc/html/static/css/rustdoc.css
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ details.undocumented > summary::before,
div.impl-items > div:not(.docblock):not(.item-info),
.content ul.crate a.crate, a.srclink,
/* This selector is for the items listed in the "all items" page. */
#main > ul.docblock > li > a {
#main > ul.docblock > li > a,
.copy-code {
font-family: "Fira Sans", Arial, sans-serif;
}

Expand Down Expand Up @@ -240,7 +241,7 @@ details:not(.rustdoc-toggle) summary {
margin-bottom: .6em;
}

code, pre, a.test-arrow, .code-header {
code, pre, div.code-buttons, .code-header {
font-family: "Source Code Pro", monospace;
}
.docblock code, .docblock-short code {
Expand Down Expand Up @@ -1039,19 +1040,33 @@ pre.rust .question-mark {
font-weight: bold;
}

a.test-arrow {
display: inline-block;
.code-buttons {
display: flex;
position: absolute;
padding: 5px 10px 5px 10px;
border-radius: 5px;
font-size: 130%;
top: 5px;
right: 5px;
z-index: 1;
font-size: 130%;
}
a.test-arrow {
border-radius: 5px;
padding: 5px 10px 5px 10px;
display: inline-block;
}
a.test-arrow:hover{
text-decoration: none;
}
.copy-code, .expand, .collapse {
width: 21px;
background-size: contain;
background-color: transparent;
background-repeat: no-repeat;
border: 0;
background-position: center;
border-radius: 4px;
cursor: pointer;
margin-left: 7px;
}

.section-header:hover a:before {
position: absolute;
Expand Down Expand Up @@ -1242,6 +1257,11 @@ pre.rust {
-moz-tab-size: 4;
}

pre .data-hidden {
width: 100%;
display: block;
}

.search-failed {
text-align: center;
margin-top: 20px;
Expand Down
13 changes: 12 additions & 1 deletion src/librustdoc/html/static/css/themes/ayu.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ pre, .rustdoc.source .example-wrap {
background-color: #191f26;
}

pre .data-hidden {
background-color: #313942;
}

.sidebar {
background-color: #14191f;
}
Expand Down Expand Up @@ -331,12 +335,19 @@ a.test-arrow {
border-radius: 4px;
background-color: rgba(57, 175, 215, 0.09);
}

a.test-arrow:hover {
background-color: rgba(57, 175, 215, 0.368);
color: #c5c5c5;
}

.copy-code, .expand, .collapse {
filter: invert(70%);
color: #fff;
}
.copy-code:hover, .expand:hover, .collapse:hover {
filter: invert(100%);
}

.toggle-label,
.code-attribute {
color: #999;
Expand Down
Loading