diff --git a/Cargo.lock b/Cargo.lock index 0446bae5..82dfa8c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi 0.3.9", ] @@ -228,10 +228,10 @@ dependencies = [ ] [[package]] -name = "difference" -version = "2.0.0" +name = "diff" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "difflib" @@ -283,9 +283,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", @@ -382,25 +382,11 @@ dependencies = [ "new_debug_unreachable", ] -[[package]] -name = "futures" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -408,15 +394,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" - -[[package]] -name = "futures-io" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-macro" @@ -433,9 +413,9 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" @@ -545,7 +525,7 @@ dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.1", + "quick-error", "serde", "serde_json", ] @@ -640,12 +620,9 @@ checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" [[package]] name = "humantime" -version = "1.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" @@ -712,15 +689,6 @@ dependencies = [ "libc", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "iovec" version = "0.1.4" @@ -1232,13 +1200,13 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "0.6.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ "ansi_term", "ctor", - "difference", + "diff", "output_vt100", ] @@ -1274,12 +1242,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -1643,6 +1605,26 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.43" @@ -1670,11 +1652,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.10.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -1687,9 +1668,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -1709,9 +1690,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" dependencies = [ "futures-util", "log", @@ -1790,19 +1771,19 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" dependencies = [ "base64", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.4", "sha-1 0.9.7", + "thiserror", "url", "utf-8", ] @@ -1905,12 +1886,13 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d47745e9a0c38636dbd454729b147d16bd1ed08ae67b3ab281c4506771054" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" dependencies = [ "bytes", - "futures", + "futures-channel", + "futures-util", "headers", "http", "hyper", diff --git a/Cargo.toml b/Cargo.toml index e3e8a32e..0b54b5ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ anyhow = "1.0.28" chrono = "0.4" clap = { version = "3.0", features = ["cargo"] } clap_complete = "3.0" -env_logger = "0.7.1" +env_logger = "0.9.0" handlebars = "4.0" lazy_static = "1.0" log = "0.4" @@ -42,7 +42,7 @@ gitignore = { version = "1.0", optional = true } # Serve feature futures-util = { version = "0.3.4", optional = true } tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true } -warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true } +warp = { version = "0.3.2", default-features = false, features = ["websocket"], optional = true } # Search feature elasticlunr-rs = { version = "3.0.0", optional = true } @@ -53,7 +53,7 @@ assert_cmd = "1" predicates = "2" select = "0.5" semver = "1.0" -pretty_assertions = "0.6" +pretty_assertions = "1.2.1" walkdir = "2.0" [features] diff --git a/guide/src/guide/installation.md b/guide/src/guide/installation.md index d7946587..128b1047 100644 --- a/guide/src/guide/installation.md +++ b/guide/src/guide/installation.md @@ -30,6 +30,8 @@ cargo install mdbook This will automatically download mdBook from [crates.io], build it, and install it in Cargo's global binary directory (`~/.cargo/bin/` by default). +To uninstall, run the command `cargo uninstall mdbook`. + [Rust installation page]: https://www.rust-lang.org/tools/install [crates.io]: https://crates.io/ diff --git a/src/book/mod.rs b/src/book/mod.rs index 3370d92c..9745d2b7 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -386,7 +386,7 @@ fn determine_renderers(config: &Config) -> Vec> { renderers } -const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"]; +const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"]; fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool { let name = pre.name(); @@ -756,10 +756,9 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(preprocessors + assert!(!preprocessors .iter() - .find(|preprocessor| preprocessor.name() == "random") - .is_none()); + .any(|preprocessor| preprocessor.name() == "random")); } #[test] @@ -776,10 +775,9 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(preprocessors + assert!(!preprocessors .iter() - .find(|preprocessor| preprocessor.name() == "links") - .is_none()); + .any(|preprocessor| preprocessor.name() == "links")); } #[test] diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 1ee5ff21..c964dcc1 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -122,8 +122,5 @@ fn confirm() -> bool { io::stdout().flush().unwrap(); let mut s = String::new(); io::stdin().read_line(&mut s).ok(); - match &*s.trim() { - "Y" | "y" | "yes" | "Yes" => true, - _ => false, - } + matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes") } diff --git a/src/config.rs b/src/config.rs index 951957bd..b7d03d1a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -227,10 +227,10 @@ impl Config { let value = Value::try_from(value) .with_context(|| "Unable to represent the item as a JSON Value")?; - if index.starts_with("book.") { - self.book.update_value(&index[5..], value); - } else if index.starts_with("build.") { - self.build.update_value(&index[6..], value); + if let Some(key) = index.strip_prefix("book.") { + self.book.update_value(key, value); + } else if let Some(key) = index.strip_prefix("build.") { + self.build.update_value(key, value); } else { self.rest.insert(index, value); } @@ -371,15 +371,8 @@ impl Serialize for Config { } fn parse_env(key: &str) -> Option { - const PREFIX: &str = "MDBOOK_"; - - if key.starts_with(PREFIX) { - let key = &key[PREFIX.len()..]; - - Some(key.to_lowercase().replace("__", ".").replace("_", "-")) - } else { - None - } + key.strip_prefix("MDBOOK_") + .map(|key| key.to_lowercase().replace("__", ".").replace('_', "-")) } fn is_legacy_format(table: &Value) -> bool { @@ -828,7 +821,7 @@ mod tests { "#; let got = Config::from_str(src).unwrap(); - assert_eq!(got.html_config().unwrap().playground.runnable, false); + assert!(!got.html_config().unwrap().playground.runnable); } #[test] @@ -1037,7 +1030,7 @@ mod tests { fn encode_env_var(key: &str) -> String { format!( "MDBOOK_{}", - key.to_uppercase().replace('.', "__").replace("-", "_") + key.to_uppercase().replace('.', "__").replace('-', "_") ) } @@ -1061,11 +1054,10 @@ mod tests { } #[test] - #[allow(clippy::approx_constant)] fn update_config_using_env_var_and_complex_value() { let mut cfg = Config::default(); let key = "foo-bar.baz"; - let value = json!({"array": [1, 2, 3], "number": 3.14}); + let value = json!({"array": [1, 2, 3], "number": 13.37}); let value_str = serde_json::to_string(&value).unwrap(); assert!(cfg.get(key).is_none()); @@ -1184,15 +1176,15 @@ mod tests { "#; let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); - assert_eq!(html_config.print.enable, false); - assert_eq!(html_config.print.page_break, true); + assert!(!html_config.print.enable); + assert!(html_config.print.page_break); let src = r#" [output.html.print] page-break = false "#; let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); - assert_eq!(html_config.print.enable, true); - assert_eq!(html_config.print.page_break, false); + assert!(html_config.print.enable); + assert!(!html_config.print.page_break); } } diff --git a/src/lib.rs b/src/lib.rs index 23309fb0..cc62b0ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,6 @@ #![deny(missing_docs)] #![deny(rust_2018_idioms)] -#![allow(clippy::comparison_chain)] #[macro_use] extern crate lazy_static; diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index 4daa3af9..df0eb36f 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -153,6 +153,7 @@ enum RangeOrAnchor { } // A range of lines specified with some include directive. +#[allow(clippy::enum_variant_names)] // The prefix can't be removed, and is meant to mirror the contained type #[derive(PartialEq, Debug, Clone)] enum LineRange { Range(Range), diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 8a13db9f..b933a359 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -116,7 +116,7 @@ impl HtmlHandlebars { if ctx.is_index { ctx.data.insert("path".to_owned(), json!("index.md")); ctx.data.insert("path_to_root".to_owned(), json!("")); - ctx.data.insert("is_index".to_owned(), json!("true")); + ctx.data.insert("is_index".to_owned(), json!(true)); let rendered_index = ctx.handlebars.render("index", &ctx.data)?; let rendered_index = self.post_process(rendered_index, &ctx.html_config.playground, ctx.edition); @@ -540,7 +540,8 @@ impl Renderer for HtmlHandlebars { chapter_titles: &ctx.chapter_titles, }; self.render_item(item, ctx, &mut print_content)?; - is_index = false; + // Only the first non-draft chapter item should be treated as the "index" + is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter()); } // Render 404 page @@ -814,7 +815,7 @@ fn fix_code_blocks(html: &str) -> String { FIX_CODE_BLOCKS .replace_all(html, |caps: &Captures<'_>| { let before = &caps[1]; - let classes = &caps[2].replace(",", " "); + let classes = &caps[2].replace(',', " "); let after = &caps[3]; format!( @@ -880,11 +881,8 @@ fn add_playground_pre( // we need to inject our own main let (attrs, code) = partition_source(code); - format!( - "\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}", - attrs, code - ) - .into() + format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code) + .into() }; hide_lines(&content) } @@ -1007,7 +1005,7 @@ mod tests { fn add_playground() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("let s = \"foo\n # bar\n\";", @@ -1037,7 +1035,7 @@ mod tests { fn add_playground_edition2015() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", @@ -1061,7 +1059,7 @@ mod tests { fn add_playground_edition2018() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", @@ -1085,7 +1083,7 @@ mod tests { fn add_playground_edition2021() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", diff --git a/src/renderer/html_handlebars/helpers/navigation.rs b/src/renderer/html_handlebars/helpers/navigation.rs index 83bdadb3..d3f6ca90 100644 --- a/src/renderer/html_handlebars/helpers/navigation.rs +++ b/src/renderer/html_handlebars/helpers/navigation.rs @@ -61,7 +61,7 @@ fn find_chapter( .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); if !rc.evaluate(ctx, "@root/is_index")?.is_missing() { // Special case for index.md which may be a synthetic page. @@ -121,7 +121,7 @@ fn render( .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); context.insert( "path_to_root".to_owned(), @@ -141,7 +141,7 @@ fn render( .with_extension("html") .to_str() .ok_or_else(|| RenderError::new("Link could not be converted to str")) - .map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/")))) + .map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/")))) })?; trace!("Render template"); diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/src/renderer/html_handlebars/helpers/toc.rs index 5869dd36..0884d30a 100644 --- a/src/renderer/html_handlebars/helpers/toc.rs +++ b/src/renderer/html_handlebars/helpers/toc.rs @@ -1,5 +1,5 @@ -use std::collections::BTreeMap; use std::path::Path; +use std::{cmp::Ordering, collections::BTreeMap}; use crate::utils; use crate::utils::bracket_escape; @@ -33,7 +33,7 @@ impl HelperDef for RenderToc { .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); let current_section = rc .evaluate(ctx, "@root/section")? @@ -57,6 +57,11 @@ impl HelperDef for RenderToc { out.write("
    ")?; let mut current_level = 1; + // The "index" page, which has this attribute set, is supposed to alias the first chapter in + // the book, i.e. the first link. There seems to be no easy way to determine which chapter + // the "index" is aliasing from within the renderer, so this is used instead to force the + // first link to be active. See further below. + let mut is_first_chapter = ctx.data().get("is_index").is_some(); for item in chapters { // Spacer @@ -81,22 +86,26 @@ impl HelperDef for RenderToc { level - 1 < fold_level as usize }; - if level > current_level { - while level > current_level { - out.write("
  1. ")?; - out.write("
      ")?; - current_level += 1; + match level.cmp(¤t_level) { + Ordering::Greater => { + while level > current_level { + out.write("
    1. ")?; + out.write("
        ")?; + current_level += 1; + } + write_li_open_tag(out, is_expanded, false)?; } - write_li_open_tag(out, is_expanded, false)?; - } else if level < current_level { - while level < current_level { - out.write("
      ")?; - out.write("
    2. ")?; - current_level -= 1; + Ordering::Less => { + while level < current_level { + out.write("
    ")?; + out.write("
  2. ")?; + current_level -= 1; + } + write_li_open_tag(out, is_expanded, false)?; + } + Ordering::Equal => { + write_li_open_tag(out, is_expanded, item.get("section").is_none())?; } - write_li_open_tag(out, is_expanded, false)?; - } else { - write_li_open_tag(out, is_expanded, item.get("section").is_none())?; } // Part title @@ -119,14 +128,15 @@ impl HelperDef for RenderToc { .to_str() .unwrap() // Hack for windows who tends to use `\` as separator instead of `/` - .replace("\\", "/"); + .replace('\\', "/"); // Add link out.write(&utils::fs::path_to_root(¤t_path))?; out.write(&tmp)?; out.write("\"")?; - if path == ¤t_path { + if path == ¤t_path || is_first_chapter { + is_first_chapter = false; out.write(" class=\"active\"")?; } diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 0a59ffe9..c3b944c9 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::Path; -use elasticlunr::Index; +use elasticlunr::{Index, IndexBuilder}; use pulldown_cmark::*; use crate::book::{Book, BookItem}; @@ -13,9 +13,25 @@ use crate::utils; use serde::Serialize; +const MAX_WORD_LENGTH_TO_INDEX: usize = 80; + +/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens. +fn tokenize(text: &str) -> Vec { + text.split(|c: char| c.is_whitespace() || c == '-') + .filter(|s| !s.is_empty()) + .map(|s| s.trim().to_lowercase()) + .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX) + .collect() +} + /// Creates all files required for search. pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> { - let mut index = Index::new(&["title", "body", "breadcrumbs"]); + let mut index = IndexBuilder::new() + .add_field_with_tokenizer("title", Box::new(&tokenize)) + .add_field_with_tokenizer("body", Box::new(&tokenize)) + .add_field_with_tokenizer("breadcrumbs", Box::new(&tokenize)) + .build(); + let mut doc_urls = Vec::with_capacity(book.sections.len()); for item in book.iter() { @@ -211,12 +227,13 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec) -> let mut fields = BTreeMap::new(); let mut opt = SearchOptionsField::default(); - opt.boost = Some(search_config.boost_title); - fields.insert("title".into(), opt); - opt.boost = Some(search_config.boost_paragraph); - fields.insert("body".into(), opt); - opt.boost = Some(search_config.boost_hierarchy); - fields.insert("breadcrumbs".into(), opt); + let mut insert_boost = |key: &str, boost| { + opt.boost = Some(boost); + fields.insert(key.into(), opt); + }; + insert_boost("title", search_config.boost_title); + insert_boost("body", search_config.boost_paragraph); + insert_boost("breadcrumbs", search_config.boost_hierarchy); let search_options = SearchOptions { bool: if search_config.use_boolean_and { diff --git a/src/theme/ayu-highlight.css b/src/theme/ayu-highlight.css index 0c45c6f1..32c94322 100644 --- a/src/theme/ayu-highlight.css +++ b/src/theme/ayu-highlight.css @@ -8,7 +8,6 @@ Original by Dempfi (https://github.com/dempfi/ayu) overflow-x: auto; background: #191f26; color: #e6e1cf; - padding: 0.5em; } .hljs-comment, diff --git a/src/theme/css/chrome.css b/src/theme/css/chrome.css index 21c08b93..53a54c85 100644 --- a/src/theme/css/chrome.css +++ b/src/theme/css/chrome.css @@ -208,24 +208,63 @@ pre { pre > .buttons { position: absolute; z-index: 100; - right: 5px; - top: 5px; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; color: var(--sidebar-fg); cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 } pre > .buttons :hover { color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); } pre > .buttons i { margin-left: 8px; } pre > .buttons button { - color: inherit; - background: transparent; - border: none; cursor: inherit; + margin: 0px 5px; + padding: 3px 5px; + font-size: 14px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); } +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } +} +code { + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + pre > .result { margin-top: 10px; } diff --git a/src/theme/css/general.css b/src/theme/css/general.css index ef2ba504..0e4f07a5 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -26,6 +26,16 @@ code { font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ } +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + /* Don't change font size in headers. */ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { font-size: unset; @@ -80,8 +90,7 @@ h6:target::before { .content { overflow-y: auto; - padding: 0 15px; - padding-bottom: 50px; + padding: 0 5px 50px 5px; } .content main { margin-left: auto; diff --git a/src/theme/highlight.css b/src/theme/highlight.css index c2343227..ba57b82b 100644 --- a/src/theme/highlight.css +++ b/src/theme/highlight.css @@ -61,7 +61,6 @@ overflow-x: auto; background: #f6f7f6; color: #000; - padding: 0.5em; } .hljs-emphasis { diff --git a/src/theme/tomorrow-night.css b/src/theme/tomorrow-night.css index f7197925..5b4aca77 100644 --- a/src/theme/tomorrow-night.css +++ b/src/theme/tomorrow-night.css @@ -81,8 +81,6 @@ overflow-x: auto; background: #1d1f21; color: #c5c8c6; - padding: 0.5em; - -webkit-text-size-adjust: none; } .coffeescript .javascript, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2000d661..a205633f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -200,12 +200,28 @@ pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&P let p = new_cmark_parser(text, curly_quotes); let events = p .map(clean_codeblock_headers) - .map(|event| adjust_links(event, path)); + .map(|event| adjust_links(event, path)) + .flat_map(|event| { + let (a, b) = wrap_tables(event); + a.into_iter().chain(b) + }); html::push_html(&mut s, events); s } +/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to. +fn wrap_tables(event: Event<'_>) -> (Option>, Option>) { + match event { + Event::Start(Tag::Table(_)) => ( + Some(Event::Html(r#"
    "#.into())), + Some(event), + ), + Event::End(Tag::Table(_)) => (Some(event), Some(Event::Html(r#"
    "#.into()))), + _ => (Some(event), None), + } +} + fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> { match event { Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => { @@ -282,6 +298,22 @@ mod tests { ); } + #[test] + fn it_can_wrap_tables() { + let src = r#" +| Original | Punycode | Punycode + Encoding | +|-----------------|-----------------|---------------------| +| føø | f-5gaa | f_5gaa | +"#; + let out = r#" +
    + +
    OriginalPunycodePunycode + Encoding
    føøf-5gaaf_5gaa
    +
    +"#.trim(); + assert_eq!(render_markdown(src, false), out); + } + #[test] fn it_can_keep_quotes_straight() { assert_eq!(render_markdown("'one'", false), "

    'one'

    \n"); diff --git a/src/utils/string.rs b/src/utils/string.rs index 59931743..97485d7b 100644 --- a/src/utils/string.rs +++ b/src/utils/string.rs @@ -122,6 +122,7 @@ mod tests { }; #[test] + #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_lines_test() { let s = "Lorem\nipsum\ndolor\nsit\namet"; assert_eq!(take_lines(s, 1..3), "ipsum\ndolor"); @@ -163,6 +164,7 @@ mod tests { } #[test] + #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_rustdoc_include_lines_test() { let s = "Lorem\nipsum\ndolor\nsit\namet"; assert_eq!( diff --git a/tests/dummy_book/index_html_test/SUMMARY.md b/tests/dummy_book/index_html_test/SUMMARY.md new file mode 100644 index 00000000..37bf68cd --- /dev/null +++ b/tests/dummy_book/index_html_test/SUMMARY.md @@ -0,0 +1,11 @@ +# Summary + +--- + +- [None of these should be treated as the "index chapter"]() + +# Part 1 + +- [Not this either]() +- [Chapter 1](./chapter_1.md) +- [And not this]() diff --git a/tests/dummy_book/index_html_test/chapter_1.md b/tests/dummy_book/index_html_test/chapter_1.md new file mode 100644 index 00000000..b743fda3 --- /dev/null +++ b/tests/dummy_book/index_html_test/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/tests/dummy_book/src/first/no-headers.md b/tests/dummy_book/src/first/no-headers.md index 8f9a6d17..5d799aa6 100644 --- a/tests/dummy_book/src/first/no-headers.md +++ b/tests/dummy_book/src/first/no-headers.md @@ -1,3 +1,5 @@ Capybara capybara capybara. -Capybara capybara capybara. \ No newline at end of file +Capybara capybara capybara. + +ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex. diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 873a622d..9750a35e 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -467,6 +467,21 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() { assert_doesnt_contain_strings(&second_index, &unexpected_strings); } +#[test] +fn first_chapter_is_copied_as_index_even_if_not_first_elem() { + let temp = DummyBook::new().build().unwrap(); + let mut cfg = Config::default(); + cfg.set("book.src", "index_html_test") + .expect("Couldn't set config.book.src to \"index_html_test\""); + let md = MDBook::load_with_config(temp.path(), cfg).unwrap(); + md.build().unwrap(); + + let root = temp.path().join("book"); + let chapter = fs::read_to_string(root.join("chapter_1.html")).expect("read chapter 1"); + let index = fs::read_to_string(root.join("index.html")).expect("read index"); + pretty_assertions::assert_eq!(chapter, index); +} + #[test] fn theme_dir_overrides_work_correctly() { let book_dir = dummy_book::new_copy_of_example_book().unwrap(); @@ -772,7 +787,7 @@ mod search { ); assert_eq!( docs[&no_headers]["body"], - "Capybara capybara capybara. Capybara capybara capybara." + "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex." ); } diff --git a/tests/searchindex_fixture.json b/tests/searchindex_fixture.json index cf00ca8d..5b23560e 100644 --- a/tests/searchindex_fixture.json +++ b/tests/searchindex_fixture.json @@ -241,7 +241,7 @@ "title": "Unicode stress tests" }, "19": { - "body": "Capybara capybara capybara. Capybara capybara capybara.", + "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.", "breadcrumbs": "First Chapter » No Headers", "id": "19", "title": "First Chapter"