]?class="([^"]+)".*?>(.*?)
)"##).unwrap());
+
fn add_playground_pre(
html: &str,
playground_config: &Playground,
edition: Option]?class="([^"]+)".*?>(.*?)
)"##).unwrap());
-
- ADD_PLAYGROUND_PRE
+ CODE_BLOCK_RE
.replace_all(html, |caps: &Captures<'_>| {
let text = &caps[1];
let classes = &caps[2];
let code = &caps[3];
- if classes.contains("language-rust") {
- if (!classes.contains("ignore")
+ if classes.contains("language-rust")
+ && ((!classes.contains("ignore")
&& !classes.contains("noplayground")
&& !classes.contains("noplaypen")
&& playground_config.runnable)
- || classes.contains("mdbook-runnable")
- {
- let contains_e2015 = classes.contains("edition2015");
- let contains_e2018 = classes.contains("edition2018");
- let contains_e2021 = classes.contains("edition2021");
- let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
- // the user forced edition, we should not overwrite it
- ""
- } else {
- match edition {
- Some(RustEdition::E2015) => " edition2015",
- Some(RustEdition::E2018) => " edition2018",
- Some(RustEdition::E2021) => " edition2021",
- None => "",
- }
- };
-
- // wrap the contents in an external pre block
- format!(
- "{}
",
- classes,
- edition_class,
- {
- let content: Cow<'_, str> = if playground_config.editable
- && classes.contains("editable")
- || text.contains("fn main")
- || text.contains("quick_main!")
- {
- code.into()
- } else {
- // we need to inject our own main
- let (attrs, code) = partition_source(code);
-
- format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code)
- .into()
- };
- hide_lines(&content)
- }
- )
+ || classes.contains("mdbook-runnable"))
+ {
+ let contains_e2015 = classes.contains("edition2015");
+ let contains_e2018 = classes.contains("edition2018");
+ let contains_e2021 = classes.contains("edition2021");
+ let edition_class = if contains_e2015 || contains_e2018 || contains_e2021 {
+ // the user forced edition, we should not overwrite it
+ ""
} else {
- format!("{}
", classes, hide_lines(code))
- }
+ match edition {
+ Some(RustEdition::E2015) => " edition2015",
+ Some(RustEdition::E2018) => " edition2018",
+ Some(RustEdition::E2021) => " edition2021",
+ None => "",
+ }
+ };
+
+ // wrap the contents in an external pre block
+ format!(
+ "{}
",
+ classes,
+ edition_class,
+ {
+ let content: Cow<'_, str> = if playground_config.editable
+ && classes.contains("editable")
+ || text.contains("fn main")
+ || text.contains("quick_main!")
+ {
+ code.into()
+ } else {
+ // we need to inject our own main
+ let (attrs, code) = partition_source(code);
+
+ format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code)
+ .into()
+ };
+ content
+ }
+ )
} else {
// not language-rust, so no-op
text.to_owned()
@@ -942,7 +958,50 @@ fn add_playground_pre(
.into_owned()
}
-fn hide_lines(content: &str) -> String {
+/// Modifies all `` blocks to convert "hidden" lines and to wrap them in
+/// a ``.
+fn hide_lines(html: &str, code_config: &Code) -> String {
+ let language_regex = Regex::new(r"\blanguage-(\w+)\b").unwrap();
+ let hidelines_regex = Regex::new(r"\bhidelines=(\S+)").unwrap();
+ CODE_BLOCK_RE
+ .replace_all(html, |caps: &Captures<'_>| {
+ let text = &caps[1];
+ let classes = &caps[2];
+ let code = &caps[3];
+
+ if classes.contains("language-rust") {
+ format!(
+ "{}
",
+ classes,
+ hide_lines_rust(code)
+ )
+ } else {
+ // First try to get the prefix from the code block
+ let hidelines_capture = hidelines_regex.captures(classes);
+ let hidelines_prefix = match &hidelines_capture {
+ Some(capture) => Some(&capture[1]),
+ None => {
+ // Then look up the prefix by language
+ language_regex.captures(classes).and_then(|capture| {
+ code_config.hidelines.get(&capture[1]).map(|p| p.as_str())
+ })
+ }
+ };
+
+ match hidelines_prefix {
+ Some(prefix) => format!(
+ "{}
",
+ classes,
+ hide_lines_with_prefix(code, prefix)
+ ),
+ None => text.to_owned(),
+ }
+ }
+ })
+ .into_owned()
+}
+
+fn hide_lines_rust(content: &str) -> String {
static BORING_LINES_REGEX: Lazy = Lazy::new(|| Regex::new(r"^(\s*)#(.?)(.*)$").unwrap());
let mut result = String::with_capacity(content.len());
@@ -975,6 +1034,26 @@ fn hide_lines(content: &str) -> String {
result
}
+fn hide_lines_with_prefix(content: &str, prefix: &str) -> String {
+ let mut result = String::with_capacity(content.len());
+ for line in content.lines() {
+ if line.trim_start().starts_with(prefix) {
+ let pos = line.find(prefix).unwrap();
+ let (ws, rest) = (&line[..pos], &line[pos + prefix.len()..]);
+
+ result += "";
+ result += ws;
+ result += rest;
+ result += "\n";
+ result += "";
+ continue;
+ }
+ result += line;
+ result += "\n";
+ }
+ result
+}
+
fn partition_source(s: &str) -> (String, String) {
let mut after_header = false;
let mut before = String::new();
@@ -1010,6 +1089,7 @@ struct RenderItemContext<'a> {
#[cfg(test)]
mod tests {
use super::*;
+ use pretty_assertions::assert_eq;
#[test]
fn original_build_header_links() {
@@ -1065,17 +1145,17 @@ mod tests {
fn add_playground() {
let inputs = [
("x()
",
- "#![allow(unused)]\nfn main() {\nx()\n}
"),
+ "# #![allow(unused)]\n#fn main() {\nx()\n#}
"),
("fn main() {}
",
"fn main() {}
"),
("let s = \"foo\n # bar\n\";
",
- "let s = \"foo\n bar\n\";
"),
- ("let s = \"foo\n ## bar\n\";
",
"let s = \"foo\n # bar\n\";
"),
+ ("let s = \"foo\n ## bar\n\";
",
+ "let s = \"foo\n ## bar\n\";
"),
("let s = \"foo\n # bar\n#\n\";
",
- "let s = \"foo\n bar\n\n\";
"),
+ "let s = \"foo\n # bar\n#\n\";
"),
("let s = \"foo\n # bar\n\";
",
- "let s = \"foo\n bar\n\";
"),
+ "let s = \"foo\n # bar\n\";
"),
("#![no_std]\nlet s = \"foo\";\n #[some_attr]
",
"#![no_std]\nlet s = \"foo\";\n #[some_attr]
"),
];
@@ -1095,7 +1175,7 @@ mod tests {
fn add_playground_edition2015() {
let inputs = [
("x()
",
- "#![allow(unused)]\nfn main() {\nx()\n}
"),
+ "# #![allow(unused)]\n#fn main() {\nx()\n#}
"),
("fn main() {}
",
"fn main() {}
"),
("fn main() {}
",
@@ -1119,7 +1199,7 @@ mod tests {
fn add_playground_edition2018() {
let inputs = [
("x()
",
- "#![allow(unused)]\nfn main() {\nx()\n}
"),
+ "# #![allow(unused)]\n#fn main() {\nx()\n#}
"),
("fn main() {}
",
"fn main() {}
"),
("fn main() {}
",
@@ -1143,7 +1223,7 @@ mod tests {
fn add_playground_edition2021() {
let inputs = [
("x()
",
- "#![allow(unused)]\nfn main() {\nx()\n}
"),
+ "# #![allow(unused)]\n#fn main() {\nx()\n#}
"),
("fn main() {}
",
"fn main() {}
"),
("fn main() {}
",
@@ -1163,4 +1243,60 @@ mod tests {
assert_eq!(&*got, *should_be);
}
}
+
+ #[test]
+ fn hide_lines_language_rust() {
+ let inputs = [
+ (
+ "\n# #![allow(unused)]\n#fn main() {\nx()\n#}
",
+ "\n#![allow(unused)]\nfn main() {\nx()\n}
",),
+ (
+ "fn main() {}
",
+ "fn main() {}
",),
+ (
+ "let s = \"foo\n # bar\n\";
",
+ "let s = \"foo\n bar\n\";
",),
+ (
+ "let s = \"foo\n ## bar\n\";
",
+ "let s = \"foo\n # bar\n\";
",),
+ (
+ "let s = \"foo\n # bar\n#\n\";
",
+ "let s = \"foo\n bar\n\n\";
",),
+ (
+ "let s = \"foo\n # bar\n\";
",
+ "let s = \"foo\n bar\n\";
",),
+ (
+ "#![no_std]\nlet s = \"foo\";\n #[some_attr]
",
+ "#![no_std]\nlet s = \"foo\";\n #[some_attr]
",),
+ ];
+ for (src, should_be) in &inputs {
+ let got = hide_lines(src, &Code::default());
+ assert_eq!(&*got, *should_be);
+ }
+ }
+
+ #[test]
+ fn hide_lines_language_other() {
+ let inputs = [
+ (
+ "~hidden()\nnothidden():\n~ hidden()\n ~hidden()\n nothidden()
",
+ "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n
",),
+ (
+ "!!!hidden()\nnothidden():\n!!! hidden()\n !!!hidden()\n nothidden()
",
+ "hidden()\nnothidden():\n hidden()\n hidden()\n nothidden()\n
",),
+ ];
+ for (src, should_be) in &inputs {
+ let got = hide_lines(
+ src,
+ &Code {
+ hidelines: {
+ let mut map = HashMap::new();
+ map.insert("python".to_string(), "~".to_string());
+ map
+ },
+ },
+ );
+ assert_eq!(&*got, *should_be);
+ }
+ }
}
diff --git a/src/theme/book.js b/src/theme/book.js
index f2516be7..ff3650eb 100644
--- a/src/theme/book.js
+++ b/src/theme/book.js
@@ -179,7 +179,7 @@ function playground_text(playground, hidden = true) {
// even if highlighting doesn't apply
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
- Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) {
+ Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return