mirror of
https://github.com/sharkdp/bat.git
synced 2026-11-01 12:05:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e95c32b6f0 | ||
|
|
f059c8da38 | ||
|
|
8f5727ea38 | ||
|
|
b4fdcec6a1 | ||
|
|
fe901f5051 | ||
|
|
3e2c1bdc8e | ||
|
|
e423744a7d | ||
|
|
a9aa73d215 | ||
|
|
f0a58595bd | ||
|
|
2fd71ad588 | ||
|
|
49d9ac1d6e | ||
|
|
54b1f9c6af | ||
|
|
b841eadf09 | ||
|
|
b49aae8b1f | ||
|
|
f748c1a467 | ||
|
|
d3afbaeb06 | ||
|
|
dbe6ec265d | ||
|
|
bc0a67e07c | ||
|
|
fca6c542d4 | ||
|
|
c2adbf6caa | ||
|
|
f48149b119 | ||
|
|
ca5eeb7c2a | ||
|
|
9438a74e6d | ||
|
|
8c63b37205 | ||
|
|
d50b53d0ef | ||
|
|
62b439dae3 | ||
|
|
e8d05da87a | ||
|
|
edd161aefb | ||
|
|
b95f8d37bf | ||
|
|
0e3074cfe1 | ||
|
|
4d916905d1 | ||
|
|
c47a163f4b | ||
|
|
a56bca503b | ||
|
|
d9559c69f5 | ||
|
|
12cbb20de4 | ||
|
|
a9b112b6fe | ||
|
|
4608fc959a | ||
|
|
dcd725dc4e | ||
|
|
4987f76709 | ||
|
|
156ef1d2f4 | ||
|
|
eca6c5b688 | ||
|
|
7323a7514f | ||
|
|
cf72a432ac | ||
|
|
16b5f99d16 | ||
|
|
b784eaf0ae | ||
|
|
2ed8b010af | ||
|
|
5fee86b97b | ||
|
|
66ad54c27d | ||
|
|
2ed7adc49f | ||
|
|
ea3909bbdf | ||
|
|
0acf9417cd | ||
|
|
b5c8e101f8 | ||
|
|
383abc83c8 | ||
|
|
7928b4be52 | ||
|
|
036a201df9 | ||
|
|
2da852dc78 | ||
|
|
65c311e9fb | ||
|
|
b3c9516a9b | ||
|
|
5f12c62b3e | ||
|
|
7892a8754b | ||
|
|
400c5dc6f1 | ||
|
|
1cedc1b4c3 | ||
|
|
b8430080f0 | ||
|
|
a3f202f367 | ||
|
|
b671e53c2c | ||
|
|
de2e651bf5 | ||
|
|
061fb216cb | ||
|
|
af59a32183 | ||
|
|
c63838efeb | ||
|
|
cecbb80093 | ||
|
|
3ca4a01be6 | ||
|
|
8112350da6 | ||
|
|
a5deaf63ff | ||
|
|
2fb2415731 | ||
|
|
a58f23724a | ||
|
|
bb492fff47 | ||
|
|
a96b9e2230 | ||
|
|
531d54b110 | ||
|
|
db69aab106 | ||
|
|
2ba8db9c14 | ||
|
|
d344844486 | ||
|
|
aeb0e4c457 | ||
|
|
1cf12d49bd | ||
|
|
7cb212646c | ||
|
|
6573ca9307 | ||
|
|
f04dc80bc7 | ||
|
|
e6ac6ce08d | ||
|
|
949f18e23a | ||
|
|
969b33dad1 | ||
|
|
73dc3258be | ||
|
|
df38225653 | ||
|
|
c2cc79daa7 | ||
|
|
0230cba42d | ||
|
|
227e0fa00f | ||
|
|
384f2f5d13 | ||
|
|
060ccb1385 | ||
|
|
feabb25209 | ||
|
|
839f2300b7 | ||
|
|
ea0742babc | ||
|
|
bfa6e59978 | ||
|
|
9710a231d8 | ||
|
|
a9b67b2d99 | ||
|
|
b22be63fec | ||
|
|
3a7a750969 | ||
|
|
be5343c742 | ||
|
|
c371044276 | ||
|
|
d449ce5b7e | ||
|
|
218afc30ac | ||
|
|
78951393e2 | ||
|
|
0a52e4321f | ||
|
|
d72163160e | ||
|
|
971f9679da | ||
|
|
57d436861a | ||
|
|
bb4f48a124 | ||
|
|
c75cc01eef | ||
|
|
835bd2a756 | ||
|
|
3409674d9c | ||
|
|
7de0c4ba47 | ||
|
|
20a818fe14 | ||
|
|
30b50a9eed | ||
|
|
088f1e4253 | ||
|
|
53769d03b0 | ||
|
|
b7ae4585ad | ||
|
|
a4609d1778 | ||
|
|
8f70e0cf00 | ||
|
|
e34595b53e | ||
|
|
6ba745b74b | ||
|
|
98f09fd6f3 | ||
|
|
69f40d9d8a | ||
|
|
6e6137ce10 | ||
|
|
df164cc2e3 | ||
|
|
f9eed8d619 | ||
|
|
d695ad8f88 | ||
|
|
e60d184f97 | ||
|
|
e2568c52cd | ||
|
|
c5e6f6aae3 | ||
|
|
8f93137246 | ||
|
|
fa00fb8ce8 | ||
|
|
d117bf2320 | ||
|
|
f4d836ee87 | ||
|
|
04576fd5a5 | ||
|
|
e7025d9bdb | ||
|
|
6d4a3cef53 | ||
|
|
6b8887133d | ||
|
|
5bb35b4bed | ||
|
|
d28aa4a8b4 | ||
|
|
e9b552d5d0 | ||
|
|
af1f53d9a9 | ||
|
|
8033b1e7be | ||
|
|
01408ce094 | ||
|
|
bcd1d3f45b | ||
|
|
4cd5db2a78 | ||
|
|
67aa491313 | ||
|
|
e490c0f322 | ||
|
|
2d29ecb939 | ||
|
|
707b5949fd | ||
|
|
fd53693e1f | ||
|
|
f3d0773468 | ||
|
|
66e336bcfb | ||
|
|
ce7ad9adca | ||
|
|
f9fa590c3d | ||
|
|
1e1c3773fb | ||
|
|
0ef0342ff2 | ||
|
|
de396794cf | ||
|
|
43c4957e18 | ||
|
|
c9fa10e76b | ||
|
|
530e83a455 | ||
|
|
2cec8cdf25 | ||
|
|
138d70fd4c | ||
|
|
98df25434e | ||
|
|
5f952066fb | ||
|
|
f74082cee8 | ||
|
|
a0c95618c4 | ||
|
|
f57c1b6b1d | ||
|
|
517de0463b | ||
|
|
3be4e30e00 | ||
|
|
15fdc7637a | ||
|
|
72fc697372 | ||
|
|
b8d2781784 | ||
|
|
787a36acb6 | ||
|
|
a79701a43e | ||
|
|
3bcbc45ab0 | ||
|
|
7953df44ea | ||
|
|
443bcb9287 | ||
|
|
0523b70473 | ||
|
|
f776d1ad3f | ||
|
|
cfc6d9c415 | ||
|
|
89d7c86b19 | ||
|
|
c437ad4d27 | ||
|
|
1c6e763d75 | ||
|
|
baebdd1789 | ||
|
|
f7b84a3b41 | ||
|
|
b483f90612 | ||
|
|
6edad56b00 | ||
|
|
e2174379ed | ||
|
|
816aea0625 | ||
|
|
7fcab0616a | ||
|
|
e157ac911b | ||
|
|
b29e1d55c4 | ||
|
|
12ee94f530 | ||
|
|
cbe7919451 | ||
|
|
10b4f07a87 | ||
|
|
ebf469b99a | ||
|
|
8fbc96a5f2 | ||
|
|
54bfc33248 | ||
|
|
91aac95db2 | ||
|
|
5bf932d511 | ||
|
|
5977245a23 | ||
|
|
3f98e6f10f | ||
|
|
78963986e0 | ||
|
|
de64d3a0eb | ||
|
|
78cb14af05 | ||
|
|
8c8ed90d6d | ||
|
|
492c387ce7 | ||
|
|
bceb260e91 | ||
|
|
8cadefb5cb | ||
|
|
64567c4819 | ||
|
|
de2f21562a | ||
|
|
5722311b2e | ||
|
|
f39d63b85d | ||
|
|
4144059e7a | ||
|
|
044d445adc | ||
|
|
c3df0e6a88 | ||
|
|
71c894e843 | ||
|
|
93c52a4f88 | ||
|
|
ea7fafca1e | ||
|
|
e782ec125c | ||
|
|
0ecdeb28d4 | ||
|
|
77ea750e66 | ||
|
|
cafad6b036 | ||
|
|
e070d105b5 | ||
|
|
1f89178fce | ||
|
|
52763e0205 | ||
|
|
f822bd05ce | ||
|
|
e89c515e9a | ||
|
|
a36b02d90f | ||
|
|
6876a782da | ||
|
|
db647c9813 | ||
|
|
33045c3558 | ||
|
|
01174b31f5 | ||
|
|
a9137fab05 | ||
|
|
a995764d23 | ||
|
|
b3aec318cf | ||
|
|
2459aa9404 | ||
|
|
111aa2e10e | ||
|
|
c64b6761fe | ||
|
|
11efacbe64 | ||
|
|
204ca18d71 | ||
|
|
a158f12106 | ||
|
|
956f0aeb08 | ||
|
|
1a6073d2e8 | ||
|
|
57e99e9bcd | ||
|
|
68eaa96e06 | ||
|
|
de9ca4ef7b | ||
|
|
7bb905436b | ||
|
|
f820ad1050 | ||
|
|
259e1266a8 | ||
|
|
31b0a35719 | ||
|
|
3e4169b8c3 | ||
|
|
ba223289ce | ||
|
|
abab6533e4 | ||
|
|
7d9884d6d2 | ||
|
|
652489251b | ||
|
|
89f3d2d31a | ||
|
|
5c3b8040db | ||
|
|
7ff1fb3278 | ||
|
|
3ee9ea9a8d | ||
|
|
b511b928f4 | ||
|
|
3e789f5241 | ||
|
|
a500fb236a | ||
|
|
515b9d62db | ||
|
|
62907b1da5 | ||
|
|
eef7107407 | ||
|
|
87e043b91c | ||
|
|
b8d462ba87 | ||
|
|
ca3ef28d56 | ||
|
|
0b4c886efc | ||
|
|
780fdeb888 | ||
|
|
7a9c6e0de5 | ||
|
|
a1e7c0ab4a | ||
|
|
1e36873704 | ||
|
|
e86797fbf4 | ||
|
|
e60875ac12 | ||
|
|
66eece4e2c | ||
|
|
a19593b383 | ||
|
|
7a6f442c86 | ||
|
|
3767f15c2a | ||
|
|
1932d1da39 | ||
|
|
b0e5590864 | ||
|
|
f2daa1eb6e | ||
|
|
1f540752ef | ||
|
|
fc94a0ec49 | ||
|
|
169dc7c45b | ||
|
|
5e140558b1 | ||
|
|
99c8e15c27 | ||
|
|
2a3ed948ec | ||
|
|
618d7340bb | ||
|
|
4a38eab3ea | ||
|
|
9fccdbc484 | ||
|
|
c6e661d80b | ||
|
|
031ba0953a | ||
|
|
2a29802dd5 | ||
|
|
31e30a2e97 | ||
|
|
ffed52f6eb | ||
|
|
a4e853c4fa | ||
|
|
f11374535d | ||
|
|
de209f8a23 | ||
|
|
22c38b6fcb | ||
|
|
63d5796f8f | ||
|
|
a1917275cd | ||
|
|
884765bdeb | ||
|
|
56fe0fa226 | ||
|
|
d9adfe90fc | ||
|
|
b1f04995a6 | ||
|
|
ea6d2ac911 | ||
|
|
ac97356930 | ||
|
|
23abbdcea9 | ||
|
|
927873392b | ||
|
|
91965d28a6 | ||
|
|
88ff40f93e | ||
|
|
652d50e92e | ||
|
|
0dcdcb678a | ||
|
|
f49641a6c3 | ||
|
|
fc4f12b390 | ||
|
|
9cce9e04d2 | ||
|
|
844bfded50 | ||
|
|
ab80bd9717 | ||
|
|
a1a10c777f | ||
|
|
cc5f782d28 | ||
|
|
1424f9d6bf | ||
|
|
fd67095cff | ||
|
|
908b1f22d1 | ||
|
|
17a70d9b8c | ||
|
|
49d77a469a | ||
|
|
fa35495888 | ||
|
|
8a858b19dc | ||
|
|
397252d2cc | ||
|
|
5e59957349 | ||
|
|
90f8e00e0c | ||
|
|
673929c189 | ||
|
|
e36bb8cc66 | ||
|
|
7d7e202bd5 | ||
|
|
74de075748 | ||
|
|
594069581d | ||
|
|
62b0388236 | ||
|
|
16d041492c | ||
|
|
22ee03ff00 | ||
|
|
790bed3a2d | ||
|
|
bc84854d4b | ||
|
|
319811df01 | ||
|
|
2c1a8caadd | ||
|
|
a240aa4afd | ||
|
|
00e38cd056 | ||
|
|
335eff51f3 | ||
|
|
d04b960c05 | ||
|
|
fbc05da785 | ||
|
|
a71d16fa1b | ||
|
|
5bd08845a3 | ||
|
|
3ebfbb7ac5 | ||
|
|
2b47f3c5eb | ||
|
|
9f8ac934ab | ||
|
|
ab393a0281 | ||
|
|
990b2fc06f | ||
|
|
0c883d6f28 | ||
|
|
167dda63a8 | ||
|
|
cb8b637574 | ||
|
|
391e7ff072 | ||
|
|
2b517199a2 | ||
|
|
a0f326aa22 | ||
|
|
a4a7692134 | ||
|
|
3df9d581b7 | ||
|
|
eb724f2dc4 | ||
|
|
c491488140 | ||
|
|
3f12b1c1ab | ||
|
|
2c682ff6a6 | ||
|
|
36b37e1aa5 | ||
|
|
d19c80a7c1 | ||
|
|
eff57943c9 | ||
|
|
5a4a7de933 | ||
|
|
ba97230b98 | ||
|
|
8664cc9df9 | ||
|
|
3b34b47d55 | ||
|
|
9a5519057c | ||
|
|
93411a96e4 | ||
|
|
bd461afd75 | ||
|
|
55306c15a6 | ||
|
|
afd804ebea | ||
|
|
3d0a7ed0a0 | ||
|
|
8f6f5dfbd2 | ||
|
|
43ab174d42 | ||
|
|
b396ab865f | ||
|
|
8a82847121 | ||
|
|
c3c19c8018 | ||
|
|
0d2078b32b | ||
|
|
cddfad83e4 | ||
|
|
2e355ffa4e | ||
|
|
fa66d8e3ef | ||
|
|
cb83b8fb9a | ||
|
|
adfdcf4c6d | ||
|
|
593cf76811 | ||
|
|
3aaec8cb3b | ||
|
|
2030ffa664 | ||
|
|
edb8342eab | ||
|
|
6a7936a26f | ||
|
|
26118afde0 | ||
|
|
783acbc83d | ||
|
|
6f0a61cef9 | ||
|
|
53a10e0b0a | ||
|
|
626154317f | ||
|
|
885ae1dc81 | ||
|
|
f6f1f18e9c | ||
|
|
69a2ffbdac | ||
|
|
6b8decf2a4 | ||
|
|
cd06fe46e0 | ||
|
|
099d716455 | ||
|
|
298e1f5321 | ||
|
|
3cb55e85d0 | ||
|
|
981f25989a | ||
|
|
57bf8ff7da | ||
|
|
31f982e736 | ||
|
|
0a97a1edd7 | ||
|
|
6c75acfffc | ||
|
|
4e84e838a3 | ||
|
|
d48bf240a7 | ||
|
|
7dc180eefb | ||
|
|
3276e9469a | ||
|
|
60902c796f | ||
|
|
c12cdcace3 | ||
|
|
823fd9f5c6 | ||
|
|
88d6fd0248 | ||
|
|
32047346e3 | ||
|
|
56678dc771 | ||
|
|
b34a1e0cc2 | ||
|
|
41f55bf902 | ||
|
|
c8514543af | ||
|
|
ad608014d9 | ||
|
|
782ce71247 | ||
|
|
8c7fccc8a5 | ||
|
|
fca5502f21 | ||
|
|
9bf344f760 | ||
|
|
4db185834b | ||
|
|
4549ab3f22 | ||
|
|
629a8968fc | ||
|
|
de414ed631 | ||
|
|
59c5896902 | ||
|
|
b22fc5db6b | ||
|
|
f97f9ebf03 | ||
|
|
64a4b204a2 | ||
|
|
5fb8a25c30 | ||
|
|
80ef6832d3 | ||
|
|
51bdaa5f88 | ||
|
|
eb2a8e29c7 | ||
|
|
b7dd88eafd | ||
|
|
79fe5fea63 | ||
|
|
d15e399fce | ||
|
|
4d188e45fe | ||
|
|
abce6ac0d1 | ||
|
|
566595f0eb | ||
|
|
1796324531 | ||
|
|
2371077352 | ||
|
|
7e7ab8a860 | ||
|
|
c054541776 | ||
|
|
d6ffe3a9be | ||
|
|
4a1ceb3ce0 | ||
|
|
45a6ee95d5 | ||
|
|
3eeaf29675 | ||
|
|
58d1fa83e9 | ||
|
|
9d4ca3bc22 | ||
|
|
9b23b05841 | ||
|
|
f3e8ffc276 | ||
|
|
9b73911f2c | ||
|
|
25bfa9fa81 |
No files matched your search
@@ -0,0 +1,11 @@
|
||||
# On Windows MSVC, statically link the C runtime so that the resulting EXE does
|
||||
# not depend on the vcruntime DLL.
|
||||
#
|
||||
# See: https://github.com/sharkdp/bat/issues/3634
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.i686-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
+15
-16
@@ -35,7 +35,7 @@ jobs:
|
||||
name: Extract crate metadata
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Extract crate information
|
||||
id: crate_metadata
|
||||
run: |
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt,clippy
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cargo fmt -- --check
|
||||
- run: cargo clippy --locked --all-targets --all-features -- -D warnings
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
needs: crate_metadata
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
name: License checks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true # we especially want to perform license checks on submodules
|
||||
- run: tests/scripts/license-checks.sh
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true # we need all syntax and theme submodules
|
||||
- name: Install Rust toolchain
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Prepare environment variables
|
||||
run: |
|
||||
echo "BAT_SYSTEM_CONFIG_PREFIX=$GITHUB_WORKSPACE/tests/examples/system_config" >> $GITHUB_ENV
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Git checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Check documentation
|
||||
@@ -150,7 +150,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: cargo install cargo-audit --locked
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- run: cargo audit
|
||||
|
||||
build:
|
||||
@@ -161,12 +161,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { target: aarch64-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: arm64, use-cross: true }
|
||||
- { target: aarch64-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: musl-linux-arm64, use-cross: true }
|
||||
- { target: aarch64-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: arm64, use-cross: true }
|
||||
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest , dpkg_arch: armhf, use-cross: true }
|
||||
- { target: arm-unknown-linux-musleabihf, os: ubuntu-latest , dpkg_arch: musl-linux-armhf, use-cross: true }
|
||||
- { target: i686-pc-windows-msvc , os: windows-2025 , }
|
||||
- { target: i686-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: i686, use-cross: true }
|
||||
# The timeout test's socketpair fails on the Ubuntu 26.04 runner.
|
||||
- { target: i686-unknown-linux-gnu , os: ubuntu-24.04 , dpkg_arch: i386, use-cross: true }
|
||||
- { target: i686-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: musl-linux-i686, use-cross: true }
|
||||
- { target: x86_64-apple-darwin , os: macos-15-intel, }
|
||||
- { target: aarch64-apple-darwin , os: macos-latest , }
|
||||
@@ -178,7 +179,7 @@ jobs:
|
||||
BUILD_CMD: cargo
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install prerequisites
|
||||
shell: bash
|
||||
@@ -195,9 +196,7 @@ jobs:
|
||||
|
||||
- name: Install cross
|
||||
if: matrix.job.use-cross
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: cross
|
||||
run: cargo install cross --git https://github.com/cross-rs/cross --rev 588b3c99db52b5a9c5906fab96cfadcf1bde7863
|
||||
|
||||
- name: Overwrite build command env variable
|
||||
if: matrix.job.use-cross
|
||||
@@ -335,7 +334,7 @@ jobs:
|
||||
|
||||
DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}
|
||||
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_ARCH="${{ matrix.job.dpkg_arch }}"
|
||||
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
|
||||
@@ -459,7 +458,7 @@ jobs:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
|
||||
- uses: vedantmgoyal9/winget-releaser@b7370d4d152efdce803a26cc6ca9eee58cb2ad54
|
||||
with:
|
||||
identifier: sharkdp.bat
|
||||
installers-regex: '-pc-windows-msvc\.zip$'
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
PR_BASE: ${{ github.base_ref }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Fetch PR base
|
||||
run: git fetch --no-tags --prune --depth=1 origin
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
.direnv/
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
|
||||
# Editors
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Generated files
|
||||
/assets/completions/_bat.ps1
|
||||
/assets/completions/bat.bash
|
||||
@@ -8,3 +13,5 @@
|
||||
/assets/completions/bat.zsh
|
||||
/assets/manual/bat.1
|
||||
/assets/metadata.yaml
|
||||
|
||||
|
||||
+11
-8
@@ -49,9 +49,6 @@
|
||||
[submodule "assets/themes/zenburn"]
|
||||
path = assets/themes/zenburn
|
||||
url = https://github.com/colinta/zenburn.git
|
||||
[submodule "assets/syntaxes/Kotlin"]
|
||||
path = assets/syntaxes/02_Extra/Kotlin
|
||||
url = https://github.com/vkostyukov/kotlin-sublime-package
|
||||
[submodule "assets/syntaxes/Elm"]
|
||||
path = assets/syntaxes/02_Extra/Elm
|
||||
url = https://github.com/elm-community/SublimeElmLanguageSupport
|
||||
@@ -130,7 +127,7 @@
|
||||
url = https://github.com/djuretic/SublimeStrace
|
||||
[submodule "assets/syntaxes/Jinja2"]
|
||||
path = assets/syntaxes/02_Extra/Jinja2
|
||||
url = https://github.com/Martin819/sublime-jinja2
|
||||
url = https://github.com/ltrzesniewski/sublime-jinja2.git
|
||||
[submodule "assets/syntaxes/SLS"]
|
||||
path = assets/syntaxes/02_Extra/SLS
|
||||
url = https://github.com/saltstack/sublime-text
|
||||
@@ -196,16 +193,13 @@
|
||||
url = https://github.com/leanprover/vscode-lean4.git
|
||||
[submodule "assets/syntaxes/02_Extra/Zig"]
|
||||
path = assets/syntaxes/02_Extra/Zig
|
||||
url = https://github.com/ziglang/sublime-zig-language.git
|
||||
url = https://codeberg.org/ziglang/sublime-zig-language.git
|
||||
[submodule "assets/syntaxes/02_Extra/gnuplot"]
|
||||
path = assets/syntaxes/02_Extra/gnuplot
|
||||
url = https://github.com/hesstobi/sublime_gnuplot
|
||||
[submodule "assets/syntaxes/02_Extra/SystemVerilog"]
|
||||
path = assets/syntaxes/02_Extra/SystemVerilog
|
||||
url = https://github.com/TheClams/SystemVerilog.git
|
||||
[submodule "assets/themes/visual-studio-dark-plus"]
|
||||
path = assets/themes/visual-studio-dark-plus
|
||||
url = https://github.com/vidann1/visual-studio-dark-plus.git
|
||||
[submodule "assets/syntaxes/02_Extra/SublimeEthereum"]
|
||||
path = assets/syntaxes/02_Extra/SublimeEthereum
|
||||
url = https://github.com/davidhq/SublimeEthereum.git
|
||||
@@ -281,3 +275,12 @@
|
||||
[submodule "assets/syntaxes/02_Extra/Gomod"]
|
||||
path = assets/syntaxes/02_Extra/Gomod
|
||||
url = https://github.com/mitranim/sublime-gomod
|
||||
[submodule "assets/syntaxes/02_Extra/COBOL"]
|
||||
path = assets/syntaxes/02_Extra/COBOL
|
||||
url = https://github.com/adukhan99/sublime_cobol.git
|
||||
[submodule "assets/syntaxes/02_Extra/Kotlin"]
|
||||
path = assets/syntaxes/02_Extra/Kotlin
|
||||
url = https://github.com/guille/sublime-kotlin
|
||||
[submodule "assets/syntaxes/02_Extra/Caddy"]
|
||||
path = assets/syntaxes/02_Extra/Caddy
|
||||
url = https://github.com/caddyserver/sublimetext.git
|
||||
+102
@@ -1,3 +1,105 @@
|
||||
# unreleased
|
||||
|
||||
|
||||
## Other
|
||||
|
||||
- Pin the i686 GNU CI runner to Ubuntu 24.04 to avoid `socketpair` failures on Ubuntu 26.04, see #4101 (@PerchAnton)
|
||||
- Fix dead Visual C++ Redistributable link in README, see #3895 (@1t1sCooL)
|
||||
- Document `StripAnsiMode` and its variants in rustdoc, see #4067 (@ump45nose)
|
||||
- Update Cargo dependencies to resolve current RustSec advisories, see #3861 (@TyceHerrman)
|
||||
- Add instructions for removing fish help abbreviations to README, see #3655 (@claw-explorer). Closes #3536
|
||||
- Add .NET slnx extension, see #3682 (@ltrzesniewski)
|
||||
|
||||
## Features
|
||||
|
||||
- Add `-b` / `--number-nonblank` flag to only number non-blank lines, for `cat -b` compatibility. Closes #3856, see #3857 (@MeGaurav4)
|
||||
- Add a `--sanitize=<auto|always|never>` flag for safe display of untrusted input. It implies `--strip-ansi` at the same value and additionally substitutes terminal-active control bytes (cursor moves, charset switches, beep, etc.) and Unicode bidi / zero-width formatting characters with the Unicode replacement character (U+FFFD). Mitigates Trojan-Source-style spoofing (CVE-2021-42574). See #3729 (@curious-rabbit)
|
||||
- Map justfile, Justfile, .justfile, and *.justfile to Makefile syntax highlighting, see #3623 (@zachvalenta)
|
||||
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
|
||||
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
|
||||
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
|
||||
- Support configuring `--terminal-width` via `BAT_WIDTH`, see #3679 (@officialasishkumar)
|
||||
- Implement `--unbuffered` mode for streaming input, allowing partial lines to display immediately (e.g. `tail -f | bat -u`). Closes #3555, see #3583 (@mainnebula)
|
||||
- Added an initial `flake.nix` for a ready made development environment; see #3578 (@vorburger)
|
||||
- Add `--quiet-empty` (`-E`) flag to suppress output when input is empty. Closes #1936, see #3563 (@NORMAL-EX)
|
||||
- Improve native man pages and command help syntax highlighting by stripping overstriking, see #3517 (@akirk)
|
||||
- Add `--fallback-syntax`/`--fallback-language` to apply syntax highlighting only when auto-detection fails, see #1341 (@Xavrir)
|
||||
- Map `BUILD` case sensitively to Python (Starlark) for Bazel, see #3576 (@vorburger)
|
||||
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
|
||||
|
||||
## Bugfixes
|
||||
- Report `BAT_CONFIG_DIR` in `bat --diagnostic`, so bug reports show a config directory that overrides `$XDG_CONFIG_HOME`. See #4068 (@ump45nose)
|
||||
- Fix `bat` hanging forever on JavaScript (and Svelte) code where an expression is followed by a block comment, e.g. `a /* x */`. Closes #4080, see #4091 (@NavpreetSSidhu)
|
||||
- Fix the first line of UTF-16BE input swallowing the second line, which broke line numbers, `--line-range` and highlighting, see #XXXX (@cindykrafft)
|
||||
- Exit quietly with status 0 instead of panicking when stdout is closed early (broken pipe) on auxiliary output paths such as `--completion`, `--config-file`, `--diagnostic`, `--generate-config-file`, `--set-terminal-title` and `cache --build`/`--clear`, matching the main output path since #232, closes #4034, see #4035 (@Mathjk)
|
||||
- Track strikethrough (SGR 9 / 29) in the ANSI style tracker so it is re-emitted like bold, dim, italic, and underline, and is no longer dropped after the first highlighted token when displaying ANSI input with syntax highlighting, see #4015 (@mikehasa)
|
||||
- Allow boolean flags to be given more than once, so that a flag set in the config file can also be passed on the command line without erroring, see #3912 (@logarithmone1128)
|
||||
- Use parsed CLI arguments to detect number flags, see #3908 (@cuishuang)
|
||||
- Detect binary content beyond the first line, preventing encrypted files with early line breaks from being treated as text. Closes #3554, see #3877 (@Matei02355)
|
||||
- Use the last occurrence of numbering and plain flags in combined short arguments, see #3897 (@cuishuang)
|
||||
- Avoid a spurious Cargo manifest error during installation from Git, see #3899 (@rootsec1)
|
||||
- Avoid repeated scans of long lines in Log syntax highlighting, see #3876 (@Matei02355)
|
||||
- Fix `--list-languages` respecting `--paging=never`, see #3828 (@cyphercodes)
|
||||
- Fix `--sanitize` passing through the bidi control characters U+200E, U+200F and U+061C, see #3862 (@lenamonj)
|
||||
- `--strip-ansi`: also strip 8-bit C1 introducers (U+0090, U+0098, U+009B, U+009D, U+009E, U+009F) and DCS/SOS/PM/APC sequence bodies, which previously passed through. See #3729 (@curious-rabbit)
|
||||
- Fix `--ignored-suffix` not falling back to first-line/shebang detection when the ignored suffix is also a registered extension (e.g. `--ignored-suffix .txt` on a shebang script), see #2745 and #3816 (@adnrivera)
|
||||
- Fix `capacity overflow` panic when printing a snip separator at `--terminal-width=1` with multiple line ranges. Closes #3803, see #3804 (@leeewee)
|
||||
- Pass `--no-paging` to `bat` invocations inside the bash / zsh / fish / PowerShell shell completion scripts so that shell-level pager wiring (e.g. `LESSOPEN='|-bat -f -pp %s'`) cannot inject ANSI escape sequences into the completion candidates. Closes #3760 (@mvanhorn)
|
||||
- Quote filenames before substituting them into `$LESSOPEN` / `$LESSCLOSE` templates, preventing shell injection when a filename contains shell metacharacters, see #3726 (@curious-rabbit)
|
||||
- Fix `--list-themes` unconditionally probing the terminal via OSC 10/11 even when `--theme` was set to an explicit value, see #3700 (regression introduced in bc42149a). (@optimistiCli)
|
||||
- Fix inverted `$LESSCLOSE` warning so bat warns on nonzero exit, not on success. See #3654 (@cuiweixie)
|
||||
- Sanitize control characters in filenames before displaying them in the file header, error messages, and the terminal title, preventing ANSI escape injection via crafted filenames. Closes #3054, see #3691 (@curious-rabbit)
|
||||
- Report initial input read errors instead of treating them as empty input. Closes #3002, see #3706 (@lawrence3699)
|
||||
- Treat ZIP archives as binary content based on their magic header, see #3686 (@officialasishkumar)
|
||||
- Fix i686 `.deb` package using incorrect architecture name (`i686` instead of `i386`), preventing installation on Debian. Closes #3611, see #3650 (@Sim-hu)
|
||||
- Fix inconsistent `.deb` MUSL package names (aarch64-musl used `arm64` instead of `musl-linux-arm64`, and `musleabihf` target missed `bat-musl` prefix). Closes #3482, see #3642 (@mvanhorn)
|
||||
- Fix incorrect text width computation when using `--binary=as-text` with non-printable characters in caret notation, see #3640 and #3631 (@eyupcanakman)
|
||||
- Fix `BAT_CONFIG_DIR` pointing at system config directory causing duplicate flag errors. Closes #3589, see #3620 (@Xavrir)
|
||||
- Fix syntax highlighting for symlinked files when the symlink name has no extension but the target does. Closes #1001, see #3621 (@Xavrir)
|
||||
- Report error when pager is missing instead of silently falling back, see #3588 (@IMaloney)
|
||||
- Fix `--wrap=never` and `-S` flags being ignored when piping to pager, see #3592 (@IMaloney)
|
||||
- Fix crash with BusyBox `less` on Windows, see #3527 (@Anchal-T)
|
||||
- Fix `bat cache --help` failing with 'unexpected argument' error, see #3580 and #3560 (@NORMAL-EX)
|
||||
- `--help` now correctly honors `--pager=builtin`. See #3516 (@keith-hall)
|
||||
- `--help` now correctly honors custom themes. See #3524 (@keith-hall)
|
||||
- Fixed test compatibility with future Cargo build directory changes, see #3550 (@nmacl)
|
||||
- Fixed bug caused by using `--plain` and `--terminal-width=N` flags simultaneously, see #3529 (@H4k1l)
|
||||
- Fixed syntax tests path, see #3610 (@foxfromworld)
|
||||
- Fix zsh tab completion word-splitting language names containing spaces (e.g. `HTML (Jinja2)`, `Apache Conf`), see #3693 (@YoshKoz)
|
||||
- Fix zsh tab completion offering invalid `-l` arguments (file globs, paths, hidden filenames) sourced from the second column of `--list-languages`. Closes #3735, see #3737 (@truffle-dev)
|
||||
- Fix `usize` underflow in `--list-languages` when `--terminal-width` is smaller than the longest language name, see #3812 (@greymoth-jp)
|
||||
|
||||
## Other
|
||||
- Use git version of cross. See #3533 (@OctopusET)
|
||||
- Bump MSRV to 1.88, update `time` crate to 0.3.47 to fix RUSTSEC-2026-0009, see #3581 (@NORMAL-EX)
|
||||
- Allow home and end keys to be used with builtin pager, see #3651 (@keith-hall)
|
||||
- Builtin syntax mapping: cleanup matcher glob parsing logic #3652 (@cyqsimon)
|
||||
- Statically link the CRT for MSVC builds via Cargo config to avoid runtime DLL dependencies. Closes #3634, see #3692 (@barry3406)
|
||||
- Replace `libgit2` with a pure Rust implementation of git called `gitoxide`, see PR #3703 (@blinxen)
|
||||
|
||||
## Syntaxes
|
||||
|
||||
- Add shebang-based detection for Tcl (`tclsh`, `wish`) and Expect (`expect`) scripts, see #3647 (@mvanhorn)
|
||||
- Change the URL of Zig submodule from GitHub to Codeberg, see #3519 (@sorairolake)
|
||||
- Don't color strings inside CSV files, to make it easier to tell which column they belong to, see #3521 (@keith-hall)
|
||||
- Add syntax highlighting support for COBOL, see #3584 (@adukhan99)
|
||||
- Fixed manpage syntax so that ANSI escape codes don't get incorrectly highlighted and thus broken, see #3586 (@BlueElectivire)
|
||||
- Map several Google Cloud CLI config files to their appropriate syntax #3635 (@victor-gp)
|
||||
- Map all ignore dotfiles to Git Ignore syntax #3636 (@victor-gp)
|
||||
- Improved Kotlin syntax, see #3699 (@guille)
|
||||
- Include subdirectories in SSH Config syntax mapping, see #3758 (@injust)
|
||||
- Add Ghostty syntax mapping, see #3759 (@injust)
|
||||
- Add syntax highlighting for `Caddyfile` #3789 (@CosmicHorrorDev)
|
||||
- Include `.code-workspace` as a JSON extension #3809 (@dhruvkb)
|
||||
- Add syntax mapping for DNF repo configuration files, see #3814 (@injust)
|
||||
- Add syntax mapping for Podman `.quadlets` files, see #4069 (@injust)
|
||||
|
||||
## Themes
|
||||
|
||||
- Remove the Visual Studio Dark+ theme, see #3552 (@CosmicHorrorDev)
|
||||
|
||||
## `bat` as a library
|
||||
|
||||
# v0.26.1
|
||||
|
||||
## Features
|
||||
|
||||
@@ -68,6 +68,8 @@ users, please read the corresponding
|
||||
[documentation](https://github.com/sharkdp/bat/blob/master/doc/assets.md)
|
||||
first.
|
||||
|
||||
To map a file name pattern to an existing syntax, read [the documentation here](https://github.com/sharkdp/bat/blob/master/src/syntax_mapping/builtins/README.md).
|
||||
|
||||
Note: We are currently not accepting new default themes.
|
||||
|
||||
|
||||
@@ -91,3 +93,29 @@ To learn how to write regression tests for theme and syntax changes, read the
|
||||
[Syntax
|
||||
tests](https://github.com/sharkdp/bat/blob/master/doc/assets.md#syntax-tests)
|
||||
section in `assets.md`.
|
||||
|
||||
### Ensuring bat is available for Syntax tests
|
||||
|
||||
The syntax test script (`tests/syntax-tests/update.sh`) calls `bat` from your PATH and regenerates the highlighted output files under
|
||||
`tests/syntax-tests/highlighted/`. These files are used to verify that syntax highlighting works as expected.
|
||||
|
||||
- If you only built the binaries with:
|
||||
```bash
|
||||
cargo build --bins
|
||||
```
|
||||
|
||||
you need to add the debug build to your PATH from the bat project root before running the tests.
|
||||
See also step 5 in [Syntax
|
||||
tests](https://github.com/sharkdp/bat/blob/master/doc/assets.md#syntax-tests) for related instructions.
|
||||
```bash
|
||||
export PATH="$PATH:$(pwd)/target/debug"
|
||||
```
|
||||
Otherwise, you will see:
|
||||
```bash
|
||||
Error: Could not execute 'bat'. Please make sure that the executable is available on the PATH.
|
||||
```
|
||||
- If you installed bat with:
|
||||
```bash
|
||||
cargo install --path . --locked
|
||||
```
|
||||
then bat will be available in ~/.cargo/bin (usually already in PATH), and the tests will run without issues.
|
||||
Generated
+1612
-782
File diff suppressed because it is too large.
Load diff
+29
-29
@@ -11,7 +11,7 @@ exclude = ["assets/syntaxes/*", "assets/themes/*"]
|
||||
build = "build/main.rs"
|
||||
edition = '2021'
|
||||
# You are free to bump MSRV as soon as a reason for bumping emerges.
|
||||
rust-version = "1.87"
|
||||
rust-version = "1.88"
|
||||
|
||||
[features]
|
||||
default = ["application", "git"]
|
||||
@@ -31,8 +31,7 @@ minimal-application = [
|
||||
"regex-onig",
|
||||
"wild",
|
||||
]
|
||||
git = ["git2"] # Support indicating git modifications
|
||||
vendored-libgit2 = ["git2/vendored-libgit2"]
|
||||
git = ["gix"] # Support indicating git modifications
|
||||
paging = [ "shell-words", "grep-cli", "minus"] # Support applying a pager on the output
|
||||
lessopen = ["execute"] # Support $LESSOPEN preprocessor
|
||||
build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"]
|
||||
@@ -42,21 +41,21 @@ regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine
|
||||
regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine
|
||||
|
||||
[dependencies]
|
||||
nu-ansi-term = "0.50.0"
|
||||
nu-ansi-term = "0.50.3"
|
||||
ansi_colours = "^1.2"
|
||||
bincode = "1.0"
|
||||
console = "0.16.0"
|
||||
console = "0.16.2"
|
||||
flate2 = "1.1"
|
||||
once_cell = "1.20"
|
||||
thiserror = "2.0"
|
||||
wild = { version = "2.2", optional = true }
|
||||
content_inspector = "0.2.4"
|
||||
shell-words = { version = "1.1.0", optional = true }
|
||||
minus = { version = "5.6", optional = true, features = [
|
||||
shell-words = { version = "1.1.1", optional = true }
|
||||
minus = { version = "5.7", optional = true, features = [
|
||||
"dynamic_output",
|
||||
"search",
|
||||
] }
|
||||
unicode-width = "0.2.1"
|
||||
unicode-width = "0.2.2"
|
||||
globset = "0.4"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
@@ -64,22 +63,23 @@ serde_yaml = "0.9.28"
|
||||
semver = "1.0"
|
||||
path_abs = { version = "0.5", default-features = false }
|
||||
clircle = { version = "0.6.1", default-features = false }
|
||||
bugreport = { version = "0.5.0", optional = true }
|
||||
bugreport = { version = "0.6.0", optional = true }
|
||||
etcetera = { version = "0.11.0", optional = true }
|
||||
grep-cli = { version = "0.1.11", optional = true }
|
||||
regex = { version = "1.12.2", optional = true }
|
||||
grep-cli = { version = "0.1.12", optional = true }
|
||||
regex = { version = "1.12.3", optional = true }
|
||||
walkdir = { version = "2.5", optional = true }
|
||||
bytesize = { version = "1.3.0" }
|
||||
bytesize = { version = "2.3.1" }
|
||||
encoding_rs = "0.8.35"
|
||||
execute = { version = "0.2.13", optional = true }
|
||||
execute = { version = "0.3.0", optional = true }
|
||||
terminal-colorsaurus = "1.0"
|
||||
unicode-segmentation = "1.12.0"
|
||||
unicode-segmentation = "1.13.2"
|
||||
itertools = "0.14.0"
|
||||
|
||||
[dependencies.git2]
|
||||
version = "0.20"
|
||||
[dependencies.gix]
|
||||
version = "0.86"
|
||||
optional = true
|
||||
default-features = false
|
||||
features = ["sha1", "blob-diff"]
|
||||
|
||||
[dependencies.syntect]
|
||||
version = "5.3.0"
|
||||
@@ -87,43 +87,43 @@ default-features = false
|
||||
features = ["parsing"]
|
||||
|
||||
[dependencies.clap]
|
||||
version = "4.5.46"
|
||||
version = "4.6.1"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
plist = "1.7.0"
|
||||
plist = "1.9.0"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.12"
|
||||
assert_cmd = "2.0.16"
|
||||
expect-test = "1.5.0"
|
||||
serial_test = { version = "2.0.0", default-features = false }
|
||||
predicates = "3.1.3"
|
||||
predicates = "3.1.4"
|
||||
wait-timeout = "0.2.1"
|
||||
tempfile = "3.16.0"
|
||||
tempfile = "3.27.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { version = "0.30", default-features = false, features = ["term"] }
|
||||
nix = { version = "0.31", default-features = false, features = ["term"] }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.97"
|
||||
indexmap = { version = "2.8.0", features = ["serde"] }
|
||||
indexmap = { version = "2.14.0", features = ["serde"] }
|
||||
itertools = "0.14.0"
|
||||
once_cell = "1.20"
|
||||
prettyplease = "0.2.37"
|
||||
proc-macro2 = "1.0.95"
|
||||
quote = "1.0.40"
|
||||
regex = "1.12.2"
|
||||
proc-macro2 = "1.0.106"
|
||||
quote = "1.0.45"
|
||||
regex = "1.12.3"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_with = { version = "3.15.1", default-features = false, features = ["macros"] }
|
||||
serde_with = { version = "3.17.0", default-features = false, features = ["macros"] }
|
||||
syn = { version = "2.0.104", features = ["full"] }
|
||||
toml = { version = "0.9.1", features = ["preserve_order"] }
|
||||
toml = { version = "1.1.1", features = ["preserve_order"] }
|
||||
walkdir = "2.5"
|
||||
|
||||
[build-dependencies.clap]
|
||||
version = "4.5.46"
|
||||
version = "4.6.1"
|
||||
optional = true
|
||||
features = ["wrap_help", "cargo"]
|
||||
|
||||
|
||||
@@ -19,26 +19,6 @@
|
||||
[<a href="doc/README-ru.md">Русский</a>]
|
||||
</p>
|
||||
|
||||
### Sponsors
|
||||
|
||||
A special *thank you* goes to our biggest <a href="doc/sponsors.md">sponsors</a>:<br>
|
||||
|
||||
<p>
|
||||
<a href="https://www.warp.dev/bat">
|
||||
<img src="doc/sponsors/warp-logo.png" width="200" alt="Warp">
|
||||
<br>
|
||||
<strong>Warp, the intelligent terminal</strong>
|
||||
<br>
|
||||
<sub>Available on MacOS, Linux, Windows</sub>
|
||||
</a>
|
||||
</p><p>
|
||||
<a href="https://graphite.dev/?utm_source=github&utm_medium=repo&utm_campaign=bat">
|
||||
<img src="doc/sponsors/graphite-logo.jpeg" width="200" alt="Graphite">
|
||||
<br>
|
||||
<strong>Graphite is the AI developer productivity platform helping<br>teams on GitHub ship higher quality software, faster</strong>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### Syntax highlighting
|
||||
|
||||
`bat` supports syntax highlighting for a large number of programming and markup
|
||||
@@ -49,7 +29,7 @@ languages:
|
||||
### Git integration
|
||||
|
||||
`bat` communicates with `git` to show modifications with respect to the index
|
||||
(see left side bar):
|
||||
(see left sidebar):
|
||||
|
||||

|
||||
|
||||
@@ -201,30 +181,19 @@ bat main.cpp | xclip
|
||||
`MANPAGER` environment variable:
|
||||
|
||||
```bash
|
||||
export MANPAGER="sh -c 'awk '\''{ gsub(/\x1B\[[0-9;]*m/, \"\", \$0); gsub(/.\x08/, \"\", \$0); print }'\'' | bat -p -lman'"
|
||||
export MANPAGER="bat -plman"
|
||||
man 2 select
|
||||
```
|
||||
(replace `bat` with `batcat` if you are on Debian or Ubuntu)
|
||||
(on some older Debian or Ubuntu releases, the executable is named `batcat` instead of `bat`)
|
||||
|
||||
If you prefer to have this bundled in a new command, you can also use [`batman`](https://github.com/eth-p/bat-extras/blob/master/doc/batman.md).
|
||||
|
||||
> [!WARNING]
|
||||
> This will [not work](https://github.com/sharkdp/bat/issues/1145) out of the box with Mandoc's `man` implementation.
|
||||
>
|
||||
> Please either use `batman`, or convert the shell script to a [shebang executable](https://en.wikipedia.org/wiki/Shebang_(Unix)) and point `MANPAGER` to that.
|
||||
|
||||
Note that the [Manpage syntax](assets/syntaxes/02_Extra/Manpage.sublime-syntax) is developed in this repository and still needs some work.
|
||||
|
||||
#### `prettier` / `shfmt` / `rustfmt`
|
||||
|
||||
The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.md) script is a wrapper that will format code and print it with `bat`.
|
||||
|
||||
#### `Warp`
|
||||
|
||||
<a href="https://app.warp.dev/drive/folder/-Bat-Warp-Pack-lxhe7HrEwgwpG17mvrFSz1">
|
||||
<img src="doc/sponsors/warp-pack-header.png" alt="Warp">
|
||||
</a>
|
||||
|
||||
#### Highlighting `--help` messages
|
||||
|
||||
You can use `bat` to colorize help text: `$ cp --help | bat -plhelp`
|
||||
@@ -257,6 +226,14 @@ abbr -a --position anywhere -- -h '-h | bat -plhelp'
|
||||
|
||||
This way, you can keep on using `cp --help`, but get colorized help pages.
|
||||
|
||||
> [!TIP]
|
||||
> To remove these abbreviations later, run:
|
||||
> ```fish
|
||||
> abbr -e -- --help
|
||||
> abbr -e -- -h
|
||||
> ```
|
||||
> The `--` before the abbreviation name is required because `--help` and `-h` start with dashes, which would otherwise be interpreted as flags to `abbr` itself.
|
||||
|
||||
Be aware that in some cases, `-h` may not be a shorthand of `--help` (for example with `ls`). In cases where you need to use `-h`
|
||||
as a command argument you can prepend `\` to the argument (eg. `ls \-h`) to escape the aliasing defined above.
|
||||
|
||||
@@ -287,8 +264,8 @@ If your Ubuntu/Debian installation is new enough you can simply run:
|
||||
sudo apt install bat
|
||||
```
|
||||
|
||||
**Important**: If you install `bat` this way, please note that the executable may be installed as `batcat` instead of `bat` (due to [a name
|
||||
clash with another package](https://github.com/sharkdp/bat/issues/982)). You can set up a `bat -> batcat` symlink or alias to prevent any issues that may come up because of this and to be consistent with other distributions:
|
||||
**Important**: On some older Ubuntu/Debian releases, the executable is installed as `batcat` instead of `bat` (due to [a name
|
||||
clash with another package](https://github.com/sharkdp/bat/issues/982)). On newer releases, the executable is available as `bat`. If `bat --version` does not work after installation, try `batcat --version` instead. You can set up a `bat -> batcat` symlink or alias to prevent any issues that may come up because of this and to be consistent with other distributions:
|
||||
``` bash
|
||||
mkdir -p ~/.local/bin
|
||||
ln -s /usr/bin/batcat ~/.local/bin/bat
|
||||
@@ -330,7 +307,7 @@ pacman -S bat
|
||||
|
||||
### On Fedora
|
||||
|
||||
You can install [the `bat` package](https://koji.fedoraproject.org/koji/packageinfo?packageID=27506) from the official [Fedora Modular](https://docs.fedoraproject.org/en-US/modularity/using-modules/) repository.
|
||||
You can install [the `bat` package](https://packages.fedoraproject.org/pkgs/rust-bat/bat/) from the official sources:
|
||||
|
||||
```bash
|
||||
dnf install bat
|
||||
@@ -440,7 +417,7 @@ scoop install bat
|
||||
|
||||
You can download prebuilt binaries from the [Release page](https://github.com/sharkdp/bat/releases),
|
||||
|
||||
You will need to install the [Visual C++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) package.
|
||||
You will need to install the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) package.
|
||||
|
||||
### From binaries
|
||||
|
||||
@@ -530,6 +507,30 @@ and line numbers but no grid and no file header. Set the `BAT_STYLE` environment
|
||||
variable to make these changes permanent or use `bat`'s
|
||||
[configuration file](#configuration-file).
|
||||
|
||||
By default, `bat` enables `changes`, `grid`, `header-filename`, `numbers`, and `snip`.
|
||||
|
||||
The available pre-defined styles are:
|
||||
|
||||
| Style | Description |
|
||||
|-------|-------------|
|
||||
| `default` | Enables the recommended style components listed above. |
|
||||
| `full` | Enables all available components. |
|
||||
| `auto` | Same as `default`, unless the output is piped. |
|
||||
| `plain` | Disables all available components. |
|
||||
|
||||
The available individual components are:
|
||||
|
||||
| Component | Description |
|
||||
|-----------|-------------|
|
||||
| `changes` | Show Git modification markers. |
|
||||
| `header` | Alias for `header-filename`. |
|
||||
| `header-filename` | Show filenames before the content. |
|
||||
| `header-filesize` | Show file sizes before the content. |
|
||||
| `grid` | Vertical/horizontal lines to separate the side bar and header from the content. |
|
||||
| `rule` | Horizontal lines to delimit files. |
|
||||
| `numbers` | Show line numbers in the side bar. |
|
||||
| `snip` | Draw separation lines between distinct line ranges. |
|
||||
|
||||
>[!tip]
|
||||
> If you specify a default style in `bat`'s config file, you can change which components
|
||||
> are displayed during a single run of `bat` using the `--style` command-line argument.
|
||||
@@ -592,7 +593,8 @@ syntax:
|
||||
|
||||
This works very similar to how we add new syntax definitions.
|
||||
> [!NOTE]
|
||||
> Themes are stored in [`.tmTheme` files](https://www.sublimetext.com/docs/color_schemes_tmtheme.html).
|
||||
> Custom themes must be stored in [`.tmTheme` files](https://www.sublimetext.com/docs/color_schemes_tmtheme.html).
|
||||
> Newer `.sublime-color-scheme` files are currently not supported.
|
||||
|
||||
First, create a folder with the new syntax highlighting themes:
|
||||
```bash
|
||||
@@ -792,7 +794,7 @@ Example configuration file:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need to install the [Visual C++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) package.
|
||||
You will need to install the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) package.
|
||||
|
||||
### Paging
|
||||
|
||||
@@ -841,6 +843,10 @@ If your version of `bat` supports the `--strip-ansi=auto` option, it can be used
|
||||
before syntax highlighting. Alternatively, you may disable both syntax highlighting and wrapping by
|
||||
passing the `--color=never --wrap=never` options to `bat`.
|
||||
|
||||
For untrusted input, the `--sanitize=auto|always|never` option additionally replaces terminal-active
|
||||
control bytes and Unicode bidi / zero-width formatting characters with the Unicode replacement
|
||||
character. It implies `--strip-ansi` at the same value.
|
||||
|
||||
> [!NOTE]
|
||||
> The `auto` option of `--strip-ansi` avoids removing escape sequences when the syntax is plain text.
|
||||
|
||||
@@ -896,7 +902,7 @@ cargo install --path . --locked --force
|
||||
```
|
||||
|
||||
If you want to build an application that uses `bat`'s pretty-printing
|
||||
features as a library, check out the [the API documentation](https://docs.rs/bat/).
|
||||
features as a library, check out the [API documentation](https://docs.rs/bat/).
|
||||
Note that you have to use either `regex-onig` or `regex-fancy` as a feature
|
||||
when you depend on `bat` as a library.
|
||||
|
||||
|
||||
Vendored
+12
-5
@@ -9,17 +9,17 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
$ArrayCompletion = @('bash', 'fish', 'zsh', 'ps1')
|
||||
$ArrayWhen = @('auto', 'never', 'always')
|
||||
$ArrayYesNo = @('never', 'always')
|
||||
$ArrayWrap = @('always', 'never', 'character')
|
||||
$ArrayWrap = @('always', 'never', 'character', 'word')
|
||||
$ArrayBinary = @('no-printing', 'as-text')
|
||||
$ArrayPrint = @('unicode', 'caret')
|
||||
|
||||
function Get-MyThemes(){
|
||||
$themes = {{PROJECT_EXECUTABLE}} --list-themes | ForEach-Object {$_ -replace "^(.*)$", '''$1'''} | select-object
|
||||
$themes = {{PROJECT_EXECUTABLE}} --no-paging --list-themes | ForEach-Object {$_ -replace "^(.*)$", '''$1'''} | select-object
|
||||
return $themes
|
||||
}
|
||||
|
||||
function Get-MyLanguages(){
|
||||
$themes = {{PROJECT_EXECUTABLE}} --list-languages | ForEach-Object{[pscustomobject]@{MyParameter=$_.Substring(0,$_.IndexOf(":")).Trim();MyDescription=$_.Substring($_.IndexOf(":")+1)}} | select-object
|
||||
$themes = {{PROJECT_EXECUTABLE}} --no-paging --list-languages | ForEach-Object{[pscustomobject]@{MyParameter=$_.Substring(0,$_.IndexOf(":")).Trim();MyDescription=$_.Substring($_.IndexOf(":")+1)}} | select-object
|
||||
return $themes
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
ForEach-Object {[System.Management.Automation.CompletionResult]::new($_, $_, [CompletionResultType]::ParameterValue, $_)}
|
||||
break
|
||||
}
|
||||
'*;--sanitize' {
|
||||
$ArrayWhen |
|
||||
ForEach-Object {[System.Management.Automation.CompletionResult]::new($_, $_, [CompletionResultType]::ParameterValue, $_)}
|
||||
break
|
||||
}
|
||||
'*;--strip-ansi' {
|
||||
$ArrayWhen |
|
||||
ForEach-Object {[System.Management.Automation.CompletionResult]::new($_, $_, [CompletionResultType]::ParameterValue, $_)}
|
||||
@@ -135,7 +140,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
[CompletionResult]::new('--file-name' , 'file-name' , [CompletionResultType]::ParameterName, 'Specify the name to display for a file.')
|
||||
[CompletionResult]::new('--diff-context' , 'diff-context' , [CompletionResultType]::ParameterName, 'diff-context')
|
||||
[CompletionResult]::new('--tabs' , 'tabs' , [CompletionResultType]::ParameterName, 'Set the tab width to T spaces.')
|
||||
[CompletionResult]::new('--wrap' , 'wrap' , [CompletionResultType]::ParameterName, 'Specify the text-wrapping mode (*auto*, character).')
|
||||
[CompletionResult]::new('--wrap' , 'wrap' , [CompletionResultType]::ParameterName, 'Specify the text-wrapping mode (*auto*, never, character, word).')
|
||||
[CompletionResult]::new('--terminal-width' , 'terminal-width' , [CompletionResultType]::ParameterName, 'Explicitly set the width of the terminal instead of determining it automatically. If prefixed with ''+'' or ''-'', the value will be treated as an offset to the actual terminal width. See also: ''--wrap''.')
|
||||
[CompletionResult]::new('--color' , 'color' , [CompletionResultType]::ParameterName, 'When to use colors (*auto*, never, always).')
|
||||
[CompletionResult]::new('--italic-text' , 'italic-text' , [CompletionResultType]::ParameterName, 'Use italics in output (always, *never*)')
|
||||
@@ -158,6 +163,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
[CompletionResult]::new('--ignored-suffix' , 'ignored-suffix' , [CompletionResultType]::ParameterName, 'Ignore extension. For example: ''bat --ignored-suffix ".dev" my_file.json.dev'' will use JSON syntax, and ignore ''.dev''')
|
||||
[CompletionResult]::new('--squeeze-blank' , 'squeeze-blank' , [CompletionResultType]::ParameterName, 'Squeeze consecutive empty lines into a single empty line.')
|
||||
[CompletionResult]::new('--squeeze-limit' , 'squeeze-limit' , [CompletionResultType]::ParameterName, 'Set the maximum number of consecutive empty lines to be printed.')
|
||||
[CompletionResult]::new('--sanitize' , 'sanitize' , [CompletionResultType]::ParameterName, 'Specify when to sanitize untrusted input for safe display. Implies --strip-ansi and also replaces terminal-active and bidi / zero-width bytes. (auto, always, *never*).')
|
||||
[CompletionResult]::new('--strip-ansi' , 'strip-ansi' , [CompletionResultType]::ParameterName, 'Specify when to strip ANSI escape sequences from the input. The automatic mode will remove escape sequences unless the syntax highlighting language is plain text. (auto, always, *never*).')
|
||||
# [CompletionResult]::new('-p' , 'p' , [CompletionResultType]::ParameterName, 'Show plain style (alias for ''--style=plain'').')
|
||||
[CompletionResult]::new('--plain' , 'plain' , [CompletionResultType]::ParameterName, 'Show plain style (alias for ''--style=plain'').')
|
||||
@@ -173,7 +179,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
# [CompletionResult]::new('-L' , 'L' , [CompletionResultType]::ParameterName, 'Display all supported languages.')
|
||||
[CompletionResult]::new('--list-languages' , 'list-languages' , [CompletionResultType]::ParameterName, 'Display all supported languages.')
|
||||
# [CompletionResult]::new('-u' , 'u' , [CompletionResultType]::ParameterName, 'u')
|
||||
[CompletionResult]::new('--unbuffered' , 'unbuffered' , [CompletionResultType]::ParameterName, 'unbuffered')
|
||||
[CompletionResult]::new('--unbuffered' , 'unbuffered' , [CompletionResultType]::ParameterName, 'Enable unbuffered input reading for streaming use cases')
|
||||
[CompletionResult]::new('--completion' , 'completion' , [CompletionResultType]::ParameterName, 'Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]')
|
||||
[CompletionResult]::new('--no-config' , 'no-config' , [CompletionResultType]::ParameterName, 'Do not use the configuration file')
|
||||
[CompletionResult]::new('--no-custom-assets' , 'no-custom-assets' , [CompletionResultType]::ParameterName, 'Do not load custom assets')
|
||||
@@ -186,6 +192,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
||||
[CompletionResult]::new('--acknowledgements' , 'acknowledgements' , [CompletionResultType]::ParameterName, 'Show acknowledgements.')
|
||||
[CompletionResult]::new('--set-terminal-title' , 'set-terminal-title' , [CompletionResultType]::ParameterName, 'Sets terminal title to filenames when using a pager.')
|
||||
[CompletionResult]::new('--diagnostic' , 'diagnostic' , [CompletionResultType]::ParameterName, 'Show diagnostic information for bug reports.')
|
||||
[CompletionResult]::new('--quiet-empty' , 'quiet-empty' , [CompletionResultType]::ParameterName, 'Do not produce any output when the input is empty.')
|
||||
# [CompletionResult]::new('-h' , 'h' , [CompletionResultType]::ParameterName, 'Print this help message.')
|
||||
[CompletionResult]::new('--help' , 'help' , [CompletionResultType]::ParameterName, 'Print this help message.')
|
||||
# [CompletionResult]::new('-V' , 'V' , [CompletionResultType]::ParameterName, 'Show version information.')
|
||||
|
||||
Vendored
+12
-5
@@ -80,7 +80,7 @@ _bat() {
|
||||
-l | --language)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "$(
|
||||
"$1" --list-languages | while IFS=: read -r lang _; do
|
||||
"$1" --no-paging --list-languages | while IFS=: read -r lang _; do
|
||||
printf "%s\n" "$lang"
|
||||
done
|
||||
)" -- "$cur"))
|
||||
@@ -100,6 +100,7 @@ _bat() {
|
||||
--lessopen | \
|
||||
--no-paging | \
|
||||
--diagnostic | \
|
||||
--quiet-empty | \
|
||||
--acknowledgements | \
|
||||
-h | --help | \
|
||||
-V | --version | \
|
||||
@@ -116,7 +117,7 @@ _bat() {
|
||||
return 0
|
||||
;;
|
||||
--wrap)
|
||||
COMPREPLY=($(compgen -W "auto never character" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "auto never character word" -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
--binary)
|
||||
@@ -131,6 +132,10 @@ _bat() {
|
||||
COMPREPLY=($(compgen -W "auto never always" -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
--sanitize)
|
||||
COMPREPLY=($(compgen -W "auto never always" -- "$cur"))
|
||||
return 0
|
||||
;;
|
||||
--completion)
|
||||
COMPREPLY=($(compgen -W "bash fish zsh ps1" -- "$cur"))
|
||||
return 0
|
||||
@@ -149,14 +154,14 @@ _bat() {
|
||||
;;
|
||||
--theme)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "auto${IFS}auto:always${IFS}auto:system${IFS}dark${IFS}light${IFS}$("$1" --list-themes)" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "auto${IFS}auto:always${IFS}auto:system${IFS}dark${IFS}light${IFS}$("$1" --no-paging --list-themes)" -- "$cur"))
|
||||
__bat_escape_completions
|
||||
return 0
|
||||
;;
|
||||
--theme-dark | \
|
||||
--theme-light)
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=($(compgen -W "$("$1" --list-themes)" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "$("$1" --no-paging --list-themes)" -- "$cur"))
|
||||
__bat_escape_completions
|
||||
return 0
|
||||
;;
|
||||
@@ -189,8 +194,8 @@ _bat() {
|
||||
$split && return 0
|
||||
|
||||
if [[ $cur == -* ]]; then
|
||||
# --unbuffered excluded intentionally (no-op)
|
||||
COMPREPLY=($(compgen -W "
|
||||
--unbuffered
|
||||
--show-all
|
||||
--nonprintable-notation
|
||||
--binary
|
||||
@@ -220,6 +225,7 @@ _bat() {
|
||||
--list-themes
|
||||
--squeeze-blank
|
||||
--squeeze-limit
|
||||
--sanitize
|
||||
--strip-ansi
|
||||
--style
|
||||
--line-range
|
||||
@@ -227,6 +233,7 @@ _bat() {
|
||||
--lessopen
|
||||
--completion
|
||||
--diagnostic
|
||||
--quiet-empty
|
||||
--acknowledgements
|
||||
--set-terminal-title
|
||||
--help
|
||||
|
||||
Vendored
+10
-7
@@ -15,11 +15,11 @@ function __bat_complete_files -a token
|
||||
end
|
||||
|
||||
function __bat_complete_one_language -a comp
|
||||
command $bat --list-languages | string split -f1 : | string match -e "$comp"
|
||||
command $bat --no-paging --list-languages | string split -f1 : | string match -e "$comp"
|
||||
end
|
||||
|
||||
function __bat_complete_list_languages
|
||||
for spec in (command $bat --list-languages)
|
||||
for spec in (command $bat --no-paging --list-languages)
|
||||
set -l name (string split -f1 : $spec)
|
||||
for ext in (string split -f2 : $spec | string split ,)
|
||||
test -n "$ext"; or continue
|
||||
@@ -61,7 +61,7 @@ function __bat_no_excl_args
|
||||
-s V -l version \
|
||||
-l acknowledgements \
|
||||
-l config-dir -l config-file \
|
||||
-l diagnostic \
|
||||
-l diagnostic -l quiet-empty \
|
||||
-l list-languages -l list-themes
|
||||
end
|
||||
|
||||
@@ -118,6 +118,7 @@ set -l wrap_opts '
|
||||
auto\tdefault
|
||||
never\t
|
||||
character\t
|
||||
word\t
|
||||
'
|
||||
|
||||
# While --tabs theoretically takes any number, most people should be OK with these.
|
||||
@@ -159,6 +160,7 @@ complete -c $bat -l config-file -f -d "Display location of configuration file" -
|
||||
complete -c $bat -l decorations -x -a "$decorations_opts" -d "When to use --style decorations" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l diagnostic -d "Print diagnostic info for bug reports" -n __fish_is_first_arg
|
||||
complete -c $bat -l quiet-empty -d "Do not produce any output when the input is empty" -n __fish_is_first_arg
|
||||
|
||||
complete -c $bat -s d -l diff -d "Only show lines with Git changes" -n __bat_no_excl_args
|
||||
|
||||
@@ -219,6 +221,7 @@ complete -c $bat -s s -l squeeze-blank -d "Squeeze consecutive empty lines into
|
||||
complete -c $bat -l squeeze-limit -x -d "Set the maximum number of consecutive empty lines to be printed" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l strip-ansi -x -a "auto never always" -d "Specify when to strip ANSI escape sequences from the input" -n __bat_no_excl_args
|
||||
complete -c $bat -l sanitize -x -a "auto never always" -d "Specify when to sanitize untrusted input for safe display" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -s p -l plain -d "Disable decorations" -n __bat_no_excl_args
|
||||
|
||||
@@ -232,13 +235,13 @@ complete -c $bat -l tabs -x -a "$tabs_opts" -d "Set tab width" -n __bat_no_excl_
|
||||
|
||||
complete -c $bat -l terminal-width -x -d "Set terminal <width>, +<offset>, or -<offset>" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme -x -a "$special_themes(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme -x -a "$special_themes(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme-dark -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme for dark backgrounds" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme-dark -x -a "(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme for dark backgrounds" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -l theme-light -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme for light backgrounds" -n __bat_no_excl_args
|
||||
complete -c $bat -l theme-light -x -a "(command $bat --no-paging --list-themes | command cat)" -d "Set the syntax highlighting theme for light backgrounds" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -s u -l unbuffered -d "This option exists for POSIX-compliance reasons" -n __bat_no_excl_args
|
||||
complete -c $bat -s u -l unbuffered -d "Enable unbuffered input reading for streaming use cases" -n __bat_no_excl_args
|
||||
|
||||
complete -c $bat -s V -l version -f -d "Show version information" -n __fish_is_first_arg
|
||||
|
||||
|
||||
Vendored
+12
-5
@@ -34,7 +34,7 @@ _{{PROJECT_EXECUTABLE}}_main() {
|
||||
'(-d --diff)'--diff'[only show lines that have been added/removed/modified]'
|
||||
--diff-context='[specify lines of context around added/removed/modified lines when using `--diff`]:lines'
|
||||
--tabs='[set the tab width]:tab width [4]'
|
||||
--wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character)'
|
||||
--wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character word)'
|
||||
'!(--wrap)'{-S,--chop-long-lines}
|
||||
--terminal-width='[explicitly set the width of the terminal instead of determining it automatically]:width'
|
||||
'(-n --number --diff --diff-context)'{-n,--number}'[show line numbers]'
|
||||
@@ -54,14 +54,16 @@ _{{PROJECT_EXECUTABLE}}_main() {
|
||||
--squeeze-blank'[squeeze consecutive empty lines into a single empty line]'
|
||||
--squeeze-limit='[set the maximum number of consecutive empty lines]:limit:'
|
||||
--strip-ansi='[specify when to strip ANSI escape sequences]:when:(auto never always)'
|
||||
--sanitize='[specify when to sanitize untrusted input for safe display]:when:(auto never always)'
|
||||
--style='[comma-separated list of style elements to display]: : _values "style [default]"
|
||||
default auto full plain changes header header-filename header-filesize grid rule numbers snip'
|
||||
\*{-r+,--line-range=}'[only print the specified line range]:start\:end'
|
||||
'(* -)'{-L,--list-languages}'[display all supported languages]'
|
||||
'(-u --unbuffered)'--unbuffered'[this option exists for POSIX-compliance reasons]'
|
||||
'(-u --unbuffered)'--unbuffered'[enable unbuffered input reading for streaming use cases]'
|
||||
--completion='[show shell completion for a certain shell]:shell:(bash fish zsh ps1)'
|
||||
--set-terminal-title'[sets terminal title to filenames when using a pager]'
|
||||
--diagnostic'[show diagnostic information for bug reports]'
|
||||
--quiet-empty'[do not produce any output when the input is empty]'
|
||||
-P'[disable paging]'
|
||||
"--no-config[don't use the configuration file]"
|
||||
"--no-custom-assets[don't load custom assets]"
|
||||
@@ -89,20 +91,25 @@ _{{PROJECT_EXECUTABLE}}_main() {
|
||||
languages)
|
||||
local IFS=$'\n'
|
||||
local -a languages
|
||||
languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
|
||||
# `--list-languages` emits one `name:matchers` line per language,
|
||||
# which `_describe` parses as `value:description`. Only the
|
||||
# language name is offered as the completion value; the matchers
|
||||
# show up as the menu description. See
|
||||
# https://github.com/sharkdp/bat/issues/3735.
|
||||
languages=( ${(f)"$({{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-languages)"} )
|
||||
|
||||
_describe 'language' languages && ret=0
|
||||
;;
|
||||
|
||||
themes)
|
||||
local -a themes expl
|
||||
themes=(${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||
themes=(${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-themes)"} )
|
||||
|
||||
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||
;;
|
||||
theme_preferences)
|
||||
local -a themes expl
|
||||
themes=(auto dark light auto:always auto:system ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||
themes=(auto dark light auto:always auto:system ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --no-paging --color=never --decorations=never --list-themes)"} )
|
||||
|
||||
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||
;;
|
||||
|
||||
Vendored
+12
-4
@@ -102,8 +102,10 @@ Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
||||
.HP
|
||||
\fB\-\-wrap\fR <mode>
|
||||
.IP
|
||||
Specify the text\-wrapping mode (*auto*, never, character). The '\-\-terminal\-width' option
|
||||
can be used in addition to control the output width.
|
||||
Specify the text\-wrapping mode (*auto*, never, character, word). The '\-\-terminal\-width' option
|
||||
can be used in addition to control the output width. In \fBword\fR mode, lines are broken at
|
||||
whitespace boundaries. If a single word exceeds the terminal width, it falls back to
|
||||
character wrapping.
|
||||
.HP
|
||||
\fB\-S\fR, \fB\-\-chop\-long\-lines\fR
|
||||
.IP
|
||||
@@ -263,8 +265,10 @@ Display a list of supported languages for syntax highlighting.
|
||||
.HP
|
||||
\fB\-u\fR, \fB\-\-unbuffered\fR
|
||||
.IP
|
||||
This option exists for POSIX\-compliance reasons ('u' is for 'unbuffered'). The output is
|
||||
always unbuffered \- this option is simply ignored.
|
||||
Enable unbuffered input reading. When this flag is set, bat will display data as soon as it
|
||||
is available, without waiting for a complete line. This is useful for streaming use cases like
|
||||
\&'tail \-f logfile | bat \-u \-\-paging=never'. Note that line numbers are automatically disabled
|
||||
in unbuffered mode, and syntax highlighting may be imperfect on partial lines.
|
||||
.HP
|
||||
\fB\-\-no\-custom\-assets\fR
|
||||
.IP
|
||||
@@ -282,6 +286,10 @@ Show bat's cache directory.
|
||||
.IP
|
||||
Show diagnostic information for bug reports.
|
||||
.HP
|
||||
\fB\-\-quiet\-empty\fR
|
||||
.IP
|
||||
Do not produce any output when the input is empty (e.g. an empty file or empty stdin). This is useful in scripts where silent behavior is preferred for empty input.
|
||||
.HP
|
||||
\fB\-\-acknowledgements\fR
|
||||
.IP
|
||||
Show acknowledgements.
|
||||
|
||||
+10
@@ -12,3 +12,13 @@ index 05a4fed6..78a7bf55 100644
|
||||
scope: source.js
|
||||
variables:
|
||||
bin_digit: '[01_]'
|
||||
@@ -93,7 +93,8 @@ variables:
|
||||
(?! \+\+ | -- )
|
||||
(?=
|
||||
!= |
|
||||
- [-+*/%><=&|^\[(;,.:?] |
|
||||
+ [-+*%><=&|^\[(;,.:?] |
|
||||
+ /(?!\*) |
|
||||
(?:in|instanceof){{identifier_break}}
|
||||
)
|
||||
))
|
||||
+9
@@ -2,6 +2,15 @@ diff --git syntaxes/01_Packages/Python/Python.sublime-syntax syntaxes/01_Package
|
||||
index 2acd86d8..86257f7b 100644
|
||||
--- syntaxes/01_Packages/Python/Python.sublime-syntax
|
||||
+++ syntaxes/01_Packages/Python/Python.sublime-syntax
|
||||
@@ -25,7 +31,7 @@ file_extensions:
|
||||
- wscript
|
||||
- bazel
|
||||
- bzl
|
||||
-first_line_match: ^#!\s*/.*\bpython(\d(\.\d)?)?\b
|
||||
+first_line_match: ^#!\s*/.*\b(python(\d(\.\d)?)?|uv)\b
|
||||
scope: source.python
|
||||
|
||||
variables:
|
||||
@@ -988,10 +988,6 @@ contexts:
|
||||
- match: \}
|
||||
scope: punctuation.section.mapping-or-set.end.python
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
diff --git syntaxes/01_Packages/TCL/Tcl.sublime-syntax syntaxes/01_Packages/TCL/Tcl.sublime-syntax
|
||||
index 1234567..abcdefg 100644
|
||||
--- syntaxes/01_Packages/TCL/Tcl.sublime-syntax
|
||||
+++ syntaxes/01_Packages/TCL/Tcl.sublime-syntax
|
||||
@@ -3,6 +3,7 @@
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Tcl
|
||||
file_extensions:
|
||||
- tcl
|
||||
+first_line_match: ^\#!.*\b(tclsh|wish|expect)\b
|
||||
scope: source.tcl
|
||||
variables:
|
||||
+1
Submodule assets/syntaxes/02_Extra/COBOL added at 27825ee022.
+1
-1
@@ -79,7 +79,7 @@ contexts:
|
||||
|
||||
double_quoted_string:
|
||||
- meta_include_prototype: false
|
||||
- meta_scope: string.quoted.double.csv
|
||||
- meta_scope: meta.string.quoted.double.csv
|
||||
- match: '""'
|
||||
scope: constant.character.escape.csv
|
||||
- match: '"'
|
||||
|
||||
+1
Submodule assets/syntaxes/02_Extra/Caddy added at cd7d132da3.
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Idris2 updated: bbfe50e023...4d8eb35a38.
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Jinja2 updated: 45355633d1...e572427f6a.
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Kotlin updated: aeeed2780b...c353694169.
-398
@@ -1,398 +0,0 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Kotlin
|
||||
file_extensions:
|
||||
- kt
|
||||
- kts
|
||||
scope: source.Kotlin
|
||||
contexts:
|
||||
main:
|
||||
- include: comments
|
||||
- match: '^\s*(package)\b(?:\s*([^ ;$]+)\s*)?'
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
2: entity.name.package.kotlin
|
||||
- include: imports
|
||||
- include: statements
|
||||
classes:
|
||||
- match: (?<!::)(?=\b(?:companion|class|object|interface)\b)
|
||||
push:
|
||||
- match: '(?=$|\})'
|
||||
pop: true
|
||||
- include: comments
|
||||
- match: \b(companion\s*)?(class|object|interface)\b
|
||||
captures:
|
||||
1: storage.modifier.kotlin
|
||||
2: storage.modifier.kotlin
|
||||
push:
|
||||
- match: '(?=<|\{|\(|:|$)'
|
||||
pop: true
|
||||
- include: comments
|
||||
- match: \w+
|
||||
scope: entity.name.type.class.kotlin
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: "(?={|$)"
|
||||
pop: true
|
||||
- match: \w+
|
||||
scope: entity.other.inherited-class.kotlin
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
comments:
|
||||
- match: /\*
|
||||
captures:
|
||||
0: punctuation.definition.comment.kotlin
|
||||
push:
|
||||
- meta_scope: comment.block.kotlin
|
||||
- match: \*/
|
||||
captures:
|
||||
0: punctuation.definition.comment.kotlin
|
||||
pop: true
|
||||
- match: \s*((//).*$\n?)
|
||||
captures:
|
||||
1: comment.line.double-slash.kotlin
|
||||
2: punctuation.definition.comment.kotlin
|
||||
constants:
|
||||
- match: \b(true|false|null|this|super)\b
|
||||
scope: constant.language.kotlin
|
||||
- match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b'
|
||||
scope: constant.numeric.kotlin
|
||||
- match: '\b([A-Z][A-Z0-9_]+)\b'
|
||||
scope: constant.other.kotlin
|
||||
expressions:
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: types
|
||||
- include: strings
|
||||
- include: constants
|
||||
- include: comments
|
||||
- include: keywords
|
||||
functions:
|
||||
- match: (?=\s*\b(?:fun)\b)
|
||||
push:
|
||||
- match: '(?=$|\})'
|
||||
pop: true
|
||||
- match: \b(fun)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=\()
|
||||
pop: true
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: '([\.<\?>\w]+\.)?(\w+)'
|
||||
captures:
|
||||
2: entity.name.function.kotlin
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: "(?={|=|$)"
|
||||
pop: true
|
||||
- include: types
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '(?=\})'
|
||||
pop: true
|
||||
- include: statements
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
generics:
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|>)
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: \w+
|
||||
scope: storage.type.generic.kotlin
|
||||
getters-and-setters:
|
||||
- match: \b(get)\b\s*\(\s*\)
|
||||
captures:
|
||||
1: entity.name.function.kotlin
|
||||
push:
|
||||
- match: '\}|(?=\bset\b)|$'
|
||||
pop: true
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$|\bset\b)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: \b(set)\b\s*(?=\()
|
||||
captures:
|
||||
1: entity.name.function.kotlin
|
||||
push:
|
||||
- match: '\}|(?=\bget\b)|$'
|
||||
pop: true
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$|\bset\b)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: expressions
|
||||
imports:
|
||||
- match: '^\s*(import)\s+[^ $]+\s+(as)?'
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
2: keyword.other.kotlin
|
||||
keywords:
|
||||
- match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b
|
||||
scope: storage.modifier.kotlin
|
||||
- match: \b(try|catch|finally|throw)\b
|
||||
scope: keyword.control.catch-exception.kotlin
|
||||
- match: \b(if|else|while|for|do|return|when|where|break|continue)\b
|
||||
scope: keyword.control.kotlin
|
||||
- match: \b(in|is|!in|!is|as|as\?|assert)\b
|
||||
scope: keyword.operator.kotlin
|
||||
- match: (==|!=|===|!==|<=|>=|<|>)
|
||||
scope: keyword.operator.comparison.kotlin
|
||||
- match: (=)
|
||||
scope: keyword.operator.assignment.kotlin
|
||||
- match: (::)
|
||||
scope: keyword.operator.kotlin
|
||||
- match: (:)
|
||||
scope: keyword.operator.declaration.kotlin
|
||||
- match: \b(by)\b
|
||||
scope: keyword.other.by.kotlin
|
||||
- match: (\?\.)
|
||||
scope: keyword.operator.safenav.kotlin
|
||||
- match: (\.)
|
||||
scope: keyword.operator.dot.kotlin
|
||||
- match: (\?:)
|
||||
scope: keyword.operator.elvis.kotlin
|
||||
- match: (\-\-|\+\+)
|
||||
scope: keyword.operator.increment-decrement.kotlin
|
||||
- match: (\+=|\-=|\*=|\/=)
|
||||
scope: keyword.operator.arithmetic.assign.kotlin
|
||||
- match: (\.\.)
|
||||
scope: keyword.operator.range.kotlin
|
||||
- match: (\-|\+|\*|\/|%)
|
||||
scope: keyword.operator.arithmetic.kotlin
|
||||
- match: (!|&&|\|\|)
|
||||
scope: keyword.operator.logical.kotlin
|
||||
- match: (;)
|
||||
scope: punctuation.terminator.kotlin
|
||||
namespaces:
|
||||
- match: \b(namespace)\b
|
||||
scope: keyword.other.kotlin
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
parameters:
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|\)|=)
|
||||
pop: true
|
||||
- include: types
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|\))
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: keywords
|
||||
- match: \w+
|
||||
scope: variable.parameter.function.kotlin
|
||||
statements:
|
||||
- include: namespaces
|
||||
- include: typedefs
|
||||
- include: classes
|
||||
- include: functions
|
||||
- include: variables
|
||||
- include: getters-and-setters
|
||||
- include: expressions
|
||||
strings:
|
||||
- match: '"""'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.third.kotlin
|
||||
- match: '"""'
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: '(\$\w+|\$\{[^\}]+\})'
|
||||
scope: variable.parameter.template.kotlin
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: '"'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.double.kotlin
|
||||
- match: '"'
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: '(\$\w+|\$\{[^\}]+\})'
|
||||
scope: variable.parameter.template.kotlin
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: "'"
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.single.kotlin
|
||||
- match: "'"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: "`"
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.single.kotlin
|
||||
- match: "`"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
typedefs:
|
||||
- match: (?=\s*(?:type))
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- match: \b(type)\b
|
||||
scope: keyword.other.kotlin
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- include: expressions
|
||||
types:
|
||||
- match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
|
||||
scope: storage.type.buildin.kotlin
|
||||
- match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b
|
||||
scope: storage.type.buildin.array.kotlin
|
||||
- match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b
|
||||
captures:
|
||||
1: storage.type.buildin.collection.kotlin
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: \w+<
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: types
|
||||
- match: (->)
|
||||
scope: keyword.operator.declaration.kotlin
|
||||
variables:
|
||||
- match: (?=\s*\b(?:var|val)\b)
|
||||
push:
|
||||
- match: (?=:|=|(\b(by)\b)|$)
|
||||
pop: true
|
||||
- match: \b(var|val)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=:|=|(\b(by)\b)|$)
|
||||
pop: true
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: '([\.<\?>\w]+\.)?(\w+)'
|
||||
captures:
|
||||
2: entity.name.variable.kotlin
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?==|$)
|
||||
pop: true
|
||||
- include: types
|
||||
- include: getters-and-setters
|
||||
- match: \b(by)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: getters-and-setters
|
||||
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/LESS updated: 836b47ec61...d076a4dd41.
+2
-1
@@ -9,6 +9,7 @@ scope: source.man
|
||||
variables:
|
||||
section_heading: '^(?!#)\S.*$'
|
||||
command_line_option: '(--?[A-Za-z0-9][_A-Za-z0-9-]*)'
|
||||
ansi_escape_sequence: '\e\[[\?=]?(?:\d+;?)*[A-Za-z]'
|
||||
|
||||
contexts:
|
||||
prototype:
|
||||
@@ -69,7 +70,7 @@ contexts:
|
||||
escape: '(?={{section_heading}})'
|
||||
|
||||
function-call:
|
||||
- match: '\b([A-Za-z0-9_\-]+\.)?([A-Za-z0-9_\-]+)(\()([^)]*)(\))'
|
||||
- match: '(?<!\e\[)(?:\b|\s)(?:{{ansi_escape_sequence}})?([A-Za-z0-9_\-]+\.)?([A-Za-z0-9_\-]+)(?:{{ansi_escape_sequence}})?(\()([^)]*)(\))'
|
||||
captures:
|
||||
1: entity.name.function.man
|
||||
2: entity.name.function.man
|
||||
|
||||
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/PureScript updated: 5acebc1850...1773f4fddb.
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/cmd-help updated: 273cb98817...2757ac1681.
+23
-4
@@ -38,22 +38,41 @@ contexts:
|
||||
scope: markup.underline.link.scheme.log
|
||||
push: url-host
|
||||
log_level_lines:
|
||||
- match: (?=.*{{error}})
|
||||
# Prevent retrying each lookahead at every position.
|
||||
- match: ^(?=.*{{error}})
|
||||
push:
|
||||
- error_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{warning}})
|
||||
- match: ^(?=.*{{warning}})
|
||||
push:
|
||||
- warning_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{info}})
|
||||
- match: ^(?=.*{{info}})
|
||||
push:
|
||||
- info_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{debug}})
|
||||
- match: ^(?=.*{{debug}})
|
||||
push:
|
||||
- debug_line_meta
|
||||
- main_pop_at_eol
|
||||
# For syntaxes that embed Log after consuming a prefix. Call this context once.
|
||||
log_level_lines_at_current_position:
|
||||
- match: (?=.*{{error}})
|
||||
set:
|
||||
- error_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{warning}})
|
||||
set:
|
||||
- warning_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{info}})
|
||||
set:
|
||||
- info_line_meta
|
||||
- main_pop_at_eol
|
||||
- match: (?=.*{{debug}})
|
||||
set:
|
||||
- debug_line_meta
|
||||
- main_pop_at_eol
|
||||
log_levels:
|
||||
- match: '{{error}}'
|
||||
scope: markup.error.log
|
||||
|
||||
+11
-1
@@ -50,6 +50,17 @@ contexts:
|
||||
- match: (?=\])
|
||||
pop: true
|
||||
text:
|
||||
- include: text_special
|
||||
- match: ''
|
||||
set: classify_log_text
|
||||
classify_log_text:
|
||||
- include: scope:text.log#log_level_lines_at_current_position
|
||||
- match: ''
|
||||
set: text_body
|
||||
text_body:
|
||||
- include: text_special
|
||||
- include: scope:text.log
|
||||
text_special:
|
||||
- match: $
|
||||
pop: true
|
||||
- match: '<\w+>'
|
||||
@@ -62,4 +73,3 @@ contexts:
|
||||
escape: \)$
|
||||
escape_captures:
|
||||
0: punctuation.section.block.end.syslog
|
||||
- include: scope:text.log
|
||||
+1
-1
Submodule assets/syntaxes/02_Extra/typst-syntax-highlight updated: 363f0e767c...5f71d12fa1.
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/vscode-wgsl updated: acf26718d7...a285c38f74.
-1
Submodule assets/themes/visual-studio-dark-plus deleted from 01ee1e8e0d.
+118
-53
@@ -47,12 +47,49 @@ impl ToTokens for MappingTarget {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)]
|
||||
/// Helper type for deserializing a `Matcher` from either a plain string or a
|
||||
/// `{ glob = "...", case_sensitive = true }` struct.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RawMatcher {
|
||||
Simple(String),
|
||||
Full {
|
||||
glob: String,
|
||||
case_sensitive: Option<bool>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
|
||||
///
|
||||
/// Mirrors the runtime `Case` type in `src/syntax_mapping.rs`.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
enum Case {
|
||||
Sensitive,
|
||||
Insensitive,
|
||||
}
|
||||
impl ToTokens for Case {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let t = match self {
|
||||
Self::Sensitive => quote! { Case::Sensitive },
|
||||
Self::Insensitive => quote! { Case::Insensitive },
|
||||
};
|
||||
tokens.append_all(t);
|
||||
}
|
||||
}
|
||||
|
||||
/// A single matcher.
|
||||
///
|
||||
/// Codegen converts this into a `Lazy<Option<GlobMatcher>>`.
|
||||
struct Matcher(Vec<MatcherSegment>);
|
||||
/// Parse a matcher.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
|
||||
#[serde(try_from = "RawMatcher")]
|
||||
struct Matcher {
|
||||
segments: Vec<MatcherSegment>,
|
||||
/// Whether the glob pattern should be matched case-sensitively.
|
||||
///
|
||||
/// Defaults to `Case::Insensitive` for backwards compatibility.
|
||||
case: Case,
|
||||
}
|
||||
/// Parse the glob pattern of a matcher.
|
||||
///
|
||||
/// Note that this implementation is rather strict: it will greedily interpret
|
||||
/// every valid environment variable replacement as such, then immediately
|
||||
@@ -66,68 +103,95 @@ struct Matcher(Vec<MatcherSegment>);
|
||||
///
|
||||
/// 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());
|
||||
fn parse_glob(s: &str) -> Result<Vec<MatcherSegment>, anyhow::Error> {
|
||||
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();
|
||||
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();
|
||||
// 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()));
|
||||
// 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(non_empty_segments)
|
||||
}
|
||||
impl TryFrom<RawMatcher> for Matcher {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(raw: RawMatcher) -> Result<Self, Self::Error> {
|
||||
const DEFAULT_CASE: Case = Case::Insensitive;
|
||||
match &raw {
|
||||
RawMatcher::Simple(s) => {
|
||||
let segments = parse_glob(s)?;
|
||||
Ok(Self {
|
||||
segments,
|
||||
case: DEFAULT_CASE,
|
||||
})
|
||||
}
|
||||
RawMatcher::Full {
|
||||
glob,
|
||||
case_sensitive,
|
||||
} => {
|
||||
let segments = parse_glob(glob)?;
|
||||
let case = match case_sensitive {
|
||||
None => DEFAULT_CASE,
|
||||
Some(false) => Case::Insensitive,
|
||||
Some(true) => Case::Sensitive,
|
||||
};
|
||||
Ok(Self { segments, case })
|
||||
}
|
||||
}
|
||||
// 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 ToTokens for Matcher {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let t = match self.0.as_slice() {
|
||||
let case = &self.case;
|
||||
let t = match self.segments.as_slice() {
|
||||
[] => unreachable!("0-length matcher should never be created"),
|
||||
[MatcherSegment::Text(text)] => {
|
||||
quote! { Lazy::new(|| Some(build_matcher_fixed(#text))) }
|
||||
quote! { Lazy::new(|| Some(build_matcher_fixed(#text, #case))) }
|
||||
}
|
||||
// parser logic ensures that this case can only happen when there are dynamic segments
|
||||
segs @ [_, ..] => quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ])) },
|
||||
segs @ [_, ..] => {
|
||||
quote! { Lazy::new(|| build_matcher_dynamic(&[ #(#segs),* ], #case)) }
|
||||
}
|
||||
};
|
||||
tokens.append_all(t);
|
||||
}
|
||||
@@ -175,6 +239,7 @@ impl MatcherSegment {
|
||||
/// A struct that models a single .toml file in /src/syntax_mapping/builtins/.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct MappingDefModel {
|
||||
#[serde(default)]
|
||||
mappings: IndexMap<MappingTarget, Vec<Matcher>>,
|
||||
}
|
||||
impl MappingDefModel {
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ The following files have been manually modified after converting from a `.tmLang
|
||||
as it is not kept in a standalone repository. The file is generated from
|
||||
https://github.com/open-policy-agent/opa/blob/master/misc/syntax/textmate/Rego.tmLanguage
|
||||
* `SML.sublime_syntax` has been added manually from
|
||||
https://github.com/seanjames777/SML-Language-Definitiona as it is not
|
||||
https://github.com/seanjames777/SML-Language-Definition as it is not
|
||||
kept in a standalone repository. The file generated is from
|
||||
https://github.com/seanjames777/SML-Language-Definition/blob/master/sml.tmLanguage
|
||||
* `Cabal.sublime_syntax` has been added manually from
|
||||
|
||||
+38
-5
@@ -37,6 +37,13 @@ Options:
|
||||
name (like 'C++' or 'LaTeX') or possible file extension (like 'cpp', 'hpp' or 'md'). Use
|
||||
'--list-languages' to show all supported language names and file extensions.
|
||||
|
||||
--fallback-syntax <fallback-syntax>
|
||||
Set a fallback language for syntax highlighting when auto-detection fails. Unlike
|
||||
'--language', this is only used when no syntax could be detected from filename, custom
|
||||
syntax mappings, or first-line detection.
|
||||
|
||||
[aliases: --fallback-language]
|
||||
|
||||
-H, --highlight-line <N:M>
|
||||
Highlight the specified line ranges with a different background color For example:
|
||||
'--highlight-line 40' highlights line 40
|
||||
@@ -61,8 +68,8 @@ Options:
|
||||
Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
||||
|
||||
--wrap <mode>
|
||||
Specify the text-wrapping mode (*auto*, never, character). The '--terminal-width' option
|
||||
can be used in addition to control the output width.
|
||||
Specify the text-wrapping mode (*auto*, never, character, word). The '--terminal-width'
|
||||
option can be used in addition to control the output width.
|
||||
|
||||
-S, --chop-long-lines
|
||||
Truncate all lines longer than screen width. Alias for '--wrap=never'.
|
||||
@@ -70,11 +77,22 @@ Options:
|
||||
--terminal-width <width>
|
||||
Explicitly set the width of the terminal instead of determining it automatically. If
|
||||
prefixed with '+' or '-', the value will be treated as an offset to the actual terminal
|
||||
width. See also: '--wrap'.
|
||||
width. This can also be configured via the BAT_WIDTH environment variable (e.g. export
|
||||
BAT_WIDTH="100"). See also: '--wrap'.
|
||||
|
||||
-n, --number
|
||||
Only show line numbers, no other decorations. This is an alias for '--style=numbers'
|
||||
|
||||
-b, --number-nonblank
|
||||
Only show line numbers for non-blank lines, no other decorations. This is an alias for
|
||||
'--style=numbers'. Non-blank lines are lines that contain any character before the line
|
||||
ending, including spaces and tabs. When used together with --number (-n),
|
||||
--number-nonblank (-b) takes precedence.
|
||||
|
||||
Example:
|
||||
printf 'alpha\n\nbeta\n' | bat -b
|
||||
numbers 'alpha' and 'beta', but skips the empty line.
|
||||
|
||||
--color <when>
|
||||
Specify when to use colored output. The automatic mode only enables colors if an
|
||||
interactive terminal is detected - colors are automatically disabled if the output goes to
|
||||
@@ -156,6 +174,14 @@ Options:
|
||||
escape sequences unless the syntax highlighting language is plain text. Possible values:
|
||||
auto, always, *never*.
|
||||
|
||||
--sanitize <when>
|
||||
Specify when to sanitize input bytes for safe terminal display. Implies --strip-ansi to
|
||||
the same value, and additionally substitutes terminal-active control bytes (cursor moves,
|
||||
charset switches, beep, etc.) and Unicode bidi / zero-width formatting characters with the
|
||||
Unicode replacement character (U+FFFD). Tab, LF, FF, and CRLF pass through. Useful for
|
||||
displaying untrusted file content (e.g. file-manager preview panes). Possible values:
|
||||
auto, always, *never*.
|
||||
|
||||
--style <components>
|
||||
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
|
||||
@@ -203,8 +229,11 @@ Options:
|
||||
Display a list of supported languages for syntax highlighting.
|
||||
|
||||
-u, --unbuffered
|
||||
This option exists for POSIX-compliance reasons ('u' is for 'unbuffered'). The output is
|
||||
always unbuffered - this option is simply ignored.
|
||||
Enable unbuffered input reading. When this flag is set, bat will display data as soon as
|
||||
it is available, without waiting for a complete line. This is useful for streaming use
|
||||
cases like 'tail -f logfile | bat -u --paging=never'. Note that line numbers are
|
||||
automatically disabled in unbuffered mode, and syntax highlighting may be imperfect on
|
||||
partial lines.
|
||||
|
||||
--completion <SHELL>
|
||||
Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]
|
||||
@@ -212,6 +241,10 @@ Options:
|
||||
--diagnostic
|
||||
Show diagnostic information for bug reports.
|
||||
|
||||
-E, --quiet-empty
|
||||
When this flag is set, bat will produce no output at all when the input is empty. This is
|
||||
useful when piping commands that may produce empty output, like 'git diff'.
|
||||
|
||||
--acknowledgements
|
||||
Show acknowledgements.
|
||||
|
||||
|
||||
+9
-1
@@ -17,6 +17,8 @@ Options:
|
||||
Show plain style (alias for '--style=plain').
|
||||
-l, --language <language>
|
||||
Set the language for syntax highlighting.
|
||||
--fallback-syntax <fallback-syntax>
|
||||
Set a fallback language for undetected syntaxes. [aliases: --fallback-language]
|
||||
-H, --highlight-line <N:M>
|
||||
Highlight lines N through M.
|
||||
--file-name <name>
|
||||
@@ -26,11 +28,13 @@ Options:
|
||||
--tabs <T>
|
||||
Set the tab width to T spaces.
|
||||
--wrap <mode>
|
||||
Specify the text-wrapping mode (*auto*, never, character).
|
||||
Specify the text-wrapping mode (*auto*, never, character, word).
|
||||
-S, --chop-long-lines
|
||||
Truncate all lines longer than screen width. Alias for '--wrap=never'.
|
||||
-n, --number
|
||||
Show line numbers (alias for '--style=numbers').
|
||||
-b, --number-nonblank
|
||||
Show line numbers for non-blank lines only (alias for '--style=numbers').
|
||||
--color <when>
|
||||
When to use colors (*auto*, never, always).
|
||||
--italic-text <when>
|
||||
@@ -58,8 +62,12 @@ Options:
|
||||
Only print the lines from N to M.
|
||||
-L, --list-languages
|
||||
Display all supported languages.
|
||||
-u, --unbuffered
|
||||
Enable unbuffered input reading for streaming use cases.
|
||||
--completion <SHELL>
|
||||
Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]
|
||||
-E, --quiet-empty
|
||||
Produce no output when the input is empty.
|
||||
-h, --help
|
||||
Print help (see more with '--help')
|
||||
-V, --version
|
||||
|
||||
@@ -10,5 +10,3 @@ No issue will have a different priority based on sponsorship status of the
|
||||
reporter.
|
||||
|
||||
Contributions from anybody are most welcomed, please see our [`CONTRIBUTING.md`](../CONTRIBUTING.md) guide.
|
||||
|
||||
If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769789167,
|
||||
"narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
description = "bat";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs =
|
||||
{ self, ... }@inputs:
|
||||
let
|
||||
supportedSystems = [
|
||||
"x86_64-linux" # 64-bit Intel/AMD Linux
|
||||
"aarch64-linux" # 64-bit ARM Linux
|
||||
"aarch64-darwin" # 64-bit ARM macOS
|
||||
"x86_64-darwin" # 64-bit Intel macOS
|
||||
];
|
||||
|
||||
forEachSupportedSystem =
|
||||
f:
|
||||
inputs.nixpkgs.lib.genAttrs supportedSystems (
|
||||
system:
|
||||
f {
|
||||
inherit system;
|
||||
pkgs = import inputs.nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
devShells = forEachSupportedSystem (
|
||||
{ pkgs, system }:
|
||||
{
|
||||
default = pkgs.mkShellNoCC {
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
+206
-30
@@ -158,7 +158,9 @@ impl HighlightingAssets {
|
||||
let syntax_match = mapping.get_syntax_for(path);
|
||||
|
||||
if let Some(MappingTarget::MapToUnknown) = syntax_match {
|
||||
return Err(Error::UndetectedSyntax(path.to_string_lossy().into()));
|
||||
return Err(Error::UndetectedSyntax(
|
||||
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(MappingTarget::MapTo(syntax_name)) = syntax_match {
|
||||
@@ -175,13 +177,17 @@ impl HighlightingAssets {
|
||||
) {
|
||||
(Some(syntax), _) => Ok(syntax),
|
||||
|
||||
(_, Some(MappingTarget::MapExtensionToUnknown)) => {
|
||||
Err(Error::UndetectedSyntax(path.to_string_lossy().into()))
|
||||
}
|
||||
(_, Some(MappingTarget::MapExtensionToUnknown)) => Err(Error::UndetectedSyntax(
|
||||
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy()),
|
||||
)),
|
||||
|
||||
_ => self
|
||||
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
|
||||
.ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
|
||||
.ok_or_else(|| {
|
||||
Error::UndetectedSyntax(crate::preprocessor::sanitize_for_terminal(
|
||||
&path.to_string_lossy(),
|
||||
))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +216,7 @@ impl HighlightingAssets {
|
||||
pub(crate) fn get_syntax(
|
||||
&self,
|
||||
language: Option<&str>,
|
||||
fallback_syntax: Option<&str>,
|
||||
input: &mut OpenedInput,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> Result<SyntaxReferenceInSet<'_>> {
|
||||
@@ -222,21 +229,50 @@ impl HighlightingAssets {
|
||||
}
|
||||
|
||||
let path = input.path();
|
||||
let path_syntax = if let Some(path) = path {
|
||||
self.get_syntax_for_path(
|
||||
PathAbs::new(path).map_or_else(|_| path.to_owned(), |p| p.as_path().to_path_buf()),
|
||||
mapping,
|
||||
)
|
||||
let absolute_path = path.and_then(|p| {
|
||||
PathAbs::new(p)
|
||||
.ok()
|
||||
.map(|abs| abs.as_path().to_path_buf())
|
||||
.or_else(|| Some(p.to_owned()))
|
||||
});
|
||||
|
||||
let path_syntax = if let Some(ref path) = absolute_path {
|
||||
self.get_syntax_for_path(path, mapping).or_else(|e| {
|
||||
// If syntax detection failed on the given path, retry with the
|
||||
// canonicalized path (which resolves symlinks). This handles
|
||||
// cases like `Aliases/0install -> ../Formula/zero-install.rb`
|
||||
// where the symlink name has no extension but the target does.
|
||||
// See #1001.
|
||||
if matches!(e, Error::UndetectedSyntax(_)) {
|
||||
if let Ok(resolved) = fs::canonicalize(path) {
|
||||
if resolved != *path {
|
||||
return match self.get_syntax_for_path(&resolved, mapping) {
|
||||
Ok(syntax) => Ok(syntax),
|
||||
Err(Error::UndetectedSyntax(_)) => Err(e),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e)
|
||||
})
|
||||
} else {
|
||||
Err(Error::UndetectedSyntax("[unknown]".into()))
|
||||
};
|
||||
|
||||
// If a path wasn't provided, or if path based syntax detection
|
||||
// above failed, we fall back to first-line syntax detection.
|
||||
match path_syntax {
|
||||
// If a path wasn't provided, or if path based syntax detection
|
||||
// above failed, we fall back to first-line syntax detection.
|
||||
Err(Error::UndetectedSyntax(path)) => self
|
||||
.get_first_line_syntax(&mut input.reader)?
|
||||
.ok_or(Error::UndetectedSyntax(path)),
|
||||
Err(Error::UndetectedSyntax(path)) => {
|
||||
if let Some(syntax_in_set) = self.get_first_line_syntax(&mut input.reader)? {
|
||||
Ok(syntax_in_set)
|
||||
} else if let Some(language) = fallback_syntax {
|
||||
self.find_syntax_by_token(language)?
|
||||
.ok_or_else(|| Error::UnknownSyntax(language.to_owned()))
|
||||
} else {
|
||||
Err(Error::UndetectedSyntax(path))
|
||||
}
|
||||
}
|
||||
_ => path_syntax,
|
||||
}
|
||||
}
|
||||
@@ -262,6 +298,24 @@ impl HighlightingAssets {
|
||||
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
|
||||
}
|
||||
|
||||
fn find_syntax_by_hidden_file_name(
|
||||
&self,
|
||||
file_name: &OsStr,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let Some(hidden_file_extension) = file_name
|
||||
.to_str()
|
||||
.and_then(|name| name.strip_prefix('.'))
|
||||
.filter(|name| !name.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// syntect stores `hidden_file_extensions` in the same extension list as
|
||||
// regular file extensions, but dotfiles must be queried without the
|
||||
// leading period.
|
||||
self.find_syntax_by_extension(Some(OsStr::new(hidden_file_extension)))
|
||||
}
|
||||
|
||||
fn find_syntax_by_token(&self, token: &str) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(syntax_set
|
||||
@@ -275,6 +329,9 @@ impl HighlightingAssets {
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let mut syntax = self.find_syntax_by_extension(Some(file_name))?;
|
||||
if syntax.is_none() {
|
||||
syntax = self.find_syntax_by_hidden_file_name(file_name)?;
|
||||
}
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
@@ -290,15 +347,15 @@ impl HighlightingAssets {
|
||||
file_name: &OsStr,
|
||||
ignored_suffixes: &IgnoredSuffixes,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?;
|
||||
if syntax.is_none() {
|
||||
syntax =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
// Note: recursion
|
||||
self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
|
||||
})?;
|
||||
let stripped =
|
||||
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
|
||||
self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
|
||||
.map(Some)
|
||||
})?;
|
||||
match stripped {
|
||||
Some(syntax) => Ok(syntax),
|
||||
None => self.find_syntax_by_extension(Path::new(file_name).extension()),
|
||||
}
|
||||
Ok(syntax)
|
||||
}
|
||||
|
||||
fn get_first_line_syntax(
|
||||
@@ -306,11 +363,11 @@ impl HighlightingAssets {
|
||||
reader: &mut InputReader,
|
||||
) -> Result<Option<SyntaxReferenceInSet<'_>>> {
|
||||
let syntax_set = self.get_syntax_set()?;
|
||||
Ok(String::from_utf8(reader.first_line.clone())
|
||||
Ok(std::str::from_utf8(&reader.first_line)
|
||||
.ok()
|
||||
.and_then(|l| {
|
||||
// Strip UTF-8 BOM if present
|
||||
let line = l.strip_prefix('\u{feff}').unwrap_or(&l);
|
||||
let line = l.strip_prefix('\u{feff}').unwrap_or(l);
|
||||
syntax_set.find_syntax_by_first_line(line)
|
||||
})
|
||||
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
|
||||
@@ -395,11 +452,12 @@ mod tests {
|
||||
fn get_syntax_name(
|
||||
&self,
|
||||
language: Option<&str>,
|
||||
fallback_syntax: Option<&str>,
|
||||
input: &mut OpenedInput,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> String {
|
||||
self.assets
|
||||
.get_syntax(language, input, mapping)
|
||||
.get_syntax(language, fallback_syntax, input, mapping)
|
||||
.map(|syntax_in_set| syntax_in_set.syntax.name.clone())
|
||||
.unwrap_or_else(|_| "!no syntax!".to_owned())
|
||||
}
|
||||
@@ -419,7 +477,7 @@ mod tests {
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
|
||||
@@ -429,7 +487,7 @@ mod tests {
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -449,7 +507,7 @@ mod tests {
|
||||
let input = Input::stdin().with_name(Some(file_name));
|
||||
let mut opened_input = input.open(content, None).unwrap();
|
||||
|
||||
self.get_syntax_name(None, &mut opened_input, &self.syntax_mapping)
|
||||
self.get_syntax_name(None, None, &mut opened_input, &self.syntax_mapping)
|
||||
}
|
||||
|
||||
fn syntax_is_same_for_inputkinds(&self, file_name: &str, content: &str) -> bool {
|
||||
@@ -628,6 +686,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_detection_ignored_suffix_falls_back_to_first_line() {
|
||||
let mut test = SyntaxDetectionTest::new();
|
||||
|
||||
// By default a `.txt` file uses Plain Text, even with a shebang: the
|
||||
// `.txt` extension wins and first-line detection is not reached.
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "#!/usr/bin/env bash"),
|
||||
"Plain Text"
|
||||
);
|
||||
|
||||
// Once `.txt` is an ignored suffix it is stripped before detection. The
|
||||
// stripped name (`test`) has no extension, so detection falls back to the
|
||||
// first line -- even though `.txt` is itself a registered extension that
|
||||
// would otherwise match as Plain Text. See #2745.
|
||||
test.syntax_mapping.insert_ignored_suffix(".txt");
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "#!/usr/bin/env bash"),
|
||||
"Bourne Again Shell (bash)"
|
||||
);
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("test.txt", "<?php"),
|
||||
"PHP"
|
||||
);
|
||||
|
||||
// A `.txt` file without a recognizable first line keeps no syntax (the
|
||||
// caller renders it as plain text); we must not resurrect the shadowed
|
||||
// `.txt` -> Plain Text match here.
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("notes.txt", "just some prose"),
|
||||
"!no syntax!"
|
||||
);
|
||||
|
||||
// Stripping that exposes a real extension still works: `.dev` is ignored,
|
||||
// and the remaining `.json` extension is detected as usual.
|
||||
test.syntax_mapping.insert_ignored_suffix(".dev");
|
||||
assert_eq!(
|
||||
test.syntax_for_file_with_content("config.json.dev", ""),
|
||||
"JSON"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn syntax_detection_is_case_insensitive() {
|
||||
let mut test = SyntaxDetectionTest::new();
|
||||
@@ -661,6 +761,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "build-assets")]
|
||||
#[test]
|
||||
fn syntax_detection_hidden_file_extensions() {
|
||||
let source_dir = TempDir::new().expect("creation of temporary source directory");
|
||||
let cache_dir = TempDir::new().expect("creation of temporary cache directory");
|
||||
let syntax_dir = source_dir.path().join("syntaxes");
|
||||
|
||||
std::fs::create_dir_all(&syntax_dir).expect("creation of syntax directory succeeds");
|
||||
std::fs::write(
|
||||
syntax_dir.join("HiddenFileExtension.sublime-syntax"),
|
||||
r#"%YAML 1.2
|
||||
---
|
||||
name: Hidden File Extension
|
||||
hidden_file_extensions:
|
||||
- testrc
|
||||
scope: source.hiddenfileextension
|
||||
|
||||
contexts:
|
||||
main:
|
||||
- match: .
|
||||
scope: source.hiddenfileextension
|
||||
"#,
|
||||
)
|
||||
.expect("custom syntax can be written");
|
||||
|
||||
build(
|
||||
source_dir.path(),
|
||||
false,
|
||||
false,
|
||||
cache_dir.path(),
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
)
|
||||
.expect("custom assets can be built");
|
||||
|
||||
let test = SyntaxDetectionTest {
|
||||
assets: HighlightingAssets::from_cache(cache_dir.path())
|
||||
.expect("custom syntax cache can be loaded"),
|
||||
syntax_mapping: SyntaxMapping::new(),
|
||||
temp_dir: TempDir::new().expect("creation of temporary directory"),
|
||||
};
|
||||
|
||||
assert_eq!(test.syntax_for_file(".testrc"), "Hidden File Extension");
|
||||
assert_eq!(
|
||||
test.syntax_for_stdin_with_content(".testrc", b""),
|
||||
"Hidden File Extension"
|
||||
);
|
||||
assert!(test.syntax_is_same_for_inputkinds(".testrc", ""));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn syntax_detection_for_symlinked_file() {
|
||||
@@ -682,8 +831,35 @@ mod tests {
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
test.get_syntax_name(None, &mut opened_input, &test.syntax_mapping),
|
||||
test.get_syntax_name(None, None, &mut opened_input, &test.syntax_mapping),
|
||||
"SSH Config"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn syntax_detection_for_symlinked_file_by_target_extension() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let test = SyntaxDetectionTest::new();
|
||||
|
||||
let formula_dir = test.temp_dir.path().join("Formula");
|
||||
std::fs::create_dir(&formula_dir).unwrap();
|
||||
let target = formula_dir.join("zero-install.rb");
|
||||
File::create(&target).unwrap();
|
||||
|
||||
let aliases_dir = test.temp_dir.path().join("Aliases");
|
||||
std::fs::create_dir(&aliases_dir).unwrap();
|
||||
let link = aliases_dir.join("0install");
|
||||
symlink(&target, &link).unwrap();
|
||||
|
||||
let input = Input::ordinary_file(&link);
|
||||
let dummy_stdin: &[u8] = &[];
|
||||
let mut opened_input = input.open(dummy_stdin, None).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
test.get_syntax_name(None, None, &mut opened_input, &test.syntax_mapping),
|
||||
"Ruby"
|
||||
);
|
||||
}
|
||||
}
|
||||
+25
-15
@@ -1,4 +1,5 @@
|
||||
use std::convert::TryInto;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use syntect::highlighting::ThemeSet;
|
||||
@@ -24,7 +25,7 @@ pub fn build(
|
||||
|
||||
let acknowledgements = build_acknowledgements(source_dir, include_acknowledgements)?;
|
||||
|
||||
print_unlinked_contexts(&syntax_set);
|
||||
print_unlinked_contexts(&syntax_set)?;
|
||||
|
||||
write_assets(
|
||||
&theme_set,
|
||||
@@ -46,16 +47,18 @@ fn build_theme_set(source_dir: &Path, include_integrated_assets: bool) -> Result
|
||||
if theme_dir.exists() {
|
||||
let res = theme_set.add_from_folder(&theme_dir);
|
||||
if let Err(err) = res {
|
||||
println!(
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"Failed to load one or more themes from '{}' (reason: '{err}')",
|
||||
theme_dir.to_string_lossy(),
|
||||
);
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"No themes were found in '{}', using the default set",
|
||||
theme_dir.to_string_lossy()
|
||||
);
|
||||
)?;
|
||||
}
|
||||
|
||||
theme_set.try_into()
|
||||
@@ -78,23 +81,25 @@ fn build_syntax_set_builder(
|
||||
if syntax_dir.exists() {
|
||||
syntax_set_builder.add_from_folder(syntax_dir, true)?;
|
||||
} else {
|
||||
println!(
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"No syntaxes were found in '{}', using the default set.",
|
||||
syntax_dir.to_string_lossy()
|
||||
);
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(syntax_set_builder)
|
||||
}
|
||||
|
||||
fn print_unlinked_contexts(syntax_set: &SyntaxSet) {
|
||||
fn print_unlinked_contexts(syntax_set: &SyntaxSet) -> Result<()> {
|
||||
let missing_contexts = syntax_set.find_unlinked_contexts();
|
||||
if !missing_contexts.is_empty() {
|
||||
println!("Some referenced contexts could not be found!");
|
||||
writeln!(io::stdout(), "Some referenced contexts could not be found!")?;
|
||||
for context in missing_contexts {
|
||||
println!("- {context}");
|
||||
writeln!(io::stdout(), "- {context}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_assets(
|
||||
@@ -127,12 +132,13 @@ fn write_assets(
|
||||
)?;
|
||||
}
|
||||
|
||||
print!(
|
||||
write!(
|
||||
io::stdout(),
|
||||
"Writing metadata to folder {} ... ",
|
||||
target_dir.to_string_lossy()
|
||||
);
|
||||
)?;
|
||||
crate::assets_metadata::AssetsMetadata::new(current_version).save_to_folder(target_dir)?;
|
||||
println!("okay");
|
||||
writeln!(io::stdout(), "okay")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -161,10 +167,14 @@ fn asset_to_cache<T: serde::Serialize>(
|
||||
description: &str,
|
||||
compressed: bool,
|
||||
) -> Result<()> {
|
||||
print!("Writing {description} to {} ... ", path.to_string_lossy());
|
||||
write!(
|
||||
io::stdout(),
|
||||
"Writing {description} to {} ... ",
|
||||
path.to_string_lossy()
|
||||
)?;
|
||||
let contents = asset_to_contents(asset, description, compressed)?;
|
||||
std::fs::write(path, &contents[..])
|
||||
.map_err(|_| format!("Could not save {description} to {}", path.to_string_lossy()))?;
|
||||
println!("okay");
|
||||
writeln!(io::stdout(), "okay")?;
|
||||
Ok(())
|
||||
}
|
||||
+105
-60
@@ -38,6 +38,15 @@ pub fn env_no_color() -> bool {
|
||||
env::var_os("NO_COLOR").is_some_and(|x| !x.is_empty())
|
||||
}
|
||||
|
||||
fn parse_strip_ansi_value(raw: Option<&str>, flag_name: &str) -> StripAnsiMode {
|
||||
match raw {
|
||||
Some("never") | None => StripAnsiMode::Never,
|
||||
Some("always") => StripAnsiMode::Always,
|
||||
Some("auto") => StripAnsiMode::Auto,
|
||||
_ => unreachable!("other values for {flag_name} are not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
enum HelpType {
|
||||
Short,
|
||||
Long,
|
||||
@@ -50,6 +59,10 @@ pub struct App {
|
||||
/// (not from config file or environment variables).
|
||||
/// This is used to honor the flag when piping output, similar to `cat -n`.
|
||||
number_from_cli: bool,
|
||||
/// True if -b / --number-nonblank was passed on the command line
|
||||
/// (not from config file or environment variables).
|
||||
/// This is used to honor the flag when piping output, similar to `cat -b`.
|
||||
number_nonblank_from_cli: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -59,36 +72,12 @@ impl App {
|
||||
|
||||
let interactive_output = std::io::stdout().is_terminal();
|
||||
|
||||
// Check if the -n / --number option was passed on the command line
|
||||
// (before merging with config file and environment variables).
|
||||
// This is needed to honor the -n flag when piping output, similar to `cat -n`.
|
||||
// We need to handle both standalone (-n, --number) and combined short flags (-pn, -An, etc.)
|
||||
// Note: We only check if -n appears and is not overridden by -p in the same combined flag.
|
||||
// For combined flags like -np, -p comes after -n and overrides it, so we don't count it.
|
||||
// For combined flags like -pn, -n comes after -p and takes effect.
|
||||
let number_from_cli = wild::args_os().any(|arg| {
|
||||
let arg_str = arg.to_string_lossy();
|
||||
if arg_str == "-n" || arg_str == "--number" {
|
||||
return true;
|
||||
}
|
||||
// Handle combined short flags
|
||||
// Only count -n if it's the LAST flag in the combined form (so -p doesn't override it)
|
||||
// or if -p is not present in the combined form
|
||||
if arg_str.starts_with('-') && !arg_str.starts_with("--") && arg_str.len() > 2 {
|
||||
let chars: Vec<char> = arg_str.chars().skip(1).collect();
|
||||
let n_pos = chars.iter().position(|&c| c == 'n');
|
||||
let p_pos = chars.iter().position(|&c| c == 'p');
|
||||
// -n is in the combined flag and either:
|
||||
// - -p is not present, OR
|
||||
// - -n comes after -p (so -n takes effect)
|
||||
if let Some(n) = n_pos {
|
||||
if p_pos.is_none() || n > p_pos.unwrap() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
// Parse the original command line separately from config and environment arguments.
|
||||
// Using clap here keeps CLI-only detection consistent with the final parser, including
|
||||
// option terminators, combined short flags, and options that take values.
|
||||
let cli_matches = Self::cli_matches(interactive_output);
|
||||
let number_from_cli = cli_matches.get_flag("number");
|
||||
let number_nonblank_from_cli = cli_matches.get_flag("number-nonblank");
|
||||
|
||||
let matches = Self::matches(interactive_output)?;
|
||||
|
||||
@@ -110,16 +99,18 @@ impl App {
|
||||
_ => interactive_output, // auto: use color if interactive
|
||||
};
|
||||
|
||||
let custom_pager = matches.get_one::<String>("pager").map(|s| s.to_string());
|
||||
let pager = matches.get_one::<String>("pager").map(|s| s.as_str());
|
||||
let theme_options = Self::theme_options_from_matches(&matches);
|
||||
let use_custom_assets = !matches.get_flag("no-custom-assets");
|
||||
|
||||
Self::display_help(
|
||||
interactive_output,
|
||||
help_type,
|
||||
use_pager,
|
||||
use_color,
|
||||
custom_pager,
|
||||
pager,
|
||||
theme_options,
|
||||
use_custom_assets,
|
||||
)?;
|
||||
std::process::exit(0);
|
||||
}
|
||||
@@ -128,6 +119,7 @@ impl App {
|
||||
matches,
|
||||
interactive_output,
|
||||
number_from_cli,
|
||||
number_nonblank_from_cli,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -136,8 +128,9 @@ impl App {
|
||||
help_type: HelpType,
|
||||
use_pager: bool,
|
||||
use_color: bool,
|
||||
custom_pager: Option<String>,
|
||||
pager: Option<&str>,
|
||||
theme_options: ThemeOptions,
|
||||
use_custom_assets: bool,
|
||||
) -> Result<()> {
|
||||
use crate::assets::assets_from_cache_or_binary;
|
||||
use crate::directories::PROJECT_DIRS;
|
||||
@@ -164,12 +157,10 @@ impl App {
|
||||
PagingMode::Never
|
||||
};
|
||||
|
||||
let pager = bat::config::get_pager_executable(custom_pager.as_deref());
|
||||
|
||||
let help_config = Config {
|
||||
style_components: StyleComponents::new(StyleComponent::Plain.components(false)),
|
||||
paging_mode,
|
||||
pager: pager.as_deref(),
|
||||
pager,
|
||||
colored_output: use_color,
|
||||
true_color: use_color,
|
||||
language: if use_color { Some("help") } else { None },
|
||||
@@ -178,7 +169,7 @@ impl App {
|
||||
};
|
||||
|
||||
let cache_dir = PROJECT_DIRS.cache_dir();
|
||||
let assets = assets_from_cache_or_binary(false, cache_dir)?;
|
||||
let assets = assets_from_cache_or_binary(use_custom_assets, cache_dir)?;
|
||||
Controller::new(&help_config, &assets)
|
||||
.run(inputs, None)
|
||||
.ok();
|
||||
@@ -200,6 +191,10 @@ impl App {
|
||||
args
|
||||
}
|
||||
|
||||
fn cli_matches(interactive_output: bool) -> ArgMatches {
|
||||
clap_app::build_app(interactive_output).get_matches_from(wild::args_os())
|
||||
}
|
||||
|
||||
fn matches(interactive_output: bool) -> Result<ArgMatches> {
|
||||
// Check if we should skip config file processing for special arguments
|
||||
// that don't require full application setup (version, diagnostic)
|
||||
@@ -383,6 +378,10 @@ impl App {
|
||||
None
|
||||
}
|
||||
}),
|
||||
fallback_syntax: self
|
||||
.matches
|
||||
.get_one::<String>("fallback-syntax")
|
||||
.map(|s| s.as_str()),
|
||||
show_nonprintable: self.matches.get_flag("show-all"),
|
||||
nonprintable_notation: match self
|
||||
.matches
|
||||
@@ -398,27 +397,30 @@ impl App {
|
||||
Some("no-printing") => BinaryBehavior::NoPrinting,
|
||||
_ => unreachable!("other values for --binary are not allowed"),
|
||||
},
|
||||
wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
|
||||
if !self.matches.get_flag("chop-long-lines") {
|
||||
wrapping_mode: {
|
||||
if self.matches.get_flag("chop-long-lines") {
|
||||
WrappingMode::NoWrapping(true)
|
||||
} else {
|
||||
match self.matches.get_one::<String>("wrap").map(|s| s.as_str()) {
|
||||
Some("character") => WrappingMode::Character,
|
||||
Some("word") => WrappingMode::Word,
|
||||
Some("never") => WrappingMode::NoWrapping(true),
|
||||
Some("auto") | None => {
|
||||
if style_components.plain() {
|
||||
WrappingMode::NoWrapping(false)
|
||||
if self.interactive_output || maybe_term_width.is_some() {
|
||||
if style_components.plain() && maybe_term_width.is_none() {
|
||||
WrappingMode::NoWrapping(false)
|
||||
} else {
|
||||
WrappingMode::Character
|
||||
}
|
||||
} else {
|
||||
WrappingMode::Character
|
||||
// We don't have the tty width when piping to another program.
|
||||
// There's no point in wrapping when this is the case.
|
||||
WrappingMode::NoWrapping(false)
|
||||
}
|
||||
}
|
||||
_ => unreachable!("other values for --wrap are not allowed"),
|
||||
}
|
||||
} else {
|
||||
WrappingMode::NoWrapping(true)
|
||||
}
|
||||
} else {
|
||||
// We don't have the tty width when piping to another program.
|
||||
// There's no point in wrapping when this is the case.
|
||||
WrappingMode::NoWrapping(false)
|
||||
},
|
||||
colored_output: self.matches.get_flag("force-colorization")
|
||||
|| match self.matches.get_one::<String>("color").map(|s| s.as_str()) {
|
||||
@@ -437,7 +439,8 @@ impl App {
|
||||
.map(|s| s.as_str())
|
||||
== Some("always")
|
||||
|| self.matches.get_flag("force-colorization")
|
||||
|| self.number_from_cli),
|
||||
|| self.number_from_cli
|
||||
|| self.number_nonblank_from_cli),
|
||||
tab_width: self
|
||||
.matches
|
||||
.get_one::<String>("tabs")
|
||||
@@ -450,16 +453,36 @@ impl App {
|
||||
4
|
||||
},
|
||||
),
|
||||
strip_ansi: match self
|
||||
.matches
|
||||
.get_one::<String>("strip-ansi")
|
||||
.map(|s| s.as_str())
|
||||
{
|
||||
Some("never") => StripAnsiMode::Never,
|
||||
Some("always") => StripAnsiMode::Always,
|
||||
Some("auto") => StripAnsiMode::Auto,
|
||||
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
||||
strip_ansi: {
|
||||
let sanitize = parse_strip_ansi_value(
|
||||
self.matches
|
||||
.get_one::<String>("sanitize")
|
||||
.map(|s| s.as_str()),
|
||||
"--sanitize",
|
||||
);
|
||||
let strip_ansi = parse_strip_ansi_value(
|
||||
self.matches
|
||||
.get_one::<String>("strip-ansi")
|
||||
.map(|s| s.as_str()),
|
||||
"--strip-ansi",
|
||||
);
|
||||
// --sanitize implies --strip-ansi to the same value.
|
||||
if sanitize != StripAnsiMode::Never {
|
||||
sanitize
|
||||
} else {
|
||||
strip_ansi
|
||||
}
|
||||
},
|
||||
sanitize: parse_strip_ansi_value(
|
||||
self.matches
|
||||
.get_one::<String>("sanitize")
|
||||
.map(|s| s.as_str()),
|
||||
"--sanitize",
|
||||
),
|
||||
quiet_empty: self.matches.get_flag("quiet-empty"),
|
||||
unbuffered: self.matches.get_flag("unbuffered"),
|
||||
number_nonblank: self.matches.get_flag("number-nonblank")
|
||||
|| self.number_nonblank_from_cli,
|
||||
theme: theme(self.theme_options()).to_string(),
|
||||
visible_lines: match self.matches.try_contains_id("diff").unwrap_or_default()
|
||||
&& self.matches.get_flag("diff")
|
||||
@@ -578,9 +601,26 @@ impl App {
|
||||
])));
|
||||
}
|
||||
|
||||
// Only line numbers for non-blank lines if `--number-nonblank`.
|
||||
if self.matches.get_flag("number-nonblank") || self.number_nonblank_from_cli {
|
||||
return Some(StyleComponents(HashSet::from([
|
||||
StyleComponent::LineNumbers,
|
||||
])));
|
||||
}
|
||||
|
||||
// Plain if `--plain` is specified at least once.
|
||||
if self.matches.get_count("plain") > 0 {
|
||||
return Some(StyleComponents(HashSet::from([StyleComponent::Plain])));
|
||||
let mut components = HashSet::from([StyleComponent::Plain]);
|
||||
// When --diff is active, preserve change markers and snip separators
|
||||
// so that diff output remains visually useful.
|
||||
if self.matches.try_contains_id("diff").unwrap_or_default()
|
||||
&& self.matches.get_flag("diff")
|
||||
{
|
||||
#[cfg(feature = "git")]
|
||||
components.insert(StyleComponent::Changes);
|
||||
components.insert(StyleComponent::Snip);
|
||||
}
|
||||
return Some(StyleComponents(components));
|
||||
}
|
||||
|
||||
// Default behavior.
|
||||
@@ -617,10 +657,15 @@ impl App {
|
||||
bat_warning!("Style 'rule' is a subset of style 'grid', 'rule' will not be visible.");
|
||||
}
|
||||
|
||||
// Auto-disable line numbers in unbuffered mode to avoid confusion with partial lines
|
||||
if self.matches.get_flag("unbuffered") {
|
||||
styled_components.0.remove(&StyleComponent::LineNumbers);
|
||||
}
|
||||
|
||||
Ok(styled_components)
|
||||
}
|
||||
|
||||
fn theme_options(&self) -> ThemeOptions {
|
||||
pub(crate) fn theme_options(&self) -> ThemeOptions {
|
||||
Self::theme_options_from_matches(&self.matches)
|
||||
}
|
||||
|
||||
|
||||
+15
-10
@@ -1,5 +1,5 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -9,10 +9,11 @@ use bat::assets::HighlightingAssets;
|
||||
use bat::assets_metadata::AssetsMetadata;
|
||||
use bat::error::*;
|
||||
|
||||
pub fn clear_assets(cache_dir: &Path) {
|
||||
clear_asset(cache_dir.join("themes.bin"), "theme set cache");
|
||||
clear_asset(cache_dir.join("syntaxes.bin"), "syntax set cache");
|
||||
clear_asset(cache_dir.join("metadata.yaml"), "metadata file");
|
||||
pub fn clear_assets(cache_dir: &Path) -> Result<()> {
|
||||
clear_asset(cache_dir.join("themes.bin"), "theme set cache")?;
|
||||
clear_asset(cache_dir.join("syntaxes.bin"), "syntax set cache")?;
|
||||
clear_asset(cache_dir.join("metadata.yaml"), "metadata file")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assets_from_cache_or_binary(
|
||||
@@ -43,15 +44,19 @@ pub fn assets_from_cache_or_binary(
|
||||
Ok(custom_assets.unwrap_or_else(HighlightingAssets::from_binary))
|
||||
}
|
||||
|
||||
fn clear_asset(path: PathBuf, description: &str) {
|
||||
print!("Clearing {description} ... ");
|
||||
fn clear_asset(path: PathBuf, description: &str) -> Result<()> {
|
||||
write!(io::stdout(), "Clearing {description} ... ")?;
|
||||
match fs::remove_file(&path) {
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
println!("skipped (not present)");
|
||||
writeln!(io::stdout(), "skipped (not present)")?;
|
||||
}
|
||||
Err(err) => {
|
||||
println!("could not remove the cache file {path:?}: {err}");
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"could not remove the cache file {path:?}: {err}"
|
||||
)?;
|
||||
}
|
||||
Ok(_) => println!("okay"),
|
||||
Ok(_) => writeln!(io::stdout(), "okay")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+94
-17
@@ -1,7 +1,5 @@
|
||||
use bat::style::StyleComponentList;
|
||||
use clap::{
|
||||
crate_name, crate_version, value_parser, Arg, ArgAction, ArgGroup, ColorChoice, Command,
|
||||
};
|
||||
use clap::{crate_name, crate_version, value_parser, Arg, ArgAction, ColorChoice, Command};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -54,6 +52,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.long("show-all")
|
||||
.alias("show-nonprintable")
|
||||
.short('A')
|
||||
.overrides_with("show-all")
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("language")
|
||||
.help("Show non-printable characters (space, tab, newline, ..).")
|
||||
@@ -99,6 +98,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Arg::new("plain")
|
||||
.overrides_with("plain")
|
||||
.overrides_with("number")
|
||||
.overrides_with("number-nonblank")
|
||||
.short('p')
|
||||
.long("plain")
|
||||
.action(ArgAction::Count)
|
||||
@@ -122,6 +122,17 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
language names and file extensions.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("fallback-syntax")
|
||||
.long("fallback-syntax")
|
||||
.visible_alias("fallback-language")
|
||||
.help("Set a fallback language for undetected syntaxes.")
|
||||
.long_help(
|
||||
"Set a fallback language for syntax highlighting when auto-detection fails. \
|
||||
Unlike '--language', this is only used when no syntax could be detected from \
|
||||
filename, custom syntax mappings, or first-line detection.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("highlight-line")
|
||||
.long("highlight-line")
|
||||
@@ -161,6 +172,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Arg::new("diff")
|
||||
.long("diff")
|
||||
.short('d')
|
||||
.overrides_with("diff")
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("line-range")
|
||||
.help("Only show lines that have been added/removed/modified.")
|
||||
@@ -213,11 +225,11 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.long("wrap")
|
||||
.overrides_with("wrap")
|
||||
.value_name("mode")
|
||||
.value_parser(["auto", "never", "character"])
|
||||
.value_parser(["auto", "never", "character", "word"])
|
||||
.default_value("auto")
|
||||
.hide_default_value(true)
|
||||
.help("Specify the text-wrapping mode (*auto*, never, character).")
|
||||
.long_help("Specify the text-wrapping mode (*auto*, never, character). \
|
||||
.help("Specify the text-wrapping mode (*auto*, never, character, word).")
|
||||
.long_help("Specify the text-wrapping mode (*auto*, never, character, word). \
|
||||
The '--terminal-width' option can be used in addition to \
|
||||
control the output width."),
|
||||
)
|
||||
@@ -225,12 +237,14 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Arg::new("chop-long-lines")
|
||||
.long("chop-long-lines")
|
||||
.short('S')
|
||||
.overrides_with("chop-long-lines")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Truncate all lines longer than screen width. Alias for '--wrap=never'."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("terminal-width")
|
||||
.long("terminal-width")
|
||||
.overrides_with("terminal-width")
|
||||
.value_name("width")
|
||||
.hide_short_help(true)
|
||||
.allow_hyphen_values(true)
|
||||
@@ -246,10 +260,13 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
})
|
||||
.help(
|
||||
.help("Explicitly set the width of the terminal instead of determining it automatically.")
|
||||
.long_help(
|
||||
"Explicitly set the width of the terminal instead of determining it \
|
||||
automatically. If prefixed with '+' or '-', the value will be treated \
|
||||
as an offset to the actual terminal width. See also: '--wrap'.",
|
||||
as an offset to the actual terminal width. This can also be configured \
|
||||
via the BAT_WIDTH environment variable (e.g. export BAT_WIDTH=\"100\"). \
|
||||
See also: '--wrap'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -264,6 +281,23 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
'--style=numbers'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("number-nonblank")
|
||||
.long("number-nonblank")
|
||||
.overrides_with("number-nonblank")
|
||||
.short('b')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Show line numbers for non-blank lines only (alias for '--style=numbers').")
|
||||
.long_help(
|
||||
"Only show line numbers for non-blank lines, no other decorations. This is an \
|
||||
alias for '--style=numbers'. Non-blank lines are lines that contain any \
|
||||
character before the line ending, including spaces and tabs. When used \
|
||||
together with --number (-n), --number-nonblank (-b) takes precedence.\n\n\
|
||||
Example:\n \
|
||||
printf 'alpha\\n\\nbeta\\n' | bat -b\n \
|
||||
numbers 'alpha' and 'beta', but skips the empty line.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("color")
|
||||
.long("color")
|
||||
@@ -442,6 +476,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Arg::new("squeeze-blank")
|
||||
.long("squeeze-blank")
|
||||
.short('s')
|
||||
.overrides_with("squeeze-blank")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Squeeze consecutive empty lines.")
|
||||
.long_help("Squeeze consecutive empty lines into a single empty line.")
|
||||
@@ -467,6 +502,24 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
language is plain text. Possible values: auto, always, *never*.")
|
||||
.hide_short_help(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("sanitize")
|
||||
.long("sanitize")
|
||||
.overrides_with("sanitize")
|
||||
.value_name("when")
|
||||
.value_parser(["auto", "always", "never"])
|
||||
.default_value("never")
|
||||
.hide_default_value(true)
|
||||
.help("Sanitize untrusted input for safe display (auto, always, *never*)")
|
||||
.long_help("Specify when to sanitize input bytes for safe terminal display. \
|
||||
Implies --strip-ansi to the same value, and additionally substitutes \
|
||||
terminal-active control bytes (cursor moves, charset switches, beep, etc.) \
|
||||
and Unicode bidi / zero-width formatting characters with the Unicode \
|
||||
replacement character (U+FFFD). Tab, LF, FF, and CRLF pass through. Useful \
|
||||
for displaying untrusted file content (e.g. file-manager preview panes). \
|
||||
Possible values: auto, always, *never*.")
|
||||
.hide_short_help(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("style")
|
||||
.long("style")
|
||||
@@ -549,12 +602,16 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Arg::new("unbuffered")
|
||||
.short('u')
|
||||
.long("unbuffered")
|
||||
.overrides_with("unbuffered")
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide_short_help(true)
|
||||
.help("Enable unbuffered input reading for streaming use cases.")
|
||||
.long_help(
|
||||
"This option exists for POSIX-compliance reasons ('u' is for \
|
||||
'unbuffered'). The output is always unbuffered - this option \
|
||||
is simply ignored.",
|
||||
"Enable unbuffered input reading. When this flag is set, bat will \
|
||||
display data as soon as it is available, without waiting for a \
|
||||
complete line. This is useful for streaming use cases like \
|
||||
'tail -f logfile | bat -u --paging=never'. Note that line numbers \
|
||||
are automatically disabled in unbuffered mode, and syntax \
|
||||
highlighting may be imperfect on partial lines.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@@ -567,6 +624,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.arg(
|
||||
Arg::new("no-custom-assets")
|
||||
.long("no-custom-assets")
|
||||
.overrides_with("no-custom-assets")
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide(true)
|
||||
.help("Do not load custom assets"),
|
||||
@@ -643,6 +701,19 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.hide_short_help(true)
|
||||
.help("Show diagnostic information for bug reports."),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("quiet-empty")
|
||||
.long("quiet-empty")
|
||||
.short('E')
|
||||
.overrides_with("quiet-empty")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Produce no output when the input is empty.")
|
||||
.long_help(
|
||||
"When this flag is set, bat will produce no output at all when \
|
||||
the input is empty. This is useful when piping commands that may \
|
||||
produce empty output, like 'git diff'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("acknowledgements")
|
||||
.long("acknowledgements")
|
||||
@@ -653,6 +724,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.arg(
|
||||
Arg::new("set-terminal-title")
|
||||
.long("set-terminal-title")
|
||||
.overrides_with("set-terminal-title")
|
||||
.action(ArgAction::SetTrue)
|
||||
.hide_short_help(true)
|
||||
.help("Sets terminal title to filenames when using a pager."),
|
||||
@@ -682,11 +754,20 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
Command::new("cache")
|
||||
.hide(true)
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
.arg_required_else_help(true)
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.short('h')
|
||||
.long("help")
|
||||
.action(ArgAction::Help)
|
||||
.help("Print help"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("build")
|
||||
.long("build")
|
||||
.short('b')
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("clear")
|
||||
.help("Initialize (or update) the syntax/theme cache.")
|
||||
.long_help(
|
||||
"Initialize (or update) the syntax/theme cache by loading from \
|
||||
@@ -698,13 +779,9 @@ pub fn build_app(interactive_output: bool) -> Command {
|
||||
.long("clear")
|
||||
.short('c')
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with("build")
|
||||
.help("Remove the cached syntax definitions and themes."),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::new("cache-actions")
|
||||
.args(["build", "clear"])
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("source")
|
||||
.long("source")
|
||||
|
||||
+63
-9
@@ -2,7 +2,7 @@ use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::directories::PROJECT_DIRS;
|
||||
|
||||
@@ -32,12 +32,13 @@ pub fn config_file() -> PathBuf {
|
||||
pub fn generate_config_file() -> bat::error::Result<()> {
|
||||
let config_file = config_file();
|
||||
if config_file.is_file() {
|
||||
println!(
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"A config file already exists at: {}",
|
||||
config_file.to_string_lossy()
|
||||
);
|
||||
)?;
|
||||
|
||||
print!("Overwrite? (y/N): ");
|
||||
write!(io::stdout(), "Overwrite? (y/N): ")?;
|
||||
io::stdout().flush()?;
|
||||
let mut decision = String::new();
|
||||
io::stdin().read_line(&mut decision)?;
|
||||
@@ -93,10 +94,11 @@ pub fn generate_config_file() -> bat::error::Result<()> {
|
||||
)
|
||||
})?;
|
||||
|
||||
println!(
|
||||
writeln!(
|
||||
io::stdout(),
|
||||
"Success! Config file written to {}",
|
||||
config_file.to_string_lossy()
|
||||
);
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -104,18 +106,32 @@ pub fn generate_config_file() -> bat::error::Result<()> {
|
||||
pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> {
|
||||
let mut config = String::new();
|
||||
|
||||
if let Ok(c) = fs::read_to_string(system_config_file()) {
|
||||
let system_config = system_config_file();
|
||||
let user_config = config_file();
|
||||
|
||||
if let Ok(c) = fs::read_to_string(&system_config) {
|
||||
config.push_str(&c);
|
||||
config.push('\n');
|
||||
}
|
||||
|
||||
if let Ok(c) = fs::read_to_string(config_file()) {
|
||||
config.push_str(&c);
|
||||
// Skip the user config if it resolves to the same file as the system config,
|
||||
// which can happen when BAT_CONFIG_DIR is set to e.g. "/etc/bat". See #3589.
|
||||
if !same_file(&system_config, &user_config) {
|
||||
if let Ok(c) = fs::read_to_string(&user_config) {
|
||||
config.push_str(&c);
|
||||
}
|
||||
}
|
||||
|
||||
get_args_from_str(&config)
|
||||
}
|
||||
|
||||
fn same_file(a: &Path, b: &Path) -> bool {
|
||||
match (fs::canonicalize(a), fs::canonicalize(b)) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => a == b,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_args_from_env_opts_var() -> Option<Result<Vec<OsString>, shell_words::ParseError>> {
|
||||
env::var("BAT_OPTS").ok().map(|s| get_args_from_str(&s))
|
||||
}
|
||||
@@ -139,6 +155,7 @@ fn get_args_from_str(content: &str) -> Result<Vec<OsString>, shell_words::ParseE
|
||||
pub fn get_args_from_env_vars() -> Vec<OsString> {
|
||||
[
|
||||
("--tabs", "BAT_TABS"),
|
||||
("--terminal-width", "BAT_WIDTH"),
|
||||
("--theme", bat::theme::env::BAT_THEME),
|
||||
("--theme-dark", bat::theme::env::BAT_THEME_DARK),
|
||||
("--theme-light", bat::theme::env::BAT_THEME_LIGHT),
|
||||
@@ -214,3 +231,40 @@ fn comments() {
|
||||
get_args_from_str(config).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_identical_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file = dir.path().join("config");
|
||||
fs::write(&file, "").unwrap();
|
||||
assert!(same_file(&file, &file));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_different_paths() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let a = dir.path().join("a");
|
||||
let b = dir.path().join("b");
|
||||
fs::write(&a, "").unwrap();
|
||||
fs::write(&b, "").unwrap();
|
||||
assert!(!same_file(&a, &b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_nonexistent() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let a = dir.path().join("a");
|
||||
let b = dir.path().join("b");
|
||||
assert!(!same_file(&a, &b));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn same_file_via_symlink() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let original = dir.path().join("config");
|
||||
let link = dir.path().join("link");
|
||||
fs::write(&original, "").unwrap();
|
||||
std::os::unix::fs::symlink(&original, &link).unwrap();
|
||||
assert!(same_file(&original, &link));
|
||||
}
|
||||
+67
-29
@@ -17,7 +17,6 @@ use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
use bat::output::{OutputHandle, OutputType};
|
||||
use bat::theme::DetectColorScheme;
|
||||
use nu_ansi_term::Color::Green;
|
||||
use nu_ansi_term::Style;
|
||||
|
||||
@@ -39,7 +38,7 @@ use bat::{
|
||||
error::*,
|
||||
input::Input,
|
||||
style::{StyleComponent, StyleComponents},
|
||||
theme::{color_scheme, default_theme, ColorScheme},
|
||||
theme::{default_theme, theme, ColorScheme, ThemeOptions},
|
||||
MappingTarget, PagingMode,
|
||||
};
|
||||
|
||||
@@ -75,9 +74,9 @@ fn run_cache_subcommand(
|
||||
#[cfg(feature = "build-assets")]
|
||||
build_assets(matches, config_dir, cache_dir)?;
|
||||
#[cfg(not(feature = "build-assets"))]
|
||||
println!("bat has been built without the 'build-assets' feature. The 'cache --build' option is not available.");
|
||||
writeln!(io::stdout(), "bat has been built without the 'build-assets' feature. The 'cache --build' option is not available.")?;
|
||||
} else if matches.get_flag("clear") {
|
||||
clear_assets(cache_dir);
|
||||
clear_assets(cache_dir)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -153,7 +152,14 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
|
||||
let comma_separator = ", ";
|
||||
let separator = " ";
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
let desired_width = config.term_width - longest - separator.len();
|
||||
// Clamp instead of subtracting: a tiny `--terminal-width` (smaller than the
|
||||
// longest language name) would otherwise underflow `usize` and wrap to a huge
|
||||
// value, silently disabling wrapping (and panicking in debug/overflow-checked
|
||||
// builds). Mirrors the `saturating_sub` fix applied to `print_snip` in #3804.
|
||||
let desired_width = config
|
||||
.term_width
|
||||
.saturating_sub(longest)
|
||||
.saturating_sub(separator.len());
|
||||
|
||||
let style = if config.colored_output {
|
||||
Green.normal()
|
||||
@@ -197,7 +203,7 @@ pub fn list_themes(
|
||||
cfg: &Config,
|
||||
config_dir: &Path,
|
||||
cache_dir: &Path,
|
||||
detect_color_scheme: DetectColorScheme,
|
||||
theme_options: ThemeOptions,
|
||||
) -> Result<()> {
|
||||
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
|
||||
let mut config = cfg.clone();
|
||||
@@ -206,7 +212,7 @@ pub fn list_themes(
|
||||
config.language = Some("Rust");
|
||||
config.style_components = StyleComponents(style);
|
||||
|
||||
let default_theme_name = default_theme(color_scheme(detect_color_scheme).unwrap_or_default());
|
||||
let default_theme_name = theme(theme_options).to_string();
|
||||
let mut buf = String::new();
|
||||
let mut handle = OutputHandle::FmtWrite(&mut buf);
|
||||
|
||||
@@ -256,11 +262,17 @@ pub fn list_themes(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_terminal_title_to(new_terminal_title: String) {
|
||||
fn set_terminal_title_to(new_terminal_title: String) -> Result<()> {
|
||||
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();
|
||||
// Prevent BEL/ESC/C1 bytes in the title from terminating or nesting the OSC.
|
||||
let safe_title = bat::sanitize_for_terminal(&new_terminal_title);
|
||||
write!(
|
||||
io::stdout(),
|
||||
"{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}"
|
||||
)?;
|
||||
io::stdout().flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_new_terminal_title(inputs: &Vec<Input>) -> String {
|
||||
@@ -278,14 +290,35 @@ fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Resu
|
||||
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
|
||||
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));
|
||||
set_terminal_title_to(get_new_terminal_title(&inputs))?;
|
||||
}
|
||||
controller.run(inputs, None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bugreport")]
|
||||
fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
||||
use bugreport::{bugreport, collector::*, format::Markdown};
|
||||
fn invoke_bugreport(app: &App, cache_dir: &Path) -> Result<()> {
|
||||
use bugreport::{bugreport, collector::*, format::Markdown, report::ReportEntry};
|
||||
|
||||
struct ColorSchemeCollector;
|
||||
|
||||
impl Collector for ColorSchemeCollector {
|
||||
fn description(&self) -> &str {
|
||||
"Detected terminal color scheme"
|
||||
}
|
||||
|
||||
fn collect(
|
||||
&mut self,
|
||||
_: &bugreport::CrateInfo,
|
||||
) -> std::result::Result<ReportEntry, CollectionError> {
|
||||
let color_scheme = bat::theme::color_scheme(bat::theme::DetectColorScheme::Always);
|
||||
let text = match color_scheme {
|
||||
Some(bat::theme::ColorScheme::Dark) => "dark",
|
||||
Some(bat::theme::ColorScheme::Light) => "light",
|
||||
None => "not detected",
|
||||
};
|
||||
Ok(ReportEntry::Text(text.to_string()))
|
||||
}
|
||||
}
|
||||
let pager = bat::config::get_pager_executable(
|
||||
app.matches.get_one::<String>("pager").map(|s| s.as_str()),
|
||||
)
|
||||
@@ -300,6 +333,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
||||
.info(CommandLine::default())
|
||||
.info(EnvironmentVariables::list(&[
|
||||
"BAT_CACHE_PATH",
|
||||
"BAT_CONFIG_DIR",
|
||||
"BAT_CONFIG_PATH",
|
||||
"BAT_OPTS",
|
||||
"BAT_PAGER",
|
||||
@@ -307,6 +341,9 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
||||
"BAT_STYLE",
|
||||
"BAT_TABS",
|
||||
"BAT_THEME",
|
||||
"BAT_WIDTH",
|
||||
bat::theme::env::BAT_THEME_DARK,
|
||||
bat::theme::env::BAT_THEME_LIGHT,
|
||||
"COLORTERM",
|
||||
"LANG",
|
||||
"LC_ALL",
|
||||
@@ -326,6 +363,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
||||
custom_assets_metadata,
|
||||
))
|
||||
.info(DirectoryEntries::new("Custom assets", cache_dir))
|
||||
.info(ColorSchemeCollector)
|
||||
.info(CompileTimeInformation::default());
|
||||
|
||||
#[cfg(feature = "paging")]
|
||||
@@ -337,7 +375,8 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
||||
))
|
||||
};
|
||||
|
||||
report.print::<Markdown>();
|
||||
writeln!(io::stdout(), "{}", report.format::<Markdown>())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
|
||||
@@ -349,21 +388,22 @@ fn run() -> Result<bool> {
|
||||
|
||||
if app.matches.get_flag("diagnostic") {
|
||||
#[cfg(feature = "bugreport")]
|
||||
invoke_bugreport(&app, cache_dir);
|
||||
invoke_bugreport(&app, cache_dir)?;
|
||||
#[cfg(not(feature = "bugreport"))]
|
||||
println!("bat has been built without the 'bugreport' feature. The '--diagnostic' option is not available.");
|
||||
writeln!(io::stdout(), "bat has been built without the 'bugreport' feature. The '--diagnostic' option is not available.")?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
#[cfg(feature = "application")]
|
||||
if let Some(shell) = app.matches.get_one::<String>("completion") {
|
||||
match shell.as_str() {
|
||||
"bash" => println!("{}", completions::BASH_COMPLETION),
|
||||
"fish" => println!("{}", completions::FISH_COMPLETION),
|
||||
"ps1" => println!("{}", completions::PS1_COMPLETION),
|
||||
"zsh" => println!("{}", completions::ZSH_COMPLETION),
|
||||
let completion = match shell.as_str() {
|
||||
"bash" => completions::BASH_COMPLETION,
|
||||
"fish" => completions::FISH_COMPLETION,
|
||||
"ps1" => completions::PS1_COMPLETION,
|
||||
"zsh" => completions::ZSH_COMPLETION,
|
||||
_ => unreachable!("No completion for shell '{shell}' available."),
|
||||
}
|
||||
};
|
||||
writeln!(io::stdout(), "{completion}")?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -394,17 +434,15 @@ fn run() -> Result<bool> {
|
||||
if app.matches.get_flag("list-languages") {
|
||||
let languages: String = get_languages(&config, cache_dir)?;
|
||||
let inputs: Vec<Input> = vec![Input::from_reader(Box::new(languages.as_bytes()))];
|
||||
let plain_config = Config {
|
||||
style_components: StyleComponents::new(StyleComponent::Plain.components(false)),
|
||||
paging_mode: PagingMode::QuitIfOneScreen,
|
||||
..Default::default()
|
||||
};
|
||||
let mut plain_config = config.clone();
|
||||
plain_config.style_components =
|
||||
StyleComponents::new(StyleComponent::Plain.components(false));
|
||||
run_controller(inputs, &plain_config, cache_dir)
|
||||
} else if app.matches.get_flag("list-themes") {
|
||||
list_themes(&config, config_dir, cache_dir, DetectColorScheme::default())?;
|
||||
list_themes(&config, config_dir, cache_dir, app.theme_options())?;
|
||||
Ok(true)
|
||||
} else if app.matches.get_flag("config-file") {
|
||||
println!("{}", config_file().to_string_lossy());
|
||||
writeln!(io::stdout(), "{}", config_file().to_string_lossy())?;
|
||||
Ok(true)
|
||||
} else if app.matches.get_flag("generate-config-file") {
|
||||
generate_config_file()?;
|
||||
|
||||
+19
-3
@@ -38,6 +38,9 @@ pub struct Config<'a> {
|
||||
/// The explicitly configured language, if any
|
||||
pub language: Option<&'a str>,
|
||||
|
||||
/// The fallback syntax used when auto-detection fails
|
||||
pub fallback_syntax: Option<&'a str>,
|
||||
|
||||
/// Whether or not to show/replace non-printable characters like space, tab and newline.
|
||||
pub show_nonprintable: bool,
|
||||
|
||||
@@ -95,18 +98,31 @@ pub struct Config<'a> {
|
||||
/// cached assets) are not available, assets from the binary will be used instead.
|
||||
pub use_custom_assets: bool,
|
||||
|
||||
// Whether or not to use $LESSOPEN if set
|
||||
/// Whether or not to use $LESSOPEN if set
|
||||
#[cfg(feature = "lessopen")]
|
||||
pub use_lessopen: bool,
|
||||
|
||||
// Weather or not to set terminal title when using a pager
|
||||
/// Whether 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>,
|
||||
|
||||
// Weather or not to set terminal title when using a pager
|
||||
/// Whether or not to strip ANSI escape codes from the input
|
||||
pub strip_ansi: StripAnsiMode,
|
||||
|
||||
/// Substitute terminal-active and spoofing-relevant bytes; implies strip_ansi.
|
||||
pub sanitize: StripAnsiMode,
|
||||
|
||||
/// Whether or not to produce no output when input is empty
|
||||
pub quiet_empty: bool,
|
||||
|
||||
/// Whether or not to use unbuffered input reading for streaming use cases
|
||||
pub unbuffered: bool,
|
||||
|
||||
/// Only number non-blank lines (like `cat -b`). Has no effect if `style_components` doesn't
|
||||
/// include `LineNumbers`.
|
||||
pub number_nonblank: bool,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
||||
|
||||
+19
-4
@@ -158,6 +158,7 @@ impl Controller<'_> {
|
||||
#[cfg(not(feature = "lessopen"))]
|
||||
input.open(stdin, stdout_identifier)?
|
||||
};
|
||||
opened_input.reader.unbuffered = self.config.unbuffered;
|
||||
#[cfg(feature = "git")]
|
||||
let line_changes = if self.config.visible_lines.diff_mode()
|
||||
|| (!self.config.loop_through && self.config.style_components.changes())
|
||||
@@ -275,10 +276,21 @@ impl Controller<'_> {
|
||||
}
|
||||
if !reached_eof {
|
||||
if reader.read_line(&mut current_line_buffer)? {
|
||||
// Fill the buffer
|
||||
buffered_lines
|
||||
.push_back((mem::take(&mut current_line_buffer), current_line_number));
|
||||
current_line_number += 1;
|
||||
// Fill the buffer. In number-nonblank mode, don't advance the
|
||||
// line counter for empty lines (content-free lines that contain
|
||||
// only the line terminator).
|
||||
if self.config.number_nonblank
|
||||
&& current_line_buffer
|
||||
.iter()
|
||||
.all(|&b| b == b'\r' || b == b'\n')
|
||||
{
|
||||
buffered_lines
|
||||
.push_back((mem::take(&mut current_line_buffer), current_line_number));
|
||||
} else {
|
||||
buffered_lines
|
||||
.push_back((mem::take(&mut current_line_buffer), current_line_number));
|
||||
current_line_number += 1;
|
||||
}
|
||||
} else {
|
||||
// No more data to read
|
||||
reached_eof = true;
|
||||
@@ -327,6 +339,9 @@ impl Controller<'_> {
|
||||
}
|
||||
|
||||
printer.print_line(false, writer, line_nr, &line, max_buffered_line_number)?;
|
||||
if self.config.unbuffered {
|
||||
writer.flush()?;
|
||||
}
|
||||
}
|
||||
RangeCheckResult::AfterLastRange => {
|
||||
break;
|
||||
|
||||
@@ -45,6 +45,14 @@ impl Decoration for LineNumberDecoration {
|
||||
continuation: bool,
|
||||
_printer: &InteractivePrinter,
|
||||
) -> DecorationText {
|
||||
if line_number == 0 {
|
||||
// Blank line in number-nonblank mode: show empty space instead of a number.
|
||||
return DecorationText {
|
||||
text: self.color.paint(" ".repeat(self.width())).to_string(),
|
||||
width: self.width(),
|
||||
};
|
||||
}
|
||||
|
||||
if continuation {
|
||||
if line_number >= self.cached_wrap_invalid_at {
|
||||
let new_width = self.cached_wrap.width + 1;
|
||||
|
||||
+176
-62
@@ -1,12 +1,13 @@
|
||||
#![cfg(feature = "git")]
|
||||
|
||||
use gix::diff::blob::pipeline::{Mode, WorktreeRoots};
|
||||
use gix::diff::blob::{Algorithm, HunkIter, ResourceKind};
|
||||
use gix::object::tree::EntryKind;
|
||||
use path_abs::PathInfo;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use git2::{DiffOptions, IntoCString, Repository};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum LineChange {
|
||||
Added,
|
||||
RemovedAbove,
|
||||
@@ -16,68 +17,181 @@ pub enum LineChange {
|
||||
|
||||
pub type LineChanges = HashMap<u32, LineChange>;
|
||||
|
||||
pub fn get_git_diff(filename: &Path) -> Option<LineChanges> {
|
||||
let repo = Repository::discover(filename).ok()?;
|
||||
|
||||
let repo_path_absolute = fs::canonicalize(repo.workdir()?).ok()?;
|
||||
|
||||
let filepath_absolute = fs::canonicalize(filename).ok()?;
|
||||
let filepath_relative_to_repo = filepath_absolute.strip_prefix(&repo_path_absolute).ok()?;
|
||||
|
||||
let mut diff_options = DiffOptions::new();
|
||||
let pathspec = filepath_relative_to_repo.into_c_string().ok()?;
|
||||
diff_options.pathspec(pathspec);
|
||||
diff_options.context_lines(0);
|
||||
|
||||
let diff = repo
|
||||
.diff_index_to_workdir(None, Some(&mut diff_options))
|
||||
.ok()?;
|
||||
|
||||
let mut line_changes: LineChanges = HashMap::new();
|
||||
|
||||
let mark_section =
|
||||
|line_changes: &mut LineChanges, start: u32, end: i32, change: LineChange| {
|
||||
for line in start..=end as u32 {
|
||||
line_changes.insert(line, change);
|
||||
fn collect_changes_from_hunks(hunks: HunkIter) -> Option<LineChanges> {
|
||||
let mut changes: LineChanges = HashMap::new();
|
||||
for hunk in hunks {
|
||||
if hunk.before.is_empty() && !hunk.after.is_empty() {
|
||||
for line in hunk.after {
|
||||
changes.insert(line + 1, LineChange::Added);
|
||||
}
|
||||
};
|
||||
|
||||
let _ = diff.foreach(
|
||||
&mut |_, _| true,
|
||||
None,
|
||||
Some(&mut |delta, hunk| {
|
||||
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
|
||||
|
||||
if filepath_relative_to_repo != path {
|
||||
return false;
|
||||
}
|
||||
|
||||
let old_lines = hunk.old_lines();
|
||||
let new_start = hunk.new_start();
|
||||
let new_lines = hunk.new_lines();
|
||||
let new_end = (new_start + new_lines) as i32 - 1;
|
||||
|
||||
if old_lines == 0 && new_lines > 0 {
|
||||
mark_section(&mut line_changes, new_start, new_end, LineChange::Added);
|
||||
} else if new_lines == 0 && old_lines > 0 {
|
||||
if new_start == 0 {
|
||||
mark_section(&mut line_changes, 1, 1, LineChange::RemovedAbove);
|
||||
} else {
|
||||
mark_section(
|
||||
&mut line_changes,
|
||||
new_start,
|
||||
new_start as i32,
|
||||
LineChange::RemovedBelow,
|
||||
);
|
||||
}
|
||||
} else if hunk.after.is_empty() && !hunk.before.is_empty() {
|
||||
if hunk.after.start == 0 {
|
||||
changes.insert(1, LineChange::RemovedAbove);
|
||||
} else {
|
||||
mark_section(&mut line_changes, new_start, new_end, LineChange::Modified);
|
||||
changes.insert(hunk.after.start, LineChange::RemovedBelow);
|
||||
}
|
||||
} else {
|
||||
for line in hunk.after {
|
||||
changes.insert(line + 1, LineChange::Modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}),
|
||||
None,
|
||||
Some(changes)
|
||||
}
|
||||
|
||||
pub fn get_git_diff(filename: &Path) -> Option<LineChanges> {
|
||||
let filepath_absolute = filename.canonicalize().ok()?;
|
||||
let repository = gix::discover(filepath_absolute.parent().ok()?).ok()?;
|
||||
let repo_path_absolute = repository.workdir()?.canonicalize().ok()?;
|
||||
let filepath_relative_to_repo = gix::path::to_unix_separators_on_windows(gix::path::into_bstr(
|
||||
filepath_absolute.strip_prefix(&repo_path_absolute).ok()?,
|
||||
));
|
||||
let index = repository.index_or_load_from_head_or_empty().ok()?;
|
||||
let index_entry = index.entry_by_path(filepath_relative_to_repo.as_ref())?;
|
||||
let mut cache = repository
|
||||
.diff_resource_cache(
|
||||
Mode::ToGit,
|
||||
WorktreeRoots {
|
||||
old_root: None,
|
||||
new_root: repository.workdir().map(Path::to_path_buf),
|
||||
},
|
||||
)
|
||||
.ok()?;
|
||||
cache
|
||||
.set_resource(
|
||||
index_entry.id,
|
||||
index_entry.mode.to_tree_entry_mode()?.kind(),
|
||||
filepath_relative_to_repo.as_ref(),
|
||||
ResourceKind::OldOrSource,
|
||||
&repository,
|
||||
)
|
||||
.ok()?;
|
||||
cache
|
||||
.set_resource(
|
||||
repository.object_hash().null(),
|
||||
EntryKind::Blob,
|
||||
filepath_relative_to_repo.as_ref(),
|
||||
ResourceKind::NewOrDestination,
|
||||
&repository,
|
||||
)
|
||||
.ok()?;
|
||||
let diff = gix::diff::blob::diff_with_slider_heuristics(
|
||||
Algorithm::Histogram,
|
||||
&cache.prepare_diff().ok()?.interned_input(),
|
||||
);
|
||||
|
||||
Some(line_changes)
|
||||
collect_changes_from_hunks(diff.hunks())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{get_git_diff, LineChange};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn git(repo: &Path, args: &[&str]) {
|
||||
let output = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(repo)
|
||||
.output()
|
||||
.expect("git command can run");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"git {args:?} failed\nstdout: {}\nstderr: {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_repo() -> tempfile::TempDir {
|
||||
let dir = tempfile::tempdir().expect("can create temporary directory");
|
||||
let repo = dir.path();
|
||||
git(repo, &["init"]);
|
||||
git(repo, &["config", "user.email", "test@test.test"]);
|
||||
git(repo, &["config", "user.name", "Test"]);
|
||||
dir
|
||||
}
|
||||
|
||||
fn create_and_track_file(repo: &tempfile::TempDir, filename: &str) -> PathBuf {
|
||||
let filepath = repo.path().join(filename);
|
||||
std::fs::write(&filepath, "file\n").expect("can write file");
|
||||
git(repo.path(), &["add", filename]);
|
||||
git(repo.path(), &["commit", "-m", "initial"]);
|
||||
|
||||
filepath.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_a_git_repository() {
|
||||
let dir = tempfile::tempdir().expect("can create temporary directory");
|
||||
let file = dir.path().join("file.txt");
|
||||
std::fs::write(&file, "line 1\n").expect("can write file");
|
||||
|
||||
assert_eq!(get_git_diff(&file), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_against_index() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
// Add modified file to index -> should find 0 changes but not none
|
||||
git(repo.path(), &["add", "file.txt"]);
|
||||
|
||||
assert_eq!(get_git_diff(&file).expect("empty map").len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_against_index_2() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
git(repo.path(), &["add", "file.txt"]);
|
||||
// modify the second line again which should show a single change
|
||||
std::fs::write(&file, "line 1\nline 2 modified again\n").expect("can write file");
|
||||
|
||||
assert_eq!(get_git_diff(&file).expect("one change").len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_is_calculated_correctly() {
|
||||
let repo = setup_repo();
|
||||
create_and_track_file(&repo, "committed.txt");
|
||||
let filename = "committed2.txt";
|
||||
let file = create_and_track_file(&repo, filename);
|
||||
|
||||
std::fs::write(&file, "file\nline 2 added\n").expect("can write file");
|
||||
let mut changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Added));
|
||||
git(repo.path(), &["add", filename]);
|
||||
|
||||
std::fs::write(&file, "file\nline 2 modified\n").expect("can write file");
|
||||
changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Modified));
|
||||
git(repo.path(), &["add", filename]);
|
||||
|
||||
std::fs::write(&file, "line 2 modified\nline 3 added").expect("can write file");
|
||||
changes = get_git_diff(&file).expect("multiple changes");
|
||||
assert_eq!(changes.get(&1), Some(&LineChange::RemovedAbove));
|
||||
assert_eq!(changes.get(&2), Some(&LineChange::Added));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn faulty_git_version_does_not_panic() {
|
||||
let repo = setup_repo();
|
||||
let file = create_and_track_file(&repo, "file.txt");
|
||||
std::fs::write(&file, "line 1\nline 2 modified\n").expect("can write file");
|
||||
// changes are detected
|
||||
assert_eq!(get_git_diff(&file).expect("one change").len(), 2);
|
||||
// write invalid repositoryformatversion
|
||||
git(
|
||||
repo.path(),
|
||||
&["config", "core.repositoryformatversion", "one"],
|
||||
);
|
||||
// changes are no longer detected
|
||||
assert_eq!(get_git_diff(&file), None);
|
||||
}
|
||||
}
|
||||
+259
-24
@@ -9,6 +9,8 @@ use content_inspector::{self, ContentType};
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
const CONTENT_INSPECTION_LIMIT: usize = 1024;
|
||||
|
||||
/// A description of an Input source.
|
||||
/// This tells bat how to refer to the input.
|
||||
#[derive(Clone)]
|
||||
@@ -207,7 +209,7 @@ impl<'a> Input<'a> {
|
||||
kind: OpenedInputKind::StdIn,
|
||||
description,
|
||||
metadata: self.metadata,
|
||||
reader: InputReader::new(stdin),
|
||||
reader: InputReader::try_new(stdin)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -216,34 +218,34 @@ impl<'a> Input<'a> {
|
||||
description,
|
||||
metadata: self.metadata,
|
||||
reader: {
|
||||
let mut file = File::open(&path)
|
||||
.map_err(|e| format!("'{}': {e}", path.to_string_lossy()))?;
|
||||
let path_display =
|
||||
crate::preprocessor::sanitize_for_terminal(&path.to_string_lossy());
|
||||
let mut file =
|
||||
File::open(&path).map_err(|e| format!("'{path_display}': {e}"))?;
|
||||
if file.metadata()?.is_dir() {
|
||||
return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
|
||||
return Err(format!("'{path_display}' is a directory.").into());
|
||||
}
|
||||
|
||||
if let Some(stdout) = stdout_identifier {
|
||||
let input_identifier = Identifier::try_from(file).map_err(|e| {
|
||||
format!("{}: Error identifying file: {e}", path.to_string_lossy())
|
||||
})?;
|
||||
let input_identifier = Identifier::try_from(file)
|
||||
.map_err(|e| format!("{path_display}: Error identifying file: {e}"))?;
|
||||
if stdout.surely_conflicts_with(&input_identifier) {
|
||||
return Err(format!(
|
||||
"IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
|
||||
path.to_string_lossy()
|
||||
"IO circle detected. The input from '{path_display}' is also an output. Aborting to avoid infinite loop.",
|
||||
)
|
||||
.into());
|
||||
}
|
||||
file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened...");
|
||||
}
|
||||
|
||||
InputReader::new(BufReader::new(file))
|
||||
InputReader::try_new(BufReader::new(file))?
|
||||
},
|
||||
}),
|
||||
InputKind::CustomReader(reader) => Ok(OpenedInput {
|
||||
description,
|
||||
kind: OpenedInputKind::CustomReader,
|
||||
metadata: self.metadata,
|
||||
reader: InputReader::new(BufReader::new(reader)),
|
||||
reader: InputReader::try_new(BufReader::new(reader))?,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -253,30 +255,57 @@ pub(crate) struct InputReader<'a> {
|
||||
inner: Box<dyn BufRead + 'a>,
|
||||
pub(crate) first_line: Vec<u8>,
|
||||
pub(crate) content_type: Option<ContentType>,
|
||||
pub(crate) unbuffered: bool,
|
||||
}
|
||||
|
||||
impl<'a> InputReader<'a> {
|
||||
pub(crate) fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
|
||||
let mut first_line = vec![];
|
||||
reader.read_until(b'\n', &mut first_line).ok();
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new<R: BufRead + 'a>(reader: R) -> InputReader<'a> {
|
||||
Self::try_new(reader).expect("reading the first line failed")
|
||||
}
|
||||
|
||||
let content_type = if first_line.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(content_inspector::inspect(&first_line[..]))
|
||||
pub(crate) fn try_new<R: BufRead + 'a>(mut reader: R) -> io::Result<InputReader<'a>> {
|
||||
// content_inspector scans at most 1024 bytes. Capture the already-buffered
|
||||
// prefix before splitting out the first line so an early newline in binary
|
||||
// data does not shorten the inspected content. This does not consume input
|
||||
// or perform an additional read beyond the one read_until needs anyway.
|
||||
let mut inspection_prefix = {
|
||||
let buffered = reader.fill_buf()?;
|
||||
buffered[..buffered.len().min(CONTENT_INSPECTION_LIMIT)].to_vec()
|
||||
};
|
||||
|
||||
if content_type == Some(ContentType::UTF_16LE) {
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A).ok();
|
||||
} else if content_type == Some(ContentType::UTF_16BE) {
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00).ok();
|
||||
let mut first_line = vec![];
|
||||
if !inspection_prefix.is_empty() {
|
||||
reader.read_until(b'\n', &mut first_line)?;
|
||||
}
|
||||
|
||||
InputReader {
|
||||
// A custom BufRead implementation may expose less than 1024 bytes at a
|
||||
// time. Keep the old behavior for long first lines in that case.
|
||||
let first_line_prefix_len = first_line.len().min(CONTENT_INSPECTION_LIMIT);
|
||||
if first_line_prefix_len > inspection_prefix.len() {
|
||||
inspection_prefix.clear();
|
||||
inspection_prefix.extend_from_slice(&first_line[..first_line_prefix_len]);
|
||||
}
|
||||
|
||||
let content_type = inspect_content_type(&inspection_prefix);
|
||||
|
||||
if content_type == Some(ContentType::UTF_16LE) {
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A)?;
|
||||
} else if content_type == Some(ContentType::UTF_16BE)
|
||||
// In UTF-16BE a newline is encoded as 0x00 0x0A, so `read_until(b'\n')` above
|
||||
// may already have read a complete first line. Only keep reading if it did not
|
||||
// (e.g. the 0x0A byte was part of another character such as U+4E0A).
|
||||
&& !(first_line.len() % 2 == 0 && first_line.ends_with(&[0x00, 0x0A]))
|
||||
{
|
||||
read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00)?;
|
||||
}
|
||||
|
||||
Ok(InputReader {
|
||||
inner: Box::new(reader),
|
||||
first_line,
|
||||
content_type,
|
||||
}
|
||||
unbuffered: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
|
||||
@@ -292,9 +321,48 @@ impl<'a> InputReader<'a> {
|
||||
return read_utf16_line(&mut self.inner, buf, 0x0A, 0x00);
|
||||
}
|
||||
|
||||
if self.unbuffered {
|
||||
return self.read_line_unbuffered(buf);
|
||||
}
|
||||
|
||||
let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn read_line_unbuffered(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
|
||||
let available = self.inner.fill_buf()?;
|
||||
if available.is_empty() {
|
||||
return Ok(!buf.is_empty());
|
||||
}
|
||||
if let Some(pos) = available.iter().position(|&b| b == b'\n') {
|
||||
buf.extend_from_slice(&available[..=pos]);
|
||||
self.inner.consume(pos + 1);
|
||||
} else {
|
||||
let len = available.len();
|
||||
buf.extend_from_slice(available);
|
||||
self.inner.consume(len);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn inspect_content_type(first_line: &[u8]) -> Option<ContentType> {
|
||||
if first_line.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let content_type = content_inspector::inspect(first_line);
|
||||
if content_type == ContentType::UTF_8 && has_zip_signature(first_line) {
|
||||
Some(ContentType::BINARY)
|
||||
} else {
|
||||
Some(content_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_zip_signature(bytes: &[u8]) -> bool {
|
||||
[b"PK\x03\x04", b"PK\x05\x06", b"PK\x07\x08"]
|
||||
.into_iter()
|
||||
.any(|signature| bytes.starts_with(signature))
|
||||
}
|
||||
|
||||
fn read_utf16_line<R: BufRead>(
|
||||
@@ -352,6 +420,90 @@ fn basic() {
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_magic_headers_are_treated_as_binary() {
|
||||
for content in [b"PK\x03\x04hello", b"PK\x05\x06hello", b"PK\x07\x08hello"] {
|
||||
let reader = InputReader::new(&content[..]);
|
||||
assert_eq!(Some(ContentType::BINARY), reader.content_type);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_zip_pk_prefix_is_not_treated_as_binary() {
|
||||
assert_eq!(
|
||||
Some(ContentType::UTF_8),
|
||||
inspect_content_type(b"PK\x03\x03hello")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_detection_scans_beyond_first_line_and_preserves_input() {
|
||||
let mut content = vec![b'a'; CONTENT_INSPECTION_LIMIT + 1];
|
||||
content[1] = b'\n';
|
||||
content[CONTENT_INSPECTION_LIMIT - 1] = 0;
|
||||
|
||||
let mut reader = InputReader::new(&content[..]);
|
||||
assert_eq!(Some(ContentType::BINARY), reader.content_type);
|
||||
|
||||
let mut replayed = Vec::new();
|
||||
while reader.read_line(&mut replayed).unwrap() {}
|
||||
assert_eq!(content, replayed);
|
||||
|
||||
drop(reader);
|
||||
|
||||
content[CONTENT_INSPECTION_LIMIT - 1] = b'a';
|
||||
content[CONTENT_INSPECTION_LIMIT] = 0;
|
||||
|
||||
let reader = InputReader::new(&content[..]);
|
||||
assert_eq!(Some(ContentType::UTF_8), reader.content_type);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_detection_does_not_read_twice() {
|
||||
struct OneRead(Option<&'static [u8]>);
|
||||
|
||||
impl Read for OneRead {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self.0.take() {
|
||||
None => Err(io::Error::new(
|
||||
io::ErrorKind::WouldBlock,
|
||||
"no more data is available yet",
|
||||
)),
|
||||
Some(content) => {
|
||||
buf[..content.len()].copy_from_slice(content);
|
||||
Ok(content.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (content, expected) in [(&b"text\n"[..], Some(ContentType::UTF_8)), (&b""[..], None)] {
|
||||
let input = InputReader::try_new(BufReader::new(OneRead(Some(content)))).unwrap();
|
||||
assert_eq!(expected, input.content_type);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_open_returns_initial_read_errors() {
|
||||
struct FailingRead;
|
||||
|
||||
impl Read for FailingRead {
|
||||
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
|
||||
Err(io::Error::other("initial read failed"))
|
||||
}
|
||||
}
|
||||
|
||||
let input = Input::from_reader(Box::new(FailingRead));
|
||||
let result = input.open(io::empty(), None);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.err()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.contains("initial read failed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16le() {
|
||||
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
|
||||
@@ -381,6 +533,89 @@ fn utf16le() {
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbuffered_returns_partial_data() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let content = b"first line\npartial";
|
||||
let mut reader = InputReader::new(Cursor::new(&content[..]));
|
||||
reader.unbuffered = true;
|
||||
|
||||
// First call returns first_line (buffered during new())
|
||||
let mut buffer = vec![];
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(b"first line\n", &buffer[..]);
|
||||
|
||||
// Subsequent calls use unbuffered reading
|
||||
buffer.clear();
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(b"partial", &buffer[..]);
|
||||
|
||||
// EOF
|
||||
buffer.clear();
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(!res.unwrap());
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbuffered_returns_complete_lines() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let content = b"line1\nline2\n";
|
||||
let mut reader = InputReader::new(Cursor::new(&content[..]));
|
||||
reader.unbuffered = true;
|
||||
|
||||
// First call returns first_line
|
||||
let mut buffer = vec![];
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(b"line1\n", &buffer[..]);
|
||||
|
||||
// Second call returns line2 (complete line with newline)
|
||||
buffer.clear();
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(b"line2\n", &buffer[..]);
|
||||
|
||||
// EOF
|
||||
buffer.clear();
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(!res.unwrap());
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbuffered_eof_handling() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let content = b"only line\n";
|
||||
let mut reader = InputReader::new(Cursor::new(&content[..]));
|
||||
reader.unbuffered = true;
|
||||
|
||||
// First call returns first_line
|
||||
let mut buffer = vec![];
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(res.unwrap());
|
||||
assert_eq!(b"only line\n", &buffer[..]);
|
||||
|
||||
// EOF - empty buffer returns false
|
||||
buffer.clear();
|
||||
let res = reader.read_line(&mut buffer);
|
||||
assert!(res.is_ok());
|
||||
assert!(!res.unwrap());
|
||||
assert!(buffer.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf16le_issue3367() {
|
||||
let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00\
|
||||
|
||||
+115
-7
@@ -14,6 +14,53 @@ use crate::{
|
||||
input::{Input, InputKind, InputReader, OpenedInput, OpenedInputKind},
|
||||
};
|
||||
|
||||
/// Wrap `s` in POSIX single quotes. An embedded `'` is encoded as `'\''`
|
||||
/// (close, escaped quote, reopen) since `'...'` has no escape character.
|
||||
fn shell_quote(s: &str) -> String {
|
||||
if !s.contains('\'') {
|
||||
let mut quoted = String::with_capacity(s.len() + 2);
|
||||
quoted.push('\'');
|
||||
quoted.push_str(s);
|
||||
quoted.push('\'');
|
||||
return quoted;
|
||||
}
|
||||
let mut quoted = String::with_capacity(s.len() + 2);
|
||||
quoted.push('\'');
|
||||
for c in s.chars() {
|
||||
if c == '\'' {
|
||||
quoted.push_str("'\\''");
|
||||
} else {
|
||||
quoted.push(c);
|
||||
}
|
||||
}
|
||||
quoted.push('\'');
|
||||
quoted
|
||||
}
|
||||
|
||||
/// Substitute the first `%s` in a $LESSOPEN/$LESSCLOSE template with a quoted value.
|
||||
fn shell_substitute(template: &str, replacement: &str) -> String {
|
||||
template.replacen("%s", &shell_quote(replacement), 1)
|
||||
}
|
||||
|
||||
/// Substitute the first two `%s` occurrences positionally in one pass; chaining
|
||||
/// `shell_substitute` twice would mis-target if `first` itself contains `%s`.
|
||||
fn shell_substitute_two(template: &str, first: &str, second: &str) -> String {
|
||||
let mut out = String::with_capacity(template.len() + first.len() + second.len() + 4);
|
||||
let mut remaining = template;
|
||||
let mut to_substitute = [Some(first), Some(second)];
|
||||
for slot in &mut to_substitute {
|
||||
if let Some(idx) = remaining.find("%s") {
|
||||
out.push_str(&remaining[..idx]);
|
||||
out.push_str(&shell_quote(slot.take().unwrap()));
|
||||
remaining = &remaining[idx + 2..];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.push_str(remaining);
|
||||
out
|
||||
}
|
||||
|
||||
/// Preprocess files and/or stdin using $LESSOPEN and $LESSCLOSE
|
||||
pub(crate) struct LessOpenPreprocessor {
|
||||
lessopen: String,
|
||||
@@ -87,7 +134,7 @@ impl LessOpenPreprocessor {
|
||||
None => return input.open(stdin, stdout_identifier),
|
||||
};
|
||||
|
||||
let mut lessopen_command = shell(self.lessopen.replacen("%s", path_str, 1));
|
||||
let mut lessopen_command = shell(shell_substitute(&self.lessopen, path_str));
|
||||
lessopen_command.stdout(Stdio::piped());
|
||||
|
||||
let lessopen_output = match lessopen_command.execute_output() {
|
||||
@@ -122,7 +169,7 @@ impl LessOpenPreprocessor {
|
||||
let mut stdin_buffer = Vec::new();
|
||||
stdin.read_to_end(&mut stdin_buffer)?;
|
||||
|
||||
let mut lessopen_command = shell(self.lessopen.replacen("%s", "-", 1));
|
||||
let mut lessopen_command = shell(shell_substitute(&self.lessopen, "-"));
|
||||
lessopen_command.stdout(Stdio::piped());
|
||||
|
||||
let lessopen_output = match lessopen_command.execute_input_output(&stdin_buffer)
|
||||
@@ -155,7 +202,7 @@ impl LessOpenPreprocessor {
|
||||
|
||||
Ok(OpenedInput {
|
||||
kind,
|
||||
reader: InputReader::new(BufReader::new(
|
||||
reader: InputReader::try_new(BufReader::new(
|
||||
if matches!(self.kind, LessOpenKind::TempFile) {
|
||||
let lessopen_string = match String::from_utf8(lessopen_stdout) {
|
||||
Ok(string) => string,
|
||||
@@ -181,7 +228,7 @@ impl LessOpenPreprocessor {
|
||||
lessclose: self
|
||||
.lessclose
|
||||
.as_ref()
|
||||
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", &stdout, 1)),
|
||||
.map(|s| shell_substitute_two(s, &path_str, &stdout)),
|
||||
}
|
||||
} else {
|
||||
Preprocessed {
|
||||
@@ -189,10 +236,10 @@ impl LessOpenPreprocessor {
|
||||
lessclose: self
|
||||
.lessclose
|
||||
.as_ref()
|
||||
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)),
|
||||
.map(|s| shell_substitute_two(s, &path_str, "-")),
|
||||
}
|
||||
},
|
||||
)),
|
||||
))?,
|
||||
metadata: input.metadata,
|
||||
description: input.description,
|
||||
})
|
||||
@@ -261,7 +308,7 @@ impl Drop for Preprocessed {
|
||||
}
|
||||
};
|
||||
|
||||
if lessclose_output.status.success() {
|
||||
if !lessclose_output.status.success() {
|
||||
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
|
||||
};
|
||||
}
|
||||
@@ -384,4 +431,65 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_plain_filename() {
|
||||
assert_eq!(super::shell_quote("file.txt"), "'file.txt'");
|
||||
assert_eq!(super::shell_quote("with space.txt"), "'with space.txt'");
|
||||
assert_eq!(super::shell_quote(""), "''");
|
||||
assert_eq!(super::shell_quote("-"), "'-'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_metacharacters_neutralised() {
|
||||
assert_eq!(super::shell_quote("; rm -rf ~ ;"), "'; rm -rf ~ ;'");
|
||||
assert_eq!(super::shell_quote("$(payload)"), "'$(payload)'");
|
||||
assert_eq!(super::shell_quote("`payload`"), "'`payload`'");
|
||||
assert_eq!(super::shell_quote("a|b&c;d"), "'a|b&c;d'");
|
||||
assert_eq!(super::shell_quote("..>/dev/null"), "'..>/dev/null'");
|
||||
assert_eq!(super::shell_quote("\n\t"), "'\n\t'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_quote_embedded_single_quote() {
|
||||
assert_eq!(super::shell_quote("it's"), "'it'\\''s'");
|
||||
assert_eq!(super::shell_quote("'"), "''\\'''");
|
||||
assert_eq!(super::shell_quote("a'b'c"), "'a'\\''b'\\''c'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_only_replaces_first_percent_s() {
|
||||
assert_eq!(super::shell_substitute("echo %s", "x"), "echo 'x'");
|
||||
assert_eq!(super::shell_substitute("echo %s %s", "x"), "echo 'x' %s");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_protects_against_filename_injection() {
|
||||
let template = "|preproc %s";
|
||||
let attacker_filename = "; rm -rf ~ ;";
|
||||
let result = super::shell_substitute(template, attacker_filename);
|
||||
assert_eq!(result, "|preproc '; rm -rf ~ ;'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_two_replaces_first_two_placeholders() {
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("echo %s and %s done", "a", "b"),
|
||||
"echo 'a' and 'b' done"
|
||||
);
|
||||
// A third %s is left alone.
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("echo %s %s %s", "a", "b"),
|
||||
"echo 'a' 'b' %s"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_substitute_two_handles_percent_s_in_first_argument() {
|
||||
// The chained-substitute approach would replace the injected `%s` here.
|
||||
assert_eq!(
|
||||
super::shell_substitute_two("a %s b %s c", "%s", "second"),
|
||||
"a '%s' b 'second' c"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ mod vscreen;
|
||||
pub(crate) mod wrapping;
|
||||
|
||||
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
|
||||
pub use preprocessor::sanitize_for_terminal;
|
||||
pub use preprocessor::StripAnsiMode;
|
||||
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
||||
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
||||
|
||||
+3
-3
@@ -355,13 +355,13 @@ fn test_parse_context_fail() {
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum RangeCheckResult {
|
||||
// Within one of the given ranges
|
||||
/// Within one of the given ranges
|
||||
InRange,
|
||||
|
||||
// Before the first range or within two ranges
|
||||
/// Before the first range or within two ranges
|
||||
BeforeOrBetweenRanges,
|
||||
|
||||
// Line number is outside of all ranges and larger than the last range.
|
||||
/// Line number is outside of all ranges and larger than the last range.
|
||||
AfterLastRange,
|
||||
}
|
||||
|
||||
|
||||
+39
-5
@@ -23,6 +23,18 @@ pub struct BuiltinPager {
|
||||
impl BuiltinPager {
|
||||
fn new() -> Self {
|
||||
let pager = minus::Pager::new();
|
||||
|
||||
let mut input_register = minus::input::HashedEventRegister::default();
|
||||
input_register.add_key_events(&["home"], |_, _| {
|
||||
minus::input::InputEvent::UpdateUpperMark(0)
|
||||
});
|
||||
input_register.add_key_events(&["end"], |_, _| {
|
||||
minus::input::InputEvent::UpdateUpperMark(usize::MAX)
|
||||
});
|
||||
pager
|
||||
.set_input_classifier(Box::new(input_register))
|
||||
.expect("failed to set input classifier on newly created pager");
|
||||
|
||||
let handle = {
|
||||
let pager = pager.clone();
|
||||
Some(spawn(move || {
|
||||
@@ -105,6 +117,10 @@ impl OutputType {
|
||||
let resolved_path = match grep_cli::resolve_binary(&pager.bin) {
|
||||
Ok(path) => path,
|
||||
Err(_) => {
|
||||
crate::bat_warning!(
|
||||
"Pager '{}' not found, outputting to stdout instead",
|
||||
pager.bin
|
||||
);
|
||||
return Ok(OutputType::stdout());
|
||||
}
|
||||
};
|
||||
@@ -131,8 +147,13 @@ impl OutputType {
|
||||
p.arg("-S"); // Short version of --chop-long-lines for compatibility
|
||||
}
|
||||
|
||||
let less_version = retrieve_less_version(&pager.bin);
|
||||
|
||||
// Ensures that 'less' quits together with 'bat'
|
||||
p.arg("-K"); // Short version of '--quit-on-intr'
|
||||
// The BusyBox version of less does not support -K
|
||||
if less_version != Some(LessVersion::BusyBox) {
|
||||
p.arg("-K"); // Short version of '--quit-on-intr'
|
||||
}
|
||||
|
||||
// Passing '--no-init' fixes a bug with '--quit-if-one-screen' in older
|
||||
// versions of 'less'. Unfortunately, it also breaks mouse-wheel support.
|
||||
@@ -142,7 +163,7 @@ impl OutputType {
|
||||
// For newer versions (530 or 558 on Windows), we omit '--no-init' as it
|
||||
// is not needed anymore.
|
||||
if single_screen_action == SingleScreenAction::Quit {
|
||||
match retrieve_less_version(&pager.bin) {
|
||||
match less_version {
|
||||
None => {
|
||||
p.arg("--no-init");
|
||||
}
|
||||
@@ -169,7 +190,13 @@ impl OutputType {
|
||||
Ok(p.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.map(OutputType::Pager)
|
||||
.unwrap_or_else(|_| OutputType::stdout()))
|
||||
.unwrap_or_else(|_| {
|
||||
crate::bat_warning!(
|
||||
"Pager '{}' not found, outputting to stdout instead",
|
||||
&pager.bin
|
||||
);
|
||||
OutputType::stdout()
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn stdout() -> Self {
|
||||
@@ -210,8 +237,8 @@ impl Drop for OutputType {
|
||||
let _ = command.wait();
|
||||
}
|
||||
OutputType::BuiltinPager(ref mut pager) => {
|
||||
if pager.handle.is_some() {
|
||||
let _ = pager.handle.take().unwrap().join().unwrap();
|
||||
if let Some(handle) = pager.handle.take() {
|
||||
let _ = handle.join();
|
||||
}
|
||||
}
|
||||
OutputType::Stdout(_) => (),
|
||||
@@ -231,4 +258,11 @@ impl OutputHandle<'_> {
|
||||
Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) -> Result<()> {
|
||||
match self {
|
||||
Self::IoWrite(handle) => handle.flush().map_err(Into::into),
|
||||
Self::FmtWrite(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+330
-2
@@ -139,21 +139,184 @@ pub fn replace_nonprintable(
|
||||
/// Strips ANSI escape sequences from the input.
|
||||
pub fn strip_ansi(line: &str) -> String {
|
||||
let mut buffer = String::with_capacity(line.len());
|
||||
|
||||
for seq in EscapeSequenceOffsetsIterator::new(line) {
|
||||
if let EscapeSequenceOffsets::Text { .. } = seq {
|
||||
buffer.push_str(&line[seq.index_of_start()..seq.index_past_end()]);
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
/// Strips ANSI escape sequences and substitutes terminal-active control bytes
|
||||
/// and visual-spoofing Unicode codepoints (bidi, zero-width) with U+FFFD.
|
||||
pub fn sanitize(line: &str) -> String {
|
||||
let stripped = strip_ansi(line);
|
||||
let mut buffer = String::with_capacity(stripped.len());
|
||||
let bytes = stripped.as_bytes();
|
||||
let mut start = 0;
|
||||
let mut i = 0;
|
||||
// Skip directly to the next trigger byte instead of testing each one.
|
||||
while let Some(off) = bytes[i..].iter().position(|&b| is_sanitize_trigger(b)) {
|
||||
i += off;
|
||||
let len = sanitize_at(bytes, i, &stripped, &mut buffer, &mut start);
|
||||
i += len;
|
||||
}
|
||||
buffer.push_str(&stripped[start..]);
|
||||
buffer
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_sanitize_trigger(b: u8) -> bool {
|
||||
// C0 controls minus \t \n \f; DEL; UTF-8 leads with dangerous codepoints.
|
||||
matches!(b, 0x00..=0x08 | 0x0B | 0x0D..=0x1F | 0x7F | 0xC2 | 0xD8 | 0xE2 | 0xEF)
|
||||
}
|
||||
|
||||
/// Substitutes the byte/sequence at `bytes[i]` (or passes it through on
|
||||
/// false-alarm trigger), flushing the prefix from `start`. Returns bytes consumed.
|
||||
fn sanitize_at(
|
||||
bytes: &[u8],
|
||||
i: usize,
|
||||
full: &str,
|
||||
buffer: &mut String,
|
||||
start: &mut usize,
|
||||
) -> usize {
|
||||
buffer.push_str(&full[*start..i]);
|
||||
let consumed = match bytes[i] {
|
||||
b'\r' if bytes.get(i + 1) == Some(&b'\n') => {
|
||||
buffer.push_str("\r\n");
|
||||
2
|
||||
}
|
||||
// 0xC2 leads U+0080..U+00FF; filter the C1 range.
|
||||
0xC2 if matches!(bytes.get(i + 1), Some(0x80..=0x9F)) => {
|
||||
buffer.push('\u{FFFD}');
|
||||
2
|
||||
}
|
||||
0xE2 if is_dangerous_e2(bytes, i) => {
|
||||
buffer.push('\u{FFFD}');
|
||||
3
|
||||
}
|
||||
// 0xEF 0xBB 0xBF = U+FEFF (BOM / zero-width no-break space).
|
||||
0xEF if bytes.get(i + 1) == Some(&0xBB) && bytes.get(i + 2) == Some(&0xBF) => {
|
||||
buffer.push('\u{FFFD}');
|
||||
3
|
||||
}
|
||||
// 0xD8 0x9C = U+061C (Arabic letter mark, a bidi control). The rest of
|
||||
// the 0xD8 block is ordinary Arabic text.
|
||||
0xD8 if bytes.get(i + 1) == Some(&0x9C) => {
|
||||
buffer.push('\u{FFFD}');
|
||||
2
|
||||
}
|
||||
// False-alarm trigger: pass the full UTF-8 sequence through.
|
||||
lead @ (0xC2 | 0xD8 | 0xE2 | 0xEF) => {
|
||||
let n = utf8_len_from_lead(lead);
|
||||
buffer.push_str(&full[i..i + n]);
|
||||
n
|
||||
}
|
||||
_ => {
|
||||
buffer.push('\u{FFFD}');
|
||||
1
|
||||
}
|
||||
};
|
||||
*start = i + consumed;
|
||||
consumed
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_dangerous_e2(bytes: &[u8], i: usize) -> bool {
|
||||
// U+200B..D (zero-width), U+200E..F (LRM/RLM), U+202A..E (bidi embedding
|
||||
// and override), U+2066..9 (bidi isolates).
|
||||
matches!(
|
||||
(bytes.get(i + 1), bytes.get(i + 2)),
|
||||
(Some(0x80), Some(0x8B..=0x8F | 0xAA..=0xAE)) | (Some(0x81), Some(0xA6..=0xA9))
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn utf8_len_from_lead(lead: u8) -> usize {
|
||||
if lead < 0x80 {
|
||||
1
|
||||
} else if lead < 0xE0 {
|
||||
2
|
||||
} else if lead < 0xF0 {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape C0, DEL, and C1 control characters so a string from an untrusted
|
||||
/// filename or path can be safely written to the terminal.
|
||||
pub fn sanitize_for_terminal(input: &str) -> String {
|
||||
if !input
|
||||
.chars()
|
||||
.any(|c| matches!(c, '\x00'..='\x08' | '\x0A'..='\x1F' | '\x7F'..='\u{9F}'))
|
||||
{
|
||||
return input.to_owned();
|
||||
}
|
||||
|
||||
let mut out = String::with_capacity(input.len() + 8);
|
||||
for c in input.chars() {
|
||||
match c {
|
||||
'\t' => out.push('\t'),
|
||||
'\x00'..='\x1F' => {
|
||||
out.push('^');
|
||||
out.push(char::from_u32(0x40 + c as u32).unwrap_or('?'));
|
||||
}
|
||||
'\x7F' => out.push_str("^?"),
|
||||
'\u{80}'..='\u{9F}' => {
|
||||
use std::fmt::Write as _;
|
||||
let _ = write!(out, "\\u{{{:x}}}", c as u32);
|
||||
}
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Strips overstrike sequences (backspace formatting) from input.
|
||||
///
|
||||
/// Overstrike formatting is used by man pages and some help output:
|
||||
/// - Bold: `X\x08X` (character, backspace, same character)
|
||||
/// - Underline: `_\x08X` (underscore, backspace, character)
|
||||
///
|
||||
/// This function removes these sequences, keeping only the visible character.
|
||||
/// `first_backspace` is the position of the first backspace in the line.
|
||||
pub fn strip_overstrike(line: &str, first_backspace: usize) -> String {
|
||||
let mut output = String::with_capacity(line.len());
|
||||
output.push_str(&line[..first_backspace]);
|
||||
output.pop();
|
||||
|
||||
let mut remaining = &line[first_backspace + 1..];
|
||||
|
||||
loop {
|
||||
if let Some(pos) = remaining.find('\x08') {
|
||||
output.push_str(&remaining[..pos]);
|
||||
output.pop();
|
||||
remaining = &remaining[pos + 1..];
|
||||
} else {
|
||||
output.push_str(remaining);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Specify when to strip ANSI escape sequences from the input.
|
||||
///
|
||||
/// Used for both the `--strip-ansi` and the `--sanitize` option, the latter
|
||||
/// implying the former with the same value.
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||
pub enum StripAnsiMode {
|
||||
/// Keep escape sequences in the output
|
||||
#[default]
|
||||
Never,
|
||||
|
||||
/// Strip escape sequences from the output
|
||||
Always,
|
||||
|
||||
/// Strip escape sequences unless the syntax highlighting language is plain
|
||||
/// text, in which case they may be part of what the input wants to display
|
||||
Auto,
|
||||
}
|
||||
|
||||
@@ -211,3 +374,168 @@ fn test_strip_ansi() {
|
||||
"multiple sequences"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_ansi_8bit_c1_introducers() {
|
||||
assert_eq!(strip_ansi("a\u{9B}31mRED\u{9B}0mb"), "aREDb");
|
||||
assert_eq!(strip_ansi("a\x1bP1;0|payload\x1b\\b"), "ab");
|
||||
assert_eq!(strip_ansi("a\u{90}body\u{9C}b"), "ab");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_ansi_single_char_esc() {
|
||||
// strip_ansi must consume both bytes of single-byte ESC sequences (RIS, DECSC, keypad, VT52).
|
||||
assert_eq!(strip_ansi("a\x1bcb"), "ab");
|
||||
assert_eq!(strip_ansi("a\x1b7b\x1b8c"), "abc");
|
||||
assert_eq!(strip_ansi("a\x1b=b\x1b>c"), "abc");
|
||||
assert_eq!(strip_ansi("a\x1bZb"), "ab");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_ansi_preserves_control_bytes() {
|
||||
// strip_ansi removes only ANSI escape sequences; control bytes pass through.
|
||||
assert_eq!(strip_ansi("safe\rEVIL"), "safe\rEVIL");
|
||||
assert_eq!(strip_ansi("a\x08b\x07c\x0E\x0Fd"), "a\x08b\x07c\x0E\x0Fd");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_substitutes_dangerous_bytes() {
|
||||
let r = '\u{FFFD}';
|
||||
assert_eq!(sanitize("safe\rEVIL"), format!("safe{r}EVIL"));
|
||||
assert_eq!(sanitize("a\x08b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\x07b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\x0Bb"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\x0Eb\x0Fc"), format!("a{r}b{r}c"));
|
||||
assert_eq!(sanitize("a\u{8D}b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\u{85}b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("trailing\r"), format!("trailing{r}"));
|
||||
assert_eq!(sanitize("a\x7Fb"), format!("a{r}b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_substitutes_bidi_and_zero_width() {
|
||||
let r = '\u{FFFD}';
|
||||
// Trojan-Source bidi formatting: U+202A..U+202E
|
||||
assert_eq!(sanitize("a\u{202A}b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\u{202E}b"), format!("a{r}b"));
|
||||
// Bidi isolates: U+2066..U+2069
|
||||
assert_eq!(sanitize("a\u{2066}b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\u{2069}b"), format!("a{r}b"));
|
||||
// Zero-width: U+200B..U+200D
|
||||
assert_eq!(sanitize("a\u{200B}b"), format!("a{r}b"));
|
||||
assert_eq!(sanitize("a\u{200D}b"), format!("a{r}b"));
|
||||
// BOM in middle of file: U+FEFF
|
||||
assert_eq!(sanitize("a\u{FEFF}b"), format!("a{r}b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_substitutes_every_bidi_control() {
|
||||
// Unicode Bidi_Control is exactly these 12 codepoints. Covering only some
|
||||
// of them leaves a reordering attack available through the rest.
|
||||
let r = '\u{FFFD}';
|
||||
for c in [
|
||||
'\u{061C}', '\u{200E}', '\u{200F}', '\u{202A}', '\u{202B}', '\u{202C}', '\u{202D}',
|
||||
'\u{202E}', '\u{2066}', '\u{2067}', '\u{2068}', '\u{2069}',
|
||||
] {
|
||||
assert_eq!(
|
||||
sanitize(&format!("a{c}b")),
|
||||
format!("a{r}b"),
|
||||
"U+{:04X} was not substituted",
|
||||
c as u32
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_preserves_arabic_sharing_the_alm_lead_byte() {
|
||||
// U+061C is reached via lead byte 0xD8, which also leads ordinary Arabic.
|
||||
assert_eq!(
|
||||
sanitize("\u{0600}\u{061F}\u{06FF}"),
|
||||
"\u{0600}\u{061F}\u{06FF}"
|
||||
);
|
||||
assert_eq!(sanitize("مرحبا بالعالم"), "مرحبا بالعالم");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_preserves_legitimate_bytes() {
|
||||
assert_eq!(sanitize("crlf\r\nline\r\n"), "crlf\r\nline\r\n");
|
||||
assert_eq!(sanitize("a\tb\nc"), "a\tb\nc");
|
||||
assert_eq!(sanitize("plain ascii"), "plain ascii");
|
||||
assert_eq!(sanitize("üñíçödé"), "üñíçödé");
|
||||
// FF (U+000C) passes through; section separator in C source / Emacs Lisp.
|
||||
assert_eq!(sanitize("section1\x0Csection2"), "section1\x0Csection2");
|
||||
// Common Unicode that shares a UTF-8 lead byte with dangerous codepoints
|
||||
// must pass through unchanged.
|
||||
assert_eq!(sanitize("snowman ☃ moon ☾"), "snowman ☃ moon ☾");
|
||||
assert_eq!(sanitize("emoji 🎉 ☃"), "emoji 🎉 ☃");
|
||||
assert_eq!(sanitize("0xC2 lead: ÿ ñ ç"), "0xC2 lead: ÿ ñ ç");
|
||||
assert_eq!(sanitize("CJK 漢字 emoji 🦀"), "CJK 漢字 emoji 🦀");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_strips_ansi() {
|
||||
let r = '\u{FFFD}';
|
||||
// sanitize is a strict superset of strip_ansi.
|
||||
assert_eq!(sanitize("a\x1B[31mb\x1B[0mc"), "abc");
|
||||
assert_eq!(sanitize("a\u{9B}31mb\u{9B}0mc"), "abc");
|
||||
// ANSI then dangerous byte: ANSI gone, byte substituted.
|
||||
assert_eq!(sanitize("\x1B[31mhello\rEVIL"), format!("hello{r}EVIL"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_overstrike() {
|
||||
// Bold: X\x08X (same char repeated)
|
||||
assert_eq!(strip_overstrike("H\x08Hello", 1), "Hello");
|
||||
|
||||
// Underline: _\x08X (underscore before char)
|
||||
assert_eq!(strip_overstrike("_\x08Hello", 1), "Hello");
|
||||
|
||||
// Multiple overstrike sequences
|
||||
assert_eq!(strip_overstrike("B\x08Bo\x08ol\x08ld\x08d", 1), "Bold");
|
||||
|
||||
// Backspace at start of line (nothing to pop)
|
||||
assert_eq!(strip_overstrike("\x08Hello", 0), "Hello");
|
||||
|
||||
// Multiple consecutive backspaces
|
||||
assert_eq!(strip_overstrike("ABC\x08\x08\x08XYZ", 3), "XYZ");
|
||||
|
||||
// Unicode with overstrike
|
||||
assert_eq!(strip_overstrike("ä\x08äöü", 2), "äöü");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_for_terminal_passthrough() {
|
||||
assert_eq!(sanitize_for_terminal(""), "");
|
||||
assert_eq!(sanitize_for_terminal("hello.txt"), "hello.txt");
|
||||
assert_eq!(sanitize_for_terminal("résumé.pdf"), "résumé.pdf");
|
||||
assert_eq!(sanitize_for_terminal("日本語.md"), "日本語.md");
|
||||
assert_eq!(
|
||||
sanitize_for_terminal("path/with spaces/file.log"),
|
||||
"path/with spaces/file.log"
|
||||
);
|
||||
assert_eq!(sanitize_for_terminal("a\tb"), "a\tb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_for_terminal_c0_controls() {
|
||||
assert_eq!(
|
||||
sanitize_for_terminal("\x1b[31mINJECTED\x1b[0m.txt"),
|
||||
"^[[31mINJECTED^[[0m.txt"
|
||||
);
|
||||
assert_eq!(sanitize_for_terminal("bad\x07rest"), "bad^Grest");
|
||||
assert_eq!(sanitize_for_terminal("\x00\x08\n\r\x7F"), "^@^H^J^M^?");
|
||||
assert_eq!(sanitize_for_terminal("\u{9b}31m"), "\\u{9b}31m");
|
||||
assert_eq!(
|
||||
sanitize_for_terminal("\u{9d}0;pwned\x07"),
|
||||
"\\u{9d}0;pwned^G"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_for_terminal_idempotent_on_sanitized() {
|
||||
let dirty = "\x1b]0;pwned\x07file.txt";
|
||||
let clean = sanitize_for_terminal(dirty);
|
||||
assert_eq!(sanitize_for_terminal(&clean), clean);
|
||||
assert!(!clean.contains('\x1b'));
|
||||
assert!(!clean.contains('\x07'));
|
||||
}
|
||||
@@ -192,6 +192,15 @@ impl<'a> PrettyPrinter<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to sanitize untrusted input for safe display (default: never)
|
||||
///
|
||||
/// Strips ANSI escape sequences and additionally substitutes terminal-active
|
||||
/// control bytes and bidi / zero-width codepoints with U+FFFD.
|
||||
pub fn sanitize(&mut self, mode: StripAnsiMode) -> &mut Self {
|
||||
self.config.sanitize = mode;
|
||||
self
|
||||
}
|
||||
|
||||
/// Text wrapping mode (default: do not wrap)
|
||||
pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
|
||||
self.config.wrapping_mode = mode;
|
||||
|
||||
+139
-19
@@ -29,8 +29,10 @@ use crate::error::*;
|
||||
use crate::input::OpenedInput;
|
||||
use crate::line_range::{MaxBufferedLineNumber, RangeCheckResult};
|
||||
use crate::output::OutputHandle;
|
||||
use crate::preprocessor::strip_ansi;
|
||||
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
||||
use crate::preprocessor::{
|
||||
expand_tabs, replace_nonprintable, sanitize, sanitize_for_terminal, strip_ansi,
|
||||
strip_overstrike,
|
||||
};
|
||||
use crate::style::StyleComponent;
|
||||
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
||||
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
||||
@@ -38,6 +40,16 @@ use crate::wrapping::WrappingMode;
|
||||
use crate::BinaryBehavior;
|
||||
use crate::StripAnsiMode;
|
||||
|
||||
// Return the displayed width of a character.
|
||||
//
|
||||
// Control characters (0x00..=0x1F and 0x7F) are rendered by the terminal
|
||||
// in caret notation (e.g. ^@, ^A, ..., ^?), which occupies two columns.
|
||||
// UnicodeWidthChar::width() returns None for these, so we map them to 2
|
||||
// here instead of the previous default of 0.
|
||||
fn char_width(c: char) -> usize {
|
||||
c.width().unwrap_or(if c.is_control() { 2 } else { 0 })
|
||||
}
|
||||
|
||||
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
||||
raw_sequence: "\x1B[4m",
|
||||
parameters: "4",
|
||||
@@ -199,6 +211,8 @@ pub(crate) struct InteractivePrinter<'a> {
|
||||
background_color_highlight: Option<Color>,
|
||||
consecutive_empty_lines: usize,
|
||||
strip_ansi: bool,
|
||||
sanitize: bool,
|
||||
strip_overstrike: bool,
|
||||
}
|
||||
|
||||
impl<'a> InteractivePrinter<'a> {
|
||||
@@ -261,19 +275,31 @@ impl<'a> InteractivePrinter<'a> {
|
||||
|
||||
let needs_to_match_syntax = (!is_printing_binary
|
||||
|| matches!(config.binary, BinaryBehavior::AsText))
|
||||
&& (config.colored_output || config.strip_ansi == StripAnsiMode::Auto);
|
||||
&& (config.colored_output
|
||||
|| config.strip_ansi == StripAnsiMode::Auto
|
||||
|| config.sanitize == StripAnsiMode::Auto);
|
||||
|
||||
let (is_plain_text, highlighter_from_set) = if needs_to_match_syntax {
|
||||
let (is_plain_text, strip_overstrike, highlighter_from_set) = if needs_to_match_syntax {
|
||||
// Determine the type of syntax for highlighting
|
||||
const PLAIN_TEXT_SYNTAX: &str = "Plain Text";
|
||||
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
||||
const MANPAGE_SYNTAX: &str = "Manpage";
|
||||
const COMMAND_HELP_SYNTAX: &str = "Command Help";
|
||||
match assets.get_syntax(
|
||||
config.language,
|
||||
config.fallback_syntax,
|
||||
input,
|
||||
&config.syntax_mapping,
|
||||
) {
|
||||
Ok(syntax_in_set) => (
|
||||
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
|
||||
syntax_in_set.syntax.name == MANPAGE_SYNTAX
|
||||
|| syntax_in_set.syntax.name == COMMAND_HELP_SYNTAX,
|
||||
Some(HighlighterFromSet::new(syntax_in_set, theme)),
|
||||
),
|
||||
|
||||
Err(Error::UndetectedSyntax(_)) => (
|
||||
true,
|
||||
false,
|
||||
Some(
|
||||
assets
|
||||
.find_syntax_by_name(PLAIN_TEXT_SYNTAX)?
|
||||
@@ -285,7 +311,7 @@ impl<'a> InteractivePrinter<'a> {
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
(false, None)
|
||||
(false, false, None)
|
||||
};
|
||||
|
||||
// Determine when to strip ANSI sequences
|
||||
@@ -297,6 +323,14 @@ impl<'a> InteractivePrinter<'a> {
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let sanitize = match config.sanitize {
|
||||
_ if config.show_nonprintable => false,
|
||||
StripAnsiMode::Always => true,
|
||||
StripAnsiMode::Auto if is_plain_text => false,
|
||||
StripAnsiMode::Auto => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Ok(InteractivePrinter {
|
||||
panel_width,
|
||||
colors,
|
||||
@@ -310,6 +344,8 @@ impl<'a> InteractivePrinter<'a> {
|
||||
background_color_highlight,
|
||||
consecutive_empty_lines: 0,
|
||||
strip_ansi,
|
||||
sanitize,
|
||||
strip_overstrike,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -448,6 +484,11 @@ impl Printer for InteractivePrinter<'_> {
|
||||
input: &OpenedInput,
|
||||
add_header_padding: bool,
|
||||
) -> Result<()> {
|
||||
// If input is empty and quiet_empty is enabled, skip all output
|
||||
if self.content_type.is_none() && self.config.quiet_empty {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if add_header_padding && self.config.style_components.rule() {
|
||||
self.print_horizontal_line_term(handle, self.colors.rule)?;
|
||||
}
|
||||
@@ -463,7 +504,7 @@ impl Printer for InteractivePrinter<'_> {
|
||||
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
|
||||
to show the binary file contents.",
|
||||
Yellow.paint("[bat warning]"),
|
||||
input.description.summary(),
|
||||
sanitize_for_terminal(&input.description.summary()),
|
||||
)?;
|
||||
} else if self.config.style_components.grid() {
|
||||
self.print_horizontal_line(handle, '┬')?;
|
||||
@@ -517,9 +558,11 @@ impl Printer for InteractivePrinter<'_> {
|
||||
"{}{}{mode}",
|
||||
description
|
||||
.kind()
|
||||
.map(|kind| format!("{kind}: "))
|
||||
.map(|kind| format!("{}: ", sanitize_for_terminal(kind)))
|
||||
.unwrap_or_else(|| "".into()),
|
||||
self.colors.header_value.paint(description.title()),
|
||||
self.colors
|
||||
.header_value
|
||||
.paint(sanitize_for_terminal(description.title())),
|
||||
);
|
||||
self.print_header_multiline_component(handle, &header_filename)
|
||||
}
|
||||
@@ -550,6 +593,11 @@ impl Printer for InteractivePrinter<'_> {
|
||||
}
|
||||
|
||||
fn print_footer(&mut self, handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
|
||||
// If input is empty and quiet_empty is enabled, skip footer
|
||||
if self.content_type.is_none() && self.config.quiet_empty {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.config.style_components.grid()
|
||||
&& (self.content_type.is_some_and(|c| c.is_text())
|
||||
|| self.config.show_nonprintable
|
||||
@@ -568,11 +616,23 @@ impl Printer for InteractivePrinter<'_> {
|
||||
let title = "8<";
|
||||
let title_count = title.chars().count();
|
||||
|
||||
let snip_left = "─ ".repeat((self.config.term_width - panel_count - (title_count / 2)) / 4);
|
||||
let snip_left = "─ ".repeat(
|
||||
self.config
|
||||
.term_width
|
||||
.saturating_sub(panel_count)
|
||||
.saturating_sub(title_count / 2)
|
||||
/ 4,
|
||||
);
|
||||
let snip_left_count = snip_left.chars().count(); // Can't use .len() with Unicode.
|
||||
|
||||
let snip_right =
|
||||
" ─".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2);
|
||||
let snip_right = " ─".repeat(
|
||||
self.config
|
||||
.term_width
|
||||
.saturating_sub(panel_count)
|
||||
.saturating_sub(snip_left_count)
|
||||
.saturating_sub(title_count)
|
||||
/ 2,
|
||||
);
|
||||
|
||||
writeln!(
|
||||
handle,
|
||||
@@ -622,8 +682,16 @@ impl Printer for InteractivePrinter<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
// If ANSI escape sequences are supposed to be stripped, do it before syntax highlighting.
|
||||
if self.strip_ansi {
|
||||
if self.strip_overstrike {
|
||||
if let Some(pos) = line.find('\x08') {
|
||||
line = strip_overstrike(&line, pos).into();
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize is the strict superset; otherwise strip-ansi alone.
|
||||
if self.sanitize {
|
||||
line = sanitize(&line).into()
|
||||
} else if self.strip_ansi {
|
||||
line = strip_ansi(&line).into()
|
||||
}
|
||||
|
||||
@@ -670,10 +738,17 @@ impl Printer for InteractivePrinter<'_> {
|
||||
|
||||
// Line decorations.
|
||||
if self.panel_width > 0 {
|
||||
let display_line_number =
|
||||
if self.config.number_nonblank && line.trim_end_matches(['\r', '\n']).is_empty() {
|
||||
0
|
||||
} else {
|
||||
line_number
|
||||
};
|
||||
|
||||
let decorations = self
|
||||
.decorations
|
||||
.iter()
|
||||
.map(|d| d.generate(line_number, false, self));
|
||||
.map(|d| d.generate(display_line_number, false, self));
|
||||
|
||||
for deco in decorations {
|
||||
write!(handle, "{} ", deco.text)?;
|
||||
@@ -759,11 +834,21 @@ impl Printer for InteractivePrinter<'_> {
|
||||
// Displayed width of line_buf
|
||||
let mut current_width = 0;
|
||||
|
||||
let word_wrap = matches!(self.config.wrapping_mode, WrappingMode::Word);
|
||||
|
||||
// For word wrapping, track last whitespace position.
|
||||
let mut last_ws_idx: Option<usize> = None;
|
||||
|
||||
for c in text.chars() {
|
||||
// calculate the displayed width for next character
|
||||
let cw = c.width().unwrap_or(0);
|
||||
let cw = char_width(c);
|
||||
current_width += cw;
|
||||
|
||||
// Track whitespace positions for word wrapping.
|
||||
if word_wrap && c.is_whitespace() {
|
||||
last_ws_idx = Some(line_buf.len());
|
||||
}
|
||||
|
||||
// if next character cannot be printed on this line,
|
||||
// flush the buffer.
|
||||
if current_width > max_width {
|
||||
@@ -785,13 +870,37 @@ impl Printer for InteractivePrinter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the break point and remainder
|
||||
// for word wrapping.
|
||||
let (emit_end, rest_start) = if word_wrap {
|
||||
if let Some(ws_idx) = last_ws_idx {
|
||||
// Skip the whitespace character itself
|
||||
// and carry the rest to the next line.
|
||||
let rs = ws_idx
|
||||
+ line_buf[ws_idx..]
|
||||
.chars()
|
||||
.next()
|
||||
.map(|ch| ch.len_utf8())
|
||||
.unwrap_or(0);
|
||||
(ws_idx, Some(rs))
|
||||
} else {
|
||||
(line_buf.len(), None)
|
||||
}
|
||||
} else {
|
||||
(line_buf.len(), None)
|
||||
};
|
||||
|
||||
// It wraps.
|
||||
write!(
|
||||
handle,
|
||||
"{}{}\n{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
&format!("{}{line_buf}", self.ansi_style),
|
||||
&format!(
|
||||
"{}{}",
|
||||
self.ansi_style,
|
||||
&line_buf[..emit_end]
|
||||
),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text,
|
||||
@@ -804,8 +913,19 @@ impl Printer for InteractivePrinter<'_> {
|
||||
cursor = 0;
|
||||
max_width = cursor_max;
|
||||
|
||||
line_buf.clear();
|
||||
current_width = cw;
|
||||
if let Some(rs) = rest_start {
|
||||
// Word wrap: carry remainder to next line.
|
||||
let remainder = line_buf[rs..].to_string();
|
||||
let rem_width: usize =
|
||||
remainder.chars().map(char_width).sum();
|
||||
line_buf.clear();
|
||||
line_buf.push_str(&remainder);
|
||||
current_width = rem_width + cw;
|
||||
} else {
|
||||
line_buf.clear();
|
||||
current_width = cw;
|
||||
}
|
||||
last_ws_idx = None;
|
||||
}
|
||||
|
||||
line_buf.push(c);
|
||||
|
||||
+54
-3
@@ -17,9 +17,16 @@ use ignored_suffixes::IgnoredSuffixes;
|
||||
mod builtin;
|
||||
pub mod ignored_suffixes;
|
||||
|
||||
fn make_glob_matcher(from: &str) -> Result<GlobMatcher> {
|
||||
/// Whether a glob pattern should be matched case-sensitively or case-insensitively.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum Case {
|
||||
Sensitive,
|
||||
Insensitive,
|
||||
}
|
||||
|
||||
fn make_glob_matcher(from: &str, case: Case) -> Result<GlobMatcher> {
|
||||
let matcher = GlobBuilder::new(from)
|
||||
.case_insensitive(true)
|
||||
.case_insensitive(matches!(case, Case::Insensitive))
|
||||
.literal_separator(true)
|
||||
.build()?
|
||||
.compile_matcher();
|
||||
@@ -97,7 +104,14 @@ impl<'a> SyntaxMapping<'a> {
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
|
||||
let matcher = make_glob_matcher(from)?;
|
||||
let matcher = make_glob_matcher(from, Case::Insensitive)?;
|
||||
self.custom_mappings.push((matcher, to));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like [`Self::insert`], but the glob pattern is matched case-sensitively.
|
||||
pub fn insert_case_sensitive(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
|
||||
let matcher = make_glob_matcher(from, Case::Sensitive)?;
|
||||
self.custom_mappings.push((matcher, to));
|
||||
Ok(())
|
||||
}
|
||||
@@ -261,4 +275,41 @@ mod tests {
|
||||
Some(MappingTarget::MapTo("alpha"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn case_sensitive_custom_mappings_work() {
|
||||
let mut map = SyntaxMapping::new();
|
||||
map.insert_case_sensitive("MY_SPECIAL_FILE", MappingTarget::MapTo("Python"))
|
||||
.ok();
|
||||
|
||||
// Exact case matches
|
||||
assert_eq!(
|
||||
map.get_syntax_for("/path/to/MY_SPECIAL_FILE"),
|
||||
Some(MappingTarget::MapTo("Python"))
|
||||
);
|
||||
// Different case should NOT match the case-sensitive rule
|
||||
assert_eq!(map.get_syntax_for("/path/to/my_special_file"), None);
|
||||
assert_eq!(map.get_syntax_for("/path/to/My_Special_File"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_mappings_build_is_case_sensitive() {
|
||||
let map = SyntaxMapping::new();
|
||||
|
||||
// "BUILD" (uppercase) should map to Python via case-sensitive builtin
|
||||
assert_eq!(
|
||||
map.get_syntax_for("/path/to/BUILD"),
|
||||
Some(MappingTarget::MapTo("Python"))
|
||||
);
|
||||
// "build" (lowercase) should still map to MapToUnknown
|
||||
assert_eq!(
|
||||
map.get_syntax_for("/path/to/build"),
|
||||
Some(MappingTarget::MapToUnknown)
|
||||
);
|
||||
// Mixed case should NOT match the Python rule
|
||||
assert_eq!(
|
||||
map.get_syntax_for("/path/to/Build"),
|
||||
Some(MappingTarget::MapToUnknown)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use std::env;
|
||||
use globset::GlobMatcher;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::syntax_mapping::{make_glob_matcher, MappingTarget};
|
||||
use crate::syntax_mapping::{make_glob_matcher, Case, MappingTarget};
|
||||
|
||||
// Static syntax mappings generated from /src/syntax_mapping/builtins/ by the
|
||||
// build script (/build/syntax_mapping.rs).
|
||||
@@ -53,8 +53,8 @@ include!(concat!(
|
||||
/// 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")
|
||||
fn build_matcher_fixed(from: &str, case: Case) -> GlobMatcher {
|
||||
make_glob_matcher(from, case).expect("A builtin fixed glob matcher failed to compile")
|
||||
}
|
||||
|
||||
/// Join a list of matcher segments to create a glob string, replacing all
|
||||
@@ -64,7 +64,7 @@ fn build_matcher_fixed(from: &str) -> GlobMatcher {
|
||||
/// to compile.
|
||||
///
|
||||
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
|
||||
fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option<GlobMatcher> {
|
||||
fn build_matcher_dynamic(segs: &[MatcherSegment], case: Case) -> Option<GlobMatcher> {
|
||||
// join segments
|
||||
let mut buf = String::new();
|
||||
for seg in segs {
|
||||
@@ -77,7 +77,7 @@ fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option<GlobMatcher> {
|
||||
}
|
||||
}
|
||||
// compile glob matcher
|
||||
let matcher = make_glob_matcher(&buf).ok()?;
|
||||
let matcher = make_glob_matcher(&buf, case).ok()?;
|
||||
Some(matcher)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,10 @@ 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").
|
||||
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".
|
||||
The value of each key should be an array of "rules". The rules are expected to be objects with a `glob` string and a `case_sensitive` boolean. For simplification, a rule can be just a glob string, which is shorthand for the default case insensitive mode.
|
||||
|
||||
For example, if `foo-application` uses both TOML and YAML configuration files,
|
||||
we could write something like this:
|
||||
@@ -98,6 +96,15 @@ like this:
|
||||
]
|
||||
```
|
||||
|
||||
### Case sensitivity
|
||||
|
||||
By default, all glob patterns are matched case-insensitively. To match a pattern case-sensitively, use the object form of the rule with the `case_sensitive` option:
|
||||
|
||||
```toml
|
||||
[mappings]
|
||||
"Python" = [{ glob = "BUILD", case_sensitive = true }]
|
||||
```
|
||||
|
||||
## Ordering
|
||||
|
||||
At compile time, all TOML files applicable to the target are processed in
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"Python" = [{ glob = "BUILD", case_sensitive = true }]
|
||||
@@ -1,2 +1,2 @@
|
||||
[mappings]
|
||||
"XML" = ["*.csproj", "*.vbproj", "*.props", "*.targets"]
|
||||
"XML" = ["*.csproj", "*.vbproj", "*.props", "*.targets", "*.slnx"]
|
||||
@@ -0,0 +1,3 @@
|
||||
[mappings]
|
||||
"INI" = [".boto", "**/gcloud/configurations/config_*"]
|
||||
"Git Ignore" = [".gcloudignore"]
|
||||
@@ -1,3 +1,3 @@
|
||||
# JSON Lines is a simple variation of JSON #2535
|
||||
[mappings]
|
||||
"JSON" = ["*.jsonl", "*.jsonc", "*.jsonld", "*.geojson", "*.ndjson"]
|
||||
"JSON" = ["*.jsonl", "*.jsonc", "*.jsonld", "*.geojson", "*.ndjson", "*.code-workspace"]
|
||||
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"Makefile" = ["justfile", "Justfile", ".justfile", "*.justfile"]
|
||||
@@ -1,2 +1,2 @@
|
||||
[mappings]
|
||||
"SSH Config" = ["**/.ssh/config"]
|
||||
"SSH Config" = ["**/.ssh/**/config"]
|
||||
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"Git Ignore" = [".?*ignore"]
|
||||
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"INI" = ["/etc/dnf/dnf.conf", "/etc/yum.repos.d/*.repo"]
|
||||
@@ -1,5 +1,6 @@
|
||||
# see `man quadlet`
|
||||
[mappings]
|
||||
"INI" = [
|
||||
"*.quadlets",
|
||||
"**/containers/systemd/**/*.{container,volume,network,kube,image,build,pod,artifact}",
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
[mappings]
|
||||
"INI" = ["**/ghostty/**/*.ghostty", "**/ghostty/themes/*"]
|
||||
+232
-16
@@ -173,8 +173,10 @@ impl Attributes {
|
||||
2 => self.dim = "\x1B[2m".to_owned(),
|
||||
3 => self.italic = "\x1B[3m".to_owned(),
|
||||
4 => self.underline = "\x1B[4m".to_owned(),
|
||||
9 => self.strike = "\x1B[9m".to_owned(),
|
||||
23 => self.italic.clear(),
|
||||
24 => self.underline.clear(),
|
||||
29 => self.strike.clear(),
|
||||
22 => {
|
||||
self.bold.clear();
|
||||
self.dim.clear();
|
||||
@@ -386,12 +388,23 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
}
|
||||
|
||||
fn next_text(&mut self) -> Option<EscapeSequenceOffsets> {
|
||||
self.chars_take_while(|c| c != '\x1B')
|
||||
self.chars_take_while(|c| !is_sequence_introducer(c))
|
||||
.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");
|
||||
|
||||
// Handle 8-bit C1 introducers as their 7-bit `ESC <x>` equivalents.
|
||||
match c {
|
||||
'\u{9B}' => return self.next_csi_body(start_sequence),
|
||||
'\u{9D}' => return self.next_osc_body(start_sequence),
|
||||
'\u{90}' | '\u{98}' | '\u{9E}' | '\u{9F}' => {
|
||||
return self.next_string_terminated_body(start_sequence)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self.chars.peek() {
|
||||
None => Some(EscapeSequenceOffsets::Unknown {
|
||||
start: start_sequence,
|
||||
@@ -400,12 +413,24 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
|
||||
Some((_, ']')) => self.next_osc(start_sequence),
|
||||
Some((_, '[')) => self.next_csi(start_sequence),
|
||||
Some((i, c)) => match c {
|
||||
// 7-bit DCS/SOS/PM/APC: `ESC P/X/^/_` introduces a string body.
|
||||
Some((_, 'P' | 'X' | '^' | '_')) => {
|
||||
self.chars.next();
|
||||
self.next_string_terminated_body(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(),
|
||||
}),
|
||||
c => {
|
||||
// Single-byte ESC sequence (RIS, DECSC/DECRC, keypad, VT52 etc.).
|
||||
let end = match self.chars.next() {
|
||||
Some((j, fc)) => j + fc.len_utf8(),
|
||||
None => i + c.len_utf8(),
|
||||
};
|
||||
Some(EscapeSequenceOffsets::Unknown {
|
||||
start: start_sequence,
|
||||
end,
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -413,12 +438,27 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
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 start_command = osc_open_index + osc_open_char.len_utf8();
|
||||
Some(self.read_osc_body(start_sequence, start_command))
|
||||
}
|
||||
|
||||
/// OSC body parser entered after the 8-bit introducer U+009D was consumed.
|
||||
fn next_osc_body(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
|
||||
let start_command = start_sequence + '\u{9D}'.len_utf8();
|
||||
Some(self.read_osc_body(start_sequence, start_command))
|
||||
}
|
||||
|
||||
fn read_osc_body(
|
||||
&mut self,
|
||||
start_sequence: usize,
|
||||
start_command: usize,
|
||||
) -> EscapeSequenceOffsets {
|
||||
let mut start_terminator: usize;
|
||||
let mut end_sequence: usize;
|
||||
|
||||
loop {
|
||||
match self.chars_take_while(|c| !matches!(c, '\x07' | '\x1B')) {
|
||||
// ST is BEL, ESC `\\`, or U+009C.
|
||||
match self.chars_take_while(|c| !matches!(c, '\x07' | '\x1B' | '\u{9C}')) {
|
||||
None => {
|
||||
start_terminator = self.text.len();
|
||||
end_sequence = start_terminator;
|
||||
@@ -437,6 +477,11 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
break;
|
||||
}
|
||||
|
||||
Some((ti, '\u{9C}')) => {
|
||||
end_sequence = ti + '\u{9C}'.len_utf8();
|
||||
break;
|
||||
}
|
||||
|
||||
Some((ti, '\x1B')) => {
|
||||
match self.chars.next() {
|
||||
Some((i, '\\')) => {
|
||||
@@ -466,20 +511,82 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Some(EscapeSequenceOffsets::OSC {
|
||||
EscapeSequenceOffsets::OSC {
|
||||
start_sequence,
|
||||
start_command: osc_open_index + osc_open_char.len_utf8(),
|
||||
start_command,
|
||||
start_terminator,
|
||||
end: end_sequence,
|
||||
}
|
||||
}
|
||||
|
||||
/// DCS/SOS/PM/APC body parser. Emitted as `Unknown` so the body is stripped.
|
||||
fn next_string_terminated_body(
|
||||
&mut self,
|
||||
start_sequence: usize,
|
||||
) -> Option<EscapeSequenceOffsets> {
|
||||
let mut end_sequence: usize;
|
||||
|
||||
loop {
|
||||
match self.chars_take_while(|c| !matches!(c, '\x07' | '\x1B' | '\u{9C}')) {
|
||||
None => {
|
||||
end_sequence = self.text.len();
|
||||
break;
|
||||
}
|
||||
Some((_, end)) => {
|
||||
end_sequence = end;
|
||||
}
|
||||
}
|
||||
|
||||
match self.chars.next() {
|
||||
Some((ti, '\x07')) => {
|
||||
end_sequence = ti + '\x07'.len_utf8();
|
||||
break;
|
||||
}
|
||||
Some((ti, '\u{9C}')) => {
|
||||
end_sequence = ti + '\u{9C}'.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;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
None => break,
|
||||
Some((_, tc)) => {
|
||||
panic!("this should not be reached: char {tc:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(EscapeSequenceOffsets::Unknown {
|
||||
start: start_sequence,
|
||||
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, '[');
|
||||
Some(self.read_csi_body(start_sequence, csi_open_index + csi_open_char.len_utf8()))
|
||||
}
|
||||
|
||||
let start_parameters: usize = csi_open_index + csi_open_char.len_utf8();
|
||||
/// CSI body parser entered after the 8-bit introducer U+009B was consumed.
|
||||
fn next_csi_body(&mut self, start_sequence: usize) -> Option<EscapeSequenceOffsets> {
|
||||
let start_parameters = start_sequence + '\u{9B}'.len_utf8();
|
||||
Some(self.read_csi_body(start_sequence, start_parameters))
|
||||
}
|
||||
|
||||
fn read_csi_body(
|
||||
&mut self,
|
||||
start_sequence: usize,
|
||||
start_parameters: usize,
|
||||
) -> EscapeSequenceOffsets {
|
||||
// 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')) {
|
||||
@@ -498,13 +605,13 @@ impl<'a> EscapeSequenceOffsetsIterator<'a> {
|
||||
Some((i, c)) => i + c.len_utf8(),
|
||||
};
|
||||
|
||||
Some(EscapeSequenceOffsets::CSI {
|
||||
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> {
|
||||
@@ -543,13 +650,25 @@ impl Iterator for EscapeSequenceOffsetsIterator<'_> {
|
||||
type Item = EscapeSequenceOffsets;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.chars.peek() {
|
||||
Some((_, '\x1B')) => self.next_sequence(),
|
||||
Some((_, c)) if is_sequence_introducer(*c) => self.next_sequence(),
|
||||
Some((_, _)) => self.next_text(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// True for ESC and the 8-bit C1 sequence introducers (DCS/SOS/CSI/OSC/PM/APC).
|
||||
#[inline]
|
||||
fn is_sequence_introducer(c: char) -> bool {
|
||||
if (c as u32) < 0x80 {
|
||||
return c == '\x1B';
|
||||
}
|
||||
matches!(
|
||||
c,
|
||||
'\u{90}' | '\u{98}' | '\u{9B}' | '\u{9D}' | '\u{9E}' | '\u{9F}'
|
||||
)
|
||||
}
|
||||
|
||||
/// An iterator over ANSI/VT escape sequences within a string.
|
||||
///
|
||||
/// ## Example
|
||||
@@ -717,6 +836,79 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_parses_8bit_csi() {
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new("\u{9B}31m");
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::CSI {
|
||||
start_sequence: 0,
|
||||
start_parameters: 2,
|
||||
start_intermediates: 4,
|
||||
start_final_byte: 4,
|
||||
end: 5,
|
||||
})
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_parses_8bit_osc_with_8bit_st() {
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new("\u{9D}0;title\u{9C}");
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::OSC {
|
||||
start_sequence: 0,
|
||||
start_command: 2,
|
||||
start_terminator: 9,
|
||||
end: 11,
|
||||
})
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_parses_7bit_dcs_consumes_body() {
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new("\x1BP1;0;|payload\x1B\\rest");
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::Unknown { start: 0, end: 16 })
|
||||
);
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::Text { start: 16, end: 20 })
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_8bit_dcs_consumes_body() {
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new("\u{90}body\u{9C}rest");
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::Unknown { start: 0, end: 8 })
|
||||
);
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::Text { start: 8, end: 12 })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_truncated_dcs_consumes_to_eof() {
|
||||
// Unterminated DCS must still be consumed, not emitted as `Text`.
|
||||
let input = "\x1BPno-terminator";
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new(input);
|
||||
assert_eq!(
|
||||
iter.next(),
|
||||
Some(EscapeSequenceOffsets::Unknown {
|
||||
start: 0,
|
||||
end: input.len(),
|
||||
})
|
||||
);
|
||||
assert_eq!(iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_sequence_offsets_iterator_parses_csi() {
|
||||
let mut iter = EscapeSequenceOffsetsIterator::new("\x1B[m");
|
||||
@@ -922,10 +1114,10 @@ mod tests {
|
||||
fn test_sgr_attributes_do_not_leak_into_wrong_field() {
|
||||
let mut attrs = crate::vscreen::Attributes::new();
|
||||
|
||||
// Bold, Dim, Italic, Underline, Foreground, Background
|
||||
// Bold, Dim, Italic, Underline, Strike, Foreground, Background
|
||||
attrs.update(EscapeSequence::CSI {
|
||||
raw_sequence: "\x1B[1;2;3;4;31;41m",
|
||||
parameters: "1;2;3;4;31;41",
|
||||
raw_sequence: "\x1B[1;2;3;4;9;31;41m",
|
||||
parameters: "1;2;3;4;9;31;41",
|
||||
intermediates: "",
|
||||
final_byte: "m",
|
||||
});
|
||||
@@ -934,6 +1126,7 @@ mod tests {
|
||||
assert_eq!(attrs.dim, "\x1B[2m");
|
||||
assert_eq!(attrs.italic, "\x1B[3m");
|
||||
assert_eq!(attrs.underline, "\x1B[4m");
|
||||
assert_eq!(attrs.strike, "\x1B[9m");
|
||||
assert_eq!(attrs.foreground, "\x1B[31m");
|
||||
assert_eq!(attrs.background, "\x1B[41m");
|
||||
|
||||
@@ -950,4 +1143,27 @@ mod tests {
|
||||
assert_eq!(attrs.foreground, "\x1B[94m");
|
||||
assert_eq!(attrs.background, "\x1B[103m");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sgr_strike_is_tracked_and_cleared() {
|
||||
let mut attrs = crate::vscreen::Attributes::new();
|
||||
|
||||
// Strikethrough on (SGR 9) must be tracked so it can be re-emitted.
|
||||
attrs.update(EscapeSequence::CSI {
|
||||
raw_sequence: "\x1B[9m",
|
||||
parameters: "9",
|
||||
intermediates: "",
|
||||
final_byte: "m",
|
||||
});
|
||||
assert_eq!(attrs.strike, "\x1B[9m");
|
||||
|
||||
// Strikethrough off (SGR 29) must clear it, just like italic (23) and underline (24).
|
||||
attrs.update(EscapeSequence::CSI {
|
||||
raw_sequence: "\x1B[29m",
|
||||
parameters: "29",
|
||||
intermediates: "",
|
||||
final_byte: "m",
|
||||
});
|
||||
assert_eq!(attrs.strike, "");
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WrappingMode {
|
||||
Character,
|
||||
// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
|
||||
Word,
|
||||
/// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
|
||||
NoWrapping(bool),
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ fn all_themes_are_present() {
|
||||
"Solarized (light)",
|
||||
"Sublime Snazzy",
|
||||
"TwoDark",
|
||||
"Visual Studio Dark+",
|
||||
"ansi",
|
||||
"base16",
|
||||
"base16-256",
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
--show-all
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
GIT-COMMIT(1) Git Manual GIT-COMMIT(1)
|
||||
|
||||
NNAAMMEE
|
||||
git-commit - Record changes to the repository
|
||||
|
||||
SSYYNNOOPPSSIISS
|
||||
ggiitt ccoommmmiitt [--aa | ----iinntteerraaccttiivvee | ----ppaattcchh] [--ss] [--vv] [--uu[_<_m_o_d_e_>]] [----aammeenndd]
|
||||
[----ddrryy--rruunn] [(--cc | --CC | ----ssqquuaasshh) _<_c_o_m_m_i_t_> | ----ffiixxuupp [(aammeenndd|rreewwoorrdd)::]_<_c_o_m_m_i_t_>]
|
||||
[--FF _<_f_i_l_e_> | --mm _<_m_s_g_>] [----rreesseett--aauutthhoorr] [----aallllooww--eemmppttyy]
|
||||
[----aallllooww--eemmppttyy--mmeessssaaggee] [----nnoo--vveerriiffyy] [--ee] [----aauutthhoorr==_<_a_u_t_h_o_r_>]
|
||||
[----ddaattee==_<_d_a_t_e_>] [----cclleeaannuupp==_<_m_o_d_e_>] [----[nnoo--]ssttaattuuss]
|
||||
[--ii | --oo] [----ppaatthhssppeecc--ffrroomm--ffiillee==_<_f_i_l_e_> [----ppaatthhssppeecc--ffiillee--nnuull]]
|
||||
[(----ttrraaiilleerr _<_t_o_k_e_n_>[(==|::)_<_v_a_l_u_e_>])...] [--SS[_<_k_e_y_i_d_>]]
|
||||
[----] [_<_p_a_t_h_s_p_e_c_>...]
|
||||
|
||||
DDEESSCCRRIIPPTTIIOONN
|
||||
Create a new commit containing the current contents of the index and
|
||||
the given log message describing the changes. The new commit is a
|
||||
direct child of HEAD, usually the tip of the current branch, and the
|
||||
branch is updated to point to it (unless no branch is associated with
|
||||
the working tree, in which case HHEEAADD is "detached" as described in ggiitt--
|
||||
cchheecckkoouutt(1)).
|
||||
|
||||
The content to be committed can be specified in several ways:
|
||||
|
||||
1. by using ggiitt--aadddd(1) to incrementally "add" changes to the index
|
||||
before using the ccoommmmiitt command (Note: even modified files must be
|
||||
"added");
|
||||
|
||||
2. by using ggiitt--rrmm(1) to remove files from the working tree and the
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
BBold tteexxtt and _u_n_d_e_r_l_i_n_e
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
NAME="${0##*/}"
|
||||
for i in {1..3}; do
|
||||
echo "hello $NAME $i"
|
||||
done
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/expect -f
|
||||
set timeout 30
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env tclsh
|
||||
puts "Hello from tclsh"
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/wish
|
||||
button .b -text "Click"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
The quick brown fox jumps over the lazy dog and then runs away
|
||||
superlongwordthatdefinitelyexceedstheterminalwidthandshouldfallbacktocharacterwrapping
|
||||
short words here
|
||||
+1310
-32
File diff suppressed because it is too large.
Load diff
+12
-10
@@ -4,6 +4,7 @@ import itertools
|
||||
import subprocess
|
||||
import pathlib
|
||||
import shutil
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def generate_snapshots():
|
||||
@@ -19,22 +20,23 @@ def generate_snapshots():
|
||||
|
||||
|
||||
def generate_style_snapshot(style):
|
||||
generate_snapshot(style.replace(",", "_"), "--style={}".format(style))
|
||||
generate_snapshot(style.replace(",", "_"), ["--style={}".format(style)])
|
||||
|
||||
|
||||
def generate_snapshot(name, arguments):
|
||||
command = "cargo run -- --paging=never --color=never --decorations=always "
|
||||
command += "{args} sample.rs > output/{name}.snapshot.txt".format(
|
||||
name=name,
|
||||
args=arguments
|
||||
)
|
||||
def generate_snapshot(name: str, arguments: Iterable[str]):
|
||||
output_file = "output/{name}.snapshot.txt".format(name=name)
|
||||
command = [
|
||||
"cargo", "run", "--", "--paging=never", "--color=never",
|
||||
"--decorations=always", *arguments, "sample.rs"
|
||||
]
|
||||
print("generating snapshot for {}".format(name))
|
||||
subprocess.call(command, shell=True)
|
||||
with open(output_file, "w") as f:
|
||||
subprocess.call(command, stdout=f)
|
||||
|
||||
|
||||
def build_bat():
|
||||
print("building bat")
|
||||
subprocess.call("cargo build", cwd="../..", shell=True)
|
||||
subprocess.call(["cargo", "build"], cwd="../..")
|
||||
|
||||
|
||||
def prepare_output_dir():
|
||||
@@ -49,7 +51,7 @@ def modify_sample_file():
|
||||
|
||||
def undo_sample_file_modification():
|
||||
print("undoing sample.rs modifications")
|
||||
subprocess.call("git checkout -- sample.rs", shell=True)
|
||||
subprocess.call(["git", "checkout", "--", "sample.rs"])
|
||||
|
||||
|
||||
build_bat()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
[38;2;249;38;114mIDENTIFICATION DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;249;38;114mPROGRAM-ID[0m[38;2;248;248;242m. [0m[38;2;248;248;242mPAYROLL-CALC[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;249;38;114mDATA DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;249;38;114mWORKING-STORAGE SECTION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m01[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-DETAILS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC X(30).[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V9[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V99[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m01[0m[38;2;248;248;242m [0m[38;2;248;248;242mPAY-CALCULATIONS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V9[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-RATE[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC V99[0m[38;2;248;248;242m [0m[38;2;249;38;114mVALUE[0m[38;2;248;248;242m [0m[38;2;190;132;255m0.10[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
|
||||
[38;2;249;38;114mPROCEDURE DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242mMAIN-LOGIC[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m--- COBOL Payroll Calculator ---[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Employee Name: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Hours Worked (e.g., 40.5): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Hourly Rate (e.g., 15.75): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-GROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-TAX[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-NET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mDISPLAY-RESULTS[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mSTOP RUN[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-GROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mIF[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m>[0m[38;2;248;248;242m [0m[38;2;190;132;255m40[0m[38;2;248;248;242m [0m[38;2;249;38;114mTHEN[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;248;248;242m [0m[38;2;190;132;255m40[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;190;132;255m1.5[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m ([0m[38;2;190;132;255m40[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m) [0m[38;2;249;38;114m+[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mELSE[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mEND-IF[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-TAX[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-RATE[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-NET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mDISPLAY-RESULTS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m----------------------------------[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEmployee Name: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mHours Worked: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mHourly Rate: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mGross Pay: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mTax (10%): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mNet Pay: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m----------------------------------[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
[38;2;249;38;114mIDENTIFICATION DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;249;38;114mPROGRAM-ID[0m[38;2;248;248;242m. [0m[38;2;248;248;242mPAYROLL-CALC[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;249;38;114mDATA DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;249;38;114mWORKING-STORAGE SECTION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m01[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-DETAILS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC X(30).[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V9[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V99[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m01[0m[38;2;248;248;242m [0m[38;2;248;248;242mPAY-CALCULATIONS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 99V9[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-RATE[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC V99[0m[38;2;248;248;242m [0m[38;2;249;38;114mVALUE[0m[38;2;248;248;242m [0m[38;2;190;132;255m0.10[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
[38;2;248;248;242m [0m[38;2;190;132;255m05[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mPIC 9(5)V99.[0m
|
||||
|
||||
[38;2;249;38;114mPROCEDURE DIVISION[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242mMAIN-LOGIC[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m--- COBOL Payroll Calculator ---[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Employee Name: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Hours Worked (e.g., 40.5): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEnter Hourly Rate (e.g., 15.75): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mACCEPT[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-GROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-TAX[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mCALCULATE-NET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mPERFORM[0m[38;2;248;248;242m [0m[38;2;248;248;242mDISPLAY-RESULTS[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mSTOP RUN[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-GROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mIF[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m>[0m[38;2;248;248;242m [0m[38;2;190;132;255m40[0m[38;2;248;248;242m [0m[38;2;249;38;114mTHEN[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;248;248;242m [0m[38;2;190;132;255m40[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-HOURS[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;190;132;255m1.5[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m ([0m[38;2;190;132;255m40[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m) [0m[38;2;249;38;114m+[0m[38;2;248;248;242m [0m[38;2;248;248;242mOVERTIME-PAY[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mELSE[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mEND-IF[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-TAX[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-RATE[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mCALCULATE-NET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mCOMPUTE[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m.[0m
|
||||
|
||||
[38;2;248;248;242mDISPLAY-RESULTS[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m----------------------------------[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mEmployee Name: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mEMPLOYEE-NAME[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mHours Worked: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURS-WORKED[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mHourly Rate: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mHOURLY-RATE[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mGross Pay: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mGROSS-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mTax (10%): [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mTAX-AMOUNT[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mNet Pay: [0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242mNET-PAY[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mDISPLAY[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116m----------------------------------[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
[3;38;2;253;151;31mfirst[0m[38;2;253;151;31m,[0m[38;2;102;217;239mlast[0m[38;2;253;151;31m,[0m[38;2;190;132;255maddress[0m[38;2;253;151;31m,[0m[38;2;249;38;114mcity[0m[38;2;253;151;31m,[0m[38;2;230;219;116mzip[0m
|
||||
[3;38;2;253;151;31mJohn[0m[38;2;253;151;31m,[0m[38;2;102;217;239mDoe[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;230;219;116m"[0m[38;2;230;219;116mAnytown, WW[0m[38;2;230;219;116m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
[3;38;2;253;151;31mJohn[0m[38;2;253;151;31m,[0m[38;2;102;217;239mDoe[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;249;38;114m"[0m[38;2;249;38;114mAnytown, WW[0m[38;2;249;38;114m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
[3;38;2;253;151;31ma[0m[38;2;253;151;31m,[0m[38;2;102;217;239mb[0m
|
||||
[3;38;2;253;151;31m1[0m[38;2;253;151;31m,[0m[38;2;230;219;116m"[0m[38;2;230;219;116mha [0m
|
||||
[38;2;190;132;255m""[0m[38;2;230;219;116mha[0m[38;2;190;132;255m""[0m[38;2;230;219;116m [0m
|
||||
[38;2;230;219;116mha[0m[38;2;230;219;116m"[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;230;219;116m"[0m[38;2;230;219;116mAnytown, WW[0m[38;2;230;219;116m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
[3;38;2;253;151;31m3[0m[38;2;253;151;31m,[0m[38;2;102;217;239m4[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;230;219;116m"[0m[38;2;230;219;116mAnytown, WW[0m[38;2;230;219;116m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
[3;38;2;253;151;31m1[0m[38;2;253;151;31m,[0m[38;2;102;217;239m"[0m[38;2;102;217;239mha [0m
|
||||
[38;2;190;132;255m""[0m[38;2;102;217;239mha[0m[38;2;190;132;255m""[0m[38;2;102;217;239m [0m
|
||||
[38;2;102;217;239mha[0m[38;2;102;217;239m"[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;249;38;114m"[0m[38;2;249;38;114mAnytown, WW[0m[38;2;249;38;114m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
[3;38;2;253;151;31m3[0m[38;2;253;151;31m,[0m[38;2;102;217;239m4[0m[38;2;253;151;31m,[0m[38;2;190;132;255m120 any st.[0m[38;2;253;151;31m,[0m[38;2;249;38;114m"[0m[38;2;249;38;114mAnytown, WW[0m[38;2;249;38;114m"[0m[38;2;253;151;31m,[0m[38;2;230;219;116m08123[0m
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 177.
|
+1
-1
@@ -1,3 +1,3 @@
|
||||
[3;38;2;253;151;31mfoo[0m[38;2;253;151;31m [0m[38;2;102;217;239mbar[0m[38;2;253;151;31m [0m[38;2;190;132;255mbaz|;,[0m[38;2;253;151;31m [0m[38;2;249;38;114mtest[0m[38;2;253;151;31m [0m[38;2;230;219;116mhello world[0m[38;2;253;151;31m [0m[3;38;2;253;151;31mtsv[0m
|
||||
[3;38;2;253;151;31m1,2[0m[38;2;253;151;31m [0m[38;2;102;217;239m1,7[0m[38;2;253;151;31m [0m[38;2;190;132;255m2,7[0m[38;2;253;151;31m [0m[38;2;249;38;114ma b c[0m[38;2;253;151;31m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mhello again[0m[38;2;230;219;116m"[0m[38;2;253;151;31m [0m[3;38;2;253;151;31mtsv[0m
|
||||
[3;38;2;230;219;116m"[0m[3;38;2;230;219;116m;|,[0m[3;38;2;230;219;116m"[0m[38;2;253;151;31m [0m[38;2;102;217;239m;|,[0m[38;2;253;151;31m [0m[38;2;190;132;255mbaz[0m[38;2;253;151;31m [0m[38;2;249;38;114mtest[0m[38;2;253;151;31m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mhello world[0m[38;2;230;219;116m"[0m[38;2;253;151;31m [0m[3;38;2;253;151;31mtsv[0m
|
||||
[3;38;2;253;151;31m"[0m[3;38;2;253;151;31m;|,[0m[3;38;2;253;151;31m"[0m[38;2;253;151;31m [0m[38;2;102;217;239m;|,[0m[38;2;253;151;31m [0m[38;2;190;132;255mbaz[0m[38;2;253;151;31m [0m[38;2;249;38;114mtest[0m[38;2;253;151;31m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mhello world[0m[38;2;230;219;116m"[0m[38;2;253;151;31m [0m[3;38;2;253;151;31mtsv[0m
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 218.
|
@@ -0,0 +1,25 @@
|
||||
[38;2;248;248;242m([0m[38;2;255;255;255mlogging[0m[38;2;248;248;242m)[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mlog[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moutput[0m[38;2;248;248;242m file /var/log/caddy.log[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;117;113;94m# a comment[0m
|
||||
[38;2;255;255;255mlocalhost:8080[0m[38;2;249;38;114m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mexample.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mroot[0m[38;2;248;248;242m * /var/www/site[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mfile_server[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mreverse_proxy[0m[38;2;248;248;242m /.well-known/matrix/* localhost:8008[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mheader_up[0m[38;2;248;248;242m Host [0m[38;2;190;132;255m{upstream_hostport}[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;255;255;255mwww.example.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mredir[0m[38;2;248;248;242m https://example.com[0m[38;2;190;132;255m{uri}[0m[38;2;248;248;242m permanent[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;255;255;255mstatus.example.com[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mreverse_proxy[0m[38;2;248;248;242m localhost:3002[0m
|
||||
[38;2;248;248;242m [0m[38;2;102;217;239mimport[0m[38;2;248;248;242m logging[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
+52
-52
@@ -1,85 +1,85 @@
|
||||
[38;2;249;38;114mimport[0m[38;2;248;248;242m kotlin.math.*[0m
|
||||
[38;2;249;38;114mimport[0m[38;2;248;248;242m [0m[38;2;248;248;242mkotlin[0m[38;2;248;248;242m.[0m[38;2;248;248;242mmath[0m[38;2;248;248;242m.[0m[38;2;249;38;114m*[0m
|
||||
|
||||
[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mExample[0m[38;2;248;248;242m([0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m,[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mList[0m[38;2;248;248;242m<[0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m?>[0m
|
||||
[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mExample[0m[38;2;248;248;242m([0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mname[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m,[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mnumbers[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mList[0m[38;2;248;248;242m<[0m[3;38;2;166;226;46mInt[0m[38;2;249;38;114m?[0m[38;2;248;248;242m>[0m
|
||||
[38;2;248;248;242m)[0m
|
||||
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46minterface[0m[38;2;248;248;242m [0m[38;2;248;248;242mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m
|
||||
[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mAbstractJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255mfalse[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mJoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m() {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mBoolean[0m[38;2;248;248;242m {[0m
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mJoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mAbstractJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46misFunny[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mBoolean[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mtrue[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mcontent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mcontent of joke here, haha[0m[38;2;230;219;116m"[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;249;38;114m:[0m[38;2;248;248;242m Joke) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mby[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mjoke[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mnumber[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJoke[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mJokeInterface[0m[38;2;248;248;242m [0m[38;2;249;38;114mby[0m[38;2;248;248;242m [0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumber[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mLong[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m123L[0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mcompanion [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mcompanion[0m[38;2;248;248;242m [0m[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mconst[0m[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255msomeConstant[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msome constant text[0m[38;2;230;219;116m"[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mSomeSingleton[0m
|
||||
[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSomeSingleton[0m
|
||||
|
||||
[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mShape[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m
|
||||
[38;2;249;38;114msealed[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mShape[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mabstract[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m sideLength[0m[38;2;249;38;114m.[0m[38;2;248;248;242mpow([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
|
||||
[38;2;249;38;114mdata[0m[38;2;248;248;242m [0m[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mSquare[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31msideLength[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255msideLength[0m[38;2;248;248;242m.[0m[38;2;248;248;242mpow[0m[38;2;248;248;242m([0m[38;2;190;132;255m2[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[38;2;166;226;46mPoint[0m[38;2;248;248;242m [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;190;132;255m0[0m
|
||||
[38;2;249;38;114mobject[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mPoint[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;190;132;255m.0[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[38;2;166;226;46mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m) [0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;4;38;2;166;226;46mShape[0m[38;2;248;248;242m() {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m()[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mDouble[0m[38;2;248;248;242m {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;190;132;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius [0m[38;2;249;38;114m*[0m[38;2;248;248;242m radius[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;249;38;114mclass[0m[38;2;248;248;242m [0m[4;38;2;102;217;239mCircle[0m[38;2;248;248;242m([0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mradius[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mShape[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114moverride[0m[38;2;248;248;242m [0m[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46marea[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mDouble[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mreturn[0m[38;2;248;248;242m [0m[38;2;255;255;255mPI[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m[38;2;248;248;242m [0m[38;2;249;38;114m*[0m[38;2;248;248;242m [0m[38;2;255;255;255mradius[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m String.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m() [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mString[0m[38;2;248;248;242m.[0m[38;2;166;226;46mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mtest[0m[38;2;230;219;116m"[0m
|
||||
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m() {[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
|
||||
[38;2;249;38;114mfun[0m[38;2;248;248;242m [0m[38;2;166;226;46mmain[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;230;219;116m"""[0m
|
||||
[38;2;230;219;116m multiline[0m
|
||||
[38;2;230;219;116m string[0m
|
||||
[38;2;230;219;116m [0m
|
||||
[38;2;230;219;116m some numbers: 123123 42[0m
|
||||
[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;249;38;114m.[0m[38;2;248;248;242mtrimIndent()[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;166;226;46mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m Example(name [0m[38;2;249;38;114m=[0m[38;2;248;248;242m name, numbers [0m[38;2;249;38;114m=[0m[38;2;248;248;242m listOf([0m[38;2;190;132;255m512[0m[38;2;248;248;242m, [0m[38;2;190;132;255m42[0m[38;2;248;248;242m, [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m, [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m))[0m
|
||||
[38;2;230;219;116m [0m[38;2;230;219;116m"""[0m[38;2;248;248;242m.[0m[38;2;248;248;242mtrimIndent[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114mval[0m[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mExample[0m[38;2;248;248;242m([0m[38;2;255;255;255mname[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;255;255;255mname[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mnumbers[0m[38;2;248;248;242m [0m[38;2;249;38;114m=[0m[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;190;132;255m512[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255m42[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;190;132;255mnull[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;249;38;114m-[0m[38;2;190;132;255m1[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
|
||||
|
||||
[38;2;248;248;242m example[0m[38;2;249;38;114m.[0m[38;2;248;248;242mnumbers[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilterNotNull()[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach { println(it) }[0m
|
||||
[38;2;248;248;242m [0m[38;2;255;255;255mexample[0m[38;2;248;248;242m.[0m[38;2;255;255;255mnumbers[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilterNotNull[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;255;255;255mit[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;248;248;242m setOf(Joke(), DelegatedJoke(Joke())[0m[38;2;249;38;114m.[0m[38;2;248;248;242mjoke)[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mfilter(JokeInterface[0m[38;2;249;38;114m::[0m[38;2;248;248;242misFunny)[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mmap(AbstractJoke[0m[38;2;249;38;114m::[0m[38;2;248;248;242mcontent)[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEachIndexed { index[0m[38;2;249;38;114m:[0m[38;2;248;248;242m [0m[3;38;2;102;217;239mInt[0m[38;2;248;248;242m, joke [0m[38;2;249;38;114m->[0m
|
||||
[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[3;38;2;253;151;31m${index + 1}[0m[38;2;230;219;116m): [0m[3;38;2;253;151;31m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242msetOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mDelegatedJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242mJoke[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m[38;2;248;248;242m.[0m[38;2;255;255;255mjoke[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mfilter[0m[38;2;248;248;242m([0m[38;2;255;255;255mJokeInterface[0m[38;2;248;248;242m::[0m[38;2;248;248;242misFunny[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mmap[0m[38;2;248;248;242m([0m[38;2;255;255;255mAbstractJoke[0m[38;2;248;248;242m::[0m[38;2;248;248;242mcontent[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEachIndexed[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mindex[0m[38;2;248;248;242m:[0m[38;2;248;248;242m [0m[3;38;2;166;226;46mInt[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[3;38;2;253;151;31mjoke[0m[38;2;248;248;242m [0m[38;2;249;38;114m->[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116mI heard a funny joke(#[0m[38;2;248;248;242m${[0m[38;2;255;255;255mindex[0m[38;2;248;248;242m [0m[38;2;249;38;114m+[0m[38;2;248;248;242m [0m[38;2;190;132;255m1[0m[38;2;248;248;242m}[0m[38;2;230;219;116m): [0m[38;2;255;255;255m$joke[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;248;248;242m listOf(Square([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m), Point, Circle([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m))[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242massociateWith(Shape[0m[38;2;249;38;114m::[0m[38;2;248;248;242marea)[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mtoList()[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242msortedBy { it[0m[38;2;249;38;114m.[0m[38;2;248;248;242msecond }[0m
|
||||
[38;2;248;248;242m [0m[38;2;249;38;114m.[0m[38;2;248;248;242mforEach {[0m
|
||||
[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[3;38;2;253;151;31m${it.first}[0m[38;2;230;219;116m: [0m[3;38;2;253;151;31m${it.second}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242mlistOf[0m[38;2;248;248;242m([0m[38;2;248;248;242mSquare[0m[38;2;248;248;242m([0m[38;2;190;132;255m12.3[0m[38;2;248;248;242m)[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;255;255;255mPoint[0m[38;2;248;248;242m,[0m[38;2;248;248;242m [0m[38;2;248;248;242mCircle[0m[38;2;248;248;242m([0m[38;2;190;132;255m5.2[0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242massociateWith[0m[38;2;248;248;242m([0m[38;2;255;255;255mShape[0m[38;2;248;248;242m::[0m[38;2;248;248;242marea[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mtoList[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242msortedBy[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m.[0m[38;2;248;248;242mforEach[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255mfirst[0m[38;2;248;248;242m}[0m[38;2;230;219;116m: [0m[38;2;248;248;242m${[0m[38;2;255;255;255mit[0m[38;2;248;248;242m.[0m[38;2;255;255;255msecond[0m[38;2;248;248;242m}[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;248;248;242m println([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;249;38;114m.[0m[38;2;248;248;242mextensionMethod())[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242mprintln[0m[38;2;248;248;242m([0m[38;2;230;219;116m"[0m[38;2;230;219;116msome string[0m[38;2;230;219;116m"[0m[38;2;248;248;242m.[0m[38;2;248;248;242mextensionMethod[0m[38;2;248;248;242m([0m[38;2;248;248;242m)[0m[38;2;248;248;242m)[0m
|
||||
|
||||
[38;2;248;248;242m require(SomeSingleton[0m[38;2;249;38;114m::[0m[38;2;248;248;242mclass[0m[38;2;249;38;114m.[0m[38;2;248;248;242msimpleName [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m) { [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m }[0m
|
||||
[38;2;248;248;242m [0m[38;2;248;248;242mrequire[0m[38;2;248;248;242m([0m[38;2;255;255;255mSomeSingleton[0m[38;2;248;248;242m::[0m[38;2;249;38;114mclass[0m[38;2;248;248;242m.[0m[38;2;255;255;255msimpleName[0m[38;2;248;248;242m [0m[38;2;249;38;114m==[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116mSomeSingletonName[0m[38;2;230;219;116m"[0m[38;2;248;248;242m)[0m[38;2;248;248;242m [0m[38;2;248;248;242m{[0m[38;2;248;248;242m [0m[38;2;230;219;116m"[0m[38;2;230;219;116msomething does not seem right...[0m[38;2;230;219;116m"[0m[38;2;248;248;242m [0m[38;2;248;248;242m}[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
@@ -0,0 +1,399 @@
|
||||
[38;2;190;132;255mUWSM[0m[38;2;249;38;114m([0m[38;2;230;219;116m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m [0m[38;2;253;151;31mGeneral[0m[38;2;253;151;31m [0m[38;2;253;151;31mCommands[0m[38;2;253;151;31m [0m[38;2;253;151;31mManual[0m[38;2;248;248;242m [0m[38;2;190;132;255mUWSM[0m[38;2;249;38;114m([0m[38;2;230;219;116m1[0m[38;2;249;38;114m)[0m
|
||||
|
||||
[38;2;253;151;31mNAME[0m
|
||||
[38;2;248;248;242m UWSM - Universal Wayland Session Manager.[0m
|
||||
|
||||
[38;2;253;151;31mSYNOPSIS[0m
|
||||
[38;2;248;248;242m uwsm [0m[38;2;248;248;242m[[0m[38;2;166;226;46m-h[0m[38;2;249;38;114m|[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m][0m[38;2;248;248;242m {subcommand} [0m[38;2;248;248;242m[[0m[38;2;248;248;242moptions ...[0m[38;2;248;248;242m][0m
|
||||
|
||||
[38;2;253;151;31mDESCRIPTION[0m
|
||||
[38;2;248;248;242m Launches arbitrary wayland compositor via a set of systemd user units to provide graphical user[0m
|
||||
[38;2;248;248;242m session with environment management, XDG autostart support, clean shutdown. Provides helpers[0m
|
||||
[38;2;248;248;242m for launching applications as scopes or services.[0m
|
||||
|
||||
[38;2;253;151;31mSUBCOMMANDS[0m
|
||||
[38;2;248;248;242m select Select default compositor Entry.[0m
|
||||
[38;2;248;248;242m start Start compositor and graphical session.[0m
|
||||
[38;2;248;248;242m finalize Send compositor-set variables and unit startup notification to systemd user manager.[0m
|
||||
[38;2;248;248;242m stop Stop graphical session and compositor.[0m
|
||||
[38;2;248;248;242m app Application unit launcher (with Desktop Entry support).[0m
|
||||
[38;2;248;248;242m check Perform state checks (for scripting and info).[0m
|
||||
[38;2;248;248;242m aux Technical functions for use inside units.[0m
|
||||
|
||||
[38;2;248;248;242m See corresponding SUBCOMMANDS subsections below for further info.[0m
|
||||
|
||||
[38;2;248;248;242m Help for each subcommand is accessible by running "uwsm {subcommand} [0m[38;2;166;226;46m-h[0m[38;2;248;248;242m".[0m
|
||||
|
||||
[38;2;253;151;31mCONFIGURATION[0m
|
||||
[38;2;248;248;242m Files[0m
|
||||
[38;2;248;248;242m In XDG config hierarchy:[0m
|
||||
[38;2;248;248;242m uwsm/env[0m
|
||||
[38;2;248;248;242m uwsm/env.d/*[0m
|
||||
[38;2;248;248;242m uwsm/env-${compositor}[0m
|
||||
[38;2;248;248;242m uwsm/env-${compositor}.d/* Environment (shell) to be sourced for the graphical session.[0m
|
||||
[38;2;248;248;242m Sourced from directories of increasing priority, in each directory[0m
|
||||
[38;2;248;248;242m common file is sourced first, then suffixed files in the order of[0m
|
||||
[38;2;248;248;242m items listed in XDG_CURRENT_SESSION var (lowercased).[0m
|
||||
[38;2;248;248;242m uwsm/default-id Stores Desktop Entry ID of default compositor.[0m
|
||||
|
||||
[38;2;248;248;242m Fallback is also extended into the system part of XDG data hierarchy, this can be used for dis‐[0m
|
||||
[38;2;248;248;242m tro level defaults.[0m
|
||||
|
||||
[38;2;248;248;242m Environment vars[0m
|
||||
[38;2;248;248;242m UWSM_UNIT_RUNG (run|home)[0m
|
||||
[38;2;248;248;242m Which rung of systemd/user/ hierarchy to manage generated unit[0m
|
||||
[38;2;248;248;242m and drop-in files in: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m or [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m.[0m
|
||||
[38;2;248;248;242m UWSM_TWEAKS (boolean value)[0m
|
||||
[38;2;248;248;242m Set to False to remove and not generate tweak drop-ins for[0m
|
||||
[38;2;248;248;242m other software.[0m
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES (whitespace-separated names of env vars)[0m
|
||||
[38;2;248;248;242m Additional variables for "uwsm finalize".[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES (whitespace-separated names of env vars)[0m
|
||||
[38;2;248;248;242m Variables to wait for in activation environment before proceed‐[0m
|
||||
[38;2;248;248;242m ing to graphical session (in addition to WAYLAND_DISPLAY).[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES_TIMEOUT (int value)[0m
|
||||
[38;2;248;248;242m Seconds to wait for variables to appear in activation environ‐[0m
|
||||
[38;2;248;248;242m ment. Essentially, startup timeout (default: 10).[0m
|
||||
[38;2;248;248;242m UWSM_WAIT_VARNAMES_SETTLETIME (float value)[0m
|
||||
[38;2;248;248;242m Seconds to pause after all expected vars found in activation[0m
|
||||
[38;2;248;248;242m environment (default: 0.2).[0m
|
||||
[38;2;248;248;242m UWSM_APP_UNIT_TYPE (scope|service)[0m
|
||||
[38;2;248;248;242m Default unit type for launching apps (default: scope).[0m
|
||||
[38;2;248;248;242m UWSM_SILENT_START (int or boolean value)[0m
|
||||
[38;2;248;248;242m True or 1 to inhibit stdout messages from "uwsm start". 2 to[0m
|
||||
[38;2;248;248;242m also inhibit warnings.[0m
|
||||
[38;2;248;248;242m DEBUG (int or boolean value)[0m
|
||||
[38;2;248;248;242m True or positive number to dump debug info to stderr.[0m
|
||||
|
||||
[38;2;253;151;31mOPERATION OVERVIEW[0m
|
||||
[38;2;248;248;242m Login Sequence Integration[0m
|
||||
[38;2;248;248;242m uwsm can be launched by using conditional exec in shell profile to replace login shell (see[0m
|
||||
[38;2;248;248;242m Shell Profile Integration section).[0m
|
||||
|
||||
[38;2;248;248;242m Alternatively "uwsm start ..." command can be put into wayland session's Desktop Entry to be[0m
|
||||
[38;2;248;248;242m launched by a display manager (see Use Inside Desktop Entry section).[0m
|
||||
|
||||
[38;2;248;248;242m Compositor Selection[0m
|
||||
[38;2;248;248;242m uwsm can run arbitrary compositor command line or a Desktop Entry by ID (specifying Action ID[0m
|
||||
[38;2;248;248;242m is also supported).[0m
|
||||
|
||||
[38;2;248;248;242m Desktop Entry can also be selected via a whiptail menu (see select subcommand section).[0m
|
||||
|
||||
[38;2;248;248;242m Startup[0m
|
||||
[38;2;248;248;242m See start subcommand section for command syntax.[0m
|
||||
|
||||
[38;2;248;248;242m UWSM uses a set of units bound to standard user session targets:[0m
|
||||
|
||||
[38;2;248;248;242m • wayland-session-pre@.target (bound to graphical-session-pre.target)[0m
|
||||
[38;2;248;248;242m • wayland-wm-env@.service (environment preloader service)[0m
|
||||
[38;2;248;248;242m • wayland-session@.target (bound to graphical-session.target)[0m
|
||||
[38;2;248;248;242m • wayland-wm@.service (service for the selected compositor)[0m
|
||||
[38;2;248;248;242m • wayland-session-xdg-autostart@.target (bound to xdg-desktop-autostart.target)[0m
|
||||
[38;2;248;248;242m • wayland-session-envelope@.target (lives through entire lifecycle)[0m
|
||||
[38;2;248;248;242m • wayland-session-shutdown.target (conflicts with targets above for shutdown)[0m
|
||||
[38;2;248;248;242m • wayland-session-bindpid@.service (PID-tracking session killswitch)[0m
|
||||
[38;2;248;248;242m • wayland-session-waitenv.service (delays graphical session until vars appear)[0m
|
||||
|
||||
[38;2;248;248;242m Compositor ID (Desktop Entry ID or executable name) becomes the specifier for all templated[0m
|
||||
[38;2;248;248;242m units.[0m
|
||||
|
||||
[38;2;248;248;242m At the stage of graphical-session-pre.target, the environment saved from "uwsm start" context[0m
|
||||
[38;2;248;248;242m is loaded (or POSIX shell profile is sourced), uwsm environment files are sourced. The delta is[0m
|
||||
[38;2;248;248;242m exported to the systemd and D-Bus activation environments by the environment preloader service[0m
|
||||
[38;2;248;248;242m and is marked for cleanup at shutdown stage. Preloader shell context for convenience has[0m
|
||||
[38;2;248;248;242m IN_UWSM_ENV_PRELOADER var set to true.[0m
|
||||
|
||||
[38;2;248;248;242m At the stage of graphical-session.target (before it) the main compositor unit wayland-[0m
|
||||
[38;2;248;248;242m wm@${ID}.service and wayland-session-waitenv.service are started.[0m
|
||||
|
||||
[38;2;248;248;242m Compositor should at least put WAYLAND_DISPLAY variable to systemd activation environment. This[0m
|
||||
[38;2;248;248;242m will trigger uwsm's automatic finalization logic. Without WAYLAND_DISPLAY in activation envi‐[0m
|
||||
[38;2;248;248;242m ronment startup will timeout in 10 seconds.[0m
|
||||
|
||||
[38;2;248;248;242m Manual finalization is possible by running "uwsm finalize" (see finalize subcommand section),[0m
|
||||
[38;2;248;248;242m also in combination with tweaking UWSM_WAIT_VARNAMES and UWSM_WAIT_VARNAMES_SETTLETIME vars[0m
|
||||
[38;2;248;248;242m (see Environment vars section).[0m
|
||||
|
||||
[38;2;248;248;242m Successful activation of compositor unit and existence of WAYLAND_DISPLAY in activation envi‐[0m
|
||||
[38;2;248;248;242m ronment will allow graphical-session.target to be declared reached.[0m
|
||||
|
||||
[38;2;248;248;242m Finally, xdg-desktop-autostart.target is activated.[0m
|
||||
|
||||
[38;2;248;248;242m Inside session[0m
|
||||
[38;2;248;248;242m It is highly recommended to configure the compositor or app launcher to launch apps as scopes[0m
|
||||
[38;2;248;248;242m or services in special user session slices (app.slice, background.slice, session.slice). uwsm[0m
|
||||
[38;2;248;248;242m provides custom nested slices for apps to live in and be terminated on session end:[0m
|
||||
[38;2;248;248;242m • app-graphical.slice[0m
|
||||
[38;2;248;248;242m • background-graphical.slice[0m
|
||||
[38;2;248;248;242m • session-graphical.slice[0m
|
||||
|
||||
[38;2;248;248;242m A helper app subcommand is provided to handle all the systemd-run invocations for you (see app[0m
|
||||
[38;2;248;248;242m subcommand section).[0m
|
||||
|
||||
[38;2;248;248;242m The compositor is launched in session.slice by default (as recommended by [0m[38;2;166;226;46msystemd.[0m[38;2;166;226;46mspecial[0m[38;2;249;38;114m([0m[38;2;190;132;255m7[0m[38;2;249;38;114m)[0m[38;2;248;248;242m).[0m
|
||||
|
||||
[38;2;248;248;242m Shutdown[0m
|
||||
[38;2;248;248;242m Can be initiated by either:[0m
|
||||
[38;2;248;248;242m • running uwsm stop[0m
|
||||
[38;2;248;248;242m • stopping wayland-wm@*.service or wayland-session-envelope@*.target[0m
|
||||
[38;2;248;248;242m • starting wayland-session-shutdown.target[0m
|
||||
|
||||
[38;2;248;248;242m Systemd stops all user units in reverse, as it usually does. During deactivation of graphical-[0m
|
||||
[38;2;248;248;242m session-pre.target, the environment preloader service cleans activation environments by unset‐[0m
|
||||
[38;2;248;248;242m ting all variables that were marked for removal during startup and finalization stages.[0m
|
||||
|
||||
[38;2;248;248;242m Do not use compositor's native exit mechanism or kill its process directly.[0m
|
||||
|
||||
[38;2;253;151;31mSUBCOMMANDS[0m
|
||||
[38;2;248;248;242m select[0m
|
||||
[38;2;248;248;242m Selects default wayland session compositor Desktop Entry.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm select[0m
|
||||
|
||||
[38;2;248;248;242m Invokes a whiptail menu to select default session among Desktop Entries in wayland-sessions XDG[0m
|
||||
[38;2;248;248;242m data hierarchy. Writes to ${XDG_CONFIG_HOME}/uwsm/default-id. Nothing else is done. Returns 1[0m
|
||||
[38;2;248;248;242m if selection is cancelled. Can be used for scripting launch condition in shell profile.[0m
|
||||
|
||||
[38;2;248;248;242m check[0m
|
||||
[38;2;248;248;242m Performs tests, returns 0 on success, 1 on failure.[0m
|
||||
|
||||
[38;2;248;248;242m is-active:[0m
|
||||
|
||||
[38;2;248;248;242m uwsm check is-active [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m] [compositor][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-v[0m[38;2;248;248;242m show additional info[0m
|
||||
[38;2;248;248;242m compositor check for specific compositor[0m
|
||||
|
||||
[38;2;248;248;242m Checks if unit of specific compositor or graphical-session*.target in general is in active or[0m
|
||||
[38;2;248;248;242m activating state.[0m
|
||||
|
||||
[38;2;248;248;242m may-start:[0m
|
||||
|
||||
[38;2;248;248;242m uwsm check may-start [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-g[0m[38;2;248;248;242m [S]] [[0m[38;2;166;226;46m-v[0m[38;2;248;248;242m|[0m[38;2;166;226;46m-q[0m[38;2;248;248;242m] [N ...][0m
|
||||
|
||||
[38;2;248;248;242m N ... allowed VT numbers (default: 1)[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m S wait S seconds for graphical.target in queue (default: 60; 0 or less disables[0m
|
||||
[38;2;248;248;242m check).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-i[0m[38;2;248;248;242m do not check for login shell[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m do not check for local session (allow remote session)[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-v[0m[38;2;248;248;242m show all failed tests[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-q[0m[38;2;248;248;242m be quiet[0m
|
||||
|
||||
[38;2;248;248;242m Checks whether it is OK to launch a wayland session via the following conditions:[0m
|
||||
[38;2;248;248;242m • DBUS_SESSION_BUS_ADDRESS is set[0m
|
||||
[38;2;248;248;242m • Running from login shell[0m
|
||||
[38;2;248;248;242m • System is at graphical.target[0m
|
||||
[38;2;248;248;242m • User graphical-session*.target units are not yet active[0m
|
||||
[38;2;248;248;242m • Foreground VT is among allowed (default: 1)[0m
|
||||
[38;2;248;248;242m • Login session's VT is matching[0m
|
||||
|
||||
[38;2;248;248;242m start[0m
|
||||
[38;2;248;248;242m Generates units for given compositor command line or Desktop Entry and starts them.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm start [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-D[0m[38;2;248;248;242m name[:name...]] [[0m[38;2;166;226;46m-a[0m[38;2;248;248;242m|[0m[38;2;166;226;46m-e[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-N[0m[38;2;248;248;242m Name] [[0m[38;2;166;226;46m-C[0m[38;2;248;248;242m Comment] [[0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home}] [[0m[38;2;166;226;46m-t[0m[38;2;248;248;242m][0m
|
||||
[38;2;248;248;242m [[0m[38;2;166;226;46m-o[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-n[0m[38;2;248;248;242m] -- compositor [args ...][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-F[0m[38;2;248;248;242m Hardcode mode, always write command line to unit drop-ins and use full[0m
|
||||
[38;2;248;248;242m paths.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m name[:name...] Names to fill XDG_CURRENT_DESKTOP with (:[0m[38;2;166;226;46m-separated[0m[38;2;248;248;242m). Existing var con‐[0m
|
||||
[38;2;248;248;242m tent is a starting point if no active session is running.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-a[0m[38;2;248;248;242m Append desktop names set by [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m to other sources (default).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-e[0m[38;2;248;248;242m Use desktop names set by [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m exclusively, discard other sources.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-N[0m[38;2;248;248;242m Name Fancy name for compositor (filled from Desktop Entry by default).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-C[0m[38;2;248;248;242m Comment Fancy description for compositor (filled from Desktop Entry by de‐[0m
|
||||
[38;2;248;248;242m fault).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home} Select rung for generated unit files: run: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m/sys‐[0m
|
||||
[38;2;248;248;242m temd/user (default), or home: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m/systemd/user. Permanent[0m
|
||||
[38;2;248;248;242m destination will save some time by removing need for reloading systemd.[0m
|
||||
[38;2;248;248;242m Managed files from other rung will be removed. Can be preset with[0m
|
||||
[38;2;248;248;242m UWSM_UNIT_RUNG environment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m Do not generate (and remove) tweak unit files. Can be preset with[0m
|
||||
[38;2;248;248;242m UWSM_TWEAKS=false environment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-T[0m[38;2;248;248;242m Generate tweak unit files for other software. This is default behavior.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m S Wait for S seconds for system graphical.target in queue and warn if[0m
|
||||
[38;2;248;248;242m timed out or not in queue (default: 60, negative to disable).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-G[0m[38;2;248;248;242m S Wait for S seconds for system graphical.target in queue and abort if[0m
|
||||
[38;2;248;248;242m timed out or not in queue (overrides [0m[38;2;166;226;46m-g[0m[38;2;248;248;242m, default: [0m[38;2;166;226;46m-1[0m[38;2;248;248;242m, (disabled)).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-o[0m[38;2;248;248;242m Only generate units, but do not start.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-n[0m[38;2;248;248;242m Dry run, do not write or start anything.[0m
|
||||
|
||||
[38;2;248;248;242m The first argument of the compositor command line acts as an ID and should be either one of:[0m
|
||||
[38;2;248;248;242m • Executable name[0m
|
||||
[38;2;248;248;242m • Desktop Entry ID (optionally with ":"[0m[38;2;166;226;46m-delimited[0m[38;2;248;248;242m action ID)[0m
|
||||
[38;2;248;248;242m • Special value:[0m
|
||||
[38;2;248;248;242m • select - invoke menu to select compositor.[0m
|
||||
[38;2;248;248;242m • default - run previously selected compositor (or select if no selection was saved).[0m
|
||||
|
||||
[38;2;248;248;242m If given as path, hardcode mode will be used implicitly.[0m
|
||||
|
||||
[38;2;248;248;242m Always use "--" to disambiguate dashed arguments intended for compositor itself.[0m
|
||||
|
||||
[38;2;248;248;242m After units are (re)generated, wayland-session-bindpid@${PID}.service is started, to track the[0m
|
||||
[38;2;248;248;242m PID of invoking uwsm, then uwsm process replaces itself with systemctl execution that starts[0m
|
||||
[38;2;248;248;242m wayland-wm@${ID}.service and waits for it to finish.[0m
|
||||
|
||||
[38;2;248;248;242m In order to complete the startup sequence, the compositor has to put WAYLAND_DISPLAY into the[0m
|
||||
[38;2;248;248;242m systemd activation environment. This can be done explicitly by making compositor run "uwsm fi‐[0m
|
||||
[38;2;248;248;242m nalize" command (see the next subsection).[0m
|
||||
|
||||
[38;2;248;248;242m finalize[0m
|
||||
[38;2;248;248;242m For running by a compositor on startup.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm finalize [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [VAR_NAME ...][0m
|
||||
|
||||
[38;2;248;248;242m Exports WAYLAND_DISPLAY, DISPLAY and any defined vars mentioned by names in arguments or in[0m
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES variable (whitespace-separated). Then sends startup notification for the[0m
|
||||
[38;2;248;248;242m unit to systemd user manager.[0m
|
||||
|
||||
[38;2;248;248;242m This is required if compositor itself does not put WAYLAND_DISPLAY to systemd activation envi‐[0m
|
||||
[38;2;248;248;242m ronment, otherwise wayland-session@.service unit or a dedicated wayland-session-waitenv.service[0m
|
||||
[38;2;248;248;242m unit will terminate due to startup timeout.[0m
|
||||
|
||||
[38;2;248;248;242m UWSM_FINALIZE_VARNAMES variable can be prefilled by plugins.[0m
|
||||
|
||||
[38;2;248;248;242m Direct assignment as VAR_NAME=value is also possible, but recommended only for creating flags[0m
|
||||
[38;2;248;248;242m for UWSM_WAIT_VARNAMES mechanism.[0m
|
||||
|
||||
[38;2;248;248;242m stop[0m
|
||||
[38;2;248;248;242m Stops compositor and optionally removes generated units.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm stop [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-r[0m[38;2;248;248;242m [compositor] [[0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home}] [[0m[38;2;166;226;46m-n[0m[38;2;248;248;242m][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m [compositor] Also remove units (all or only compositor-specific).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-U[0m[38;2;248;248;242m {run|home} Select rung for generated unit files: run: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_RUNTIME_DIR[0m[38;2;248;248;242m/systemd/user[0m
|
||||
[38;2;248;248;242m (default), or home: [0m[38;2;255;255;255m$[0m[38;2;190;132;255mXDG_CONFIG_HOME[0m[38;2;248;248;242m/systemd/user. Permanent destination[0m
|
||||
[38;2;248;248;242m will save some time by removing need for reloading systemd. Managed files[0m
|
||||
[38;2;248;248;242m from other rung will be removed. Can be preset with UWSM_UNIT_RUNG envi‐[0m
|
||||
[38;2;248;248;242m ronment var.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-n[0m[38;2;248;248;242m Dry run, do not stop or remove anything.[0m
|
||||
|
||||
[38;2;248;248;242m app[0m
|
||||
[38;2;248;248;242m Application-to-unit launcher with Desktop Entry support.[0m
|
||||
|
||||
[38;2;248;248;242m uwsm app [[0m[38;2;166;226;46m-h[0m[38;2;248;248;242m] [[0m[38;2;166;226;46m-s[0m[38;2;248;248;242m {a,b,s,custom.slice}] [[0m[38;2;166;226;46m-t[0m[38;2;248;248;242m {scope,service}] [[0m[38;2;166;226;46m-a[0m[38;2;248;248;242m app_name] [[0m[38;2;166;226;46m-u[0m[38;2;248;248;242m unit_name][0m
|
||||
[38;2;248;248;242m [[0m[38;2;166;226;46m-d[0m[38;2;248;248;242m unit_description] [[0m[38;2;166;226;46m-S[0m[38;2;248;248;242m ] [[0m[38;2;166;226;46m-T[0m[38;2;248;248;242m] -- application [args ...][0m
|
||||
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-s[0m[38;2;248;248;242m {a,b,s,custom.slice} Slice selector (default: a):[0m
|
||||
[38;2;248;248;242m a - app-graphical.slice[0m
|
||||
[38;2;248;248;242m b - background-graphical.slice[0m
|
||||
[38;2;248;248;242m s - session-graphical.slice[0m
|
||||
[38;2;248;248;242m any slice by full name[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m {scope,service} Type of unit to launch (default: scope, can be preset by[0m
|
||||
[38;2;248;248;242m UWSM_APP_UNIT_TYPE env var).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-a[0m[38;2;248;248;242m app_name Override app name (a substring in unit name).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-u[0m[38;2;248;248;242m unit_name Override the whole autogenerated unit name.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-d[0m[38;2;248;248;242m unit_description Unit Description.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-p[0m[38;2;248;248;242m Property=value Set additional unit property (option is repeatable).[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-S[0m[38;2;248;248;242m {out,err,both} Silence stdout, stderr, or both.[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46m-T[0m[38;2;248;248;242m Launch app in a terminal. Allows command to be empty to just[0m
|
||||
[38;2;248;248;242m launch a terminal.[0m
|
||||
|
||||
[38;2;248;248;242m Application can be provided as a command with optional arguments, or a Desktop Entry ID, op‐[0m
|
||||
[38;2;248;248;242m tionally suffixed with ":"[0m[38;2;166;226;46m-delimited[0m[38;2;248;248;242m Action ID. If Desktop Entry is being launched, arguments[0m
|
||||
[38;2;248;248;242m should be compatible with it.[0m
|
||||
|
||||
[38;2;248;248;242m Always use "--" to disambiguate dashed arguments intended for application itself.[0m
|
||||
|
||||
[38;2;248;248;242m aux[0m
|
||||
[38;2;248;248;242m For use in systemd user services. Can only be called by systemd user manager.[0m
|
||||
|
||||
[38;2;248;248;242m prepare-env Prepares environment (for use in ExecStart in wayland-wm-env@.service bound to[0m
|
||||
[38;2;248;248;242m wayland-session-pre@.target).[0m
|
||||
[38;2;248;248;242m cleanup-env Cleans up environment (for use ExecStop in in wayland-wm-env@.service bound to[0m
|
||||
[38;2;248;248;242m wayland-session-pre@.target).[0m
|
||||
[38;2;248;248;242m exec Executes a command with arguments or a desktop entry (for use in Exec in wayland-[0m
|
||||
[38;2;248;248;242m wm@.service bound to wayland-session@.target).[0m
|
||||
[38;2;248;248;242m app-daemon Daemon for faster app argument generation, used by uwsm-app client.[0m
|
||||
|
||||
[38;2;253;151;31mAPP DAEMON[0m
|
||||
[38;2;248;248;242m Provided as wayland-wm-app-daemon.service to be started on-demand.[0m
|
||||
|
||||
[38;2;248;248;242m Daemon receives app arguments from ${XDG_RUNTIME_DIR}/uwsm-app-daemon-in pipe. Resulting argu‐[0m
|
||||
[38;2;248;248;242m ments are formatted as shell code and written to ${XDG_RUNTIME_DIR}/uwsm-app-daemon-out pipe.[0m
|
||||
|
||||
[38;2;248;248;242m Arguments are expected to be \0-delimited, leading \0 are stripped. One command is received per[0m
|
||||
[38;2;248;248;242m write+close.[0m
|
||||
|
||||
[38;2;248;248;242m The first argument determines the behavior:[0m
|
||||
|
||||
[38;2;248;248;242m • app the rest is processed the same as in "uwsm app"[0m
|
||||
[38;2;248;248;242m • ping just "pong" is returnedn[0m
|
||||
[38;2;248;248;242m • stop daemon is stoppedn[0m
|
||||
|
||||
[38;2;248;248;242m Single commands are prepended with exec, iterated commands are assembled with trailing & each,[0m
|
||||
[38;2;248;248;242m followed by wait.[0m
|
||||
|
||||
[38;2;248;248;242m The purpose of all this is to skip all the expensive Python startup and import routines that[0m
|
||||
[38;2;248;248;242m slow things down every time "uwsm app" is called. Instead the daemon does it once and then lis‐[0m
|
||||
[38;2;248;248;242m tens for requests, while a simple shell script may dump arguments to one pipe and run the code[0m
|
||||
[38;2;248;248;242m received from another via eval, which is much faster.[0m
|
||||
|
||||
[38;2;248;248;242m The simplest script is:[0m
|
||||
|
||||
[38;2;248;248;242m #!/bin/sh[0m
|
||||
[38;2;248;248;242m printf '0%s' app "$@" > "${XDG_RUNTIME_DIR}/uwsm-app-daemon-in"[0m
|
||||
[38;2;248;248;242m IFS='' read [0m[38;2;166;226;46m-r[0m[38;2;248;248;242m cmd < "${XDG_RUNTIME_DIR}/uwsm-app-daemon-out"[0m
|
||||
[38;2;248;248;242m eval "[0m[38;2;255;255;255m$[0m[38;2;190;132;255mcmd[0m[38;2;248;248;242m"[0m
|
||||
|
||||
[38;2;248;248;242m Provided uwsm-app client script is a bit smarter: it can start the daemon, applies timeouts,[0m
|
||||
[38;2;248;248;242m and supports newlines in returned args.[0m
|
||||
|
||||
[38;2;253;151;31mSHELL PROFILE INTEGRATION[0m
|
||||
[38;2;248;248;242m To launch uwsm automatically on login, add one of constructs below (or similar) to shell pro‐[0m
|
||||
[38;2;248;248;242m file.[0m
|
||||
|
||||
[38;2;248;248;242m This asks to select a compositor (or refuse and continue with login shell) when logged in on VT[0m
|
||||
[38;2;248;248;242m 1:[0m
|
||||
|
||||
[38;2;248;248;242m if uwsm check may-start && uwsm select; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start default[0m
|
||||
[38;2;248;248;242m fi[0m
|
||||
|
||||
[38;2;248;248;242m This just starts a specific compositor depending on foreground VT:[0m
|
||||
|
||||
[38;2;248;248;242m if uwsm check may-start 1; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start sway.desktop[0m
|
||||
[38;2;248;248;242m elif uwsm check may-start 2; then[0m
|
||||
[38;2;248;248;242m exec systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start uwsm start labwc.desktop[0m
|
||||
[38;2;248;248;242m fi[0m
|
||||
|
||||
[38;2;248;248;242m Using "uwsm check may-start" as a condition is essential, not only to prevent accidental[0m
|
||||
[38;2;248;248;242m startup attempts where they are not expected, but also since startup may involve sourcing shell[0m
|
||||
[38;2;248;248;242m profile, which might lead to nasty loops.[0m
|
||||
|
||||
[38;2;248;248;242m See check subcommand section for info on may-start checker.[0m
|
||||
|
||||
[38;2;248;248;242m exec allows uwsm to replace login shell in order to properly bind to user session and handle[0m
|
||||
[38;2;248;248;242m session termination.[0m
|
||||
|
||||
[38;2;248;248;242m "systemd-cat [0m[38;2;166;226;46m-t[0m[38;2;248;248;242m uwsm_start" (optional) executes the command given to it (uwsm) with its stdout[0m
|
||||
[38;2;248;248;242m and stderr connected to the systemd journal, tagged with identifier "uwsm_start". See systemd-[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46mcat[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m for more options.[0m
|
||||
|
||||
[38;2;253;151;31mUSE INSIDE DESKTOP ENTRY[0m
|
||||
[38;2;248;248;242m To launch uwsm from a display/login manager, "uwsm start" can be used inside Desktop Entries.[0m
|
||||
[38;2;248;248;242m Example /usr/local/share/wayland-sessions/my-compositor.desktop:[0m
|
||||
|
||||
[38;2;248;248;242m [Desktop Entry][0m
|
||||
[38;2;248;248;242m Name=My compositor (with UWSM)[0m
|
||||
[38;2;248;248;242m Comment=My cool compositor[0m
|
||||
[38;2;248;248;242m Exec=uwsm start [0m[38;2;166;226;46m-N[0m[38;2;248;248;242m "My compositor" [0m[38;2;166;226;46m-D[0m[38;2;248;248;242m mycompositor [0m[38;2;166;226;46m-C[0m[38;2;248;248;242m "My cool compositor" mywm[0m
|
||||
[38;2;248;248;242m DesktopNames=mycompositor[0m
|
||||
[38;2;248;248;242m Type=Application[0m
|
||||
|
||||
[38;2;248;248;242m Things to keep in mind:[0m
|
||||
|
||||
[38;2;248;248;242m • For consistency, command line arguments should mirror the keys of the entry[0m
|
||||
[38;2;248;248;242m • Command in Exec= should start with "uwsm start"[0m
|
||||
[38;2;248;248;242m • It should not point to itself (as a combination of Desktop Entry ID and Action ID)[0m
|
||||
[38;2;248;248;242m • It should not point to a Desktop Entry ID and Action ID that also uses ‘uwsm‘[0m
|
||||
|
||||
[38;2;248;248;242m Potentially such entries may be found and used by uwsm itself, i.e. in shell profile integra‐[0m
|
||||
[38;2;248;248;242m tion situation, or when launched manually. Following the principles above ensures uwsm will[0m
|
||||
[38;2;248;248;242m properly recognize itself and parse requested arguments inside the entry without any side ef‐[0m
|
||||
[38;2;248;248;242m fects.[0m
|
||||
|
||||
[38;2;253;151;31mSEE ALSO[0m
|
||||
[38;2;248;248;242m [0m[38;2;166;226;46muwsm-plugins[0m[38;2;249;38;114m([0m[38;2;190;132;255m3[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd-run[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd-cat[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m[38;2;248;248;242m, [0m[38;2;166;226;46msystemd.[0m[38;2;166;226;46mspecial[0m[38;2;249;38;114m([0m[38;2;190;132;255m7[0m[38;2;249;38;114m)[0m
|
||||
|
||||
[38;2;248;248;242m 2026-02-14 [0m[38;2;166;226;46mUWSM[0m[38;2;249;38;114m([0m[38;2;190;132;255m1[0m[38;2;249;38;114m)[0m
|
||||
@@ -0,0 +1,5 @@
|
||||
[38;2;248;248;242m{[0m[38;2;255;255;255ma[0m
|
||||
[38;2;248;248;242m [0m[38;2;117;113;94m/*[0m[38;2;117;113;94m comment [0m[38;2;117;113;94m*/[0m
|
||||
[38;2;248;248;242m}[0m
|
||||
|
||||
[38;2;248;248;242m{[0m[38;2;255;255;255ma[0m[38;2;248;248;242m [0m[38;2;117;113;94m/*[0m[38;2;117;113;94m comment [0m[38;2;117;113;94m*/[0m[38;2;248;248;242m}[0m
|
||||
Vendored
File renamed without changes.
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user