diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 01b77ac9..73ff02d2 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -168,12 +168,12 @@ jobs: - { target: aarch64-unknown-linux-gnu , os: ubuntu-latest, dpkg_arch: arm64, use-cross: true } - { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest, dpkg_arch: armhf, use-cross: true } - { target: arm-unknown-linux-musleabihf, os: ubuntu-latest, dpkg_arch: musl-linux-armhf, use-cross: true } - - { target: i686-pc-windows-msvc , os: windows-2019, } + - { target: i686-pc-windows-msvc , os: windows-2025, } - { target: i686-unknown-linux-gnu , os: ubuntu-latest, dpkg_arch: i686, use-cross: true } - { target: i686-unknown-linux-musl , os: ubuntu-latest, dpkg_arch: musl-linux-i686, use-cross: true } - { target: x86_64-apple-darwin , os: macos-13, } - { target: aarch64-apple-darwin , os: macos-14, } - - { target: x86_64-pc-windows-msvc , os: windows-2019, } + - { target: x86_64-pc-windows-msvc , os: windows-2025, } - { target: x86_64-unknown-linux-gnu , os: ubuntu-latest, dpkg_arch: amd64, use-cross: true } - { target: x86_64-unknown-linux-musl , os: ubuntu-latest, dpkg_arch: musl-linux-amd64, use-cross: true } env: diff --git a/.gitmodules b/.gitmodules index 0577d9d0..1ba4495e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -275,3 +275,6 @@ [submodule "assets/themes/Catppuccin"] path = assets/themes/Catppuccin url = https://github.com/SchweGELBin/catppuccin-bat-sub.git +[submodule "assets/syntaxes/02_Extra/SmartVHDL"] + path = assets/syntaxes/02_Extra/SmartVHDL + url = https://github.com/TheClams/SmartVHDL diff --git a/CHANGELOG.md b/CHANGELOG.md index da785ae9..593ca14d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,15 @@ - Make highlight tests fail when new syntaxes don't have fixtures PR #3255 (@dan-hipschman) - Fix crash for multibyte characters in file path, see issue #3230 and PR #3245 (@HSM95) - Add missing mappings for various bash/zsh files, see PR #3262 (@AdamGaskins) +- Send all bat errors to stderr by default, see #3336 (@JerryImMouse) ## Other - Update base16 README links to community driven base16 work #2871 (@JamyGolden) - Work around build failures when building `bat` from vendored sources #3179 (@dtolnay) - CICD: Stop building for x86_64-pc-windows-gnu which fails #3261 (Enselic) +- CICD: CICD: replace windows-2019 runners with windows-2025 #3339 (@cyqsimon) +- Build script: replace string-based codegen with quote-based codegen #3340 (@cyqsimon) ## Syntaxes @@ -36,6 +39,8 @@ - Update quadlet syntax mapping rules to cover quadlets in subdirectories #3299 (@cyqsimon) - Add syntax Typst #3300 (@cskeeters) - Map `.mill` files to Scala syntax for Mill build tool configuration files #3311 (@krikera) +- Add syntax highlighting for VHDL, see #3337 (@JerryImMouse) +- Add syntax mapping for certbot certificate configuration #3338 (@cyqsimon) - Update Lean syntax from Lean 3 to Lean 4 #3322 (@YDX-2147483647) ## Themes diff --git a/Cargo.lock b/Cargo.lock index f836e3eb..c3b4a944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,9 @@ dependencies = [ "path_abs", "plist", "predicates", + "prettyplease", + "proc-macro2", + "quote", "regex", "semver", "serde", @@ -149,6 +152,7 @@ dependencies = [ "serde_yaml", "serial_test", "shell-words", + "syn", "syntect", "tempfile", "terminal-colorsaurus", @@ -559,9 +563,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -972,9 +976,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -1163,10 +1167,20 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.92" +name = "prettyplease" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -1182,9 +1196,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1330,9 +1344,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ "serde", ] @@ -1440,9 +1454,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.95" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1644,39 +1658,43 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "0207d6ed1852c2a124c1fbec61621acb8330d2bf969a5d0643131e9affd985a5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" dependencies = [ "serde", ] [[package]] -name = "toml_edit" -version = "0.22.22" +name = "toml_parser" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "b5c1c469eda89749d2230d8156a5969a69ffe0d6d01200581cdc6110674d293e" dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b679217f2848de74cabd3e8fc5e6d66f40b7da40f8e1954d92054d9010690fd5" + [[package]] name = "typenum" version = "1.17.0" @@ -2003,12 +2021,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.22" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980" -dependencies = [ - "memchr", -] +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" [[package]] name = "wit-bindgen-rt" diff --git a/Cargo.toml b/Cargo.toml index bd7bd912..ad9f10db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ nu-ansi-term = "0.50.0" ansi_colours = "^1.2" bincode = "1.0" console = "0.15.10" -flate2 = "1.0" +flate2 = "1.1" once_cell = "1.20" thiserror = "2.0" wild = { version = "2.2", optional = true } @@ -108,11 +108,15 @@ anyhow = "1.0.97" indexmap = { version = "2.8.0", features = ["serde"] } itertools = "0.14.0" once_cell = "1.20" +prettyplease = "0.2.35" +proc-macro2 = "1.0.95" +quote = "1.0.40" regex = "1.10.6" serde = "1.0" serde_derive = "1.0" serde_with = { version = "3.12.0", default-features = false, features = ["macros"] } -toml = { version = "0.8.19", features = ["preserve_order"] } +syn = { version = "2.0.104", features = ["full"] } +toml = { version = "0.9.1", features = ["preserve_order"] } walkdir = "2.5" [build-dependencies.clap] diff --git a/assets/manual/bat.1.in b/assets/manual/bat.1.in index 4dc51148..84608c15 100644 --- a/assets/manual/bat.1.in +++ b/assets/manual/bat.1.in @@ -203,8 +203,18 @@ Configure which elements (line numbers, file headers, grid borders, Git modifica \&..) to display in addition to the file contents. The argument is a comma\-separated list of components to display (e.g. 'numbers,changes,grid') or a pre\-defined style ('full'). To set a default style, add the '\-\-style=".."' option to the configuration file or -export the BAT_STYLE environment variable (e.g.: export BAT_STYLE=".."). Possible -values: *default*, full, auto, plain, changes, header, header-filename, header-filesize, grid, +export the BAT_STYLE environment variable (e.g.: export BAT_STYLE=".."). +.IP +When styles are specified in multiple places, the "nearest" set of styles take precedence. +The command\-line arguments are the highest priority, followed by the BAT_STYLE environment +variable, and then the configuration file. If any set of styles consists entirely of +components prefixed with "+" or "\-", it will modify the previous set of styles instead of +replacing them. +.IP +By default, the following components are enabled: +changes, grid, header\-filename, numbers, snip +.IP +Possible values: *default*, full, auto, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip. .HP \fB\-r\fR, \fB\-\-line\-range\fR ... diff --git a/assets/syntaxes/02_Extra/SmartVHDL b/assets/syntaxes/02_Extra/SmartVHDL new file mode 160000 index 00000000..b45507dd --- /dev/null +++ b/assets/syntaxes/02_Extra/SmartVHDL @@ -0,0 +1 @@ +Subproject commit b45507ddc8a417b84872a1f28388f9650851fca5 diff --git a/assets/syntaxes/02_Extra/typst-syntax-highlight b/assets/syntaxes/02_Extra/typst-syntax-highlight index 1bde1ea5..3f2561d4 160000 --- a/assets/syntaxes/02_Extra/typst-syntax-highlight +++ b/assets/syntaxes/02_Extra/typst-syntax-highlight @@ -1 +1 @@ -Subproject commit 1bde1ea511d86c622a0fd27d9e0db3e047d4094f +Subproject commit 3f2561d4d891f9f326fa70f9f06b6e99fda8adc3 diff --git a/build/syntax_mapping.rs b/build/syntax_mapping.rs index 48468b9a..64be4bb9 100644 --- a/build/syntax_mapping.rs +++ b/build/syntax_mapping.rs @@ -9,6 +9,8 @@ use anyhow::{anyhow, bail}; use indexmap::IndexMap; use itertools::Itertools; use once_cell::sync::Lazy; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens, TokenStreamExt}; use regex::Regex; use serde_derive::Deserialize; use serde_with::DeserializeFromStr; @@ -34,13 +36,14 @@ impl FromStr for MappingTarget { } } } -impl MappingTarget { - fn codegen(&self) -> String { - match self { - Self::MapTo(syntax) => format!(r###"MappingTarget::MapTo(r#"{syntax}"#)"###), - Self::MapToUnknown => "MappingTarget::MapToUnknown".into(), - Self::MapExtensionToUnknown => "MappingTarget::MapExtensionToUnknown".into(), - } +impl ToTokens for MappingTarget { + fn to_tokens(&self, tokens: &mut TokenStream) { + let t = match self { + Self::MapTo(syntax) => quote! { MappingTarget::MapTo(#syntax) }, + Self::MapToUnknown => quote! { MappingTarget::MapToUnknown }, + Self::MapExtensionToUnknown => quote! { MappingTarget::MapExtensionToUnknown }, + }; + tokens.append_all(t); } } @@ -116,22 +119,17 @@ impl FromStr for Matcher { Ok(Self(non_empty_segments)) } } -impl Matcher { - fn codegen(&self) -> String { - match self.0.len() { - 0 => unreachable!("0-length matcher should never be created"), - // if-let guard would be ideal here - // see: https://github.com/rust-lang/rust/issues/51114 - 1 if self.0[0].is_text() => { - let s = self.0[0].text().unwrap(); - format!(r###"Lazy::new(|| Some(build_matcher_fixed(r#"{s}"#)))"###) +impl ToTokens for Matcher { + fn to_tokens(&self, tokens: &mut TokenStream) { + let t = match self.0.as_slice() { + [] => unreachable!("0-length matcher should never be created"), + [MatcherSegment::Text(text)] => { + quote! { Lazy::new(|| Some(build_matcher_fixed(#text))) } } // parser logic ensures that this case can only happen when there are dynamic segments - _ => { - let segs = self.0.iter().map(MatcherSegment::codegen).join(", "); - format!(r###"Lazy::new(|| build_matcher_dynamic(&[{segs}]))"###) - } - } + segs @ [_, ..] => quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ])) }, + }; + tokens.append_all(t); } } @@ -143,6 +141,15 @@ enum MatcherSegment { Text(String), Env(String), } +impl ToTokens for MatcherSegment { + fn to_tokens(&self, tokens: &mut TokenStream) { + let t = match self { + Self::Text(text) => quote! { MatcherSegment::Text(#text) }, + Self::Env(env) => quote! { MatcherSegment::Env(#env) }, + }; + tokens.append_all(t); + } +} #[allow(dead_code)] impl MatcherSegment { fn is_text(&self) -> bool { @@ -163,12 +170,6 @@ impl MatcherSegment { Self::Env(t) => Some(t), } } - fn codegen(&self) -> String { - match self { - Self::Text(s) => format!(r###"MatcherSegment::Text(r#"{s}"#)"###), - Self::Env(s) => format!(r###"MatcherSegment::Env(r#"{s}"#)"###), - } - } } /// A struct that models a single .toml file in /src/syntax_mapping/builtins/. @@ -194,22 +195,19 @@ impl MappingDefModel { #[derive(Clone, Debug)] struct MappingList(Vec<(Matcher, MappingTarget)>); -impl MappingList { - fn codegen(&self) -> String { - let array_items: Vec<_> = self +impl ToTokens for MappingList { + fn to_tokens(&self, tokens: &mut TokenStream) { + let len = self.0.len(); + let array_items = self .0 .iter() - .map(|(matcher, target)| { - format!("({m}, {t})", m = matcher.codegen(), t = target.codegen()) - }) - .collect(); - let len = array_items.len(); + .map(|(matcher, target)| quote! { (#matcher, #target) }); - format!( - "/// Generated by build script from /src/syntax_mapping/builtins/.\n\ - pub(crate) static BUILTIN_MAPPINGS: [(Lazy>, MappingTarget); {len}] = [\n{items}\n];", - items = array_items.join(",\n") - ) + let t = quote! { + /// Generated by build script from /src/syntax_mapping/builtins/. + pub(crate) static BUILTIN_MAPPINGS: [(Lazy>, MappingTarget); #len] = [#(#array_items),*]; + }; + tokens.append_all(t); } } @@ -291,10 +289,15 @@ pub fn build_static_mappings() -> anyhow::Result<()> { let mappings = read_all_mappings()?; + // IMPRV: parse + unparse is a bit cringe, but there seems to be no better + // option given the limited APIs of `prettyplease` + let rs_src = syn::parse_file(&mappings.to_token_stream().to_string())?; + let rs_src_pretty = prettyplease::unparse(&rs_src); + let codegen_path = Path::new(&env::var_os("OUT_DIR").ok_or(anyhow!("OUT_DIR is unset"))?) .join("codegen_static_syntax_mappings.rs"); - fs::write(codegen_path, mappings.codegen())?; + fs::write(codegen_path, rs_src_pretty)?; Ok(()) } diff --git a/src/error.rs b/src/error.rs index 007737b0..e5db81cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -67,7 +67,13 @@ pub fn default_error_handler(error: &Error, output: &mut dyn Write) { .ok(); } _ => { - writeln!(output, "{}: {}", Red.paint("[bat error]"), error).ok(); + writeln!( + &mut std::io::stderr().lock(), + "{}: {}", + Red.paint("[bat error]"), + error + ) + .ok(); } }; } diff --git a/src/syntax_mapping/builtins/unix-family/50-certbot.toml b/src/syntax_mapping/builtins/unix-family/50-certbot.toml new file mode 100644 index 00000000..52d47988 --- /dev/null +++ b/src/syntax_mapping/builtins/unix-family/50-certbot.toml @@ -0,0 +1,3 @@ +# See https://eff-certbot.readthedocs.io/en/stable/using.html#configuration-file +[mappings] +"INI" = ["/etc/letsencrypt/renewal/*.conf"] diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 5a97c517..540f85e8 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -450,6 +450,16 @@ fn stdin_to_stdout_cycle() -> io::Result<()> { Ok(()) } +#[cfg(unix)] +#[test] +fn bat_error_to_stderr() { + bat() + .arg("/tmp") + .assert() + .failure() + .stderr(predicate::str::contains("[bat error]")); +} + #[cfg(unix)] #[test] fn no_args_doesnt_break() { diff --git a/tests/syntax-tests/highlighted/VHDL/test.vhdl b/tests/syntax-tests/highlighted/VHDL/test.vhdl new file mode 100644 index 00000000..a1bb6b2b --- /dev/null +++ b/tests/syntax-tests/highlighted/VHDL/test.vhdl @@ -0,0 +1,74 @@ +-- This is a single-line comment + +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; +use IEEE.NUMERIC_STD.ALL; + +entity SyntaxTest is + generic ( + DATA_WIDTH : integer := 8 + ); + port ( + clk : in std_logic; + rst : in std_logic; + a, b : in std_logic_vector(DATA_WIDTH - 1 downto 0); + sel : in std_logic; + result : out std_logic_vector(DATA_WIDTH - 1 downto 0); + flag : out std_logic + ); +end SyntaxTest; + +architecture Behavioral of SyntaxTest is + + signal tmp : std_logic_vector(DATA_WIDTH - 1 downto 0); + signal done : std_logic := '0'; + + type state_type is (IDLE, LOAD, EXECUTE, DONE); + signal state : state_type := IDLE; + +begin + + process(clk, rst) + variable i : integer := 0; + begin + if rst = '1' then + tmp <= (others => '0'); + flag <= '0'; + state <= IDLE; + + elsif rising_edge(clk) then + case state is + when IDLE => + if sel = '1' then + tmp <= a and b; + state <= EXECUTE; + else + tmp <= a or b; + state <= LOAD; + end if; + + when LOAD => + tmp <= a xor b; + state <= EXECUTE; + + when EXECUTE => + if i < DATA_WIDTH then + tmp(i) <= not tmp(i); + i := i + 1; + else + state <= DONE; + end if; + + when DONE => + flag <= '1'; + state <= IDLE; + + when others => + state <= IDLE; + end case; + end if; + end process; + + result <= tmp; + +end Behavioral; \ No newline at end of file diff --git a/tests/syntax-tests/source/VHDL/test.vhdl b/tests/syntax-tests/source/VHDL/test.vhdl new file mode 100644 index 00000000..096594ed --- /dev/null +++ b/tests/syntax-tests/source/VHDL/test.vhdl @@ -0,0 +1,74 @@ +-- This is a single-line comment + +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; +use IEEE.NUMERIC_STD.ALL; + +entity SyntaxTest is + generic ( + DATA_WIDTH : integer := 8 + ); + port ( + clk : in std_logic; + rst : in std_logic; + a, b : in std_logic_vector(DATA_WIDTH - 1 downto 0); + sel : in std_logic; + result : out std_logic_vector(DATA_WIDTH - 1 downto 0); + flag : out std_logic + ); +end SyntaxTest; + +architecture Behavioral of SyntaxTest is + + signal tmp : std_logic_vector(DATA_WIDTH - 1 downto 0); + signal done : std_logic := '0'; + + type state_type is (IDLE, LOAD, EXECUTE, DONE); + signal state : state_type := IDLE; + +begin + + process(clk, rst) + variable i : integer := 0; + begin + if rst = '1' then + tmp <= (others => '0'); + flag <= '0'; + state <= IDLE; + + elsif rising_edge(clk) then + case state is + when IDLE => + if sel = '1' then + tmp <= a and b; + state <= EXECUTE; + else + tmp <= a or b; + state <= LOAD; + end if; + + when LOAD => + tmp <= a xor b; + state <= EXECUTE; + + when EXECUTE => + if i < DATA_WIDTH then + tmp(i) <= not tmp(i); + i := i + 1; + else + state <= DONE; + end if; + + when DONE => + flag <= '1'; + state <= IDLE; + + when others => + state <= IDLE; + end case; + end if; + end process; + + result <= tmp; + +end Behavioral; \ No newline at end of file