diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index aedf6e9c..b1732b43 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,9 +7,26 @@ assignees: '' --- - + + +**What steps will reproduce the bug?** + +1. step 1 +2. step 2 +3. ... + +**What happens?** ... diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index c2ad5607..d8c95bc5 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -7,3 +7,5 @@ assignees: '' --- + diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 710bd4f2..2ccb2f32 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -1,7 +1,7 @@ name: CICD env: - MIN_SUPPORTED_RUST_VERSION: "1.51.0" + MIN_SUPPORTED_RUST_VERSION: "1.60.0" CICD_INTERMEDIATES_DIR: "_cicd-intermediates" on: @@ -39,6 +39,8 @@ jobs: min_version: name: Minimum supported rust version runs-on: ubuntu-20.04 + env: + MSRV_FEATURES: --no-default-features --features minimal-application,bugreport,build-assets steps: - name: Checkout source code uses: actions/checkout@v2 @@ -54,12 +56,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --locked --all-targets --all-features + args: --locked --all-targets ${{ env.MSRV_FEATURES }} - name: Run tests uses: actions-rs/cargo@v1 with: command: test - args: --locked + args: --locked ${{ env.MSRV_FEATURES }} test_with_new_syntaxes_and_themes: name: Run tests with updated syntaxes and themes @@ -144,6 +146,18 @@ jobs: toolchain: stable default: true profile: minimal + - name: Print -h + uses: actions-rs/cargo@v1 + with: + command: run + args: --locked -- -h + - name: Print --help + uses: actions-rs/cargo@v1 + with: + command: run + args: --locked -- --help + - name: Show man page + run: man $(find . -name bat.1) - name: Check documentation env: RUSTDOCFLAGS: -D warnings @@ -168,7 +182,7 @@ jobs: - { target: x86_64-apple-darwin , os: macos-10.15 } - { target: x86_64-pc-windows-gnu , os: windows-2019 } - { target: x86_64-pc-windows-msvc , os: windows-2019 } - - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 } + - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } steps: - name: Checkout source code diff --git a/.gitmodules b/.gitmodules index 50616ed2..156f4206 100644 --- a/.gitmodules +++ b/.gitmodules @@ -239,3 +239,8 @@ [submodule "assets/syntaxes/02_Extra/SublimeJQ"] path = assets/syntaxes/02_Extra/SublimeJQ url = https://github.com/zogwarg/SublimeJQ.git +[submodule "assets/syntaxes/02_Extra/cmd-help"] + path = assets/syntaxes/02_Extra/cmd-help + url = https://github.com/victor-gp/cmd-help-sublime-syntax.git + branch = main + shallow = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c193aee..c06342b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,22 +2,75 @@ ## Features -- Correctly render tab stops in --show-all, see #2038 (@Synthetica9) +- Make the default macOS theme depend on Dark Mode. See #2197, #1746 (@Enselic) ## Bugfixes +- Prevent fork nightmare with `PAGER=batcat`. See #2235 (@johnmatthiggins) +- Make `--no-paging`/`-P` override `--paging=...` if passed as a later arg, see #2201 (@themkat) +- `--map-syntax` and `--ignored-suffix` now works together, see #2093 (@czzrr) + ## Other -- Include contents of custom assets `metadata.yaml` in `--diagnostics`. See #2107 (@Enselic) +- Relaxed glibc requirements on amd64, see #2106 and #2194 (@sharkdp) +- Improved fish completions. See #2275 (@zgracem) +- Stop pre-processing ANSI escape characters. Syntax highlighting on ANSI escaped input is not supported. See #2185 and #2189 (@Enselic) ## Syntaxes -- Mapped clang-format config file (.clang-format) to YAML syntax (@TruncatedDinosour) +- NSE (Nmap Scripting Engine) is mapped to Lua, see #2151 (@Cre3per) +- Correctly color `fstab` dump and pass fields, see #2246 (@yuvalmo) +- Update `Command Help` syntax, see #2255 +- `Julia`: Fix syntax highlighting for function name starting with `struct`, see #2230 +- Minor update to `LiveScript`, see #2291 ## Themes ## `bat` as a library +- Make `bat::PrettyPrinter::syntaxes()` iterate over new `bat::Syntax` struct instead of `&syntect::parsing::SyntaxReference`. See #2222 (@Enselic) + + +# v0.21.0 + +## Features + +- Correctly render tab stops in `--show-all`, see #2038 (@Synthetica9) +- Add a `--style=default` option and make it the default. It is less verbose than `full`, see #2061 (@IsaacHorvath) +- Enable BusyBox `less` as pager, see #2162 (@nfisher1226) +- File extensions are now matched case-insensitively. See #1854, #2181 (@Enselic) + +## Bugfixes + +- Bump `regex` dependency from 1.5.4 to 1.5.5 to fix [CVE-2022-24713](https://blog.rust-lang.org/2022/03/08/cve-2022-24713.html), see #2145, #2139 (@Enselic) +- `bat` no longer crashes when encountering files that references missing syntaxes. See #915, #2181 (@Enselic) + +## Performance + +- Skip syntax highlighting on long lines (> 16384 chars) to help improve performance. See #2165 (@keith-hall) +- Vastly improve startup time by lazy-loading syntaxes via syntect 5.0.0. This makes bat display small files ~75% faster than before. See #951, #2181 (@Enselic) + +## Other + +- Include info about custom assets in `--diagnostics` if used. See #2107, #2144 (@Enselic) + +## Syntaxes + +- Mapped clang-format config file (.clang-format) to YAML syntax (@TruncatedDinosour) +- log syntax: improved handling of escape characters in double quoted strings. See #2123 (@keith-hall) +- Associate `/var/spool/mail/*` and `/var/mail/*` with the `Email` syntax. See #2156 (@cyqsimon) +- Added cmd-help syntax to scope --help messages. See #2148 (@victor-gp) +- Slightly adjust Zig syntax. See #2136 (@Enselic) +- Associate `.inf` files with the `INI` syntax. See #2190 (@Enselic) + +## `bat` as a library + +- Allow configuration of `show_nonprintable` with `PrettyPrinter`, see #2142 +- The binary format of syntaxes.bin has been changed due to syntaxes now being lazy-loaded via syntect 5.0.0. See #2181 (@Enselic) +- Mark `bat::error::Error` enum as `#[non_exhaustive]` to allow adding new variants without future semver breakage. See #2181 (@Enselic) +- Change `Error::SyntectError(syntect::LoadingError)` to `Error::SyntectError(syntect::Error)`. See #2181 (@Enselic) +- Add `Error::SyntectLoadingError(syntect::LoadingError)` enum variant. See #2181 (@Enselic) + # v0.20.0 diff --git a/Cargo.lock b/Cargo.lock index 476d6589..f308ae19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bat" -version = "0.20.0" +version = "0.21.0" dependencies = [ "ansi_colours", "ansi_term", @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "bugreport" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0014b4b2b4f63bfe69c3838470121290cc437fdc79785d408a761a21e8b2404c" +checksum = "535120b8182547808081a66f1f77a64533c780b23da26763e0ee34dfb94f98c9" dependencies = [ "git-version", "shell-escape", @@ -193,18 +193,28 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.34.0" +version = "3.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd" dependencies = [ - "ansi_term", "atty", "bitflags", + "clap_lex", + "indexmap", + "once_cell", "strsim", - "term_size", + "termcolor", + "terminal_size", "textwrap", - "unicode-width", - "vec_map", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -221,14 +231,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" dependencies = [ "encode_unicode", "libc", "once_cell", - "regex", "terminal_size", "unicode-width", "winapi", @@ -382,13 +391,11 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if", "crc32fast", - "libc", "miniz_oxide", ] @@ -452,9 +459,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7d3b96ec1fcaa8431cf04a4f1ef5caafe58d5cf7bcc31f09c1626adddb0ffe" +checksum = "2994bee4a3a6a51eb90c218523be382fd7ea09b16380b9312e9dbe955ff7c7d1" dependencies = [ "bitflags", "libc", @@ -471,9 +478,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -580,23 +587,17 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" -version = "0.2.112" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "libgit2-sys" -version = "0.13.1+1.4.2" +version = "0.14.0+1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e598aa7a4faedf1ea1b4608f582b06f0f40211eec551b7ef36019ae3f62def" +checksum = "47a00859c70c8a4f7218e6d1cc32875c4b55f6799445b842b0d8ed5e4c3d959b" dependencies = [ "cc", "libc", @@ -661,36 +662,24 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ "adler", - "autocfg", ] [[package]] name = "nix" -version = "0.23.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", - "cc", "cfg-if", "libc", - "memoffset", ] [[package]] @@ -710,9 +699,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "onig" @@ -736,6 +725,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + [[package]] name = "parking_lot" version = "0.11.2" @@ -858,11 +853,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -895,9 +890,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -912,9 +907,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "remove_dir_all" @@ -969,24 +964,24 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "1.0.6" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -1006,9 +1001,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" dependencies = [ "indexmap", "ryu", @@ -1066,26 +1061,26 @@ checksum = "8207e78455ffdf55661170876f88daf85356e4edd54e0a3dbc79586ca1e50cbe" [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.85" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "syntect" -version = "4.6.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b20815bbe80ee0be06e6957450a841185fcf690fe0178f14d77a05ce2caa031" +checksum = "c6c454c27d9d7d9a84c7803aaa3c50cd088d2906fe3c6e42da3209aa623576a8" dependencies = [ "bincode", "bitflags", @@ -1093,13 +1088,14 @@ dependencies = [ "flate2", "fnv", "lazy_static", - "lazycell", + "once_cell", "onig", "plist", "regex-syntax", "serde", "serde_derive", "serde_json", + "thiserror", "walkdir", "yaml-rust", ] @@ -1128,16 +1124,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.2" @@ -1165,28 +1151,27 @@ checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" [[package]] name = "textwrap" -version = "0.11.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" dependencies = [ - "term_size", - "unicode-width", + "terminal_size", ] [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" dependencies = [ "proc-macro2", "quote", @@ -1224,6 +1209,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -1239,12 +1230,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "url" version = "2.2.2" @@ -1263,12 +1248,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -1303,9 +1282,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wild" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" +checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74" dependencies = [ "glob", ] diff --git a/Cargo.toml b/Cargo.toml index 95fb5bd1..991e1b1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ homepage = "https://github.com/sharkdp/bat" license = "MIT/Apache-2.0" name = "bat" repository = "https://github.com/sharkdp/bat" -version = "0.20.0" +version = "0.21.0" exclude = ["assets/syntaxes/*", "assets/themes/*"] build = "build.rs" edition = '2018' @@ -33,8 +33,7 @@ minimal-application = [ ] git = ["git2"] # Support indicating git modifications paging = ["shell-words", "grep-cli"] # Support applying a pager on the output -# Add "syntect/plist-load" when https://github.com/trishume/syntect/pull/345 reaches us -build-assets = ["syntect/yaml-load", "syntect/dump-create", "regex", "walkdir"] +build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"] # You need to use one of these if you depend on bat as a library: regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine @@ -45,11 +44,11 @@ atty = { version = "0.2.14", optional = true } ansi_term = "^0.12.1" ansi_colours = "^1.1" bincode = "1.0" -console = "0.15.0" +console = "0.15.1" flate2 = "1.0" -once_cell = "1.9" +once_cell = "1.13" thiserror = "1.0" -wild = { version = "2.0", optional = true } +wild = { version = "2.1", optional = true } content_inspector = "0.2.4" encoding = "0.2" shell-words = { version = "1.1.0", optional = true } @@ -60,28 +59,28 @@ serde_yaml = "0.8" semver = "1.0" path_abs = { version = "0.5", default-features = false } clircle = "0.3" -bugreport = { version = "0.4", optional = true } +bugreport = { version = "0.5.0", optional = true } dirs-next = { version = "2.0.0", optional = true } grep-cli = { version = "0.1.6", optional = true } -regex = { version = "1.0", optional = true } +regex = { version = "1.6.0", optional = true } walkdir = { version = "2.0", optional = true } bytesize = { version = "1.1.0" } [dependencies.git2] -version = "0.14" +version = "0.15" optional = true default-features = false [dependencies.syntect] -version = "4.6.0" +version = "5.0.0" default-features = false features = ["parsing"] [dependencies.clap] -version = "2.34" +version = "3.2.20" optional = true default-features = false -features = ["suggestions", "color", "wrap_help"] +features = ["std", "suggestions", "color", "wrap_help", "cargo"] [dev-dependencies] assert_cmd = "2.0.4" @@ -91,10 +90,10 @@ wait-timeout = "0.2.0" tempfile = "3.3.0" [target.'cfg(unix)'.dev-dependencies] -nix = "0.23.1" +nix = { version = "0.24.2", default-features = false, features = ["term"] } [build-dependencies] -clap = { version = "2.34", optional = true } +clap = { version = "3.2.20", optional = true } [profile.release] lto = true diff --git a/README.md b/README.md index 95b67bd0..b1e24339 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ You can combine `bat` with `git diff` to view lines around code changes with pro highlighting: ```bash batdiff() { - git diff --name-only --diff-filter=d | xargs bat --diff + git diff --name-only --relative --diff-filter=d | xargs bat --diff } ``` If you prefer to use this as a separate tool, check out `batdiff` in [`bat-extras`](https://github.com/eth-p/bat-extras). @@ -212,6 +212,24 @@ Also, note that this will [not work](https://github.com/sharkdp/bat/issues/1145) The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`. +#### Highlighting `--help` messages + +You can use `bat` to colorize help text: `$ cp --help | bat -plhelp` + +You can also use a wrapper around this: + +```bash +# in your .bashrc/.zshrc/*rc +alias bathelp='bat --plain --language=help' +help() { + "$@" --help 2>&1 | bathelp +} +``` + +Then you can do `$ help cp` or `$ help git commit`. + +Please report any issues with the help syntax in [this repository](https://github.com/victor-gp/cmd-help-sublime-syntax). + ## Installation @@ -400,7 +418,7 @@ binaries are also available: look for archives with `musl` in the file name. ### From source -If you want to build `bat` from source, you need Rust 1.51 or +If you want to build `bat` from source, you need Rust 1.60.0 or higher. You can then use `cargo` to build everything: ```bash diff --git a/assets/acknowledgements.bin b/assets/acknowledgements.bin index b3d31ea7..748d1e32 100644 Binary files a/assets/acknowledgements.bin and b/assets/acknowledgements.bin differ diff --git a/assets/completions/_bat.ps1.in b/assets/completions/_bat.ps1.in index 0a706752..cd8818b4 100644 --- a/assets/completions/_bat.ps1.in +++ b/assets/completions/_bat.ps1.in @@ -37,7 +37,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script [CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').') [CompletionResult]::new('--map-syntax', 'map-syntax', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').') [CompletionResult]::new('--theme', 'theme', [CompletionResultType]::ParameterName, 'Set the color theme for syntax highlighting.') - [CompletionResult]::new('--style', 'style', [CompletionResultType]::ParameterName, 'Comma-separated list of style elements to display (*auto*, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip).') + [CompletionResult]::new('--style', 'style', [CompletionResultType]::ParameterName, 'Comma-separated list of style elements to display (*default*, auto, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip).') [CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.') [CompletionResult]::new('--line-range', 'line-range', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.') [CompletionResult]::new('-A', 'A', [CompletionResultType]::ParameterName, 'Show non-printable characters (space, tab, newline, ..).') diff --git a/assets/completions/bat.fish.in b/assets/completions/bat.fish.in index 4b118aa7..f3a5f9b8 100644 --- a/assets/completions/bat.fish.in +++ b/assets/completions/bat.fish.in @@ -1,78 +1,219 @@ # Fish Shell Completions -# Place or symlink to $XDG_CONFIG_HOME/fish/completions/{{PROJECT_EXECUTABLE}}.fish ($XDG_CONFIG_HOME is usually set to ~/.config) +# Copy or symlink to $XDG_CONFIG_HOME/fish/completions/{{PROJECT_EXECUTABLE}}.fish +# ($XDG_CONFIG_HOME is usually set to ~/.config) -# Helper function: -function __{{PROJECT_EXECUTABLE}}_autocomplete_languages --description "A helper function used by "(status filename) - {{PROJECT_EXECUTABLE}} --list-languages | awk -F':' ' - { - lang=$1 - split($2, exts, ",") +# `bat` is `batcat` on Debian and Ubuntu +set bat {{PROJECT_EXECUTABLE}} - for (i in exts) { - ext=exts[i] - if (ext !~ /[A-Z].*/ && ext !~ /^\..*rc$/) { - print ext"\t"lang - } - } - } - ' | sort +# Helper functions: + +function __bat_complete_files -a token + # Cheat to complete files by calling `complete -C` on a fake command name, + # like `__fish_complete_directories` does. + set -l fake_command aaabccccdeeeeefffffffffgghhhhhhiiiii + complete -C"$fake_command $token" end +function __bat_complete_one_language -a comp + command $bat --list-languages | string split -f1 : | string match -e "$comp" +end + +function __bat_complete_list_languages + for spec in (command $bat --list-languages) + set -l name (string split -f1 : $spec) + for ext in (string split -f2 : $spec | string split ,) + test -n "$ext"; or continue + string match -rq '[/*]' $ext; and continue + printf "%s\t%s\n" $ext $name + end + printf "%s\t\n" $name + end +end + +function __bat_complete_map_syntax + set -l token (commandline -ct) + + if string match -qr '(?.+):(?.*)' -- $token + # If token ends with a colon, complete with the list of language names. + set -f comps $glob:(__bat_complete_one_language $syntax) + else if string match -qr '\*' -- $token + # If token contains a globbing character (`*`), complete only possible + # globs in the current directory + set -f comps (__bat_complete_files $token | string match -er '[*]'): + else + # Complete files (and globs). + set -f comps (__bat_complete_files $token | string match -erv '/$'): + end + + if set -q comps[1] + printf "%s\t\n" $comps + end +end + +function __bat_cache_subcommand + __fish_seen_subcommand_from cache +end + +# Returns true if no exclusive arguments seen. +function __bat_no_excl_args + not __bat_cache_subcommand; and not __fish_seen_argument \ + -s h -l help \ + -s V -l version \ + -l acknowledgements \ + -l config-dir -l config-file \ + -l diagnostic \ + -l list-languages -l list-themes +end + +# Returns true if the 'cache' subcommand is seen without any exclusive arguments. +function __bat_cache_no_excl + __bat_cache_subcommand; and not __fish_seen_argument \ + -s h -l help \ + -l acknowledgements -l build -l clear +end + +function __bat_style_opts + set -l style_opts \ + "default,recommended components" \ + "auto,same as 'default' unless piped" \ + "full,all components" \ + "plain,no components" \ + "changes,Git change markers" \ + "header,alias for header-filename" \ + "header-filename,filename above content" \ + "header-filesize,filesize above content" \ + "grid,lines b/w sidebar/header/content" \ + "numbers,line numbers in sidebar" \ + "rule,separate files" \ + "snip,separate ranges" + + string replace , \t $style_opts +end + +# Use option argument descriptions to indicate which is the default, saving +# horizontal space and making sure the option description isn't truncated. +set -l color_opts ' + auto\tdefault + never\t + always\t +' +set -l decorations_opts $color_opts +set -l paging_opts $color_opts + +# Include some examples so we can indicate the default. +set -l pager_opts ' + less\tdefault + less\ -FR\t + more\t + vimpager\t +' + +set -l italic_text_opts ' + always\t + never\tdefault +' + +set -l wrap_opts ' + auto\tdefault + never\t + character\t +' + +# While --tabs theoretically takes any number, most people should be OK with these. +# Specifying a list lets us explain what 0 does. +set -l tabs_opts ' + 0\tpass\ tabs\ through\ directly + 1\t + 2\t + 4\t + 8\t +' + # Completions: -complete -c {{PROJECT_EXECUTABLE}} -l color -xka "auto never always" -d "Specify when to use colored output (default: auto)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l acknowledgements -d "Print acknowledgements" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -l config-dir -d "Display location of '{{PROJECT_EXECUTABLE}}' configuration directory" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l color -x -a "$color_opts" -d "When to use colored output" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l config-file -d "Display location of '{{PROJECT_EXECUTABLE}}' configuration file" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l config-dir -f -d "Display location of configuration directory" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -l decorations -xka "auto never always" -d "Specify when to use the decorations specified with '--style' (default: auto)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l config-file -f -d "Display location of configuration file" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -s h -l help -d "Print help message" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l decorations -x -a "$decorations_opts" -d "When to use --style decorations" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s H -l highlight-line -x -d " Highlight the N-th line with a different background color" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l diagnostic -d "Print diagnostic info for bug reports" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -l italic-text -xka "always never" -d "Specify when to use ANSI sequences for italic text (default: never)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s d -l diff -d "Only show lines with Git changes" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s l -l language -d "Set the language for syntax highlighting" -n "not __fish_seen_subcommand_from cache" -xa "(__{{PROJECT_EXECUTABLE}}_autocomplete_languages)" +complete -c $bat -l diff-context -x -d "Show N context lines around Git changes" -n "__fish_seen_argument -s d -l diff" -complete -c {{PROJECT_EXECUTABLE}} -s r -l line-range -x -d " Only print the specified range of lines for each file" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l file-name -x -d "Specify the display name" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l list-languages -d "Display list of supported languages for syntax highlighting" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s f -l force-colorization -d "Force color and decorations" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l list-themes -d "Display a list of supported themes for syntax highlighting" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s h -d "Print a concise overview" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -s m -l map-syntax -x -d " Map a file extension or file name to an existing syntax" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l help -f -d "Print all help information" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -s n -l number -d "Only show line numbers, no other decorations. Alias for '--style=numbers'" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s H -l highlight-line -x -d "Highlight line(s) N[:M]" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l pager -x -d " Specify which pager program to use (default: less)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l ignored-suffix -x -d "Ignore extension" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l paging -xka "auto never always" -d "Specify when to use the pager (default: auto)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l italic-text -x -a "$italic_text_opts" -d "When to use italic text in the output" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s p -l plain -d "Only show plain style, no decorations. Alias for '--style=plain'" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s l -l language -x -k -a "(__bat_complete_list_languages)" -d "Set the syntax highlighting language" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s P -d "Disable paging. Alias for '--paging=never'" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s r -l line-range -x -d "Only print lines [M]:[N] (either optional)" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s A -l show-all -d "Show non-printable characters like space/tab/newline" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l list-languages -f -d "List syntax highlighting languages" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -l style -xka "auto full plain changes header header-filename header-filesize grid rule numbers snip" -d "Comma-separated list of style elements or presets to display with file contents" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l list-themes -f -d "List syntax highlighting themes" -n __fish_is_first_arg -complete -c {{PROJECT_EXECUTABLE}} -l tabs -x -d " Set the tab width to T spaces (width of 0 passes tabs through directly)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s m -l map-syntax -x -a "(__bat_complete_map_syntax)" -d "Map :" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l terminal-width -x -d " Explicitly set terminal width; Prefix with '+' or '-' to offset (default width is auto determined)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s n -l number -d "Only show line numbers, no other decorations" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l theme -xka "({{PROJECT_EXECUTABLE}} --list-themes | cat)" -d "Set the theme for syntax highlighting" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l pager -x -a "$pager_opts" -d "Which pager to use" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s u -l unbuffered -d "POSIX-compliant unbuffered output. Option is ignored" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -l paging -x -a "$paging_opts" -d "When to use the pager" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -s V -l version -d "Show version information" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -s p -l plain -d "Disable decorations" -n __bat_no_excl_args -complete -c {{PROJECT_EXECUTABLE}} -l wrap -xka "auto never character" -d " Specify the text-wrapping mode (default: auto)" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -o pp -d "Disable decorations and paging" -n __bat_no_excl_args + +complete -c $bat -s P -d "Disable paging" -n __bat_no_excl_args + +complete -c $bat -s A -l show-all -d "Show non-printable characters" -n __bat_no_excl_args + +complete -c $bat -l style -x -k -a "(__fish_complete_list , __bat_style_opts)" -d "Specify which non-content elements to display" -n __bat_no_excl_args + +complete -c $bat -l tabs -x -a "$tabs_opts" -d "Set tab width" -n __bat_no_excl_args + +complete -c $bat -l terminal-width -x -d "Set terminal , +, or -" -n __bat_no_excl_args + +complete -c $bat -l theme -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args + +complete -c $bat -s V -l version -f -d "Show version information" -n __fish_is_first_arg + +complete -c $bat -l wrap -x -a "$wrap_opts" -d "Text-wrapping mode" -n __bat_no_excl_args # Sub-command 'cache' completions -complete -c {{PROJECT_EXECUTABLE}} -a "cache" -d "Modify the syntax/language definition cache" -n "not __fish_seen_subcommand_from cache" +complete -c $bat -a cache -d "Modify the syntax/language definition cache" -n __fish_use_subcommand -complete -c {{PROJECT_EXECUTABLE}} -l build -f -d "Parse syntaxes/language definitions into cache" -n "__fish_seen_subcommand_from cache" +complete -c $bat -l build -f -d "Parse new definitions into cache" -n __bat_cache_no_excl -complete -c {{PROJECT_EXECUTABLE}} -l clear -f -d "Reset syntaxes/language definitions to default settings" -n "__fish_seen_subcommand_from cache" +complete -c $bat -l clear -f -d "Reset definitions to defaults" -n __bat_cache_no_excl + +complete -c $bat -l blank -f -d "Create new data instead of appending" -n "__bat_cache_subcommand; and not __fish_seen_argument -l clear" + +complete -c $bat -l source -x -a "(__fish_complete_directories)" -d "Load syntaxes and themes from DIR" -n "__bat_cache_subcommand; and not __fish_seen_argument -l clear" + +complete -c $bat -l target -x -a "(__fish_complete_directories)" -d "Store cache in DIR" -n __bat_cache_subcommand + +complete -c $bat -l acknowledgements -d "Build acknowledgements.bin" -n __bat_cache_no_excl + +complete -c $bat -s h -d "Print a concise overview of $bat-cache help" -n __bat_cache_no_excl + +complete -c $bat -l help -f -d "Print all $bat-cache help" -n __bat_cache_no_excl + +# vim:ft=fish diff --git a/assets/completions/bat.zsh.in b/assets/completions/bat.zsh.in index fa9a0fce..d37022a2 100644 --- a/assets/completions/bat.zsh.in +++ b/assets/completions/bat.zsh.in @@ -75,7 +75,7 @@ _{{PROJECT_EXECUTABLE}}_main() { ;; style) - _values -s , 'style' auto full plain changes header header-filename header-filesize grid rule numbers snip + _values -s , 'style' default auto full plain changes header header-filename header-filesize grid rule numbers snip ;; esac } diff --git a/assets/manual/bat.1.in b/assets/manual/bat.1.in index c7890516..f4322010 100644 --- a/assets/manual/bat.1.in +++ b/assets/manual/bat.1.in @@ -29,7 +29,7 @@ control the width of the tab\-placeholders. .IP Only show plain style, no decorations. This is an alias for \&'\-\-style=plain'. When '\-p' is used twice ('\-pp'), it also disables -automatic paging (alias for '\-\-style=plain \fB\-\-pager\fR=\fI\,never\/\fR'). +automatic paging (alias for '\-\-style=plain \fB\-\-paging\fR=\fI\,never\/\fR'). .HP \fB\-l\fR, \fB\-\-language\fR .IP @@ -146,7 +146,7 @@ Configure which elements (line numbers, file headers, grid borders, Git modifica 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: *full*, auto, plain, changes, header, header-filename, header-filesize, grid, +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.bin b/assets/syntaxes.bin index c5cb3bbb..fb4e8a9a 100644 Binary files a/assets/syntaxes.bin and b/assets/syntaxes.bin differ diff --git a/assets/syntaxes/02_Extra/Fish b/assets/syntaxes/02_Extra/Fish index 2c254cc8..98316d43 160000 --- a/assets/syntaxes/02_Extra/Fish +++ b/assets/syntaxes/02_Extra/Fish @@ -1 +1 @@ -Subproject commit 2c254cc8512d53b7af306e4379fc9744ee5b4aee +Subproject commit 98316d4332936f74babb51cb56161410ae9d6e2c diff --git a/assets/syntaxes/02_Extra/Fstab.sublime-syntax b/assets/syntaxes/02_Extra/Fstab.sublime-syntax index b450f99d..de79aea3 100644 --- a/assets/syntaxes/02_Extra/Fstab.sublime-syntax +++ b/assets/syntaxes/02_Extra/Fstab.sublime-syntax @@ -95,7 +95,7 @@ contexts: fstab_dump: - include: comment - - match: '\s*[01]\s*' + - match: '\s*[012]\s*' comment: dump field scope: constant.numeric set: fstab_pass @@ -107,7 +107,7 @@ contexts: fstab_pass: - include: comment - - match: '\s*[01]\s*' + - match: '\s*[012]\s*' comment: pass field scope: constant.numeric set: expected_eol diff --git a/assets/syntaxes/02_Extra/INI.sublime-syntax b/assets/syntaxes/02_Extra/INI.sublime-syntax index 4d31bffd..2ea30cd5 100644 --- a/assets/syntaxes/02_Extra/INI.sublime-syntax +++ b/assets/syntaxes/02_Extra/INI.sublime-syntax @@ -5,8 +5,8 @@ name: INI file_extensions: - ini - INI - - inf - - INF + - "inf" + - "INF" - reg - REG - lng diff --git a/assets/syntaxes/02_Extra/Julia b/assets/syntaxes/02_Extra/Julia index 1e55f321..4fde0fde 160000 --- a/assets/syntaxes/02_Extra/Julia +++ b/assets/syntaxes/02_Extra/Julia @@ -1 +1 @@ -Subproject commit 1e55f3211b282ffd555d54b1798668bf5097df6a +Subproject commit 4fde0fdeddb3ca8486d3f490a2f051cba39a0a48 diff --git a/assets/syntaxes/02_Extra/LiveScript b/assets/syntaxes/02_Extra/LiveScript index 25750138..d82aeb73 160000 --- a/assets/syntaxes/02_Extra/LiveScript +++ b/assets/syntaxes/02_Extra/LiveScript @@ -1 +1 @@ -Subproject commit 25750138511925b74da9508050c766f360618055 +Subproject commit d82aeb737d4883d1a74aba7a07053f90211d427b diff --git a/assets/syntaxes/02_Extra/Nginx b/assets/syntaxes/02_Extra/Nginx index 15a1db15..65f5a63c 160000 --- a/assets/syntaxes/02_Extra/Nginx +++ b/assets/syntaxes/02_Extra/Nginx @@ -1 +1 @@ -Subproject commit 15a1db15106fb668b3b1396a725ab002a8ef286c +Subproject commit 65f5a63c0d1760c5db2264e50e3b14a7a4567cc0 diff --git a/assets/syntaxes/02_Extra/PowerShell b/assets/syntaxes/02_Extra/PowerShell index 742f0b5d..c0372a1d 160000 --- a/assets/syntaxes/02_Extra/PowerShell +++ b/assets/syntaxes/02_Extra/PowerShell @@ -1 +1 @@ -Subproject commit 742f0b5d4b60f5930c0b47fcc1f646860521296e +Subproject commit c0372a1d2df136ca6b3d1a9f7b85031dedf117f9 diff --git a/assets/syntaxes/02_Extra/Zig b/assets/syntaxes/02_Extra/Zig index 87ecbcae..1a4a3844 160000 --- a/assets/syntaxes/02_Extra/Zig +++ b/assets/syntaxes/02_Extra/Zig @@ -1 +1 @@ -Subproject commit 87ecbcae6fb5718369ce3bb3472ca0b2634e78e6 +Subproject commit 1a4a38445fec495817625bafbeb01e79c44abcba diff --git a/assets/syntaxes/02_Extra/cmd-help b/assets/syntaxes/02_Extra/cmd-help new file mode 160000 index 00000000..aa477471 --- /dev/null +++ b/assets/syntaxes/02_Extra/cmd-help @@ -0,0 +1 @@ +Subproject commit aa477471c870360bd1705a1a370d67b7c6fbe731 diff --git a/assets/syntaxes/02_Extra/log.sublime-syntax b/assets/syntaxes/02_Extra/log.sublime-syntax index 3c699d2e..a68c7e83 100644 --- a/assets/syntaxes/02_Extra/log.sublime-syntax +++ b/assets/syntaxes/02_Extra/log.sublime-syntax @@ -6,8 +6,16 @@ file_extensions: scope: text.log variables: ipv4_part: (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) + hours_minutes_seconds: (?:[01]\d|2[0-3]):(?:[0-5]\d):(?:[0-5]\d) + error: \b(?i:fail(?:ure|ed)?|error|exception|fatal|critical)\b + warning: \b(?i:warn(?:ing)?)\b + info: \b(?i:info)\b + debug: \b(?i:debug)\b contexts: main: + - include: log_level_lines + - include: main_without_log_level_line + main_without_log_level_line: - match: (\w+)(=) captures: 1: variable.parameter.log @@ -17,31 +25,85 @@ contexts: captures: 1: punctuation.definition.string.begin.log 3: punctuation.definition.string.end.log - - match: (")([^"]*)(") - scope: string.quoted.double.log + - match: \" captures: 1: punctuation.definition.string.begin.log - 3: punctuation.definition.string.end.log + push: double_quoted_string - include: dates - include: ip_addresses - include: numbers - - match: \b(?i:fail(?:ure|ed)?|error|exception)\b - scope: markup.error.log - - match: \b(?i:warn(?:ing)?)\b - scope: markup.warning.log + - include: log_levels #- include: scope:text.html.markdown#autolink-inet - match: \b\w+:/{2,3} scope: markup.underline.link.scheme.log push: url-host + log_level_lines: + - match: ^(?=.*{{error}}) + push: + - error_line + - main_pop_at_eol + - match: ^(?=.*{{warning}}) + push: + - warning_line + - main_pop_at_eol + - match: ^(?=.*{{info}}) + push: + - info_line + - main_pop_at_eol + - match: ^(?=.*{{debug}}) + push: + - debug_line + - main_pop_at_eol + log_levels: + - match: '{{error}}' + scope: markup.error.log + - match: '{{warning}}' + scope: markup.warning.log + - match: '{{info}}' + scope: markup.info.log + - match: '{{debug}}' + scope: markup.info.log + error_line: + - meta_scope: meta.annotation.error-line.log + - include: immediately_pop + warning_line: + - meta_scope: meta.annotation.warning-line.log + - include: immediately_pop + info_line: + - meta_scope: meta.annotation.info-line.log + - include: immediately_pop + debug_line: + - meta_scope: meta.annotation.debug-line.log + - include: immediately_pop + immediately_pop: + - match: '' + pop: true + pop_at_eol: + - match: $ + pop: true + main_pop_at_eol: + - include: main_without_log_level_line + - include: pop_at_eol dates: - - match: \b\d{4}-\d{2}-\d{2}\b + - match: \b\d{4}-\d{2}-\d{2}(?=\b|T) scope: meta.date.log meta.number.integer.decimal.log constant.numeric.value.log - - match: \b\d{4}/\d{2}/\d{2}\b + push: maybe_date_time_separator + - match: \b\d{4}/\d{2}/\d{2}(?=\b|T) scope: meta.date.log meta.number.integer.decimal.log constant.numeric.value.log - - match: \b(?:[01]\d|2[0-3]):(?:[0-5]\d):(?:[0-5]\d)(?:(\.)\d{3})?\b + push: maybe_date_time_separator + - match: \b(?={{hours_minutes_seconds}}) + push: time + time: + - match: (?:{{hours_minutes_seconds}})(?:(\.)\d{3})?\b scope: meta.time.log meta.number.integer.decimal.log constant.numeric.value.log captures: 1: punctuation.separator.decimal.log + - include: immediately_pop + maybe_date_time_separator: + - match: T(?={{hours_minutes_seconds}}) + scope: meta.date.log meta.time.log keyword.other.log + set: time + - include: immediately_pop ip_addresses: - match: \b(?=(?:{{ipv4_part}}\.){3}{{ipv4_part}}\b) push: @@ -50,8 +112,7 @@ contexts: scope: constant.numeric.value.log - match: \. scope: punctuation.separator.sequence.log - - match: '' - pop: true + - include: immediately_pop - match: (?=(?:\h{0,4}:){2,6}\h{1,4}\b) push: - meta_scope: meta.ipaddress.v6.log meta.number.integer.hexadecimal.log @@ -59,8 +120,7 @@ contexts: scope: constant.numeric.value.log - match: ':' scope: punctuation.separator.sequence.log - - match: '' - pop: true + - include: immediately_pop numbers: - match: \b(0x)(\h+)(?:(\.)(\h+))?\b scope: meta.number.float.hexadecimal.log @@ -112,5 +172,13 @@ contexts: pop: true - match: '[^?!.,:*_~\s<&()%]+|\S' scope: markup.underline.link.path.log - - match: '' + - include: immediately_pop + double_quoted_string: + - meta_scope: string.quoted.double.log + - match: \\" + scope: constant.character.escape.log + - match: \\n + scope: constant.character.escape.log + - match: \" + scope: punctuation.definition.string.end.log pop: true diff --git a/assets/themes.bin b/assets/themes.bin index 5d309127..1163d1d2 100644 Binary files a/assets/themes.bin and b/assets/themes.bin differ diff --git a/doc/README-ja.md b/doc/README-ja.md index f126c34c..7badf2b9 100644 --- a/doc/README-ja.md +++ b/doc/README-ja.md @@ -366,7 +366,7 @@ ansible-galaxy install aeimer.install_bat ### From source -`bat` をソースからビルドしたいならば、Rust 1.51 以上の環境が必要です。 +`bat` をソースからビルドしたいならば、Rust 1.60.0 以上の環境が必要です。 `cargo` を使用してビルドすることができます: ```bash diff --git a/doc/README-ko.md b/doc/README-ko.md index 956f9c1f..6fe1839f 100644 --- a/doc/README-ko.md +++ b/doc/README-ko.md @@ -416,7 +416,7 @@ scoop install bat ### 소스에서 -`bat`의 소스를 빌드하기 위해서는, Rust 1.51 이상이 필요합니다. +`bat`의 소스를 빌드하기 위해서는, Rust 1.60.0 이상이 필요합니다. `cargo`를 이용해 전부 빌드할 수 있습니다: ```bash diff --git a/doc/README-ru.md b/doc/README-ru.md index 89aba7fc..f3bc4092 100644 --- a/doc/README-ru.md +++ b/doc/README-ru.md @@ -344,7 +344,7 @@ ansible-galaxy install aeimer.install_bat ### Из исходников -Если вы желаете установить `bat` из исходников, вам понадобится Rust 1.51 или выше. После этого используйте `cargo`, чтобы все скомпилировать: +Если вы желаете установить `bat` из исходников, вам понадобится Rust 1.60.0 или выше. После этого используйте `cargo`, чтобы все скомпилировать: ```bash cargo install --locked bat diff --git a/doc/README-zh.md b/doc/README-zh.md index b41e2842..cbc901a1 100644 --- a/doc/README-zh.md +++ b/doc/README-zh.md @@ -372,7 +372,7 @@ scoop install bat ### 从源码编译 -如果你想要自己构建`bat`,那么你需要安装有高于1.51版本的 Rust。 +如果你想要自己构建`bat`,那么你需要安装有高于1.58.0版本的 Rust。 使用以下命令编译。 @@ -550,7 +550,7 @@ bat --generate-config-file # 在终端中以斜体输出文本(不是所有终端都支持) --italic-text=always -# 使用 C++ 语法来给 Ardiuno 的 .ino 文件提供高亮 +# 使用 C++ 语法来给 Arduino 的 .ino 文件提供高亮 --map-syntax "*.ino:C++" ``` diff --git a/doc/alternatives.md b/doc/alternatives.md index 665398fe..3dbb36f7 100644 --- a/doc/alternatives.md +++ b/doc/alternatives.md @@ -4,17 +4,17 @@ The following table tries to give an overview *from `bat`s perspective*, i.e. we categories which are relevant for `bat`. Some of these projects have completely different goals and if you are not looking for a program like `bat`, this comparison might not be for you. -| | bat | [pygments](http://pygments.org/) | [highlight](http://www.andre-simon.de/doku/highlight/highlight.php) | [ccat](https://github.com/jingweno/ccat) | [source-highlight](https://www.gnu.org/software/src-highlite/) | [hicat](https://github.com/rstacruz/hicat) | [coderay](https://github.com/rubychan/coderay) | [rouge](https://github.com/jneen/rouge) | -|----------------------------------------------|---------------------------------------------------------------------|----------------------------------|---------------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------| -| Drop-in `cat` replacement | :heavy_check_mark: [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: | -| Git integration | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | -| Automatic paging | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | -| Languages (circa) | 150 | 300 | 200 | 7 | 80 | 130 | 30 | 130 | -| Extensible (languages, themes) | :heavy_check_mark: | (:heavy_check_mark:) | (:heavy_check_mark:) | :x: | (:heavy_check_mark:) | :x: | :x: | :x: | -| Advanced highlighting (e.g. nested syntaxes) | :heavy_check_mark: | :heavy_check_mark: | (:heavy_check_mark:) ? | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | -| Execution time [ms] (`jquery-3.3.1.js`) | 624 | 789 | 400 | 80 | 300 | 316 | 157 | 695 | -| Execution time [ms] (`miniz.c`) | 66 | 656 | 26 | 8 | 53 | 141 | 75 | 254 | -| Execution time [ms] (370 kB XML file) | 238 | 487 | 129 | 111 | 110 | 339 | 147 | 359 | +| | bat | [pygments](http://pygments.org/) | [highlight](http://www.andre-simon.de/doku/highlight/highlight.php) | [ccat](https://github.com/jingweno/ccat) | [source-highlight](https://www.gnu.org/software/src-highlite/) | [hicat](https://github.com/rstacruz/hicat) | [coderay](https://github.com/rubychan/coderay) | [rouge](https://github.com/jneen/rouge) | [clp](https://github.com/jpe90/clp) | +|----------------------------------------------|---------------------------------------------------------------------|----------------------------------|---------------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------| +| Drop-in `cat` replacement | :heavy_check_mark: [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: | :x: | +| Git integration | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | :x: | +| Automatic paging | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: | :x: | +| Languages (circa) | 150 | 300 | 200 | 7 | 80 | 130 | 30 | 130 | 150 | +| Extensible (languages, themes) | :heavy_check_mark: | (:heavy_check_mark:) | (:heavy_check_mark:) | :x: | (:heavy_check_mark:) | :x: | :x: | :x: | :heavy_check_mark: | +| Advanced highlighting (e.g. nested syntaxes) | :heavy_check_mark: | :heavy_check_mark: | (:heavy_check_mark:) ? | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| Execution time [ms] (`jquery-3.3.1.js`) | 422 | 455 | 299 | 39 | 208 | 287 | 128 | 740 | 22 | +| Execution time [ms] (`miniz.c`) | 27 | 169 | 19 | 4 | 36 | 131 | 58 | 231 | 4 | +| Execution time [ms] (957 kB XML file) | 215 | 296 | 236 | 165 | 83 | 412 | 135 | 386 | 127 | If you think that some entries in this table are outdated or wrong, please open a ticket or pull request. @@ -49,6 +49,7 @@ cmd_source_highlight="source-highlight --failsafe --infer-lang -f esc -i '$SRC'" cmd_hicat="hicat '$SRC'" cmd_coderay="coderay '$SRC'" cmd_rouge="rougify '$SRC'" +cmd_clp="clp '$SRC'" hyperfine --warmup 3 \ "$cmd_bat" \ @@ -60,4 +61,5 @@ hyperfine --warmup 3 \ "$cmd_hicat" \ "$cmd_coderay" \ "$cmd_rouge" \ + "$cmd_clp" \ ``` diff --git a/doc/release-checklist.md b/doc/release-checklist.md index 26cd4839..aa871dd0 100644 --- a/doc/release-checklist.md +++ b/doc/release-checklist.md @@ -7,21 +7,22 @@ - [ ] Find the current min. supported Rust version by running `grep '^\s*MIN_SUPPORTED_RUST_VERSION' .github/workflows/CICD.yml`. - [ ] Update the version and the min. supported Rust version in `README.md` and - `doc/README-*.md`. Check with `git grep -i 'rust.*1\.'` and - `git grep -i '1\..*rust'`. + `doc/README-*.md`. Check with + `git grep -i -e 'rust.*1\.' -e '1\..*rust' | grep README | grep -v tests/`. - [ ] Update `CHANGELOG.md`. Introduce a section for the new release. ## Update syntaxes and themes (build assets) -- [ ] Install the latest master version (`cargo clean && cargo install -f --path .`) and make +- [ ] Install the latest master version (`cargo clean && cargo install --locked -f --path .`) and make sure that it is available on the `PATH` (`bat --version` should show the new version). - [ ] Run `assets/create.sh` and check in the binary asset files. ## Documentation -- [ ] Review the `-h` and `--help` texts -- [ ] Review the `man` page (`man $(fd -HIp target/release/build.*out/assets/manual/bat.1) | tee`) +- [ ] Review `-h`, `--help`, and the `man` page. All of these are shown in + the output of the CI job called *Documentation*, so look there. + The CI workflow corresponding to the tip of the master branch is a good place to look. ## Pre-release checks @@ -30,7 +31,7 @@ - [ ] Optional: manually test the new features and command-line options. To do this, install the latest `bat` version again (to include the new syntaxes and themes). -- [ ] Run `cargo publish --dry-run --allow-dirty` to make sure that it will +- [ ] Run `cargo publish --dry-run` to make sure that it will succeed later (after creating the GitHub release). ## Release diff --git a/src/assets.rs b/src/assets.rs index e25c233f..f87d3934 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -43,8 +43,9 @@ pub struct SyntaxReferenceInSet<'a> { pub syntax_set: &'a SyntaxSet, } -/// Compress for size of ~700 kB instead of ~4600 kB at the cost of ~30% longer deserialization time -pub(crate) const COMPRESS_SYNTAXES: bool = true; +/// Lazy-loaded syntaxes are already compressed, and we don't want to compress +/// already compressed data. +pub(crate) const COMPRESS_SYNTAXES: bool = false; /// We don't want to compress our [LazyThemeSet] since the lazy-loaded themes /// within it are already compressed, and compressing another time just makes @@ -68,10 +69,57 @@ impl HighlightingAssets { } } + /// The default theme. + /// + /// ### Windows and Linux + /// + /// Windows and most Linux distributions has a dark terminal theme by + /// default. On these platforms, this function always returns a theme that + /// looks good on a dark background. + /// + /// ### macOS + /// + /// On macOS the default terminal background is light, but it is common that + /// Dark Mode is active, which makes the terminal background dark. On this + /// platform, the default theme depends on + /// ```bash + /// defaults read -globalDomain AppleInterfaceStyle + /// ```` + /// To avoid the overhead of the check on macOS, simply specify a theme + /// explicitly via `--theme`, `BAT_THEME`, or `~/.config/bat`. + /// + /// See and + /// for more context. pub fn default_theme() -> &'static str { + #[cfg(not(target_os = "macos"))] + { + Self::default_dark_theme() + } + #[cfg(target_os = "macos")] + { + if macos_dark_mode_active() { + Self::default_dark_theme() + } else { + Self::default_light_theme() + } + } + } + + /** + * The default theme that looks good on a dark background. + */ + fn default_dark_theme() -> &'static str { "Monokai Extended" } + /** + * The default theme that looks good on a light background. + */ + #[cfg(target_os = "macos")] + fn default_light_theme() -> &'static str { + "Monokai Extended Light" + } + pub fn from_cache(cache_path: &Path) -> Result { Ok(HighlightingAssets::new( SerializedSyntaxSet::FromFile(cache_path.join("syntaxes.bin")), @@ -351,6 +399,16 @@ fn asset_from_cache( .map_err(|_| format!("Could not parse cached {}", description).into()) } +#[cfg(target_os = "macos")] +fn macos_dark_mode_active() -> bool { + let mut defaults_cmd = std::process::Command::new("defaults"); + defaults_cmd.args(&["read", "-globalDomain", "AppleInterfaceStyle"]); + match defaults_cmd.output() { + Ok(output) => output.stdout == b"Dark\n", + Err(_) => true, + } +} + #[cfg(test)] mod tests { use super::*; @@ -581,13 +639,22 @@ mod tests { } #[test] - fn syntax_detection_is_case_sensitive() { + fn syntax_detection_is_case_insensitive() { let mut test = SyntaxDetectionTest::new(); - assert_ne!(test.syntax_for_file("README.MD"), "Markdown"); + assert_eq!(test.syntax_for_file("README.md"), "Markdown"); + assert_eq!(test.syntax_for_file("README.mD"), "Markdown"); + assert_eq!(test.syntax_for_file("README.Md"), "Markdown"); + assert_eq!(test.syntax_for_file("README.MD"), "Markdown"); + + // Adding a mapping for "MD" in addition to "md" should not break the mapping test.syntax_mapping .insert("*.MD", MappingTarget::MapTo("Markdown")) .ok(); + + assert_eq!(test.syntax_for_file("README.md"), "Markdown"); + assert_eq!(test.syntax_for_file("README.mD"), "Markdown"); + assert_eq!(test.syntax_for_file("README.Md"), "Markdown"); assert_eq!(test.syntax_for_file("README.MD"), "Markdown"); } diff --git a/src/assets/assets_metadata.rs b/src/assets/assets_metadata.rs index 5dc2dd2c..700c4c3b 100644 --- a/src/assets/assets_metadata.rs +++ b/src/assets/assets_metadata.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::error::*; -#[derive(Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct AssetsMetadata { bat_version: Option, creation_time: Option, diff --git a/src/assets/build_assets/acknowledgements.rs b/src/assets/build_assets/acknowledgements.rs index f5d7a384..fc915548 100644 --- a/src/assets/build_assets/acknowledgements.rs +++ b/src/assets/build_assets/acknowledgements.rs @@ -1,3 +1,4 @@ +use std::fmt::Write; use std::fs::read_to_string; use std::path::{Path, PathBuf}; @@ -124,7 +125,7 @@ fn append_to_acknowledgements( relative_path: &str, license_text: &str, ) { - acknowledgements.push_str(&format!("## {}\n\n{}", relative_path, license_text)); + write!(acknowledgements, "## {}\n\n{}", relative_path, license_text).ok(); // Make sure the last char is a newline to not mess up formatting later if acknowledgements diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 0b17abce..7bab26c3 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str::FromStr; use atty::{self, Stream}; @@ -32,7 +32,7 @@ fn is_truecolor_terminal() -> bool { } pub struct App { - pub matches: ArgMatches<'static>, + pub matches: ArgMatches, interactive_output: bool, } @@ -49,7 +49,7 @@ impl App { }) } - fn matches(interactive_output: bool) -> Result> { + fn matches(interactive_output: bool) -> Result { let args = if wild::args_os().nth(1) == Some("cache".into()) || wild::args_os().any(|arg| arg == "--no-config") { @@ -79,13 +79,13 @@ impl App { pub fn config(&self, inputs: &[Input]) -> Result { let style_components = self.style_components()?; - let paging_mode = match self.matches.value_of("paging") { + let paging_mode = match self.matches.get_one::("paging").map(|s| s.as_str()) { Some("always") => PagingMode::Always, Some("never") => PagingMode::Never, Some("auto") | None => { // If we have -pp as an option when in auto mode, the pager should be disabled. - let extra_plain = self.matches.occurrences_of("plain") > 1; - if extra_plain || self.matches.is_present("no-paging") { + let extra_plain = self.matches.get_count("plain") > 1; + if extra_plain || self.matches.get_flag("no-paging") { PagingMode::Never } else if inputs.iter().any(Input::is_stdin) { // If we are reading from stdin, only enable paging if we write to an @@ -107,13 +107,13 @@ impl App { let mut syntax_mapping = SyntaxMapping::builtin(); - if let Some(values) = self.matches.values_of("ignored-suffix") { + if let Some(values) = self.matches.get_many::("ignored-suffix") { for suffix in values { syntax_mapping.insert_ignored_suffix(suffix); } } - if let Some(values) = self.matches.values_of("map-syntax") { + if let Some(values) = self.matches.get_many::("map-syntax") { for from_to in values { let parts: Vec<_> = from_to.split(':').collect(); @@ -125,36 +125,43 @@ impl App { } } - let maybe_term_width = self.matches.value_of("terminal-width").and_then(|w| { - if w.starts_with('+') || w.starts_with('-') { - // Treat argument as a delta to the current terminal width - w.parse().ok().map(|delta: i16| { - let old_width: u16 = Term::stdout().size().1; - let new_width: i32 = i32::from(old_width) + i32::from(delta); + let maybe_term_width = self + .matches + .get_one::("terminal-width") + .and_then(|w| { + if w.starts_with('+') || w.starts_with('-') { + // Treat argument as a delta to the current terminal width + w.parse().ok().map(|delta: i16| { + let old_width: u16 = Term::stdout().size().1; + let new_width: i32 = i32::from(old_width) + i32::from(delta); - if new_width <= 0 { - old_width as usize - } else { - new_width as usize - } - }) - } else { - w.parse().ok() - } - }); + if new_width <= 0 { + old_width as usize + } else { + new_width as usize + } + }) + } else { + w.parse().ok() + } + }); Ok(Config { true_color: is_truecolor_terminal(), - language: self.matches.value_of("language").or_else(|| { - if self.matches.is_present("show-all") { - Some("show-nonprintable") - } else { - None - } - }), - show_nonprintable: self.matches.is_present("show-all"), + language: self + .matches + .get_one::("language") + .map(|s| s.as_str()) + .or_else(|| { + if self.matches.get_flag("show-all") { + Some("show-nonprintable") + } else { + None + } + }), + show_nonprintable: self.matches.get_flag("show-all"), wrapping_mode: if self.interactive_output || maybe_term_width.is_some() { - match self.matches.value_of("wrap") { + match self.matches.get_one::("wrap").map(|s| s.as_str()) { Some("character") => WrappingMode::Character, Some("never") => WrappingMode::NoWrapping(true), Some("auto") | None => { @@ -171,8 +178,8 @@ impl App { // There's no point in wrapping when this is the case. WrappingMode::NoWrapping(false) }, - colored_output: self.matches.is_present("force-colorization") - || match self.matches.value_of("color") { + colored_output: self.matches.get_flag("force-colorization") + || match self.matches.get_one::("color").map(|s| s.as_str()) { Some("always") => true, Some("never") => false, Some("auto") => env::var_os("NO_COLOR").is_none() && self.interactive_output, @@ -181,12 +188,16 @@ impl App { paging_mode, term_width: maybe_term_width.unwrap_or(Term::stdout().size().1 as usize), loop_through: !(self.interactive_output - || self.matches.value_of("color") == Some("always") - || self.matches.value_of("decorations") == Some("always") - || self.matches.is_present("force-colorization")), + || self.matches.get_one::("color").map(|s| s.as_str()) == Some("always") + || self + .matches + .get_one::("decorations") + .map(|s| s.as_str()) + == Some("always") + || self.matches.get_flag("force-colorization")), tab_width: self .matches - .value_of("tabs") + .get_one::("tabs") .map(String::from) .or_else(|| env::var("BAT_TABS").ok()) .and_then(|t| t.parse().ok()) @@ -199,7 +210,7 @@ impl App { ), theme: self .matches - .value_of("theme") + .get_one::("theme") .map(String::from) .or_else(|| env::var("BAT_THEME").ok()) .map(|s| { @@ -210,19 +221,19 @@ impl App { } }) .unwrap_or_else(|| String::from(HighlightingAssets::default_theme())), - visible_lines: match self.matches.is_present("diff") { + visible_lines: match self.matches.contains_id("diff") && self.matches.get_flag("diff") { #[cfg(feature = "git")] true => VisibleLines::DiffContext( self.matches - .value_of("diff-context") + .get_one::("diff-context") .and_then(|t| t.parse().ok()) .unwrap_or(2), ), _ => VisibleLines::Ranges( self.matches - .values_of("line-range") - .map(|vs| vs.map(LineRange::from).collect()) + .get_many::("line-range") + .map(|vs| vs.map(|s| LineRange::from(s.as_str())).collect()) .transpose()? .map(LineRanges::from) .unwrap_or_default(), @@ -230,45 +241,47 @@ impl App { }, style_components, syntax_mapping, - pager: self.matches.value_of("pager"), - use_italic_text: self.matches.value_of("italic-text") == Some("always"), + pager: self.matches.get_one::("pager").map(|s| s.as_str()), + use_italic_text: self + .matches + .get_one::("italic-text") + .map(|s| s.as_str()) + == Some("always"), highlighted_lines: self .matches - .values_of("highlight-line") - .map(|ws| ws.map(LineRange::from).collect()) + .get_many::("highlight-line") + .map(|ws| ws.map(|s| LineRange::from(s.as_str())).collect()) .transpose()? .map(LineRanges::from) .map(HighlightedLineRanges) .unwrap_or_default(), - use_custom_assets: !self.matches.is_present("no-custom-assets"), + use_custom_assets: !self.matches.get_flag("no-custom-assets"), }) } pub fn inputs(&self) -> Result> { - // verify equal length of file-names and input FILEs - match self.matches.values_of("file-name") { - Some(ref filenames) - if self.matches.values_of_os("FILE").is_some() - && filenames.len() != self.matches.values_of_os("FILE").unwrap().len() => - { - return Err("Must be one file name per input type.".into()); - } - _ => {} - } let filenames: Option> = self .matches - .values_of_os("file-name") - .map(|values| values.map(Path::new).collect()); + .get_many::("file-name") + .map(|vs| vs.map(|p| p.as_path()).collect::>()); + + let files: Option> = self + .matches + .get_many::("FILE") + .map(|vs| vs.map(|p| p.as_path()).collect::>()); + + // verify equal length of file-names and input FILEs + if filenames.is_some() + && files.is_some() + && filenames.as_ref().map(|v| v.len()) != files.as_ref().map(|v| v.len()) + { + return Err("Must be one file name per input type.".into()); + } let mut filenames_or_none: Box>> = match filenames { Some(filenames) => Box::new(filenames.into_iter().map(Some)), None => Box::new(std::iter::repeat(None)), }; - let files: Option> = self - .matches - .values_of_os("FILE") - .map(|vs| vs.map(Path::new).collect()); - if files.is_none() { return Ok(vec![new_stdin_input( filenames_or_none.next().unwrap_or(None), @@ -294,12 +307,12 @@ impl App { fn style_components(&self) -> Result { let matches = &self.matches; - let mut styled_components = - StyleComponents(if matches.value_of("decorations") == Some("never") { + let mut styled_components = StyleComponents( + if matches.get_one::("decorations").map(|s| s.as_str()) == Some("never") { HashSet::new() - } else if matches.is_present("number") { + } else if matches.get_flag("number") { [StyleComponent::LineNumbers].iter().cloned().collect() - } else if matches.is_present("plain") { + } else if 0 < matches.get_count("plain") { [StyleComponent::Plain].iter().cloned().collect() } else { let env_style_components: Option> = env::var("BAT_STYLE") @@ -313,7 +326,7 @@ impl App { .transpose()?; matches - .value_of("style") + .get_one::("style") .map(|styles| { styles .split(',') @@ -322,14 +335,15 @@ impl App { .collect::>() }) .or(env_style_components) - .unwrap_or_else(|| vec![StyleComponent::Full]) + .unwrap_or_else(|| vec![StyleComponent::Default]) .into_iter() .map(|style| style.components(self.interactive_output)) .fold(HashSet::new(), |mut acc, components| { acc.extend(components.iter().cloned()); acc }) - }); + }, + ); // If `grid` is set, remove `rule` as it is a subset of `grid`, and print a warning. if styled_components.grid() && styled_components.0.remove(&StyleComponent::Rule) { diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index 0a1a81cf..f96e9e98 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -1,7 +1,10 @@ -use clap::{crate_name, crate_version, App as ClapApp, AppSettings, Arg, ArgGroup, SubCommand}; +use clap::{ + crate_name, crate_version, value_parser, AppSettings, Arg, ArgAction, ArgGroup, ColorChoice, + Command, +}; use once_cell::sync::Lazy; use std::env; -use std::path::Path; +use std::path::{Path, PathBuf}; static VERSION: Lazy = Lazy::new(|| { #[cfg(feature = "bugreport")] @@ -16,23 +19,21 @@ static VERSION: Lazy = Lazy::new(|| { } }); -pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { - let clap_color_setting = if interactive_output && env::var_os("NO_COLOR").is_none() { - AppSettings::ColoredHelp +pub fn build_app(interactive_output: bool) -> Command<'static> { + let color_when = if interactive_output && env::var_os("NO_COLOR").is_none() { + ColorChoice::Auto } else { - AppSettings::ColorNever + ColorChoice::Never }; - let mut app = ClapApp::new(crate_name!()) + let mut app = Command::new(crate_name!()) .version(VERSION.as_str()) - .global_setting(clap_color_setting) + .color(color_when) .global_setting(AppSettings::DeriveDisplayOrder) - .global_setting(AppSettings::UnifiedHelpMessage) - .global_setting(AppSettings::HidePossibleValuesInHelp) - .setting(AppSettings::ArgsNegateSubcommands) - .setting(AppSettings::AllowExternalSubcommands) - .setting(AppSettings::DisableHelpSubcommand) - .setting(AppSettings::VersionlessSubcommands) + .hide_possible_values(true) + .args_conflicts_with_subcommands(true) + .allow_external_subcommands(true) + .disable_help_subcommand(true) .max_term_width(100) .about( "A cat(1) clone with wings.\n\n\ @@ -44,20 +45,22 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ) .long_about("A cat(1) clone with syntax highlighting and Git integration.") .arg( - Arg::with_name("FILE") + Arg::new("FILE") .help("File(s) to print / concatenate. Use '-' for standard input.") .long_help( "File(s) to print / concatenate. Use a dash ('-') or no argument at all \ to read from standard input.", ) - .multiple(true) - .empty_values(false), + .takes_value(true) + .multiple_values(true) + .value_parser(value_parser!(PathBuf)), ) .arg( - Arg::with_name("show-all") + Arg::new("show-all") .long("show-all") .alias("show-nonprintable") - .short("A") + .short('A') + .action(ArgAction::SetTrue) .conflicts_with("language") .help("Show non-printable characters (space, tab, newline, ..).") .long_help( @@ -67,22 +70,22 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("plain") + Arg::new("plain") .overrides_with("plain") .overrides_with("number") - .short("p") + .short('p') .long("plain") - .multiple(true) + .action(ArgAction::Count) .help("Show plain style (alias for '--style=plain').") .long_help( "Only show plain style, no decorations. This is an alias for \ '--style=plain'. When '-p' is used twice ('-pp'), it also disables \ - automatic paging (alias for '--style=plain --pager=never').", + automatic paging (alias for '--style=plain --paging=never').", ), ) .arg( - Arg::with_name("language") - .short("l") + Arg::new("language") + .short('l') .long("language") .overrides_with("language") .help("Set the language for syntax highlighting.") @@ -95,12 +98,11 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { .takes_value(true), ) .arg( - Arg::with_name("highlight-line") + Arg::new("highlight-line") .long("highlight-line") - .short("H") + .short('H') .takes_value(true) - .number_of_values(1) - .multiple(true) + .action(ArgAction::Append) .value_name("N:M") .help("Highlight lines N through M.") .long_help( @@ -114,12 +116,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("file-name") + Arg::new("file-name") .long("file-name") .takes_value(true) - .number_of_values(1) - .multiple(true) + .action(ArgAction::Append) .value_name("name") + .value_parser(value_parser!(PathBuf)) .help("Specify the name to display for a file.") .long_help( "Specify the name to display for a file. Useful when piping \ @@ -133,9 +135,11 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { { app = app .arg( - Arg::with_name("diff") + Arg::new("diff") .long("diff") - .short("d") + .short('d') + .action(ArgAction::SetTrue) + .conflicts_with("line-range") .help("Only show lines that have been added/removed/modified.") .long_help( "Only show lines that have been added/removed/modified with respect \ @@ -143,20 +147,20 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("diff-context") + Arg::new("diff-context") .long("diff-context") .overrides_with("diff-context") .takes_value(true) .value_name("N") - .validator( - |n| { + .value_parser( + |n: &str| { n.parse::() .map_err(|_| "must be a number") - .map(|_| ()) // Convert to Result<(), &str> + .map(|_| n.to_owned()) // Convert to Result .map_err(|e| e.to_string()) }, // Convert to Result<(), String> ) - .hidden_short_help(true) + .hide_short_help(true) .long_help( "Include N lines of context around added/removed/modified lines when using '--diff'.", ), @@ -164,16 +168,16 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { } app = app.arg( - Arg::with_name("tabs") + Arg::new("tabs") .long("tabs") .overrides_with("tabs") .takes_value(true) .value_name("T") - .validator( - |t| { + .value_parser( + |t: &str| { t.parse::() .map_err(|_t| "must be a number") - .map(|_t| ()) // Convert to Result<(), &str> + .map(|_t| t.to_owned()) // Convert to Result .map_err(|e| e.to_string()) }, // Convert to Result<(), String> ) @@ -184,12 +188,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("wrap") + Arg::new("wrap") .long("wrap") .overrides_with("wrap") .takes_value(true) .value_name("mode") - .possible_values(&["auto", "never", "character"]) + .value_parser(["auto", "never", "character"]) .default_value("auto") .hide_default_value(true) .help("Specify the text-wrapping mode (*auto*, never, character).") @@ -198,21 +202,21 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { control the output width."), ) .arg( - Arg::with_name("terminal-width") + Arg::new("terminal-width") .long("terminal-width") .takes_value(true) .value_name("width") - .hidden_short_help(true) + .hide_short_help(true) .allow_hyphen_values(true) - .validator( - |t| { + .value_parser( + |t: &str| { let is_offset = t.starts_with('+') || t.starts_with('-'); t.parse::() .map_err(|_e| "must be an offset or number") .and_then(|v| if v == 0 && !is_offset { Err("terminal width cannot be zero") } else { - Ok(()) + Ok(t.to_owned()) }) .map_err(|e| e.to_string()) }) @@ -223,10 +227,11 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("number") + Arg::new("number") .long("number") .overrides_with("number") - .short("n") + .short('n') + .action(ArgAction::SetTrue) .help("Show line numbers (alias for '--style=numbers').") .long_help( "Only show line numbers, no other decorations. This is an alias for \ @@ -234,12 +239,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("color") + Arg::new("color") .long("color") .overrides_with("color") .takes_value(true) .value_name("when") - .possible_values(&["auto", "never", "always"]) + .value_parser(["auto", "never", "always"]) .hide_default_value(true) .default_value("auto") .help("When to use colors (*auto*, never, always).") @@ -251,23 +256,23 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("italic-text") + Arg::new("italic-text") .long("italic-text") .takes_value(true) .value_name("when") - .possible_values(&["always", "never"]) + .value_parser(["always", "never"]) .default_value("never") .hide_default_value(true) .help("Use italics in output (always, *never*)") .long_help("Specify when to use ANSI sequences for italic text in the output. Possible values: always, *never*."), ) .arg( - Arg::with_name("decorations") + Arg::new("decorations") .long("decorations") .overrides_with("decorations") .takes_value(true) .value_name("when") - .possible_values(&["auto", "never", "always"]) + .value_parser(["auto", "never", "always"]) .default_value("auto") .hide_default_value(true) .help("When to show the decorations (*auto*, never, always).") @@ -278,24 +283,26 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("force-colorization") + Arg::new("force-colorization") .long("force-colorization") - .short("f") + .short('f') + .action(ArgAction::SetTrue) .conflicts_with("color") .conflicts_with("decorations") .overrides_with("force-colorization") - .hidden_short_help(true) + .hide_short_help(true) .long_help("Alias for '--decorations=always --color=always'. This is useful \ if the output of bat is piped to another program, but you want \ to keep the colorization/decorations.") ) .arg( - Arg::with_name("paging") + Arg::new("paging") .long("paging") .overrides_with("paging") + .overrides_with("no-paging") .takes_value(true) .value_name("when") - .possible_values(&["auto", "never", "always"]) + .value_parser(["auto", "never", "always"]) .default_value("auto") .hide_default_value(true) .help("Specify when to use the pager, or use `-P` to disable (*auto*, never, always).") @@ -307,22 +314,23 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("no-paging") - .short("P") + Arg::new("no-paging") + .short('P') .long("no-paging") .alias("no-pager") + .action(ArgAction::SetTrue) .overrides_with("no-paging") - .hidden(true) - .hidden_short_help(true) + .hide(true) + .hide_short_help(true) .help("Alias for '--paging=never'") ) .arg( - Arg::with_name("pager") + Arg::new("pager") .long("pager") .overrides_with("pager") .takes_value(true) .value_name("command") - .hidden_short_help(true) + .hide_short_help(true) .help("Determine which pager to use.") .long_help( "Determine which pager is used. This option will override the \ @@ -332,12 +340,11 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("map-syntax") - .short("m") + Arg::new("map-syntax") + .short('m') .long("map-syntax") - .multiple(true) + .action(ArgAction::Append) .takes_value(true) - .number_of_values(1) .value_name("glob:syntax") .help("Use the specified syntax for files matching the glob pattern ('*.cpp:C++').") .long_help( @@ -350,19 +357,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { .takes_value(true), ) .arg( - Arg::with_name("ignored-suffix") - .number_of_values(1) - .multiple(true) + Arg::new("ignored-suffix") + .action(ArgAction::Append) .takes_value(true) .long("ignored-suffix") - .hidden_short_help(true) + .hide_short_help(true) .help( "Ignore extension. For example:\n \ 'bat --ignored-suffix \".dev\" my_file.json.dev' will use JSON syntax, and ignore '.dev'" ) ) .arg( - Arg::with_name("theme") + Arg::new("theme") .long("theme") .overrides_with("theme") .takes_value(true) @@ -376,28 +382,30 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("list-themes") + Arg::new("list-themes") .long("list-themes") + .action(ArgAction::SetTrue) .help("Display all supported highlighting themes.") .long_help("Display a list of supported themes for syntax highlighting."), ) .arg( - Arg::with_name("style") + Arg::new("style") .long("style") .value_name("components") // Need to turn this off for overrides_with to work as we want. See the bottom most // example at https://docs.rs/clap/2.32.0/clap/struct.Arg.html#method.overrides_with - .use_delimiter(false) + .use_value_delimiter(false) .takes_value(true) .overrides_with("style") .overrides_with("plain") .overrides_with("number") // Cannot use claps built in validation because we have to turn off clap's delimiters - .validator(|val| { + .value_parser(|val: &str| { let mut invalid_vals = val.split(',').filter(|style| { !&[ "auto", "full", + "default", "plain", "header", "header-filename", @@ -414,12 +422,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { if let Some(invalid) = invalid_vals.next() { Err(format!("Unknown style, '{}'", invalid)) } else { - Ok(()) + Ok(val.to_owned()) } }) .help( "Comma-separated list of style elements to display \ - (*auto*, full, plain, changes, header, grid, rule, numbers, snip).", + (*default*, auto, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip).", ) .long_help( "Configure which elements (line numbers, file headers, grid \ @@ -430,8 +438,9 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { '--style=\"..\"' option to the configuration file or export the \ BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\ Possible values:\n\n \ - * full: enables all available components (default).\n \ - * auto: same as 'full', unless the output is piped.\n \ + * default: enables recommended style components (default).\n \ + * full: enables all available components.\n \ + * auto: same as 'default', unless the output is piped.\n \ * plain: disables all available components.\n \ * changes: show Git modification markers.\n \ * header: alias for 'header-filename'.\n \ @@ -445,14 +454,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("line-range") + Arg::new("line-range") .long("line-range") - .short("r") - .multiple(true) + .short('r') + .action(ArgAction::Append) .takes_value(true) - .number_of_values(1) .value_name("N:M") - .conflicts_with("diff") .help("Only print the lines from N to M.") .long_help( "Only print the specified range of lines for each file. \ @@ -465,18 +472,19 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("list-languages") + Arg::new("list-languages") .long("list-languages") - .short("L") + .short('L') + .action(ArgAction::SetTrue) .conflicts_with("list-themes") .help("Display all supported languages.") .long_help("Display a list of supported languages for syntax highlighting."), ) .arg( - Arg::with_name("unbuffered") - .short("u") + Arg::new("unbuffered") + .short('u') .long("unbuffered") - .hidden_short_help(true) + .hide_short_help(true) .long_help( "This option exists for POSIX-compliance reasons ('u' is for \ 'unbuffered'). The output is always unbuffered - this option \ @@ -484,60 +492,66 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("no-config") + Arg::new("no-config") .long("no-config") - .hidden(true) + .hide(true) .help("Do not use the configuration file"), ) .arg( - Arg::with_name("no-custom-assets") + Arg::new("no-custom-assets") .long("no-custom-assets") - .hidden(true) + .action(ArgAction::SetTrue) + .hide(true) .help("Do not load custom assets"), ) .arg( - Arg::with_name("config-file") + Arg::new("config-file") .long("config-file") + .action(ArgAction::SetTrue) .conflicts_with("list-languages") .conflicts_with("list-themes") - .hidden(true) + .hide(true) .help("Show path to the configuration file."), ) .arg( - Arg::with_name("generate-config-file") + Arg::new("generate-config-file") .long("generate-config-file") + .action(ArgAction::SetTrue) .conflicts_with("list-languages") .conflicts_with("list-themes") - .hidden(true) + .hide(true) .help("Generates a default configuration file."), ) .arg( - Arg::with_name("config-dir") + Arg::new("config-dir") .long("config-dir") - .hidden(true) + .action(ArgAction::SetTrue) + .hide(true) .help("Show bat's configuration directory."), ) .arg( - Arg::with_name("cache-dir") + Arg::new("cache-dir") .long("cache-dir") - .hidden(true) + .action(ArgAction::SetTrue) + .hide(true) .help("Show bat's cache directory."), ) .arg( - Arg::with_name("diagnostic") + Arg::new("diagnostic") .long("diagnostic") .alias("diagnostics") - .hidden_short_help(true) + .action(ArgAction::SetTrue) + .hide_short_help(true) .help("Show diagnostic information for bug reports.") ) .arg( - Arg::with_name("acknowledgements") + Arg::new("acknowledgements") .long("acknowledgements") - .hidden_short_help(true) + .action(ArgAction::SetTrue) + .hide_short_help(true) .help("Show acknowledgements."), ) - .help_message("Print this help message.") - .version_message("Show version information."); + .mut_arg("help", |arg| arg.help("Print this help message.")); // Check if the current directory contains a file name cache. Otherwise, // enable the 'bat cache' subcommand. @@ -545,12 +559,13 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { app } else { app.subcommand( - SubCommand::with_name("cache") + Command::new("cache") .about("Modify the syntax-definition and theme cache") .arg( - Arg::with_name("build") + Arg::new("build") .long("build") - .short("b") + .short('b') + .action(ArgAction::SetTrue) .help("Initialize (or update) the syntax/theme cache.") .long_help( "Initialize (or update) the syntax/theme cache by loading from \ @@ -558,18 +573,19 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("clear") + Arg::new("clear") .long("clear") - .short("c") + .short('c') + .action(ArgAction::SetTrue) .help("Remove the cached syntax definitions and themes."), ) .group( - ArgGroup::with_name("cache-actions") + ArgGroup::new("cache-actions") .args(&["build", "clear"]) .required(true), ) .arg( - Arg::with_name("source") + Arg::new("source") .long("source") .requires("build") .takes_value(true) @@ -577,7 +593,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { .help("Use a different directory to load syntaxes and themes from."), ) .arg( - Arg::with_name("target") + Arg::new("target") .long("target") .requires("build") .takes_value(true) @@ -587,8 +603,9 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("blank") + Arg::new("blank") .long("blank") + .action(ArgAction::SetTrue) .requires("build") .help( "Create completely new syntax and theme sets \ @@ -596,11 +613,17 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> { ), ) .arg( - Arg::with_name("acknowledgements") + Arg::new("acknowledgements") .long("acknowledgements") + .action(ArgAction::SetTrue) .requires("build") .help("Build acknowledgements.bin."), ), ) } } + +#[test] +fn verify_app() { + build_app(false).debug_assert(); +} diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index 0369e436..b4e5d9b7 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -8,6 +8,7 @@ mod directories; mod input; use std::collections::{HashMap, HashSet}; +use std::fmt::Write as _; use std::io; use std::io::{BufReader, Write}; use std::path::Path; @@ -42,30 +43,30 @@ const THEME_PREVIEW_DATA: &[u8] = include_bytes!("../../../assets/theme_preview. #[cfg(feature = "build-assets")] fn build_assets(matches: &clap::ArgMatches) -> Result<()> { let source_dir = matches - .value_of("source") + .get_one::("source") .map(Path::new) .unwrap_or_else(|| PROJECT_DIRS.config_dir()); let target_dir = matches - .value_of("target") + .get_one::("target") .map(Path::new) .unwrap_or_else(|| PROJECT_DIRS.cache_dir()); bat::assets::build( source_dir, - !matches.is_present("blank"), - matches.is_present("acknowledgements"), + !matches.get_flag("blank"), + matches.get_flag("acknowledgements"), target_dir, clap::crate_version!(), ) } fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> { - if matches.is_present("build") { + if matches.get_flag("build") { #[cfg(feature = "build-assets")] build_assets(matches)?; #[cfg(not(feature = "build-assets"))] println!("bat has been built without the 'build-assets' feature. The 'cache --build' option is not available."); - } else if matches.is_present("clear") { + } else if matches.get_flag("clear") { clear_assets(); } @@ -128,7 +129,7 @@ pub fn get_languages(config: &Config) -> Result { if config.loop_through { for lang in languages { - result += &format!("{}:{}\n", lang.name, lang.file_extensions.join(",")); + writeln!(result, "{}:{}", lang.name, lang.file_extensions.join(",")).ok(); } } else { let longest = languages @@ -149,7 +150,7 @@ pub fn get_languages(config: &Config) -> Result { }; for lang in languages { - result += &format!("{:width$}{}", lang.name, separator, width = longest); + write!(result, "{:width$}{}", lang.name, separator, width = longest).ok(); // Number of characters on this line so far, wrap before `desired_width` let mut num_chars = 0; @@ -160,11 +161,11 @@ pub fn get_languages(config: &Config) -> Result { let new_chars = word.len() + comma_separator.len(); if num_chars + new_chars >= desired_width { num_chars = 0; - result += &format!("\n{:width$}{}", "", separator, width = longest); + write!(result, "\n{:width$}{}", "", separator, width = longest).ok(); } num_chars += new_chars; - result += &format!("{}", style.paint(&word[..])); + write!(result, "{}", style.paint(&word[..])).ok(); if extension.peek().is_some() { result += comma_separator; } @@ -230,8 +231,10 @@ fn run_controller(inputs: Vec, config: &Config) -> Result { #[cfg(feature = "bugreport")] fn invoke_bugreport(app: &App) { use bugreport::{bugreport, collector::*, format::Markdown}; - let pager = bat::config::get_pager_executable(app.matches.value_of("pager")) - .unwrap_or_else(|| "less".to_owned()); // FIXME: Avoid non-canonical path to "less". + let pager = bat::config::get_pager_executable( + app.matches.get_one::("pager").map(|s| s.as_str()), + ) + .unwrap_or_else(|| "less".to_owned()); // FIXME: Avoid non-canonical path to "less". let mut custom_assets_metadata = PROJECT_DIRS.cache_dir().to_path_buf(); custom_assets_metadata.push("metadata.yaml"); @@ -265,6 +268,10 @@ fn invoke_bugreport(app: &App) { "Custom assets metadata", custom_assets_metadata, )) + .info(DirectoryEntries::new( + "Custom assets", + PROJECT_DIRS.cache_dir(), + )) .info(CompileTimeInformation::default()); #[cfg(feature = "paging")] @@ -284,7 +291,7 @@ fn invoke_bugreport(app: &App) { fn run() -> Result { let app = App::new()?; - if app.matches.is_present("diagnostic") { + if app.matches.get_flag("diagnostic") { #[cfg(feature = "bugreport")] invoke_bugreport(&app); #[cfg(not(feature = "bugreport"))] @@ -293,11 +300,11 @@ fn run() -> Result { } match app.matches.subcommand() { - ("cache", Some(cache_matches)) => { + Some(("cache", cache_matches)) => { // If there is a file named 'cache' in the current working directory, // arguments for subcommand 'cache' are not mandatory. // If there are non-zero arguments, execute the subcommand cache, else, open the file cache. - if !cache_matches.args.is_empty() { + if cache_matches.args_present() { run_cache_subcommand(cache_matches)?; Ok(true) } else { @@ -311,7 +318,7 @@ fn run() -> Result { let inputs = app.inputs()?; let config = app.config(&inputs)?; - if app.matches.is_present("list-languages") { + if app.matches.get_flag("list-languages") { let languages: String = get_languages(&config)?; let inputs: Vec = vec![Input::from_reader(Box::new(languages.as_bytes()))]; let plain_config = Config { @@ -320,22 +327,22 @@ fn run() -> Result { ..Default::default() }; run_controller(inputs, &plain_config) - } else if app.matches.is_present("list-themes") { + } else if app.matches.get_flag("list-themes") { list_themes(&config)?; Ok(true) - } else if app.matches.is_present("config-file") { + } else if app.matches.get_flag("config-file") { println!("{}", config_file().to_string_lossy()); Ok(true) - } else if app.matches.is_present("generate-config-file") { + } else if app.matches.get_flag("generate-config-file") { generate_config_file()?; Ok(true) - } else if app.matches.is_present("config-dir") { + } else if app.matches.get_flag("config-dir") { writeln!(io::stdout(), "{}", config_dir())?; Ok(true) - } else if app.matches.is_present("cache-dir") { + } else if app.matches.get_flag("cache-dir") { writeln!(io::stdout(), "{}", cache_dir())?; Ok(true) - } else if app.matches.is_present("acknowledgements") { + } else if app.matches.get_flag("acknowledgements") { writeln!(io::stdout(), "{}", bat::assets::get_acknowledgements())?; Ok(true) } else { diff --git a/src/error.rs b/src/error.rs index 54f460e7..f9845138 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,11 +2,14 @@ use std::io::Write; use thiserror::Error; #[derive(Error, Debug)] +#[non_exhaustive] pub enum Error { #[error(transparent)] Io(#[from] ::std::io::Error), #[error(transparent)] - SyntectError(#[from] ::syntect::LoadingError), + SyntectError(#[from] ::syntect::Error), + #[error(transparent)] + SyntectLoadingError(#[from] ::syntect::LoadingError), #[error(transparent)] ParseIntError(#[from] ::std::num::ParseIntError), #[error(transparent)] diff --git a/src/input.rs b/src/input.rs index 3c295822..23e21506 100644 --- a/src/input.rs +++ b/src/input.rs @@ -118,7 +118,7 @@ impl OpenedInput<'_> { self.metadata .user_provided_name .as_ref() - .or_else(|| match self.kind { + .or(match self.kind { OpenedInputKind::OrdinaryFile(ref path) => Some(path), _ => None, }) diff --git a/src/less.rs b/src/less.rs index 9de6d508..df6a513d 100644 --- a/src/less.rs +++ b/src/less.rs @@ -3,21 +3,38 @@ use std::ffi::OsStr; use std::process::Command; -pub fn retrieve_less_version(less_path: &dyn AsRef) -> Option { +#[derive(Debug, PartialEq, Eq)] +pub enum LessVersion { + Less(usize), + BusyBox, +} + +pub fn retrieve_less_version(less_path: &dyn AsRef) -> Option { let resolved_path = grep_cli::resolve_binary(less_path.as_ref()).ok()?; let cmd = Command::new(resolved_path).arg("--version").output().ok()?; - parse_less_version(&cmd.stdout) + if cmd.status.success() { + parse_less_version(&cmd.stdout) + } else { + parse_less_version_busybox(&cmd.stderr) + } } -fn parse_less_version(output: &[u8]) -> Option { +fn parse_less_version(output: &[u8]) -> Option { if !output.starts_with(b"less ") { return None; } let version = std::str::from_utf8(&output[5..]).ok()?; let end = version.find(|c: char| !c.is_ascii_digit())?; - version[..end].parse::().ok() + Some(LessVersion::Less(version[..end].parse::().ok()?)) +} + +fn parse_less_version_busybox(output: &[u8]) -> Option { + match std::str::from_utf8(output) { + Ok(version) if version.contains("BusyBox ") => Some(LessVersion::BusyBox), + _ => None, + } } #[test] @@ -30,7 +47,7 @@ For information about the terms of redistribution, see the file named README in the less distribution. Homepage: http://www.greenwoodsoftware.com/less"; - assert_eq!(Some(487), parse_less_version(output)); + assert_eq!(Some(LessVersion::Less(487)), parse_less_version(output)); } #[test] @@ -43,7 +60,7 @@ For information about the terms of redistribution, see the file named README in the less distribution. Homepage: http://www.greenwoodsoftware.com/less"; - assert_eq!(Some(529), parse_less_version(output)); + assert_eq!(Some(LessVersion::Less(529)), parse_less_version(output)); } #[test] @@ -56,7 +73,7 @@ For information about the terms of redistribution, see the file named README in the less distribution. Home page: http://www.greenwoodsoftware.com/less"; - assert_eq!(Some(551), parse_less_version(output)); + assert_eq!(Some(LessVersion::Less(551)), parse_less_version(output)); } #[test] @@ -69,7 +86,7 @@ For information about the terms of redistribution, see the file named README in the less distribution. Home page: https://greenwoodsoftware.com/less"; - assert_eq!(Some(581), parse_less_version(output)); + assert_eq!(Some(LessVersion::Less(581)), parse_less_version(output)); } #[test] @@ -77,4 +94,38 @@ fn test_parse_less_version_wrong_program() { let output = b"more from util-linux 2.34"; assert_eq!(None, parse_less_version(output)); + assert_eq!(None, parse_less_version_busybox(output)); +} + +#[test] +fn test_parse_less_version_busybox() { + let output = b"pkg/less: unrecognized option '--version' +BusyBox v1.35.0 (2022-04-21 10:38:11 EDT) multi-call binary. + +Usage: less [-EFIMmNSRh~] [FILE]... + +View FILE (or stdin) one screenful at a time + + -E Quit once the end of a file is reached + -F Quit if entire file fits on first screen + -I Ignore case in all searches + -M,-m Display status line with line numbers + and percentage through the file + -N Prefix line number to each line + -S Truncate long lines + -R Remove color escape codes in input + -~ Suppress ~s displayed past EOF"; + + assert_eq!( + Some(LessVersion::BusyBox), + parse_less_version_busybox(output) + ); +} + +#[test] +fn test_parse_less_version_invalid_utf_8() { + let output = b"\xff"; + + assert_eq!(None, parse_less_version(output)); + assert_eq!(None, parse_less_version_busybox(output)); } diff --git a/src/lib.rs b/src/lib.rs index 37b1cd83..4e3fcb95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,10 +46,9 @@ pub(crate) mod printer; pub mod style; pub(crate) mod syntax_mapping; mod terminal; -mod vscreen; pub(crate) mod wrapping; -pub use pretty_printer::{Input, PrettyPrinter}; +pub use pretty_printer::{Input, PrettyPrinter, Syntax}; pub use syntax_mapping::{MappingTarget, SyntaxMapping}; pub use wrapping::WrappingMode; diff --git a/src/line_range.rs b/src/line_range.rs index ccd998f6..c9094fc9 100644 --- a/src/line_range.rs +++ b/src/line_range.rs @@ -168,7 +168,7 @@ fn test_parse_minus_fail() { assert!(range.is_err()); } -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RangeCheckResult { // Within one of the given ranges InRange, diff --git a/src/output.rs b/src/output.rs index 5ab3680b..55ec1cef 100644 --- a/src/output.rs +++ b/src/output.rs @@ -4,7 +4,7 @@ use std::process::Child; use crate::error::*; #[cfg(feature = "paging")] -use crate::less::retrieve_less_version; +use crate::less::{retrieve_less_version, LessVersion}; #[cfg(feature = "paging")] use crate::paging::PagingMode; #[cfg(feature = "paging")] @@ -83,13 +83,13 @@ impl OutputType { let replace_arguments_to_less = pager.source == PagerSource::EnvVarPager; if args.is_empty() || replace_arguments_to_less { - p.arg("--RAW-CONTROL-CHARS"); + p.arg("-R"); // Short version of --RAW-CONTROL-CHARS for maximum compatibility if single_screen_action == SingleScreenAction::Quit { - p.arg("--quit-if-one-screen"); + p.arg("-F"); // Short version of --quit-if-one-screen for compatibility } if wrapping_mode == WrappingMode::NoWrapping(true) { - p.arg("--chop-long-lines"); + p.arg("-S"); // Short version of --chop-long-lines for compatibility } // Passing '--no-init' fixes a bug with '--quit-if-one-screen' in older @@ -103,7 +103,9 @@ impl OutputType { None => { p.arg("--no-init"); } - Some(version) if (version < 530 || (cfg!(windows) && version < 558)) => { + Some(LessVersion::Less(version)) + if (version < 530 || (cfg!(windows) && version < 558)) => + { p.arg("--no-init"); } _ => {} diff --git a/src/pager.rs b/src/pager.rs index 3473aa67..5b707779 100644 --- a/src/pager.rs +++ b/src/pager.rs @@ -40,15 +40,23 @@ impl PagerKind { fn from_bin(bin: &str) -> PagerKind { use std::path::Path; - match Path::new(bin) - .file_stem() - .map(|s| s.to_string_lossy()) - .as_deref() - { - Some("bat") => PagerKind::Bat, + // Set to `less` by default on most Linux distros. + let pager_bin = Path::new(bin).file_stem(); + + // The name of the current running binary. Normally `bat` but sometimes + // `batcat` for compatibility reasons. + let current_bin = env::args_os().next(); + + // Check if the current running binary is set to be our pager. + let is_current_bin_pager = current_bin + .map(|s| Path::new(&s).file_stem() == pager_bin) + .unwrap_or(false); + + match pager_bin.map(|s| s.to_string_lossy()).as_deref() { Some("less") => PagerKind::Less, Some("more") => PagerKind::More, Some("most") => PagerKind::Most, + _ if is_current_bin_pager => PagerKind::Bat, _ => PagerKind::Unknown, } } diff --git a/src/paging.rs b/src/paging.rs index 77a183c2..e1db6a13 100644 --- a/src/paging.rs +++ b/src/paging.rs @@ -1,4 +1,4 @@ -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PagingMode { Always, QuitIfOneScreen, diff --git a/src/preprocessor.rs b/src/preprocessor.rs index 2380e7f9..6e775595 100644 --- a/src/preprocessor.rs +++ b/src/preprocessor.rs @@ -1,35 +1,28 @@ -use console::AnsiCodeIterator; +use std::fmt::Write; /// Expand tabs like an ANSI-enabled expand(1). -pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String { - let mut buffer = String::with_capacity(line.len() * 2); +pub fn expand_tabs(mut text: &str, width: usize, cursor: &mut usize) -> String { + let mut buffer = String::with_capacity(text.len() * 2); - for chunk in AnsiCodeIterator::new(line) { - match chunk { - (text, true) => buffer.push_str(text), - (mut text, false) => { - while let Some(index) = text.find('\t') { - // Add previous text. - if index > 0 { - *cursor += index; - buffer.push_str(&text[0..index]); - } - - // Add tab. - let spaces = width - (*cursor % width); - *cursor += spaces; - buffer.push_str(&*" ".repeat(spaces)); - - // Next. - text = &text[index + 1..text.len()]; - } - - *cursor += text.len(); - buffer.push_str(text); - } + while let Some(index) = text.find('\t') { + // Add previous text. + if index > 0 { + *cursor += index; + buffer.push_str(&text[0..index]); } + + // Add tab. + let spaces = width - (*cursor % width); + *cursor += spaces; + buffer.push_str(&*" ".repeat(spaces)); + + // Next. + text = &text[index + 1..text.len()]; } + *cursor += text.len(); + buffer.push_str(text); + buffer } @@ -101,7 +94,7 @@ pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { c => output.push_str(&c.escape_unicode().collect::()), } } else { - output.push_str(&format!("\\x{:02X}", input[idx])); + write!(output, "\\x{:02X}", input[idx]).ok(); idx += 1; } } diff --git a/src/pretty_printer.rs b/src/pretty_printer.rs index d537b890..28644513 100644 --- a/src/pretty_printer.rs +++ b/src/pretty_printer.rs @@ -2,7 +2,6 @@ use std::io::Read; use std::path::Path; use console::Term; -use syntect::parsing::SyntaxReference; use crate::{ assets::HighlightingAssets, @@ -28,6 +27,12 @@ struct ActiveStyleComponents { snip: bool, } +#[non_exhaustive] +pub struct Syntax { + pub name: String, + pub file_extensions: Vec, +} + pub struct PrettyPrinter<'a> { inputs: Vec>, config: Config<'a>, @@ -164,6 +169,12 @@ impl<'a> PrettyPrinter<'a> { self } + /// Whether to print binary content or nonprintable characters (default: no) + pub fn show_nonprintable(&mut self, yes: bool) -> &mut Self { + self.config.show_nonprintable = yes; + self + } + /// Whether to show "snip" markers between visible line ranges (default: no) pub fn snip(&mut self, yes: bool) -> &mut Self { self.active_style_components.snip = yes; @@ -234,10 +245,18 @@ impl<'a> PrettyPrinter<'a> { self.assets.themes() } - pub fn syntaxes(&self) -> impl Iterator { + pub fn syntaxes(&self) -> impl Iterator + '_ { // We always use assets from the binary, which are guaranteed to always // be valid, so get_syntaxes() can never fail here - self.assets.get_syntaxes().unwrap().iter() + self.assets + .get_syntaxes() + .unwrap() + .iter() + .filter(|s| !s.hidden) + .map(|s| Syntax { + name: s.name.clone(), + file_extensions: s.file_extensions.clone(), + }) } /// Pretty-print all specified inputs. This method will "use" all stored inputs. diff --git a/src/printer.rs b/src/printer.rs index ad041e03..3f7f1e09 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -6,8 +6,6 @@ use ansi_term::Style; use bytesize::ByteSize; -use console::AnsiCodeIterator; - use syntect::easy::HighlightLines; use syntect::highlighting::Color; use syntect::highlighting::Theme; @@ -33,7 +31,6 @@ use crate::line_range::RangeCheckResult; use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::style::StyleComponent; use crate::terminal::{as_terminal_escaped, to_ansi_color}; -use crate::vscreen::AnsiStyle; use crate::wrapping::WrappingMode; pub(crate) trait Printer { @@ -122,7 +119,6 @@ pub(crate) struct InteractivePrinter<'a> { config: &'a Config<'a>, decorations: Vec>, panel_width: usize, - ansi_style: AnsiStyle, content_type: Option, #[cfg(feature = "git")] pub line_changes: &'a Option, @@ -206,7 +202,6 @@ impl<'a> InteractivePrinter<'a> { config, decorations, content_type: input.reader.content_type, - ansi_style: AnsiStyle::new(), #[cfg(feature = "git")] line_changes, highlighter_from_set, @@ -445,9 +440,21 @@ impl<'a> Printer for InteractivePrinter<'a> { return Ok(()); } }; - highlighter_from_set + + // skip syntax highlighting on long lines + let too_long = line.len() > 1024 * 16; + + let for_highlighting: &str = if too_long { "\n" } else { &line }; + + let mut highlighted_line = highlighter_from_set .highlighter - .highlight(&line, highlighter_from_set.syntax_set) + .highlight_line(for_highlighting, highlighter_from_set.syntax_set)?; + + if too_long { + highlighted_line[0].1 = &line; + } + + highlighted_line }; if out_of_range { @@ -464,7 +471,7 @@ impl<'a> Printer for InteractivePrinter<'a> { self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange; if highlight_this_line && self.config.theme == "ansi" { - self.ansi_style.update("^[4m"); + write!(handle, "\x1B[4m")?; } let background_color = self @@ -491,51 +498,37 @@ impl<'a> Printer for InteractivePrinter<'a> { let italics = self.config.use_italic_text; for &(style, region) in ®ions { - let ansi_iterator = AnsiCodeIterator::new(region); - for chunk in ansi_iterator { - match chunk { - // ANSI escape passthrough. - (ansi, true) => { - self.ansi_style.update(ansi); - write!(handle, "{}", ansi)?; - } + let text = &*self.preprocess(region, &mut cursor_total); + let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); - // Regular text. - (text, false) => { - let text = &*self.preprocess(text, &mut cursor_total); - let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); + write!( + handle, + "{}", + as_terminal_escaped( + style, + text_trimmed, + true_color, + colored_output, + italics, + background_color + ) + )?; - write!( - handle, - "{}", - as_terminal_escaped( - style, - &format!("{}{}", self.ansi_style, text_trimmed), - true_color, - colored_output, - italics, - background_color - ) - )?; + if text.len() != text_trimmed.len() { + if let Some(background_color) = background_color { + let ansi_style = Style { + background: to_ansi_color(background_color, true_color), + ..Default::default() + }; - if text.len() != text_trimmed.len() { - if let Some(background_color) = background_color { - let ansi_style = Style { - background: to_ansi_color(background_color, true_color), - ..Default::default() - }; - - let width = if cursor_total <= cursor_max { - cursor_max - cursor_total + 1 - } else { - 0 - }; - write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?; - } - write!(handle, "{}", &text[text_trimmed.len()..])?; - } - } + let width = if cursor_total <= cursor_max { + cursor_max - cursor_total + 1 + } else { + 0 + }; + write!(handle, "{}", ansi_style.paint(" ".repeat(width)))?; } + write!(handle, "{}", &text[text_trimmed.len()..])?; } } @@ -544,98 +537,82 @@ impl<'a> Printer for InteractivePrinter<'a> { } } else { for &(style, region) in ®ions { - let ansi_iterator = AnsiCodeIterator::new(region); - for chunk in ansi_iterator { - match chunk { - // ANSI escape passthrough. - (ansi, true) => { - self.ansi_style.update(ansi); - write!(handle, "{}", ansi)?; - } + let text = self.preprocess( + region.trim_end_matches(|c| c == '\r' || c == '\n'), + &mut cursor_total, + ); - // Regular text. - (text, false) => { - let text = self.preprocess( - text.trim_end_matches(|c| c == '\r' || c == '\n'), - &mut cursor_total, - ); + let mut max_width = cursor_max - cursor; - let mut max_width = cursor_max - cursor; + // line buffer (avoid calling write! for every character) + let mut line_buf = String::with_capacity(max_width * 4); - // line buffer (avoid calling write! for every character) - let mut line_buf = String::with_capacity(max_width * 4); + // Displayed width of line_buf + let mut current_width = 0; - // Displayed width of line_buf - let mut current_width = 0; + for c in text.chars() { + // calculate the displayed width for next character + let cw = c.width().unwrap_or(0); + current_width += cw; - for c in text.chars() { - // calculate the displayed width for next character - let cw = c.width().unwrap_or(0); - current_width += cw; - - // if next character cannot be printed on this line, - // flush the buffer. - if current_width > max_width { - // Generate wrap padding if not already generated. - if panel_wrap.is_none() { - panel_wrap = if self.panel_width > 0 { - Some(format!( - "{} ", - self.decorations - .iter() - .map(|d| d - .generate(line_number, true, self) - .text) - .collect::>() - .join(" ") - )) - } else { - Some("".to_string()) - } - } - - // It wraps. - write!( - handle, - "{}\n{}", - as_terminal_escaped( - style, - &*format!("{}{}", self.ansi_style, line_buf), - self.config.true_color, - self.config.colored_output, - self.config.use_italic_text, - background_color - ), - panel_wrap.clone().unwrap() - )?; - - cursor = 0; - max_width = cursor_max; - - line_buf.clear(); - current_width = cw; - } - - line_buf.push(c); + // if next character cannot be printed on this line, + // flush the buffer. + if current_width > max_width { + // Generate wrap padding if not already generated. + if panel_wrap.is_none() { + panel_wrap = if self.panel_width > 0 { + Some(format!( + "{} ", + self.decorations + .iter() + .map(|d| d.generate(line_number, true, self).text) + .collect::>() + .join(" ") + )) + } else { + Some("".to_string()) } - - // flush the buffer - cursor += current_width; - write!( - handle, - "{}", - as_terminal_escaped( - style, - &*format!("{}{}", self.ansi_style, line_buf), - self.config.true_color, - self.config.colored_output, - self.config.use_italic_text, - background_color - ) - )?; } + + // It wraps. + write!( + handle, + "{}\n{}", + as_terminal_escaped( + style, + &line_buf, + self.config.true_color, + self.config.colored_output, + self.config.use_italic_text, + background_color + ), + panel_wrap.clone().unwrap() + )?; + + cursor = 0; + max_width = cursor_max; + + line_buf.clear(); + current_width = cw; } + + line_buf.push(c); } + + // flush the buffer + cursor += current_width; + write!( + handle, + "{}", + as_terminal_escaped( + style, + &line_buf, + self.config.true_color, + self.config.colored_output, + self.config.use_italic_text, + background_color + ) + )?; } if let Some(background_color) = background_color { @@ -654,7 +631,6 @@ impl<'a> Printer for InteractivePrinter<'a> { } if highlight_this_line && self.config.theme == "ansi" { - self.ansi_style.update("^[24m"); write!(handle, "\x1B[24m")?; } diff --git a/src/style.rs b/src/style.rs index 1bdcaba1..27ee33e8 100644 --- a/src/style.rs +++ b/src/style.rs @@ -17,6 +17,7 @@ pub enum StyleComponent { LineNumbers, Snip, Full, + Default, Plain, } @@ -25,7 +26,7 @@ impl StyleComponent { match self { StyleComponent::Auto => { if interactive_terminal { - StyleComponent::Full.components(interactive_terminal) + StyleComponent::Default.components(interactive_terminal) } else { StyleComponent::Plain.components(interactive_terminal) } @@ -48,6 +49,14 @@ impl StyleComponent { StyleComponent::LineNumbers, StyleComponent::Snip, ], + StyleComponent::Default => &[ + #[cfg(feature = "git")] + StyleComponent::Changes, + StyleComponent::Grid, + StyleComponent::HeaderFilename, + StyleComponent::LineNumbers, + StyleComponent::Snip, + ], StyleComponent::Plain => &[], } } @@ -69,6 +78,7 @@ impl FromStr for StyleComponent { "numbers" => Ok(StyleComponent::LineNumbers), "snip" => Ok(StyleComponent::Snip), "full" => Ok(StyleComponent::Full), + "default" => Ok(StyleComponent::Default), "plain" => Ok(StyleComponent::Plain), _ => Err(format!("Unknown style '{}'", s).into()), } diff --git a/src/syntax_mapping.rs b/src/syntax_mapping.rs index dd6dc1fc..8c8473fd 100644 --- a/src/syntax_mapping.rs +++ b/src/syntax_mapping.rs @@ -7,7 +7,7 @@ use globset::{Candidate, GlobBuilder, GlobMatcher}; pub mod ignored_suffixes; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum MappingTarget<'a> { /// For mapping a path to a specific syntax. @@ -67,6 +67,11 @@ impl<'a> SyntaxMapping<'a> { .insert("*.pac", MappingTarget::MapTo("JavaScript (Babel)")) .unwrap(); + // See #2151, https://nmap.org/book/nse-language.html + mapping + .insert("*.nse", MappingTarget::MapTo("Lua")) + .unwrap(); + // See #1008 mapping .insert("rails", MappingTarget::MapToUnknown) @@ -120,6 +125,11 @@ impl<'a> SyntaxMapping<'a> { mapping.insert(glob, MappingTarget::MapTo("INI")).unwrap(); } + // unix mail spool + for glob in &["/var/spool/mail/*", "/var/mail/*"] { + mapping.insert(glob, MappingTarget::MapTo("Email")).unwrap() + } + // pacman hooks mapping .insert("*.hook", MappingTarget::MapTo("INI")) @@ -181,6 +191,7 @@ impl<'a> SyntaxMapping<'a> { } pub(crate) fn get_syntax_for(&self, path: impl AsRef) -> Option> { + // Try matching on the file name as-is. let candidate = Candidate::new(&path); let candidate_filename = path.as_ref().file_name().map(Candidate::new); for (ref glob, ref syntax) in self.mappings.iter().rev() { @@ -192,7 +203,13 @@ impl<'a> SyntaxMapping<'a> { return Some(*syntax); } } - None + // Try matching on the file name after removing an ignored suffix. + let file_name = path.as_ref().file_name()?; + self.ignored_suffixes + .try_with_stripped_suffix(file_name, |stripped_file_name| { + Ok(self.get_syntax_for(stripped_file_name)) + }) + .ok()? } pub fn insert_ignored_suffix(&mut self, suffix: &'a str) { diff --git a/src/vscreen.rs b/src/vscreen.rs deleted file mode 100644 index ea5d4da6..00000000 --- a/src/vscreen.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::fmt::{Display, Formatter}; - -// Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences. -pub struct AnsiStyle { - attributes: Option, -} - -impl AnsiStyle { - pub fn new() -> Self { - AnsiStyle { attributes: None } - } - - pub fn update(&mut self, sequence: &str) -> bool { - match &mut self.attributes { - Some(a) => a.update(sequence), - None => { - self.attributes = Some(Attributes::new()); - self.attributes.as_mut().unwrap().update(sequence) - } - } - } -} - -impl Display for AnsiStyle { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.attributes { - Some(ref a) => a.fmt(f), - None => Ok(()), - } - } -} - -struct Attributes { - foreground: String, - background: String, - underlined: String, - - /// The character set to use. - /// REGEX: `\^[()][AB0-3]` - charset: String, - - /// A buffer for unknown sequences. - unknown_buffer: String, - - /// ON: ^[1m - /// OFF: ^[22m - bold: String, - - /// ON: ^[2m - /// OFF: ^[22m - dim: String, - - /// ON: ^[4m - /// OFF: ^[24m - underline: String, - - /// ON: ^[3m - /// OFF: ^[23m - italic: String, - - /// ON: ^[9m - /// OFF: ^[29m - strike: String, -} - -impl Attributes { - pub fn new() -> Self { - Attributes { - foreground: "".to_owned(), - background: "".to_owned(), - underlined: "".to_owned(), - charset: "".to_owned(), - unknown_buffer: "".to_owned(), - bold: "".to_owned(), - dim: "".to_owned(), - underline: "".to_owned(), - italic: "".to_owned(), - strike: "".to_owned(), - } - } - - /// Update the attributes with an escape sequence. - /// Returns `false` if the sequence is unsupported. - pub fn update(&mut self, sequence: &str) -> bool { - let mut chars = sequence.char_indices().skip(1); - - if let Some((_, t)) = chars.next() { - match t { - '(' => self.update_with_charset('(', chars.map(|(_, c)| c)), - ')' => self.update_with_charset(')', chars.map(|(_, c)| c)), - '[' => { - if let Some((i, last)) = chars.last() { - // SAFETY: Always starts with ^[ and ends with m. - self.update_with_csi(last, &sequence[2..i]) - } else { - false - } - } - _ => self.update_with_unsupported(sequence), - } - } else { - false - } - } - - fn sgr_reset(&mut self) { - self.foreground.clear(); - self.background.clear(); - self.underlined.clear(); - self.bold.clear(); - self.dim.clear(); - self.underline.clear(); - self.italic.clear(); - self.strike.clear(); - } - - fn update_with_sgr(&mut self, parameters: &str) -> bool { - let mut iter = parameters - .split(';') - .map(|p| if p.is_empty() { "0" } else { p }) - .map(|p| p.parse::()) - .map(|p| p.unwrap_or(0)); // Treat errors as 0. - - while let Some(p) = iter.next() { - match p { - 0 => self.sgr_reset(), - 1 => self.bold = format!("\x1B[{}m", parameters), - 2 => self.dim = format!("\x1B[{}m", parameters), - 3 => self.italic = format!("\x1B[{}m", parameters), - 4 => self.underline = format!("\x1B[{}m", parameters), - 23 => self.italic.clear(), - 24 => self.underline.clear(), - 22 => { - self.bold.clear(); - self.dim.clear(); - } - 30..=39 => self.foreground = Self::parse_color(p, &mut iter), - 40..=49 => self.background = Self::parse_color(p, &mut iter), - 58..=59 => self.underlined = Self::parse_color(p, &mut iter), - 90..=97 => self.foreground = Self::parse_color(p, &mut iter), - 100..=107 => self.foreground = Self::parse_color(p, &mut iter), - _ => { - // Unsupported SGR sequence. - // Be compatible and pretend one just wasn't was provided. - } - } - } - - true - } - - fn update_with_csi(&mut self, finalizer: char, sequence: &str) -> bool { - if finalizer == 'm' { - self.update_with_sgr(sequence) - } else { - false - } - } - - fn update_with_unsupported(&mut self, sequence: &str) -> bool { - self.unknown_buffer.push_str(sequence); - false - } - - fn update_with_charset(&mut self, kind: char, set: impl Iterator) -> bool { - self.charset = format!("\x1B{}{}", kind, set.take(1).collect::()); - true - } - - fn parse_color(color: u16, parameters: &mut dyn Iterator) -> String { - match color % 10 { - 8 => match parameters.next() { - Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)), - Some(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)), - Some(c) => format!("\x1B[{};{}m", color, c), - _ => "".to_owned(), - }, - 9 => "".to_owned(), - _ => format!("\x1B[{}m", color), - } - } -} - -impl Display for Attributes { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}{}{}{}{}{}{}{}{}", - self.foreground, - self.background, - self.underlined, - self.charset, - self.bold, - self.dim, - self.underline, - self.italic, - self.strike, - ) - } -} - -fn join( - delimiter: &str, - limit: usize, - iterator: &mut dyn Iterator, -) -> String { - iterator - .take(limit) - .map(|i| i.to_string()) - .collect::>() - .join(delimiter) -} diff --git a/tests/examples/longline.json b/tests/examples/longline.json new file mode 100644 index 00000000..0e11bf14 --- /dev/null +++ b/tests/examples/longline.json @@ -0,0 +1,3 @@ +{"api": + {"ANGLE_instanced_arrays":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"drawArraysInstancedANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/drawArraysInstancedANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"drawElementsInstancedANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/drawElementsInstancedANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"vertexAttribDivisorANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/vertexAttribDivisorANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbortController":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController","spec_url":"https://dom.spec.whatwg.org/#interface-abortcontroller","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}},"AbortController":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/AbortController","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-abortcontroller①","description":"AbortController() constructor","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}},"abort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/abort","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-abortcontroller①","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}},"signal":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/signal","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-signal②","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}}},"AbortPaymentEvent":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}},"AbortPaymentEvent":{"__compat":{"description":"AbortPaymentEvent() constructor","mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent/AbortPaymentEvent","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}}},"respondWith":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent/respondWith","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}}}},"AbortSignal":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal","spec_url":"https://dom.spec.whatwg.org/#interface-AbortSignal","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"abort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/abort","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortsignal-abort①","support":{"chrome":{"version_added":false},"chrome_android":{"version_added":false},"edge":{"version_added":false},"firefox":{"version_added":"88"},"firefox_android":{"version_added":"88"},"ie":{"version_added":false},"nodejs":{"version_added":false},"opera":{"version_added":false},"opera_android":{"version_added":false},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":false},"webview_android":{"version_added":false}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"abort_event":{"__compat":{"description":"abort event","mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event","spec_url":"https://dom.spec.whatwg.org/#eventdef-abortsignal-abort","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"aborted":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortsignal-aborted①","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"onabort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/onabort","spec_url":"https://dom.spec.whatwg.org/#abortsignal-onabort","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbsoluteOrientationSensor":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbsoluteOrientationSensor","spec_url":"https://w3c.github.io/orientation-sensor/#absoluteorientationsensor-interface","support":{"chrome":{"version_added":"67"},"chrome_android":{"version_added":"67"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"54"},"opera_android":{"version_added":"48"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"67"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"AbsoluteOrientationSensor":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbsoluteOrientationSensor/AbsoluteOrientationSensor","spec_url":"https://w3c.github.io/orientation-sensor/#dom-absoluteorientationsensor-absoluteorientationsensor","description":"AbsoluteOrientationSensor() constructor","support":{"chrome":{"version_added":"67"},"chrome_android":{"version_added":"67"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"54"},"opera_android":{"version_added":"48"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"67"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbstractRange":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbstractRange","spec_url":"https://dom.spec.whatwg.org/#interface-abstractrange","support":{"chrome":{"version_added":"90"},"chrome_android":{"version_added":"90"},"edge":[{"version_added":"90"},{"version_added":"18","version_removed":"79"}],"firefox":{"version_added":"69"},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":false},"opera_android":{"version_added":false},"safari":{"version_added":"14.1"},"safari_ios":{"version_added":"14.5"},"samsunginternet_android":{"version_added":false},"webview_android":{"version_added":"90"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"collapsed":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbstractRange/collapsed","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-range-collapsed①","support":{"chrome":{"version_added":"90"},"chrome_android":{"version_added":"90"},"edge":[{"version_added":"90"},{"version_added":"18","version_removed":"79"}],"firefox":{"version_added":"69"},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":false},"opera_android": + {"version_added":false} diff --git a/tests/examples/test.demo.foo.suffix b/tests/examples/test.demo.foo.suffix new file mode 100644 index 00000000..20ec3289 --- /dev/null +++ b/tests/examples/test.demo.foo.suffix @@ -0,0 +1 @@ +{"test": "value"} \ No newline at end of file diff --git a/tests/examples/test.demo.suffix b/tests/examples/test.demo.suffix new file mode 100644 index 00000000..20ec3289 --- /dev/null +++ b/tests/examples/test.demo.suffix @@ -0,0 +1 @@ +{"test": "value"} \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3baac02b..3deb65a7 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -667,6 +667,18 @@ fn alias_pager_disable_long_overrides_short() { .stdout(predicate::eq("pager-output\n").normalize()); } +#[test] +fn disable_pager_if_disable_paging_flag_comes_after_paging() { + bat() + .env("PAGER", "echo pager-output") + .arg("--paging=always") + .arg("-P") + .arg("test.txt") + .assert() + .success() + .stdout(predicate::eq("hello world\n").normalize()); +} + #[test] fn pager_failed_to_parse() { bat() @@ -946,6 +958,55 @@ fn header_full_binary() { .stderr(""); } +#[test] +#[cfg(feature = "git")] // Expected output assumes git is enabled +fn header_default() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--terminal-width=80") + .arg("--wrap=never") + .arg("--decorations=always") + .arg("--style=default") + .arg("single-line.txt") + .assert() + .success() + .stdout( + "\ +───────┬──────────────────────────────────────────────────────────────────────── + │ File: single-line.txt +───────┼──────────────────────────────────────────────────────────────────────── + 1 │ Single Line +───────┴──────────────────────────────────────────────────────────────────────── +", + ) + .stderr(""); +} + +#[test] +#[cfg(feature = "git")] // Expected output assumes git is enabled +fn header_default_is_default() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--terminal-width=80") + .arg("--wrap=never") + .arg("--decorations=always") + .arg("single-line.txt") + .assert() + .success() + .stdout( + "\ +───────┬──────────────────────────────────────────────────────────────────────── + │ File: single-line.txt +───────┼──────────────────────────────────────────────────────────────────────── + 1 │ Single Line +───────┴──────────────────────────────────────────────────────────────────────── +", + ) + .stderr(""); +} + #[test] fn filename_stdin() { bat() @@ -1286,6 +1347,7 @@ fn plain_mode_does_not_add_nonexisting_newline() { // Regression test for https://github.com/sharkdp/bat/issues/299 #[test] +#[cfg(feature = "git")] // Expected output assumes git is enabled fn grid_for_file_without_newline() { bat() .arg("--paging=never") @@ -1329,29 +1391,12 @@ fn ansi_highlight_underline() { .stderr(""); } -// Ensure that ANSI passthrough is emitted properly for both wrapping and non-wrapping printer. -#[test] -fn ansi_passthrough_emit() { - for wrapping in &["never", "character"] { - bat() - .arg("--paging=never") - .arg("--color=never") - .arg("--terminal-width=80") - .arg(format!("--wrap={}", wrapping)) - .arg("--decorations=always") - .arg("--style=plain") - .write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n") - .assert() - .success() - .stdout("\x1B[33m\x1B[33mColor\n\x1B[33mColor \x1B[m\nPlain\n") - .stderr(""); - } -} - #[test] fn ignored_suffix_arg() { bat() .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("test.json~") .assert() @@ -1361,6 +1406,8 @@ fn ignored_suffix_arg() { bat() .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("--ignored-suffix=.suffix") .arg("test.json.suffix") @@ -1371,6 +1418,8 @@ fn ignored_suffix_arg() { bat() .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("test.json.suffix") .assert() @@ -1379,6 +1428,25 @@ fn ignored_suffix_arg() { .stderr(""); } +#[test] +fn highlighting_is_skipped_on_long_lines() { + let expected = "\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mapi\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\n".to_owned() + + "\u{1b}" + + r#"[38;5;231m {"ANGLE_instanced_arrays":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"drawArraysInstancedANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/drawArraysInstancedANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"drawElementsInstancedANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/drawElementsInstancedANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"vertexAttribDivisorANGLE":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/ANGLE_instanced_arrays/vertexAttribDivisorANGLE","spec_url":"https://www.khronos.org/registry/webgl/extensions/ANGLE_instanced_arrays/","support":{"chrome":{"version_added":"32"},"chrome_android":{"version_added":"32"},"edge":{"version_added":"12"},"firefox":{"version_added":"47"},"firefox_android":{"version_added":true},"ie":{"version_added":"11"},"opera":{"version_added":"19"},"opera_android":{"version_added":"19"},"safari":{"version_added":"8"},"safari_ios":{"version_added":"8"},"samsunginternet_android":{"version_added":"2.0"},"webview_android":{"version_added":"4.4"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbortController":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController","spec_url":"https://dom.spec.whatwg.org/#interface-abortcontroller","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}},"AbortController":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/AbortController","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-abortcontroller①","description":"AbortController() constructor","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}},"abort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/abort","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-abortcontroller①","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}},"signal":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortController/signal","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortcontroller-signal②","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":[{"version_added":"12.1"},{"version_added":"11.1","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"safari_ios":[{"version_added":"12.2"},{"version_added":"11.3","partial_implementation":true,"notes":"Even though window.AbortController is defined, it doesn't really abort fetch requests. See bug 174980."}],"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":true,"standard_track":true,"deprecated":false}}}},"AbortPaymentEvent":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}},"AbortPaymentEvent":{"__compat":{"description":"AbortPaymentEvent() constructor","mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent/AbortPaymentEvent","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}}},"respondWith":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortPaymentEvent/respondWith","support":{"chrome":{"version_added":"70"},"chrome_android":{"version_added":"70"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"57"},"opera_android":{"version_added":"49"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"10.0"},"webview_android":{"version_added":false}},"status":{"experimental":true,"standard_track":false,"deprecated":false}}}},"AbortSignal":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal","spec_url":"https://dom.spec.whatwg.org/#interface-AbortSignal","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"abort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/abort","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortsignal-abort①","support":{"chrome":{"version_added":false},"chrome_android":{"version_added":false},"edge":{"version_added":false},"firefox":{"version_added":"88"},"firefox_android":{"version_added":"88"},"ie":{"version_added":false},"nodejs":{"version_added":false},"opera":{"version_added":false},"opera_android":{"version_added":false},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":false},"webview_android":{"version_added":false}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"abort_event":{"__compat":{"description":"abort event","mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event","spec_url":"https://dom.spec.whatwg.org/#eventdef-abortsignal-abort","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"aborted":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-abortsignal-aborted①","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}},"onabort":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbortSignal/onabort","spec_url":"https://dom.spec.whatwg.org/#abortsignal-onabort","support":{"chrome":{"version_added":"66"},"chrome_android":{"version_added":"66"},"edge":{"version_added":"16"},"firefox":{"version_added":"57"},"firefox_android":{"version_added":"57"},"ie":{"version_added":false},"nodejs":{"version_added":"15.0.0"},"opera":{"version_added":"53"},"opera_android":{"version_added":"47"},"safari":{"version_added":"11.1"},"safari_ios":{"version_added":"11.3"},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"66"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbsoluteOrientationSensor":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbsoluteOrientationSensor","spec_url":"https://w3c.github.io/orientation-sensor/#absoluteorientationsensor-interface","support":{"chrome":{"version_added":"67"},"chrome_android":{"version_added":"67"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"54"},"opera_android":{"version_added":"48"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"67"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"AbsoluteOrientationSensor":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbsoluteOrientationSensor/AbsoluteOrientationSensor","spec_url":"https://w3c.github.io/orientation-sensor/#dom-absoluteorientationsensor-absoluteorientationsensor","description":"AbsoluteOrientationSensor() constructor","support":{"chrome":{"version_added":"67"},"chrome_android":{"version_added":"67"},"edge":{"version_added":"79"},"firefox":{"version_added":false},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":"54"},"opera_android":{"version_added":"48"},"safari":{"version_added":false},"safari_ios":{"version_added":false},"samsunginternet_android":{"version_added":"9.0"},"webview_android":{"version_added":"67"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}}}},"AbstractRange":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbstractRange","spec_url":"https://dom.spec.whatwg.org/#interface-abstractrange","support":{"chrome":{"version_added":"90"},"chrome_android":{"version_added":"90"},"edge":[{"version_added":"90"},{"version_added":"18","version_removed":"79"}],"firefox":{"version_added":"69"},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":false},"opera_android":{"version_added":false},"safari":{"version_added":"14.1"},"safari_ios":{"version_added":"14.5"},"samsunginternet_android":{"version_added":false},"webview_android":{"version_added":"90"}},"status":{"experimental":false,"standard_track":true,"deprecated":false}},"collapsed":{"__compat":{"mdn_url":"https://developer.mozilla.org/docs/Web/API/AbstractRange/collapsed","spec_url":"https://dom.spec.whatwg.org/#ref-for-dom-range-collapsed①","support":{"chrome":{"version_added":"90"},"chrome_android":{"version_added":"90"},"edge":[{"version_added":"90"},{"version_added":"18","version_removed":"79"}],"firefox":{"version_added":"69"},"firefox_android":{"version_added":false},"ie":{"version_added":false},"opera":{"version_added":false},"opera_android":"# + + "\u{1b}[0m\n\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mversion_added\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;141mfalse\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m\n"; + + bat() + .arg("-f") + .arg("--theme") + .arg("Monokai Extended") + .arg("-p") + .arg("longline.json") + .assert() + .success() + .stdout(expected) + .stderr(""); +} + #[test] fn all_global_git_config_locations_syntax_mapping_work() { let fake_home = Path::new(EXAMPLES_DIR).join("git").canonicalize().unwrap(); @@ -1390,6 +1458,8 @@ fn all_global_git_config_locations_syntax_mapping_work() { bat() .env("XDG_CONFIG_HOME", fake_home.join(".config").as_os_str()) .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("git/.config/git/config") .assert() @@ -1400,6 +1470,8 @@ fn all_global_git_config_locations_syntax_mapping_work() { bat() .env("HOME", fake_home.as_os_str()) .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("git/.config/git/config") .assert() @@ -1410,6 +1482,8 @@ fn all_global_git_config_locations_syntax_mapping_work() { bat() .env("HOME", fake_home.as_os_str()) .arg("-f") + .arg("--theme") + .arg("Monokai Extended") .arg("-p") .arg("git/.gitconfig") .assert() @@ -1418,6 +1492,36 @@ fn all_global_git_config_locations_syntax_mapping_work() { .stderr(""); } +#[test] +fn map_syntax_and_ignored_suffix_work_together() { + bat() + .arg("-f") + .arg("--theme") + .arg("Monokai Extended") + .arg("-p") + .arg("--ignored-suffix=.suffix") + .arg("--map-syntax=*.demo:JSON") + .arg("test.demo.suffix") + .assert() + .success() + .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m") + .stderr(""); + + bat() + .arg("-f") + .arg("--theme") + .arg("Monokai Extended") + .arg("-p") + .arg("--ignored-suffix=.suffix") + .arg("--ignored-suffix=.foo") + .arg("--map-syntax=*.demo:JSON") + .arg("test.demo.foo.suffix") + .assert() + .success() + .stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m") + .stderr(""); +} + #[test] fn acknowledgements() { bat() diff --git a/tests/scripts/license-checks.sh b/tests/scripts/license-checks.sh index 4de9f2b0..21365084 100755 --- a/tests/scripts/license-checks.sh +++ b/tests/scripts/license-checks.sh @@ -18,7 +18,7 @@ gpl_occurances=$(git grep --recurse-submodules "${gpl_term}" -- "${gpl_excludes[ if [ -z "${gpl_occurances}" ]; then echo "PASS: No files under GPL were found" else - echo "FAIL: GPL:ed code is not compatible with bat, but occurances of '${gpl_term}' were found:" + echo "FAIL: GPL:ed code is not compatible with bat, but occurrences of '${gpl_term}' were found:" echo "${gpl_occurances}" exit 1 fi diff --git a/tests/snapshot_tests.rs b/tests/snapshot_tests.rs index 14c3eee1..a68e74e2 100644 --- a/tests/snapshot_tests.rs +++ b/tests/snapshot_tests.rs @@ -1,13 +1,13 @@ +#[cfg(feature = "git")] mod tester; -use crate::tester::BatTester; - macro_rules! snapshot_tests { ($($test_name: ident: $style: expr,)*) => { $( #[test] + #[cfg(feature = "git")] fn $test_name() { - let bat_tester = BatTester::default(); + let bat_tester = tester::BatTester::default(); bat_tester.test_snapshot(stringify!($test_name), $style); } )* diff --git a/tests/syntax-tests/highlighted/Fstab/fstab b/tests/syntax-tests/highlighted/Fstab/fstab index 0107bf4f..2a44ab61 100644 --- a/tests/syntax-tests/highlighted/Fstab/fstab +++ b/tests/syntax-tests/highlighted/Fstab/fstab @@ -3,5 +3,6 @@ #  -UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 0 1 -UUID=62F8-2047 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 2 +UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 0 0 +UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 1 1 +UUID=62F8-2047 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 2 2 diff --git a/tests/syntax-tests/highlighted/INI/test.inf b/tests/syntax-tests/highlighted/INI/test.inf new file mode 100644 index 00000000..dae43ba9 --- /dev/null +++ b/tests/syntax-tests/highlighted/INI/test.inf @@ -0,0 +1,5 @@ +[section] +key=value +# This file is just for testing that the INI syntax is registered to handle +# the .inf file extension, it is not testing the syntax highlighting capabilities +# of the INI syntax itself diff --git a/tests/syntax-tests/highlighted/Log/example.log b/tests/syntax-tests/highlighted/Log/example.log index 8387670c..f0e9754a 100644 --- a/tests/syntax-tests/highlighted/Log/example.log +++ b/tests/syntax-tests/highlighted/Log/example.log @@ -1,2 +1,3 @@ 2021-03-06 23:22:21.392 https://[2001:db8:4006:812::200e]:8080/path/the%20page.html 2021-03-06 23:22:21 https://example.com:8080/path/the%20page(with_parens).html +2022-03-16T17:41:02.519 helix_term::application [WARN] unhandled window/showMessage: ShowMessageParams { typ: Error, message: "rust-analyzer failed to load workspace: Failed to read Cargo metadata from Cargo.toml file /home/zeta/dev/raytracer/Cargo.toml, cargo 1.61.0-nightly (65c8266 2022-03-09): Failed to run `\"cargo\" \"metadata\" \"--format-version\" \"1\" \"--manifest-path\" \"/home/zeta/dev/raytracer/Cargo.toml\" \"--filter-platform\" \"wasm32-unknown-unknown\"`: `cargo metadata` exited with an error: Updating crates.io index\nerror: failed to select a version for `parking_lot`.\n ... required by package `raytracer v0.1.0 (/home/zeta/dev/raytracer)`\nversions that meet the requirements `^0.12.0` are: 0.12.0\n\nthe package `raytracer` depends on `parking_lot`, with features: `wasm-bindgen` but `parking_lot` does not have these features.\n\n\nfailed to select a version for `parking_lot` which could resolve this conflict\n" } diff --git a/tests/syntax-tests/highlighted/NSE/test.nse b/tests/syntax-tests/highlighted/NSE/test.nse new file mode 100644 index 00000000..3c37aeee --- /dev/null +++ b/tests/syntax-tests/highlighted/NSE/test.nse @@ -0,0 +1,34 @@ +--- Finds factorial of a number. +-- @param value Number to find factorial. +-- @return Factorial of number. +local function factorial(value) + if value <= 1 then + return 1 + else + return value * factorial(value - 1) + end +end + +--- Joins a table of strings into a new string. +-- @param table Table of strings. +-- @param separator Separator character. +-- @return Joined string. +local function join(table, separator) + local data = "" +  + for index, value in ipairs(table) do + data = data .. value .. separator + end +  + data = data:sub(1, data:len() - 1) +  + return data +end + +local a = factorial(5) + +print(a) + +local b = join({ "l", "u", "a" }, ",") + +print(b) diff --git a/tests/syntax-tests/highlighted/Svelte/App.svelte b/tests/syntax-tests/highlighted/Svelte/App.svelte index 7a09649a..d1ec9948 100644 --- a/tests/syntax-tests/highlighted/Svelte/App.svelte +++ b/tests/syntax-tests/highlighted/Svelte/App.svelte @@ -27,9 +27,13 @@  <script type="text/livescript"> - // This block is a regression test for a bat panic when a LiveScript syntax definition is missing + // This block is a regression test for a bat panic when a LiveScript syntax definition is missing  +<style lang="text/postcss"> + /* This block is a regression test for a bat panic when a PostCSS syntax definition is missing */ + + <style>  main {  position: relative; diff --git a/tests/syntax-tests/highlighted/Syslog/example.syslog b/tests/syntax-tests/highlighted/Syslog/example.syslog index 9ec04944..0c200c33 100644 --- a/tests/syntax-tests/highlighted/Syslog/example.syslog +++ b/tests/syntax-tests/highlighted/Syslog/example.syslog @@ -1,7 +1,7 @@ Apr 4 00:00:01 hostname-here systemd[1]: logrotate.service: Succeeded. Apr 4 00:00:01 hostname-here systemd[1]: Finished Rotate log files. Apr 4 00:00:01 hostname-here colord[920]: failed to get session [pid 137485]: No data available -Apr 4 00:00:21 hostname-here kernel: [55604.908232] audit: type=1400 audit(1617483621.094:28): apparmor="DENIED" operation="capable" profile="/usr/sbin/cups-browsed" pid=59311 comm="cups-browsed" capability=23 capname="sys_nice" +Apr 4 00:00:21 hostname-here kernel: [55604.908232] audit: type=1400 audit(1617483621.094:28): apparmor="DENIED" operation="capable" profile="/usr/sbin/cups-browsed" pid=59311 comm="cups-browsed" capability=23 capname="sys_nice" Apr 4 00:01:38 hostname-here systemd-resolved[721]: Server returned error NXDOMAIN, mitigating potential DNS violation DVE-2018-0001, retrying transaction with reduced feature level UDP. Apr 4 00:04:46 hostname-here ntpd[952]: Soliciting pool server 255.76.59.37 Apr 4 00:05:21 hostname-here ntpd[952]: ::1 local addr 0:0:0:0:0:0:0:1 ->  @@ -10,7 +10,7 @@ Apr 4 16:32:07 hostname-here NetworkManager[740]:  [1617629527.1101] manager: NetworkManager state is now CONNECTED_GLOBAL Apr 4 22:00:45 hostname-here dbus-daemon[1094]: [session uid=1000 pid=1094] Successfully activated service 'io.github.celluloid_player.Celluloid' Aug 11 13:29:06 hostname-here insomnia_insomnia.desktop[142666]: 13:29:06.316 › [updater] Updater not running platform=linux dev=false -Aug 11 13:36:34 192.168.220.5 nginx: 2021/08/11 13:36:34 [debug] 2031#2031: epoll add event: fd:6 op:1 ev:00002001 -Aug 11 21:31:08 ::1 nginx: 2021/08/11 21:31:08 [debug] 760831#760831: epoll add event: fd:6 op:1 ev:10000001 +Aug 11 13:36:34 192.168.220.5 nginx: 2021/08/11 13:36:34 [debug] 2031#2031: epoll add event: fd:6 op:1 ev:00002001 +Aug 11 21:31:08 ::1 nginx: 2021/08/11 21:31:08 [debug] 760831#760831: epoll add event: fd:6 op:1 ev:10000001 Aug 11 21:40:31 hostname-here scop hello Aug 16 21:38:21 hostname-here systemd[1]: Finished Cleanup of Temporary Directories. diff --git a/tests/syntax-tests/highlighted/Vue/example.vue b/tests/syntax-tests/highlighted/Vue/example.vue index d8da0852..07c27986 100644 --- a/tests/syntax-tests/highlighted/Vue/example.vue +++ b/tests/syntax-tests/highlighted/Vue/example.vue @@ -8,6 +8,11 @@    +<template lang='pug'> + #container.col + p This shall be formatted as Plain Text as long as a Pug syntax definition is missing + + <script> import AppHeader from "@/components/AppHeader"; import AppLoadingIndicator from "@/components/AppLoadingIndicator"; diff --git a/tests/syntax-tests/highlighted/Zig/example.zig b/tests/syntax-tests/highlighted/Zig/example.zig index 7153dbfc..11bcd248 100644 --- a/tests/syntax-tests/highlighted/Zig/example.zig +++ b/tests/syntax-tests/highlighted/Zig/example.zig @@ -1,18 +1,18 @@ //! this is a top level doc, starts with "//!" -const std = @import("std"); +const std = @import("std"); pub fn main() anyerror!void { - const stdout = std.io.getStdOut().writer(); - try stdout.print("Hello, {}!\n", .{"world"}); + const stdout = std.io.getStdOut().writer(); + try stdout.print("Hello, {}!\n", .{"world"}); } -const expect = std.testing.expect; +const expect = std.testing.expect; test "comments" {  // comments start with "//" until newline  // foo bar baz - const x = true; // another comment + const x = true; // another comment  expect(x); } @@ -25,26 +25,26 @@  /// number of nanoseconds past the second  nano: u32, - const Self = @This(); + const Self = @This();  pub fn unixEpoch() Self {  return Self{ - .seconds = 0, - .nanos = 0, + .seconds = 0, + .nanos = 0,  };  } }; -const my_val = switch (std.Target.current.os.tag) { - .linux => "Linux", +const my_val = switch (std.Target.current.os.tag) { + .linux => "Linux",  else => "not Linux", }; const Book = enum { - paperback, - hardcover, - ebook, - pdf, + paperback, + hardcover, + ebook, + pdf, }; const TokenType = union(enum) { @@ -54,54 +54,54 @@ }; const array_lit: [4]u8 = .{ 11, 22, 33, 44 }; -const sentinal_lit = [_:0]u8{ 1, 2, 3, 4 }; +const sentinal_lit = [_:0]u8{ 1, 2, 3, 4 }; test "address of syntax" {  // Get the address of a variable:  const x: i32 = 1234; - const x_ptr = &x; + const x_ptr = &x;  // Dereference a pointer:  expect(x_ptr.* == 1234);  // When you get the address of a const variable, you get a const pointer to a single item. - expect(@TypeOf(x_ptr) == *const i32); + expect(@TypeOf(x_ptr) == *const i32);  // If you want to mutate the value, you'd need an address of a mutable variable:  var y: i32 = 5678; - const y_ptr = &y; - expect(@TypeOf(y_ptr) == *i32); - y_ptr.* += 1; + const y_ptr = &y; + expect(@TypeOf(y_ptr) == *i32); + y_ptr.* += 1;  expect(y_ptr.* == 5679); } // integer literals -const decimal_int = 98222; -const hex_int = 0xff; -const another_hex_int = 0xFF; -const octal_int = 0o755; -const binary_int = 0b11110000; +const decimal_int = 98222; +const hex_int = 0xff; +const another_hex_int = 0xFF; +const octal_int = 0o755; +const binary_int = 0b11110000; // underscores may be placed between two digits as a visual separator -const one_billion = 1_000_000_000; -const binary_mask = 0b1_1111_1111; -const permissions = 0o7_5_5; -const big_address = 0xFF80_0000_0000_0000; +const one_billion = 1_000_000_000; +const binary_mask = 0b1_1111_1111; +const permissions = 0o7_5_5; +const big_address = 0xFF80_0000_0000_0000; // float literals -const floating_point = 123.0E+77; -const another_float = 123.0; -const yet_another = 123.0e+77; +const floating_point = 123.0E+77; +const another_float = 123.0; +const yet_another = 123.0e+77; -const hex_floating_point = 0x103.70p-5; -const another_hex_float = 0x103.70; -const yet_another_hex_float = 0x103.70P-5; +const hex_floating_point = 0x103.70p-5; +const another_hex_float = 0x103.70; +const yet_another_hex_float = 0x103.70P-5; // underscores may be placed between two digits as a visual separator -const lightspeed = 299_792_458.000_000; -const nanosecond = 0.000_000_001; -const more_hex = 0x1234_5678.9ABC_CDEFp-10; +const lightspeed = 299_792_458.000_000; +const nanosecond = 0.000_000_001; +const more_hex = 0x1234_5678.9ABC_CDEFp-10; fn max(comptime T: type, a: T, b: T) T { - return if (a > b) a else b; + return if (a > b) a else b; } diff --git a/tests/syntax-tests/highlighted/cmd-help/test.cmd-help b/tests/syntax-tests/highlighted/cmd-help/test.cmd-help new file mode 100644 index 00000000..c758a0d9 --- /dev/null +++ b/tests/syntax-tests/highlighted/cmd-help/test.cmd-help @@ -0,0 +1,54 @@ +bat 0.20.0 (e735562-modified) +A cat(1) clone with syntax highlighting and Git integration. + +USAGE: + bat [OPTIONS] [FILE]... + bat  + +OPTIONS: + -A, --show-all + Show non-printable characters (space, tab, newline, ..). + + -p, --plain Show plain style (alias for '--style=plain'). + -l, --language  Set the language for syntax highlighting. + -H, --highlight-line ... Highlight lines N through M. + --file-name ... Specify the name to display for a file. + -d, --diff + Only show lines that have been added/removed/modified. + + --tabs  Set the tab width to T spaces. + --wrap  + Specify the text-wrapping mode (*auto*, never, character). + + -n, --number + Show line numbers (alias for '--style=numbers'). + + --color  When to use colors (*auto*, never, always). + --italic-text  Use italics in output (always, *never*) + --decorations  + When to show the decorations (*auto*, never, always). + + --paging  + Specify when to use the pager, or use `-P` to disable (*auto*, never, + always). + -m, --map-syntax ... + Use the specified syntax for files matching the glob pattern + ('*.cpp:C++'). + --theme  Set the color theme for syntax highlighting. + --list-themes Display all supported highlighting themes. + --style  + Comma-separated list of style elements to display (*auto*, full, plain, + changes, header, grid, rule, numbers, snip). + -r, --line-range ... Only print the lines from N to M. + -L, --list-languages Display all supported languages. + -h, --help Print this help message. + -V, --version Show version information. + +ARGS: + ... File(s) to print / concatenate. Use '-' for standard input. + +SUBCOMMANDS: + cache Modify the syntax-definition and theme cache + +Note: `bat -h` prints a short and concise overview while `bat --help` gives all +details. diff --git a/tests/syntax-tests/source/Fstab/fstab b/tests/syntax-tests/source/Fstab/fstab index e000b8d9..2282f0cf 100644 --- a/tests/syntax-tests/source/Fstab/fstab +++ b/tests/syntax-tests/source/Fstab/fstab @@ -3,5 +3,6 @@ # -UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 0 1 -UUID=62F8-2047 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 2 +UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 0 0 +UUID=9e6faddf-31ab-3f3e-9b50-2ad4fbc2ea8b / ext4 rw,relatime,data=ordered 1 1 +UUID=62F8-2047 /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 2 2 diff --git a/tests/syntax-tests/source/INI/test.inf b/tests/syntax-tests/source/INI/test.inf new file mode 100644 index 00000000..5b03db1c --- /dev/null +++ b/tests/syntax-tests/source/INI/test.inf @@ -0,0 +1,5 @@ +[section] +key=value +# This file is just for testing that the INI syntax is registered to handle +# the .inf file extension, it is not testing the syntax highlighting capabilities +# of the INI syntax itself diff --git a/tests/syntax-tests/source/Log/example.log b/tests/syntax-tests/source/Log/example.log index 35eb1c78..3f1b8cd8 100644 --- a/tests/syntax-tests/source/Log/example.log +++ b/tests/syntax-tests/source/Log/example.log @@ -1,2 +1,3 @@ 2021-03-06 23:22:21.392 https://[2001:db8:4006:812::200e]:8080/path/the%20page.html 2021-03-06 23:22:21 https://example.com:8080/path/the%20page(with_parens).html +2022-03-16T17:41:02.519 helix_term::application [WARN] unhandled window/showMessage: ShowMessageParams { typ: Error, message: "rust-analyzer failed to load workspace: Failed to read Cargo metadata from Cargo.toml file /home/zeta/dev/raytracer/Cargo.toml, cargo 1.61.0-nightly (65c8266 2022-03-09): Failed to run `\"cargo\" \"metadata\" \"--format-version\" \"1\" \"--manifest-path\" \"/home/zeta/dev/raytracer/Cargo.toml\" \"--filter-platform\" \"wasm32-unknown-unknown\"`: `cargo metadata` exited with an error: Updating crates.io index\nerror: failed to select a version for `parking_lot`.\n ... required by package `raytracer v0.1.0 (/home/zeta/dev/raytracer)`\nversions that meet the requirements `^0.12.0` are: 0.12.0\n\nthe package `raytracer` depends on `parking_lot`, with features: `wasm-bindgen` but `parking_lot` does not have these features.\n\n\nfailed to select a version for `parking_lot` which could resolve this conflict\n" } diff --git a/tests/syntax-tests/source/NSE/test.nse b/tests/syntax-tests/source/NSE/test.nse new file mode 100644 index 00000000..68eb90e6 --- /dev/null +++ b/tests/syntax-tests/source/NSE/test.nse @@ -0,0 +1,34 @@ +--- Finds factorial of a number. +-- @param value Number to find factorial. +-- @return Factorial of number. +local function factorial(value) + if value <= 1 then + return 1 + else + return value * factorial(value - 1) + end +end + +--- Joins a table of strings into a new string. +-- @param table Table of strings. +-- @param separator Separator character. +-- @return Joined string. +local function join(table, separator) + local data = "" + + for index, value in ipairs(table) do + data = data .. value .. separator + end + + data = data:sub(1, data:len() - 1) + + return data +end + +local a = factorial(5) + +print(a) + +local b = join({ "l", "u", "a" }, ",") + +print(b) diff --git a/tests/syntax-tests/source/Svelte/App.svelte b/tests/syntax-tests/source/Svelte/App.svelte index 089172c4..e1ae02c3 100644 --- a/tests/syntax-tests/source/Svelte/App.svelte +++ b/tests/syntax-tests/source/Svelte/App.svelte @@ -27,9 +27,13 @@ + +