diff --git a/Cargo.lock b/Cargo.lock index d189eddc3b6bb..16d79964238ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,243 @@ dependencies = [ "object 0.37.3", ] +[[package]] +name = "arborium" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00a72aec131b90c1c6495c565c4e4842fdbaf137ea4cacec110d353937ab99d" +dependencies = [ + "arborium-bash", + "arborium-c", + "arborium-cpp", + "arborium-css", + "arborium-go", + "arborium-highlight", + "arborium-html", + "arborium-java", + "arborium-javascript", + "arborium-json", + "arborium-python", + "arborium-ruby", + "arborium-sql", + "arborium-theme", + "arborium-toml", + "arborium-tree-sitter", + "arborium-typescript", + "arborium-yaml", + "dlmalloc", +] + +[[package]] +name = "arborium-bash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34c6fd5230512b1191480100dd7876e972d1d6fd8e04fc62950a46b2a185405a" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-c" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eb49d9d4d314d39e3ad41f891f3c46a417e21127c572621d3bb2b8acb0f67d2" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-cpp" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3813b0fd9e61425fe387333eb77ff5a3ea890f66d89e1262a66372ecad1274f" +dependencies = [ + "arborium-c", + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-css" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d95540ee6ea2c33f40b45d9c40283a5c396e0ceb8529c4f2151932e43858a3b" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-go" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7f8df9adca3da8c9e36889e0f52ab359dd36d168bc677e65fce5f43ca66b0d" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-highlight" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f2df668f0c80bfa2e437f74d45a4922a0e9256c2476560200774be4b60686f3" +dependencies = [ + "arborium-theme", + "arborium-tree-sitter", + "streaming-iterator", +] + +[[package]] +name = "arborium-html" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d898910e534cddb0dca62ff3789b94637979b16d5354f153235438f58d29d6" +dependencies = [ + "arborium-css", + "arborium-javascript", + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-java" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91053783a4c3806cdcf92a7009bfe765ebcf0a13cd49ee361751a68f1f2c10f" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-javascript" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7220c71b1056508a028a4acfa4a10d8ca1713420ac3a36853dfd4a55bb335a4b" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-json" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775321ffff8b71819c6dfead9e62bcd1efc1616306e59f2e1317ba7bef282e1f" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-python" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c5db8f67870cc64b67933a644fb296b3c367e9bfd85aa222a1ff6d49883e25" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-ruby" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7391c3561e43b892241ef216aafa974e1c3a59cd4e29d9cb9a7febc41645c0a8" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-sql" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2c5bd1ed383d8ed8f0269d0cfddb475af9c98580d43f76cc4bb85a6f052633" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-sysroot" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d25c6fe8f35b7803048ca9f0846432011510d5196eb1089cf3a4bb37c35d094" +dependencies = [ + "cc", + "dlmalloc", +] + +[[package]] +name = "arborium-theme" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da38b2109b8af45b7e0bce0c96f7db1c17831a62a23ae586c5705efac635758" + +[[package]] +name = "arborium-toml" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b391b5bf276950b457d0b05efc0089c74b94e2f0939f8a63d98b4b84da5ebf12" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-tree-sitter" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e30ab0ba24131c37823d6d087b4ab6b7d59c6dd26fd1f1470e50582dc07ba" +dependencies = [ + "arborium-sysroot", + "cc", + "regex", + "regex-syntax", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "arborium-typescript" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd96fae6737d469b2042e2f7c37e37e6276465ba1c15162182e4106c189fc41" +dependencies = [ + "arborium-javascript", + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + +[[package]] +name = "arborium-yaml" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef47437d2fe489ba8aa718dd1be3152b80b988b976183e84cfb1865a17cae1e8" +dependencies = [ + "arborium-sysroot", + "cc", + "tree-sitter-language", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -1265,6 +1502,17 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +[[package]] +name = "dlmalloc" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6738d2e996274e499bc7b0d693c858b7720b9cd2543a0643a3087e6cb0a4fa16" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "dyn-clone" version = "1.0.20" @@ -4861,6 +5109,7 @@ dependencies = [ name = "rustdoc" version = "0.0.0" dependencies = [ + "arborium", "arrayvec", "askama", "base64", @@ -5329,6 +5578,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + [[package]] name = "string_cache" version = "0.8.9" @@ -5851,6 +6106,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tree-sitter-language" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae62f7eae5eb549c71b76658648b72cc6111f2d87d24a1e31fa907f4943e3ce" + [[package]] name = "twox-hash" version = "1.6.3" diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index dcfc1ffc251ec..069bd5a8b3610 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -9,6 +9,23 @@ path = "lib.rs" [dependencies] # tidy-alphabetical-start +arborium = { version = "2.0.0", default-features = false, features = [ + "lang-bash", + "lang-c", + "lang-cpp", + "lang-css", + "lang-go", + "lang-html", + "lang-java", + "lang-javascript", + "lang-json", + "lang-python", + "lang-ruby", + "lang-sql", + "lang-toml", + "lang-typescript", + "lang-yaml", +] } arrayvec = { version = "0.7", default-features = false } askama = { version = "0.14", default-features = false, features = ["alloc", "config", "derive"] } base64 = "0.21.7" diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index e5a4593260a42..6ba2b9ffae878 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -312,6 +312,8 @@ pub(crate) struct RenderOptions { pub(crate) disable_minification: bool, /// If `true`, HTML source pages will generate the possibility to expand macros. pub(crate) generate_macro_expansion: bool, + /// If `true`, non-Rust code blocks will be syntax-highlighted using tree-sitter. + pub(crate) highlight_foreign_code: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -805,6 +807,7 @@ impl Options { let no_capture = matches.opt_present("no-capture"); let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); let generate_macro_expansion = matches.opt_present("generate-macro-expansion"); + let highlight_foreign_code = matches.opt_present("highlight-foreign-code"); let extern_html_root_takes_precedence = matches.opt_present("extern-html-root-takes-precedence"); let html_no_source = matches.opt_present("html-no-source"); @@ -916,6 +919,7 @@ impl Options { include_parts_dir, parts_out_dir, disable_minification, + highlight_foreign_code, }; Some((input, options, render_options, loaded_paths)) } diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index 42ade5b90048b..04a136c3e0cc7 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -47,6 +47,7 @@ impl ExternalHtml { edition, playground, heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, } .write_into(&mut bc) .unwrap(); @@ -63,6 +64,7 @@ impl ExternalHtml { edition, playground, heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, } .write_into(&mut ac) .unwrap(); diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 6f6345cd86664..1a96b37ca4ea0 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -1475,5 +1475,31 @@ fn string_without_closing_tag( } } +/// Highlights non-Rust code using arborium (tree-sitter based). +/// Returns `None` if the language is not supported, in which case +/// the caller should fall back to plain escaped text. +pub(crate) fn highlight_foreign_code(lang: &str, code: &str) -> Option { + use std::cell::RefCell; + + thread_local! { + static HIGHLIGHTER: RefCell = + RefCell::new(arborium::Highlighter::new()); + } + + // Map common language aliases to arborium grammar names + let lang = match lang { + "js" => "javascript", + "ts" => "typescript", + "py" => "python", + "rb" => "ruby", + "sh" | "shell" | "zsh" => "bash", + "yml" => "yaml", + "c++" | "cxx" => "cpp", + other => other, + }; + + HIGHLIGHTER.with_borrow_mut(|h| h.highlight(lang, code).ok()) +} + #[cfg(test)] mod tests; diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index a4d377432c914..500867110da14 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -20,6 +20,7 @@ //! edition: Edition::Edition2015, //! playground: &None, //! heading_offset: HeadingOffset::H2, +//! highlight_foreign_code: false, //! }; //! let mut html = String::new(); //! md.write_into(&mut html).unwrap(); @@ -99,6 +100,8 @@ pub struct Markdown<'a> { /// Offset at which we render headings. /// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `

`. pub heading_offset: HeadingOffset, + /// Whether to syntax-highlight non-Rust code blocks using tree-sitter. + pub highlight_foreign_code: bool, } /// A struct like `Markdown` that renders the markdown with a table of contents. pub(crate) struct MarkdownWithToc<'a> { @@ -108,6 +111,7 @@ pub(crate) struct MarkdownWithToc<'a> { pub(crate) error_codes: ErrorCodes, pub(crate) edition: Edition, pub(crate) playground: &'a Option, + pub(crate) highlight_foreign_code: bool, } /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags /// and includes no paragraph tags. @@ -210,6 +214,8 @@ struct CodeBlocks<'p, 'a, I: Iterator>> { // Information about the playground if a URL has been specified, containing an // optional crate name and the URL. playground: &'p Option, + // Whether to use tree-sitter highlighting for non-Rust code blocks. + highlight_foreign_code: bool, } impl<'p, 'a, I: Iterator>> CodeBlocks<'p, 'a, I> { @@ -218,8 +224,15 @@ impl<'p, 'a, I: Iterator>> CodeBlocks<'p, 'a, I> { error_codes: ErrorCodes, edition: Edition, playground: &'p Option, + highlight_foreign_code: bool, ) -> Self { - CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground } + CodeBlocks { + inner: iter, + check_error_codes: error_codes, + edition, + playground, + highlight_foreign_code, + } } } @@ -250,21 +263,31 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { LangString::parse_without_check(lang, self.check_error_codes); if !parse_result.rust { let added_classes = parse_result.added_classes; - let lang_string = if let Some(lang) = parse_result.unknown.first() { - format!("language-{lang}") + let lang = parse_result.unknown.first().map(|s| s.as_str()); + let lang_string = lang.map(|l| format!("language-{l}")).unwrap_or_default(); + let whitespace = if added_classes.is_empty() { "" } else { " " }; + + // Try to highlight with arborium if enabled and we have a language + let code_html = if self.highlight_foreign_code { + lang.and_then(|l| { + highlight::highlight_foreign_code( + l, + original_text.trim_suffix('\n'), + ) + }) + .unwrap_or_else(|| Escape(original_text.trim_suffix('\n')).to_string()) } else { - String::new() + Escape(original_text.trim_suffix('\n')).to_string() }; - let whitespace = if added_classes.is_empty() { "" } else { " " }; + return Some(Event::Html( format!( "
\
\
-                                     {text}\
+                                     {code_html}\
                                  
\
", added_classes = added_classes.join(" "), - text = Escape(original_text.trim_suffix('\n')), ) .into(), )); @@ -1348,6 +1371,7 @@ impl<'a> Markdown<'a> { edition, playground, heading_offset, + highlight_foreign_code, } = self; let replacer = move |broken_link: BrokenLink<'_>| { @@ -1365,7 +1389,7 @@ impl<'a> Markdown<'a> { let p = SpannedLinkReplacer::new(p, links); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); - CodeBlocks::new(p, codes, edition, playground) + CodeBlocks::new(p, codes, edition, playground, highlight_foreign_code) }) } @@ -1421,8 +1445,15 @@ impl<'a> Markdown<'a> { impl MarkdownWithToc<'_> { pub(crate) fn into_parts(self) -> (Toc, String) { - let MarkdownWithToc { content: md, links, ids, error_codes: codes, edition, playground } = - self; + let MarkdownWithToc { + content: md, + links, + ids, + error_codes: codes, + edition, + playground, + highlight_foreign_code, + } = self; // This is actually common enough to special-case if md.is_empty() { @@ -1446,7 +1477,7 @@ impl MarkdownWithToc<'_> { let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1); let p = footnotes::Footnotes::new(p, existing_footnotes); let p = TableWrapper::new(p.map(|(ev, _)| ev)); - let p = CodeBlocks::new(p, codes, edition, playground); + let p = CodeBlocks::new(p, codes, edition, playground, highlight_foreign_code); html::push_html(&mut s, p); }); diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 61fd428746332..e12494bcfbe87 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -306,6 +306,7 @@ fn test_header() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, } .write_into(&mut output) .unwrap(); @@ -359,6 +360,7 @@ fn test_header_ids_multiple_blocks() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, } .write_into(&mut output) .unwrap(); @@ -510,6 +512,7 @@ fn test_ascii_with_prepending_hashtag() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, } .write_into(&mut output) .unwrap(); @@ -541,3 +544,138 @@ fn test_ascii_with_prepending_hashtag() { # hello", ); } + +#[test] +fn test_foreign_code_highlighting_enabled() { + fn t(input: &str, expected_contains: &str) { + let mut map = IdMap::new(); + let mut output = String::new(); + Markdown { + content: input, + links: &[], + ids: &mut map, + error_codes: ErrorCodes::Yes, + edition: DEFAULT_EDITION, + playground: &None, + heading_offset: HeadingOffset::H2, + highlight_foreign_code: true, + } + .write_into(&mut output) + .unwrap(); + assert!( + output.contains(expected_contains), + "expected output to contain {:?}, got: {}", + expected_contains, + output + ); + } + + // Python: keywords should be wrapped in + t( + r#"```python +def hello(): + pass +```"#, + "def", + ); + + // JavaScript: keywords and numbers + t( + r#"```javascript +let x = 42; +```"#, + "let", + ); + + // JSON: strings should be highlighted + t( + r#"```json +{"key": "value"} +```"#, + "", + ); + + // Language aliases should work + t( + r#"```py +def foo(): + pass +```"#, + "def", + ); + + t( + r#"```js +const x = 1; +```"#, + "const", + ); +} + +#[test] +fn test_foreign_code_highlighting_disabled() { + fn t(input: &str, should_not_contain: &str) { + let mut map = IdMap::new(); + let mut output = String::new(); + Markdown { + content: input, + links: &[], + ids: &mut map, + error_codes: ErrorCodes::Yes, + edition: DEFAULT_EDITION, + playground: &None, + heading_offset: HeadingOffset::H2, + highlight_foreign_code: false, + } + .write_into(&mut output) + .unwrap(); + assert!( + !output.contains(should_not_contain), + "expected output NOT to contain {:?}, got: {}", + should_not_contain, + output + ); + } + + // With highlighting disabled, no arborium tags should appear + t( + r#"```python +def hello(): + pass +```"#, + "", + ); + + t( + r#"```javascript +let x = 42; +```"#, + "", + ); +} + +#[test] +fn test_foreign_code_unsupported_language_fallback() { + // Unsupported languages should fall back to plain text even with highlighting enabled + let mut map = IdMap::new(); + let mut output = String::new(); + Markdown { + content: r#"```someunknownlang +hello world +```"#, + links: &[], + ids: &mut map, + error_codes: ErrorCodes::Yes, + edition: DEFAULT_EDITION, + playground: &None, + heading_offset: HeadingOffset::H2, + highlight_foreign_code: true, + } + .write_into(&mut output) + .unwrap(); + + // Should have the language class but no arborium tags + assert!(output.contains("language-someunknownlang")); + assert!(!output.contains(" { /// Controls whether we read / write to cci files in the doc root. Defaults read=true, /// write=true should_merge: ShouldMerge, + /// Whether to syntax-highlight non-Rust code blocks using tree-sitter. + pub(super) highlight_foreign_code: bool, } impl SharedContext<'_> { @@ -495,6 +497,7 @@ impl<'tcx> Context<'tcx> { call_locations, no_emit_shared, html_no_source, + highlight_foreign_code, .. } = options; @@ -580,6 +583,7 @@ impl<'tcx> Context<'tcx> { call_locations, should_merge: options.should_merge, expanded_codes, + highlight_foreign_code, }; let dst = output; diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8740b5935973c..5c4a1f92e4dff 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -651,6 +651,7 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String { edition: shared.edition(), playground: &shared.playground, heading_offset: HeadingOffset::H1, + highlight_foreign_code: shared.highlight_foreign_code, } .write_into(f)) ) @@ -693,6 +694,7 @@ fn render_markdown( edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset, + highlight_foreign_code: cx.shared.highlight_foreign_code, } .write_into(&mut *f)?; f.write_str("") @@ -2164,6 +2166,7 @@ fn render_impl( edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset: HeadingOffset::H4, + highlight_foreign_code: cx.shared.highlight_foreign_code, } .split_summary_and_content() }) diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index df9e8631bbdd6..021ef71c5f3f0 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -229,6 +229,7 @@ fn docblock_toc<'a>( error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, + highlight_foreign_code: cx.shared.highlight_foreign_code, } .into_parts(); let links: Vec> = toc diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 69a79f2736e77..8326f14589c26 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1685,6 +1685,35 @@ pre.rust .doccomment { color: var(--code-highlight-doc-comment-color); } +/* Arborium (tree-sitter) syntax highlighting for non-Rust code blocks */ +a-k { color: var(--code-highlight-kw-color); } +a-f { color: var(--code-highlight-kw-2-color); } +a-s { color: var(--code-highlight-string-color); } +a-c { color: var(--code-highlight-comment-color); } +a-t { color: var(--code-highlight-prelude-color); } +a-v { color: var(--code-highlight-self-color); } +a-co { color: var(--code-highlight-literal-color); } +a-n { color: var(--code-highlight-number-color); } +a-o { color: var(--code-highlight-kw-2-color); } +a-p { /* punctuation: inherit */ } +a-pr { color: var(--code-highlight-prelude-val-color); } +a-at { color: var(--code-highlight-attribute-color); } +a-tg { color: var(--code-highlight-attribute-color); } +a-m { color: var(--code-highlight-macro-color); } +a-l { color: var(--code-highlight-lifetime-color); } +a-ns { color: var(--code-highlight-prelude-color); } +a-cr { color: var(--code-highlight-prelude-val-color); } +a-tt { color: var(--code-highlight-doc-comment-color); font-weight: bold; } +a-st { font-weight: bold; } +a-em { font-style: italic; } +a-tu { color: var(--code-highlight-string-color); text-decoration: underline; } +a-tl { color: var(--code-highlight-literal-color); } +a-tx { text-decoration: line-through; } +a-da { color: #83a300; } +a-dd { color: #ee6868; } +a-eb { color: var(--code-highlight-macro-color); } +a-er { color: #ee6868; text-decoration: wavy underline; } + .rustdoc.src .example-wrap pre.rust a:not([data-nosnippet]) { background: var(--codeblock-link-background); } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index c74c556b6210a..2785775ca393c 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -654,6 +654,14 @@ fn opts() -> Vec { "Add possibility to expand macros in the HTML source code pages", "", ), + opt( + Unstable, + Flag, + "", + "highlight-foreign-code", + "Syntax-highlight non-Rust code blocks using tree-sitter (arborium)", + "", + ), // deprecated / removed options opt( Stable, diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 4ca2c104888bb..883cb5f64f04c 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -87,6 +87,7 @@ pub(crate) fn render_and_write>( error_codes, edition, playground: &playground, + highlight_foreign_code: options.highlight_foreign_code, } .write_into(f) } else { @@ -98,6 +99,7 @@ pub(crate) fn render_and_write>( edition, playground: &playground, heading_offset: HeadingOffset::H1, + highlight_foreign_code: options.highlight_foreign_code, } .write_into(f) } diff --git a/tests/run-make/rustdoc-default-output/output-default.stdout b/tests/run-make/rustdoc-default-output/output-default.stdout index 4e28be347cbb1..d078427175e24 100644 --- a/tests/run-make/rustdoc-default-output/output-default.stdout +++ b/tests/run-make/rustdoc-default-output/output-default.stdout @@ -201,6 +201,9 @@ Options: --generate-macro-expansion Add possibility to expand macros in the HTML source code pages + --highlight-foreign-code + Syntax-highlight non-Rust code blocks using + tree-sitter (arborium) --plugin-path DIR removed, see issue #44136 for