Skip to content

Commit 5c773e4

Browse files
Add "copy to clipboard" button for all codeblocks
1 parent e0c38af commit 5c773e4

File tree

13 files changed

+201
-61
lines changed

13 files changed

+201
-61
lines changed

src/librustdoc/html/highlight.rs

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ use crate::clean::PrimitiveType;
99
use crate::html::escape::Escape;
1010
use crate::html::render::Context;
1111

12-
use std::fmt::{Display, Write};
13-
use std::iter::Peekable;
12+
use std::borrow::Cow;
13+
use std::fmt::{Debug, Display, Write};
14+
use std::iter::{once, Peekable};
1415

1516
use rustc_lexer::{LiteralKind, TokenKind};
1617
use rustc_span::edition::Edition;
1718
use rustc_span::symbol::Symbol;
1819
use rustc_span::{BytePos, Span, DUMMY_SP};
1920

2021
use super::format::{self, Buffer};
22+
use super::markdown::Line;
2123
use super::render::LinkFromSrc;
2224

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

3335
/// Highlights `src`, returning the HTML output.
34-
crate fn render_with_highlighting(
36+
crate fn render_source_with_highlighting(
3537
src: &str,
3638
out: &mut Buffer,
3739
class: Option<&str>,
@@ -41,7 +43,31 @@ crate fn render_with_highlighting(
4143
extra_content: Option<Buffer>,
4244
context_info: Option<ContextInfo<'_, '_, '_>>,
4345
) {
44-
debug!("highlighting: ================\n{}\n==============", src);
46+
render_with_highlighting(
47+
once(Line::Shown(Cow::Borrowed(src))),
48+
out,
49+
class,
50+
playground_button,
51+
tooltip,
52+
edition,
53+
extra_content,
54+
context_info,
55+
)
56+
}
57+
58+
/// Highlights `src` containing potential hidden lines, returning the HTML output. If you don't have
59+
/// hidden lines, use [`render_source_with_highlighting`] instead.
60+
crate fn render_with_highlighting<'a>(
61+
src: impl Iterator<Item = Line<'a>> + Debug,
62+
out: &mut Buffer,
63+
class: Option<&str>,
64+
playground_button: Option<&str>,
65+
tooltip: Option<(Option<Edition>, &str)>,
66+
edition: Edition,
67+
extra_content: Option<Buffer>,
68+
context_info: Option<ContextInfo<'_, '_, '_>>,
69+
) {
70+
debug!("highlighting: ================\n{:?}\n==============", src);
4571
if let Some((edition_info, class)) = tooltip {
4672
write!(
4773
out,
@@ -56,7 +82,7 @@ crate fn render_with_highlighting(
5682
}
5783

5884
write_header(out, class, extra_content);
59-
write_code(out, &src, edition, context_info);
85+
write_code(out, src, edition, context_info);
6086
write_footer(out, playground_button);
6187
}
6288

@@ -86,24 +112,50 @@ fn write_header(out: &mut Buffer, class: Option<&str>, extra_content: Option<Buf
86112
/// More explanations about spans and how we use them here are provided in the
87113
fn write_code(
88114
out: &mut Buffer,
89-
src: &str,
115+
src: impl Iterator<Item = Line<'a>>,
90116
edition: Edition,
91117
context_info: Option<ContextInfo<'_, '_, '_>>,
92118
) {
93-
// This replace allows to fix how the code source with DOS backline characters is displayed.
94-
let src = src.replace("\r\n", "\n");
95-
Classifier::new(&src, edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP))
96-
.highlight(&mut |highlight| {
97-
match highlight {
98-
Highlight::Token { text, class } => string(out, Escape(text), class, &context_info),
99-
Highlight::EnterSpan { class } => enter_span(out, class),
100-
Highlight::ExitSpan => exit_span(out),
101-
};
102-
});
119+
let mut iter = src.peekable();
120+
121+
// For each `Line`, we replace DOS backlines with '\n'. This replace allows to fix how the code
122+
// source with DOS backline characters is displayed.
123+
while let Some(line) = iter.next() {
124+
match line {
125+
Line::Hidden(text) => {
126+
write!(
127+
out,
128+
"<span class=\"hidden\">{}{}</span>",
129+
Escape(&text.replace("\r\n", "\n")),
130+
if iter.peek().is_some() && !text.ends_with('\n') { "\n" } else { "" },
131+
);
132+
}
133+
Line::Shown(text) => {
134+
Classifier::new(&text.replace("\r\n", "\n"), edition, context_info.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP)).highlight(&mut |highlight| {
135+
match highlight {
136+
Highlight::Token { text, class } => string(out, Escape(text), class),
137+
Highlight::EnterSpan { class } => enter_span(out, class),
138+
Highlight::ExitSpan => exit_span(out),
139+
};
140+
});
141+
if iter.peek().is_some() && !text.ends_with('\n') {
142+
write!(out, "\n");
143+
}
144+
}
145+
}
146+
}
103147
}
104148

105149
fn write_footer(out: &mut Buffer, playground_button: Option<&str>) {
106-
writeln!(out, "</code></pre>{}</div>", playground_button.unwrap_or_default());
150+
writeln!(
151+
out,
152+
"</code></pre>\
153+
<div class=\"code-buttons\">\
154+
{}<button class=\"copy-code\" onclick=\"copyCode(this)\"></button>\
155+
</div>\
156+
</div>",
157+
playground_button.unwrap_or_default()
158+
);
107159
}
108160

109161
/// How a span of text is classified. Mostly corresponds to token kinds.

src/librustdoc/html/highlight/tests.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use super::write_code;
22
use crate::html::format::Buffer;
3+
use crate::html::markdown::Line;
34
use expect_test::expect_file;
45
use rustc_span::create_default_session_globals_then;
56
use rustc_span::edition::Edition;
67

8+
use std::borrow::Cow;
9+
use std::iter::once;
10+
711
const STYLE: &str = r#"
812
<style>
913
.kw { color: #8959A8; }
@@ -20,6 +24,7 @@ const STYLE: &str = r#"
2024
fn test_html_highlighting() {
2125
create_default_session_globals_then(|| {
2226
let src = include_str!("fixtures/sample.rs");
27+
let src = once(Line::Shown(Cow::Borrowed(src)));
2328
let html = {
2429
let mut out = Buffer::new();
2530
write_code(&mut out, src, Edition::Edition2018, None);
@@ -35,6 +40,7 @@ fn test_dos_backline() {
3540
let src = "pub fn foo() {\r\n\
3641
println!(\"foo\");\r\n\
3742
}\r\n";
43+
let src = once(Line::Shown(Cow::Borrowed(src)));
3844
let mut html = Buffer::new();
3945
write_code(&mut html, src, Edition::Edition2018, None);
4046
expect_file!["fixtures/dos_line.html"].assert_eq(&html.into_inner());

src/librustdoc/html/markdown.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,14 @@ impl ErrorCodes {
123123
/// Controls whether a line will be hidden or shown in HTML output.
124124
///
125125
/// All lines are used in documentation tests.
126-
enum Line<'a> {
126+
#[derive(Debug)]
127+
crate enum Line<'a> {
127128
Hidden(&'a str),
128129
Shown(Cow<'a, str>),
129130
}
130131

131132
impl<'a> Line<'a> {
132-
fn for_html(self) -> Option<Cow<'a, str>> {
133-
match self {
134-
Line::Shown(l) => Some(l),
135-
Line::Hidden(_) => None,
136-
}
137-
}
138-
139-
fn for_code(self) -> Cow<'a, str> {
133+
crate fn for_code(self) -> Cow<'a, str> {
140134
match self {
141135
Line::Shown(l) => l,
142136
Line::Hidden(l) => Cow::Borrowed(l),
@@ -229,8 +223,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
229223
_ => {}
230224
}
231225
}
232-
let lines = origtext.lines().filter_map(|l| map_line(l).for_html());
233-
let text = lines.collect::<Vec<Cow<'_, str>>>().join("\n");
226+
let lines = origtext.lines().map(map_line);
234227

235228
let parse_result = match kind {
236229
CodeBlockKind::Fenced(ref lang) => {
@@ -243,7 +236,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
243236
<pre class=\"language-{}\"><code>{}</code></pre>\
244237
</div>",
245238
lang,
246-
Escape(&text),
239+
Escape(&origtext),
247240
)
248241
.into(),
249242
));
@@ -326,7 +319,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
326319
let mut s = Buffer::new();
327320
s.push_str("\n");
328321
highlight::render_with_highlighting(
329-
&text,
322+
lines,
330323
&mut s,
331324
Some(&format!(
332325
"rust-example-rendered{}",

src/librustdoc/html/render/print_item.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ fn item_enum(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, e: &clean::Enum
11151115

11161116
fn item_macro(w: &mut Buffer, cx: &Context<'_>, it: &clean::Item, t: &clean::Macro) {
11171117
wrap_into_docblock(w, |w| {
1118-
highlight::render_with_highlighting(
1118+
highlight::render_source_with_highlighting(
11191119
&t.source,
11201120
w,
11211121
Some("macro"),

src/librustdoc/html/sources.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ fn print_src(
264264
writeln!(line_numbers, "<span id=\"{0}\">{0:1$}</span>", i, cols);
265265
}
266266
line_numbers.write_str("</pre>");
267-
highlight::render_with_highlighting(
267+
highlight::render_source_with_highlighting(
268268
s,
269269
buf,
270270
None,

src/librustdoc/html/static/css/noscript.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ rules.
99
margin-left: 0 !important;
1010
}
1111

12-
#copy-path {
12+
#copy-path, .copy-code {
1313
/* It requires JS to work so no need to display it in this case. */
1414
display: none;
1515
}

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

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ details.undocumented > summary::before,
190190
div.impl-items > div:not(.docblock):not(.item-info),
191191
.content ul.crate a.crate, a.srclink,
192192
/* This selector is for the items listed in the "all items" page. */
193-
#main > ul.docblock > li > a {
193+
#main > ul.docblock > li > a,
194+
.copy-code {
194195
font-family: "Fira Sans", Arial, sans-serif;
195196
}
196197

@@ -240,7 +241,7 @@ details:not(.rustdoc-toggle) summary {
240241
margin-bottom: .6em;
241242
}
242243

243-
code, pre, a.test-arrow, .code-header {
244+
code, pre, div.code-buttons, .code-header {
244245
font-family: "Source Code Pro", monospace;
245246
}
246247
.docblock code, .docblock-short code {
@@ -1039,19 +1040,34 @@ pre.rust .question-mark {
10391040
font-weight: bold;
10401041
}
10411042

1042-
a.test-arrow {
1043-
display: inline-block;
1043+
.code-buttons {
1044+
display: flex;
10441045
position: absolute;
1045-
padding: 5px 10px 5px 10px;
1046-
border-radius: 5px;
1047-
font-size: 130%;
10481046
top: 5px;
10491047
right: 5px;
10501048
z-index: 1;
1049+
font-size: 130%;
1050+
}
1051+
a.test-arrow {
1052+
border-radius: 5px;
1053+
padding: 5px 10px 5px 10px;
1054+
display: inline-block;
10511055
}
10521056
a.test-arrow:hover{
10531057
text-decoration: none;
10541058
}
1059+
.copy-code {
1060+
background-image: url(clipboard1.55.0.svg);
1061+
width: 21px;
1062+
background-size: contain;
1063+
background-color: transparent;
1064+
background-repeat: no-repeat;
1065+
border: 0;
1066+
background-position: center;
1067+
border-radius: 4px;
1068+
cursor: pointer;
1069+
margin-left: 7px;
1070+
}
10551071

10561072
.section-header:hover a:before {
10571073
position: absolute;

src/librustdoc/html/static/css/themes/ayu.css

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,12 +331,19 @@ a.test-arrow {
331331
border-radius: 4px;
332332
background-color: rgba(57, 175, 215, 0.09);
333333
}
334-
335334
a.test-arrow:hover {
336335
background-color: rgba(57, 175, 215, 0.368);
337336
color: #c5c5c5;
338337
}
339338

339+
.copy-code {
340+
filter: invert(70%);
341+
color: #fff;
342+
}
343+
.copy-code:hover {
344+
filter: invert(100%);
345+
}
346+
340347
.toggle-label,
341348
.code-attribute {
342349
color: #999;

src/librustdoc/html/static/css/themes/dark.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ body.source .example-wrap pre.rust a {
190190
a.test-arrow {
191191
color: #dedede;
192192
}
193+
.copy-code {
194+
filter: invert(50%);
195+
color: #999;
196+
}
197+
.copy-code:hover {
198+
filter: invert(65%);
199+
}
193200

194201
details.rustdoc-toggle > summary.hideme > span,
195202
details.rustdoc-toggle > summary::before,

src/librustdoc/html/static/css/themes/light.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ body.source .example-wrap pre.rust a {
185185
a.test-arrow {
186186
color: #f5f5f5;
187187
}
188+
.copy-code {
189+
filter: invert(50%);
190+
color: #999;
191+
}
192+
.copy-code:hover {
193+
filter: invert(35%);
194+
}
188195

189196
details.rustdoc-toggle > summary.hideme > span,
190197
details.rustdoc-toggle > summary::before,

0 commit comments

Comments
 (0)