1
0
mirror of https://github.com/sharkdp/bat.git synced 2025-09-01 10:52:24 +01:00

Compare commits

...

244 Commits

Author SHA1 Message Date
David Peter
018a482621 Update Warp logo 2024-03-23 21:15:25 +01:00
guoguangwu
4790def1ef fix: typo
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
2024-03-17 15:40:48 +01:00
Hamir Mahal
07c26adc35 style: simplify equality check 2024-03-11 18:57:51 +01:00
David Peter
f29f9387b5 Merge pull request #2868 from cyqsimon/builtin-offload-v2
Faster startup by offloading glob matcher building to a worker thread
2024-03-10 20:08:43 +01:00
Hamir Mahal
c290bfff1e refactor: remove reference that is unnecessary 2024-03-10 11:17:02 +01:00
dependabot[bot]
42153f2b99 Bump nu-ansi-term from 0.49.0 to 0.50.0 (#2879)
Bumps [nu-ansi-term](https://github.com/nushell/nu-ansi-term) from 0.49.0 to 0.50.0.
- [Release notes](https://github.com/nushell/nu-ansi-term/releases)
- [Changelog](https://github.com/nushell/nu-ansi-term/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nushell/nu-ansi-term/compare/v0.49.0...v0.50.0)

---
updated-dependencies:
- dependency-name: nu-ansi-term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 04:45:43 +00:00
dependabot[bot]
6d7537d3ec Bump itertools from 0.11.0 to 0.12.1 (#2880)
Bumps [itertools](https://github.com/rust-itertools/itertools) from 0.11.0 to 0.12.1.
- [Changelog](https://github.com/rust-itertools/itertools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-itertools/itertools/compare/v0.11.0...v0.12.1)

---
updated-dependencies:
- dependency-name: itertools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 04:29:33 +00:00
dependabot[bot]
b30ec9f975 Bump git2 from 0.18.1 to 0.18.2 (#2877)
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.18.1 to 0.18.2.
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.18.1...git2-0.18.2)

---
updated-dependencies:
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 04:10:37 +00:00
dependabot[bot]
a7074f10d4 Bump plist from 1.5.1 to 1.6.0 (#2875)
Bumps [plist](https://github.com/ebarnard/rust-plist) from 1.5.1 to 1.6.0.
- [Release notes](https://github.com/ebarnard/rust-plist/releases)
- [Commits](https://github.com/ebarnard/rust-plist/compare/v1.5.1...v1.6.0)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-01 03:44:56 +00:00
David Peter
d185f0973b Merge pull request #2874 from cyqsimon/wg
Add syntax mapping for Wireguard config
2024-02-29 11:09:31 +01:00
cyqsimon
071874ea8f Write changelog 2024-02-29 14:34:00 +08:00
cyqsimon
46a2c004a2 Add syntax mapping for Wireguard config 2024-02-29 14:25:20 +08:00
cyqsimon
26ac179548 Add note on thread synchronization 2024-02-26 11:31:30 +08:00
Lena
4c85483486 Inline format! args wherever possible 2024-02-25 00:38:11 +01:00
einfachIrgendwer0815
487bed2d95 Change CONTRIBUTING.md according to new changelog requirements (#2839)
* Mention changelog requirements in CONTRIBUTING

* Refer to CONTRIBUTING in changelog workflow

* Clarify when changelog entries are necessary

---------

Co-authored-by: Martin Nordholts <martin.nordholts@codetale.se>
2024-02-24 23:58:20 +01:00
David Peter
6f69682552 Merge pull request #2665 from einfachIrgendwer0815/feature_squeeze
Add `-s`/`--squeeze` and `--squeeze-limit` (based on #1441).
2024-02-24 20:36:17 +01:00
David Peter
bc5beaec5d Update CHANGELOG.md 2024-02-24 20:36:08 +01:00
einfachIrgendwer0815
83b00bc653 Rename --squeeze to --squeeze-blank 2024-02-24 14:02:27 +01:00
einfachIrgendwer0815
f041ff8c5f Hide --squeeze-limit from short help 2024-02-24 13:57:44 +01:00
einfachIrgendwer0815
1fbdbfc4b2 Update CHANGELOG 2024-02-24 13:52:40 +01:00
einfachIrgendwer0815
2323aa0def Add tests for --squeeze 2024-02-24 13:51:44 +01:00
einfachIrgendwer0815
6c2ce63101 Add squeeze functionality to SimplePrinter 2024-02-24 13:51:44 +01:00
einfachIrgendwer0815
13204c46e2 Update short-help/long-help 2024-02-24 13:51:43 +01:00
Ethan P
9bb0271e7d Update CHANGELOG.md (PR #1441) 2024-02-24 13:51:40 +01:00
Ethan P
0e4e10edb6 Add --squeeze-limit to specify max number of consecutive empty lines
Co-authored-by: einfachIrgendwer0815 <85333734+einfachIrgendwer0815@users.noreply.github.com>
2024-02-24 13:51:06 +01:00
Ethan P
0c7e5299bf Add squeeze_empty_lines to PrettyPrinter 2024-02-24 13:51:05 +01:00
Ethan P
c36ed32816 Add --squeeze/-s option
Co-authored-by: einfachIrgendwer0815 <85333734+einfachIrgendwer0815@users.noreply.github.com>
2024-02-24 13:51:00 +01:00
David Peter
e1a3fc5529 Merge pull request #2857 from eth-p/skip-highlighting-when-no-color
Skip syntax parsing when color is not being output.
2024-02-23 21:59:45 +01:00
David Peter
1ae9e843ed Merge branch 'master' into skip-highlighting-when-no-color 2024-02-23 21:59:32 +01:00
David Peter
dbe4cfb763 Merge pull request #2866 from cyqsimon/quadlet
Add syntax mapping for podman quadlets
2024-02-23 21:54:44 +01:00
David Peter
4549f83689 Merge branch 'master' into quadlet 2024-02-23 21:54:36 +01:00
David Peter
e6e8f847be Merge pull request #2865 from cyqsimon/syntax-mapping-fix
Relax syntax mapping rule restrictions to allow brace expansion
2024-02-23 21:52:55 +01:00
David Peter
b9e249f782 Merge branch 'master' into syntax-mapping-fix 2024-02-23 21:52:46 +01:00
David Peter
3ffa3648cf Merge pull request #2853 from einfachIrgendwer0815/document_default_style_components
Document default style components
2024-02-23 21:51:55 +01:00
David Peter
5c2cc53882 Merge pull request #2867 from cyqsimon/containers-conf
Map containers `.conf` files to TOML syntax
2024-02-23 21:50:55 +01:00
David Peter
a6f01af8de Merge branch 'master' into containers-conf 2024-02-23 21:50:35 +01:00
David Peter
85a549e293 Merge pull request #2840 from anki-code/master
Added xonsh shell support
2024-02-23 21:48:58 +01:00
David Peter
b718889ba2 Merge branch 'master' into master 2024-02-23 21:48:44 +01:00
David Peter
708c74f6af Merge pull request #2795 from mxaddict/master
Added JSONC and aws credentials to the syntax mappings
2024-02-23 21:48:20 +01:00
David Peter
74d666f5c0 Merge branch 'master' into skip-highlighting-when-no-color 2024-02-23 21:45:38 +01:00
David Peter
7604fe5567 Merge pull request #2807 from Oliver-Looney/2783-setting-terminal-title
2783 setting terminal title
2024-02-23 21:37:28 +01:00
David Peter
0080b043c4 Merge branch 'master' into master 2024-02-23 21:35:21 +01:00
cyqsimon
c7bce46622 Write changelog 2024-02-21 15:24:36 +08:00
cyqsimon
2b4339663c Builtin glob matchers build offload, v2 2024-02-21 14:56:37 +08:00
cyqsimon
6a6b02117b Apply clippy fixes (#2864)
* Apply clippy fixes

* Write changelog
2024-02-20 19:39:22 +01:00
cyqsimon
511cd30105 Write changelog 2024-02-21 00:26:23 +08:00
cyqsimon
92915e22e7 Map containers .conf files to TOML syntax 2024-02-21 00:25:06 +08:00
cyqsimon
d499191b0a Write changelog 2024-02-20 14:54:43 +08:00
cyqsimon
152d69fe98 Add syntax mapping for podman quadlets 2024-02-20 14:49:12 +08:00
cyqsimon
81aa24310c Write changelog 2024-02-20 14:33:02 +08:00
cyqsimon
75cdabaf13 Relax syntax mapping rule restrictions to allow brace expansion 2024-02-20 14:24:51 +08:00
Oliver Looney
1f628203e5 Merge branch 'master' into 2783-setting-terminal-title 2024-02-12 09:34:01 +00:00
David Peter
1b9fc1d5af Merge pull request #2856 from eth-p/fix-2185
Fix incorrect categorization of ANSI SGR sequences.
2024-02-12 08:32:47 +01:00
David Peter
bc1ca1a346 Merge branch 'master' into fix-2185 2024-02-12 08:19:56 +01:00
David Peter
f735120978 Merge pull request #2854 from eth-p/fix-line-number-wrap-off-by-one
Fix off-by-one error in line number continuation
2024-02-12 08:19:34 +01:00
Ethan P.
25b5a41189 Skip syntax parsing when color output disabled 2024-02-11 22:49:49 -08:00
Ethan P.
c94cf4e14e Split syntax highlighting from print_line into fn
This simplifies the `print_line` function a little bit while also
providing a way for syntax highlighting to be skipped when it's
not being used (i.e. `--color=never`).
2024-02-11 22:27:35 -08:00
Ethan P.
84d80eebd0 Test for correct categorization of SGR sequences
This adds a regression test for the fix in the previous commit.
2024-02-11 19:40:29 -08:00
Ethan P.
915dd9fbf8 Fix incorrect categorization of SGR sequences
Specifically, prevent other attributes from leaking into the
bold/dim/italic/underline attributes, and ensure that bright backgrounds
are put into the background attribute instead of the foreground
attribute.
2024-02-11 19:35:54 -08:00
Ethan P.
9d77c1373c Fix off-by-one error in line number continuation 2024-02-11 16:49:06 -08:00
Oliver Looney
c3f2ddf509 Merge branch 'master' into 2783-setting-terminal-title 2024-02-11 22:53:48 +00:00
Oliver looney
8a51172b11 simplified basic_set_terminal_title 2024-02-11 22:43:07 +00:00
einfachIrgendwer0815
875046e4cd Mention which style components are the default 2024-02-11 10:06:01 +01:00
mxaddict
a5bd9f51be Added JSONC and aws credentials to the syntax mappings 2024-02-11 04:57:42 +08:00
Ethan P
5a2a20af42 Merge pull request #2544 from eth-p/fix-2541
Treat OSC ANSI Sequences as Invisible Text & Add OSC 8 Support
2024-02-09 22:20:30 -08:00
Ethan P
61029c8bd2 Update changelog for fix of ANSI OSC sequences 2024-02-09 22:09:40 -08:00
Ethan P
1023399c5e Remove hyperlink when wrapping lines 2024-02-09 22:09:39 -08:00
Ethan P
6549e26f5d Re-emit hyperlinks when wrapping lines 2024-02-09 22:09:39 -08:00
Ethan P
165c495e75 Replace AnsiCodeIterator in printer.rs
This uses the new EscapeSequenceIterator, saving us a preprocessing step
for each line.
2024-02-09 22:09:39 -08:00
Ethan P
6b9b085be3 Add EscapeSequenceIterator
This is an iterator for escape sequences, using
EscapeSequenceOffsetsIterator for the underlying parsing of individual
escape sequences.
2024-02-09 22:09:39 -08:00
Ethan P
2d46d54ae3 Add tests for re-emitting SGR sequences on wrap 2024-02-09 22:09:39 -08:00
Ethan P
3d04699710 Add regression test for #2541
More specifically, the test ensures that OSC sequences don't end up
wrapping the line.
2024-02-09 22:09:39 -08:00
Ethan P
054421268f Strip OSC sequences before printing
This commit strips OSC (Operating System Command) sequences before
printing lines. Eventually when time permits, I want to add back
support for printing OSC sequences (and improve it to treat hyperlinks
like an attribute).

Until then, this should help prevent garbled output :)
2024-02-09 22:09:39 -08:00
Ethan P
414403b062 Add EscapeSequenceOffsetsIterator
This can be used to extract a subset of ANSI escape sequences from a
string of text. I have big plans for this eventually, but for now, it'll
be used to strip OSC before printing.
2024-02-09 22:09:35 -08:00
Ethan P
c29bf2ff28 Update git2 dependency 2024-02-09 22:05:30 -08:00
David Peter
ab4e5ed52e Merge pull request #2815 from dtolnay-contrib/derive
Eliminate dependency on serde's "derive" feature
2024-02-09 08:21:21 +01:00
David Tolnay
1a54c9bf6d Eliminate dependency on serde's "derive" feature 2024-02-08 15:55:30 -08:00
Oliver looney
02077db53e undid unnecessary api visibility changes 2024-02-08 21:41:20 +00:00
Oliver looney
7ce010d9ed Using hypens instead of underscores for set-terminal-title command 2024-02-08 21:33:03 +00:00
dependabot[bot]
95993cf37e Bump assets/syntaxes/02_Extra/cmd-help from b150d84 to 209559b (#2841)
Bumps [assets/syntaxes/02_Extra/cmd-help](https://github.com/victor-gp/cmd-help-sublime-syntax) from `b150d84` to `209559b`.
- [Commits](b150d84534...209559b72f)

---
updated-dependencies:
- dependency-name: assets/syntaxes/02_Extra/cmd-help
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-08 07:54:17 +00:00
David Peter
3761df9112 Merge pull request #2849 from eth-p/readme-less-options-explained
Clarify when/how default args are added to less.
2024-02-08 08:29:20 +01:00
Ethan P.
adfaef19da Clarify when/how default args are added to less 2024-02-07 21:22:08 -08:00
dependabot[bot]
f7bea6de5b Bump clircle from 0.4.0 to 0.5.0 (#2845)
Bumps [clircle](https://github.com/niklasmohrin/clircle) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/niklasmohrin/clircle/releases)
- [Commits](https://github.com/niklasmohrin/clircle/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: clircle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 05:00:25 +00:00
dependabot[bot]
65aae5d0a1 Bump toml from 0.8.6 to 0.8.9 (#2844)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.6 to 0.8.9.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.6...toml-v0.8.9)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:43:10 +00:00
dependabot[bot]
e3866b1f7e Bump indexmap from 2.1.0 to 2.2.2 (#2847)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.1.0 to 2.2.2.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.1.0...2.2.2)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:25:19 +00:00
dependabot[bot]
23de8e093b Bump semver from 1.0.20 to 1.0.21 (#2843)
Bumps [semver](https://github.com/dtolnay/semver) from 1.0.20 to 1.0.21.
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.20...1.0.21)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 04:05:59 +00:00
dependabot[bot]
196a4cb18f Bump assets/themes/zenburn from e627f1c to 86d4ee7 (#2842)
Bumps [assets/themes/zenburn](https://github.com/colinta/zenburn) from `e627f1c` to `86d4ee7`.
- [Commits](e627f1cb22...86d4ee7a1f)

---
updated-dependencies:
- dependency-name: assets/themes/zenburn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-01 03:15:26 +00:00
Andy Kipp
695cf1f387 Update CHANGELOG.md 2024-01-31 14:12:00 +01:00
Andy Kipp
0af1df5258 Create xonsh.toml 2024-01-31 14:07:56 +01:00
Oliver looney
a8d07333e9 updated integration_tests.rs 2024-01-30 19:29:28 +00:00
Oliver looney
7f12989127 added set_terminal_title arg to clap_app.rs to fix ci errors 2024-01-29 09:47:41 +00:00
Oliver looney
60e32cf823 removed set_terminal_title arg from clap_app.rs since other boolean args aren't in clap_app.rs 2024-01-27 14:46:13 +00:00
Oliver looney
e9a6aaa30f cargo fmt 2024-01-27 14:44:54 +00:00
Oliver looney
9be2a36a01 fixed system wide config tests failing 2024-01-27 14:38:09 +00:00
Oliver looney
22254936a2 ran cargo fmt 2024-01-27 14:32:34 +00:00
Oliver looney
f6d76e0104 added integration test for setting terminal title 2024-01-27 14:31:28 +00:00
Oliver looney
c911829771 fixed failing tests 2024-01-27 14:23:24 +00:00
Oliver looney
b33e33fe26 terminal title is only set if user opts in with --set_terminal_title flag 2024-01-27 14:17:25 +00:00
Oliver looney
9239b125b1 added a flag to config for setting terminal title 2024-01-27 14:14:40 +00:00
David Peter
2086cd2668 Merge pull request #2835 from FilipRazek/fix-long-file-names
fix: Wrap file name in header
2024-01-21 20:17:34 +01:00
Filip Razek
1b88267320 Ensure test succeeds by adding flag 2024-01-21 19:48:26 +01:00
Filip Razek
e586751208 Attempt to fix integration test 2024-01-21 19:48:26 +01:00
Filip Razek
e7256a624b Refer to PR instead of issue in CHANGELOG 2024-01-21 19:48:26 +01:00
Filip Razek
5c1f47359e Run cargo fmt 2024-01-21 19:48:26 +01:00
Filip Razek
45ee2dc4c7 fix: Wrap file name in header 2024-01-21 19:48:26 +01:00
David Peter
db66e4459b Merge pull request #2755 from cyqsimon/syntax-mapping-refactor
More extensible syntax mapping mechanism
2024-01-21 19:43:24 +01:00
cyqsimon
55e02e101d Apply review suggestions to benchmark script 2024-01-22 00:25:23 +08:00
cyqsimon
230abfd2bc Merge branch 'master' into syntax-mapping-refactor 2024-01-19 15:18:48 +08:00
cyqsimon
c0f2d6f934 Add note on subdirectory's irrelevance to ordering 2024-01-18 23:26:00 +08:00
cyqsimon
9f36a7a284 Minor codestyle improvements 2024-01-18 23:07:11 +08:00
cyqsimon
e4d637a3d8 Reverse precedence of syntax mappings specified on CLI 2024-01-18 22:41:57 +08:00
rhysd
98a2b6bc17 Update changelog 2024-01-17 22:00:29 +01:00
rhysd
8e66bc8722 Make the error handler FnMut 2024-01-17 22:00:29 +01:00
dependabot[bot]
cd81c7fa6b Bump assets/syntaxes/02_Extra/SublimeJQ from 6870582 to b7e53e5 (#2820)
* Bump assets/syntaxes/02_Extra/SublimeJQ from `6870582` to `b7e53e5`

Bumps [assets/syntaxes/02_Extra/SublimeJQ](https://github.com/zogwarg/SublimeJQ) from `6870582` to `b7e53e5`.
- [Commits](687058289c...b7e53e5d86)

---
updated-dependencies:
- dependency-name: assets/syntaxes/02_Extra/SublimeJQ
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* update highlighted version of sample.jq

* update changelog

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Keith Hall <kingkeith+github@gmail.com>
2024-01-03 14:16:33 +00:00
Oliver Looney
b4fdb5dc36 Merge branch 'master' into 2783-setting-terminal-title 2024-01-01 09:45:53 +00:00
dependabot[bot]
c76ed99db2 Bump once_cell from 1.18.0 to 1.19.0 (#2826)
Bumps [once_cell](https://github.com/matklad/once_cell) from 1.18.0 to 1.19.0.
- [Changelog](https://github.com/matklad/once_cell/blob/master/CHANGELOG.md)
- [Commits](https://github.com/matklad/once_cell/compare/v1.18.0...v1.19.0)

---
updated-dependencies:
- dependency-name: once_cell
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 04:50:36 +00:00
dependabot[bot]
06aef22943 Bump home from 0.5.5 to 0.5.9 (#2823)
Bumps [home](https://github.com/rust-lang/cargo) from 0.5.5 to 0.5.9.
- [Changelog](https://github.com/rust-lang/cargo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cargo/commits)

---
updated-dependencies:
- dependency-name: home
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 04:37:23 +00:00
dependabot[bot]
128b0d6dd3 Bump thiserror from 1.0.50 to 1.0.53 (#2825)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.50 to 1.0.53.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.50...1.0.53)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 04:24:27 +00:00
dependabot[bot]
15dc20109f Bump clap from 4.4.10 to 4.4.12 (#2824)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.10 to 4.4.12.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.10...v4.4.12)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 04:10:40 +00:00
dependabot[bot]
5c4bcd6611 Bump anyhow from 1.0.75 to 1.0.78 (#2822)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.75 to 1.0.78.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.75...1.0.78)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-01 03:52:33 +00:00
Keith Hall
ecf4029dc7 Merge pull request #2819 from victor-gp/bump-cmd-help-syntax-b150d84
Bump cmd-help syntax to `b150d84`
2024-01-01 05:22:23 +02:00
Oliver looney
c261b41578 trying to fix failing system_wide_config.rs tests 2023-12-31 22:44:17 +00:00
Oliver Looney
6f1cc80d68 Merge branch 'master' into 2783-setting-terminal-title 2023-12-31 22:25:25 +00:00
Oliver looney
3b0ade9cb8 slightly changed set terminal command to match docs & broke print line into multiple variables 2023-12-31 22:24:44 +00:00
Oliver looney
57016f4e04 small refactoring of set terminal title function 2023-12-31 22:15:00 +00:00
Víctor González Prieto
497342fabb Bump cmd-help syntax to latest
Manual update (as opposed to Dependabot's) because the highlighting for
the test help message has changed. It's all good because it's as
intended, an improvement.

See victor-gp/cmd-help-sublime-syntax#23
2023-12-29 18:15:55 +01:00
David Tolnay
bf56cd90f0 Eliminate dependency on syn v1 by updating git-version 2023-12-29 08:37:11 +01:00
David Tolnay
0acb979e9e Pull in fix for unsafe-libyaml security advisory 2023-12-28 23:01:55 +01:00
Oliver Looney
d7503bfc09 Merge branch 'master' into 2783-setting-terminal-title 2023-12-27 16:14:04 +00:00
Martin Nordholts
b89dc15be1 Use proper Architecture for Debian packages built for musl
New values taken from

    dpkg-architecture -L
2023-12-27 17:03:40 +01:00
cyqsimon
15ab4478c9 Reduce unnecessary build deps 2023-12-22 19:55:49 +08:00
Oliver looney
5b4ce684a1 ran cargo fmt 2023-12-18 17:47:09 +00:00
Oliver looney
0027055a83 fixed system_wide_config.rs tests 2023-12-18 17:44:52 +00:00
Oliver looney
321b3ec81b updated CHANGELOG.md 2023-12-18 17:38:11 +00:00
Oliver looney
1679460f42 updated CHANGELOG.md 2023-12-18 17:32:08 +00:00
Oliver looney
907af9e35f updated tests since terminal title is set conditionally 2023-12-18 17:27:51 +00:00
Oliver looney
12b74dfb4e terminal title is only set when pager is being used 2023-12-18 16:59:12 +00:00
Oliver looney
fd84e4f49f fixed all but two failing tests. Last two tests are erroring because of IO circle detected error 2023-12-11 19:09:48 +00:00
cyqsimon
f0a6fe216d Benchmark prototype 2023-12-11 10:38:59 +08:00
cyqsimon
d792dc5804 Move rusty-fork to dev-dependencies 2023-12-11 10:21:07 +08:00
cyqsimon
8a08025091 Disable builtin_mappings_are_lazily_evaluated for ARM builds 2023-12-11 10:21:06 +08:00
cyqsimon
586c804b1e Add test: builtin_mappings_are_lazily_evaluated 2023-12-11 10:21:06 +08:00
cyqsimon
e30161ac3c Add test: builtin_mappings_matcher_only_compile_once 2023-12-11 10:21:06 +08:00
cyqsimon
3865908439 Add test: custom_mappings_precedence 2023-12-11 10:21:06 +08:00
cyqsimon
9474b4cf8b Rename & sort syntax mapping tests 2023-12-11 10:21:06 +08:00
cyqsimon
b48bda21a3 Add test: all_fixed_builtin_mappings_can_compile 2023-12-11 10:21:06 +08:00
cyqsimon
daf33709a0 Additional sanity check for matcher parser 2023-12-11 10:21:06 +08:00
cyqsimon
36073a3d95 Document another rule ignore condition 2023-12-11 10:21:06 +08:00
cyqsimon
12fa2cb1eb - is not a legal character in variable name 2023-12-11 10:21:06 +08:00
cyqsimon
1f10d846a3 Fix tests
1. for override test, test for something in `common`
2. move git mappings to `common`
2023-12-11 10:21:05 +08:00
cyqsimon
22531eab90 Port all mappings to new system 2023-12-11 10:21:05 +08:00
cyqsimon
0c1b80faab Impl per-target syntax mappings 2023-12-11 10:21:05 +08:00
cyqsimon
2c9bf229e1 Add doc comment for BUILTIN_MAPPINGS 2023-12-11 10:21:05 +08:00
cyqsimon
822e81bb24 Suppress clippy warning for MappingTarget in build script 2023-12-11 10:21:05 +08:00
cyqsimon
ad628c0471 Fix iterator lifetime incompatibility 2023-12-11 10:21:05 +08:00
cyqsimon
f483d2df42 Lazily compile GlobMatchers 2023-12-11 10:21:05 +08:00
cyqsimon
4ad3002543 s/Lazy<GlobMatcher>/Lazy<Option<GlobMatcher>>/ 2023-12-11 10:21:05 +08:00
cyqsimon
cfd622d6e1 Migrate SyntaxMapping impl to new system
Most existing builtins are removed but not yet ported, so the test
`user_can_override_builtin_mappings` should fail. It should pass once the old
rules have been ported.
2023-12-11 10:21:05 +08:00
cyqsimon
1c7c9a6b6d Split syntax_mapping/builtin.rs 2023-12-11 10:21:05 +08:00
cyqsimon
0c93ca80f4 Guard against duplicate matchers in build script 2023-12-11 10:21:04 +08:00
cyqsimon
de6d418d42 Remove BuiltinMatcher enum
Explanation added as comments in code
Using plain `Lazy<Option<GlobMatcher>>` is just better
2023-12-11 10:21:04 +08:00
cyqsimon
c016b462c0 STATIC_RULES => BUILTIN_MAPPINGS 2023-12-11 10:21:04 +08:00
cyqsimon
7e1fbcfe95 Fix visibility for SyntaxMapping::get_syntax_for
This change was made in 31fb708, but then was reverted (I assume incorrectly)
in 6246485.
2023-12-11 10:21:04 +08:00
cyqsimon
4815b6155e Write changelog 2023-12-11 10:21:03 +08:00
cyqsimon
075b5b288a Matcher parser errors when empty 2023-12-11 10:20:12 +08:00
cyqsimon
7cfd1e0d78 Rerun build script only when necessary 2023-12-11 10:20:12 +08:00
cyqsimon
9f7d70f642 Use regex for matcher parsing
- Somewhat better maintainability I think
2023-12-11 10:20:12 +08:00
cyqsimon
0fea82cff9 Minor improvement of variable naming accuracy 2023-12-11 10:20:11 +08:00
cyqsimon
64840fbbae join_segments -> build_glob_string
- this is a more descriptive name
2023-12-11 10:20:11 +08:00
cyqsimon
827b3eca2f Add unset *.conf example 2023-12-11 10:20:11 +08:00
cyqsimon
9478d2dfe8 Isolate variables at compile time 2023-12-11 10:20:11 +08:00
cyqsimon
d24501ab5e include! generated mappings in source 2023-12-11 10:20:11 +08:00
cyqsimon
9f4259721a Fix codegen type declaration 2023-12-11 10:20:11 +08:00
cyqsimon
77e491161c Ensure lexicographical ordering 2023-12-11 10:20:10 +08:00
cyqsimon
97780f987e MappingDefModel::into_mapping_list returns MappingList 2023-12-11 10:20:10 +08:00
cyqsimon
d1bc0ef0d4 Build script codegen initial impl 2023-12-11 10:20:10 +08:00
cyqsimon
52f94b4623 Clarify syntax selection algorithm behaviour 2023-12-11 10:20:10 +08:00
cyqsimon
37fd050100 Describe filename ordering more accurately 2023-12-11 10:20:10 +08:00
cyqsimon
83286975ff Add ordering prefix to git-config example 2023-12-11 10:20:10 +08:00
cyqsimon
f705fcb984 Add global git-config example 2023-12-11 10:20:10 +08:00
cyqsimon
9ca1f20f43 Add specification draft 2023-12-11 10:20:10 +08:00
Oliver looney
6ad800e43a tidied commits 2023-12-10 17:24:49 +00:00
Oliver looney
069318b139 fixed formatting of terminal title 2023-12-10 17:20:42 +00:00
Oliver looney
b9b554248d successfully setting the terminal title to bat's input's names 2023-12-10 17:17:19 +00:00
Oliver looney
4863d428dd title is being reset on quit, so no need to restore terminal title 2023-12-10 16:44:47 +00:00
Oliver looney
2e103ee6b3 able to set terminal title to hardcoded value 2023-12-10 16:39:34 +00:00
Lena
28990bc451 fix: display color when NO_COLOR is an empty string (#2767) 2023-12-02 11:43:55 +00:00
cyqsimon
748e2a681f Add columns=3 to repology badge 2023-12-01 21:39:25 +01:00
Broono Lu
bfa0b5241f Update Arch Linux package URL in README files (#2779) 2023-12-01 21:26:35 +01:00
Oliver looney
4af4bfc0f1 fixed syntax typo in russian readme 2023-12-01 20:59:52 +01:00
cyqsimon
51203ff750 Use GitHub API to get correct PR submitter (#2791) 2023-12-01 11:18:10 +00:00
dependabot[bot]
96cef9a24e Bump assets/themes/zenburn from 43dc527 to e627f1c (#2790)
Bumps [assets/themes/zenburn](https://github.com/colinta/zenburn) from `43dc527` to `e627f1c`.
- [Commits](43dc527731...e627f1cb22)

---
updated-dependencies:
- dependency-name: assets/themes/zenburn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 10:38:36 +00:00
Oliver Kiddle
b43d31b75a Improvements and fixes to zsh completion
* Use correct return status so that approximate completion isn't broken
* Follow zsh conventions on description forms
* Some options can be used multiple times, e.g. -H, -r, --file-name
* Set completion context correctly for the cache subcommand
* Better completion for --map-syntax argument
* Add --nonprintable-notation option
* Correct some of the mutual exclusion lists for options
2023-12-01 11:11:44 +01:00
dependabot[bot]
ad3ff26960 Bump serde from 1.0.190 to 1.0.193 (#2788)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.190 to 1.0.193.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.190...v1.0.193)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 04:15:35 +00:00
dependabot[bot]
86b40993c3 Bump grep-cli from 0.1.9 to 0.1.10 (#2789)
Bumps [grep-cli](https://github.com/BurntSushi/ripgrep) from 0.1.9 to 0.1.10.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/grep-cli-0.1.9...0.1.10)

---
updated-dependencies:
- dependency-name: grep-cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 04:01:10 +00:00
dependabot[bot]
31bed250ba Bump clap from 4.4.6 to 4.4.10 (#2786)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.6 to 4.4.10.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.6...v4.4.10)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-01 03:44:01 +00:00
Cosmic Horror
7658334645 Consider adding ?exclude_unsupported=1 to repology badge 2023-11-10 09:00:28 +01:00
cyqsimon
491ae70aa9 Write changelog 2023-11-06 06:02:59 +01:00
cyqsimon
d64c568196 Allow overriding {WARMUP,RUN}_COUNT using env 2023-11-06 06:02:59 +01:00
cyqsimon
b5982a6174 Allow setting number of runs in run-benchmarks.sh 2023-11-06 06:02:59 +01:00
Maria José Solano
04e7d2a313 fix(completions): add missing fish completions 2023-11-04 08:51:13 +01:00
cyqsimon
bcc2de86b4 CI: add changelog check (#2766)
* Added changelog item check

Fetch the base branch and diff correctly

- See: https://github.com/actions/checkout/issues/160

Improve organisation

Add some echo for easier debugging

Fix bad variable syntax

Clarify the double-print in the success case

* Write to changelog

* Don't block dependabot PRs
2023-11-03 08:53:29 +01:00
cyqsimon
1296aea836 Write changelog for #2756
- I always forget to write changelog.
2023-11-03 06:25:18 +01:00
cyqsimon
5498c24c33 Rerun build script only when necessary 2023-11-02 17:54:57 +01:00
cyqsimon
79a03b4299 Reorganise build script into modules 2023-11-02 17:54:57 +01:00
cyqsimon
f3a5e9a73c Use implicit format string capture syntax 2023-11-02 17:54:57 +01:00
cyqsimon
2710a19ecb Immutably construct variables map 2023-11-02 17:54:57 +01:00
cyqsimon
6d0ef259f6 Read env in build script at runtime 2023-11-02 17:54:57 +01:00
cyqsimon
b1577cc083 Reorganise functions in build script 2023-11-02 17:54:57 +01:00
cyqsimon
28d947fd8b Use anyhow in build script 2023-11-02 17:54:57 +01:00
cyqsimon
b000db8f32 Remove old completion generation code 2023-11-02 17:54:57 +01:00
dependabot[bot]
116a6cc9a8 Bump thiserror from 1.0.49 to 1.0.50 (#2758)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.49 to 1.0.50.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.49...1.0.50)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 04:58:49 +00:00
dependabot[bot]
c8291a36b7 Bump serde from 1.0.189 to 1.0.190 (#2760)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.189 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.189...v1.0.190)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 04:39:45 +00:00
dependabot[bot]
8180c76890 Bump plist from 1.5.0 to 1.5.1 (#2759)
Bumps [plist](https://github.com/ebarnard/rust-plist) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/ebarnard/rust-plist/releases)
- [Commits](https://github.com/ebarnard/rust-plist/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 04:20:25 +00:00
dependabot[bot]
a0f33b1cdc Bump globset from 0.4.10 to 0.4.13 (#2761)
Bumps [globset](https://github.com/BurntSushi/ripgrep) from 0.4.10 to 0.4.13.
- [Release notes](https://github.com/BurntSushi/ripgrep/releases)
- [Changelog](https://github.com/BurntSushi/ripgrep/blob/master/CHANGELOG.md)
- [Commits](https://github.com/BurntSushi/ripgrep/compare/globset-0.4.10...globset-0.4.13)

---
updated-dependencies:
- dependency-name: globset
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 03:53:53 +00:00
dependabot[bot]
8b60dae81c Bump tempfile from 3.8.0 to 3.8.1 (#2757)
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.8.0 to 3.8.1.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-01 03:26:48 +00:00
cyqsimon
4b33093f9e Bump edition to 2021 (#2748) 2023-10-31 04:54:06 +01:00
einfachIrgendwer0815
3d87b25b19 Add unicode/caret replacements for all control characters (#2712) 2023-10-20 19:41:48 +02:00
dependabot[bot]
f2f6902279 Bump syntect from 5.0.0 to 5.1.0 (#2742)
Bumps [syntect](https://github.com/trishume/syntect) from 5.0.0 to 5.1.0.
- [Release notes](https://github.com/trishume/syntect/releases)
- [Changelog](https://github.com/trishume/syntect/blob/master/CHANGELOG.md)
- [Commits](https://github.com/trishume/syntect/compare/v5.0.0...v5.1.0)

---
updated-dependencies:
- dependency-name: syntect
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 06:43:09 +00:00
dependabot[bot]
c0b17e73e1 Bump assert_cmd from 2.0.10 to 2.0.12 (#2741)
Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 2.0.10 to 2.0.12.
- [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/assert_cmd/compare/v2.0.10...v2.0.12)

---
updated-dependencies:
- dependency-name: assert_cmd
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 06:21:55 +00:00
dependabot[bot]
94544d963b Bump console from 0.15.5 to 0.15.7 (#2743)
Bumps [console](https://github.com/console-rs/console) from 0.15.5 to 0.15.7.
- [Changelog](https://github.com/console-rs/console/blob/master/CHANGELOG.md)
- [Commits](https://github.com/console-rs/console/compare/0.15.5...0.15.7)

---
updated-dependencies:
- dependency-name: console
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 05:54:24 +00:00
dependabot[bot]
72abbd22de Bump regex from 1.9.4 to 1.10.2 (#2740)
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.4 to 1.10.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.4...1.10.2)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 05:26:40 +00:00
dependabot[bot]
64e10ffb21 Bump semver from 1.0.17 to 1.0.20 (#2739)
Bumps [semver](https://github.com/dtolnay/semver) from 1.0.17 to 1.0.20.
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.17...1.0.20)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 05:01:07 +00:00
Martin Nordholts
35d8146bba Revert "Temporarily change dependabot to daily"
This reverts commit 86ac48d68e.
2023-10-19 06:33:48 +02:00
Martin Nordholts
a5a7ede698 release-checklist.md: Adapt to new auto-merge workflow 2023-10-18 21:33:44 +02:00
dependabot[bot]
b551049706 Bump nix from 0.26.2 to 0.26.4 (#2736)
Bumps [nix](https://github.com/nix-rust/nix) from 0.26.2 to 0.26.4.
- [Changelog](https://github.com/nix-rust/nix/blob/v0.26.4/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.26.2...v0.26.4)

---
updated-dependencies:
- dependency-name: nix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 16:48:05 +00:00
dependabot[bot]
99cfc13eab Bump serde from 1.0.163 to 1.0.189 (#2735)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.163 to 1.0.189.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.163...v1.0.189)

---
updated-dependencies:
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 16:28:33 +00:00
dependabot[bot]
4b0b5afa13 Bump wild from 2.1.0 to 2.2.0 (#2734)
Bumps [wild](https://gitlab.com/kornelski/wild) from 2.1.0 to 2.2.0.
- [Commits](https://gitlab.com/kornelski/wild/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: wild
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 16:06:35 +00:00
dependabot[bot]
d343428441 Bump run_script from 0.10.0 to 0.10.1 (#2737)
Bumps [run_script](https://github.com/sagiegurari/run_script) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/sagiegurari/run_script/releases)
- [Changelog](https://github.com/sagiegurari/run_script/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sagiegurari/run_script/compare/0.10.0...0.10.1)

---
updated-dependencies:
- dependency-name: run_script
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 15:43:17 +00:00
dependabot[bot]
16e409ec87 Bump walkdir from 2.3.3 to 2.4.0 (#2733)
Bumps [walkdir](https://github.com/BurntSushi/walkdir) from 2.3.3 to 2.4.0.
- [Commits](https://github.com/BurntSushi/walkdir/compare/2.3.3...2.4.0)

---
updated-dependencies:
- dependency-name: walkdir
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 15:11:17 +00:00
dependabot[bot]
94d059f258 Bump ansi_colours from 1.2.1 to 1.2.2 (#2729)
Bumps [ansi_colours](https://github.com/mina86/ansi_colours) from 1.2.1 to 1.2.2.
- [Commits](https://github.com/mina86/ansi_colours/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: ansi_colours
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 16:07:49 +00:00
dependabot[bot]
c8b9de889d Bump thiserror from 1.0.40 to 1.0.49 (#2730)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.40 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.40...1.0.49)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 15:42:03 +00:00
dependabot[bot]
75340d54f9 Bump flate2 from 1.0.27 to 1.0.28 (#2727)
Bumps [flate2](https://github.com/rust-lang/flate2-rs) from 1.0.27 to 1.0.28.
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.27...1.0.28)

---
updated-dependencies:
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 15:17:31 +00:00
dependabot[bot]
b28383e0fa Bump regex from 1.8.3 to 1.9.4 (#2726)
Bumps [regex](https://github.com/rust-lang/regex) from 1.8.3 to 1.9.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.8.3...1.9.4)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:50:40 +00:00
dependabot[bot]
8e866db281 Bump actions/checkout from 3 to 4 (#2719)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:13:22 +00:00
dependabot[bot]
0eb157e090 Bump unicode-width from 0.1.10 to 0.1.11 (#2716)
Bumps [unicode-width](https://github.com/unicode-rs/unicode-width) from 0.1.10 to 0.1.11.
- [Commits](https://github.com/unicode-rs/unicode-width/compare/v0.1.10...v0.1.11)

---
updated-dependencies:
- dependency-name: unicode-width
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 06:48:32 +00:00
dependabot[bot]
85636c28bc Bump predicates from 3.0.3 to 3.0.4 (#2717)
Bumps [predicates](https://github.com/assert-rs/predicates-rs) from 3.0.3 to 3.0.4.
- [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/predicates-rs/compare/v3.0.3...v3.0.4)

---
updated-dependencies:
- dependency-name: predicates
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 06:31:56 +00:00
dependabot[bot]
a70e5c6c65 Bump os_str_bytes from 6.4.1 to 6.6.1 (#2720)
Bumps [os_str_bytes](https://github.com/dylni/os_str_bytes) from 6.4.1 to 6.6.1.
- [Release notes](https://github.com/dylni/os_str_bytes/releases)
- [Commits](https://github.com/dylni/os_str_bytes/compare/6.4.1...6.6.1)

---
updated-dependencies:
- dependency-name: os_str_bytes
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 06:12:48 +00:00
dependabot[bot]
32e01f740b Bump plist from 1.4.3 to 1.5.0 (#2718)
Bumps [plist](https://github.com/ebarnard/rust-plist) from 1.4.3 to 1.5.0.
- [Release notes](https://github.com/ebarnard/rust-plist/releases)
- [Commits](https://github.com/ebarnard/rust-plist/compare/v1.4.3...v1.5.0)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 05:50:47 +00:00
dependabot[bot]
7b20f8fc7b Bump git2 from 0.18.0 to 0.18.1 (#2715)
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.18.0 to 0.18.1.
- [Changelog](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/git2-rs/compare/git2-0.18.0...git2-0.18.1)

---
updated-dependencies:
- dependency-name: git2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 05:00:01 +00:00
Martin Nordholts
86ac48d68e Temporarily change dependabot to daily
So we can more easily test that auto-merge works as it should. Once we
have confirmed it does, we revert this commit.
2023-10-17 06:36:36 +02:00
Martin Nordholts
c42fc810ea Setup auto-merge of dependabot PRs that pass CI 2023-10-17 06:18:51 +02:00
Martin Nordholts
6baebd79fa Add test for what jobs 'all-jobs' references 2023-10-13 05:24:25 +02:00
Martin Nordholts
c6cae09f99 CHANGELOG.md: Add new unreleased section 2023-10-11 20:43:52 +02:00
100 changed files with 3102 additions and 909 deletions

View File

@@ -0,0 +1,23 @@
# This workflow triggers auto-merge of any PR that dependabot creates so that
# PRs will be merged automatically without maintainer intervention if CI passes
name: Auto-merge dependabot PRs
on:
pull_request_target:
types: [opened]
jobs:
auto-merge:
if: github.repository == 'sharkdp/bat' && startsWith(github.head_ref, 'dependabot/')
runs-on: ubuntu-latest
environment:
name: auto-merge
url: https://github.com/sharkdp/bat/blob/main/.github/workflows/Auto-merge-dependabot-PRs.yml
env:
GITHUB_TOKEN: ${{ secrets.AUTO_MERGE_GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
- run: |
gh pr review ${{ github.event.pull_request.number }} --comment --body "If CI passes, this dependabot PR will be [auto-merged](https://github.com/sharkdp/bat/blob/main/.github/workflows/Auto-merge-dependabot-PRs.yml) 🚀"
- run: |
gh pr merge --auto --squash ${{ github.event.pull_request.number }}

View File

@@ -163,17 +163,17 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
job: job:
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: arm64, use-cross: true }
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, dpkg_arch: armhf, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, dpkg_arch: musl-linux-armhf, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2019 } - { target: i686-pc-windows-msvc , os: windows-2019, }
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: i686, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } - { target: i686-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-i686, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-12 } - { target: x86_64-apple-darwin , os: macos-12, }
- { target: x86_64-pc-windows-gnu , os: windows-2019 } - { target: x86_64-pc-windows-gnu , os: windows-2019, }
- { target: x86_64-pc-windows-msvc , os: windows-2019 } - { target: x86_64-pc-windows-msvc , os: windows-2019, }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: amd64, use-cross: true }
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-amd64, use-cross: true }
env: env:
BUILD_CMD: cargo BUILD_CMD: cargo
steps: steps:
@@ -337,16 +337,7 @@ jobs:
DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}
DPKG_ARCH="${{ matrix.job.dpkg_arch }}"
unset DPKG_ARCH
case ${{ matrix.job.target }} in
aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
arm-*-linux-*hf) DPKG_ARCH=armhf ;;
i686-*-linux-*) DPKG_ARCH=i686 ;;
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
*) DPKG_ARCH=notset ;;
esac;
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,33 @@
name: Changelog
on:
pull_request:
jobs:
check-changelog:
name: Check for changelog entry
runs-on: ubuntu-latest
# dependabot PRs are automerged if CI passes; we shouldn't block these
if: github.actor != 'dependabot[bot]'
env:
PR_NUMBER: ${{ github.event.number }}
PR_BASE: ${{ github.base_ref }}
steps:
- uses: actions/checkout@v4
- name: Fetch PR base
run: git fetch --no-tags --prune --depth=1 origin
# cannot use `github.actor`: the triggering commit may be authored by a maintainer
- name: Get PR submitter
id: get-submitter
run: curl -sSfL https://api.github.com/repos/sharkdp/bat/pulls/${PR_NUMBER} | jq -r '"submitter=" + .user.login' | tee -a $GITHUB_OUTPUT
- name: Search for added line in changelog
env:
PR_SUBMITTER: ${{ steps.get-submitter.outputs.submitter }}
run: |
ADDED=$(git diff -U0 "origin/${PR_BASE}" HEAD -- CHANGELOG.md | grep -P '^\+[^\+].+$')
echo "Added lines in CHANGELOG.md:"
echo "$ADDED"
echo "Grepping for PR info (see CONTRIBUTING.md):"
grep "#${PR_NUMBER}\\b.*@${PR_SUBMITTER}\\b" <<< "$ADDED"

View File

@@ -1,3 +1,61 @@
# unreleased
## Features
- Set terminal title to file names when Paging is not Paging::Never #2807 (@Oliver-Looney)
- `bat --squeeze-blank`/`bat -s` will now squeeze consecutive empty lines, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
- `bat --squeeze-limit` to set the maximum number of empty consecutive when using `--squeeze-blank`, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
- `PrettyPrinter::squeeze_empty_lines` to support line squeezing for bat as a library, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
## Bugfixes
- Fix long file name wrapping in header, see #2835 (@FilipRazek)
- Fix `NO_COLOR` support, see #2767 (@acuteenvy)
- Fix handling of inputs with OSC ANSI escape sequences, see #2541 and #2544 (@eth-p)
- Fix handling of inputs with combined ANSI color and attribute sequences, see #2185 and #2856 (@eth-p)
- Fix panel width when line 10000 wraps, see #2854 (@eth-p)
## Other
- Upgrade to Rust 2021 edition #2748 (@cyqsimon)
- Refactor and cleanup build script #2756 (@cyqsimon)
- Checks changelog has been written to for PRs in CI #2766 (@cyqsimon)
- Use GitHub API to get correct PR submitter #2791 (@cyqsimon)
- Minor benchmark script improvements #2768 (@cyqsimon)
- Update Arch Linux package URL in README files #2779 (@brunobell)
- Update and improve `zsh` completion, see #2772 (@okapia)
- More extensible syntax mapping mechanism #2755 (@cyqsimon)
- Use proper Architecture for Debian packages built for musl, see #2811 (@Enselic)
- Pull in fix for unsafe-libyaml security advisory, see #2812 (@dtolnay)
- Update git-version dependency to use Syn v2, see #2816 (@dtolnay)
- Update git2 dependency to v0.18.2, see #2852 (@eth-p)
- Improve performance when color output disabled, see #2397 and #2857 (@eth-p)
- Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon)
- Apply clippy fixes #2864 (@cyqsimon)
- Faster startup by offloading glob matcher building to a worker thread #2868 (@cyqsimon)
## Syntaxes
- `cmd-help`: scope subcommands followed by other terms, and other misc improvements, see #2819 (@victor-gp)
- Upgrade JQ syntax, see #2820 (@dependabot[bot])
- Add syntax mapping for quadman quadlets #2866 (@cyqsimon)
- Map containers .conf files to TOML syntax #2867 (@cyqsimon)
- Associate `xsh` files with `xonsh` syntax that is Python, see #2840 (@anki-code).
- Added auto detect syntax for `.jsonc` #2795 (@mxaddict)
- Added auto detect syntax for `.aws/{config,credentials}` #2795 (@mxaddict)
- Add syntax mapping for Wireguard config #2874 (@cyqsimon)
## Themes
## `bat` as a library
- Changes to `syntax_mapping::SyntaxMapping` #2755 (@cyqsimon)
- `SyntaxMapping::get_syntax_for` is now correctly public
- [BREAKING] `SyntaxMapping::{empty,builtin}` are removed; use `SyntaxMapping::new` instead
- [BREAKING] `SyntaxMapping::mappings` is replaced by `SyntaxMapping::{builtin,custom,all}_mappings`
- Make `Controller::run_with_error_handler`'s error handler `FnMut`, see #2831 (@rhysd)
- Improve compile time by 20%, see #2815 (@dtolnay)
# v0.24.0 # v0.24.0
## Features ## Features

View File

@@ -6,21 +6,42 @@ Thank you for considering to contribute to `bat`!
## Add an entry to the changelog ## Add an entry to the changelog
If your contribution changes the behavior of `bat` (as opposed to a typo-fix Keeping the [`CHANGELOG.md`](CHANGELOG.md) file up-to-date makes the release
in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file process much easier and therefore helps to get your changes into a new `bat`
and describe your changes. This makes the release process much easier and release faster. However, not every change to the repository requires a
therefore helps to get your changes into a new `bat` release faster. changelog entry. Below are a few examples of that.
Please update the changelog if your contribution contains changes regarding
any of the following:
- the behavior of `bat`
- syntax mappings
- syntax definitions
- themes
- the build system, linting, or CI workflows
A changelog entry is not necessary when:
- updating documentation
- fixing typos
>[!NOTE]
> For PRs, a CI workflow verifies that a suitable changelog entry is
> added. If such an entry is missing, the workflow will fail. If your
> changes do not need an entry to the changelog (see above), that
> workflow failure can be disregarded.
### Changelog entry format
The top of the `CHANGELOG` contains a *"unreleased"* section with a few The top of the `CHANGELOG` contains a *"unreleased"* section with a few
subsections (Features, Bugfixes, …). Please add your entry to the subsection subsections (Features, Bugfixes, …). Please add your entry to the subsection
that best describes your change. that best describes your change.
Entries follow this format: Entries must follow this format:
``` ```
- Short description of what has been changed, see #123 (@user) - Short description of what has been changed, see #123 (@user)
``` ```
Here, `#123` is the number of the original issue and/or your pull request. Please replace `#123` with the number of your pull request (not issue) and
Please replace `@user` by your GitHub username. `@user` by your GitHub username.
## Development ## Development

617
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ name = "bat"
repository = "https://github.com/sharkdp/bat" repository = "https://github.com/sharkdp/bat"
version = "0.24.0" version = "0.24.0"
exclude = ["assets/syntaxes/*", "assets/themes/*"] exclude = ["assets/syntaxes/*", "assets/themes/*"]
build = "build.rs" build = "build/main.rs"
edition = '2018' edition = '2021'
rust-version = "1.70" rust-version = "1.70"
[features] [features]
@@ -41,32 +41,33 @@ regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine
regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine
[dependencies] [dependencies]
nu-ansi-term = "0.49.0" nu-ansi-term = "0.50.0"
ansi_colours = "^1.2" ansi_colours = "^1.2"
bincode = "1.0" bincode = "1.0"
console = "0.15.5" console = "0.15.7"
flate2 = "1.0" flate2 = "1.0"
once_cell = "1.18" once_cell = "1.19"
thiserror = "1.0" thiserror = "1.0"
wild = { version = "2.1", optional = true } wild = { version = "2.2", optional = true }
content_inspector = "0.2.4" content_inspector = "0.2.4"
shell-words = { version = "1.1.0", optional = true } shell-words = { version = "1.1.0", optional = true }
unicode-width = "0.1.10" unicode-width = "0.1.11"
globset = "0.4" globset = "0.4"
serde = { version = "1.0", features = ["derive"] } serde = "1.0"
serde_yaml = "0.9" serde_derive = "1.0"
serde_yaml = "0.9.28"
semver = "1.0" semver = "1.0"
path_abs = { version = "0.5", default-features = false } path_abs = { version = "0.5", default-features = false }
clircle = "0.4" clircle = "0.5"
bugreport = { version = "0.5.0", optional = true } bugreport = { version = "0.5.0", optional = true }
etcetera = { version = "0.8.0", optional = true } etcetera = { version = "0.8.0", optional = true }
grep-cli = { version = "0.1.9", optional = true } grep-cli = { version = "0.1.10", optional = true }
regex = { version = "1.8.3", optional = true } regex = { version = "1.10.2", optional = true }
walkdir = { version = "2.3", optional = true } walkdir = { version = "2.4", optional = true }
bytesize = { version = "1.3.0" } bytesize = { version = "1.3.0" }
encoding_rs = "0.8.33" encoding_rs = "0.8.33"
os_str_bytes = { version = "~6.4", optional = true } os_str_bytes = { version = "~6.6", optional = true }
run_script = { version = "^0.10.0", optional = true} run_script = { version = "^0.10.1", optional = true}
[dependencies.git2] [dependencies.git2]
version = "0.18" version = "0.18"
@@ -74,32 +75,45 @@ optional = true
default-features = false default-features = false
[dependencies.syntect] [dependencies.syntect]
version = "5.0.0" version = "5.2.0"
default-features = false default-features = false
features = ["parsing"] features = ["parsing"]
[dependencies.clap] [dependencies.clap]
version = "4.4.6" version = "4.4.12"
optional = true optional = true
features = ["wrap_help", "cargo"] features = ["wrap_help", "cargo"]
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
home = "0.5.4" home = "0.5.9"
plist = "1.4.3" plist = "1.6.0"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.10" assert_cmd = "2.0.12"
expect-test = "1.4.1" expect-test = "1.4.1"
serial_test = { version = "2.0.0", default-features = false } serial_test = { version = "2.0.0", default-features = false }
predicates = "3.0.3" predicates = "3.0.4"
wait-timeout = "0.2.0" wait-timeout = "0.2.0"
tempfile = "3.8.0" tempfile = "3.8.1"
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
nix = { version = "0.26.2", default-features = false, features = ["term"] } nix = { version = "0.26.4", default-features = false, features = ["term"] }
[build-dependencies]
anyhow = "1.0.78"
indexmap = { version = "2.2.2", features = ["serde"] }
itertools = "0.12.1"
once_cell = "1.18"
regex = "1.10.2"
serde = "1.0"
serde_derive = "1.0"
serde_with = { version = "3.6.1", default-features = false, features = ["macros"] }
toml = { version = "0.8.9", features = ["preserve_order"] }
walkdir = "2.4"
[build-dependencies.clap] [build-dependencies.clap]
version = "4.4.6" version = "4.4.12"
optional = true optional = true
features = ["wrap_help", "cargo"] features = ["wrap_help", "cargo"]

View File

@@ -254,7 +254,7 @@ Please report any issues with the help syntax in [this repository](https://githu
## Installation ## Installation
[![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg)](https://repology.org/project/bat-cat/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/bat-cat/versions)
### On Ubuntu (using `apt`) ### On Ubuntu (using `apt`)
*... and other Debian-based Linux distributions.* *... and other Debian-based Linux distributions.*
@@ -296,7 +296,7 @@ apk add bat
### On Arch Linux ### On Arch Linux
You can install [the `bat` package](https://www.archlinux.org/packages/community/x86_64/bat/) You can install [the `bat` package](https://www.archlinux.org/packages/extra/x86_64/bat/)
from the official sources: from the official sources:
```bash ```bash
@@ -602,7 +602,8 @@ set, `less` is used by default. If you want to use a different pager, you can ei
`PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in `PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in
`PAGER`. `PAGER`.
**Note**: If `PAGER` is `more` or `most`, `bat` will silently use `less` instead to ensure support for colors. >[!NOTE]
> If `PAGER` is `more` or `most`, `bat` will silently use `less` instead to ensure support for colors.
If you want to pass command-line arguments to the pager, you can also set them via the If you want to pass command-line arguments to the pager, you can also set them via the
`PAGER`/`BAT_PAGER` variables: `PAGER`/`BAT_PAGER` variables:
@@ -613,20 +614,37 @@ export BAT_PAGER="less -RF"
Instead of using environment variables, you can also use `bat`s [configuration file](https://github.com/sharkdp/bat#configuration-file) to configure the pager (`--pager` option). Instead of using environment variables, you can also use `bat`s [configuration file](https://github.com/sharkdp/bat#configuration-file) to configure the pager (`--pager` option).
**Note**: By default, if the pager is set to `less` (and no command-line options are specified),
`bat` will pass the following command line options to the pager: `-R`/`--RAW-CONTROL-CHARS`,
`-F`/`--quit-if-one-screen` and `-X`/`--no-init`. The last option (`-X`) is only used for `less`
versions older than 530.
The `-R` option is needed to interpret ANSI colors correctly. The second option (`-F`) instructs ### Using `less` as a pager
less to exit immediately if the output size is smaller than the vertical size of the terminal.
This is convenient for small files because you do not have to press `q` to quit the pager. The
third option (`-X`) is needed to fix a bug with the `--quit-if-one-screen` feature in old versions
of `less`. Unfortunately, it also breaks mouse-wheel support in `less`.
If you want to enable mouse-wheel scrolling on older versions of `less`, you can pass just `-R` (as When using `less` as a pager, `bat` will automatically pass extra options along to `less`
in the example above, this will disable the quit-if-one-screen feature). For less 530 or newer, to improve the experience. Specifically, `-R`/`--RAW-CONTROL-CHARS`, `-F`/`--quit-if-one-screen`,
it should work out of the box. and under certain conditions, `-X`/`--no-init` and/or `-S`/`--chop-long-lines`.
>[!IMPORTANT]
> These options will not be added if:
> - The pager is not named `less`.
> - The `--pager` argument contains any command-line arguments (e.g. `--pager="less -R"`).
> - The `BAT_PAGER` environment variable contains any command-line arguments (e.g. `export BAT_PAGER="less -R"`)
>
> The `--quit-if-one-screen` option will not be added when:
> - The `--paging=always` argument is used.
> - The `BAT_PAGING` environment is set to `always`.
The `-R` option is needed to interpret ANSI colors correctly.
The `-F` option instructs `less` to exit immediately if the output size is smaller than
the vertical size of the terminal. This is convenient for small files because you do not
have to press `q` to quit the pager.
The `-X` option is needed to fix a bug with the `--quit-if-one-screen` feature in versions
of `less` older than version 530. Unfortunately, it also breaks mouse-wheel support in `less`.
If you want to enable mouse-wheel scrolling on older versions of `less` and do not mind losing
the quit-if-one-screen feature, you can set the pager (via `--pager` or `BAT_PAGER`) to `less -R`.
For `less` 530 or newer, it should work out of the box.
The `-S` option is added when `bat`'s `-S`/`--chop-long-lines` option is used. This tells `less`
to truncate any lines larger than the terminal width.
### Indentation ### Indentation

View File

@@ -147,6 +147,8 @@ complete -c $bat -s d -l diff -d "Only show lines with Git changes" -n __bat_no_
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 $bat -l diff-context -x -d "Show N context lines around Git changes" -n "__fish_seen_argument -s d -l diff"
complete -c $bat -l generate-config-file -f -d "Generates a default configuration file" -n __fish_is_first_arg
complete -c $bat -l file-name -x -d "Specify the display name" -n __bat_no_excl_args complete -c $bat -l file-name -x -d "Specify the display name" -n __bat_no_excl_args
complete -c $bat -s f -l force-colorization -d "Force color and decorations" -n __bat_no_excl_args complete -c $bat -s f -l force-colorization -d "Force color and decorations" -n __bat_no_excl_args
@@ -173,6 +175,12 @@ complete -c $bat -l list-themes -f -d "List syntax highlighting themes" -n __fis
complete -c $bat -s m -l map-syntax -x -a "(__bat_complete_map_syntax)" -d "Map <glob pattern>:<language syntax>" -n __bat_no_excl_args complete -c $bat -s m -l map-syntax -x -a "(__bat_complete_map_syntax)" -d "Map <glob pattern>:<language syntax>" -n __bat_no_excl_args
complete -c $bat -l no-config -d "Do not use the configuration file"
complete -c $bat -l no-custom-assets -d "Do not load custom assets"
complete -c $bat -l no-lessopen -d "Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)"
complete -c $bat -s n -l number -d "Only show line numbers, no other decorations" -n __bat_no_excl_args complete -c $bat -s n -l number -d "Only show line numbers, no other decorations" -n __bat_no_excl_args
complete -c $bat -l pager -x -a "$pager_opts" -d "Which pager to use" -n __bat_no_excl_args complete -c $bat -l pager -x -a "$pager_opts" -d "Which pager to use" -n __bat_no_excl_args

View File

@@ -1,19 +1,20 @@
#compdef {{PROJECT_EXECUTABLE}} #compdef {{PROJECT_EXECUTABLE}}
local context state state_descr line local curcontext="$curcontext" ret=1
local -a state state_descr line
typeset -A opt_args typeset -A opt_args
(( $+functions[_{{PROJECT_EXECUTABLE}}_cache_subcommand] )) || (( $+functions[_{{PROJECT_EXECUTABLE}}_cache_subcommand] )) ||
_{{PROJECT_EXECUTABLE}}_cache_subcommand() { _{{PROJECT_EXECUTABLE}}_cache_subcommand() {
local -a args local -a args
args=( args=(
'(-b --build -c --clear)'{-b,--build}'[Initialize or update the syntax/theme cache]' '(-b --build -c --clear)'{-b,--build}'[initialize or update the syntax/theme cache]'
'(-b --build -c --clear)'{-c,--clear}'[Remove the cached syntax definitions and themes]' '(-b --build -c --clear)'{-c,--clear}'[remove the cached syntax definitions and themes]'
'(--source)'--source='[Use a different directory to load syntaxes and themes from]:directory:_files -/' --source='[specify directory to load syntaxes and themes from]:directory:_files -/'
'(--target)'--target='[Use a different directory to store the cached syntax and theme set]:directory:_files -/' --target='[specify directory to store the cached syntax and theme set in]:directory:_files -/'
'(--blank)'--blank'[Create completely new syntax and theme sets]' --blank'[create completely new syntax and theme sets]'
'(: -)'{-h,--help}'[Prints help information]' --acknowledgements'[build acknowledgements.bin]'
'*: :' '(: -)'{-h,--help}'[show help information]'
) )
_arguments -S -s $args _arguments -S -s $args
@@ -23,69 +24,79 @@ _{{PROJECT_EXECUTABLE}}_cache_subcommand() {
_{{PROJECT_EXECUTABLE}}_main() { _{{PROJECT_EXECUTABLE}}_main() {
local -a args local -a args
args=( args=(
'(-A --show-all)'{-A,--show-all}'[Show non-printable characters (space, tab, newline, ..)]' '(-A --show-all)'{-A,--show-all}'[show non-printable characters (space, tab, newline, ..)]'
'*'{-p,--plain}'[Show plain style (alias for `--style=plain`), repeat twice to disable disable automatic paging (alias for `--paging=never`)]' --nonprintable-notation='[specify how to display non-printable characters when using --show-all]:notation:(caret unicode)'
'(-l --language)'{-l+,--language=}'[Set the language for syntax highlighting]:<language>:->language' \*{-p,--plain}'[show plain style (alias for `--style=plain`), repeat twice to disable disable automatic paging (alias for `--paging=never`)]'
'(-H --highlight-line)'{-H,--highlight-line}'[Highlight lines N through M]:<N\:M>...' '(-l --language)'{-l+,--language=}'[set the language for syntax highlighting]:language:->languages'
'(--file-name)'--file-name'[Specify the name to display for a file]:<name>...:_files' \*{-H+,--highlight-line=}'[highlight specified block of lines]:start\:end'
'(-d --diff)'--diff'[Only show lines that have been added/removed/modified]' \*--file-name='[specify the name to display for a file]:name:_files'
'(--diff-context)'--diff-context'[Include N lines of context around added/removed/modified lines when using `--diff`]:<N> (lines):()' '(-d --diff)'--diff'[only show lines that have been added/removed/modified]'
'(--tabs)'--tabs'[Set the tab width to T spaces]:<T> (tab width):()' --diff-context='[specify lines of context around added/removed/modified lines when using `--diff`]:lines'
'(--wrap)'--wrap='[Specify the text-wrapping mode]:<when>:(auto never character)' --tabs='[set the tab width]:tab width [4]'
'(--terminal-width)'--terminal-width'[Explicitly set the width of the terminal instead of determining it automatically]:<width>' --wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character)'
'(-n --number)'{-n,--number}'[Show line numbers]' '!(--wrap)'{-S,--chop-long-lines}
'(--color)'--color='[When to use colors]:<when>:(auto never always)' --terminal-width='[explicitly set the width of the terminal instead of determining it automatically]:width'
'(--italic-text)'--italic-text='[Use italics in output]:<when>:(always never)' '(-n --number --diff --diff-context)'{-n,--number}'[show line numbers]'
'(--decorations)'--decorations='[When to show the decorations]:<when>:(auto never always)' --color='[specify when to use colors]:when:(auto never always)'
'(--paging)'--paging='[Specify when to use the pager]:<when>:(auto never always)' --italic-text='[use italics in output]:when:(always never)'
'(-m --map-syntax)'{-m+,--map-syntax=}'[Use the specified syntax for files matching the glob pattern]:<glob\:syntax>...' --decorations='[specify when to show the decorations]:when:(auto never always)'
'(--theme)'--theme='[Set the color theme for syntax highlighting]:<theme>:->theme' --paging='[specify when to use the pager]:when:(auto never always)'
'(: --list-themes --list-languages -L)'--list-themes'[Display all supported highlighting themes]' '(-m --map-syntax)'{-m+,--map-syntax=}'[map a glob pattern to an existing syntax name]: :->syntax-maps'
'(--style)'--style='[Comma-separated list of style elements to display]:<components>:->style' '(--theme)'--theme='[set the color theme for syntax highlighting]:theme:->themes'
'(-r --line-range)'{-r+,--line-range=}'[Only print the lines from N to M]:<N\:M>...' '(: --list-themes --list-languages -L)'--list-themes'[show all supported highlighting themes]'
'(: --list-themes --list-languages -L)'{-L,--list-languages}'[Display all supported languages]' --style='[comma-separated list of style elements to display]: : _values "style [default]"
'(: --no-config)'--no-config'[Do not use the configuration file]' default auto full plain changes header header-filename header-filesize grid rule numbers snip'
'(: --no-custom-assets)'--no-custom-assets'[Do not load custom assets]' \*{-r+,--line-range=}'[only print the specified line range]:start\:end'
'(: --lessopen)'--lessopen'[Enable the $LESSOPEN preprocessor]' '(* -)'{-L,--list-languages}'[display all supported languages]'
'(: --no-lessopen)'--no-lessopen'[Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)]' "--no-config[don't use the configuration file]"
'(: --config-dir)'--config-dir'[Show bat'"'"'s configuration directory]' "--no-custom-assets[don't load custom assets]"
'(: --config-file)'--config-file'[Show path to the configuration file]' '(--no-lessopen)'--lessopen'[enable the $LESSOPEN preprocessor]'
'(: --generate-config-file)'--generate-config-file'[Generates a default configuration file]' '(--lessopen)'--no-lessopen'[disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)]'
'(: --cache-dir)'--cache-dir'[Show bat'"'"'s cache directory]' '(* -)'--config-dir"[show bat's configuration directory]"
'(: -)'{-h,--help}'[Print this help message]' '(* -)'--config-file'[show path to the configuration file]'
'(: -)'{-V,--version}'[Show version information]' '(* -)'--generate-config-file'[generate a default configuration file]'
'*: :_files' '(* -)'--cache-dir"[show bat's cache directory]"
'(* -)'{-h,--help}'[show help information]'
'(* -)'{-V,--version}'[show version information]'
'*: :{ _files || compadd cache }'
) )
_arguments -S -s $args _arguments -S -s $args && ret=0
case "$state" in case "$state" in
language) syntax-maps)
if ! compset -P '*:'; then
_message -e patterns 'glob pattern:language'
return
fi
;& # fall-through
languages)
local IFS=$'\n' local IFS=$'\n'
local -a languages local -a languages
languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') ) languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
_describe 'language' languages _describe 'language' languages && ret=0
;; ;;
theme) themes)
local IFS=$'\n' local -a themes expl
local -a themes themes=( ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
themes=( $({{PROJECT_EXECUTABLE}} --list-themes | sort) )
_values 'theme' $themes _wanted themes expl 'theme' compadd -a themes && ret=0
;;
style)
_values -s , 'style' default auto full plain changes header header-filename header-filesize grid rule numbers snip
;; ;;
esac esac
return ret
} }
case $words[2] in case $words[2] in
cache) cache)
## Completion of the 'cache' command itself is removed for better UX ## Completion of the 'cache' command itself is removed for better UX
## See https://github.com/sharkdp/bat/issues/2085#issuecomment-1271646802 ## See https://github.com/sharkdp/bat/issues/2085#issuecomment-1271646802
shift words
(( CURRENT-- ))
curcontext="${curcontext%:*}-${words[1]}:"
_{{PROJECT_EXECUTABLE}}_cache_subcommand _{{PROJECT_EXECUTABLE}}_cache_subcommand
;; ;;

108
build.rs
View File

@@ -1,108 +0,0 @@
// TODO: Re-enable generation of shell completion files (below) when clap 3 is out.
// For more details, see https://github.com/sharkdp/bat/issues/372
// For bat-as-a-library, no build script is required. The build script is for
// the manpage and completions, which are only relevant to the bat application.
#[cfg(not(feature = "application"))]
fn main() {}
#[cfg(feature = "application")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::path::Path;
// Read environment variables.
let project_name = option_env!("PROJECT_NAME").unwrap_or("bat");
let executable_name = option_env!("PROJECT_EXECUTABLE").unwrap_or(project_name);
let executable_name_uppercase = executable_name.to_uppercase();
static PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Generates a file from a template.
fn template(
variables: &HashMap<&str, &str>,
in_file: &str,
out_file: impl AsRef<Path>,
) -> Result<(), Box<dyn Error>> {
let mut content = fs::read_to_string(in_file)?;
for (variable_name, value) in variables {
// Replace {{variable_name}} by the value
let pattern = format!("{{{{{variable_name}}}}}", variable_name = variable_name);
content = content.replace(&pattern, value);
}
fs::write(out_file, content)?;
Ok(())
}
let mut variables = HashMap::new();
variables.insert("PROJECT_NAME", project_name);
variables.insert("PROJECT_EXECUTABLE", executable_name);
variables.insert("PROJECT_EXECUTABLE_UPPERCASE", &executable_name_uppercase);
variables.insert("PROJECT_VERSION", PROJECT_VERSION);
let out_dir_env = std::env::var_os("BAT_ASSETS_GEN_DIR")
.or_else(|| std::env::var_os("OUT_DIR"))
.expect("BAT_ASSETS_GEN_DIR or OUT_DIR to be set in build.rs");
let out_dir = Path::new(&out_dir_env);
fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
template(
&variables,
"assets/manual/bat.1.in",
out_dir.join("assets/manual/bat.1"),
)?;
template(
&variables,
"assets/completions/bat.bash.in",
out_dir.join("assets/completions/bat.bash"),
)?;
template(
&variables,
"assets/completions/bat.fish.in",
out_dir.join("assets/completions/bat.fish"),
)?;
template(
&variables,
"assets/completions/_bat.ps1.in",
out_dir.join("assets/completions/_bat.ps1"),
)?;
template(
&variables,
"assets/completions/bat.zsh.in",
out_dir.join("assets/completions/bat.zsh"),
)?;
Ok(())
}
// #[macro_use]
// extern crate clap;
// use clap::Shell;
// use std::fs;
// include!("src/clap_app.rs");
// const BIN_NAME: &str = "bat";
// fn main() {
// let outdir = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
// let outdir = match outdir {
// None => return,
// Some(outdir) => outdir,
// };
// fs::create_dir_all(&outdir).unwrap();
// let mut app = build_app(true);
// app.gen_completions(BIN_NAME, Shell::Bash, &outdir);
// app.gen_completions(BIN_NAME, Shell::Fish, &outdir);
// app.gen_completions(BIN_NAME, Shell::Zsh, &outdir);
// app.gen_completions(BIN_NAME, Shell::PowerShell, &outdir);
// }

67
build/application.rs Normal file
View File

@@ -0,0 +1,67 @@
use std::{env, fs, path::PathBuf};
use crate::util::render_template;
/// Generate manpage and shell completions for the bat application.
pub fn gen_man_and_comp() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=assets/manual/");
println!("cargo:rerun-if-changed=assets/completions/");
println!("cargo:rerun-if-env-changed=PROJECT_NAME");
println!("cargo:rerun-if-env-changed=PROJECT_EXECUTABLE");
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
println!("cargo:rerun-if-env-changed=BAT_ASSETS_GEN_DIR");
// Read environment variables.
let project_name = env::var("PROJECT_NAME").unwrap_or("bat".into());
let executable_name = env::var("PROJECT_EXECUTABLE").unwrap_or(project_name.clone());
let executable_name_uppercase = executable_name.to_uppercase();
let project_version = env::var("CARGO_PKG_VERSION")?;
let variables = [
("PROJECT_NAME", project_name),
("PROJECT_EXECUTABLE", executable_name),
("PROJECT_EXECUTABLE_UPPERCASE", executable_name_uppercase),
("PROJECT_VERSION", project_version),
]
.into_iter()
.collect();
let Some(out_dir) = env::var_os("BAT_ASSETS_GEN_DIR")
.or_else(|| env::var_os("OUT_DIR"))
.map(PathBuf::from)
else {
anyhow::bail!("BAT_ASSETS_GEN_DIR or OUT_DIR should be set for build.rs");
};
fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
render_template(
&variables,
"assets/manual/bat.1.in",
out_dir.join("assets/manual/bat.1"),
)?;
render_template(
&variables,
"assets/completions/bat.bash.in",
out_dir.join("assets/completions/bat.bash"),
)?;
render_template(
&variables,
"assets/completions/bat.fish.in",
out_dir.join("assets/completions/bat.fish"),
)?;
render_template(
&variables,
"assets/completions/_bat.ps1.in",
out_dir.join("assets/completions/_bat.ps1"),
)?;
render_template(
&variables,
"assets/completions/bat.zsh.in",
out_dir.join("assets/completions/bat.zsh"),
)?;
Ok(())
}

17
build/main.rs Normal file
View File

@@ -0,0 +1,17 @@
#[cfg(feature = "application")]
mod application;
mod syntax_mapping;
mod util;
fn main() -> anyhow::Result<()> {
// only watch manually-designated files
// see: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
println!("cargo:rerun-if-changed=build/");
syntax_mapping::build_static_mappings()?;
#[cfg(feature = "application")]
application::gen_man_and_comp()?;
Ok(())
}

294
build/syntax_mapping.rs Normal file
View File

@@ -0,0 +1,294 @@
use std::{
convert::Infallible,
env, fs,
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{anyhow, bail};
use indexmap::IndexMap;
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use serde_derive::Deserialize;
use serde_with::DeserializeFromStr;
use walkdir::WalkDir;
/// Known mapping targets.
///
/// Corresponds to `syntax_mapping::MappingTarget`.
#[allow(clippy::enum_variant_names)]
#[derive(Clone, Debug, Eq, PartialEq, Hash, DeserializeFromStr)]
pub enum MappingTarget {
MapTo(String),
MapToUnknown,
MapExtensionToUnknown,
}
impl FromStr for MappingTarget {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"MappingTarget::MapToUnknown" => Ok(Self::MapToUnknown),
"MappingTarget::MapExtensionToUnknown" => Ok(Self::MapExtensionToUnknown),
syntax => Ok(Self::MapTo(syntax.into())),
}
}
}
impl MappingTarget {
fn codegen(&self) -> String {
match self {
Self::MapTo(syntax) => format!(r###"MappingTarget::MapTo(r#"{syntax}"#)"###),
Self::MapToUnknown => "MappingTarget::MapToUnknown".into(),
Self::MapExtensionToUnknown => "MappingTarget::MapExtensionToUnknown".into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)]
/// A single matcher.
///
/// Codegen converts this into a `Lazy<Option<GlobMatcher>>`.
struct Matcher(Vec<MatcherSegment>);
/// Parse a matcher.
///
/// Note that this implementation is rather strict: it will greedily interpret
/// every valid environment variable replacement as such, then immediately
/// hard-error if it finds a '$' anywhere in the remaining text segments.
///
/// The reason for this strictness is I currently cannot think of a valid reason
/// why you would ever need '$' as plaintext in a glob pattern. Therefore any
/// such occurrences are likely human errors.
///
/// If we later discover some edge cases, it's okay to make it more permissive.
///
/// Revision history:
/// - 2024-02-20: allow `{` and `}` (glob brace expansion)
impl FromStr for Matcher {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use MatcherSegment as Seg;
static VAR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{([\w\d_]+)\}").unwrap());
let mut segments = vec![];
let mut text_start = 0;
for capture in VAR_REGEX.captures_iter(s) {
let match_0 = capture.get(0).unwrap();
// text before this var
let text_end = match_0.start();
segments.push(Seg::Text(s[text_start..text_end].into()));
text_start = match_0.end();
// this var
segments.push(Seg::Env(capture.get(1).unwrap().as_str().into()));
}
// possible trailing text
segments.push(Seg::Text(s[text_start..].into()));
// cleanup empty text segments
let non_empty_segments = segments
.into_iter()
.filter(|seg| seg.text().map(|t| !t.is_empty()).unwrap_or(true))
.collect_vec();
// sanity check
if non_empty_segments
.windows(2)
.any(|segs| segs[0].is_text() && segs[1].is_text())
{
unreachable!("Parsed into consecutive text segments: {non_empty_segments:?}");
}
// guard empty case
if non_empty_segments.is_empty() {
bail!(r#"Parsed an empty matcher: "{s}""#);
}
// guard variable syntax leftover fragments
if non_empty_segments
.iter()
.filter_map(Seg::text)
.any(|t| t.contains('$'))
{
bail!(r#"Invalid matcher: "{s}""#);
}
Ok(Self(non_empty_segments))
}
}
impl Matcher {
fn codegen(&self) -> String {
match self.0.len() {
0 => unreachable!("0-length matcher should never be created"),
// if-let guard would be ideal here
// see: https://github.com/rust-lang/rust/issues/51114
1 if self.0[0].is_text() => {
let s = self.0[0].text().unwrap();
format!(r###"Lazy::new(|| Some(build_matcher_fixed(r#"{s}"#)))"###)
}
// parser logic ensures that this case can only happen when there are dynamic segments
_ => {
let segs = self.0.iter().map(MatcherSegment::codegen).join(", ");
format!(r###"Lazy::new(|| build_matcher_dynamic(&[{segs}]))"###)
}
}
}
}
/// A segment in a matcher.
///
/// Corresponds to `syntax_mapping::MatcherSegment`.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum MatcherSegment {
Text(String),
Env(String),
}
#[allow(dead_code)]
impl MatcherSegment {
fn is_text(&self) -> bool {
matches!(self, Self::Text(_))
}
fn is_env(&self) -> bool {
matches!(self, Self::Env(_))
}
fn text(&self) -> Option<&str> {
match self {
Self::Text(t) => Some(t),
Self::Env(_) => None,
}
}
fn env(&self) -> Option<&str> {
match self {
Self::Text(_) => None,
Self::Env(t) => Some(t),
}
}
fn codegen(&self) -> String {
match self {
Self::Text(s) => format!(r###"MatcherSegment::Text(r#"{s}"#)"###),
Self::Env(s) => format!(r###"MatcherSegment::Env(r#"{s}"#)"###),
}
}
}
/// A struct that models a single .toml file in /src/syntax_mapping/builtins/.
#[derive(Clone, Debug, Deserialize)]
struct MappingDefModel {
mappings: IndexMap<MappingTarget, Vec<Matcher>>,
}
impl MappingDefModel {
fn into_mapping_list(self) -> MappingList {
let list = self
.mappings
.into_iter()
.flat_map(|(target, matchers)| {
matchers
.into_iter()
.map(|matcher| (matcher, target.clone()))
.collect::<Vec<_>>()
})
.collect();
MappingList(list)
}
}
#[derive(Clone, Debug)]
struct MappingList(Vec<(Matcher, MappingTarget)>);
impl MappingList {
fn codegen(&self) -> String {
let array_items: Vec<_> = self
.0
.iter()
.map(|(matcher, target)| {
format!("({m}, {t})", m = matcher.codegen(), t = target.codegen())
})
.collect();
let len = array_items.len();
format!(
"/// Generated by build script from /src/syntax_mapping/builtins/.\n\
pub(crate) static BUILTIN_MAPPINGS: [(Lazy<Option<GlobMatcher>>, MappingTarget); {len}] = [\n{items}\n];",
items = array_items.join(",\n")
)
}
}
/// Get the list of paths to all mapping definition files that should be
/// included for the current target platform.
fn get_def_paths() -> anyhow::Result<Vec<PathBuf>> {
let source_subdirs = [
"common",
#[cfg(target_family = "unix")]
"unix-family",
#[cfg(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "macos"
))]
"bsd-family",
#[cfg(target_os = "linux")]
"linux",
#[cfg(target_os = "macos")]
"macos",
#[cfg(target_os = "windows")]
"windows",
];
let mut toml_paths = vec![];
for subdir in source_subdirs {
let wd = WalkDir::new(Path::new("src/syntax_mapping/builtins").join(subdir));
let paths = wd
.into_iter()
.filter_map_ok(|entry| {
let path = entry.path();
(path.is_file() && path.extension().map(|ext| ext == "toml").unwrap_or(false))
.then(|| path.to_owned())
})
.collect::<Result<Vec<_>, _>>()?;
toml_paths.extend(paths);
}
toml_paths.sort_by_key(|path| {
path.file_name()
.expect("file name should not terminate in ..")
.to_owned()
});
Ok(toml_paths)
}
fn read_all_mappings() -> anyhow::Result<MappingList> {
let mut all_mappings = vec![];
for path in get_def_paths()? {
let toml_string = fs::read_to_string(path)?;
let mappings = toml::from_str::<MappingDefModel>(&toml_string)?.into_mapping_list();
all_mappings.extend(mappings.0);
}
let duplicates = all_mappings
.iter()
.duplicates_by(|(matcher, _)| matcher)
.collect_vec();
if !duplicates.is_empty() {
bail!("Rules with duplicate matchers found: {duplicates:?}");
}
Ok(MappingList(all_mappings))
}
/// Build the static syntax mappings defined in /src/syntax_mapping/builtins/
/// into a .rs source file, which is to be inserted with `include!`.
pub fn build_static_mappings() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=src/syntax_mapping/builtins/");
let mappings = read_all_mappings()?;
let codegen_path = Path::new(&env::var_os("OUT_DIR").ok_or(anyhow!("OUT_DIR is unset"))?)
.join("codegen_static_syntax_mappings.rs");
fs::write(codegen_path, mappings.codegen())?;
Ok(())
}

21
build/util.rs Normal file
View File

@@ -0,0 +1,21 @@
#![allow(dead_code)]
use std::{collections::HashMap, fs, path::Path};
/// Generates a file from a template.
pub fn render_template(
variables: &HashMap<&str, String>,
in_file: &str,
out_file: impl AsRef<Path>,
) -> anyhow::Result<()> {
let mut content = fs::read_to_string(in_file)?;
for (variable_name, value) in variables {
// Replace {{variable_name}} by the value
let pattern = format!("{{{{{variable_name}}}}}");
content = content.replace(&pattern, value);
}
fs::write(out_file, content)?;
Ok(())
}

View File

@@ -181,7 +181,7 @@ man 2 select
## インストール ## インストール
[![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg)](https://repology.org/project/bat-cat/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/bat-cat/versions)
### On Ubuntu (`apt` を使用) ### On Ubuntu (`apt` を使用)
*... や他のDebianベースのLinuxディストリビューション* *... や他のDebianベースのLinuxディストリビューション*
@@ -219,7 +219,7 @@ apk add bat
### On Arch Linux ### On Arch Linux
[Arch Linuxの公式リソース](https://www.archlinux.org/packages/community/x86_64/bat/) [Arch Linuxの公式リソース](https://www.archlinux.org/packages/extra/x86_64/bat/)
からインストールできます。 からインストールできます。
```bash ```bash

View File

@@ -214,7 +214,7 @@ man 2 select
## 설치 ## 설치
[![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg)](https://repology.org/project/bat-cat/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/bat-cat/versions)
### Ubuntu에서 (`apt` 사용) ### Ubuntu에서 (`apt` 사용)
*... 그리고 다른 Debian 기반의 Linux 배포판들에서.* *... 그리고 다른 Debian 기반의 Linux 배포판들에서.*
@@ -264,7 +264,7 @@ apk add bat
### Arch Linux에서 ### Arch Linux에서
공식 소스를 통해 공식 소스를 통해
[`bat` 패키지](https://www.archlinux.org/packages/community/x86_64/bat/)를 [`bat` 패키지](https://www.archlinux.org/packages/extra/x86_64/bat/)를
설치할 수 있습니다: 설치할 수 있습니다:
```bash ```bash

View File

@@ -12,7 +12,7 @@
<a href="#установка">Установка</a> <a href="#установка">Установка</a>
<a href="#кастомизация">Кастомизация</a> <a href="#кастомизация">Кастомизация</a>
<a href="#цели-и-альтернативы">Цели и альтернативы </a><br> <a href="#цели-и-альтернативы">Цели и альтернативы </a><br>
[<a href="../README.md">English] [<a href="../README.md">English</a>]
[<a href="README-zh.md">中文</a>] [<a href="README-zh.md">中文</a>]
[<a href="README-ja.md">日本語</a>] [<a href="README-ja.md">日本語</a>]
[<a href="README-ko.md">한국어</a>] [<a href="README-ko.md">한국어</a>]
@@ -160,7 +160,7 @@ man 2 select
## Установка ## Установка
[![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg)](https://repology.org/project/bat-cat/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/bat-cat/versions)
### Ubuntu (с помощью `apt`) ### Ubuntu (с помощью `apt`)
*... и другие дистрибутивы основанные на Debian.* *... и другие дистрибутивы основанные на Debian.*
@@ -201,7 +201,7 @@ apk add bat
### Arch Linux ### Arch Linux
Вы можете установить [`bat`](https://www.archlinux.org/packages/community/x86_64/bat/) из официального источника: Вы можете установить [`bat`](https://www.archlinux.org/packages/extra/x86_64/bat/) из официального источника:
```bash ```bash
pacman -S bat pacman -S bat

View File

@@ -191,7 +191,7 @@ man 2 select
## 安装 ## 安装
[![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg)](https://repology.org/project/bat-cat/versions) [![Packaging status](https://repology.org/badge/vertical-allrepos/bat-cat.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/bat-cat/versions)
### Ubuntu (使用 `apt`) ### Ubuntu (使用 `apt`)
@@ -232,7 +232,7 @@ apk add bat
### Arch Linux ### Arch Linux
你可以用下面下列命令从官方源中安装[`bat`包](https://www.archlinux.org/packages/community/x86_64/bat/) 你可以用下面下列命令从官方源中安装[`bat`包](https://www.archlinux.org/packages/extra/x86_64/bat/)
```bash ```bash
pacman -S bat pacman -S bat

View File

@@ -116,6 +116,12 @@ Options:
--list-themes --list-themes
Display a list of supported themes for syntax highlighting. Display a list of supported themes for syntax highlighting.
-s, --squeeze-blank
Squeeze consecutive empty lines into a single empty line.
--squeeze-limit <squeeze-limit>
Set the maximum number of consecutive empty lines to be printed.
--style <components> --style <components>
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..) Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
to display in addition to the file contents. The argument is a comma-separated list of to display in addition to the file contents. The argument is a comma-separated list of
@@ -123,6 +129,9 @@ Options:
set a default style, add the '--style=".."' option to the configuration file or export the set a default style, add the '--style=".."' option to the configuration file or export the
BAT_STYLE environment variable (e.g.: export BAT_STYLE=".."). BAT_STYLE environment variable (e.g.: export BAT_STYLE="..").
By default, the following components are enabled:
changes, grid, header-filename, numbers, snip
Possible values: Possible values:
* default: enables recommended style components (default). * default: enables recommended style components (default).
@@ -160,6 +169,9 @@ Options:
--acknowledgements --acknowledgements
Show acknowledgements. Show acknowledgements.
--set-terminal-title
Sets terminal title to filenames when using a pager.
-h, --help -h, --help
Print help (see a summary with '-h') Print help (see a summary with '-h')

View File

@@ -9,7 +9,16 @@
- [ ] Update the version and the min. supported Rust version in `README.md` and - [ ] Update the version and the min. supported Rust version in `README.md` and
`doc/README-*.md`. Check with `doc/README-*.md`. Check with
`git grep -i -e 'rust.*1\.' -e '1\..*rust' | grep README | grep -v tests/`. `git grep -i -e 'rust.*1\.' -e '1\..*rust' | grep README | grep -v tests/`.
- [ ] Update `CHANGELOG.md`. Introduce a section for the new release.
## CHANGELOG.md updates
- [ ] Go to https://github.com/sharkdp/bat/releases/new, click "Choose a tag",
type the name of the tag that will be created later, click "Generate release
notes". DO NOT ACTUALLY CREATE ANY RELEASE IN THIS STEP.
- [ ] Compare current `CHANGELOG.md` with auto-generated release notes and add
missing entries. Expect in particular dependabot PRs to not be in
`CHANGELOG.md` since they are [auto-merged] if CI passes.
- [ ] Introduce a section for the new release and perform final touch-ups.
## Update syntaxes and themes (build assets) ## Update syntaxes and themes (build assets)
@@ -71,3 +80,5 @@
``` ```
[auto-merged]: https://github.com/sharkdp/bat/blob/master/.github/workflows/Auto-merge-dependabot-PRs.yml

View File

@@ -43,6 +43,8 @@ Options:
Set the color theme for syntax highlighting. Set the color theme for syntax highlighting.
--list-themes --list-themes
Display all supported highlighting themes. Display all supported highlighting themes.
-s, --squeeze-blank
Squeeze consecutive empty lines.
--style <components> --style <components>
Comma-separated list of style elements to display (*default*, auto, full, plain, changes, Comma-separated list of style elements to display (*default*, auto, full, plain, changes,
header, header-filename, header-filesize, grid, rule, numbers, snip). header, header-filename, header-filesize, grid, rule, numbers, snip).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -13,6 +13,6 @@ fn main() {
println!("Themes:"); println!("Themes:");
for theme in printer.themes() { for theme in printer.themes() {
println!("- {}", theme); println!("- {theme}");
} }
} }

View File

@@ -380,7 +380,7 @@ fn asset_from_contents<T: serde::de::DeserializeOwned>(
} else { } else {
bincode::deserialize_from(contents) bincode::deserialize_from(contents)
} }
.map_err(|_| format!("Could not parse {}", description).into()) .map_err(|_| format!("Could not parse {description}").into())
} }
fn asset_from_cache<T: serde::de::DeserializeOwned>( fn asset_from_cache<T: serde::de::DeserializeOwned>(
@@ -396,7 +396,7 @@ fn asset_from_cache<T: serde::de::DeserializeOwned>(
) )
})?; })?;
asset_from_contents(&contents[..], description, compressed) asset_from_contents(&contents[..], description, compressed)
.map_err(|_| format!("Could not parse cached {}", description).into()) .map_err(|_| format!("Could not parse cached {description}").into())
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -441,7 +441,7 @@ mod tests {
fn new() -> Self { fn new() -> Self {
SyntaxDetectionTest { SyntaxDetectionTest {
assets: HighlightingAssets::from_binary(), assets: HighlightingAssets::from_binary(),
syntax_mapping: SyntaxMapping::builtin(), syntax_mapping: SyntaxMapping::new(),
temp_dir: TempDir::new().expect("creation of temporary directory"), temp_dir: TempDir::new().expect("creation of temporary directory"),
} }
} }
@@ -466,7 +466,7 @@ mod tests {
let file_path = self.temp_dir.path().join(file_name); let file_path = self.temp_dir.path().join(file_name);
{ {
let mut temp_file = File::create(&file_path).unwrap(); let mut temp_file = File::create(&file_path).unwrap();
writeln!(temp_file, "{}", first_line).unwrap(); writeln!(temp_file, "{first_line}").unwrap();
} }
let input = Input::ordinary_file(&file_path); let input = Input::ordinary_file(&file_path);
@@ -514,8 +514,7 @@ mod tests {
if !consistent { if !consistent {
eprintln!( eprintln!(
"Inconsistent syntax detection:\nFor File: {}\nFor Reader: {}", "Inconsistent syntax detection:\nFor File: {as_file}\nFor Reader: {as_reader}"
as_file, as_reader
) )
} }

View File

@@ -3,7 +3,7 @@ use std::path::Path;
use std::time::SystemTime; use std::time::SystemTime;
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use crate::error::*; use crate::error::*;

View File

@@ -93,7 +93,7 @@ fn print_unlinked_contexts(syntax_set: &SyntaxSet) {
if !missing_contexts.is_empty() { if !missing_contexts.is_empty() {
println!("Some referenced contexts could not be found!"); println!("Some referenced contexts could not be found!");
for context in missing_contexts { for context in missing_contexts {
println!("- {}", context); println!("- {context}");
} }
} }
} }
@@ -152,7 +152,7 @@ pub(crate) fn asset_to_contents<T: serde::Serialize>(
} else { } else {
bincode::serialize_into(&mut contents, asset) bincode::serialize_into(&mut contents, asset)
} }
.map_err(|_| format!("Could not serialize {}", description))?; .map_err(|_| format!("Could not serialize {description}"))?;
Ok(contents) Ok(contents)
} }

View File

@@ -80,7 +80,7 @@ fn handle_license(path: &Path) -> Result<Option<String>> {
} else if license_not_needed_in_acknowledgements(&license_text) { } else if license_not_needed_in_acknowledgements(&license_text) {
Ok(None) Ok(None)
} else { } else {
Err(format!("ERROR: License is of unknown type: {:?}", path).into()) Err(format!("ERROR: License is of unknown type: {path:?}").into())
} }
} }
@@ -125,7 +125,7 @@ fn append_to_acknowledgements(
relative_path: &str, relative_path: &str,
license_text: &str, license_text: &str,
) { ) {
write!(acknowledgements, "## {}\n\n{}", relative_path, license_text).ok(); write!(acknowledgements, "## {relative_path}\n\n{license_text}").ok();
// Make sure the last char is a newline to not mess up formatting later // Make sure the last char is a newline to not mess up formatting later
if acknowledgements if acknowledgements

View File

@@ -3,8 +3,7 @@ use super::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use serde::Deserialize; use serde_derive::{Deserialize, Serialize};
use serde::Serialize;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
@@ -89,7 +88,7 @@ impl TryFrom<ThemeSet> for LazyThemeSet {
let lazy_theme = LazyTheme { let lazy_theme = LazyTheme {
serialized: crate::assets::build_assets::asset_to_contents( serialized: crate::assets::build_assets::asset_to_contents(
&theme, &theme,
&format!("theme {}", name), &format!("theme {name}"),
COMPRESS_LAZY_THEMES, COMPRESS_LAZY_THEMES,
)?, )?,
deserialized: OnceCell::new(), deserialized: OnceCell::new(),

View File

@@ -29,6 +29,10 @@ fn is_truecolor_terminal() -> bool {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn env_no_color() -> bool {
env::var_os("NO_COLOR").is_some_and(|x| !x.is_empty())
}
pub struct App { pub struct App {
pub matches: ArgMatches, pub matches: ArgMatches,
interactive_output: bool, interactive_output: bool,
@@ -117,7 +121,11 @@ impl App {
_ => unreachable!("other values for --paging are not allowed"), _ => unreachable!("other values for --paging are not allowed"),
}; };
let mut syntax_mapping = SyntaxMapping::builtin(); let mut syntax_mapping = SyntaxMapping::new();
// start building glob matchers for builtin mappings immediately
// this is an appropriate approach because it's statistically likely that
// all the custom mappings need to be checked
syntax_mapping.start_offload_build_all();
if let Some(values) = self.matches.get_many::<String>("ignored-suffix") { if let Some(values) = self.matches.get_many::<String>("ignored-suffix") {
for suffix in values { for suffix in values {
@@ -126,7 +134,9 @@ impl App {
} }
if let Some(values) = self.matches.get_many::<String>("map-syntax") { if let Some(values) = self.matches.get_many::<String>("map-syntax") {
for from_to in values { // later args take precedence over earlier ones, hence `.rev()`
// see: https://github.com/sharkdp/bat/pull/2755#discussion_r1456416875
for from_to in values.rev() {
let parts: Vec<_> = from_to.split(':').collect(); let parts: Vec<_> = from_to.split(':').collect();
if parts.len() != 2 { if parts.len() != 2 {
@@ -207,7 +217,7 @@ impl App {
|| match self.matches.get_one::<String>("color").map(|s| s.as_str()) { || match self.matches.get_one::<String>("color").map(|s| s.as_str()) {
Some("always") => true, Some("always") => true,
Some("never") => false, Some("never") => false,
Some("auto") => env::var_os("NO_COLOR").is_none() && self.interactive_output, Some("auto") => !env_no_color() && self.interactive_output,
_ => unreachable!("other values for --color are not allowed"), _ => unreachable!("other values for --color are not allowed"),
}, },
paging_mode, paging_mode,
@@ -283,6 +293,17 @@ impl App {
use_custom_assets: !self.matches.get_flag("no-custom-assets"), use_custom_assets: !self.matches.get_flag("no-custom-assets"),
#[cfg(feature = "lessopen")] #[cfg(feature = "lessopen")]
use_lessopen: self.matches.get_flag("lessopen"), use_lessopen: self.matches.get_flag("lessopen"),
set_terminal_title: self.matches.get_flag("set-terminal-title"),
squeeze_lines: if self.matches.get_flag("squeeze-blank") {
Some(
self.matches
.get_one::<usize>("squeeze-limit")
.map(|limit| limit.to_owned())
.unwrap_or(1),
)
} else {
None
},
}) })
} }

View File

@@ -44,7 +44,7 @@ pub fn assets_from_cache_or_binary(
} }
fn clear_asset(path: PathBuf, description: &str) { fn clear_asset(path: PathBuf, description: &str) {
print!("Clearing {} ... ", description); print!("Clearing {description} ... ");
match fs::remove_file(&path) { match fs::remove_file(&path) {
Err(err) if err.kind() == io::ErrorKind::NotFound => { Err(err) if err.kind() == io::ErrorKind::NotFound => {
println!("skipped (not present)"); println!("skipped (not present)");

View File

@@ -19,7 +19,7 @@ static VERSION: Lazy<String> = Lazy::new(|| {
}); });
pub fn build_app(interactive_output: bool) -> Command { pub fn build_app(interactive_output: bool) -> Command {
let color_when = if interactive_output && env::var_os("NO_COLOR").is_none() { let color_when = if interactive_output && !crate::app::env_no_color() {
ColorChoice::Auto ColorChoice::Auto
} else { } else {
ColorChoice::Never ColorChoice::Never
@@ -387,6 +387,21 @@ pub fn build_app(interactive_output: bool) -> Command {
.help("Display all supported highlighting themes.") .help("Display all supported highlighting themes.")
.long_help("Display a list of supported themes for syntax highlighting."), .long_help("Display a list of supported themes for syntax highlighting."),
) )
.arg(
Arg::new("squeeze-blank")
.long("squeeze-blank")
.short('s')
.action(ArgAction::SetTrue)
.help("Squeeze consecutive empty lines.")
.long_help("Squeeze consecutive empty lines into a single empty line.")
)
.arg(
Arg::new("squeeze-limit")
.long("squeeze-limit")
.value_parser(|s: &str| s.parse::<usize>().map_err(|_| "Requires a non-negative number".to_owned()))
.long_help("Set the maximum number of consecutive empty lines to be printed.")
.hide_short_help(true)
)
.arg( .arg(
Arg::new("style") Arg::new("style")
.long("style") .long("style")
@@ -415,7 +430,7 @@ pub fn build_app(interactive_output: bool) -> Command {
}); });
if let Some(invalid) = invalid_vals.next() { if let Some(invalid) = invalid_vals.next() {
Err(format!("Unknown style, '{}'", invalid)) Err(format!("Unknown style, '{invalid}'"))
} else { } else {
Ok(val.to_owned()) Ok(val.to_owned())
} }
@@ -432,6 +447,8 @@ pub fn build_app(interactive_output: bool) -> Command {
pre-defined style ('full'). To set a default style, add the \ pre-defined style ('full'). To set a default style, add the \
'--style=\"..\"' option to the configuration file or export the \ '--style=\"..\"' option to the configuration file or export the \
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\ BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\
By default, the following components are enabled:\n \
changes, grid, header-filename, numbers, snip\n\n\
Possible values:\n\n \ Possible values:\n\n \
* default: enables recommended style components (default).\n \ * default: enables recommended style components (default).\n \
* full: enables all available components.\n \ * full: enables all available components.\n \
@@ -567,6 +584,13 @@ pub fn build_app(interactive_output: bool) -> Command {
.action(ArgAction::SetTrue) .action(ArgAction::SetTrue)
.hide_short_help(true) .hide_short_help(true)
.help("Show acknowledgements."), .help("Show acknowledgements."),
)
.arg(
Arg::new("set-terminal-title")
.long("set-terminal-title")
.action(ArgAction::SetTrue)
.hide_short_help(true)
.help("Sets terminal title to filenames when using a pager."),
); );
// Check if the current directory contains a file name cache. Otherwise, // Check if the current directory contains a file name cache. Otherwise,

View File

@@ -78,9 +78,11 @@ fn run_cache_subcommand(
Ok(()) Ok(())
} }
fn get_syntax_mapping_to_paths<'a>( fn get_syntax_mapping_to_paths<'r, 't, I>(mappings: I) -> HashMap<&'t str, Vec<String>>
mappings: &[(GlobMatcher, MappingTarget<'a>)], where
) -> HashMap<&'a str, Vec<String>> { I: IntoIterator<Item = (&'r GlobMatcher, &'r MappingTarget<'t>)>,
't: 'r, // target text outlives rule
{
let mut map = HashMap::new(); let mut map = HashMap::new();
for mapping in mappings { for mapping in mappings {
if let (matcher, MappingTarget::MapTo(s)) = mapping { if let (matcher, MappingTarget::MapTo(s)) = mapping {
@@ -123,7 +125,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
languages.sort_by_key(|lang| lang.name.to_uppercase()); languages.sort_by_key(|lang| lang.name.to_uppercase());
let configured_languages = get_syntax_mapping_to_paths(config.syntax_mapping.mappings()); let configured_languages = get_syntax_mapping_to_paths(config.syntax_mapping.all_mappings());
for lang in &mut languages { for lang in &mut languages {
if let Some(additional_paths) = configured_languages.get(lang.name.as_str()) { if let Some(additional_paths) = configured_languages.get(lang.name.as_str()) {
@@ -220,16 +222,37 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
)?; )?;
} else { } else {
for theme in assets.themes() { for theme in assets.themes() {
writeln!(stdout, "{}", theme)?; writeln!(stdout, "{theme}")?;
} }
} }
Ok(()) Ok(())
} }
fn set_terminal_title_to(new_terminal_title: String) {
let osc_command_for_setting_terminal_title = "\x1b]0;";
let osc_end_command = "\x07";
print!("{osc_command_for_setting_terminal_title}{new_terminal_title}{osc_end_command}");
io::stdout().flush().unwrap();
}
fn get_new_terminal_title(inputs: &Vec<Input>) -> String {
let mut new_terminal_title = "bat: ".to_string();
for (index, input) in inputs.iter().enumerate() {
new_terminal_title += input.description().title();
if index < inputs.len() - 1 {
new_terminal_title += ", ";
}
}
new_terminal_title
}
fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> { fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> {
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?; let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
let controller = Controller::new(config, &assets); let controller = Controller::new(config, &assets);
if config.paging_mode != PagingMode::Never && config.set_terminal_title {
set_terminal_title_to(get_new_terminal_title(&inputs));
}
controller.run(inputs, None) controller.run(inputs, None)
} }

View File

@@ -94,6 +94,12 @@ pub struct Config<'a> {
// Whether or not to use $LESSOPEN if set // Whether or not to use $LESSOPEN if set
#[cfg(feature = "lessopen")] #[cfg(feature = "lessopen")]
pub use_lessopen: bool, pub use_lessopen: bool,
// Weather or not to set terminal title when using a pager
pub set_terminal_title: bool,
/// The maximum number of consecutive empty lines to display
pub squeeze_lines: Option<usize>,
} }
#[cfg(all(feature = "minimal-application", feature = "paging"))] #[cfg(all(feature = "minimal-application", feature = "paging"))]

View File

@@ -47,7 +47,7 @@ impl<'b> Controller<'b> {
&self, &self,
inputs: Vec<Input>, inputs: Vec<Input>,
output_buffer: Option<&mut dyn std::fmt::Write>, output_buffer: Option<&mut dyn std::fmt::Write>,
handle_error: impl Fn(&Error, &mut dyn Write), mut handle_error: impl FnMut(&Error, &mut dyn Write),
) -> Result<bool> { ) -> Result<bool> {
let mut output_type; let mut output_type;

View File

@@ -46,7 +46,7 @@ impl Decoration for LineNumberDecoration {
_printer: &InteractivePrinter, _printer: &InteractivePrinter,
) -> DecorationText { ) -> DecorationText {
if continuation { if continuation {
if line_number > self.cached_wrap_invalid_at { if line_number >= self.cached_wrap_invalid_at {
let new_width = self.cached_wrap.width + 1; let new_width = self.cached_wrap.width + 1;
return DecorationText { return DecorationText {
text: self.color.paint(" ".repeat(new_width)).to_string(), text: self.color.paint(" ".repeat(new_width)).to_string(),
@@ -56,7 +56,7 @@ impl Decoration for LineNumberDecoration {
self.cached_wrap.clone() self.cached_wrap.clone()
} else { } else {
let plain: String = format!("{:4}", line_number); let plain: String = format!("{line_number:4}");
DecorationText { DecorationText {
width: plain.len(), width: plain.len(),
text: self.color.paint(plain).to_string(), text: self.color.paint(plain).to_string(),

View File

@@ -197,7 +197,7 @@ impl<'a> Input<'a> {
InputKind::StdIn => { InputKind::StdIn => {
if let Some(stdout) = stdout_identifier { if let Some(stdout) = stdout_identifier {
let input_identifier = Identifier::try_from(clircle::Stdio::Stdin) let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
.map_err(|e| format!("Stdin: Error identifying file: {}", e))?; .map_err(|e| format!("Stdin: Error identifying file: {e}"))?;
if stdout.surely_conflicts_with(&input_identifier) { if stdout.surely_conflicts_with(&input_identifier) {
return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into()); return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
} }

View File

@@ -91,31 +91,27 @@ pub fn replace_nonprintable(
}); });
line_idx = 0; line_idx = 0;
} }
// carriage return // ASCII control characters
'\x0D' => output.push_str(match nonprintable_notation { '\x00'..='\x1F' => {
NonprintableNotation::Caret => "^M", let c = u32::from(chr);
NonprintableNotation::Unicode => "",
}), match nonprintable_notation {
// null NonprintableNotation::Caret => {
'\x00' => output.push_str(match nonprintable_notation { let caret_character = char::from_u32(0x40 + c).unwrap();
NonprintableNotation::Caret => "^@", write!(output, "^{caret_character}").ok();
NonprintableNotation::Unicode => "", }
}),
// bell NonprintableNotation::Unicode => {
'\x07' => output.push_str(match nonprintable_notation { let replacement_symbol = char::from_u32(0x2400 + c).unwrap();
NonprintableNotation::Caret => "^G", output.push(replacement_symbol)
NonprintableNotation::Unicode => "", }
}), }
// backspace }
'\x08' => output.push_str(match nonprintable_notation { // delete
NonprintableNotation::Caret => "^H", '\x7F' => match nonprintable_notation {
NonprintableNotation::Unicode => "", NonprintableNotation::Caret => output.push_str("^?"),
}), NonprintableNotation::Unicode => output.push('\u{2421}'),
// escape },
'\x1B' => output.push_str(match nonprintable_notation {
NonprintableNotation::Caret => "^[",
NonprintableNotation::Unicode => "",
}),
// printable ASCII // printable ASCII
c if c.is_ascii_alphanumeric() c if c.is_ascii_alphanumeric()
|| c.is_ascii_punctuation() || c.is_ascii_punctuation()

View File

@@ -230,6 +230,12 @@ impl<'a> PrettyPrinter<'a> {
self self
} }
/// Specify the maximum number of consecutive empty lines to print.
pub fn squeeze_empty_lines(&mut self, maximum: Option<usize>) -> &mut Self {
self.config.squeeze_lines = maximum;
self
}
/// Specify the highlighting theme /// Specify the highlighting theme
pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self { pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
self.config.theme = theme.as_ref().to_owned(); self.config.theme = theme.as_ref().to_owned();

View File

@@ -7,10 +7,9 @@ use nu_ansi_term::Style;
use bytesize::ByteSize; use bytesize::ByteSize;
use console::AnsiCodeIterator;
use syntect::easy::HighlightLines; use syntect::easy::HighlightLines;
use syntect::highlighting::Color; use syntect::highlighting::Color;
use syntect::highlighting::FontStyle;
use syntect::highlighting::Theme; use syntect::highlighting::Theme;
use syntect::parsing::SyntaxSet; use syntect::parsing::SyntaxSet;
@@ -33,9 +32,39 @@ use crate::line_range::RangeCheckResult;
use crate::preprocessor::{expand_tabs, replace_nonprintable}; use crate::preprocessor::{expand_tabs, replace_nonprintable};
use crate::style::StyleComponent; use crate::style::StyleComponent;
use crate::terminal::{as_terminal_escaped, to_ansi_color}; use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::vscreen::AnsiStyle; use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
use crate::wrapping::WrappingMode; use crate::wrapping::WrappingMode;
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
raw_sequence: "\x1B[4m",
parameters: "4",
intermediates: "",
final_byte: "m",
};
const ANSI_UNDERLINE_DISABLE: EscapeSequence = EscapeSequence::CSI {
raw_sequence: "\x1B[24m",
parameters: "24",
intermediates: "",
final_byte: "m",
};
const EMPTY_SYNTECT_STYLE: syntect::highlighting::Style = syntect::highlighting::Style {
foreground: Color {
r: 127,
g: 127,
b: 127,
a: 255,
},
background: Color {
r: 127,
g: 127,
b: 127,
a: 255,
},
font_style: FontStyle::empty(),
};
pub enum OutputHandle<'a> { pub enum OutputHandle<'a> {
IoWrite(&'a mut dyn io::Write), IoWrite(&'a mut dyn io::Write),
FmtWrite(&'a mut dyn fmt::Write), FmtWrite(&'a mut dyn fmt::Write),
@@ -72,11 +101,15 @@ pub(crate) trait Printer {
pub struct SimplePrinter<'a> { pub struct SimplePrinter<'a> {
config: &'a Config<'a>, config: &'a Config<'a>,
consecutive_empty_lines: usize,
} }
impl<'a> SimplePrinter<'a> { impl<'a> SimplePrinter<'a> {
pub fn new(config: &'a Config) -> Self { pub fn new(config: &'a Config) -> Self {
SimplePrinter { config } SimplePrinter {
config,
consecutive_empty_lines: 0,
}
} }
} }
@@ -105,6 +138,21 @@ impl<'a> Printer for SimplePrinter<'a> {
_line_number: usize, _line_number: usize,
line_buffer: &[u8], line_buffer: &[u8],
) -> Result<()> { ) -> Result<()> {
// Skip squeezed lines.
if let Some(squeeze_limit) = self.config.squeeze_lines {
if String::from_utf8_lossy(line_buffer)
.trim_end_matches(|c| c == '\r' || c == '\n')
.is_empty()
{
self.consecutive_empty_lines += 1;
if self.consecutive_empty_lines > squeeze_limit {
return Ok(());
}
} else {
self.consecutive_empty_lines = 0;
}
}
if !out_of_range { if !out_of_range {
if self.config.show_nonprintable { if self.config.show_nonprintable {
let line = replace_nonprintable( let line = replace_nonprintable(
@@ -112,7 +160,7 @@ impl<'a> Printer for SimplePrinter<'a> {
self.config.tab_width, self.config.tab_width,
self.config.nonprintable_notation, self.config.nonprintable_notation,
); );
write!(handle, "{}", line)?; write!(handle, "{line}")?;
} else { } else {
match handle { match handle {
OutputHandle::IoWrite(handle) => handle.write_all(line_buffer)?, OutputHandle::IoWrite(handle) => handle.write_all(line_buffer)?,
@@ -158,6 +206,7 @@ pub(crate) struct InteractivePrinter<'a> {
pub line_changes: &'a Option<LineChanges>, pub line_changes: &'a Option<LineChanges>,
highlighter_from_set: Option<HighlighterFromSet<'a>>, highlighter_from_set: Option<HighlighterFromSet<'a>>,
background_color_highlight: Option<Color>, background_color_highlight: Option<Color>,
consecutive_empty_lines: usize,
} }
impl<'a> InteractivePrinter<'a> { impl<'a> InteractivePrinter<'a> {
@@ -210,11 +259,13 @@ impl<'a> InteractivePrinter<'a> {
panel_width = 0; panel_width = 0;
} }
let highlighter_from_set = if input // Get the highlighter for the output.
let is_printing_binary = input
.reader .reader
.content_type .content_type
.map_or(false, |c| c.is_binary() && !config.show_nonprintable) .map_or(false, |c| c.is_binary() && !config.show_nonprintable);
{
let highlighter_from_set = if is_printing_binary || !config.colored_output {
None None
} else { } else {
// Determine the type of syntax for highlighting // Determine the type of syntax for highlighting
@@ -241,6 +292,7 @@ impl<'a> InteractivePrinter<'a> {
line_changes, line_changes,
highlighter_from_set, highlighter_from_set,
background_color_highlight, background_color_highlight,
consecutive_empty_lines: 0,
}) })
} }
@@ -281,12 +333,20 @@ impl<'a> InteractivePrinter<'a> {
" ".repeat(self.panel_width - 1 - text_truncated.len()) " ".repeat(self.panel_width - 1 - text_truncated.len())
); );
if self.config.style_components.grid() { if self.config.style_components.grid() {
format!("{}", text_filled) format!("{text_filled}")
} else { } else {
text_filled text_filled
} }
} }
fn get_header_component_indent_length(&self) -> usize {
if self.config.style_components.grid() && self.panel_width > 0 {
self.panel_width + 2
} else {
self.panel_width
}
}
fn print_header_component_indent(&mut self, handle: &mut OutputHandle) -> Result<()> { fn print_header_component_indent(&mut self, handle: &mut OutputHandle) -> Result<()> {
if self.config.style_components.grid() { if self.config.style_components.grid() {
write!( write!(
@@ -302,6 +362,55 @@ impl<'a> InteractivePrinter<'a> {
} }
} }
fn print_header_component_with_indent(
&mut self,
handle: &mut OutputHandle,
content: &str,
) -> Result<()> {
self.print_header_component_indent(handle)?;
writeln!(handle, "{content}")
}
fn print_header_multiline_component(
&mut self,
handle: &mut OutputHandle,
content: &str,
) -> Result<()> {
let mut content = content;
let content_width = self.config.term_width - self.get_header_component_indent_length();
while content.len() > content_width {
let (content_line, remaining) = content.split_at(content_width);
self.print_header_component_with_indent(handle, content_line)?;
content = remaining;
}
self.print_header_component_with_indent(handle, content)
}
fn highlight_regions_for_line<'b>(
&mut self,
line: &'b str,
) -> Result<Vec<(syntect::highlighting::Style, &'b str)>> {
let highlighter_from_set = match self.highlighter_from_set {
Some(ref mut highlighter_from_set) => highlighter_from_set,
_ => return Ok(vec![(EMPTY_SYNTECT_STYLE, line)]),
};
// 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(for_highlighting, highlighter_from_set.syntax_set)?;
if too_long {
highlighted_line[0].1 = &line;
}
Ok(highlighted_line)
}
fn preprocess(&self, text: &str, cursor: &mut usize) -> String { fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
if self.config.tab_width > 0 { if self.config.tab_width > 0 {
return expand_tabs(text, self.config.tab_width, cursor); return expand_tabs(text, self.config.tab_width, cursor);
@@ -377,31 +486,32 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
} }
header_components.iter().try_for_each(|component| { header_components
self.print_header_component_indent(handle)?; .iter()
.try_for_each(|component| match component {
match component { StyleComponent::HeaderFilename => {
StyleComponent::HeaderFilename => writeln!( let header_filename = format!(
handle, "{}{}{}",
"{}{}{}", description
description .kind()
.kind() .map(|kind| format!("{kind}: "))
.map(|kind| format!("{}: ", kind)) .unwrap_or_else(|| "".into()),
.unwrap_or_else(|| "".into()), self.colors.header_value.paint(description.title()),
self.colors.header_value.paint(description.title()), mode
mode );
), self.print_header_multiline_component(handle, &header_filename)
}
StyleComponent::HeaderFilesize => { StyleComponent::HeaderFilesize => {
let bsize = metadata let bsize = metadata
.size .size
.map(|s| format!("{}", ByteSize(s))) .map(|s| format!("{}", ByteSize(s)))
.unwrap_or_else(|| "-".into()); .unwrap_or_else(|| "-".into());
writeln!(handle, "Size: {}", self.colors.header_value.paint(bsize)) let header_filesize =
format!("Size: {}", self.colors.header_value.paint(bsize));
self.print_header_multiline_component(handle, &header_filesize)
} }
_ => Ok(()), _ => Ok(()),
} })?;
})?;
if self.config.style_components.grid() { if self.config.style_components.grid() {
if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable { if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable {
@@ -442,7 +552,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
"{}", "{}",
self.colors self.colors
.grid .grid
.paint(format!("{}{}{}{}", panel, snip_left, title, snip_right)) .paint(format!("{panel}{snip_left}{title}{snip_right}"))
)?; )?;
Ok(()) Ok(())
@@ -483,34 +593,23 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
}; };
let regions = { let regions = self.highlight_regions_for_line(&line)?;
let highlighter_from_set = match self.highlighter_from_set {
Some(ref mut highlighter_from_set) => highlighter_from_set,
_ => {
return Ok(());
}
};
// 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(for_highlighting, highlighter_from_set.syntax_set)?;
if too_long {
highlighted_line[0].1 = &line;
}
highlighted_line
};
if out_of_range { if out_of_range {
return Ok(()); return Ok(());
} }
// Skip squeezed lines.
if let Some(squeeze_limit) = self.config.squeeze_lines {
if line.trim_end_matches(|c| c == '\r' || c == '\n').is_empty() {
self.consecutive_empty_lines += 1;
if self.consecutive_empty_lines > squeeze_limit {
return Ok(());
}
} else {
self.consecutive_empty_lines = 0;
}
}
let mut cursor: usize = 0; let mut cursor: usize = 0;
let mut cursor_max: usize = self.config.term_width; let mut cursor_max: usize = self.config.term_width;
let mut cursor_total: usize = 0; let mut cursor_total: usize = 0;
@@ -521,7 +620,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange; self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange;
if highlight_this_line && self.config.theme == "ansi" { if highlight_this_line && self.config.theme == "ansi" {
self.ansi_style.update("^[4m"); self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
} }
let background_color = self let background_color = self
@@ -548,23 +647,17 @@ impl<'a> Printer for InteractivePrinter<'a> {
let italics = self.config.use_italic_text; let italics = self.config.use_italic_text;
for &(style, region) in &regions { for &(style, region) in &regions {
let ansi_iterator = AnsiCodeIterator::new(region); let ansi_iterator = EscapeSequenceIterator::new(region);
for chunk in ansi_iterator { for chunk in ansi_iterator {
match chunk { match chunk {
// ANSI escape passthrough.
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text. // Regular text.
(text, false) => { EscapeSequence::Text(text) => {
let text = &*self.preprocess(text, &mut cursor_total); let text = self.preprocess(text, &mut cursor_total);
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n'); let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
write!( write!(
handle, handle,
"{}", "{}{}",
as_terminal_escaped( as_terminal_escaped(
style, style,
&format!("{}{}", self.ansi_style, text_trimmed), &format!("{}{}", self.ansi_style, text_trimmed),
@@ -572,9 +665,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
colored_output, colored_output,
italics, italics,
background_color background_color
) ),
self.ansi_style.to_reset_sequence(),
)?; )?;
// Pad the rest of the line.
if text.len() != text_trimmed.len() { if text.len() != text_trimmed.len() {
if let Some(background_color) = background_color { if let Some(background_color) = background_color {
let ansi_style = Style { let ansi_style = Style {
@@ -592,6 +687,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
write!(handle, "{}", &text[text_trimmed.len()..])?; write!(handle, "{}", &text[text_trimmed.len()..])?;
} }
} }
// ANSI escape passthrough.
_ => {
write!(handle, "{}", chunk.raw())?;
self.ansi_style.update(chunk);
}
} }
} }
} }
@@ -601,17 +702,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
} else { } else {
for &(style, region) in &regions { for &(style, region) in &regions {
let ansi_iterator = AnsiCodeIterator::new(region); let ansi_iterator = EscapeSequenceIterator::new(region);
for chunk in ansi_iterator { for chunk in ansi_iterator {
match chunk { match chunk {
// ANSI escape passthrough.
(ansi, true) => {
self.ansi_style.update(ansi);
write!(handle, "{}", ansi)?;
}
// Regular text. // Regular text.
(text, false) => { EscapeSequence::Text(text) => {
let text = self.preprocess( let text = self.preprocess(
text.trim_end_matches(|c| c == '\r' || c == '\n'), text.trim_end_matches(|c| c == '\r' || c == '\n'),
&mut cursor_total, &mut cursor_total,
@@ -654,7 +749,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
// It wraps. // It wraps.
write!( write!(
handle, handle,
"{}\n{}", "{}{}\n{}",
as_terminal_escaped( as_terminal_escaped(
style, style,
&format!("{}{}", self.ansi_style, line_buf), &format!("{}{}", self.ansi_style, line_buf),
@@ -663,6 +758,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
self.config.use_italic_text, self.config.use_italic_text,
background_color background_color
), ),
self.ansi_style.to_reset_sequence(),
panel_wrap.clone().unwrap() panel_wrap.clone().unwrap()
)?; )?;
@@ -691,6 +787,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
) )
)?; )?;
} }
// ANSI escape passthrough.
_ => {
write!(handle, "{}", chunk.raw())?;
self.ansi_style.update(chunk);
}
} }
} }
} }
@@ -711,8 +813,8 @@ impl<'a> Printer for InteractivePrinter<'a> {
} }
if highlight_this_line && self.config.theme == "ansi" { if highlight_this_line && self.config.theme == "ansi" {
self.ansi_style.update("^[24m"); write!(handle, "{}", ANSI_UNDERLINE_DISABLE.raw())?;
write!(handle, "\x1B[24m")?; self.ansi_style.update(ANSI_UNDERLINE_DISABLE);
} }
Ok(()) Ok(())

View File

@@ -80,7 +80,7 @@ impl FromStr for StyleComponent {
"full" => Ok(StyleComponent::Full), "full" => Ok(StyleComponent::Full),
"default" => Ok(StyleComponent::Default), "default" => Ok(StyleComponent::Default),
"plain" => Ok(StyleComponent::Plain), "plain" => Ok(StyleComponent::Plain),
_ => Err(format!("Unknown style '{}'", s).into()), _ => Err(format!("Unknown style '{s}'").into()),
} }
} }
} }

View File

@@ -1,12 +1,31 @@
use std::path::Path; use std::{
path::Path,
use crate::error::Result; sync::{
use ignored_suffixes::IgnoredSuffixes; atomic::{AtomicBool, Ordering},
Arc,
},
thread,
};
use globset::{Candidate, GlobBuilder, GlobMatcher}; use globset::{Candidate, GlobBuilder, GlobMatcher};
use once_cell::sync::Lazy;
use crate::error::Result;
use builtin::BUILTIN_MAPPINGS;
use ignored_suffixes::IgnoredSuffixes;
mod builtin;
pub mod ignored_suffixes; pub mod ignored_suffixes;
fn make_glob_matcher(from: &str) -> Result<GlobMatcher> {
let matcher = GlobBuilder::new(from)
.case_insensitive(true)
.literal_separator(true)
.build()?
.compile_matcher();
Ok(matcher)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
pub enum MappingTarget<'a> { pub enum MappingTarget<'a> {
@@ -29,204 +48,108 @@ pub enum MappingTarget<'a> {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct SyntaxMapping<'a> { pub struct SyntaxMapping<'a> {
mappings: Vec<(GlobMatcher, MappingTarget<'a>)>, /// User-defined mappings at run time.
///
/// Rules in front have precedence.
custom_mappings: Vec<(GlobMatcher, MappingTarget<'a>)>,
pub(crate) ignored_suffixes: IgnoredSuffixes<'a>, pub(crate) ignored_suffixes: IgnoredSuffixes<'a>,
/// A flag to halt glob matcher building, which is offloaded to another thread.
///
/// We have this so that we can signal the thread to halt early when appropriate.
halt_glob_build: Arc<AtomicBool>,
}
impl<'a> Drop for SyntaxMapping<'a> {
fn drop(&mut self) {
// signal the offload thread to halt early
self.halt_glob_build.store(true, Ordering::Relaxed);
}
} }
impl<'a> SyntaxMapping<'a> { impl<'a> SyntaxMapping<'a> {
pub fn empty() -> SyntaxMapping<'a> { pub fn new() -> SyntaxMapping<'a> {
Default::default() Default::default()
} }
pub fn builtin() -> SyntaxMapping<'a> { /// Start a thread to build the glob matchers for all builtin mappings.
let mut mapping = Self::empty(); ///
mapping.insert("*.h", MappingTarget::MapTo("C++")).unwrap(); /// The use of this function while not necessary, is useful to speed up startup
mapping /// times by starting this work early in parallel.
.insert(".clang-format", MappingTarget::MapTo("YAML")) ///
.unwrap(); /// The thread halts if/when `halt_glob_build` is set to true.
mapping.insert("*.fs", MappingTarget::MapTo("F#")).unwrap(); pub fn start_offload_build_all(&self) {
mapping let halt = Arc::clone(&self.halt_glob_build);
.insert("build", MappingTarget::MapToUnknown) thread::spawn(move || {
.unwrap(); for (matcher, _) in BUILTIN_MAPPINGS.iter() {
mapping if halt.load(Ordering::Relaxed) {
.insert("**/.ssh/config", MappingTarget::MapTo("SSH Config")) break;
.unwrap(); }
mapping Lazy::force(matcher);
.insert(
"**/bat/config",
MappingTarget::MapTo("Bourne Again Shell (bash)"),
)
.unwrap();
mapping
.insert(
"/etc/profile",
MappingTarget::MapTo("Bourne Again Shell (bash)"),
)
.unwrap();
mapping
.insert(
"os-release",
MappingTarget::MapTo("Bourne Again Shell (bash)"),
)
.unwrap();
mapping
.insert("*.pac", MappingTarget::MapTo("JavaScript (Babel)"))
.unwrap();
mapping
.insert("fish_history", MappingTarget::MapTo("YAML"))
.unwrap();
for glob in ["*.jsonl", "*.sarif"] {
mapping.insert(glob, MappingTarget::MapTo("JSON")).unwrap();
}
// See #2151, https://nmap.org/book/nse-language.html
mapping
.insert("*.nse", MappingTarget::MapTo("Lua"))
.unwrap();
// See #1008
mapping
.insert("rails", MappingTarget::MapToUnknown)
.unwrap();
mapping
.insert("Containerfile", MappingTarget::MapTo("Dockerfile"))
.unwrap();
mapping
.insert("*.ksh", MappingTarget::MapTo("Bourne Again Shell (bash)"))
.unwrap();
// Nginx and Apache syntax files both want to style all ".conf" files
// see #1131 and #1137
mapping
.insert("*.conf", MappingTarget::MapExtensionToUnknown)
.unwrap();
for glob in &[
"/etc/nginx/**/*.conf",
"/etc/nginx/sites-*/**/*",
"nginx.conf",
"mime.types",
] {
mapping.insert(glob, MappingTarget::MapTo("nginx")).unwrap();
}
for glob in &[
"/etc/apache2/**/*.conf",
"/etc/apache2/sites-*/**/*",
"httpd.conf",
] {
mapping
.insert(glob, MappingTarget::MapTo("Apache Conf"))
.unwrap();
}
for glob in &[
"**/systemd/**/*.conf",
"**/systemd/**/*.example",
"*.automount",
"*.device",
"*.dnssd",
"*.link",
"*.mount",
"*.netdev",
"*.network",
"*.nspawn",
"*.path",
"*.service",
"*.scope",
"*.slice",
"*.socket",
"*.swap",
"*.target",
"*.timer",
] {
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"))
.unwrap();
mapping
.insert("*.ron", MappingTarget::MapTo("Rust"))
.unwrap();
// Global git config files rooted in `$XDG_CONFIG_HOME/git/` or `$HOME/.config/git/`
// See e.g. https://git-scm.com/docs/git-config#FILES
match (
std::env::var_os("XDG_CONFIG_HOME").filter(|val| !val.is_empty()),
std::env::var_os("HOME")
.filter(|val| !val.is_empty())
.map(|home| Path::new(&home).join(".config")),
) {
(Some(xdg_config_home), Some(default_config_home))
if xdg_config_home == default_config_home => {
insert_git_config_global(&mut mapping, &xdg_config_home)
} }
(Some(xdg_config_home), Some(default_config_home)) /* else guard */ => { });
insert_git_config_global(&mut mapping, &xdg_config_home); // Note that this thread is not joined upon completion because there's
insert_git_config_global(&mut mapping, &default_config_home) // no shared resources that need synchronization to be safely dropped.
} // If we later add code into this thread that requires interesting
(Some(config_home), None) => insert_git_config_global(&mut mapping, &config_home), // resources (e.g. IO), it would be a good idea to store the handle
(None, Some(config_home)) => insert_git_config_global(&mut mapping, &config_home), // and join it on drop.
(None, None) => (),
};
fn insert_git_config_global(mapping: &mut SyntaxMapping, config_home: impl AsRef<Path>) {
let git_config_path = config_home.as_ref().join("git");
mapping
.insert(
&git_config_path.join("config").to_string_lossy(),
MappingTarget::MapTo("Git Config"),
)
.ok();
mapping
.insert(
&git_config_path.join("ignore").to_string_lossy(),
MappingTarget::MapTo("Git Ignore"),
)
.ok();
mapping
.insert(
&git_config_path.join("attributes").to_string_lossy(),
MappingTarget::MapTo("Git Attributes"),
)
.ok();
}
mapping
} }
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> { pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
let glob = GlobBuilder::new(from) let matcher = make_glob_matcher(from)?;
.case_insensitive(true) self.custom_mappings.push((matcher, to));
.literal_separator(true)
.build()?;
self.mappings.push((glob.compile_matcher(), to));
Ok(()) Ok(())
} }
pub fn mappings(&self) -> &[(GlobMatcher, MappingTarget<'a>)] { /// Returns an iterator over all mappings. User-defined mappings are listed
&self.mappings /// before builtin mappings; mappings in front have higher precedence.
///
/// Builtin mappings' `GlobMatcher`s are lazily compiled.
///
/// Note that this function only returns mappings that are valid under the
/// current environment. For details see [`Self::builtin_mappings`].
pub fn all_mappings(&self) -> impl Iterator<Item = (&GlobMatcher, &MappingTarget<'a>)> {
self.custom_mappings()
.iter()
.map(|(matcher, target)| (matcher, target)) // as_ref
.chain(
// we need a map with a closure to "do" the lifetime variance
// see: https://discord.com/channels/273534239310479360/1120124565591425034/1170543402870382653
// also, clippy false positive:
// see: https://github.com/rust-lang/rust-clippy/issues/9280
#[allow(clippy::map_identity)]
self.builtin_mappings().map(|rule| rule),
)
} }
pub(crate) fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> { /// Returns an iterator over all valid builtin mappings. Mappings in front
/// have higher precedence.
///
/// The `GlabMatcher`s are lazily compiled.
///
/// Mappings that are invalid under the current environment (i.e. rule
/// requires environment variable(s) that is unset, or the joined string
/// after variable(s) replacement is not a valid glob expression) are
/// ignored.
pub fn builtin_mappings(
&self,
) -> impl Iterator<Item = (&'static GlobMatcher, &'static MappingTarget<'static>)> {
BUILTIN_MAPPINGS
.iter()
.filter_map(|(matcher, target)| matcher.as_ref().map(|glob| (glob, target)))
}
/// Returns all user-defined mappings.
pub fn custom_mappings(&self) -> &[(GlobMatcher, MappingTarget<'a>)] {
&self.custom_mappings
}
pub fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> {
// Try matching on the file name as-is. // Try matching on the file name as-is.
let candidate = Candidate::new(&path); let candidate = Candidate::new(&path);
let candidate_filename = path.as_ref().file_name().map(Candidate::new); let candidate_filename = path.as_ref().file_name().map(Candidate::new);
for (ref glob, ref syntax) in self.mappings.iter().rev() { for (glob, syntax) in self.all_mappings() {
if glob.is_match_candidate(&candidate) if glob.is_match_candidate(&candidate)
|| candidate_filename || candidate_filename
.as_ref() .as_ref()
@@ -252,9 +175,46 @@ impl<'a> SyntaxMapping<'a> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
fn basic() { fn builtin_mappings_work() {
let mut map = SyntaxMapping::empty(); let map = SyntaxMapping::new();
assert_eq!(
map.get_syntax_for("/path/to/build"),
Some(MappingTarget::MapToUnknown)
);
}
#[test]
fn all_fixed_builtin_mappings_can_compile() {
let map = SyntaxMapping::new();
// collect call evaluates all lazy closures
// fixed builtin mappings will panic if they fail to compile
let _mappings = map.builtin_mappings().collect::<Vec<_>>();
}
#[test]
fn builtin_mappings_matcher_only_compile_once() {
let map = SyntaxMapping::new();
let two_iterations: Vec<_> = (0..2)
.map(|_| {
// addresses of every matcher
map.builtin_mappings()
.map(|(matcher, _)| matcher as *const _ as usize)
.collect::<Vec<_>>()
})
.collect();
// if the matchers are only compiled once, their address should remain the same
assert_eq!(two_iterations[0], two_iterations[1]);
}
#[test]
fn custom_mappings_work() {
let mut map = SyntaxMapping::new();
map.insert("/path/to/Cargo.lock", MappingTarget::MapTo("TOML")) map.insert("/path/to/Cargo.lock", MappingTarget::MapTo("TOML"))
.ok(); .ok();
map.insert("/path/to/.ignore", MappingTarget::MapTo("Git Ignore")) map.insert("/path/to/.ignore", MappingTarget::MapTo("Git Ignore"))
@@ -273,52 +233,32 @@ mod tests {
} }
#[test] #[test]
fn user_can_override_builtin_mappings() { fn custom_mappings_override_builtin() {
let mut map = SyntaxMapping::builtin(); let mut map = SyntaxMapping::new();
assert_eq!( assert_eq!(
map.get_syntax_for("/etc/profile"), map.get_syntax_for("/path/to/httpd.conf"),
Some(MappingTarget::MapTo("Bourne Again Shell (bash)")) Some(MappingTarget::MapTo("Apache Conf"))
); );
map.insert("/etc/profile", MappingTarget::MapTo("My Syntax")) map.insert("httpd.conf", MappingTarget::MapTo("My Syntax"))
.ok(); .ok();
assert_eq!( assert_eq!(
map.get_syntax_for("/etc/profile"), map.get_syntax_for("/path/to/httpd.conf"),
Some(MappingTarget::MapTo("My Syntax")) Some(MappingTarget::MapTo("My Syntax"))
); );
} }
#[test] #[test]
fn builtin_mappings() { fn custom_mappings_precedence() {
let map = SyntaxMapping::builtin(); let mut map = SyntaxMapping::new();
map.insert("/path/to/foo", MappingTarget::MapTo("alpha"))
.ok();
map.insert("/path/to/foo", MappingTarget::MapTo("bravo"))
.ok();
assert_eq!( assert_eq!(
map.get_syntax_for("/path/to/build"), map.get_syntax_for("/path/to/foo"),
Some(MappingTarget::MapToUnknown) Some(MappingTarget::MapTo("alpha"))
); );
} }
#[test]
/// verifies that SyntaxMapping::builtin() doesn't repeat `Glob`-based keys
fn no_duplicate_builtin_keys() {
let mappings = SyntaxMapping::builtin().mappings;
for i in 0..mappings.len() {
let tail = mappings[i + 1..].into_iter();
let (dupl, _): (Vec<_>, Vec<_>) =
tail.partition(|item| item.0.glob() == mappings[i].0.glob());
// emit repeats on failure
assert_eq!(
dupl.len(),
0,
"Glob pattern `{}` mapped to multiple: {:?}",
mappings[i].0.glob().glob(),
{
let (_, mut dupl_targets): (Vec<GlobMatcher>, Vec<MappingTarget>) =
dupl.into_iter().cloned().unzip();
dupl_targets.push(mappings[i].1)
},
)
}
}
} }

View File

@@ -0,0 +1,91 @@
use std::env;
use globset::GlobMatcher;
use once_cell::sync::Lazy;
use crate::syntax_mapping::{make_glob_matcher, MappingTarget};
// Static syntax mappings generated from /src/syntax_mapping/builtins/ by the
// build script (/build/syntax_mapping.rs).
include!(concat!(
env!("OUT_DIR"),
"/codegen_static_syntax_mappings.rs"
));
// The defined matcher strings are analysed at compile time and converted into
// lazily-compiled `GlobMatcher`s. This is so that the string searches are moved
// from run time to compile time, thus improving startup performance.
//
// To any future maintainer (including possibly myself) wondering why there is
// not a `BuiltinMatcher` enum that looks like this:
//
// ```
// enum BuiltinMatcher {
// Fixed(&'static str),
// Dynamic(Lazy<Option<String>>),
// }
// ```
//
// Because there was. I tried it and threw it out.
//
// Naively looking at the problem from a distance, this may seem like a good
// design (strongly typed etc. etc.). It would also save on compiled size by
// extracting out common behaviour into functions. But while actually
// implementing the lazy matcher compilation logic, I realised that it's most
// convenient for `BUILTIN_MAPPINGS` to have the following type:
//
// `[(Lazy<Option<GlobMatcher>>, MappingTarget); N]`
//
// The benefit for this is that operations like listing all builtin mappings
// would be effectively memoised. The caller would not have to compile another
// `GlobMatcher` for rules that they have previously visited.
//
// Unfortunately, this means we are going to have to store a distinct closure
// for each rule anyway, which makes a `BuiltinMatcher` enum a pointless layer
// of indirection.
//
// In the current implementation, the closure within each generated rule simply
// calls either `build_matcher_fixed` or `build_matcher_dynamic`, depending on
// whether the defined matcher contains dynamic segments or not.
/// Compile a fixed glob string into a glob matcher.
///
/// A failure to compile is a fatal error.
///
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
fn build_matcher_fixed(from: &str) -> GlobMatcher {
make_glob_matcher(from).expect("A builtin fixed glob matcher failed to compile")
}
/// Join a list of matcher segments to create a glob string, replacing all
/// environment variables, then compile to a glob matcher.
///
/// Returns `None` if any replacement fails, or if the joined glob string fails
/// to compile.
///
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option<GlobMatcher> {
// join segments
let mut buf = String::new();
for seg in segs {
match seg {
MatcherSegment::Text(s) => buf.push_str(s),
MatcherSegment::Env(var) => {
let replaced = env::var(var).ok()?;
buf.push_str(&replaced);
}
}
}
// compile glob matcher
let matcher = make_glob_matcher(&buf).ok()?;
Some(matcher)
}
/// A segment of a dynamic builtin matcher.
///
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
#[derive(Clone, Debug)]
enum MatcherSegment {
Text(&'static str),
Env(&'static str),
}

View File

@@ -0,0 +1,116 @@
# `/src/syntax_mapping/builtins`
The files in this directory define path/name-based syntax mappings, which amend
and take precedence over the extension/content-based syntax mappings provided by
[syntect](https://github.com/trishume/syntect).
## File organisation
Each TOML file should describe the syntax mappings of a single application, or
otherwise a set of logically-related rules.
What defines "a single application" here is deliberately vague, since the
file-splitting is purely for maintainability reasons. (Technically, we could
just as well use a single TOML file.) So just use common sense.
TOML files should reside in the corresponding subdirectory of the platform(s)
that they intend to target. At compile time, the build script will go through
each subdirectory that is applicable to the compilation target, collect the
syntax mappings defined by all TOML files, and embed them into the binary.
## File syntax
Each TOML file should contain a single section named `mappings`, with each of
its keys being a language identifier (first column of `bat -L`; also referred to
as "target").
The value of each key should be an array of strings, with each item being a glob
matcher. We will call each of these items a "rule".
For example, if `foo-application` uses both TOML and YAML configuration files,
we could write something like this:
```toml
# 30-foo-application.toml
[mappings]
"TOML" = [
# rules for TOML syntax go here
"/usr/share/foo-application/toml-config/*.conf",
"/etc/foo-application/toml-config/*.conf",
]
"YAML" = [
# rules for YAML syntax go here
# ...
]
```
### Dynamic environment variable replacement
In additional to the standard glob matcher syntax, rules also support dynamic
replacement of environment variables at runtime. This allows us to concisely
handle things like [XDG](https://specifications.freedesktop.org/basedir-spec/latest/).
All environment variables intended to be replaced at runtime must be enclosed in
`${}`, for example `"/foo/*/${YOUR_ENV}-suffix/*.log"`. Note that this is the
**only** admissible syntax; other variable substitution syntaxes are not
supported and will either cause a compile time error, or be treated as plain
text.
For example, if `foo-application` also supports per-user configuration files, we
could write something like this:
```toml
# 30-foo-application.toml
[mappings]
"TOML" = [
# rules for TOML syntax go here
"/usr/share/foo-application/toml-config/*.conf",
"/etc/foo-application/toml-config/*.conf",
"${XDG_CONFIG_HOME}/foo-application/toml-config/*.conf",
"${HOME}/.config/foo-application/toml-config/*.conf",
]
"YAML" = [
# rules for YAML syntax go here
# ...
]
```
If any environment variable replacement in a rule fails (for example when a
variable is unset), or if the glob string after replacements is invalid, the
entire rule will be ignored.
### Explicitly mapping to unknown
Sometimes it may be necessary to "unset" a particular syntect mapping - perhaps
a syntax's matching rules are "too greedy", and is claiming files that it should
not. In this case, there are two special identifiers:
`MappingTarget::MapToUnknown` and `MappingTarget::MapExtensionToUnknown`
(corresponding to the two variants of the `syntax_mapping::MappingTarget` enum).
An example of this would be `*.conf` files in general. So we may write something
like this:
```toml
# 99-unset-ambiguous-extensions.toml
[mappings]
"MappingTarget::MapExtensionToUnknown" = [
"*.conf",
]
```
## Ordering
At compile time, all TOML files applicable to the target are processed in
lexicographical filename order. So `00-foo.toml` takes precedence over
`10-bar.toml`, which takes precedence over `20-baz.toml`, and so on. Note that
**only** the filenames of the TOML files are taken into account; the
subdirectories they are placed in have no influence on ordering.
This behaviour can be occasionally useful for creating high/low priority rules,
such as in the aforementioned example of explicitly mapping `*.conf` files to
unknown. Generally this should not be much of a concern though, since rules
should be written as specifically as possible for each application.
Rules within each TOML file are processed (and therefore matched) in the order
in which they are defined. At runtime, the syntax selection algorithm will
short-circuit and return the target of the first matching rule.

View File

@@ -0,0 +1,2 @@
[mappings]
"Bourne Again Shell (bash)" = ["/etc/os-release", "/var/run/os-release"]

View File

@@ -0,0 +1,2 @@
[mappings]
"Apache Conf" = ["httpd.conf"]

View File

@@ -0,0 +1,2 @@
[mappings]
"INI" = ["**/.aws/credentials", "**/.aws/config"]

View File

@@ -0,0 +1,2 @@
[mappings]
"Bourne Again Shell (bash)" = ["**/bat/config"]

View File

@@ -0,0 +1,2 @@
[mappings]
"Dockerfile" = ["Containerfile"]

View File

@@ -0,0 +1,6 @@
[mappings]
"C++" = [
# probably better than the default Objective C mapping #877
"*.h",
]
"YAML" = [".clang-format"]

View File

@@ -0,0 +1,2 @@
[mappings]
"F#" = ["*.fs"]

View File

@@ -0,0 +1,10 @@
# Global git config files rooted in `$XDG_CONFIG_HOME/git/` or `$HOME/.config/git/`
# See e.g. https://git-scm.com/docs/git-config#FILES
[mappings]
"Git Config" = ["${XDG_CONFIG_HOME}/git/config", "${HOME}/.config/git/config"]
"Git Ignore" = ["${XDG_CONFIG_HOME}/git/ignore", "${HOME}/.config/git/ignore"]
"Git Attributes" = [
"${XDG_CONFIG_HOME}/git/attributes",
"${HOME}/.config/git/attributes",
]

View File

@@ -0,0 +1,3 @@
# JSON Lines is a simple variation of JSON #2535
[mappings]
"JSON" = ["*.jsonl", "*.jsonc"]

View File

@@ -0,0 +1,2 @@
[mappings]
"nginx" = ["nginx.conf", "mime.types"]

View File

@@ -0,0 +1,3 @@
[mappings]
# See #2151, https://nmap.org/book/nse-language.html
"Lua" = ["*.nse"]

View File

@@ -0,0 +1,3 @@
# 1515
[mappings]
"JavaScript (Babel)" = ["*.pac"]

View File

@@ -0,0 +1,3 @@
# Rusty Object Notation #2427
[mappings]
"Rust" = ["*.ron"]

View File

@@ -0,0 +1,3 @@
# SARIF is a format for reporting static analysis results #2695
[mappings]
"JSON" = ["*.sarif"]

View File

@@ -0,0 +1,2 @@
[mappings]
"SSH Config" = ["**/.ssh/config"]

View File

@@ -0,0 +1,5 @@
[mappings]
"MappingTarget::MapExtensionToUnknown" = [
# common extension used for all kinds of formats
"*.conf",
]

View File

@@ -0,0 +1,7 @@
[mappings]
"MappingTarget::MapToUnknown" = [
# "NAnt Build File" should only match *.build files, not files named "build"
"build",
# "bin/rails" scripts in a Ruby project misidentified as HTML (Rails) #1008
"rails",
]

View File

@@ -0,0 +1,3 @@
# Xonsh shell (https://xon.sh/)
[mappings]
"Python" = ["*.xsh", "*.xonshrc"]

View File

@@ -0,0 +1,8 @@
# see https://github.com/containers/image/tree/main/docs
[mappings]
"TOML" = [
"/usr/share/containers/**/*.conf",
"/etc/containers/**/*.conf",
"${HOME}/.config/containers/**/*.conf",
"${XDG_CONFIG_HOME}/containers/**/*.conf",
]

View File

@@ -0,0 +1,7 @@
[mappings]
"Bourne Again Shell (bash)" = [
"/etc/os-release",
"/usr/lib/os-release",
"/etc/initrd-release",
"/usr/lib/extension-release.d/extension-release.*",
]

View File

@@ -0,0 +1,3 @@
[mappings]
# pacman hooks
"INI" = ["/usr/share/libalpm/hooks/*.hook", "/etc/pacman.d/hooks/*.hook"]

View File

@@ -0,0 +1,7 @@
# see `man quadlet`
[mappings]
"INI" = [
"**/containers/systemd/*.{container,volume,network,kube,image}",
"**/containers/systemd/users/*.{container,volume,network,kube,image}",
"**/containers/systemd/users/*/*.{container,volume,network,kube,image}",
]

View File

@@ -0,0 +1,21 @@
[mappings]
"INI" = [
"**/systemd/**/*.conf",
"**/systemd/**/*.example",
"*.automount",
"*.device",
"*.dnssd",
"*.link",
"*.mount",
"*.netdev",
"*.network",
"*.nspawn",
"*.path",
"*.service",
"*.scope",
"*.slice",
"*.socket",
"*.swap",
"*.target",
"*.timer",
]

View File

@@ -0,0 +1,2 @@
[mappings]
"Apache Conf" = ["/etc/apache2/**/*.conf", "/etc/apache2/sites-*/**/*"]

View File

@@ -0,0 +1,2 @@
[mappings]
"YAML" = ["fish_history"]

View File

@@ -0,0 +1,3 @@
# KornShell is backward-compatible with the Bourne shell #2633
[mappings]
"Bourne Again Shell (bash)" = ["*.ksh"]

View File

@@ -0,0 +1,2 @@
[mappings]
"Email" = ["/var/spool/mail/*", "/var/mail/*"]

View File

@@ -0,0 +1,2 @@
[mappings]
"nginx" = ["/etc/nginx/**/*.conf", "/etc/nginx/sites-*/**/*"]

View File

@@ -0,0 +1,5 @@
[mappings]
"Bourne Again Shell (bash)" = [
# used by lots of shells
"/etc/profile",
]

View File

@@ -0,0 +1,3 @@
# see `man wg-quick`
[mappings]
"INI" = ["/etc/wireguard/*.conf"]

View File

@@ -73,7 +73,7 @@ fn internal_suffixes() {
let file_names = ignored_suffixes let file_names = ignored_suffixes
.values .values
.iter() .iter()
.map(|suffix| format!("test.json{}", suffix)); .map(|suffix| format!("test.json{suffix}"));
for file_name_str in file_names { for file_name_str in file_names {
let file_name = OsStr::new(&file_name_str); let file_name = OsStr::new(&file_name_str);
let expected_stripped_file_name = OsStr::new("test.json"); let expected_stripped_file_name = OsStr::new("test.json");
@@ -95,7 +95,7 @@ fn external_suffixes() {
let file_names = ignored_suffixes let file_names = ignored_suffixes
.values .values
.iter() .iter()
.map(|suffix| format!("test.json{}", suffix)); .map(|suffix| format!("test.json{suffix}"));
for file_name_str in file_names { for file_name_str in file_names {
let file_name = OsStr::new(&file_name_str); let file_name = OsStr::new(&file_name_str);
let expected_stripped_file_name = OsStr::new("test.json"); let expected_stripped_file_name = OsStr::new("test.json");

View File

@@ -1,4 +1,8 @@
use std::fmt::{Display, Formatter}; use std::{
fmt::{Display, Formatter},
iter::Peekable,
str::CharIndices,
};
// Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences. // Wrapper to avoid unnecessary branching when input doesn't have ANSI escape sequences.
pub struct AnsiStyle { pub struct AnsiStyle {
@@ -10,7 +14,7 @@ impl AnsiStyle {
AnsiStyle { attributes: None } AnsiStyle { attributes: None }
} }
pub fn update(&mut self, sequence: &str) -> bool { pub fn update(&mut self, sequence: EscapeSequence) -> bool {
match &mut self.attributes { match &mut self.attributes {
Some(a) => a.update(sequence), Some(a) => a.update(sequence),
None => { None => {
@@ -19,6 +23,13 @@ impl AnsiStyle {
} }
} }
} }
pub fn to_reset_sequence(&self) -> String {
match self.attributes {
Some(ref a) => a.to_reset_sequence(),
None => String::new(),
}
}
} }
impl Display for AnsiStyle { impl Display for AnsiStyle {
@@ -31,6 +42,8 @@ impl Display for AnsiStyle {
} }
struct Attributes { struct Attributes {
has_sgr_sequences: bool,
foreground: String, foreground: String,
background: String, background: String,
underlined: String, underlined: String,
@@ -61,11 +74,20 @@ struct Attributes {
/// ON: ^[9m /// ON: ^[9m
/// OFF: ^[29m /// OFF: ^[29m
strike: String, strike: String,
/// The hyperlink sequence.
/// FORMAT: \x1B]8;{ID};{URL}\e\\
///
/// `\e\\` may be replaced with BEL `\x07`.
/// Setting both {ID} and {URL} to an empty string represents no hyperlink.
hyperlink: String,
} }
impl Attributes { impl Attributes {
pub fn new() -> Self { pub fn new() -> Self {
Attributes { Attributes {
has_sgr_sequences: false,
foreground: "".to_owned(), foreground: "".to_owned(),
background: "".to_owned(), background: "".to_owned(),
underlined: "".to_owned(), underlined: "".to_owned(),
@@ -76,34 +98,56 @@ impl Attributes {
underline: "".to_owned(), underline: "".to_owned(),
italic: "".to_owned(), italic: "".to_owned(),
strike: "".to_owned(), strike: "".to_owned(),
hyperlink: "".to_owned(),
} }
} }
/// Update the attributes with an escape sequence. /// Update the attributes with an escape sequence.
/// Returns `false` if the sequence is unsupported. /// Returns `false` if the sequence is unsupported.
pub fn update(&mut self, sequence: &str) -> bool { pub fn update(&mut self, sequence: EscapeSequence) -> bool {
let mut chars = sequence.char_indices().skip(1); use EscapeSequence::*;
match sequence {
if let Some((_, t)) = chars.next() { Text(_) => return false,
match t { Unknown(_) => { /* defer to update_with_unsupported */ }
'(' => self.update_with_charset('(', chars.map(|(_, c)| c)), OSC {
')' => self.update_with_charset(')', chars.map(|(_, c)| c)), raw_sequence,
'[' => { command,
if let Some((i, last)) = chars.last() { ..
// SAFETY: Always starts with ^[ and ends with m. } => {
self.update_with_csi(last, &sequence[2..i]) if command.starts_with("8;") {
} else { return self.update_with_hyperlink(raw_sequence);
false }
/* defer to update_with_unsupported */
}
CSI {
final_byte,
parameters,
..
} => {
match final_byte {
"m" => return self.update_with_sgr(parameters),
_ => {
// NOTE(eth-p): We might want to ignore these, since they involve cursor or buffer manipulation.
/* defer to update_with_unsupported */
} }
} }
_ => self.update_with_unsupported(sequence),
} }
} else { NF { nf_sequence, .. } => {
false let mut iter = nf_sequence.chars();
match iter.next() {
Some('(') => return self.update_with_charset('(', iter),
Some(')') => return self.update_with_charset(')', iter),
_ => { /* defer to update_with_unsupported */ }
}
}
} }
self.update_with_unsupported(sequence.raw())
} }
fn sgr_reset(&mut self) { fn sgr_reset(&mut self) {
self.has_sgr_sequences = false;
self.foreground.clear(); self.foreground.clear();
self.background.clear(); self.background.clear();
self.underlined.clear(); self.underlined.clear();
@@ -121,13 +165,14 @@ impl Attributes {
.map(|p| p.parse::<u16>()) .map(|p| p.parse::<u16>())
.map(|p| p.unwrap_or(0)); // Treat errors as 0. .map(|p| p.unwrap_or(0)); // Treat errors as 0.
self.has_sgr_sequences = true;
while let Some(p) = iter.next() { while let Some(p) = iter.next() {
match p { match p {
0 => self.sgr_reset(), 0 => self.sgr_reset(),
1 => self.bold = format!("\x1B[{}m", parameters), 1 => self.bold = "\x1B[1m".to_owned(),
2 => self.dim = format!("\x1B[{}m", parameters), 2 => self.dim = "\x1B[2m".to_owned(),
3 => self.italic = format!("\x1B[{}m", parameters), 3 => self.italic = "\x1B[3m".to_owned(),
4 => self.underline = format!("\x1B[{}m", parameters), 4 => self.underline = "\x1B[4m".to_owned(),
23 => self.italic.clear(), 23 => self.italic.clear(),
24 => self.underline.clear(), 24 => self.underline.clear(),
22 => { 22 => {
@@ -138,7 +183,7 @@ impl Attributes {
40..=49 => self.background = 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), 58..=59 => self.underlined = Self::parse_color(p, &mut iter),
90..=97 => self.foreground = 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), 100..=107 => self.background = Self::parse_color(p, &mut iter),
_ => { _ => {
// Unsupported SGR sequence. // Unsupported SGR sequence.
// Be compatible and pretend one just wasn't was provided. // Be compatible and pretend one just wasn't was provided.
@@ -149,19 +194,23 @@ impl Attributes {
true 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 { fn update_with_unsupported(&mut self, sequence: &str) -> bool {
self.unknown_buffer.push_str(sequence); self.unknown_buffer.push_str(sequence);
false false
} }
fn update_with_hyperlink(&mut self, sequence: &str) -> bool {
if sequence == "8;;" {
// Empty hyperlink ID and HREF -> end of hyperlink.
self.hyperlink.clear();
} else {
self.hyperlink.clear();
self.hyperlink.push_str(sequence);
}
true
}
fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool { fn update_with_charset(&mut self, kind: char, set: impl Iterator<Item = char>) -> bool {
self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>()); self.charset = format!("\x1B{}{}", kind, set.take(1).collect::<String>());
true true
@@ -172,20 +221,42 @@ impl Attributes {
8 => match parameters.next() { 8 => match parameters.next() {
Some(5) /* 256-color */ => format!("\x1B[{};5;{}m", color, join(";", 1, parameters)), 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(2) /* 24-bit color */ => format!("\x1B[{};2;{}m", color, join(";", 3, parameters)),
Some(c) => format!("\x1B[{};{}m", color, c), Some(c) => format!("\x1B[{color};{c}m"),
_ => "".to_owned(), _ => "".to_owned(),
}, },
9 => "".to_owned(), 9 => "".to_owned(),
_ => format!("\x1B[{}m", color), _ => format!("\x1B[{color}m"),
} }
} }
/// Gets an ANSI escape sequence to reset all the known attributes.
pub fn to_reset_sequence(&self) -> String {
let mut buf = String::with_capacity(17);
// TODO: Enable me in a later pull request.
// if self.has_sgr_sequences {
// buf.push_str("\x1B[m");
// }
if !self.hyperlink.is_empty() {
buf.push_str("\x1B]8;;\x1B\\"); // Disable hyperlink.
}
// TODO: Enable me in a later pull request.
// if !self.charset.is_empty() {
// // https://espterm.github.io/docs/VT100%20escape%20codes.html
// buf.push_str("\x1B(B\x1B)B"); // setusg0 and setusg1
// }
buf
}
} }
impl Display for Attributes { impl Display for Attributes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{}{}{}{}{}{}{}{}{}", "{}{}{}{}{}{}{}{}{}{}",
self.foreground, self.foreground,
self.background, self.background,
self.underlined, self.underlined,
@@ -195,6 +266,7 @@ impl Display for Attributes {
self.underline, self.underline,
self.italic, self.italic,
self.strike, self.strike,
self.hyperlink,
) )
} }
} }
@@ -210,3 +282,646 @@ fn join(
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(delimiter) .join(delimiter)
} }
/// A range of indices for a raw ANSI escape sequence.
#[derive(Debug, PartialEq)]
enum EscapeSequenceOffsets {
Text {
start: usize,
end: usize,
},
Unknown {
start: usize,
end: usize,
},
#[allow(clippy::upper_case_acronyms)]
NF {
// https://en.wikipedia.org/wiki/ANSI_escape_code#nF_Escape_sequences
start_sequence: usize,
start: usize,
end: usize,
},
#[allow(clippy::upper_case_acronyms)]
OSC {
// https://en.wikipedia.org/wiki/ANSI_escape_code#OSC_(Operating_System_Command)_sequences
start_sequence: usize,
start_command: usize,
start_terminator: usize,
end: usize,
},
#[allow(clippy::upper_case_acronyms)]
CSI {
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
start_sequence: usize,
start_parameters: usize,
start_intermediates: usize,
start_final_byte: usize,
end: usize,
},
}
/// An iterator over the offests of ANSI/VT escape sequences within a string.
///
/// ## Example
///
/// ```ignore
/// let iter = EscapeSequenceOffsetsIterator::new("\x1B[33mThis is yellow text.\x1B[m");
/// ```
struct EscapeSequenceOffsetsIterator<'a> {
text: &'a str,
chars: Peekable<CharIndices<'a>>,
}
impl<'a> EscapeSequenceOffsetsIterator<'a> {
pub fn new(text: &'a str) -> EscapeSequenceOffsetsIterator<'a> {
return EscapeSequenceOffsetsIterator {
text,
chars: text.char_indices().peekable(),
};
}
/// Takes values from the iterator while the predicate returns true.
/// If the predicate returns false, that value is left.
fn chars_take_while(&mut self, pred: impl Fn(char) -> bool) -> Option<(usize, usize)> {
self.chars.peek()?;
let start = self.chars.peek().unwrap().0;
let mut end: usize = start;
while let Some((i, c)) = self.chars.peek() {
if !pred(*c) {
break;
}
end = *i + c.len_utf8();
self.chars.next();
}
Some((start, end))
}
fn next_text(&mut self) -> Option<EscapeSequenceOffsets> {
self.chars_take_while(|c| c != '\x1B')
.map(|(start, end)| EscapeSequenceOffsets::Text { start, end })
}
fn next_sequence(&mut self) -> Option<EscapeSequenceOffsets> {
let (start_sequence, c) = self.chars.next().expect("to not be finished");
match self.chars.peek() {
None => Some(EscapeSequenceOffsets::Unknown {
start: start_sequence,
end: start_sequence + c.len_utf8(),
}),
Some((_, ']')) => self.next_osc(start_sequence),
Some((_, '[')) => self.next_csi(start_sequence),
Some((i, c)) => match c {
'\x20'..='\x2F' => self.next_nf(start_sequence),
c => Some(EscapeSequenceOffsets::Unknown {
start: start_sequence,
end: i + c.len_utf8(),
}),
},
}
}
fn next_osc(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
let (osc_open_index, osc_open_char) = self.chars.next().expect("to not be finished");
debug_assert_eq!(osc_open_char, ']');
let mut start_terminator: usize;
let mut end_sequence: usize;
loop {
match self.chars_take_while(|c| !matches!(c, '\x07' | '\x1B')) {
None => {
start_terminator = self.text.len();
end_sequence = start_terminator;
break;
}
Some((_, end)) => {
start_terminator = end;
end_sequence = end;
}
}
match self.chars.next() {
Some((ti, '\x07')) => {
end_sequence = ti + '\x07'.len_utf8();
break;
}
Some((ti, '\x1B')) => {
match self.chars.next() {
Some((i, '\\')) => {
end_sequence = i + '\\'.len_utf8();
break;
}
None => {
end_sequence = ti + '\x1B'.len_utf8();
break;
}
_ => {
// Repeat, since `\\`(anything) isn't a valid ST.
}
}
}
None => {
// Prematurely ends.
break;
}
Some((_, tc)) => {
panic!("this should not be reached: char {tc:?}")
}
}
}
Some(EscapeSequenceOffsets::OSC {
start_sequence,
start_command: osc_open_index + osc_open_char.len_utf8(),
start_terminator,
end: end_sequence,
})
}
fn next_csi(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
let (csi_open_index, csi_open_char) = self.chars.next().expect("to not be finished");
debug_assert_eq!(csi_open_char, '[');
let start_parameters: usize = csi_open_index + csi_open_char.len_utf8();
// Keep iterating while within the range of `0x30-0x3F`.
let mut start_intermediates: usize = start_parameters;
if let Some((_, end)) = self.chars_take_while(|c| matches!(c, '\x30'..='\x3F')) {
start_intermediates = end;
}
// Keep iterating while within the range of `0x20-0x2F`.
let mut start_final_byte: usize = start_intermediates;
if let Some((_, end)) = self.chars_take_while(|c| matches!(c, '\x20'..='\x2F')) {
start_final_byte = end;
}
// Take the last char.
let end_of_sequence = match self.chars.next() {
None => start_final_byte,
Some((i, c)) => i + c.len_utf8(),
};
Some(EscapeSequenceOffsets::CSI {
start_sequence,
start_parameters,
start_intermediates,
start_final_byte,
end: end_of_sequence,
})
}
fn next_nf(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
let (nf_open_index, nf_open_char) = self.chars.next().expect("to not be finished");
debug_assert!(matches!(nf_open_char, '\x20'..='\x2F'));
let start: usize = nf_open_index;
let mut end: usize = start;
// Keep iterating while within the range of `0x20-0x2F`.
match self.chars_take_while(|c| matches!(c, '\x20'..='\x2F')) {
Some((_, i)) => end = i,
None => {
return Some(EscapeSequenceOffsets::NF {
start_sequence,
start,
end,
})
}
}
// Get the final byte.
if let Some((i, c)) = self.chars.next() {
end = i + c.len_utf8()
}
Some(EscapeSequenceOffsets::NF {
start_sequence,
start,
end,
})
}
}
impl<'a> Iterator for EscapeSequenceOffsetsIterator<'a> {
type Item = EscapeSequenceOffsets;
fn next(&mut self) -> Option<Self::Item> {
match self.chars.peek() {
Some((_, '\x1B')) => self.next_sequence(),
Some((_, _)) => self.next_text(),
None => None,
}
}
}
/// An iterator over ANSI/VT escape sequences within a string.
///
/// ## Example
///
/// ```ignore
/// let iter = EscapeSequenceIterator::new("\x1B[33mThis is yellow text.\x1B[m");
/// ```
pub struct EscapeSequenceIterator<'a> {
text: &'a str,
offset_iter: EscapeSequenceOffsetsIterator<'a>,
}
impl<'a> EscapeSequenceIterator<'a> {
pub fn new(text: &'a str) -> EscapeSequenceIterator<'a> {
return EscapeSequenceIterator {
text,
offset_iter: EscapeSequenceOffsetsIterator::new(text),
};
}
}
impl<'a> Iterator for EscapeSequenceIterator<'a> {
type Item = EscapeSequence<'a>;
fn next(&mut self) -> Option<Self::Item> {
use EscapeSequenceOffsets::*;
self.offset_iter.next().map(|offsets| match offsets {
Unknown { start, end } => EscapeSequence::Unknown(&self.text[start..end]),
Text { start, end } => EscapeSequence::Text(&self.text[start..end]),
NF {
start_sequence,
start,
end,
} => EscapeSequence::NF {
raw_sequence: &self.text[start_sequence..end],
nf_sequence: &self.text[start..end],
},
OSC {
start_sequence,
start_command,
start_terminator,
end,
} => EscapeSequence::OSC {
raw_sequence: &self.text[start_sequence..end],
command: &self.text[start_command..start_terminator],
terminator: &self.text[start_terminator..end],
},
CSI {
start_sequence,
start_parameters,
start_intermediates,
start_final_byte,
end,
} => EscapeSequence::CSI {
raw_sequence: &self.text[start_sequence..end],
parameters: &self.text[start_parameters..start_intermediates],
intermediates: &self.text[start_intermediates..start_final_byte],
final_byte: &self.text[start_final_byte..end],
},
})
}
}
/// A parsed ANSI/VT100 escape sequence.
#[derive(Debug, PartialEq)]
pub enum EscapeSequence<'a> {
Text(&'a str),
Unknown(&'a str),
#[allow(clippy::upper_case_acronyms)]
NF {
raw_sequence: &'a str,
nf_sequence: &'a str,
},
#[allow(clippy::upper_case_acronyms)]
OSC {
raw_sequence: &'a str,
command: &'a str,
terminator: &'a str,
},
#[allow(clippy::upper_case_acronyms)]
CSI {
raw_sequence: &'a str,
parameters: &'a str,
intermediates: &'a str,
final_byte: &'a str,
},
}
impl<'a> EscapeSequence<'a> {
pub fn raw(&self) -> &'a str {
use EscapeSequence::*;
match *self {
Text(raw) => raw,
Unknown(raw) => raw,
NF { raw_sequence, .. } => raw_sequence,
OSC { raw_sequence, .. } => raw_sequence,
CSI { raw_sequence, .. } => raw_sequence,
}
}
}
#[cfg(test)]
mod tests {
use crate::vscreen::{
EscapeSequence, EscapeSequenceIterator, EscapeSequenceOffsets,
EscapeSequenceOffsetsIterator,
};
#[test]
fn test_escape_sequence_offsets_iterator_parses_text() {
let mut iter = EscapeSequenceOffsetsIterator::new("text");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_text_stops_at_esc() {
let mut iter = EscapeSequenceOffsetsIterator::new("text\x1B[ming");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_osc_with_bel() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]abc\x07");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::OSC {
start_sequence: 0,
start_command: 2,
start_terminator: 5,
end: 6,
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_osc_with_st() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]abc\x1B\\");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::OSC {
start_sequence: 0,
start_command: 2,
start_terminator: 5,
end: 7,
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_osc_thats_broken() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B]ab");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::OSC {
start_sequence: 0,
start_command: 2,
start_terminator: 4,
end: 4,
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_csi() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[m");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 2,
start_final_byte: 2,
end: 3
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1;34m");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 6,
start_final_byte: 6,
end: 7
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_csi_with_intermediates() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[$m");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 2,
start_final_byte: 3,
end: 4
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_csi_with_parameters_and_intermediates() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1$m");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 3,
start_final_byte: 4,
end: 5
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_csi_thats_broken() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 2,
start_final_byte: 2,
end: 2
})
);
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 3,
start_final_byte: 3,
end: 3
})
);
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[1$");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 0,
start_parameters: 2,
start_intermediates: 3,
start_final_byte: 4,
end: 4
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_nf() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B($0");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::NF {
start_sequence: 0,
start: 1,
end: 4
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_parses_nf_thats_broken() {
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B(");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::NF {
start_sequence: 0,
start: 1,
end: 1
})
);
}
#[test]
fn test_escape_sequence_offsets_iterator_iterates() {
let mut iter = EscapeSequenceOffsetsIterator::new("text\x1B[33m\x1B]OSC\x07\x1B(0");
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::Text { start: 0, end: 4 })
);
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::CSI {
start_sequence: 4,
start_parameters: 6,
start_intermediates: 8,
start_final_byte: 8,
end: 9
})
);
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::OSC {
start_sequence: 9,
start_command: 11,
start_terminator: 14,
end: 15
})
);
assert_eq!(
iter.next(),
Some(EscapeSequenceOffsets::NF {
start_sequence: 15,
start: 16,
end: 18
})
);
assert_eq!(iter.next(), None);
}
#[test]
fn test_escape_sequence_iterator_iterates() {
let mut iter = EscapeSequenceIterator::new("text\x1B[33m\x1B]OSC\x07\x1B]OSC\x1B\\\x1B(0");
assert_eq!(iter.next(), Some(EscapeSequence::Text("text")));
assert_eq!(
iter.next(),
Some(EscapeSequence::CSI {
raw_sequence: "\x1B[33m",
parameters: "33",
intermediates: "",
final_byte: "m",
})
);
assert_eq!(
iter.next(),
Some(EscapeSequence::OSC {
raw_sequence: "\x1B]OSC\x07",
command: "OSC",
terminator: "\x07",
})
);
assert_eq!(
iter.next(),
Some(EscapeSequence::OSC {
raw_sequence: "\x1B]OSC\x1B\\",
command: "OSC",
terminator: "\x1B\\",
})
);
assert_eq!(
iter.next(),
Some(EscapeSequence::NF {
raw_sequence: "\x1B(0",
nf_sequence: "(0",
})
);
assert_eq!(iter.next(), None);
}
#[test]
fn test_sgr_attributes_do_not_leak_into_wrong_field() {
let mut attrs = crate::vscreen::Attributes::new();
// Bold, Dim, Italic, Underline, Foreground, Background
attrs.update(EscapeSequence::CSI {
raw_sequence: "\x1B[1;2;3;4;31;41m",
parameters: "1;2;3;4;31;41",
intermediates: "",
final_byte: "m",
});
assert_eq!(attrs.bold, "\x1B[1m");
assert_eq!(attrs.dim, "\x1B[2m");
assert_eq!(attrs.italic, "\x1B[3m");
assert_eq!(attrs.underline, "\x1B[4m");
assert_eq!(attrs.foreground, "\x1B[31m");
assert_eq!(attrs.background, "\x1B[41m");
// Bold, Bright Foreground, Bright Background
attrs.sgr_reset();
attrs.update(EscapeSequence::CSI {
raw_sequence: "\x1B[1;94;103m",
parameters: "1;94;103",
intermediates: "",
final_byte: "m",
});
assert_eq!(attrs.bold, "\x1B[1m");
assert_eq!(attrs.foreground, "\x1B[94m");
assert_eq!(attrs.background, "\x1B[103m");
}
}

View File

@@ -9,6 +9,13 @@ if ! command -v hyperfine > /dev/null 2>&1; then
exit 1 exit 1
fi fi
# Check that jq is installed.
if ! command -v jq > /dev/null 2>&1; then
echo "'jq' does not seem to be installed."
echo "You can get it here: https://jqlang.github.io/jq/download/"
exit 1
fi
# Check that python3 is installed. # Check that python3 is installed.
if ! command -v python3 > /dev/null 2>&1; then if ! command -v python3 > /dev/null 2>&1; then
echo "'python3' does not seem to be installed." echo "'python3' does not seem to be installed."
@@ -49,7 +56,8 @@ REPORT="$RESULT_DIR/report.md"
TARGET_DIR="$(get_cargo_target_dir)" TARGET_DIR="$(get_cargo_target_dir)"
TARGET_RELEASE="${TARGET_DIR}/release/bat" TARGET_RELEASE="${TARGET_DIR}/release/bat"
WARMUP_COUNT=3 : ${WARMUP_COUNT:=3}
: ${RUN_COUNT:=10}
# Determine which target to benchmark. # Determine which target to benchmark.
BAT='' BAT=''
@@ -88,16 +96,28 @@ hyperfine \
"$(printf "%q" "$BAT") --no-config" \ "$(printf "%q" "$BAT") --no-config" \
--command-name "bat" \ --command-name "bat" \
--warmup "$WARMUP_COUNT" \ --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time.md" \ --export-markdown "$RESULT_DIR/startup-time.md" \
--export-json "$RESULT_DIR/startup-time.json" --export-json "$RESULT_DIR/startup-time.json"
cat "$RESULT_DIR/startup-time.md" >> "$REPORT" cat "$RESULT_DIR/startup-time.md" >> "$REPORT"
heading "Startup time without syntax highlighting"
hyperfine \
"$(printf "%q" "$BAT") --no-config startup-time-src/small-CpuInfo-file.cpuinfo" \
--command-name "bat … small-CpuInfo-file.cpuinfo" \
--warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-without-syntax-highlighting.md" \
--export-json "$RESULT_DIR/startup-time-without-syntax-highlighting.json"
cat "$RESULT_DIR/startup-time-without-syntax-highlighting.md" >> "$REPORT"
heading "Startup time with syntax highlighting" heading "Startup time with syntax highlighting"
hyperfine \ hyperfine \
"$(printf "%q" "$BAT") --no-config --color=always startup-time-src/small-CpuInfo-file.cpuinfo" \ "$(printf "%q" "$BAT") --no-config --color=always startup-time-src/small-CpuInfo-file.cpuinfo" \
--command-name "bat … small-CpuInfo-file.cpuinfo" \ --command-name "bat … --color=always small-CpuInfo-file.cpuinfo" \
--warmup "$WARMUP_COUNT" \ --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-with-syntax-highlighting.md" \ --export-markdown "$RESULT_DIR/startup-time-with-syntax-highlighting.md" \
--export-json "$RESULT_DIR/startup-time-with-syntax-highlighting.json" --export-json "$RESULT_DIR/startup-time-with-syntax-highlighting.json"
cat "$RESULT_DIR/startup-time-with-syntax-highlighting.md" >> "$REPORT" cat "$RESULT_DIR/startup-time-with-syntax-highlighting.md" >> "$REPORT"
@@ -108,16 +128,52 @@ hyperfine \
"$(printf "%q" "$BAT") --no-config --color=always startup-time-src/small-Markdown-file.md" \ "$(printf "%q" "$BAT") --no-config --color=always startup-time-src/small-Markdown-file.md" \
--command-name "bat … small-Markdown-file.md" \ --command-name "bat … small-Markdown-file.md" \
--warmup "$WARMUP_COUNT" \ --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-with-syntax-with-dependencies.md" \ --export-markdown "$RESULT_DIR/startup-time-with-syntax-with-dependencies.md" \
--export-json "$RESULT_DIR/startup-time-with-syntax-with-dependencies.json" --export-json "$RESULT_DIR/startup-time-with-syntax-with-dependencies.json"
cat "$RESULT_DIR/startup-time-with-syntax-with-dependencies.md" >> "$REPORT" cat "$RESULT_DIR/startup-time-with-syntax-with-dependencies.md" >> "$REPORT"
heading "Startup time with indeterminant syntax"
hyperfine \
"$(printf "%q" "$BAT") --no-config --color=always startup-time-src/mystery-file" \
--shell none \
--command-name 'bat … mystery-file' \
--warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-with-indeterminant-syntax.md" \
--export-json "$RESULT_DIR/startup-time-with-indeterminant-syntax.json"
cat "$RESULT_DIR/startup-time-with-indeterminant-syntax.md" >> "$REPORT"
heading "Startup time with manually set syntax"
hyperfine \
"$(printf "%q" "$BAT") --no-config --color=always --language=Dockerfile startup-time-src/mystery-file" \
--shell none \
--command-name 'bat … --language=Dockerfile mystery-file' \
--warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-with-manually-set-syntax.md" \
--export-json "$RESULT_DIR/startup-time-with-manually-set-syntax.json"
cat "$RESULT_DIR/startup-time-with-manually-set-syntax.md" >> "$REPORT"
heading "Startup time with mapped syntax"
hyperfine \
"$(printf "%q" "$BAT") --no-config --color=always startup-time-src/Containerfile" \
--shell none \
--command-name 'bat … Containerfile' \
--warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/startup-time-with-mapped-syntax.md" \
--export-json "$RESULT_DIR/startup-time-with-mapped-syntax.json"
cat "$RESULT_DIR/startup-time-with-mapped-syntax.md" >> "$REPORT"
heading "Plain-text speed" heading "Plain-text speed"
hyperfine \ hyperfine \
"$(printf "%q" "$BAT") --no-config --language=txt --style=plain highlighting-speed-src/numpy_test_multiarray.py" \ "$(printf "%q" "$BAT") --no-config --language=txt --style=plain highlighting-speed-src/numpy_test_multiarray.py" \
--command-name 'bat … --language=txt numpy_test_multiarray.py' \ --command-name 'bat … --language=txt numpy_test_multiarray.py' \
--warmup "$WARMUP_COUNT" \ --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/plain-text-speed.md" \ --export-markdown "$RESULT_DIR/plain-text-speed.md" \
--export-json "$RESULT_DIR/plain-text-speed.json" --export-json "$RESULT_DIR/plain-text-speed.json"
cat "$RESULT_DIR/plain-text-speed.md" >> "$REPORT" cat "$RESULT_DIR/plain-text-speed.md" >> "$REPORT"
@@ -129,6 +185,7 @@ for wrap in character never; do
heading "Syntax highlighting speed --wrap=${wrap}: \`$filename\`" heading "Syntax highlighting speed --wrap=${wrap}: \`$filename\`"
hyperfine --warmup "$WARMUP_COUNT" \ hyperfine --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
"$(printf "%q" "$BAT") --no-config --style=full --color=always --wrap=${wrap} --terminal-width=80 '$SRC'" \ "$(printf "%q" "$BAT") --no-config --style=full --color=always --wrap=${wrap} --terminal-width=80 '$SRC'" \
--command-name "bat … ${filename}" \ --command-name "bat … ${filename}" \
--export-markdown "$RESULT_DIR/syntax-highlighting-speed-${filename}.md" \ --export-markdown "$RESULT_DIR/syntax-highlighting-speed-${filename}.md" \
@@ -143,6 +200,7 @@ hyperfine \
"$(printf "%q" "$BAT") --no-config --language=txt --style=plain many-small-files/*.txt" \ "$(printf "%q" "$BAT") --no-config --language=txt --style=plain many-small-files/*.txt" \
--command-name 'bat … --language=txt *.txt' \ --command-name 'bat … --language=txt *.txt' \
--warmup "$WARMUP_COUNT" \ --warmup "$WARMUP_COUNT" \
--runs "$RUN_COUNT" \
--export-markdown "$RESULT_DIR/many-small-files-speed.md" \ --export-markdown "$RESULT_DIR/many-small-files-speed.md" \
--export-json "$RESULT_DIR/many-small-files-speed.json" --export-json "$RESULT_DIR/many-small-files-speed.json"
cat "$RESULT_DIR/many-small-files-speed.md" >> "$REPORT" cat "$RESULT_DIR/many-small-files-speed.md" >> "$REPORT"

View File

@@ -0,0 +1,3 @@
FROM docker.io/alpine:latest
COPY foo /root/bar
RUN sleep 60

View File

@@ -0,0 +1,3 @@
FROM docker.io/alpine:latest
COPY foo /root/bar
RUN sleep 60

BIN
tests/examples/control_characters.txt vendored Normal file

Binary file not shown.

30
tests/examples/empty_lines.txt vendored Normal file
View File

@@ -0,0 +1,30 @@
line 1
line 5
line 20
line 21
line 24
line 26
line 30

View File

@@ -0,0 +1 @@
]8;;http://example.com\This is a link]8;;\n

View File

@@ -0,0 +1 @@
The header is not broken

53
tests/github-actions.rs Normal file
View File

@@ -0,0 +1,53 @@
#[test]
fn all_jobs_not_missing_any_jobs() {
let yaml: serde_yaml::Value =
serde_yaml::from_reader(std::fs::File::open(".github/workflows/CICD.yml").unwrap())
.unwrap();
let jobs = yaml.get("jobs").unwrap();
// Get all jobs that all-jobs depends on:
//
// jobs:
// all-jobs:
// needs:
// - this
// - list
// - ...
let actual = jobs
.get("all-jobs")
.unwrap()
.get("needs")
.unwrap()
.as_sequence()
.unwrap();
// Get all jobs used in CI, except the ones we want to ignore:
//
// jobs:
// this: ...
// list: ...
// ...
let exceptions = [
"all-jobs", // 'all-jobs' should not reference itself
"winget", // only used when publishing a release
];
let expected = jobs
.as_mapping()
.unwrap()
.keys()
.filter_map(|k| {
if exceptions.contains(&k.as_str().unwrap_or_default()) {
None
} else {
Some(k)
}
})
.map(ToOwned::to_owned)
.collect::<Vec<_>>();
// Make sure they match
assert_eq!(
*actual, expected,
"`all-jobs` should depend on all other jobs"
);
}

View File

@@ -208,6 +208,70 @@ fn line_range_multiple() {
.stdout("line 1\nline 2\nline 4\n"); .stdout("line 1\nline 2\nline 4\n");
} }
#[test]
fn squeeze_blank() {
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.assert()
.success()
.stdout("line 1\n\nline 5\n\nline 20\nline 21\n\nline 24\n\nline 26\n\nline 30\n");
}
#[test]
fn squeeze_blank_line_numbers() {
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.arg("--decorations=always")
.arg("--number")
.assert()
.success()
.stdout(" 1 line 1\n 2 \n 5 line 5\n 6 \n 20 line 20\n 21 line 21\n 22 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 30 line 30\n");
}
#[test]
fn squeeze_limit() {
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.arg("--squeeze-limit=2")
.assert()
.success()
.stdout("line 1\n\n\nline 5\n\n\nline 20\nline 21\n\n\nline 24\n\nline 26\n\n\nline 30\n");
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.arg("--squeeze-limit=5")
.assert()
.success()
.stdout("line 1\n\n\n\nline 5\n\n\n\n\n\nline 20\nline 21\n\n\nline 24\n\nline 26\n\n\n\nline 30\n");
}
#[test]
fn squeeze_limit_line_numbers() {
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.arg("--squeeze-limit=2")
.arg("--decorations=always")
.arg("--number")
.assert()
.success()
.stdout(" 1 line 1\n 2 \n 3 \n 5 line 5\n 6 \n 7 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 30 line 30\n");
bat()
.arg("empty_lines.txt")
.arg("--squeeze-blank")
.arg("--squeeze-limit=5")
.arg("--decorations=always")
.arg("--number")
.assert()
.success()
.stdout(" 1 line 1\n 2 \n 3 \n 4 \n 5 line 5\n 6 \n 7 \n 8 \n 9 \n 10 \n 20 line 20\n 21 line 21\n 22 \n 23 \n 24 line 24\n 25 \n 26 line 26\n 27 \n 28 \n 29 \n 30 line 30\n");
}
#[test] #[test]
#[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)] #[cfg_attr(any(not(feature = "git"), target_os = "windows"), ignore)]
fn short_help() { fn short_help() {
@@ -936,6 +1000,18 @@ fn env_var_bat_paging() {
}); });
} }
#[test]
fn basic_set_terminal_title() {
bat()
.arg("--paging=always")
.arg("--set-terminal-title")
.arg("test.txt")
.assert()
.success()
.stdout("\u{1b}]0;bat: test.txt\x07hello world\n")
.stderr("");
}
#[test] #[test]
fn diagnostic_sanity_check() { fn diagnostic_sanity_check() {
bat() bat()
@@ -1163,6 +1239,20 @@ fn bom_stripped_when_no_color_and_not_loop_through() {
); );
} }
// Regression test for https://github.com/sharkdp/bat/issues/2541
#[test]
fn no_broken_osc_emit_with_line_wrapping() {
bat()
.arg("--color=always")
.arg("--decorations=never")
.arg("--wrap=character")
.arg("--terminal-width=40")
.arg("regression_tests/issue_2541.txt")
.assert()
.success()
.stdout(predicate::function(|s: &str| s.lines().count() == 1));
}
#[test] #[test]
fn can_print_file_named_cache() { fn can_print_file_named_cache() {
bat_with_config() bat_with_config()
@@ -1381,6 +1471,61 @@ fn header_full_binary() {
.stderr(""); .stderr("");
} }
#[test]
#[cfg(not(feature = "git"))]
fn header_narrow_terminal() {
bat()
.arg("--terminal-width=30")
.arg("--decorations=always")
.arg("this-file-path-is-really-long-and-would-have-broken-the-layout-of-the-header.txt")
.assert()
.success()
.stdout(
"\
─────┬────────────────────────
│ File: this-file-path-is
│ -really-long-and-would-
│ have-broken-the-layout-
│ of-the-header.txt
─────┼────────────────────────
1 │ The header is not broke
│ n
─────┴────────────────────────
",
)
.stderr("");
}
#[test]
fn header_very_narrow_terminal() {
bat()
.arg("--terminal-width=10")
.arg("--decorations=always")
.arg("this-file-path-is-really-long-and-would-have-broken-the-layout-of-the-header.txt")
.assert()
.success()
.stdout(
"\
──────────
File: this
-file-path
-is-really
-long-and-
would-have
-broken-th
e-layout-o
f-the-head
er.txt
──────────
The header
is not br
oken
──────────
",
)
.stderr("");
}
#[test] #[test]
#[cfg(feature = "git")] // Expected output assumes git is enabled #[cfg(feature = "git")] // Expected output assumes git is enabled
fn header_default() { fn header_default() {
@@ -1613,7 +1758,7 @@ fn do_not_panic_regression_tests() {
] { ] {
bat() bat()
.arg("--color=always") .arg("--color=always")
.arg(&format!("regression_tests/{}", filename)) .arg(&format!("regression_tests/{filename}"))
.assert() .assert()
.success(); .success();
} }
@@ -1626,7 +1771,7 @@ fn do_not_detect_different_syntax_for_stdin_and_files() {
let cmd_for_file = bat() let cmd_for_file = bat()
.arg("--color=always") .arg("--color=always")
.arg("--map-syntax=*.js:Markdown") .arg("--map-syntax=*.js:Markdown")
.arg(&format!("--file-name={}", file)) .arg(&format!("--file-name={file}"))
.arg("--style=plain") .arg("--style=plain")
.arg(file) .arg(file)
.assert() .assert()
@@ -1636,7 +1781,7 @@ fn do_not_detect_different_syntax_for_stdin_and_files() {
.arg("--color=always") .arg("--color=always")
.arg("--map-syntax=*.js:Markdown") .arg("--map-syntax=*.js:Markdown")
.arg("--style=plain") .arg("--style=plain")
.arg(&format!("--file-name={}", file)) .arg(&format!("--file-name={file}"))
.pipe_stdin(Path::new(EXAMPLES_DIR).join(file)) .pipe_stdin(Path::new(EXAMPLES_DIR).join(file))
.unwrap() .unwrap()
.assert() .assert()
@@ -1655,7 +1800,7 @@ fn no_first_line_fallback_when_mapping_to_invalid_syntax() {
bat() bat()
.arg("--color=always") .arg("--color=always")
.arg("--map-syntax=*.invalid-syntax:InvalidSyntax") .arg("--map-syntax=*.invalid-syntax:InvalidSyntax")
.arg(&format!("--file-name={}", file)) .arg(&format!("--file-name={file}"))
.arg("--style=plain") .arg("--style=plain")
.arg(file) .arg(file)
.assert() .assert()
@@ -1728,6 +1873,25 @@ fn show_all_with_caret_notation() {
.assert() .assert()
.stdout("hello·world^J\n├──┤^M^@^G^H^[") .stdout("hello·world^J\n├──┤^M^@^G^H^[")
.stderr(""); .stderr("");
bat()
.arg("--show-all")
.arg("--nonprintable-notation=caret")
.arg("control_characters.txt")
.assert()
.stdout("^@^A^B^C^D^E^F^G^H├─┤^J\n^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^Z^[^\\^]^^^_^?")
.stderr("");
}
#[test]
fn show_all_with_unicode() {
bat()
.arg("--show-all")
.arg("--nonprintable-notation=unicode")
.arg("control_characters.txt")
.assert()
.stdout("␀␁␂␃␄␅␆␇␈├─┤␊\n␋␌␍␎␏␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟␡")
.stderr("");
} }
#[test] #[test]
@@ -1834,7 +1998,7 @@ fn ansi_passthrough_emit() {
.arg("--paging=never") .arg("--paging=never")
.arg("--color=never") .arg("--color=never")
.arg("--terminal-width=80") .arg("--terminal-width=80")
.arg(format!("--wrap={}", wrapping)) .arg(format!("--wrap={wrapping}"))
.arg("--decorations=always") .arg("--decorations=always")
.arg("--style=plain") .arg("--style=plain")
.write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n") .write_stdin("\x1B[33mColor\nColor \x1B[m\nPlain\n")
@@ -1845,6 +2009,62 @@ fn ansi_passthrough_emit() {
} }
} }
// Ensure that a simple ANSI sequence passthrough is emitted properly on wrapped lines.
// This also helps ensure that escape sequences are counted as part of the visible characters when wrapping.
#[test]
fn ansi_sgr_emitted_when_wrapped() {
bat()
.arg("--paging=never")
.arg("--color=never")
.arg("--terminal-width=20")
.arg("--wrap=character")
.arg("--decorations=always")
.arg("--style=plain")
.write_stdin("\x1B[33mColor...............Also color.\n")
.assert()
.success()
.stdout("\x1B[33m\x1B[33mColor...............\n\x1B[33mAlso color.\n")
// FIXME: ~~~~~~~~ should not be emitted twice.
.stderr("");
}
// Ensure that a simple ANSI sequence passthrough is emitted properly on wrapped lines.
// This also helps ensure that escape sequences are counted as part of the visible characters when wrapping.
#[test]
fn ansi_hyperlink_emitted_when_wrapped() {
bat()
.arg("--paging=never")
.arg("--color=never")
.arg("--terminal-width=20")
.arg("--wrap=character")
.arg("--decorations=always")
.arg("--style=plain")
.write_stdin("\x1B]8;;http://example.com/\x1B\\Hyperlinks..........Wrap across lines.\n")
.assert()
.success()
.stdout("\x1B]8;;http://example.com/\x1B\\\x1B]8;;http://example.com/\x1B\\Hyperlinks..........\x1B]8;;\x1B\\\n\x1B]8;;http://example.com/\x1B\\Wrap across lines.\n")
// FIXME: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ should not be emitted twice.
.stderr("");
}
// Ensure that multiple ANSI sequence SGR attributes are combined when emitted on wrapped lines.
#[test]
fn ansi_sgr_joins_attributes_when_wrapped() {
bat()
.arg("--paging=never")
.arg("--color=never")
.arg("--terminal-width=20")
.arg("--wrap=character")
.arg("--decorations=always")
.arg("--style=plain")
.write_stdin("\x1B[33mColor. \x1B[1mBold.........Also bold and color.\n")
.assert()
.success()
.stdout("\x1B[33m\x1B[33mColor. \x1B[1m\x1B[33m\x1B[1mBold.........\n\x1B[33m\x1B[1mAlso bold and color.\n")
// FIXME: ~~~~~~~~ ~~~~~~~~~~~~~~~ should not be emitted twice.
.stderr("");
}
#[test] #[test]
fn ignored_suffix_arg() { fn ignored_suffix_arg() {
bat() bat()

View File

@@ -30,8 +30,7 @@ fn no_duplicate_extensions() {
for extension in &syntax.file_extensions { for extension in &syntax.file_extensions {
assert!( assert!(
KNOWN_EXCEPTIONS.contains(&extension.as_str()) || extensions.insert(extension), KNOWN_EXCEPTIONS.contains(&extension.as_str()) || extensions.insert(extension),
"File extension / pattern \"{}\" appears twice in the syntax set", "File extension / pattern \"{extension}\" appears twice in the syntax set"
extension
); );
} }
} }

View File

@@ -1,31 +1,31 @@
import "../imported-file" ; import "../imported-file" ;
# With Comments ! # With Comments !
def weird($a; $b; $c): def weird($a; $b; $c):
 [ $a, $b, $c ] | transpose | reduce .[][] as $item (  [ $a, $b, $c ] | transpose | reduce .[][] as $item (
 [];  [];
 . + $item.property  . + $item.property
 )  )
; ;
. | weird (.a; .b; .c) | . | weird (.a; .b; .c) |
( (
if (. | contains("never") ) then if (. | contains("never") ) then
 "Why yes"  "Why yes"
else else
 12.23  12.23
end end
) as $never | ) as $never |
{ {
 hello,  hello,
 why: "because",  why: "because",
 hello: ( weird | ascii_upcase ),  hello: ( weird | ascii_upcase ),
 format_eg: ( . | @json "My json string \( . | this | part | just | white | ascii_upcase | transpose)" ),  format_eg: ( . | @json "My json string \( . | this | part | just | white | ascii_upcase | transpose)" ),
 never: $never,  never: $never,
 "literal_key": literal_value,  "literal_key": literal_value,
 "this": 12.1e12,  "this": 12.1e12,
 "part": "almost"  "part": "almost"
@@ -38,8 +38,8 @@
 similar: "but not quite"  similar: "but not quite"
 }  }
 }  }
 ],  ],
} | ( } | (
   
 # And with very basic brace matching  # And with very basic brace matching
   
@@ -47,13 +47,13 @@
 ]   ] 
   
 # Other invalid ends  # Other invalid ends
 ( [ } ] )  ( [ } ] )
 # A "valid" sequence  # A "valid" sequence
 ( [ { key: () , other_key:( [ [] [[]] ] ), gaga } ] )  ( [ { key: () , other_key:( [ [] [[]] ] ), gaga } ] )
 # A "invalid" sequence  # A "invalid" sequence
 ( [ { key: () , other_key:( [ [] [[] ] ), gaga } ] )  ( [ { key: () , other_key:( [ [] [[] ] ), gaga } ] )
 "A string\n whith escaped characters \" because we can"  "A string\n whith escaped characters \" because we can"
) )

View File

@@ -1,36 +1,36 @@
␀␊ ␀␊
\u{1}␊ ␁␊
\u{2}␊ ␂␊
\u{3}␊ ␃␊
\u{4}␊ ␄␊
\u{5}␊ ␅␊
\u{6}␊ ␆␊
␇␊ ␇␊
␈␊ ␈␊
├──┤␊ ├──┤␊
␊ ␊
␊ ␊
\u{b}␊ ␋␊
\u{c}␊ ␌␊
␊ ␊
\u{e}␊ ␎␊
\u{f}␊ ␏␊
\u{10}␊ ␐␊
\u{11}␊ ␑␊
\u{12}␊ ␒␊
\u{13}␊ ␓␊
\u{14}␊ ␔␊
\u{15}␊ ␕␊
\u{16}␊ ␖␊
\u{17}␊ ␗␊
\u{18}␊ ␘␊
\u{19}␊ ␙␊
\u{1a}␊ ␚␊
␛␊ ␛␊
\u{1c}␊ ␜␊
\u{1d}␊ ␝␊
\u{1e}␊ ␞␊
\u{1f}␊ ␟␊
·␊ ·␊
!␊ !␊
"␊ "␊
@@ -126,7 +126,7 @@
|␊ |␊
}␊ }␊
~␊ ~␊
\u{7f}␊ ␡␊
\u{80}␊ \u{80}␊
\u{81}␊ \u{81}␊
\u{82}␊ \u{82}␊

View File

@@ -4,8 +4,8 @@
Use '--help' instead of '-h' to see a more detailed version of the help text. Use '--help' instead of '-h' to see a more detailed version of the help text.
USAGE: USAGE:
 bat [OPTIONS] [FILE]...  bat [OPTIONS] [FILE]...
 bat <SUBCOMMAND>  bat <SUBCOMMAND>
ARGS: ARGS:
 <FILE>... File(s) to print / concatenate. Use '-' for standard input.  <FILE>... File(s) to print / concatenate. Use '-' for standard input.

View File

@@ -41,7 +41,7 @@ fail_test() {
echo_step "TEST: Make sure 'BatTestCustomAssets' is not part of integrated syntaxes" echo_step "TEST: Make sure 'BatTestCustomAssets' is not part of integrated syntaxes"
bat -f "${custom_syntax_args[@]}" && bat -f "${custom_syntax_args[@]}" &&
fail_test "EXPECTED: 'unknown syntax' error ACTUAL: no error occured" fail_test "EXPECTED: 'unknown syntax' error ACTUAL: no error occurred"
echo_step "PREPARE: Install custom syntax 'BatTestCustomAssets'" echo_step "PREPARE: Install custom syntax 'BatTestCustomAssets'"
custom_syntaxes_dir="$(bat --config-dir)/syntaxes" custom_syntaxes_dir="$(bat --config-dir)/syntaxes"
@@ -62,7 +62,7 @@ bat -f "${integrated_syntax_args[@]}" ||
echo_step "TEST: 'BatTestCustomAssets' is an unknown syntax with --no-custom-assets" echo_step "TEST: 'BatTestCustomAssets' is an unknown syntax with --no-custom-assets"
bat -f --no-custom-assets "${custom_syntax_args[@]}" && bat -f --no-custom-assets "${custom_syntax_args[@]}" &&
fail_test "EXPECTED: 'unknown syntax' error because of --no-custom-assets ACTUAL: no error occured" fail_test "EXPECTED: 'unknown syntax' error because of --no-custom-assets ACTUAL: no error occurred"
echo_step "TEST: 'bat cache --clear' removes all files" echo_step "TEST: 'bat cache --clear' removes all files"
bat cache --clear bat cache --clear

View File

@@ -22,14 +22,14 @@ impl BatTester {
pub fn test_snapshot(&self, name: &str, style: &str) { pub fn test_snapshot(&self, name: &str, style: &str) {
let output = Command::new(&self.exe) let output = Command::new(&self.exe)
.current_dir(self.temp_dir.path()) .current_dir(self.temp_dir.path())
.args(&[ .args([
"sample.rs", "sample.rs",
"--no-config", "--no-config",
"--paging=never", "--paging=never",
"--color=never", "--color=never",
"--decorations=always", "--decorations=always",
"--terminal-width=80", "--terminal-width=80",
&format!("--style={}", style), &format!("--style={style}"),
]) ])
.output() .output()
.expect("bat failed"); .expect("bat failed");
@@ -40,7 +40,7 @@ impl BatTester {
.replace("tests/snapshots/", ""); .replace("tests/snapshots/", "");
let mut expected = String::new(); let mut expected = String::new();
let mut file = File::open(format!("tests/snapshots/output/{}.snapshot.txt", name)) let mut file = File::open(format!("tests/snapshots/output/{name}.snapshot.txt"))
.expect("snapshot file missing"); .expect("snapshot file missing");
file.read_to_string(&mut expected) file.read_to_string(&mut expected)
.expect("could not read snapshot file"); .expect("could not read snapshot file");