mirror of
https://github.com/sharkdp/bat.git
synced 2025-02-22 12:58:26 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
b718889ba2
10
CHANGELOG.md
10
CHANGELOG.md
@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Set terminal title to file names when Paging is not Paging::Never #2807 (@Oliver-Looney)
|
||||||
|
|
||||||
## Bugfixes
|
## Bugfixes
|
||||||
|
|
||||||
- Fix long file name wrapping in header, see #2835 (@FilipRazek)
|
- Fix long file name wrapping in header, see #2835 (@FilipRazek)
|
||||||
- Fix `NO_COLOR` support, see #2767 (@acuteenvy)
|
- 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
|
## Other
|
||||||
|
|
||||||
@ -20,12 +25,16 @@
|
|||||||
- Use proper Architecture for Debian packages built for musl, see #2811 (@Enselic)
|
- Use proper Architecture for Debian packages built for musl, see #2811 (@Enselic)
|
||||||
- Pull in fix for unsafe-libyaml security advisory, see #2812 (@dtolnay)
|
- Pull in fix for unsafe-libyaml security advisory, see #2812 (@dtolnay)
|
||||||
- Update git-version dependency to use Syn v2, see #2816 (@dtolnay)
|
- Update git-version dependency to use Syn v2, see #2816 (@dtolnay)
|
||||||
|
- Update git2 dependency to v0.18.2, see #2852 (@eth-p)
|
||||||
|
- Apply clippy fixes #2864 (@cyqsimon)
|
||||||
|
|
||||||
## Syntaxes
|
## Syntaxes
|
||||||
|
|
||||||
- `cmd-help`: scope subcommands followed by other terms, and other misc improvements, see #2819 (@victor-gp)
|
- `cmd-help`: scope subcommands followed by other terms, and other misc improvements, see #2819 (@victor-gp)
|
||||||
- Upgrade JQ syntax, see #2820 (@dependabot[bot])
|
- Upgrade JQ syntax, see #2820 (@dependabot[bot])
|
||||||
- Associate `xsh` files with `xonsh` syntax that is Python, see #2840 (@anki-code).
|
- 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)
|
||||||
|
|
||||||
## Themes
|
## Themes
|
||||||
|
|
||||||
@ -36,6 +45,7 @@
|
|||||||
- [BREAKING] `SyntaxMapping::{empty,builtin}` are removed; use `SyntaxMapping::new` instead
|
- [BREAKING] `SyntaxMapping::{empty,builtin}` are removed; use `SyntaxMapping::new` instead
|
||||||
- [BREAKING] `SyntaxMapping::mappings` is replaced by `SyntaxMapping::{builtin,custom,all}_mappings`
|
- [BREAKING] `SyntaxMapping::mappings` is replaced by `SyntaxMapping::{builtin,custom,all}_mappings`
|
||||||
- Make `Controller::run_with_error_handler`'s error handler `FnMut`, see #2831 (@rhysd)
|
- 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
|
||||||
|
|
||||||
|
66
Cargo.lock
generated
66
Cargo.lock
generated
@ -129,7 +129,7 @@ dependencies = [
|
|||||||
"globset",
|
"globset",
|
||||||
"grep-cli",
|
"grep-cli",
|
||||||
"home",
|
"home",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.2",
|
||||||
"itertools",
|
"itertools",
|
||||||
"nix",
|
"nix",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
@ -142,6 +142,7 @@ dependencies = [
|
|||||||
"run_script",
|
"run_script",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"serial_test",
|
"serial_test",
|
||||||
@ -272,13 +273,14 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clircle"
|
name = "clircle"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c8e87cbed5354f17bd8ca8821a097fb62599787fe8f611743fad7ee156a0a600"
|
checksum = "ec0b92245ea62a7a751db4b0e4a583f8978e508077ef6de24fcc0d0dc5311a8d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -580,7 +582,7 @@ dependencies = [
|
|||||||
"bstr",
|
"bstr",
|
||||||
"log",
|
"log",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -646,9 +648,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.14.1",
|
"hashbrown 0.14.1",
|
||||||
@ -693,9 +695,9 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
version = "0.16.1+1.7.1"
|
version = "0.16.2+1.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c"
|
checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@ -1027,7 +1029,7 @@ dependencies = [
|
|||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1038,15 +1040,9 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.7.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
@ -1113,9 +1109,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.20"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
|
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@ -1150,28 +1146,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.4"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80"
|
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with"
|
name = "serde_with"
|
||||||
version = "3.4.0"
|
version = "3.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
|
checksum = "15d167997bd841ec232f5b2b8e0e26606df2e7caa4c31b95ea9ca52b200bd270"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_with_macros",
|
"serde_with_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_with_macros"
|
name = "serde_with_macros"
|
||||||
version = "3.4.0"
|
version = "3.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
|
checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -1185,7 +1182,7 @@ version = "0.9.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129"
|
checksum = "a15e0ef66bf939a7c890a0bf6d5a733c70202225f9888a89ed5c62298b019129"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.2",
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1258,9 +1255,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syntect"
|
name = "syntect"
|
||||||
version = "5.1.0"
|
version = "5.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e02b4b303bf8d08bfeb0445cba5068a3d306b6baece1d5582171a9bf49188f91"
|
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
@ -1270,8 +1267,9 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"onig",
|
"onig",
|
||||||
"plist",
|
"plist",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
@ -1374,11 +1372,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.6"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
|
checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@ -1396,11 +1394,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.20.7"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.2.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
17
Cargo.toml
17
Cargo.toml
@ -53,11 +53,12 @@ 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.11"
|
unicode-width = "0.1.11"
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
serde_yaml = "0.9.28"
|
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.10", optional = true }
|
grep-cli = { version = "0.1.10", optional = true }
|
||||||
@ -74,7 +75,7 @@ optional = true
|
|||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.syntect]
|
[dependencies.syntect]
|
||||||
version = "5.1.0"
|
version = "5.2.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["parsing"]
|
features = ["parsing"]
|
||||||
|
|
||||||
@ -94,19 +95,21 @@ serial_test = { version = "2.0.0", default-features = false }
|
|||||||
predicates = "3.0.4"
|
predicates = "3.0.4"
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
tempfile = "3.8.1"
|
tempfile = "3.8.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
nix = { version = "0.26.4", default-features = false, features = ["term"] }
|
nix = { version = "0.26.4", default-features = false, features = ["term"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1.0.78"
|
anyhow = "1.0.78"
|
||||||
indexmap = { version = "2.1.0", features = ["serde"] }
|
indexmap = { version = "2.2.2", features = ["serde"] }
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
regex = "1.10.2"
|
regex = "1.10.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = "1.0"
|
||||||
serde_with = { version = "3.4.0", default-features = false, features = ["macros"] }
|
serde_derive = "1.0"
|
||||||
toml = { version = "0.8.6", features = ["preserve_order"] }
|
serde_with = { version = "3.6.1", default-features = false, features = ["macros"] }
|
||||||
|
toml = { version = "0.8.9", features = ["preserve_order"] }
|
||||||
walkdir = "2.4"
|
walkdir = "2.4"
|
||||||
|
|
||||||
[build-dependencies.clap]
|
[build-dependencies.clap]
|
||||||
|
44
README.md
44
README.md
@ -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
|
||||||
|
|
||||||
|
2
assets/syntaxes/02_Extra/cmd-help
vendored
2
assets/syntaxes/02_Extra/cmd-help
vendored
@ -1 +1 @@
|
|||||||
Subproject commit b150d84534dd060afdcaf3f58977faeaf5917e56
|
Subproject commit 209559b72f7e8848c988828088231b3a4d8b6838
|
2
assets/themes/zenburn
vendored
2
assets/themes/zenburn
vendored
@ -1 +1 @@
|
|||||||
Subproject commit e627f1cb223c1171ab0a6a48d166c87aeae2a1d5
|
Subproject commit 86d4ee7a1f884851a1d21d66249687f527fced32
|
@ -10,7 +10,7 @@ use indexmap::IndexMap;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
use serde_with::DeserializeFromStr;
|
use serde_with::DeserializeFromStr;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
@ -160,6 +160,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')
|
||||||
|
|
||||||
|
@ -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::*;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -289,6 +289,7 @@ 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"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,6 +567,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,
|
||||||
|
@ -229,9 +229,33 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +94,9 @@ 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,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
||||||
|
@ -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(),
|
||||||
|
@ -7,8 +7,6 @@ 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::Theme;
|
use syntect::highlighting::Theme;
|
||||||
@ -33,9 +31,23 @@ 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",
|
||||||
|
};
|
||||||
|
|
||||||
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),
|
||||||
@ -554,7 +566,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
|
||||||
@ -581,23 +593,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 ®ions {
|
for &(style, region) in ®ions {
|
||||||
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),
|
||||||
@ -605,9 +611,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 {
|
||||||
@ -625,6 +633,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -634,17 +648,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for &(style, region) in ®ions {
|
for &(style, region) in ®ions {
|
||||||
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,
|
||||||
@ -687,7 +695,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),
|
||||||
@ -696,6 +704,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()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -724,6 +733,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
)
|
)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANSI escape passthrough.
|
||||||
|
_ => {
|
||||||
|
write!(handle, "{}", chunk.raw())?;
|
||||||
|
self.ansi_style.update(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -744,8 +759,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(())
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"INI" = ["**/.aws/credentials", "**/.aws/config"]
|
@ -1,3 +1,3 @@
|
|||||||
# JSON Lines is a simple variation of JSON #2535
|
# JSON Lines is a simple variation of JSON #2535
|
||||||
[mappings]
|
[mappings]
|
||||||
"JSON" = ["*.jsonl"]
|
"JSON" = ["*.jsonl", "*.jsonc"]
|
785
src/vscreen.rs
785
src/vscreen.rs
@ -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 {
|
||||||
|
Text(_) => return false,
|
||||||
|
Unknown(_) => { /* defer to update_with_unsupported */ }
|
||||||
|
OSC {
|
||||||
|
raw_sequence,
|
||||||
|
command,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if command.starts_with("8;") {
|
||||||
|
return self.update_with_hyperlink(raw_sequence);
|
||||||
|
}
|
||||||
|
/* 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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NF { nf_sequence, .. } => {
|
||||||
|
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 */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some((_, t)) = chars.next() {
|
self.update_with_unsupported(sequence.raw())
|
||||||
match t {
|
|
||||||
'(' => self.update_with_charset('(', chars.map(|(_, c)| c)),
|
|
||||||
')' => self.update_with_charset(')', chars.map(|(_, c)| c)),
|
|
||||||
'[' => {
|
|
||||||
if let Some((i, last)) = chars.last() {
|
|
||||||
// SAFETY: Always starts with ^[ and ends with m.
|
|
||||||
self.update_with_csi(last, &sequence[2..i])
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => self.update_with_unsupported(sequence),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sgr_reset(&mut self) {
|
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
|
||||||
@ -179,13 +228,35 @@ impl Attributes {
|
|||||||
_ => format!("\x1B[{}m", color),
|
_ => format!("\x1B[{}m", color),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
tests/examples/regression_tests/issue_2541.txt
vendored
Normal file
1
tests/examples/regression_tests/issue_2541.txt
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
]8;;http://example.com\This is a link]8;;\n
|
@ -936,6 +936,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 +1175,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()
|
||||||
@ -1919,6 +1945,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()
|
||||||
|
@ -22,7 +22,7 @@ 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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user