mirror of
https://github.com/sharkdp/bat
synced 2026-08-04 19:01:44 +00:00
Compare commits
596 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 25f4f96ea3 | |||
| 5cfd22ee8b | |||
| ae07586fef | |||
| 96e4882b5c | |||
| a0a090c307 | |||
| bc61d84408 | |||
| de8bb79a6f | |||
| 7bf459f0ff | |||
| 18ed69aff4 | |||
| c9431933e5 | |||
| 2d3581e8c4 | |||
| 8453bc93cc | |||
| d1418a9ab0 | |||
| 09bdcde6c5 | |||
| 0d2afe79af | |||
| 5a12f187fa | |||
| 753bfaff49 | |||
| 9744971b19 | |||
| c8f7787281 | |||
| 26f19703d3 | |||
| b7b05a3553 | |||
| caba54efc9 | |||
| e2528dff6d | |||
| ad11fb815f | |||
| 305e8a4d84 | |||
| 7e804b27a0 | |||
| 18d963f423 | |||
| 9d91d22e29 | |||
| 3fa9044029 | |||
| 8a9821fa9a | |||
| 75fadb8d1c | |||
| 63560c4733 | |||
| d660cca8a9 | |||
| be62a4cea4 | |||
| 874448c112 | |||
| 3e07483f7a | |||
| 9859077306 | |||
| 0cde7167d6 | |||
| 7eff8b687c | |||
| 04c7d15084 | |||
| dbe25ba5e6 | |||
| f81d4dc321 | |||
| f1c5c02713 | |||
| 6c25c693a8 | |||
| 4be55ead4d | |||
| 8df326aeed | |||
| f87157d009 | |||
| 0b4fe03ec3 | |||
| 1063264d88 | |||
| 4bee858c78 | |||
| d832367d95 | |||
| d89a20f8b4 | |||
| 41ef01f25d | |||
| 7b797fd830 | |||
| d27064cea5 | |||
| c9aa8d59ef | |||
| 57a8b10883 | |||
| e608b33142 | |||
| 6598442d41 | |||
| 822eff6028 | |||
| da4defad07 | |||
| 9bed8adb16 | |||
| 2e836e9822 | |||
| 07554b0c1d | |||
| 022574314f | |||
| 8a4701f93f | |||
| 08047a6a2e | |||
| 2caeaef14b | |||
| d00e05643f | |||
| 60693db73f | |||
| 15f2553a3d | |||
| dbe352d8b8 | |||
| 517ddd8e00 | |||
| 558b487c91 | |||
| f8f12a7db5 | |||
| d725443116 | |||
| dbaa0a6d9e | |||
| 6b2c5645d2 | |||
| e7bef716c9 | |||
| c627526f8c | |||
| 95cb705ac0 | |||
| ca43c7a8f8 | |||
| 0a8bfc52a6 | |||
| d0ae2e6826 | |||
| 9090fb75e0 | |||
| 50fa25db60 | |||
| bd3c49d234 | |||
| ce7a3d9f7d | |||
| 3b71837174 | |||
| 2be3a14a7e | |||
| 8d82402d74 | |||
| 1942d40863 | |||
| 5042844342 | |||
| 649fb05c58 | |||
| c533a4aa14 | |||
| b93a41a9a3 | |||
| 937c59fca9 | |||
| a80d5ea9f7 | |||
| c11a7d9e41 | |||
| ac082ab64b | |||
| c9fd0f3cf0 | |||
| e667415def | |||
| fd6c7637e4 | |||
| 9a816c9c68 | |||
| 431344ba67 | |||
| eca6b8a376 | |||
| b01ed8d773 | |||
| 3e5f4266ce | |||
| 61c9f312c9 | |||
| c59dad0cae | |||
| 16a79d38c2 | |||
| 9d635627dc | |||
| bc69ffb0f0 | |||
| a0226f5670 | |||
| ed7789d9b7 | |||
| b747184788 | |||
| 02ae6ef348 | |||
| 0ebb9cbfe2 | |||
| f6cbee9e27 | |||
| 10e823c4b7 | |||
| 60e4027332 | |||
| e075fee5bf | |||
| 16d9b99f6c | |||
| ffa74a7f49 | |||
| c1772b7793 | |||
| 5be9d19500 | |||
| 0603f0b844 | |||
| 50958472e5 | |||
| 89ce060183 | |||
| bc42149a72 | |||
| 0c3b22e0f0 | |||
| a769a3d813 | |||
| 7cc231b82b | |||
| 7f089ead62 | |||
| b9b981f657 | |||
| abf9dada04 | |||
| 5c6974703e | |||
| 1b0a6da4be | |||
| 06b645435a | |||
| c3b190d45b | |||
| 594b1417f1 | |||
| e8ca6ec7c3 | |||
| 6498615f5f | |||
| 30b0143ccf | |||
| ff81cfd584 | |||
| 14ce668a1d | |||
| 9a1bfe946d | |||
| cea45e05f3 | |||
| cda363a3f7 | |||
| de796392cf | |||
| 1423dd9440 | |||
| b662fec214 | |||
| d8d0b77568 | |||
| 13317b0a3b | |||
| 2616296183 | |||
| 39684b85ad | |||
| aa3ec109b7 | |||
| 180a77ee99 | |||
| 93b25d75a0 | |||
| 6e91ba83b7 | |||
| b74c125c43 | |||
| fd1e0d5876 | |||
| 9a650e8279 | |||
| d5aa9d8e05 | |||
| b1cdc06430 | |||
| 83a15ac05f | |||
| 43a77a42f8 | |||
| 44ca1e7df1 | |||
| 23fd200482 | |||
| 9f1c62b013 | |||
| 319b8868fc | |||
| 7bd6cdbebc | |||
| 1488a4a006 | |||
| 56d2823b0e | |||
| 1a11ba11e3 | |||
| c34bdb5e66 | |||
| 10456e1d51 | |||
| bbdadc7e38 | |||
| ff71e32c0f | |||
| 50b01ea8da | |||
| de697f7717 | |||
| 575457c944 | |||
| 6fc58821a5 | |||
| 7873b473e8 | |||
| 609005a225 | |||
| b9c74ab859 | |||
| cee5584daf | |||
| b265b20721 | |||
| a7a9727c11 | |||
| 90dfa7f18d | |||
| 3f8d62e7d6 | |||
| 3f22311ec8 | |||
| 9e8176b1c6 | |||
| 70ff93d238 | |||
| c264ecd26b | |||
| 10a1b24191 | |||
| 243819ecdc | |||
| 9c76b72825 | |||
| b4fe182960 | |||
| b7e44c76dc | |||
| f3cc69733f | |||
| 3625f0ea1c | |||
| e6e2d4c65d | |||
| 340e873eff | |||
| 3407bf4bf6 | |||
| 25cd4991d2 | |||
| 503b2c5126 | |||
| 026bc05d70 | |||
| 61005f19fa | |||
| 8f8c953ab6 | |||
| f82487daf8 | |||
| 3a9d574770 | |||
| b4e3a84e1a | |||
| f7c39e8353 | |||
| 37d9f0533c | |||
| d560f2a515 | |||
| bb4d1cbd2e | |||
| 23ec433167 | |||
| 9eaed3e3f0 | |||
| d5bd4aa93f | |||
| 66b70dd8ed | |||
| 01731478a6 | |||
| f8c5429a6c | |||
| f71226adbb | |||
| e8d777b73a | |||
| 3cff44b652 | |||
| 26302a8b08 | |||
| adc5bd0402 | |||
| e3c3be950a | |||
| 8d92dc2083 | |||
| a1f85b9e06 | |||
| 424c02dfa7 | |||
| 018a482621 | |||
| 4790def1ef | |||
| 07c26adc35 | |||
| f29f9387b5 | |||
| c290bfff1e | |||
| 42153f2b99 | |||
| 6d7537d3ec | |||
| b30ec9f975 | |||
| a7074f10d4 | |||
| d185f0973b | |||
| 071874ea8f | |||
| 46a2c004a2 | |||
| 26ac179548 | |||
| 4c85483486 | |||
| 487bed2d95 | |||
| 6f69682552 | |||
| bc5beaec5d | |||
| 83b00bc653 | |||
| f041ff8c5f | |||
| 1fbdbfc4b2 | |||
| 2323aa0def | |||
| 6c2ce63101 | |||
| 13204c46e2 | |||
| 9bb0271e7d | |||
| 0e4e10edb6 | |||
| 0c7e5299bf | |||
| c36ed32816 | |||
| e1a3fc5529 | |||
| 1ae9e843ed | |||
| dbe4cfb763 | |||
| 4549f83689 | |||
| e6e8f847be | |||
| b9e249f782 | |||
| 3ffa3648cf | |||
| 5c2cc53882 | |||
| a6f01af8de | |||
| 85a549e293 | |||
| b718889ba2 | |||
| 708c74f6af | |||
| 74d666f5c0 | |||
| 7604fe5567 | |||
| 0080b043c4 | |||
| c7bce46622 | |||
| 2b4339663c | |||
| 6a6b02117b | |||
| 511cd30105 | |||
| 92915e22e7 | |||
| d499191b0a | |||
| 152d69fe98 | |||
| 81aa24310c | |||
| 75cdabaf13 | |||
| 1f628203e5 | |||
| 1b9fc1d5af | |||
| bc1ca1a346 | |||
| f735120978 | |||
| 25b5a41189 | |||
| c94cf4e14e | |||
| 84d80eebd0 | |||
| 915dd9fbf8 | |||
| 9d77c1373c | |||
| c3f2ddf509 | |||
| 8a51172b11 | |||
| 875046e4cd | |||
| a5bd9f51be | |||
| 5a2a20af42 | |||
| 61029c8bd2 | |||
| 1023399c5e | |||
| 6549e26f5d | |||
| 165c495e75 | |||
| 6b9b085be3 | |||
| 2d46d54ae3 | |||
| 3d04699710 | |||
| 054421268f | |||
| 414403b062 | |||
| c29bf2ff28 | |||
| ab4e5ed52e | |||
| 1a54c9bf6d | |||
| 02077db53e | |||
| 7ce010d9ed | |||
| 95993cf37e | |||
| 3761df9112 | |||
| adfaef19da | |||
| f7bea6de5b | |||
| 65aae5d0a1 | |||
| e3866b1f7e | |||
| 23de8e093b | |||
| 196a4cb18f | |||
| 695cf1f387 | |||
| 0af1df5258 | |||
| a8d07333e9 | |||
| 7f12989127 | |||
| 60e32cf823 | |||
| e9a6aaa30f | |||
| 9be2a36a01 | |||
| 22254936a2 | |||
| f6d76e0104 | |||
| c911829771 | |||
| b33e33fe26 | |||
| 9239b125b1 | |||
| 2086cd2668 | |||
| 1b88267320 | |||
| e586751208 | |||
| e7256a624b | |||
| 5c1f47359e | |||
| 45ee2dc4c7 | |||
| db66e4459b | |||
| 55e02e101d | |||
| 230abfd2bc | |||
| c0f2d6f934 | |||
| 9f36a7a284 | |||
| e4d637a3d8 | |||
| 98a2b6bc17 | |||
| 8e66bc8722 | |||
| cd81c7fa6b | |||
| b4fdb5dc36 | |||
| c76ed99db2 | |||
| 06aef22943 | |||
| 128b0d6dd3 | |||
| 15dc20109f | |||
| 5c4bcd6611 | |||
| ecf4029dc7 | |||
| c261b41578 | |||
| 6f1cc80d68 | |||
| 3b0ade9cb8 | |||
| 57016f4e04 | |||
| 497342fabb | |||
| bf56cd90f0 | |||
| 0acb979e9e | |||
| d7503bfc09 | |||
| b89dc15be1 | |||
| 15ab4478c9 | |||
| 5b4ce684a1 | |||
| 0027055a83 | |||
| 321b3ec81b | |||
| 1679460f42 | |||
| 907af9e35f | |||
| 12b74dfb4e | |||
| fd84e4f49f | |||
| f0a6fe216d | |||
| d792dc5804 | |||
| 8a08025091 | |||
| 586c804b1e | |||
| e30161ac3c | |||
| 3865908439 | |||
| 9474b4cf8b | |||
| b48bda21a3 | |||
| daf33709a0 | |||
| 36073a3d95 | |||
| 12fa2cb1eb | |||
| 1f10d846a3 | |||
| 22531eab90 | |||
| 0c1b80faab | |||
| 2c9bf229e1 | |||
| 822e81bb24 | |||
| ad628c0471 | |||
| f483d2df42 | |||
| 4ad3002543 | |||
| cfd622d6e1 | |||
| 1c7c9a6b6d | |||
| 0c93ca80f4 | |||
| de6d418d42 | |||
| c016b462c0 | |||
| 7e1fbcfe95 | |||
| 4815b6155e | |||
| 075b5b288a | |||
| 7cfd1e0d78 | |||
| 9f7d70f642 | |||
| 0fea82cff9 | |||
| 64840fbbae | |||
| 827b3eca2f | |||
| 9478d2dfe8 | |||
| d24501ab5e | |||
| 9f4259721a | |||
| 77e491161c | |||
| 97780f987e | |||
| d1bc0ef0d4 | |||
| 52f94b4623 | |||
| 37fd050100 | |||
| 83286975ff | |||
| f705fcb984 | |||
| 9ca1f20f43 | |||
| 6ad800e43a | |||
| 069318b139 | |||
| b9b554248d | |||
| 4863d428dd | |||
| 2e103ee6b3 | |||
| 28990bc451 | |||
| 748e2a681f | |||
| bfa0b5241f | |||
| 4af4bfc0f1 | |||
| 51203ff750 | |||
| 96cef9a24e | |||
| b43d31b75a | |||
| ad3ff26960 | |||
| 86b40993c3 | |||
| 31bed250ba | |||
| 7658334645 | |||
| 491ae70aa9 | |||
| d64c568196 | |||
| b5982a6174 | |||
| 04e7d2a313 | |||
| bcc2de86b4 | |||
| 1296aea836 | |||
| 5498c24c33 | |||
| 79a03b4299 | |||
| f3a5e9a73c | |||
| 2710a19ecb | |||
| 6d0ef259f6 | |||
| b1577cc083 | |||
| 28d947fd8b | |||
| b000db8f32 | |||
| 116a6cc9a8 | |||
| c8291a36b7 | |||
| 8180c76890 | |||
| a0f33b1cdc | |||
| 8b60dae81c | |||
| 4b33093f9e | |||
| 3d87b25b19 | |||
| f2f6902279 | |||
| c0b17e73e1 | |||
| 94544d963b | |||
| 72abbd22de | |||
| 64e10ffb21 | |||
| 35d8146bba | |||
| a5a7ede698 | |||
| b551049706 | |||
| 99cfc13eab | |||
| 4b0b5afa13 | |||
| d343428441 | |||
| 16e409ec87 | |||
| 94d059f258 | |||
| c8b9de889d | |||
| 75340d54f9 | |||
| b28383e0fa | |||
| 8e866db281 | |||
| 0eb157e090 | |||
| 85636c28bc | |||
| a70e5c6c65 | |||
| 32e01f740b | |||
| 7b20f8fc7b | |||
| 86ac48d68e | |||
| c42fc810ea | |||
| 6baebd79fa | |||
| c6cae09f99 | |||
| fc9546854f | |||
| 32da58e723 | |||
| 63aaa03013 | |||
| fbe9b6f15f | |||
| c946ddb012 | |||
| e5c81ed41e | |||
| 0937d575fd | |||
| 4382be5e7c | |||
| d89df18de2 | |||
| 9fcc57e496 | |||
| 8e35a56712 | |||
| 9f795dd947 | |||
| 721f956c1c | |||
| db4cdc1cb8 | |||
| e455f93a1e | |||
| d756d6f1ed | |||
| 6568fb775d | |||
| d376a4b631 | |||
| 6e5fd36882 | |||
| c7cc4b5c59 | |||
| db76091a53 | |||
| 506fe65474 | |||
| 69c326fd41 | |||
| 466b275e87 | |||
| 319c675f3e | |||
| 5a240f36b9 | |||
| e2bf85e749 | |||
| fe73010a5e | |||
| 150c04acb6 | |||
| 433176ab54 | |||
| b56021ffa9 | |||
| ac2953c070 | |||
| d6e11b59e7 | |||
| f49278cfaa | |||
| 9b73eda957 | |||
| ed99a3a104 | |||
| e32ad0b048 | |||
| 3abc0c0fc4 | |||
| b06f13f0c5 | |||
| a721cc31f3 | |||
| 1e52785d35 | |||
| 9ba3b6ed13 | |||
| 72e1a40bab | |||
| 33a89410d4 | |||
| b2c28cdddb | |||
| 906e774372 | |||
| d929becefc | |||
| 103a2f0d9b | |||
| d33b2e158e | |||
| d295b3a394 | |||
| 81a217b1f3 | |||
| bb17f74757 | |||
| 75928d20f8 | |||
| 2cc45eea1a | |||
| 7dbc6e3da4 | |||
| f34b852aa8 | |||
| 4b04f901fe | |||
| 26111950b3 | |||
| f21d3502e1 | |||
| 5687af8bc7 | |||
| 0f5f51f9ba | |||
| 32c0e1fb45 | |||
| 34440f1b0c | |||
| f1d6cc7a64 | |||
| 628ba5d31f | |||
| e24150ca3f | |||
| d580445aef | |||
| 48dc2b0d90 | |||
| 82664e3c82 | |||
| 1893405cbf | |||
| 00b7e3b864 | |||
| 7826e506f8 | |||
| 6dd8b28b0d | |||
| b720af8a06 | |||
| b1fbcc8a6e | |||
| 9bf74d687f | |||
| 783d4877e1 | |||
| 8b187e6997 | |||
| c46c53320b | |||
| 7f884fa582 | |||
| 8676bbf97f | |||
| 77069b368c | |||
| 70e49bd32b | |||
| 5042f969e5 | |||
| 876f5d4113 | |||
| 39d9d64e1e | |||
| 8e866babfc | |||
| f8c700027e | |||
| 7639e8b406 | |||
| 149dec6953 | |||
| b420c42aff | |||
| 3da7adb763 | |||
| 165e5da6b9 | |||
| c5a9fdff93 | |||
| 8aac5acc0a | |||
| 7162e38d50 | |||
| 63ac60a3a0 | |||
| 9683bfdb3f | |||
| e155f56484 | |||
| e828d7840a | |||
| 52f7a5a06f | |||
| baa1476d8d | |||
| 6be56972b6 | |||
| 08445eba91 | |||
| 2298422e85 | |||
| c5731b9079 | |||
| 458e818ab4 | |||
| 993b33b5b4 | |||
| ba95955fe6 | |||
| 57cc0d8435 | |||
| 60a9ac041f | |||
| 33997106da | |||
| 1b2c69aee8 | |||
| 87e87a349a | |||
| e49235e119 | |||
| 1712ec9e73 | |||
| 3ca8c43a67 | |||
| 01f1b1efe8 | |||
| c7905a6029 | |||
| d27aaffead |
@@ -0,0 +1,2 @@
|
|||||||
|
[advisories]
|
||||||
|
ignore = ["RUSTSEC-2024-0320", "RUSTSEC-2024-0421"]
|
||||||
@@ -26,4 +26,4 @@ guidelines for adding new syntaxes:
|
|||||||
[Name or description of the syntax/language here]
|
[Name or description of the syntax/language here]
|
||||||
|
|
||||||
**Guideline Criteria:**
|
**Guideline Criteria:**
|
||||||
[packagecontro.io link here]
|
[packagecontrol.io link here]
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# This workflow triggers auto-merge of any PR that dependabot creates so that
|
||||||
|
# PRs will be merged automatically without maintainer intervention if CI passes
|
||||||
|
name: Auto-merge dependabot PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-merge:
|
||||||
|
if: github.repository == 'sharkdp/bat' && startsWith(github.head_ref, 'dependabot/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: auto-merge
|
||||||
|
url: https://github.com/sharkdp/bat/blob/main/.github/workflows/Auto-merge-dependabot-PRs.yml
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.AUTO_MERGE_GITHUB_TOKEN }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: |
|
||||||
|
gh pr review ${{ github.event.pull_request.number }} --comment --body "If CI passes, this dependabot PR will be [auto-merged](https://github.com/sharkdp/bat/blob/main/.github/workflows/Auto-merge-dependabot-PRs.yml) 🚀"
|
||||||
|
- run: |
|
||||||
|
gh pr merge --auto --squash ${{ github.event.pull_request.number }}
|
||||||
+127
-81
@@ -2,6 +2,7 @@ name: CICD
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
CICD_INTERMEDIATES_DIR: "_cicd-intermediates"
|
||||||
|
MSRV_FEATURES: --no-default-features --features minimal-application,bugreport,build-assets
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -13,6 +14,43 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
all-jobs:
|
||||||
|
if: always() # Otherwise this job is skipped if the matrix job fails
|
||||||
|
name: all-jobs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- crate_metadata
|
||||||
|
- ensure_cargo_fmt
|
||||||
|
- min_version
|
||||||
|
- license_checks
|
||||||
|
- test_with_new_syntaxes_and_themes
|
||||||
|
- test_with_system_config
|
||||||
|
- documentation
|
||||||
|
- cargo-audit
|
||||||
|
- build
|
||||||
|
steps:
|
||||||
|
- run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}'
|
||||||
|
|
||||||
|
crate_metadata:
|
||||||
|
name: Extract crate metadata
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Extract crate information
|
||||||
|
id: crate_metadata
|
||||||
|
run: |
|
||||||
|
cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT
|
||||||
|
cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT
|
||||||
|
cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT
|
||||||
|
cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT
|
||||||
|
cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT
|
||||||
|
outputs:
|
||||||
|
name: ${{ steps.crate_metadata.outputs.name }}
|
||||||
|
version: ${{ steps.crate_metadata.outputs.version }}
|
||||||
|
maintainer: ${{ steps.crate_metadata.outputs.maintainer }}
|
||||||
|
homepage: ${{ steps.crate_metadata.outputs.homepage }}
|
||||||
|
msrv: ${{ steps.crate_metadata.outputs.msrv }}
|
||||||
|
|
||||||
ensure_cargo_fmt:
|
ensure_cargo_fmt:
|
||||||
name: Ensure 'cargo fmt' has been run
|
name: Ensure 'cargo fmt' has been run
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
@@ -20,46 +58,42 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- run: cargo fmt -- --check
|
- run: cargo fmt -- --check
|
||||||
|
|
||||||
license_checks:
|
|
||||||
name: License checks
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: true # we especially want to perform license checks on submodules
|
|
||||||
- run: tests/scripts/license-checks.sh
|
|
||||||
|
|
||||||
min_version:
|
min_version:
|
||||||
name: Minimum supported rust version
|
name: Minimum supported rust version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
env:
|
needs: crate_metadata
|
||||||
MSRV_FEATURES: --no-default-features --features minimal-application,bugreport,build-assets
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Get the MSRV from the package metadata
|
- name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})
|
||||||
id: msrv
|
|
||||||
run: cargo metadata --no-deps --format-version 1 | jq -r '"version=" + (.packages[] | select(.name == "bat")).rust_version' >> $GITHUB_OUTPUT
|
|
||||||
- name: Install rust toolchain (v${{ steps.msrv.outputs.version }})
|
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ steps.msrv.outputs.version }}
|
toolchain: ${{ needs.crate_metadata.outputs.msrv }}
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
|
||||||
run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}
|
run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --locked ${{ env.MSRV_FEATURES }}
|
run: cargo test --locked ${{ env.MSRV_FEATURES }}
|
||||||
|
|
||||||
|
license_checks:
|
||||||
|
name: License checks
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true # we especially want to perform license checks on submodules
|
||||||
|
- run: tests/scripts/license-checks.sh
|
||||||
|
|
||||||
test_with_new_syntaxes_and_themes:
|
test_with_new_syntaxes_and_themes:
|
||||||
name: Run tests with updated syntaxes and themes
|
name: Run tests with updated syntaxes and themes
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Git checkout
|
- name: Git checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true # we need all syntax and theme submodules
|
submodules: true # we need all syntax and theme submodules
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
@@ -88,7 +122,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Git checkout
|
- name: Git checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Prepare environment variables
|
- name: Prepare environment variables
|
||||||
run: |
|
run: |
|
||||||
echo "BAT_SYSTEM_CONFIG_PREFIX=$GITHUB_WORKSPACE/tests/examples/system_config" >> $GITHUB_ENV
|
echo "BAT_SYSTEM_CONFIG_PREFIX=$GITHUB_WORKSPACE/tests/examples/system_config" >> $GITHUB_ENV
|
||||||
@@ -104,7 +138,7 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- name: Git checkout
|
- name: Git checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Check documentation
|
- name: Check documentation
|
||||||
@@ -114,29 +148,40 @@ jobs:
|
|||||||
- name: Show man page
|
- name: Show man page
|
||||||
run: man $(find . -name bat.1)
|
run: man $(find . -name bat.1)
|
||||||
|
|
||||||
|
cargo-audit:
|
||||||
|
name: cargo audit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: cargo install cargo-audit --locked
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: cargo audit
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
needs: crate_metadata
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: aarch64-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: arm64, use-cross: true }
|
||||||
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
|
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: arm64, use-cross: true }
|
||||||
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, dpkg_arch: armhf, use-cross: true }
|
||||||
- { target: i686-pc-windows-msvc , os: windows-2019 }
|
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, dpkg_arch: musl-linux-armhf, use-cross: true }
|
||||||
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: i686-pc-windows-msvc , os: windows-2019, }
|
||||||
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: i686, use-cross: true }
|
||||||
- { target: x86_64-apple-darwin , os: macos-10.15 }
|
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-i686, use-cross: true }
|
||||||
- { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
- { target: x86_64-apple-darwin , os: macos-13, }
|
||||||
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
|
- { target: aarch64-apple-darwin , os: macos-14, }
|
||||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: x86_64-pc-windows-gnu , os: windows-2019, }
|
||||||
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
- { target: x86_64-pc-windows-msvc , os: windows-2019, }
|
||||||
|
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, dpkg_arch: amd64, use-cross: true }
|
||||||
|
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, dpkg_arch: musl-linux-amd64, use-cross: true }
|
||||||
env:
|
env:
|
||||||
BUILD_CMD: cargo
|
BUILD_CMD: cargo
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install prerequisites
|
- name: Install prerequisites
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -146,14 +191,6 @@ jobs:
|
|||||||
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
- name: Extract crate information
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
|
|
||||||
echo "PROJECT_VERSION=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV
|
|
||||||
echo "PROJECT_MAINTAINER=$(sed -n 's/^authors = \["\(.*\)"\]/\1/p' Cargo.toml)" >> $GITHUB_ENV
|
|
||||||
echo "PROJECT_HOMEPAGE=$(sed -n 's/^homepage = "\(.*\)"/\1/p' Cargo.toml)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -184,7 +221,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}
|
run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}
|
||||||
|
|
||||||
- name: Set bin name & path
|
- name: Set binary name & path
|
||||||
id: bin
|
id: bin
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -195,10 +232,10 @@ jobs:
|
|||||||
esac;
|
esac;
|
||||||
|
|
||||||
# Setup paths
|
# Setup paths
|
||||||
BIN_NAME="${{ env.PROJECT_NAME }}${EXE_suffix}"
|
BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}"
|
||||||
BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}"
|
BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}"
|
||||||
|
|
||||||
# Let subsequent steps know where to find the bin
|
# Let subsequent steps know where to find the binary
|
||||||
echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
|
echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT
|
||||||
echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT
|
echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -208,12 +245,18 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# test only library unit tests and binary for arm-type targets
|
# test only library unit tests and binary for arm-type targets
|
||||||
unset CARGO_TEST_OPTIONS
|
unset CARGO_TEST_OPTIONS
|
||||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${PROJECT_NAME}" ;; esac;
|
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
|
||||||
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
|
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
shell: bash
|
||||||
run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
run: |
|
||||||
|
if [[ ${{ matrix.job.os }} = windows-* ]]
|
||||||
|
then
|
||||||
|
powershell.exe -command "$BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}"
|
||||||
|
else
|
||||||
|
$BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run bat
|
- name: Run bat
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -248,7 +291,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
|
PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac;
|
||||||
PKG_BASENAME=${PROJECT_NAME}-v${PROJECT_VERSION}-${{ matrix.job.target }}
|
PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }}
|
||||||
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
|
PKG_NAME=${PKG_BASENAME}${PKG_suffix}
|
||||||
echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT
|
echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -260,17 +303,17 @@ jobs:
|
|||||||
# Binary
|
# Binary
|
||||||
cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
|
cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR"
|
||||||
|
|
||||||
# Man page
|
|
||||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/manual/bat.1 "$ARCHIVE_DIR"
|
|
||||||
|
|
||||||
# README, LICENSE and CHANGELOG files
|
# README, LICENSE and CHANGELOG files
|
||||||
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
|
cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR"
|
||||||
|
|
||||||
|
# Man page
|
||||||
|
cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/manual/bat.1 "$ARCHIVE_DIR"
|
||||||
|
|
||||||
# Autocompletion files
|
# Autocompletion files
|
||||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.bash "$ARCHIVE_DIR/autocomplete/${{ env.PROJECT_NAME }}.bash"
|
cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.bash "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.bash"
|
||||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.fish "$ARCHIVE_DIR/autocomplete/${{ env.PROJECT_NAME }}.fish"
|
cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.fish "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.fish"
|
||||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/_bat.ps1 "$ARCHIVE_DIR/autocomplete/_${{ env.PROJECT_NAME }}.ps1"
|
cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/_bat.ps1 "$ARCHIVE_DIR/autocomplete/_${{ needs.crate_metadata.outputs.name }}.ps1"
|
||||||
cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.zsh "$ARCHIVE_DIR/autocomplete/${{ env.PROJECT_NAME }}.zsh"
|
cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.zsh "$ARCHIVE_DIR/autocomplete/${{ needs.crate_metadata.outputs.name }}.zsh"
|
||||||
|
|
||||||
# base compressed package
|
# base compressed package
|
||||||
pushd "${PKG_STAGING}/" >/dev/null
|
pushd "${PKG_STAGING}/" >/dev/null
|
||||||
@@ -293,20 +336,11 @@ jobs:
|
|||||||
DPKG_DIR="${DPKG_STAGING}/dpkg"
|
DPKG_DIR="${DPKG_STAGING}/dpkg"
|
||||||
mkdir -p "${DPKG_DIR}"
|
mkdir -p "${DPKG_DIR}"
|
||||||
|
|
||||||
DPKG_BASENAME=${PROJECT_NAME}
|
DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}
|
||||||
DPKG_CONFLICTS=${PROJECT_NAME}-musl
|
DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl
|
||||||
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${PROJECT_NAME}-musl ; DPKG_CONFLICTS=${PROJECT_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=${PROJECT_VERSION}
|
DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}
|
||||||
|
DPKG_ARCH="${{ matrix.job.dpkg_arch }}"
|
||||||
unset DPKG_ARCH
|
|
||||||
case ${{ matrix.job.target }} in
|
|
||||||
aarch64-*-linux-*) DPKG_ARCH=arm64 ;;
|
|
||||||
arm-*-linux-*hf) DPKG_ARCH=armhf ;;
|
|
||||||
i686-*-linux-*) DPKG_ARCH=i686 ;;
|
|
||||||
x86_64-*-linux-*) DPKG_ARCH=amd64 ;;
|
|
||||||
*) DPKG_ARCH=notset ;;
|
|
||||||
esac;
|
|
||||||
|
|
||||||
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
|
DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb"
|
||||||
echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT
|
echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
@@ -314,13 +348,13 @@ jobs:
|
|||||||
install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}"
|
install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}"
|
||||||
|
|
||||||
# Man page
|
# Man page
|
||||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/manual/bat.1 "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
|
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/manual/bat.1 "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1"
|
||||||
gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1"
|
gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1"
|
||||||
|
|
||||||
# Autocompletion files
|
# Autocompletion files
|
||||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.bash "${DPKG_DIR}/usr/share/bash-completion/completions/${{ env.PROJECT_NAME }}"
|
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.bash "${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}"
|
||||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.fish "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish"
|
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.fish "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish"
|
||||||
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.zsh "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}"
|
install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'-*/out/assets/completions/bat.zsh "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}"
|
||||||
|
|
||||||
# README and LICENSE
|
# README and LICENSE
|
||||||
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
|
install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md"
|
||||||
@@ -331,12 +365,12 @@ jobs:
|
|||||||
|
|
||||||
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
|
cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" <<EOF
|
||||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: ${{ env.PROJECT_NAME }}
|
Upstream-Name: ${{ needs.crate_metadata.outputs.name }}
|
||||||
Source: ${{ env.PROJECT_HOMEPAGE }}
|
Source: ${{ needs.crate_metadata.outputs.homepage }}
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: ${{ env.PROJECT_MAINTAINER }}
|
Copyright: ${{ needs.crate_metadata.outputs.maintainer }}
|
||||||
Copyright: $COPYRIGHT_YEARS ${{ env.PROJECT_MAINTAINER }}
|
Copyright: $COPYRIGHT_YEARS ${{ needs.crate_metadata.outputs.maintainer }}
|
||||||
License: Apache-2.0 or MIT
|
License: Apache-2.0 or MIT
|
||||||
|
|
||||||
License: Apache-2.0
|
License: Apache-2.0
|
||||||
@@ -377,10 +411,10 @@ jobs:
|
|||||||
Version: ${DPKG_VERSION}
|
Version: ${DPKG_VERSION}
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: ${{ env.PROJECT_MAINTAINER }}
|
Maintainer: ${{ needs.crate_metadata.outputs.maintainer }}
|
||||||
Homepage: ${{ env.PROJECT_HOMEPAGE }}
|
Homepage: ${{ needs.crate_metadata.outputs.homepage }}
|
||||||
Architecture: ${DPKG_ARCH}
|
Architecture: ${DPKG_ARCH}
|
||||||
Provides: ${{ env.PROJECT_NAME }}
|
Provides: ${{ needs.crate_metadata.outputs.name }}
|
||||||
Conflicts: ${DPKG_CONFLICTS}
|
Conflicts: ${DPKG_CONFLICTS}
|
||||||
Description: cat(1) clone with wings.
|
Description: cat(1) clone with wings.
|
||||||
A cat(1) clone with syntax highlighting and Git integration.
|
A cat(1) clone with syntax highlighting and Git integration.
|
||||||
@@ -413,7 +447,7 @@ jobs:
|
|||||||
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
|
echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Publish archives and packages
|
- name: Publish archives and packages
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v2
|
||||||
if: steps.is-release.outputs.IS_RELEASE
|
if: steps.is-release.outputs.IS_RELEASE
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
@@ -421,3 +455,15 @@ jobs:
|
|||||||
${{ steps.debian-package.outputs.DPKG_PATH }}
|
${{ steps.debian-package.outputs.DPKG_PATH }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
winget:
|
||||||
|
name: Publish to Winget
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
steps:
|
||||||
|
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||||
|
with:
|
||||||
|
identifier: sharkdp.bat
|
||||||
|
installers-regex: '-pc-windows-msvc\.zip$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Changelog
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-changelog:
|
||||||
|
name: Check for changelog entry
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# dependabot PRs are automerged if CI passes; we shouldn't block these
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
env:
|
||||||
|
PR_NUMBER: ${{ github.event.number }}
|
||||||
|
PR_BASE: ${{ github.base_ref }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Fetch PR base
|
||||||
|
run: git fetch --no-tags --prune --depth=1 origin
|
||||||
|
|
||||||
|
# cannot use `github.actor`: the triggering commit may be authored by a maintainer
|
||||||
|
- name: Get PR submitter
|
||||||
|
id: get-submitter
|
||||||
|
run: curl -sSfL https://api.github.com/repos/sharkdp/bat/pulls/${PR_NUMBER} | jq -r '"submitter=" + .user.login' | tee -a $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Search for added line in changelog
|
||||||
|
env:
|
||||||
|
PR_SUBMITTER: ${{ steps.get-submitter.outputs.submitter }}
|
||||||
|
run: |
|
||||||
|
ADDED=$(git diff -U0 "origin/${PR_BASE}" HEAD -- CHANGELOG.md | grep -P '^\+[^\+].+$')
|
||||||
|
echo "Added lines in CHANGELOG.md:"
|
||||||
|
echo "$ADDED"
|
||||||
|
echo "Grepping for PR info (see CONTRIBUTING.md):"
|
||||||
|
grep "#${PR_NUMBER}\\b.*@${PR_SUBMITTER}\\b" <<< "$ADDED"
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
||||||
# Generated files
|
# Generated files
|
||||||
|
/assets/completions/_bat.ps1
|
||||||
/assets/completions/bat.bash
|
/assets/completions/bat.bash
|
||||||
/assets/completions/bat.fish
|
/assets/completions/bat.fish
|
||||||
/assets/completions/bat.zsh
|
/assets/completions/bat.zsh
|
||||||
|
|||||||
@@ -254,3 +254,12 @@
|
|||||||
[submodule "assets/syntaxes/02_Extra/Crontab"]
|
[submodule "assets/syntaxes/02_Extra/Crontab"]
|
||||||
path = assets/syntaxes/02_Extra/Crontab
|
path = assets/syntaxes/02_Extra/Crontab
|
||||||
url = https://github.com/michaelblyons/SublimeSyntax-Crontab
|
url = https://github.com/michaelblyons/SublimeSyntax-Crontab
|
||||||
|
[submodule "assets/syntaxes/02_Extra/NSIS"]
|
||||||
|
path = assets/syntaxes/02_Extra/NSIS
|
||||||
|
url = https://github.com/SublimeText/NSIS
|
||||||
|
[submodule "assets/syntaxes/02_Extra/vscode-wgsl"]
|
||||||
|
path = assets/syntaxes/02_Extra/vscode-wgsl
|
||||||
|
url = https://github.com/PolyMeilex/vscode-wgsl.git
|
||||||
|
[submodule "assets/syntaxes/02_Extra/CFML"]
|
||||||
|
path = assets/syntaxes/02_Extra/CFML
|
||||||
|
url = https://github.com/jcberquist/sublimetext-cfml.git
|
||||||
|
|||||||
+138
@@ -1,3 +1,141 @@
|
|||||||
|
# v0.25.0
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Set terminal title to file names when Paging is not Paging::Never #2807 (@Oliver-Looney)
|
||||||
|
- `bat --squeeze-blank`/`bat -s` will now squeeze consecutive empty lines, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
|
||||||
|
- `bat --squeeze-limit` to set the maximum number of empty consecutive when using `--squeeze-blank`, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
|
||||||
|
- `PrettyPrinter::squeeze_empty_lines` to support line squeezing for bat as a library, see #1441 (@eth-p) and #2665 (@einfachIrgendwer0815)
|
||||||
|
- Syntax highlighting for JavaScript files that start with `#!/usr/bin/env bun` #2913 (@sharunkumar)
|
||||||
|
- `bat --strip-ansi={never,always,auto}` to remove ANSI escape sequences from bat's input, see #2999 (@eth-p)
|
||||||
|
- Add or remove individual style components without replacing all styles #2929 (@eth-p)
|
||||||
|
- Automatically choose theme based on the terminal's color scheme, see #2896 (@bash)
|
||||||
|
- Add option `--binary=as-text` for printing binary content, see issue #2974 and PR #2976 (@einfachIrgendwer0815)
|
||||||
|
- Make shell completions available via `--completion <shell>`, see issue #2057 and PR #3126 (@einfachIrgendwer0815)
|
||||||
|
- Syntax highlighting for puppet code blocks within Markdown files, see #3152 (@liliwilson)
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fix long file name wrapping in header, see #2835 (@FilipRazek)
|
||||||
|
- Fix `NO_COLOR` support, see #2767 (@acuteenvy)
|
||||||
|
- Fix handling of inputs with OSC ANSI escape sequences, see #2541 and #2544 (@eth-p)
|
||||||
|
- Fix handling of inputs with combined ANSI color and attribute sequences, see #2185 and #2856 (@eth-p)
|
||||||
|
- Fix panel width when line 10000 wraps, see #2854 (@eth-p)
|
||||||
|
- Fix compile issue of `time` dependency caused by standard library regression #3045 (@cyqsimon)
|
||||||
|
- Fix override behavior of --plain and --paging, see issue #2731 and PR #3108 (@einfachIrgendwer0815)
|
||||||
|
- Fix bugs in `$LESSOPEN` support, see #2805 (@Anomalocaridid)
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
- Upgrade to Rust 2021 edition #2748 (@cyqsimon)
|
||||||
|
- Refactor and cleanup build script #2756 (@cyqsimon)
|
||||||
|
- Checks changelog has been written to for PRs in CI #2766 (@cyqsimon)
|
||||||
|
- Use GitHub API to get correct PR submitter #2791 (@cyqsimon)
|
||||||
|
- Minor benchmark script improvements #2768 (@cyqsimon)
|
||||||
|
- Update Arch Linux package URL in README files #2779 (@brunobell)
|
||||||
|
- Update and improve `zsh` completion, see #2772 (@okapia)
|
||||||
|
- More extensible syntax mapping mechanism #2755 (@cyqsimon)
|
||||||
|
- Use proper Architecture for Debian packages built for musl, see #2811 (@Enselic)
|
||||||
|
- Pull in fix for unsafe-libyaml security advisory, see #2812 (@dtolnay)
|
||||||
|
- Update git-version dependency to use Syn v2, see #2816 (@dtolnay)
|
||||||
|
- Update git2 dependency to v0.18.2, see #2852 (@eth-p)
|
||||||
|
- Improve performance when color output disabled, see #2397 and #2857 (@eth-p)
|
||||||
|
- Relax syntax mapping rule restrictions to allow brace expansion #2865 (@cyqsimon)
|
||||||
|
- Apply clippy fixes #2864 (@cyqsimon)
|
||||||
|
- Faster startup by offloading glob matcher building to a worker thread #2868 (@cyqsimon)
|
||||||
|
- Display which theme is the default one in basic output (no colors), see #2937 (@sblondon)
|
||||||
|
- Display which theme is the default one in colored output, see #2838 (@sblondon)
|
||||||
|
- Add aarch64-apple-darwin ("Apple Silicon") binary tarballs to releases, see #2967 (@someposer)
|
||||||
|
- Update the Lisp syntax, see #2970 (@ccqpein)
|
||||||
|
- Use bat's ANSI iterator during tab expansion, see #2998 (@eth-p)
|
||||||
|
- Support 'statically linked binary' for aarch64 in 'Release' page, see #2992 (@tzq0301)
|
||||||
|
- Update options in shell completions and the man page of `bat`, see #2995 (@akinomyoga)
|
||||||
|
- Update nix dev-dependency to v0.29.0, see #3112 (@decathorpe)
|
||||||
|
- Bump MSRV to [1.74](https://blog.rust-lang.org/2023/11/16/Rust-1.74.0.html), see #3154 (@keith-hall)
|
||||||
|
- Update clircle dependency to remove winapi transitive dependency, see #3113 (@niklasmohrin)
|
||||||
|
|
||||||
|
## Syntaxes
|
||||||
|
|
||||||
|
- `cmd-help`: scope subcommands followed by other terms, and other misc improvements, see #2819 (@victor-gp)
|
||||||
|
- Upgrade JQ syntax, see #2820 (@dependabot[bot])
|
||||||
|
- Add syntax mapping for quadman quadlets #2866 (@cyqsimon)
|
||||||
|
- Map containers .conf files to TOML syntax #2867 (@cyqsimon)
|
||||||
|
- Associate `.xsh` files with `xonsh` syntax that is Python, see #2840 (@anki-code)
|
||||||
|
- Associate JSON with Comments `.jsonc` with `json` syntax, see #2795 (@mxaddict)
|
||||||
|
- Associate JSON-LD `.jsonld` files with `json` syntax, see #3037 (@vorburger)
|
||||||
|
- Associate `.textproto` files with `ProtoBuf` syntax, see #3038 (@vorburger)
|
||||||
|
- Associate GeoJSON `.geojson` files with `json` syntax, see #3084 (@mvaaltola)
|
||||||
|
- Associate `.aws/{config,credentials}`, see #2795 (@mxaddict)
|
||||||
|
- Associate Wireguard config `/etc/wireguard/*.conf`, see #2874 (@cyqsimon)
|
||||||
|
- Add support for [CFML](https://www.adobe.com/products/coldfusion-family.html), see #3031 (@brenton-at-pieces)
|
||||||
|
- Map `*.mkd` files to `Markdown` syntax, see issue #3060 and PR #3061 (@einfachIrgendwer0815)
|
||||||
|
- Add syntax mapping for CITATION.cff, see #3103 (@Ugzuzg)
|
||||||
|
- Add syntax mapping for kubernetes config files #3049 (@cyqsimon)
|
||||||
|
- Adds support for pipe delimiter for CSV #3115 (@pratik-m)
|
||||||
|
- Add syntax mapping for `/etc/pacman.conf` #2961 (@cyqsimon)
|
||||||
|
- Associate `uv.lock` with `TOML` syntax, see #3132 (@fepegar)
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
- Patched/improved themes for better Manpage syntax highlighting support, see #2994 (@keith-hall).
|
||||||
|
|
||||||
|
## `bat` as a library
|
||||||
|
|
||||||
|
- Changes to `syntax_mapping::SyntaxMapping` #2755 (@cyqsimon)
|
||||||
|
- `SyntaxMapping::get_syntax_for` is now correctly public
|
||||||
|
- [BREAKING] `SyntaxMapping::{empty,builtin}` are removed; use `SyntaxMapping::new` instead
|
||||||
|
- [BREAKING] `SyntaxMapping::mappings` is replaced by `SyntaxMapping::{builtin,custom,all}_mappings`
|
||||||
|
- Make `Controller::run_with_error_handler`'s error handler `FnMut`, see #2831 (@rhysd)
|
||||||
|
- Improve compile time by 20%, see #2815 (@dtolnay)
|
||||||
|
- Add `theme::theme` for choosing an appropriate theme based on the
|
||||||
|
terminal's color scheme, see #2896 (@bash)
|
||||||
|
- [BREAKING] Remove `HighlightingAssets::default_theme`. Use `theme::default_theme` instead.
|
||||||
|
- Add `PrettyPrinter::print_with_writer` for custom output destinations, see #3070 (@kojix2)
|
||||||
|
|
||||||
|
# v0.24.0
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Add environment variable `BAT_PAGING`, see #2629 (@einfachIrgendwer0815)
|
||||||
|
- Add opt-in (`--features lessopen`) support for `LESSOPEN` and `LESSCLOSE`. See #1597, #1739, #2444, #2602, and #2662 (@Anomalocaridid)
|
||||||
|
|
||||||
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fix `more` not being found on Windows when provided via `BAT_PAGER`, see #2570, #2580, and #2651 (@mataha)
|
||||||
|
- Switched default behavior of `--map-syntax` to be case insensitive #2520
|
||||||
|
- Updated version of `serde_yaml` to `0.9`. See #2627 (@Raghav-Bell)
|
||||||
|
- Fix arithmetic overflow in `LineRange::from` and `LineRange::parse_range`, see #2674, #2698 (@skoriop)
|
||||||
|
- Fix paging not happening when stdout is interactive but stdin is not, see #2574 (@Nigecat)
|
||||||
|
- Make `-pp` override `--paging` and vice versa when passed as a later argument, see #2660 (@J-Kappes)
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
- Output directory for generated assets (completion, manual) can be customized, see #2515 (@tranzystorek-io)
|
||||||
|
- Use the `is-terminal` crate instead of `atty`, see #2530 (@nickelc)
|
||||||
|
- Add Winget Releaser workflow, see #2519 (@sitiom)
|
||||||
|
- Bump MSRV to 1.70, see #2651 (@mataha)
|
||||||
|
|
||||||
|
## Syntaxes
|
||||||
|
|
||||||
|
- Associate `os-release` with `bash` syntax, see #2587 (@cyqsimon)
|
||||||
|
- Associate `Containerfile` with `Dockerfile` syntax, see #2606 (@einfachIrgendwer0815)
|
||||||
|
- Replaced quotes with double quotes so fzf integration example script works on windows and linux. see #2095 (@johnmatthiggins)
|
||||||
|
- Associate `ksh` files with `bash` syntax, see #2633 (@johnmatthiggins)
|
||||||
|
- Associate `sarif` files with `JSON` syntax, see #2695 (@rhysd)
|
||||||
|
- Associate `ron` files with `rust` syntax, see #2427 (@YeungOnion)
|
||||||
|
- Add support for [WebGPU Shader Language](https://www.w3.org/TR/WGSL/), see #2692 (@rhysd)
|
||||||
|
- Add `.dpkg-new` and `.dpkg-tmp` to ignored suffixe, see #2595 (@scop)
|
||||||
|
- fix: Add syntax mapping `*.jsonl` => `json`, see #2539 (@WinterCore)
|
||||||
|
- Update `Julia` syntax, see #2553 (@dependabot)
|
||||||
|
- add `NSIS` support, see #2577 (@idleberg)
|
||||||
|
- Update `ssh-config`, see #2697 (@mrmeszaros)
|
||||||
|
- Add syntax mapping `*.debdiff` => `diff`, see #2947 (@jacg)
|
||||||
|
|
||||||
|
## `bat` as a library
|
||||||
|
|
||||||
|
- Add optional output_buffer arg to `Controller::run()` and `Controller::run_with_error_handler()`, see #2618 (@Piturnah)
|
||||||
|
|
||||||
|
|
||||||
# v0.23.0
|
# v0.23.0
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
+28
-7
@@ -6,21 +6,42 @@ Thank you for considering to contribute to `bat`!
|
|||||||
|
|
||||||
## Add an entry to the changelog
|
## Add an entry to the changelog
|
||||||
|
|
||||||
If your contribution changes the behavior of `bat` (as opposed to a typo-fix
|
Keeping the [`CHANGELOG.md`](CHANGELOG.md) file up-to-date makes the release
|
||||||
in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file
|
process much easier and therefore helps to get your changes into a new `bat`
|
||||||
and describe your changes. This makes the release process much easier and
|
release faster. However, not every change to the repository requires a
|
||||||
therefore helps to get your changes into a new `bat` release faster.
|
changelog entry. Below are a few examples of that.
|
||||||
|
|
||||||
|
Please update the changelog if your contribution contains changes regarding
|
||||||
|
any of the following:
|
||||||
|
- the behavior of `bat`
|
||||||
|
- syntax mappings
|
||||||
|
- syntax definitions
|
||||||
|
- themes
|
||||||
|
- the build system, linting, or CI workflows
|
||||||
|
|
||||||
|
A changelog entry is not necessary when:
|
||||||
|
- updating documentation
|
||||||
|
- fixing typos
|
||||||
|
|
||||||
|
>[!NOTE]
|
||||||
|
> For PRs, a CI workflow verifies that a suitable changelog entry is
|
||||||
|
> added. If such an entry is missing, the workflow will fail. If your
|
||||||
|
> changes do not need an entry to the changelog (see above), that
|
||||||
|
> workflow failure can be disregarded.
|
||||||
|
|
||||||
|
|
||||||
|
### Changelog entry format
|
||||||
|
|
||||||
The top of the `CHANGELOG` contains a *"unreleased"* section with a few
|
The top of the `CHANGELOG` contains a *"unreleased"* section with a few
|
||||||
subsections (Features, Bugfixes, …). Please add your entry to the subsection
|
subsections (Features, Bugfixes, …). Please add your entry to the subsection
|
||||||
that best describes your change.
|
that best describes your change.
|
||||||
|
|
||||||
Entries follow this format:
|
Entries must follow this format:
|
||||||
```
|
```
|
||||||
- Short description of what has been changed, see #123 (@user)
|
- Short description of what has been changed, see #123 (@user)
|
||||||
```
|
```
|
||||||
Here, `#123` is the number of the original issue and/or your pull request.
|
Please replace `#123` with the number of your pull request (not issue) and
|
||||||
Please replace `@user` by your GitHub username.
|
`@user` by your GitHub username.
|
||||||
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
Generated
+823
-465
File diff suppressed because it is too large
Load Diff
+49
-34
@@ -3,14 +3,14 @@ authors = ["David Peter <mail@david-peter.de>"]
|
|||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
description = "A cat(1) clone with wings."
|
description = "A cat(1) clone with wings."
|
||||||
homepage = "https://github.com/sharkdp/bat"
|
homepage = "https://github.com/sharkdp/bat"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
name = "bat"
|
name = "bat"
|
||||||
repository = "https://github.com/sharkdp/bat"
|
repository = "https://github.com/sharkdp/bat"
|
||||||
version = "0.23.0"
|
version = "0.25.0"
|
||||||
exclude = ["assets/syntaxes/*", "assets/themes/*"]
|
exclude = ["assets/syntaxes/*", "assets/themes/*"]
|
||||||
build = "build.rs"
|
build = "build/main.rs"
|
||||||
edition = '2018'
|
edition = '2021'
|
||||||
rust-version = "1.64"
|
rust-version = "1.74"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["application"]
|
default = ["application"]
|
||||||
@@ -25,15 +25,15 @@ application = [
|
|||||||
# Mainly for developers that want to iterate quickly
|
# Mainly for developers that want to iterate quickly
|
||||||
# Be aware that the included features might change in the future
|
# Be aware that the included features might change in the future
|
||||||
minimal-application = [
|
minimal-application = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
"dirs",
|
"etcetera",
|
||||||
"paging",
|
"paging",
|
||||||
"regex-onig",
|
"regex-onig",
|
||||||
"wild",
|
"wild",
|
||||||
]
|
]
|
||||||
git = ["git2"] # Support indicating git modifications
|
git = ["git2"] # Support indicating git modifications
|
||||||
paging = ["shell-words", "grep-cli"] # Support applying a pager on the output
|
paging = ["shell-words", "grep-cli"] # Support applying a pager on the output
|
||||||
|
lessopen = ["execute"] # Support $LESSOPEN preprocessor
|
||||||
build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"]
|
build-assets = ["syntect/yaml-load", "syntect/plist-load", "regex", "walkdir"]
|
||||||
|
|
||||||
# You need to use one of these if you depend on bat as a library:
|
# You need to use one of these if you depend on bat as a library:
|
||||||
@@ -41,64 +41,79 @@ regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine
|
|||||||
regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine
|
regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
atty = { version = "0.2.14", optional = true }
|
nu-ansi-term = "0.50.0"
|
||||||
nu-ansi-term = "0.47.0"
|
|
||||||
ansi_colours = "^1.2"
|
ansi_colours = "^1.2"
|
||||||
bincode = "1.0"
|
bincode = "1.0"
|
||||||
console = "0.15.5"
|
console = "0.15.10"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.20"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
wild = { version = "2.1", optional = true }
|
wild = { version = "2.2", optional = true }
|
||||||
content_inspector = "0.2.4"
|
content_inspector = "0.2.4"
|
||||||
encoding = "0.2"
|
|
||||||
shell-words = { version = "1.1.0", optional = true }
|
shell-words = { version = "1.1.0", optional = true }
|
||||||
unicode-width = "0.1.10"
|
unicode-width = "0.1.13"
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_derive = "1.0"
|
||||||
|
serde_yaml = "0.9.28"
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
path_abs = { version = "0.5", default-features = false }
|
path_abs = { version = "0.5", default-features = false }
|
||||||
clircle = "0.3"
|
clircle = { version = "0.6.1", default-features = false }
|
||||||
bugreport = { version = "0.5.0", optional = true }
|
bugreport = { version = "0.5.0", optional = true }
|
||||||
dirs = { version = "5.0.0", optional = true }
|
etcetera = { version = "0.8.0", optional = true }
|
||||||
grep-cli = { version = "0.1.7", optional = true }
|
grep-cli = { version = "0.1.11", optional = true }
|
||||||
regex = { version = "1.7.0", optional = true }
|
regex = { version = "1.10.6", optional = true }
|
||||||
walkdir = { version = "2.0", optional = true }
|
walkdir = { version = "2.5", optional = true }
|
||||||
bytesize = { version = "1.1.0" }
|
bytesize = { version = "1.3.0" }
|
||||||
|
encoding_rs = "0.8.35"
|
||||||
|
execute = { version = "0.2.13", optional = true }
|
||||||
|
terminal-colorsaurus = "0.4"
|
||||||
|
|
||||||
[dependencies.git2]
|
[dependencies.git2]
|
||||||
version = "0.16"
|
version = "0.19"
|
||||||
optional = true
|
optional = true
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[dependencies.syntect]
|
[dependencies.syntect]
|
||||||
version = "5.0.0"
|
version = "5.2.0"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["parsing"]
|
features = ["parsing"]
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.1.8"
|
version = "4.4.12"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["wrap_help", "cargo"]
|
features = ["wrap_help", "cargo"]
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
dirs = "5.0.0"
|
home = "0.5.9"
|
||||||
plist = "1.3"
|
plist = "1.7.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0.8"
|
assert_cmd = "2.0.12"
|
||||||
expect-test = "1.4.0"
|
expect-test = "1.5.0"
|
||||||
serial_test = "0.6.0"
|
serial_test = { version = "2.0.0", default-features = false }
|
||||||
predicates = "2.1.5"
|
predicates = "3.1.3"
|
||||||
wait-timeout = "0.2.0"
|
wait-timeout = "0.2.0"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.8.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
nix = { version = "0.26.2", default-features = false, features = ["term"] }
|
nix = { version = "0.29", default-features = false, features = ["term"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
anyhow = "1.0.86"
|
||||||
|
indexmap = { version = "2.3.0", features = ["serde"] }
|
||||||
|
itertools = "0.13.0"
|
||||||
|
once_cell = "1.20"
|
||||||
|
regex = "1.10.6"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
serde_with = { version = "3.12.0", default-features = false, features = ["macros"] }
|
||||||
|
toml = { version = "0.8.19", features = ["preserve_order"] }
|
||||||
|
walkdir = "2.5"
|
||||||
|
|
||||||
[build-dependencies.clap]
|
[build-dependencies.clap]
|
||||||
version = "4.1.8"
|
version = "4.4.12"
|
||||||
optional = true
|
optional = true
|
||||||
features = ["wrap_help", "cargo"]
|
features = ["wrap_help", "cargo"]
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ A special *thank you* goes to our biggest <a href="doc/sponsors.md">sponsors</a>
|
|||||||
<sup>Add Single Sign-On (and more) in minutes instead of months.</sup>
|
<sup>Add Single Sign-On (and more) in minutes instead of months.</sup>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="https://www.warp.dev/?utm_source=github&utm_medium=referral&utm_campaign=bat_20231001">
|
||||||
|
<img src="doc/sponsors/warp-logo.png" width="200" alt="Warp">
|
||||||
|
<br>
|
||||||
|
<strong>Warp, the intelligent terminal</strong>
|
||||||
|
<br>
|
||||||
|
<sub>Run commands like a power user with AI and your dev team’s</sub>
|
||||||
|
<br>
|
||||||
|
<sup>knowledge in one fast, intuitive terminal. For MacOS or Linux.</sup>
|
||||||
|
</a>
|
||||||
|
|
||||||
### Syntax highlighting
|
### Syntax highlighting
|
||||||
|
|
||||||
`bat` supports syntax highlighting for a large number of programming and markup
|
`bat` supports syntax highlighting for a large number of programming and markup
|
||||||
@@ -118,7 +128,7 @@ use `bat`s `--color=always` option to force colorized output. You can also use `
|
|||||||
option to restrict the load times for long files:
|
option to restrict the load times for long files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
fzf --preview 'bat --color=always --style=numbers --line-range=:500 {}'
|
fzf --preview "bat --color=always --style=numbers --line-range=:500 {}"
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information, see [`fzf`'s `README`](https://github.com/junegunn/fzf#preview-window).
|
For more information, see [`fzf`'s `README`](https://github.com/junegunn/fzf#preview-window).
|
||||||
@@ -194,19 +204,19 @@ bat main.cpp | xclip
|
|||||||
`MANPAGER` environment variable:
|
`MANPAGER` environment variable:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
|
export MANPAGER="sh -c 'sed -u -e \"s/\\x1B\[[0-9;]*m//g; s/.\\x08//g\" | bat -p -lman'"
|
||||||
man 2 select
|
man 2 select
|
||||||
```
|
```
|
||||||
(replace `bat` with `batcat` if you are on Debian or Ubuntu)
|
(replace `bat` with `batcat` if you are on Debian or Ubuntu)
|
||||||
|
|
||||||
It might also be necessary to set `MANROFFOPT="-c"` if you experience
|
|
||||||
formatting problems.
|
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
||||||
Note that the [Manpage syntax](assets/syntaxes/02_Extra/Manpage.sublime-syntax) is developed in this repository and still needs some work.
|
> [!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.
|
||||||
|
|
||||||
Also, note that this will [not work](https://github.com/sharkdp/bat/issues/1145) with Mandocs `man` implementation.
|
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`
|
#### `prettier` / `shfmt` / `rustfmt`
|
||||||
|
|
||||||
@@ -228,12 +238,23 @@ help() {
|
|||||||
|
|
||||||
Then you can do `$ help cp` or `$ help git commit`.
|
Then you can do `$ help cp` or `$ help git commit`.
|
||||||
|
|
||||||
|
When you are using `zsh`, you can also use global aliases to override `-h` and `--help` entirely:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
alias -g -- -h='-h 2>&1 | bat --language=help --style=plain'
|
||||||
|
alias -g -- --help='--help 2>&1 | bat --language=help --style=plain'
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, you can keep on using `cp --help`, but get colorized help pages.
|
||||||
|
|
||||||
|
Be aware that in some cases, `-h` may not be a shorthand of `--help` (for example with `ls`).
|
||||||
|
|
||||||
Please report any issues with the help syntax in [this repository](https://github.com/victor-gp/cmd-help-sublime-syntax).
|
Please report any issues with the help syntax in [this repository](https://github.com/victor-gp/cmd-help-sublime-syntax).
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
[](https://repology.org/project/bat-cat/versions)
|
[](https://repology.org/project/bat-cat/versions)
|
||||||
|
|
||||||
### On Ubuntu (using `apt`)
|
### On Ubuntu (using `apt`)
|
||||||
*... and other Debian-based Linux distributions.*
|
*... and other Debian-based Linux distributions.*
|
||||||
@@ -275,7 +296,7 @@ apk add bat
|
|||||||
|
|
||||||
### On Arch Linux
|
### On Arch Linux
|
||||||
|
|
||||||
You can install [the `bat` package](https://www.archlinux.org/packages/community/x86_64/bat/)
|
You can install [the `bat` package](https://www.archlinux.org/packages/extra/x86_64/bat/)
|
||||||
from the official sources:
|
from the official sources:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -352,6 +373,14 @@ You can install `bat` using the [nix package manager](https://nixos.org/nix):
|
|||||||
nix-env -i bat
|
nix-env -i bat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Via flox
|
||||||
|
|
||||||
|
You can install `bat` using [Flox](https://flox.dev)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flox install bat
|
||||||
|
```
|
||||||
|
|
||||||
### On openSUSE
|
### On openSUSE
|
||||||
|
|
||||||
You can install `bat` with zypper:
|
You can install `bat` with zypper:
|
||||||
@@ -390,6 +419,14 @@ take a look at the ["Using `bat` on Windows"](#using-bat-on-windows) section.
|
|||||||
|
|
||||||
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://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) package.
|
||||||
|
|
||||||
|
#### With WinGet
|
||||||
|
|
||||||
|
You can install `bat` via [WinGet](https://learn.microsoft.com/en-us/windows/package-manager/winget):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
winget install sharkdp.bat
|
||||||
|
```
|
||||||
|
|
||||||
#### With Chocolatey
|
#### With Chocolatey
|
||||||
|
|
||||||
You can install `bat` via [Chocolatey](https://chocolatey.org/packages/Bat):
|
You can install `bat` via [Chocolatey](https://chocolatey.org/packages/Bat):
|
||||||
@@ -418,7 +455,7 @@ binaries are also available: look for archives with `musl` in the file name.
|
|||||||
|
|
||||||
### From source
|
### From source
|
||||||
|
|
||||||
If you want to build `bat` from source, you need Rust 1.64.0 or
|
If you want to build `bat` from source, you need Rust 1.74.0 or
|
||||||
higher. You can then use `cargo` to build everything:
|
higher. You can then use `cargo` to build everything:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -428,6 +465,12 @@ cargo install --locked bat
|
|||||||
Note that additional files like the man page or shell completion
|
Note that additional files like the man page or shell completion
|
||||||
files can not be installed in this way. They will be generated by `cargo` and should be available in the cargo target folder (under `build`).
|
files can not be installed in this way. They will be generated by `cargo` and should be available in the cargo target folder (under `build`).
|
||||||
|
|
||||||
|
Shell completions are also available by running:
|
||||||
|
```bash
|
||||||
|
bat --completion <shell>
|
||||||
|
# see --help for supported shells
|
||||||
|
```
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
||||||
### Highlighting theme
|
### Highlighting theme
|
||||||
@@ -445,8 +488,10 @@ the following command (you need [`fzf`](https://github.com/junegunn/fzf) for thi
|
|||||||
bat --list-themes | fzf --preview="bat --theme={} --color=always /path/to/file"
|
bat --list-themes | fzf --preview="bat --theme={} --color=always /path/to/file"
|
||||||
```
|
```
|
||||||
|
|
||||||
`bat` looks good on a dark background by default. However, if your terminal uses a
|
`bat` automatically picks a fitting theme depending on your terminal's background color.
|
||||||
light background, some themes like `GitHub` or `OneHalfLight` will work better for you.
|
You can use the `--theme-light` / `--theme-light` options or the `BAT_THEME_DARK` / `BAT_THEME_LIGHT` environment variables
|
||||||
|
to customize the themes used. This is especially useful if you frequently switch between dark and light mode.
|
||||||
|
|
||||||
You can also use a custom theme by following the
|
You can also use a custom theme by following the
|
||||||
['Adding new themes' section below](https://github.com/sharkdp/bat#adding-new-themes).
|
['Adding new themes' section below](https://github.com/sharkdp/bat#adding-new-themes).
|
||||||
|
|
||||||
@@ -478,6 +523,16 @@ 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
|
variable to make these changes permanent or use `bat`s
|
||||||
[configuration file](https://github.com/sharkdp/bat#configuration-file).
|
[configuration file](https://github.com/sharkdp/bat#configuration-file).
|
||||||
|
|
||||||
|
>[!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.
|
||||||
|
> By prefixing a component with `+` or `-`, it can be added or removed from the current style.
|
||||||
|
>
|
||||||
|
> For example, if your config contains `--style=full,-snip`, you can run bat with
|
||||||
|
> `--style=-grid,+snip` to remove the grid and add back the `snip` component.
|
||||||
|
> Or, if you want to override the styles completely, you use `--style=numbers` to
|
||||||
|
> only show the line numbers.
|
||||||
|
|
||||||
### Adding new syntaxes / language definitions
|
### Adding new syntaxes / language definitions
|
||||||
|
|
||||||
Should you find that a particular syntax is not available within `bat`, you can follow these
|
Should you find that a particular syntax is not available within `bat`, you can follow these
|
||||||
@@ -573,7 +628,8 @@ set, `less` is used by default. If you want to use a different pager, you can ei
|
|||||||
`PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in
|
`PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in
|
||||||
`PAGER`.
|
`PAGER`.
|
||||||
|
|
||||||
**Note**: If `PAGER` is `more` or `most`, `bat` will silently use `less` instead to ensure support for colors.
|
>[!NOTE]
|
||||||
|
> If `PAGER` is `more` or `most`, `bat` will silently use `less` instead to ensure support for colors.
|
||||||
|
|
||||||
If you want to pass command-line arguments to the pager, you can also set them via the
|
If you want to pass command-line arguments to the pager, you can also set them via the
|
||||||
`PAGER`/`BAT_PAGER` variables:
|
`PAGER`/`BAT_PAGER` variables:
|
||||||
@@ -584,20 +640,37 @@ export BAT_PAGER="less -RF"
|
|||||||
|
|
||||||
Instead of using environment variables, you can also use `bat`s [configuration file](https://github.com/sharkdp/bat#configuration-file) to configure the pager (`--pager` option).
|
Instead of using environment variables, you can also use `bat`s [configuration file](https://github.com/sharkdp/bat#configuration-file) to configure the pager (`--pager` option).
|
||||||
|
|
||||||
**Note**: By default, if the pager is set to `less` (and no command-line options are specified),
|
|
||||||
`bat` will pass the following command line options to the pager: `-R`/`--RAW-CONTROL-CHARS`,
|
|
||||||
`-F`/`--quit-if-one-screen` and `-X`/`--no-init`. The last option (`-X`) is only used for `less`
|
|
||||||
versions older than 530.
|
|
||||||
|
|
||||||
The `-R` option is needed to interpret ANSI colors correctly. The second option (`-F`) instructs
|
### Using `less` as a pager
|
||||||
less to exit immediately if the output size is smaller than the vertical size of the terminal.
|
|
||||||
This is convenient for small files because you do not have to press `q` to quit the pager. The
|
|
||||||
third option (`-X`) is needed to fix a bug with the `--quit-if-one-screen` feature in old versions
|
|
||||||
of `less`. Unfortunately, it also breaks mouse-wheel support in `less`.
|
|
||||||
|
|
||||||
If you want to enable mouse-wheel scrolling on older versions of `less`, you can pass just `-R` (as
|
When using `less` as a pager, `bat` will automatically pass extra options along to `less`
|
||||||
in the example above, this will disable the quit-if-one-screen feature). For less 530 or newer,
|
to improve the experience. Specifically, `-R`/`--RAW-CONTROL-CHARS`, `-F`/`--quit-if-one-screen`,
|
||||||
it should work out of the box.
|
and under certain conditions, `-X`/`--no-init` and/or `-S`/`--chop-long-lines`.
|
||||||
|
|
||||||
|
>[!IMPORTANT]
|
||||||
|
> These options will not be added if:
|
||||||
|
> - The pager is not named `less`.
|
||||||
|
> - The `--pager` argument contains any command-line arguments (e.g. `--pager="less -R"`).
|
||||||
|
> - The `BAT_PAGER` environment variable contains any command-line arguments (e.g. `export BAT_PAGER="less -R"`)
|
||||||
|
>
|
||||||
|
> The `--quit-if-one-screen` option will not be added when:
|
||||||
|
> - The `--paging=always` argument is used.
|
||||||
|
> - The `BAT_PAGING` environment is set to `always`.
|
||||||
|
|
||||||
|
The `-R` option is needed to interpret ANSI colors correctly.
|
||||||
|
|
||||||
|
The `-F` option instructs `less` to exit immediately if the output size is smaller than
|
||||||
|
the vertical size of the terminal. This is convenient for small files because you do not
|
||||||
|
have to press `q` to quit the pager.
|
||||||
|
|
||||||
|
The `-X` option is needed to fix a bug with the `--quit-if-one-screen` feature in versions
|
||||||
|
of `less` older than version 530. Unfortunately, it also breaks mouse-wheel support in `less`.
|
||||||
|
If you want to enable mouse-wheel scrolling on older versions of `less` and do not mind losing
|
||||||
|
the quit-if-one-screen feature, you can set the pager (via `--pager` or `BAT_PAGER`) to `less -R`.
|
||||||
|
For `less` 530 or newer, it should work out of the box.
|
||||||
|
|
||||||
|
The `-S` option is added when `bat`'s `-S`/`--chop-long-lines` option is used. This tells `less`
|
||||||
|
to truncate any lines larger than the terminal width.
|
||||||
|
|
||||||
### Indentation
|
### Indentation
|
||||||
|
|
||||||
@@ -628,10 +701,11 @@ on your operating system. To get the default path for your system, call
|
|||||||
bat --config-file
|
bat --config-file
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can use the `BAT_CONFIG_PATH` environment variable to point `bat` to a
|
Alternatively, you can use `BAT_CONFIG_PATH` or `BAT_CONFIG_DIR` environment variables to point `bat`
|
||||||
non-default location of the configuration file:
|
to a non-default location of the configuration file or the configuration directory respectively:
|
||||||
```bash
|
```bash
|
||||||
export BAT_CONFIG_PATH="/path/to/bat.conf"
|
export BAT_CONFIG_PATH="/path/to/bat/bat.conf"
|
||||||
|
export BAT_CONFIG_DIR="/path/to/bat"
|
||||||
```
|
```
|
||||||
|
|
||||||
A default configuration file can be created with the `--generate-config-file` option.
|
A default configuration file can be created with the `--generate-config-file` option.
|
||||||
@@ -682,11 +756,9 @@ your `PATH` or [define an environment variable](#using-a-different-pager). The [
|
|||||||
Windows 10 natively supports colors in both `conhost.exe` (Command Prompt) and PowerShell since
|
Windows 10 natively supports colors in both `conhost.exe` (Command Prompt) and PowerShell since
|
||||||
[v1511](https://en.wikipedia.org/wiki/Windows_10_version_history#Version_1511_(November_Update)), as
|
[v1511](https://en.wikipedia.org/wiki/Windows_10_version_history#Version_1511_(November_Update)), as
|
||||||
well as in newer versions of bash. On earlier versions of Windows, you can use
|
well as in newer versions of bash. On earlier versions of Windows, you can use
|
||||||
[Cmder](http://cmder.net/), which includes [ConEmu](https://conemu.github.io/).
|
[Cmder](http://cmder.app/), which includes [ConEmu](https://conemu.github.io/).
|
||||||
|
|
||||||
**Note:** The Git and MSYS versions of `less` do not correctly interpret colors on Windows. If you
|
**Note:** Old versions of `less` do not correctly interpret colors on Windows. To fix this, you can add the optional Unix tools to your PATH when installing Git. If you don’t have any other pagers installed, you can disable paging entirely by passing `--paging=never` or by setting `BAT_PAGER` to an empty string.
|
||||||
don’t have any other pagers installed, you can disable paging entirely by passing `--paging=never`
|
|
||||||
or by setting `BAT_PAGER` to an empty string.
|
|
||||||
|
|
||||||
### Cygwin
|
### Cygwin
|
||||||
|
|
||||||
@@ -714,9 +786,14 @@ bat() {
|
|||||||
|
|
||||||
If an input file contains color codes or other ANSI escape sequences or control characters, `bat` will have problems
|
If an input file contains color codes or other ANSI escape sequences or control characters, `bat` will have problems
|
||||||
performing syntax highlighting and text wrapping, and thus the output can become garbled.
|
performing syntax highlighting and text wrapping, and thus the output can become garbled.
|
||||||
When displaying such files it is recommended to disable both syntax highlighting and wrapping by
|
|
||||||
|
If your version of `bat` supports the `--strip-ansi=auto` option, it can be used to remove such sequences
|
||||||
|
before syntax highlighting. Alternatively, you may disable both syntax highlighting and wrapping by
|
||||||
passing the `--color=never --wrap=never` options to `bat`.
|
passing the `--color=never --wrap=never` options to `bat`.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> The `auto` option of `--strip-ansi` avoids removing escape sequences when the syntax is plain text.
|
||||||
|
|
||||||
### Terminals & colors
|
### Terminals & colors
|
||||||
|
|
||||||
`bat` handles terminals *with* and *without* truecolor support. However, the colors in most syntax
|
`bat` handles terminals *with* and *without* truecolor support. However, the colors in most syntax
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Vendored
+4
@@ -37,6 +37,8 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
|||||||
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').')
|
[CompletionResult]::new('-m', 'm', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').')
|
||||||
[CompletionResult]::new('--map-syntax', 'map-syntax', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').')
|
[CompletionResult]::new('--map-syntax', 'map-syntax', [CompletionResultType]::ParameterName, 'Use the specified syntax for files matching the glob pattern (''*.cpp:C++'').')
|
||||||
[CompletionResult]::new('--theme', 'theme', [CompletionResultType]::ParameterName, 'Set the color theme for syntax highlighting.')
|
[CompletionResult]::new('--theme', 'theme', [CompletionResultType]::ParameterName, 'Set the color theme for syntax highlighting.')
|
||||||
|
[CompletionResult]::new('--theme-dark', 'theme', [CompletionResultType]::ParameterName, 'Set the color theme for syntax highlighting for dark backgrounds.')
|
||||||
|
[CompletionResult]::new('--theme-light', 'theme', [CompletionResultType]::ParameterName, 'Set the color theme for syntax highlighting for light backgrounds.')
|
||||||
[CompletionResult]::new('--style', 'style', [CompletionResultType]::ParameterName, 'Comma-separated list of style elements to display (*default*, auto, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip).')
|
[CompletionResult]::new('--style', 'style', [CompletionResultType]::ParameterName, 'Comma-separated list of style elements to display (*default*, auto, full, plain, changes, header, header-filename, header-filesize, grid, rule, numbers, snip).')
|
||||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.')
|
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.')
|
||||||
[CompletionResult]::new('--line-range', 'line-range', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.')
|
[CompletionResult]::new('--line-range', 'line-range', [CompletionResultType]::ParameterName, 'Only print the lines from N to M.')
|
||||||
@@ -59,6 +61,8 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
|
|||||||
[CompletionResult]::new('--unbuffered', 'unbuffered', [CompletionResultType]::ParameterName, 'unbuffered')
|
[CompletionResult]::new('--unbuffered', 'unbuffered', [CompletionResultType]::ParameterName, 'unbuffered')
|
||||||
[CompletionResult]::new('--no-config', 'no-config', [CompletionResultType]::ParameterName, 'Do not use the configuration file')
|
[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')
|
[CompletionResult]::new('--no-custom-assets', 'no-custom-assets', [CompletionResultType]::ParameterName, 'Do not load custom assets')
|
||||||
|
[CompletionResult]::new('--lessopen', 'lessopen', [CompletionResultType]::ParameterName, 'Enable the $LESSOPEN preprocessor')
|
||||||
|
[CompletionResult]::new('--no-lessopen', 'no-lessopen', [CompletionResultType]::ParameterName, 'Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)')
|
||||||
[CompletionResult]::new('--config-file', 'config-file', [CompletionResultType]::ParameterName, 'Show path to the configuration file.')
|
[CompletionResult]::new('--config-file', 'config-file', [CompletionResultType]::ParameterName, 'Show path to the configuration file.')
|
||||||
[CompletionResult]::new('--generate-config-file', 'generate-config-file', [CompletionResultType]::ParameterName, 'Generates a default configuration file.')
|
[CompletionResult]::new('--generate-config-file', 'generate-config-file', [CompletionResultType]::ParameterName, 'Generates a default configuration file.')
|
||||||
[CompletionResult]::new('--config-dir', 'config-dir', [CompletionResultType]::ParameterName, 'Show bat''s configuration directory.')
|
[CompletionResult]::new('--config-dir', 'config-dir', [CompletionResultType]::ParameterName, 'Show bat''s configuration directory.')
|
||||||
|
|||||||
Vendored
+20
-1
@@ -32,7 +32,7 @@ __bat_escape_completions()
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bat() {
|
_bat() {
|
||||||
local cur prev words cword split=false
|
local cur prev words split=false
|
||||||
if declare -F _init_completion >/dev/null 2>&1; then
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
_init_completion -s || return 0
|
_init_completion -s || return 0
|
||||||
else
|
else
|
||||||
@@ -76,8 +76,10 @@ _bat() {
|
|||||||
-m | --map-syntax | \
|
-m | --map-syntax | \
|
||||||
--ignored-suffix | \
|
--ignored-suffix | \
|
||||||
--list-themes | \
|
--list-themes | \
|
||||||
|
--squeeze-limit | \
|
||||||
--line-range | \
|
--line-range | \
|
||||||
-L | --list-languages | \
|
-L | --list-languages | \
|
||||||
|
--lessopen | \
|
||||||
--diagnostic | \
|
--diagnostic | \
|
||||||
--acknowledgements | \
|
--acknowledgements | \
|
||||||
-h | --help | \
|
-h | --help | \
|
||||||
@@ -111,6 +113,13 @@ _bat() {
|
|||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--theme)
|
--theme)
|
||||||
|
local IFS=$'\n'
|
||||||
|
COMPREPLY=($(compgen -W "auto${IFS}auto:always${IFS}auto:system${IFS}dark${IFS}light${IFS}$("$1" --list-themes)" -- "$cur"))
|
||||||
|
__bat_escape_completions
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--theme-dark | \
|
||||||
|
--theme-light)
|
||||||
local IFS=$'\n'
|
local IFS=$'\n'
|
||||||
COMPREPLY=($(compgen -W "$("$1" --list-themes)" -- "$cur"))
|
COMPREPLY=($(compgen -W "$("$1" --list-themes)" -- "$cur"))
|
||||||
__bat_escape_completions
|
__bat_escape_completions
|
||||||
@@ -156,6 +165,7 @@ _bat() {
|
|||||||
--diff-context
|
--diff-context
|
||||||
--tabs
|
--tabs
|
||||||
--wrap
|
--wrap
|
||||||
|
--chop-long-lines
|
||||||
--terminal-width
|
--terminal-width
|
||||||
--number
|
--number
|
||||||
--color
|
--color
|
||||||
@@ -167,18 +177,27 @@ _bat() {
|
|||||||
--map-syntax
|
--map-syntax
|
||||||
--ignored-suffix
|
--ignored-suffix
|
||||||
--theme
|
--theme
|
||||||
|
--theme-dark
|
||||||
|
--theme-light
|
||||||
--list-themes
|
--list-themes
|
||||||
|
--squeeze-blank
|
||||||
|
--squeeze-limit
|
||||||
--style
|
--style
|
||||||
--line-range
|
--line-range
|
||||||
--list-languages
|
--list-languages
|
||||||
|
--lessopen
|
||||||
--diagnostic
|
--diagnostic
|
||||||
--acknowledgements
|
--acknowledgements
|
||||||
|
--set-terminal-title
|
||||||
--help
|
--help
|
||||||
--version
|
--version
|
||||||
--cache-dir
|
--cache-dir
|
||||||
--config-dir
|
--config-dir
|
||||||
--config-file
|
--config-file
|
||||||
--generate-config-file
|
--generate-config-file
|
||||||
|
--no-config
|
||||||
|
--no-custom-assets
|
||||||
|
--no-lessopen
|
||||||
" -- "$cur"))
|
" -- "$cur"))
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
Vendored
+25
-1
@@ -129,10 +129,20 @@ set -l tabs_opts '
|
|||||||
8\t
|
8\t
|
||||||
'
|
'
|
||||||
|
|
||||||
|
set -l special_themes '
|
||||||
|
auto\tdefault,\ Choose\ a\ theme\ based\ on\ dark\ or\ light\ mode
|
||||||
|
auto:always\tChoose\ a\ theme\ based\ on\ dark\ or\ light\ mode
|
||||||
|
auto:system\tChoose\ a\ theme\ based\ on\ dark\ or\ light\ mode
|
||||||
|
dark\tUse\ the\ theme\ specified\ by\ --theme-dark
|
||||||
|
light\tUse\ the\ theme\ specified\ by\ --theme-light
|
||||||
|
'
|
||||||
|
|
||||||
# Completions:
|
# Completions:
|
||||||
|
|
||||||
complete -c $bat -l acknowledgements -d "Print acknowledgements" -n __fish_is_first_arg
|
complete -c $bat -l acknowledgements -d "Print acknowledgements" -n __fish_is_first_arg
|
||||||
|
|
||||||
|
complete -c $bat -l cache-dir -f -d "Show bat's cache directory" -n __fish_is_first_arg
|
||||||
|
|
||||||
complete -c $bat -l color -x -a "$color_opts" -d "When to use colored output" -n __bat_no_excl_args
|
complete -c $bat -l color -x -a "$color_opts" -d "When to use colored output" -n __bat_no_excl_args
|
||||||
|
|
||||||
complete -c $bat -l config-dir -f -d "Display location of configuration directory" -n __fish_is_first_arg
|
complete -c $bat -l config-dir -f -d "Display location of configuration directory" -n __fish_is_first_arg
|
||||||
@@ -147,6 +157,8 @@ complete -c $bat -s d -l diff -d "Only show lines with Git changes" -n __bat_no_
|
|||||||
|
|
||||||
complete -c $bat -l diff-context -x -d "Show N context lines around Git changes" -n "__fish_seen_argument -s d -l diff"
|
complete -c $bat -l diff-context -x -d "Show N context lines around Git changes" -n "__fish_seen_argument -s d -l diff"
|
||||||
|
|
||||||
|
complete -c $bat -l generate-config-file -f -d "Generates a default configuration file" -n __fish_is_first_arg
|
||||||
|
|
||||||
complete -c $bat -l file-name -x -d "Specify the display name" -n __bat_no_excl_args
|
complete -c $bat -l file-name -x -d "Specify the display name" -n __bat_no_excl_args
|
||||||
|
|
||||||
complete -c $bat -s f -l force-colorization -d "Force color and decorations" -n __bat_no_excl_args
|
complete -c $bat -s f -l force-colorization -d "Force color and decorations" -n __bat_no_excl_args
|
||||||
@@ -163,6 +175,8 @@ complete -c $bat -l italic-text -x -a "$italic_text_opts" -d "When to use italic
|
|||||||
|
|
||||||
complete -c $bat -s l -l language -x -k -a "(__bat_complete_list_languages)" -d "Set the syntax highlighting language" -n __bat_no_excl_args
|
complete -c $bat -s l -l language -x -k -a "(__bat_complete_list_languages)" -d "Set the syntax highlighting language" -n __bat_no_excl_args
|
||||||
|
|
||||||
|
complete -c $bat -l lessopen -d "Enable the $LESSOPEN preprocessor" -n __fish_is_first_arg
|
||||||
|
|
||||||
complete -c $bat -s r -l line-range -x -d "Only print lines [M]:[N] (either optional)" -n __bat_no_excl_args
|
complete -c $bat -s r -l line-range -x -d "Only print lines [M]:[N] (either optional)" -n __bat_no_excl_args
|
||||||
|
|
||||||
complete -c $bat -l list-languages -f -d "List syntax highlighting languages" -n __fish_is_first_arg
|
complete -c $bat -l list-languages -f -d "List syntax highlighting languages" -n __fish_is_first_arg
|
||||||
@@ -171,6 +185,12 @@ complete -c $bat -l list-themes -f -d "List syntax highlighting themes" -n __fis
|
|||||||
|
|
||||||
complete -c $bat -s m -l map-syntax -x -a "(__bat_complete_map_syntax)" -d "Map <glob pattern>:<language syntax>" -n __bat_no_excl_args
|
complete -c $bat -s m -l map-syntax -x -a "(__bat_complete_map_syntax)" -d "Map <glob pattern>:<language syntax>" -n __bat_no_excl_args
|
||||||
|
|
||||||
|
complete -c $bat -l no-config -d "Do not use the configuration file"
|
||||||
|
|
||||||
|
complete -c $bat -l no-custom-assets -d "Do not load custom assets"
|
||||||
|
|
||||||
|
complete -c $bat -l no-lessopen -d "Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)"
|
||||||
|
|
||||||
complete -c $bat -s n -l number -d "Only show line numbers, no other decorations" -n __bat_no_excl_args
|
complete -c $bat -s n -l number -d "Only show line numbers, no other decorations" -n __bat_no_excl_args
|
||||||
|
|
||||||
complete -c $bat -l pager -x -a "$pager_opts" -d "Which pager to use" -n __bat_no_excl_args
|
complete -c $bat -l pager -x -a "$pager_opts" -d "Which pager to use" -n __bat_no_excl_args
|
||||||
@@ -191,7 +211,11 @@ 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 terminal-width -x -d "Set terminal <width>, +<offset>, or -<offset>" -n __bat_no_excl_args
|
||||||
|
|
||||||
complete -c $bat -l theme -x -a "(command $bat --list-themes | command cat)" -d "Set the syntax highlighting theme" -n __bat_no_excl_args
|
complete -c $bat -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-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-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 -s V -l version -f -d "Show version information" -n __fish_is_first_arg
|
complete -c $bat -s V -l version -f -d "Show version information" -n __fish_is_first_arg
|
||||||
|
|
||||||
|
|||||||
Vendored
+70
-48
@@ -1,19 +1,20 @@
|
|||||||
#compdef {{PROJECT_EXECUTABLE}}
|
#compdef {{PROJECT_EXECUTABLE}}
|
||||||
|
|
||||||
local context state state_descr line
|
local curcontext="$curcontext" ret=1
|
||||||
|
local -a state state_descr line
|
||||||
typeset -A opt_args
|
typeset -A opt_args
|
||||||
|
|
||||||
(( $+functions[_{{PROJECT_EXECUTABLE}}_cache_subcommand] )) ||
|
(( $+functions[_{{PROJECT_EXECUTABLE}}_cache_subcommand] )) ||
|
||||||
_{{PROJECT_EXECUTABLE}}_cache_subcommand() {
|
_{{PROJECT_EXECUTABLE}}_cache_subcommand() {
|
||||||
local -a args
|
local -a args
|
||||||
args=(
|
args=(
|
||||||
'(-b --build -c --clear)'{-b,--build}'[Initialize or update the syntax/theme cache]'
|
'(-b --build -c --clear)'{-b,--build}'[initialize or update the syntax/theme cache]'
|
||||||
'(-b --build -c --clear)'{-c,--clear}'[Remove the cached syntax definitions and themes]'
|
'(-b --build -c --clear)'{-c,--clear}'[remove the cached syntax definitions and themes]'
|
||||||
'(--source)'--source='[Use a different directory to load syntaxes and themes from]:directory:_files -/'
|
--source='[specify directory to load syntaxes and themes from]:directory:_files -/'
|
||||||
'(--target)'--target='[Use a different directory to store the cached syntax and theme set]:directory:_files -/'
|
--target='[specify directory to store the cached syntax and theme set in]:directory:_files -/'
|
||||||
'(--blank)'--blank'[Create completely new syntax and theme sets]'
|
--blank'[create completely new syntax and theme sets]'
|
||||||
'(: -)'{-h,--help}'[Prints help information]'
|
--acknowledgements'[build acknowledgements.bin]'
|
||||||
'*: :'
|
'(: -)'{-h,--help}'[show help information]'
|
||||||
)
|
)
|
||||||
|
|
||||||
_arguments -S -s $args
|
_arguments -S -s $args
|
||||||
@@ -23,67 +24,88 @@ _{{PROJECT_EXECUTABLE}}_cache_subcommand() {
|
|||||||
_{{PROJECT_EXECUTABLE}}_main() {
|
_{{PROJECT_EXECUTABLE}}_main() {
|
||||||
local -a args
|
local -a args
|
||||||
args=(
|
args=(
|
||||||
'(-A --show-all)'{-A,--show-all}'[Show non-printable characters (space, tab, newline, ..)]'
|
'(-A --show-all)'{-A,--show-all}'[show non-printable characters (space, tab, newline, ..)]'
|
||||||
'*'{-p,--plain}'[Show plain style (alias for `--style=plain`), repeat twice to disable disable automatic paging (alias for `--paging=never`)]'
|
--nonprintable-notation='[specify how to display non-printable characters when using --show-all]:notation:(caret unicode)'
|
||||||
'(-l --language)'{-l+,--language=}'[Set the language for syntax highlighting]:<language>:->language'
|
\*{-p,--plain}'[show plain style (alias for `--style=plain`), repeat twice to disable automatic paging (alias for `--paging=never`)]'
|
||||||
'(-H --highlight-line)'{-H,--highlight-line}'[Highlight lines N through M]:<N\:M>...'
|
'(-l --language)'{-l+,--language=}'[set the language for syntax highlighting]:language:->languages'
|
||||||
'(--file-name)'--file-name'[Specify the name to display for a file]:<name>...:_files'
|
\*{-H+,--highlight-line=}'[highlight specified block of lines]:start\:end'
|
||||||
'(-d --diff)'--diff'[Only show lines that have been added/removed/modified]'
|
\*--file-name='[specify the name to display for a file]:name:_files'
|
||||||
'(--diff-context)'--diff-context'[Include N lines of context around added/removed/modified lines when using `--diff`]:<N> (lines):()'
|
'(-d --diff)'--diff'[only show lines that have been added/removed/modified]'
|
||||||
'(--tabs)'--tabs'[Set the tab width to T spaces]:<T> (tab width):()'
|
--diff-context='[specify lines of context around added/removed/modified lines when using `--diff`]:lines'
|
||||||
'(--wrap)'--wrap='[Specify the text-wrapping mode]:<when>:(auto never character)'
|
--tabs='[set the tab width]:tab width [4]'
|
||||||
'(--terminal-width)'--terminal-width'[Explicitly set the width of the terminal instead of determining it automatically]:<width>'
|
--wrap='[specify the text-wrapping mode]:mode [auto]:(auto never character)'
|
||||||
'(-n --number)'{-n,--number}'[Show line numbers]'
|
'!(--wrap)'{-S,--chop-long-lines}
|
||||||
'(--color)'--color='[When to use colors]:<when>:(auto never always)'
|
--terminal-width='[explicitly set the width of the terminal instead of determining it automatically]:width'
|
||||||
'(--italic-text)'--italic-text='[Use italics in output]:<when>:(always never)'
|
'(-n --number --diff --diff-context)'{-n,--number}'[show line numbers]'
|
||||||
'(--decorations)'--decorations='[When to show the decorations]:<when>:(auto never always)'
|
--color='[specify when to use colors]:when:(auto never always)'
|
||||||
'(--paging)'--paging='[Specify when to use the pager]:<when>:(auto never always)'
|
--italic-text='[use italics in output]:when:(always never)'
|
||||||
'(-m --map-syntax)'{-m+,--map-syntax=}'[Use the specified syntax for files matching the glob pattern]:<glob\:syntax>...'
|
--decorations='[specify when to show the decorations]:when:(auto never always)'
|
||||||
'(--theme)'--theme='[Set the color theme for syntax highlighting]:<theme>:->theme'
|
--paging='[specify when to use the pager]:when:(auto never always)'
|
||||||
'(: --list-themes --list-languages -L)'--list-themes'[Display all supported highlighting themes]'
|
'(-m --map-syntax)'{-m+,--map-syntax=}'[map a glob pattern to an existing syntax name]: :->syntax-maps'
|
||||||
'(--style)'--style='[Comma-separated list of style elements to display]:<components>:->style'
|
'(--theme)'--theme='[set the color theme for syntax highlighting]:theme:->theme_preferences'
|
||||||
'(-r --line-range)'{-r+,--line-range=}'[Only print the lines from N to M]:<N\:M>...'
|
'(--theme-dark)'--theme-dark='[set the color theme for syntax highlighting for dark backgrounds]:theme:->themes'
|
||||||
'(: --list-themes --list-languages -L)'{-L,--list-languages}'[Display all supported languages]'
|
'(--theme-light)'--theme-light='[set the color theme for syntax highlighting for light backgrounds]:theme:->themes'
|
||||||
'(: --no-config)'--no-config'[Do not use the configuration file]'
|
'(: --list-themes --list-languages -L)'--list-themes'[show all supported highlighting themes]'
|
||||||
'(: --no-custom-assets)'--no-custom-assets'[Do not load custom assets]'
|
--style='[comma-separated list of style elements to display]: : _values "style [default]"
|
||||||
'(: --config-dir)'--config-dir'[Show bat'"'"'s configuration directory]'
|
default auto full plain changes header header-filename header-filesize grid rule numbers snip'
|
||||||
'(: --config-file)'--config-file'[Show path to the configuration file]'
|
\*{-r+,--line-range=}'[only print the specified line range]:start\:end'
|
||||||
'(: --generate-config-file)'--generate-config-file'[Generates a default configuration file]'
|
'(* -)'{-L,--list-languages}'[display all supported languages]'
|
||||||
'(: --cache-dir)'--cache-dir'[Show bat'"'"'s cache directory]'
|
-P'[disable paging]'
|
||||||
'(: -)'{-h,--help}'[Print this help message]'
|
"--no-config[don't use the configuration file]"
|
||||||
'(: -)'{-V,--version}'[Show version information]'
|
"--no-custom-assets[don't load custom assets]"
|
||||||
'*: :_files'
|
'(--no-lessopen)'--lessopen'[enable the $LESSOPEN preprocessor]'
|
||||||
|
'(--lessopen)'--no-lessopen'[disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)]'
|
||||||
|
'(* -)'--config-dir"[show bat's configuration directory]"
|
||||||
|
'(* -)'--config-file'[show path to the configuration file]'
|
||||||
|
'(* -)'--generate-config-file'[generate a default configuration file]'
|
||||||
|
'(* -)'--cache-dir"[show bat's cache directory]"
|
||||||
|
'(* -)'{-h,--help}'[show help information]'
|
||||||
|
'(* -)'{-V,--version}'[show version information]'
|
||||||
|
'*: :{ _files || compadd cache }'
|
||||||
)
|
)
|
||||||
|
|
||||||
_arguments -S -s $args
|
_arguments -S -s $args && ret=0
|
||||||
|
|
||||||
case "$state" in
|
case "$state" in
|
||||||
language)
|
syntax-maps)
|
||||||
|
if ! compset -P '*:'; then
|
||||||
|
_message -e patterns 'glob pattern:language'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
;& # fall-through
|
||||||
|
|
||||||
|
languages)
|
||||||
local IFS=$'\n'
|
local IFS=$'\n'
|
||||||
local -a languages
|
local -a languages
|
||||||
languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
|
languages=( $({{PROJECT_EXECUTABLE}} --list-languages | awk -F':|,' '{ for (i = 1; i <= NF; ++i) printf("%s:%s\n", $i, $1) }') )
|
||||||
|
|
||||||
_describe 'language' languages
|
_describe 'language' languages && ret=0
|
||||||
;;
|
;;
|
||||||
|
|
||||||
theme)
|
themes)
|
||||||
local IFS=$'\n'
|
local -a themes expl
|
||||||
local -a themes
|
themes=(${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||||
themes=( $({{PROJECT_EXECUTABLE}} --list-themes | sort) )
|
|
||||||
|
|
||||||
_values 'theme' $themes
|
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||||
;;
|
;;
|
||||||
|
theme_preferences)
|
||||||
|
local -a themes expl
|
||||||
|
themes=(auto dark light auto:always auto:system ${(f)"$(_call_program themes {{PROJECT_EXECUTABLE}} --list-themes)"} )
|
||||||
|
|
||||||
style)
|
_wanted themes expl 'theme' compadd -a themes && ret=0
|
||||||
_values -s , 'style' default auto full plain changes header header-filename header-filesize grid rule numbers snip
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
case $words[2] in
|
case $words[2] in
|
||||||
cache)
|
cache)
|
||||||
## Completion of the 'cache' command itself is removed for better UX
|
## Completion of the 'cache' command itself is removed for better UX
|
||||||
## See https://github.com/sharkdp/bat/issues/2085#issuecomment-1271646802
|
## See https://github.com/sharkdp/bat/issues/2085#issuecomment-1271646802
|
||||||
|
shift words
|
||||||
|
(( CURRENT-- ))
|
||||||
|
curcontext="${curcontext%:*}-${words[1]}:"
|
||||||
_{{PROJECT_EXECUTABLE}}_cache_subcommand
|
_{{PROJECT_EXECUTABLE}}_cache_subcommand
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
|||||||
Vendored
+115
-4
@@ -25,7 +25,7 @@ either '--language value', '--language=value', '-l value' or '-lvalue'.
|
|||||||
Show non\-printable characters like space, tab or newline. Use '\-\-tabs' to
|
Show non\-printable characters like space, tab or newline. Use '\-\-tabs' to
|
||||||
control the width of the tab\-placeholders.
|
control the width of the tab\-placeholders.
|
||||||
.HP
|
.HP
|
||||||
\fB\-\-nonprintable\-notation\fR
|
\fB\-\-nonprintable\-notation\fR <notation>
|
||||||
.IP
|
.IP
|
||||||
Specify how to display non-printable characters when using \-\-show\-all.
|
Specify how to display non-printable characters when using \-\-show\-all.
|
||||||
|
|
||||||
@@ -87,6 +87,10 @@ Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
|||||||
Specify the text\-wrapping mode (*auto*, never, character). The '\-\-terminal\-width' option
|
Specify the text\-wrapping mode (*auto*, never, character). The '\-\-terminal\-width' option
|
||||||
can be used in addition to control the output width.
|
can be used in addition to control the output width.
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-S\fR, \fB\-\-chop\-long\-lines\fR
|
||||||
|
.IP
|
||||||
|
Truncate all lines longer than screen width. Alias for '\-\-wrap=never'.
|
||||||
|
.HP
|
||||||
\fB\-\-terminal\-width\fR <width>
|
\fB\-\-terminal\-width\fR <width>
|
||||||
.IP
|
.IP
|
||||||
Explicitly set the width of the terminal instead of determining it automatically. If
|
Explicitly set the width of the terminal instead of determining it automatically. If
|
||||||
@@ -141,16 +145,58 @@ use -m '*.build:Python'. To highlight files named '.myignore' with the Git Ignor
|
|||||||
syntax, use -m '.myignore:Git Ignore'.
|
syntax, use -m '.myignore:Git Ignore'.
|
||||||
Note that the right-hand side is the *name* of the syntax, not a file extension.
|
Note that the right-hand side is the *name* of the syntax, not a file extension.
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-\-ignored\-suffix\fR <ignored-suffix>
|
||||||
|
.IP
|
||||||
|
Ignore extension. For example: 'bat \-\-ignored-suffix ".dev" my_file.json.dev'
|
||||||
|
will use JSON syntax, and ignore '.dev'
|
||||||
|
.HP
|
||||||
\fB\-\-theme\fR <theme>
|
\fB\-\-theme\fR <theme>
|
||||||
.IP
|
.IP
|
||||||
Set the theme for syntax highlighting. Use '\-\-list\-themes' to see all available themes.
|
Set the theme for syntax highlighting. Use \fB\-\-list\-themes\fP to see all available themes.
|
||||||
To set a default theme, add the '\-\-theme="..."' option to the configuration file or
|
To set a default theme, add the \fB\-\-theme="..."\fP option to the configuration file or
|
||||||
export the BAT_THEME environment variable (e.g.: export BAT_THEME="...").
|
export the \fBBAT_THEME\fP environment variable (e.g.: \fBexport BAT_THEME="..."\fP).
|
||||||
|
|
||||||
|
Special values:
|
||||||
|
.RS
|
||||||
|
.IP "auto (\fIdefault\fR)"
|
||||||
|
Picks a dark or light theme depending on the terminal's colors.
|
||||||
|
Use \fB-\-theme\-light\fR and \fB-\-theme\-dark\fR to customize the selected theme.
|
||||||
|
.IP "auto:always"
|
||||||
|
Variation of \fBauto\fR where where the terminal's colors are detected even when the output is redirected.
|
||||||
|
.IP "auto:system (macOS only)"
|
||||||
|
Variation of \fBauto\fR where the color scheme is detected from the system-wide preference instead.
|
||||||
|
.IP "dark"
|
||||||
|
Use the dark theme specified by \fB-\-theme-dark\fR.
|
||||||
|
.IP "light"
|
||||||
|
Use the light theme specified by \fB-\-theme-light\fR.
|
||||||
|
.RE
|
||||||
|
.HP
|
||||||
|
\fB\-\-theme\-dark\fR <theme>
|
||||||
|
.IP
|
||||||
|
Sets the theme name for syntax highlighting used when the terminal uses a dark background.
|
||||||
|
To set a default theme, add the \fB\-\-theme-dark="..."\fP option to the configuration file or
|
||||||
|
export the \fBBAT_THEME_DARK\fP environment variable (e.g. \fBexport BAT_THEME_DARK="..."\fP).
|
||||||
|
This option only has an effect when \fB\-\-theme\fP option is set to \fBauto\fR or \fBdark\fR.
|
||||||
|
.HP
|
||||||
|
\fB\-\-theme\-light\fR <theme>
|
||||||
|
.IP
|
||||||
|
Sets the theme name for syntax highlighting used when the terminal uses a dark background.
|
||||||
|
To set a default theme, add the \fB\-\-theme-dark="..."\fP option to the configuration file or
|
||||||
|
export the \fBBAT_THEME_LIGHT\fP environment variable (e.g. \fBexport BAT_THEME_LIGHT="..."\fP).
|
||||||
|
This option only has an effect when \fB\-\-theme\fP option is set to \fBauto\fR or \fBlight\fR.
|
||||||
.HP
|
.HP
|
||||||
\fB\-\-list\-themes\fR
|
\fB\-\-list\-themes\fR
|
||||||
.IP
|
.IP
|
||||||
Display a list of supported themes for syntax highlighting.
|
Display a list of supported themes for syntax highlighting.
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-s\fR, \fB\-\-squeeze\-blank\fR
|
||||||
|
.IP
|
||||||
|
Squeeze consecutive empty lines into a single empty line.
|
||||||
|
.HP
|
||||||
|
\fB\-\-squeeze\-limit\fR <squeeze-limit>
|
||||||
|
.IP
|
||||||
|
Set the maximum number of consecutive empty lines to be printed.
|
||||||
|
.HP
|
||||||
\fB\-\-style\fR <style\-components>
|
\fB\-\-style\fR <style\-components>
|
||||||
.IP
|
.IP
|
||||||
Configure which elements (line numbers, file headers, grid borders, Git modifications,
|
Configure which elements (line numbers, file headers, grid borders, Git modifications,
|
||||||
@@ -184,6 +230,30 @@ Display a list of supported languages for syntax highlighting.
|
|||||||
This option exists for POSIX\-compliance reasons ('u' is for 'unbuffered'). The output is
|
This option exists for POSIX\-compliance reasons ('u' is for 'unbuffered'). The output is
|
||||||
always unbuffered \- this option is simply ignored.
|
always unbuffered \- this option is simply ignored.
|
||||||
.HP
|
.HP
|
||||||
|
\fB\-\-no\-custom\-assets\fR
|
||||||
|
.IP
|
||||||
|
Do not load custom assets.
|
||||||
|
.HP
|
||||||
|
\fB\-\-config\-dir\fR
|
||||||
|
.IP
|
||||||
|
Show bat's configuration directory.
|
||||||
|
.HP
|
||||||
|
\fB\-\-cache\-dir\fR
|
||||||
|
.IP
|
||||||
|
Show bat's cache directory.
|
||||||
|
.HP
|
||||||
|
\fB\-\-diagnostic\fR
|
||||||
|
.IP
|
||||||
|
Show diagnostic information for bug reports.
|
||||||
|
.HP
|
||||||
|
\fB\-\-acknowledgements\fR
|
||||||
|
.IP
|
||||||
|
Show acknowledgements.
|
||||||
|
.HP
|
||||||
|
\fB\-\-set\-terminal\-title\fR
|
||||||
|
.IP
|
||||||
|
Sets terminal title to filenames when using a pager.
|
||||||
|
.HP
|
||||||
\fB\-h\fR, \fB\-\-help\fR
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
.IP
|
.IP
|
||||||
Print this help message.
|
Print this help message.
|
||||||
@@ -212,6 +282,20 @@ location of the configuration file.
|
|||||||
To generate a default configuration file, call:
|
To generate a default configuration file, call:
|
||||||
|
|
||||||
\fB{{PROJECT_EXECUTABLE}} --generate-config-file\fR
|
\fB{{PROJECT_EXECUTABLE}} --generate-config-file\fR
|
||||||
|
|
||||||
|
These are related options:
|
||||||
|
.HP
|
||||||
|
\fB\-\-config\-file\fR
|
||||||
|
.IP
|
||||||
|
Show path to the configuration file.
|
||||||
|
.HP
|
||||||
|
\fB\-\-generate-config\-file\fR
|
||||||
|
.IP
|
||||||
|
Generates a default configuration file.
|
||||||
|
.HP
|
||||||
|
\fB\-\-no\-config\fR
|
||||||
|
.IP
|
||||||
|
Do not use the configuration file.
|
||||||
.SH "ADDING CUSTOM LANGUAGES"
|
.SH "ADDING CUSTOM LANGUAGES"
|
||||||
{{PROJECT_EXECUTABLE}} supports Sublime Text \fB.sublime-syntax\fR language files, and can be
|
{{PROJECT_EXECUTABLE}} supports Sublime Text \fB.sublime-syntax\fR language files, and can be
|
||||||
customized to add additional languages to your local installation. To do this, add the \fB.sublime-syntax\fR language
|
customized to add additional languages to your local installation. To do this, add the \fB.sublime-syntax\fR language
|
||||||
@@ -243,6 +327,33 @@ If you ever want to remove the custom languages, you can clear the cache with `\
|
|||||||
Similarly to custom languages, {{PROJECT_EXECUTABLE}} supports Sublime Text \fB.tmTheme\fR themes.
|
Similarly to custom languages, {{PROJECT_EXECUTABLE}} supports Sublime Text \fB.tmTheme\fR themes.
|
||||||
These can be installed to `\fB$({{PROJECT_EXECUTABLE}} --config-dir)/themes\fR`, and are added to the cache with
|
These can be installed to `\fB$({{PROJECT_EXECUTABLE}} --config-dir)/themes\fR`, and are added to the cache with
|
||||||
`\fB{{PROJECT_EXECUTABLE}} cache --build`.
|
`\fB{{PROJECT_EXECUTABLE}} cache --build`.
|
||||||
|
|
||||||
|
.SH "INPUT PREPROCESSOR"
|
||||||
|
Much like less(1) does, {{PROJECT_EXECUTABLE}} supports input preprocessors via the LESSOPEN and LESSCLOSE environment variables.
|
||||||
|
In addition, {{PROJECT_EXECUTABLE}} attempts to be as compatible with less's preprocessor implementation as possible.
|
||||||
|
|
||||||
|
To use the preprocessor, call:
|
||||||
|
|
||||||
|
\fB{{PROJECT_EXECUTABLE}} --lessopen\fR
|
||||||
|
|
||||||
|
Alternatively, the preprocessor may be enabled by default by adding the '\-\-lessopen' option to the configuration file.
|
||||||
|
|
||||||
|
To temporarily disable the preprocessor if it is enabled by default, call:
|
||||||
|
|
||||||
|
\fB{{PROJECT_EXECUTABLE}} --no-lessopen\fR
|
||||||
|
|
||||||
|
These are related options:
|
||||||
|
.HP
|
||||||
|
\fB\-\-lessopen\fR
|
||||||
|
.IP
|
||||||
|
Enable the $LESSOPEN preprocessor.
|
||||||
|
.HP
|
||||||
|
\fB\-\-no\-lessopen\fR
|
||||||
|
.IP
|
||||||
|
Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)
|
||||||
|
.PP
|
||||||
|
For more information, see the "INPUT PREPROCESSOR" section of less(1).
|
||||||
|
|
||||||
.SH "MORE INFORMATION"
|
.SH "MORE INFORMATION"
|
||||||
|
|
||||||
For more information and up-to-date documentation, visit the {{PROJECT_EXECUTABLE}} repo:
|
For more information and up-to-date documentation, visit the {{PROJECT_EXECUTABLE}} repo:
|
||||||
|
|||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
diff --git themes/1337-Scheme/1337.tmTheme themes/1337-Scheme/1337.tmTheme
|
||||||
|
index fdff5bf..8cfc888 100644
|
||||||
|
--- themes/1337-Scheme/1337.tmTheme
|
||||||
|
+++ themes/1337-Scheme/1337.tmTheme
|
||||||
|
@@ -280,7 +280,7 @@ SOFTWARE.
|
||||||
|
<key>name</key>
|
||||||
|
<string>PHP Namespaces</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>support.other.namespace, entity.name.type.namespace</string>
|
||||||
|
+ <string>support.other.namespace, entity.name.type.namespace, entity.name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
@@ -561,7 +561,7 @@ SOFTWARE.
|
||||||
|
<key>name</key>
|
||||||
|
<string>diff.header</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>meta.diff, meta.diff.header</string>
|
||||||
|
+ <string>meta.diff, meta.diff.header, markup.heading</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
Submodule assets/syntaxes/01_Packages contains modified content
|
||||||
|
diff --git syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
|
||||||
|
index 05a4fed6..78a7bf55 100644
|
||||||
|
--- syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
|
||||||
|
+++ syntaxes/01_Packages/JavaScript/JavaScript.sublime-syntax
|
||||||
|
@@ -5,7 +5,7 @@ name: JavaScript
|
||||||
|
file_extensions:
|
||||||
|
- js
|
||||||
|
- htc
|
||||||
|
-first_line_match: ^#!\s*/.*\b(node|js)\b
|
||||||
|
+first_line_match: ^#!\s*/.*\b(node|bun|js)\b
|
||||||
|
scope: source.js
|
||||||
|
variables:
|
||||||
|
bin_digit: '[01_]'
|
||||||
+2365
File diff suppressed because one or more lines are too long
+24
-2
@@ -1,5 +1,5 @@
|
|||||||
diff --git syntaxes/01_Packages/Markdown/Markdown.sublime-syntax syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
diff --git syntaxes/01_Packages/Markdown/Markdown.sublime-syntax syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
||||||
index 19dc685d..44440c7f 100644
|
index 19dc685d..3a45ea05 100644
|
||||||
--- syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
--- syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
||||||
+++ syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
+++ syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
|
||||||
@@ -24,7 +24,6 @@ variables:
|
@@ -24,7 +24,6 @@ variables:
|
||||||
@@ -166,7 +166,29 @@ index 19dc685d..44440c7f 100644
|
|||||||
- match: ^\s*$\n?
|
- match: ^\s*$\n?
|
||||||
scope: invalid.illegal.non-terminated.bold-italic.markdown
|
scope: invalid.illegal.non-terminated.bold-italic.markdown
|
||||||
pop: true
|
pop: true
|
||||||
@@ -1152,7 +1110,7 @@ contexts:
|
@@ -1073,6 +1031,21 @@ contexts:
|
||||||
|
escape: '{{code_fence_escape}}'
|
||||||
|
escape_captures:
|
||||||
|
0: meta.code-fence.definition.end.python.markdown-gfm
|
||||||
|
+ 1: punctuation.definition.raw.code-fence.end.markdown
|
||||||
|
+ - match: |-
|
||||||
|
+ (?x)
|
||||||
|
+ {{fenced_code_block_start}}
|
||||||
|
+ ((?i:puppet))
|
||||||
|
+ {{fenced_code_block_trailing_infostring_characters}}
|
||||||
|
+ captures:
|
||||||
|
+ 0: meta.code-fence.definition.begin.puppet.markdown-gfm
|
||||||
|
+ 2: punctuation.definition.raw.code-fence.begin.markdown
|
||||||
|
+ 5: constant.other.language-name.markdown
|
||||||
|
+ embed: scope:source.puppet
|
||||||
|
+ embed_scope: markup.raw.code-fence.puppet.markdown-gfm
|
||||||
|
+ escape: '{{code_fence_escape}}'
|
||||||
|
+ escape_captures:
|
||||||
|
+ 0: meta.code-fence.definition.end.puppet.markdown-gfm
|
||||||
|
1: punctuation.definition.raw.code-fence.end.markdown
|
||||||
|
- match: |-
|
||||||
|
(?x)
|
||||||
|
@@ -1152,7 +1125,7 @@ contexts:
|
||||||
- match: |-
|
- match: |-
|
||||||
(?x)
|
(?x)
|
||||||
{{fenced_code_block_start}}
|
{{fenced_code_block_start}}
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
diff --git themes/onehalf/sublimetext/OneHalfDark.tmTheme themes/onehalf/sublimetext/OneHalfDark.tmTheme
|
||||||
|
index b16050c..b021071 100644
|
||||||
|
--- themes/onehalf/sublimetext/OneHalfDark.tmTheme
|
||||||
|
+++ themes/onehalf/sublimetext/OneHalfDark.tmTheme
|
||||||
|
@@ -28,7 +28,7 @@
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>name</key>
|
||||||
|
- <string>OneHalfLight</string>
|
||||||
|
+ <string>OneHalfDark</string>
|
||||||
|
<key>semanticClass</key>
|
||||||
|
<string>theme.dark.one_half_dark</string>
|
||||||
|
<key>uuid</key>
|
||||||
|
@@ -155,7 +155,7 @@
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>support.class, entity.name.class, entity.name.type.class</string>
|
||||||
|
+ <string>support.class, entity.name.class, entity.name.type.class, entity.name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
@@ -188,7 +188,7 @@
|
||||||
|
<key>name</key>
|
||||||
|
<string>Storage</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>storage</string>
|
||||||
|
+ <string>storage, meta.mapping.key string</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
@@ -309,7 +309,7 @@
|
||||||
|
<key>name</key>
|
||||||
|
<string>Markdown: Headings</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
||||||
|
+ <string>markup.heading punctuation.definition.heading, entity.name.section, markup.heading - text.html.markdown</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
|
@@ -660,4 +660,4 @@
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
-</plist>
|
||||||
|
\ No newline at end of file
|
||||||
|
+</plist>
|
||||||
Vendored
+18
@@ -2,6 +2,24 @@ diff --git themes/TwoDark/TwoDark.tmTheme themes/TwoDark/TwoDark.tmTheme
|
|||||||
index 87fd358..56376d3 100644
|
index 87fd358..56376d3 100644
|
||||||
--- themes/TwoDark/TwoDark.tmTheme
|
--- themes/TwoDark/TwoDark.tmTheme
|
||||||
+++ themes/TwoDark/TwoDark.tmTheme
|
+++ themes/TwoDark/TwoDark.tmTheme
|
||||||
|
@@ -125,7 +125,7 @@
|
||||||
|
<key>name</key>
|
||||||
|
<string>Classes</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>support.class, entity.name.class, entity.name.type.class</string>
|
||||||
|
+ <string>support.class, entity.name.class, entity.name.type.class, entity.name</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>foreground</key>
|
||||||
|
@@ -290,7 +290,7 @@
|
||||||
|
<key>name</key>
|
||||||
|
<string>Headings</string>
|
||||||
|
<key>scope</key>
|
||||||
|
- <string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
||||||
|
+ <string>markup.heading punctuation.definition.heading, entity.name.section, markup.heading - text.html.markdown</string>
|
||||||
|
<key>settings</key>
|
||||||
|
<dict>
|
||||||
|
<key>fontStyle</key>
|
||||||
@@ -533,7 +533,7 @@
|
@@ -533,7 +533,7 @@
|
||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Json key</string>
|
<string>Json key</string>
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
+1
Submodule assets/syntaxes/02_Extra/CFML added at b91c44a32e
+9
-10
@@ -7,14 +7,14 @@ file_extensions:
|
|||||||
- tsv
|
- tsv
|
||||||
scope: text.csv
|
scope: text.csv
|
||||||
variables:
|
variables:
|
||||||
field_separator: (?:[,;\t])
|
field_separator: (?:[,;|\t])
|
||||||
record_separator: (?:$\n?)
|
record_separator: (?:$\n?)
|
||||||
contexts:
|
contexts:
|
||||||
prototype:
|
prototype:
|
||||||
- match: (?={{record_separator}})
|
- match: (?={{record_separator}})
|
||||||
pop: true
|
pop: true
|
||||||
fields:
|
fields:
|
||||||
- match: ''
|
- match: ""
|
||||||
push:
|
push:
|
||||||
- field_or_record_separator
|
- field_or_record_separator
|
||||||
- field4
|
- field4
|
||||||
@@ -26,15 +26,15 @@ contexts:
|
|||||||
- field1
|
- field1
|
||||||
main:
|
main:
|
||||||
- meta_include_prototype: false
|
- meta_include_prototype: false
|
||||||
- match: '^'
|
- match: "^"
|
||||||
set: fields
|
set: fields
|
||||||
|
|
||||||
field_or_record_separator:
|
field_or_record_separator:
|
||||||
- meta_include_prototype: false
|
- meta_include_prototype: false
|
||||||
- match: '{{record_separator}}'
|
- match: "{{record_separator}}"
|
||||||
scope: punctuation.terminator.record.csv
|
scope: punctuation.terminator.record.csv
|
||||||
pop: true
|
pop: true
|
||||||
- match: '{{field_separator}}'
|
- match: "{{field_separator}}"
|
||||||
scope: punctuation.separator.sequence.csv
|
scope: punctuation.separator.sequence.csv
|
||||||
pop: true
|
pop: true
|
||||||
|
|
||||||
@@ -56,23 +56,22 @@ contexts:
|
|||||||
pop: true
|
pop: true
|
||||||
|
|
||||||
field1:
|
field1:
|
||||||
- match: ''
|
- match: ""
|
||||||
set:
|
set:
|
||||||
- meta_content_scope: meta.field-1.csv support.type
|
- meta_content_scope: meta.field-1.csv support.type
|
||||||
- include: field_contents
|
- include: field_contents
|
||||||
field2:
|
field2:
|
||||||
- match: ''
|
- match: ""
|
||||||
set:
|
set:
|
||||||
- meta_content_scope: meta.field-2.csv support.function
|
- meta_content_scope: meta.field-2.csv support.function
|
||||||
- include: field_contents
|
- include: field_contents
|
||||||
field3:
|
field3:
|
||||||
- match: ''
|
- match: ""
|
||||||
set:
|
set:
|
||||||
- meta_content_scope: meta.field-3.csv constant.numeric
|
- meta_content_scope: meta.field-3.csv constant.numeric
|
||||||
- include: field_contents
|
- include: field_contents
|
||||||
field4:
|
field4:
|
||||||
- match: ''
|
- match: ""
|
||||||
set:
|
set:
|
||||||
- meta_content_scope: meta.field-4.csv keyword.operator
|
- meta_content_scope: meta.field-4.csv keyword.operator
|
||||||
- include: field_contents
|
- include: field_contents
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Docker updated: 9e9a518aed...0f6b7bc87a
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Julia updated: 4fde0fdedd...3366b10be9
@@ -85,6 +85,9 @@ contexts:
|
|||||||
|
|
||||||
options:
|
options:
|
||||||
# command-line options like --option=value, --some-flag, or -x
|
# command-line options like --option=value, --some-flag, or -x
|
||||||
|
- match: '^[ ]{7}(-)(?=\s)'
|
||||||
|
captures:
|
||||||
|
1: entity.name.command-line-option.man
|
||||||
- match: '^[ ]{7}(?=-|\+)'
|
- match: '^[ ]{7}(?=-|\+)'
|
||||||
push: expect-command-line-option
|
push: expect-command-line-option
|
||||||
- match: '(?:[^a-zA-Z0-9_-]|^|\s){{command_line_option}}'
|
- match: '(?:[^a-zA-Z0-9_-]|^|\s){{command_line_option}}'
|
||||||
|
|||||||
+1
Submodule assets/syntaxes/02_Extra/NSIS added at 619a65a04e
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/Protobuf updated: 726e21d74d...1365331580
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/SublimeJQ updated: 687058289c...b7e53e5d86
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/TOML updated: fd0bf3e5d6...f5a57e8bff
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/cmd-help updated: f41e5fc838...209559b72f
Vendored
+1
-1
Submodule assets/syntaxes/02_Extra/ssh-config updated: e1012e9f13...bf49e9181c
@@ -131,6 +131,12 @@ OPTIONS
|
|||||||
# ^^ - variable
|
# ^^ - variable
|
||||||
output NUM (default 3) lines of copied context
|
output NUM (default 3) lines of copied context
|
||||||
|
|
||||||
|
- This is not really a switch, but indicates that standard input
|
||||||
|
# ^ entity.name.command-line-option.man
|
||||||
|
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - entity - variable
|
||||||
|
is coming from a file or a pipe and not interactively from the
|
||||||
|
command line.
|
||||||
|
|
||||||
EXAMPLE
|
EXAMPLE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
# ^^^^^^^^ source.c meta.preprocessor.include keyword.control.import.include
|
# ^^^^^^^^ source.c meta.preprocessor.include keyword.control.import.include
|
||||||
|
|||||||
+1
Submodule assets/syntaxes/02_Extra/vscode-wgsl added at acf26718d7
+198
@@ -0,0 +1,198 @@
|
|||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
# http://www.sublimetext.com/docs/syntax.html
|
||||||
|
name: WGSL
|
||||||
|
file_extensions:
|
||||||
|
- wgsl
|
||||||
|
scope: source.wgsl
|
||||||
|
contexts:
|
||||||
|
main:
|
||||||
|
- include: line_comments
|
||||||
|
- include: block_comments
|
||||||
|
- include: keywords
|
||||||
|
- include: attributes
|
||||||
|
- include: functions
|
||||||
|
- include: function_calls
|
||||||
|
- include: constants
|
||||||
|
- include: types
|
||||||
|
- include: variables
|
||||||
|
- include: punctuation
|
||||||
|
attributes:
|
||||||
|
- match: '(@)([A-Za-z_]+)'
|
||||||
|
comment: attribute declaration
|
||||||
|
scope: meta.attribute.wgsl
|
||||||
|
captures:
|
||||||
|
1: keyword.operator.attribute.at
|
||||||
|
2: entity.name.attribute.wgsl
|
||||||
|
block_comments:
|
||||||
|
- match: /\*\*/
|
||||||
|
comment: empty block comments
|
||||||
|
scope: comment.block.wgsl
|
||||||
|
- match: /\*\*
|
||||||
|
comment: block documentation comments
|
||||||
|
push:
|
||||||
|
- meta_scope: comment.block.documentation.wgsl
|
||||||
|
- match: \*/
|
||||||
|
pop: true
|
||||||
|
- include: block_comments
|
||||||
|
- match: /\*(?!\*)
|
||||||
|
comment: block comments
|
||||||
|
push:
|
||||||
|
- meta_scope: comment.block.wgsl
|
||||||
|
- match: \*/
|
||||||
|
pop: true
|
||||||
|
- include: block_comments
|
||||||
|
constants:
|
||||||
|
- match: '(-?\b[0-9][0-9]*\.[0-9][0-9]*)([eE][+-]?[0-9]+)?\b'
|
||||||
|
comment: decimal float literal
|
||||||
|
scope: constant.numeric.float.wgsl
|
||||||
|
- match: '-?\b0x[0-9a-fA-F]+\b|\b0\b|-?\b[1-9][0-9]*\b'
|
||||||
|
comment: int literal
|
||||||
|
scope: constant.numeric.decimal.wgsl
|
||||||
|
- match: '\b0x[0-9a-fA-F]+u\b|\b0u\b|\b[1-9][0-9]*u\b'
|
||||||
|
comment: uint literal
|
||||||
|
scope: constant.numeric.decimal.wgsl
|
||||||
|
- match: \b(true|false)\b
|
||||||
|
comment: boolean constant
|
||||||
|
scope: constant.language.boolean.wgsl
|
||||||
|
function_calls:
|
||||||
|
- match: '([A-Za-z0-9_]+)(\()'
|
||||||
|
comment: function/method calls
|
||||||
|
captures:
|
||||||
|
1: entity.name.function.wgsl
|
||||||
|
2: punctuation.brackets.round.wgsl
|
||||||
|
push:
|
||||||
|
- meta_scope: meta.function.call.wgsl
|
||||||
|
- match: \)
|
||||||
|
captures:
|
||||||
|
0: punctuation.brackets.round.wgsl
|
||||||
|
pop: true
|
||||||
|
- include: line_comments
|
||||||
|
- include: block_comments
|
||||||
|
- include: keywords
|
||||||
|
- include: attributes
|
||||||
|
- include: function_calls
|
||||||
|
- include: constants
|
||||||
|
- include: types
|
||||||
|
- include: variables
|
||||||
|
- include: punctuation
|
||||||
|
functions:
|
||||||
|
- match: '\b(fn)\s+([A-Za-z0-9_]+)((\()|(<))'
|
||||||
|
comment: function definition
|
||||||
|
captures:
|
||||||
|
1: keyword.other.fn.wgsl
|
||||||
|
2: entity.name.function.wgsl
|
||||||
|
4: punctuation.brackets.round.wgsl
|
||||||
|
push:
|
||||||
|
- meta_scope: meta.function.definition.wgsl
|
||||||
|
- match: '\{'
|
||||||
|
captures:
|
||||||
|
0: punctuation.brackets.curly.wgsl
|
||||||
|
pop: true
|
||||||
|
- include: line_comments
|
||||||
|
- include: block_comments
|
||||||
|
- include: keywords
|
||||||
|
- include: attributes
|
||||||
|
- include: function_calls
|
||||||
|
- include: constants
|
||||||
|
- include: types
|
||||||
|
- include: variables
|
||||||
|
- include: punctuation
|
||||||
|
keywords:
|
||||||
|
- match: \b(bitcast|block|break|case|continue|continuing|default|discard|else|elseif|enable|fallthrough|for|function|if|loop|private|read|read_write|return|storage|switch|uniform|while|workgroup|write)\b
|
||||||
|
comment: other keywords
|
||||||
|
scope: keyword.control.wgsl
|
||||||
|
- match: \b(asm|const|do|enum|handle|mat|premerge|regardless|typedef|unless|using|vec|void)\b
|
||||||
|
comment: reserved keywords
|
||||||
|
scope: keyword.control.wgsl
|
||||||
|
- match: \b(let|var)\b
|
||||||
|
comment: storage keywords
|
||||||
|
scope: keyword.other.wgsl storage.type.wgsl
|
||||||
|
- match: \b(type)\b
|
||||||
|
comment: type keyword
|
||||||
|
scope: keyword.declaration.type.wgsl storage.type.wgsl
|
||||||
|
- match: \b(enum)\b
|
||||||
|
comment: enum keyword
|
||||||
|
scope: keyword.declaration.enum.wgsl storage.type.wgsl
|
||||||
|
- match: \b(struct)\b
|
||||||
|
comment: struct keyword
|
||||||
|
scope: keyword.declaration.struct.wgsl storage.type.wgsl
|
||||||
|
- match: \bfn\b
|
||||||
|
comment: fn
|
||||||
|
scope: keyword.other.fn.wgsl
|
||||||
|
- match: (\^|\||\|\||&&|<<|>>|!)(?!=)
|
||||||
|
comment: logical operators
|
||||||
|
scope: keyword.operator.logical.wgsl
|
||||||
|
- match: '&(?![&=])'
|
||||||
|
comment: logical AND, borrow references
|
||||||
|
scope: keyword.operator.borrow.and.wgsl
|
||||||
|
- match: (\+=|-=|\*=|/=|%=|\^=|&=|\|=|<<=|>>=)
|
||||||
|
comment: assignment operators
|
||||||
|
scope: keyword.operator.assignment.wgsl
|
||||||
|
- match: '(?<![<>])=(?!=|>)'
|
||||||
|
comment: single equal
|
||||||
|
scope: keyword.operator.assignment.equal.wgsl
|
||||||
|
- match: (=(=)?(?!>)|!=|<=|(?<!=)>=)
|
||||||
|
comment: comparison operators
|
||||||
|
scope: keyword.operator.comparison.wgsl
|
||||||
|
- match: '(([+%]|(\*(?!\w)))(?!=))|(-(?!>))|(/(?!/))'
|
||||||
|
comment: math operators
|
||||||
|
scope: keyword.operator.math.wgsl
|
||||||
|
- match: \.(?!\.)
|
||||||
|
comment: dot access
|
||||||
|
scope: keyword.operator.access.dot.wgsl
|
||||||
|
- match: '->'
|
||||||
|
comment: dashrocket, skinny arrow
|
||||||
|
scope: keyword.operator.arrow.skinny.wgsl
|
||||||
|
line_comments:
|
||||||
|
- match: \s*//.*
|
||||||
|
comment: single line comment
|
||||||
|
scope: comment.line.double-slash.wgsl
|
||||||
|
punctuation:
|
||||||
|
- match: ','
|
||||||
|
comment: comma
|
||||||
|
scope: punctuation.comma.wgsl
|
||||||
|
- match: '[{}]'
|
||||||
|
comment: curly braces
|
||||||
|
scope: punctuation.brackets.curly.wgsl
|
||||||
|
- match: '[()]'
|
||||||
|
comment: parentheses, round brackets
|
||||||
|
scope: punctuation.brackets.round.wgsl
|
||||||
|
- match: ;
|
||||||
|
comment: semicolon
|
||||||
|
scope: punctuation.semi.wgsl
|
||||||
|
- match: '[\[\]]'
|
||||||
|
comment: square brackets
|
||||||
|
scope: punctuation.brackets.square.wgsl
|
||||||
|
- match: '(?<![=-])[<>]'
|
||||||
|
comment: angle brackets
|
||||||
|
scope: punctuation.brackets.angle.wgsl
|
||||||
|
types:
|
||||||
|
- match: \b(bool|i32|u32|f32)\b
|
||||||
|
comment: scalar Types
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: \b(i64|u64|f64)\b
|
||||||
|
comment: reserved scalar Types
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: \b(vec2i|vec3i|vec4i|vec2u|vec3u|vec4u|vec2f|vec3f|vec4f|vec2h|vec3h|vec4h)\b
|
||||||
|
comment: vector type aliasses
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: \b(mat2x2f|mat2x3f|mat2x4f|mat3x2f|mat3x3f|mat3x4f|mat4x2f|mat4x3f|mat4x4f|mat2x2h|mat2x3h|mat2x4h|mat3x2h|mat3x3h|mat3x4h|mat4x2h|mat4x3h|mat4x4h)\b
|
||||||
|
comment: matrix type aliasses
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: '\b(vec[2-4]|mat[2-4]x[2-4])\b'
|
||||||
|
comment: vector/matrix types
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: \b(atomic)\b
|
||||||
|
comment: atomic types
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: \b(array)\b
|
||||||
|
comment: array types
|
||||||
|
scope: storage.type.wgsl
|
||||||
|
- match: '\b([A-Z][A-Za-z0-9]*)\b'
|
||||||
|
comment: Custom type
|
||||||
|
scope: entity.name.type.wgsl
|
||||||
|
variables:
|
||||||
|
- match: '\b(?<!(?<!\.)\.)(?:r#(?!(crate|[Ss]elf|super)))?[a-z0-9_]+\b'
|
||||||
|
comment: variables
|
||||||
|
scope: variable.other.wgsl
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
+1
-1
Submodule assets/themes/Nord-sublime updated: 0d655b23d6...bf92a9e445
Vendored
+3
-3
@@ -69,7 +69,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Labels</string>
|
<string>Labels</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>entity.name.label</string>
|
<string>entity.name.label, variable.parameter</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>foreground</key>
|
<key>foreground</key>
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Classes</string>
|
<string>Classes</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>support.class, entity.name.class, entity.name.type.class</string>
|
<string>support.class, entity.name.class, entity.name.type.class, entity.name</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>foreground</key>
|
<key>foreground</key>
|
||||||
@@ -234,7 +234,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Headings</string>
|
<string>Headings</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
<string>markup.heading punctuation.definition.heading, entity.name.section, markup.heading - text.html.markdown</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>fontStyle</key>
|
<key>fontStyle</key>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -257,7 +257,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Tags</string>
|
<string>Tags</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>entity.name.tag</string>
|
<string>entity.name.tag, entity.name</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>foreground</key>
|
<key>foreground</key>
|
||||||
@@ -312,7 +312,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Headings</string>
|
<string>Headings</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
<string>markup.heading punctuation.definition.heading, entity.name.section, markup.heading - text.html.markdown</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>fontStyle</key>
|
<key>fontStyle</key>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -256,7 +256,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Tags</string>
|
<string>Tags</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>entity.name.tag</string>
|
<string>entity.name.tag, entity.name</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>foreground</key>
|
<key>foreground</key>
|
||||||
@@ -311,7 +311,7 @@
|
|||||||
<key>name</key>
|
<key>name</key>
|
||||||
<string>Headings</string>
|
<string>Headings</string>
|
||||||
<key>scope</key>
|
<key>scope</key>
|
||||||
<string>markup.heading punctuation.definition.heading, entity.name.section</string>
|
<string>markup.heading punctuation.definition.heading, entity.name.section, markup.heading - text.html.markdown</string>
|
||||||
<key>settings</key>
|
<key>settings</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>fontStyle</key>
|
<key>fontStyle</key>
|
||||||
|
|||||||
Vendored
+1
-1
Submodule assets/themes/sublime-snazzy updated: 70343201f1...48f43a7350
Vendored
+1
-1
Submodule assets/themes/zenburn updated: 43dc527731...86d4ee7a1f
@@ -1,106 +0,0 @@
|
|||||||
// TODO: Re-enable generation of shell completion files (below) when clap 3 is out.
|
|
||||||
// For more details, see https://github.com/sharkdp/bat/issues/372
|
|
||||||
|
|
||||||
// For bat-as-a-library, no build script is required. The build script is for
|
|
||||||
// the manpage and completions, which are only relevant to the bat application.
|
|
||||||
#[cfg(not(feature = "application"))]
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
#[cfg(feature = "application")]
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
// Read environment variables.
|
|
||||||
let project_name = option_env!("PROJECT_NAME").unwrap_or("bat");
|
|
||||||
let executable_name = option_env!("PROJECT_EXECUTABLE").unwrap_or(project_name);
|
|
||||||
let executable_name_uppercase = executable_name.to_uppercase();
|
|
||||||
static PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
||||||
|
|
||||||
/// Generates a file from a template.
|
|
||||||
fn template(
|
|
||||||
variables: &HashMap<&str, &str>,
|
|
||||||
in_file: &str,
|
|
||||||
out_file: impl AsRef<Path>,
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
let mut content = fs::read_to_string(in_file)?;
|
|
||||||
|
|
||||||
for (variable_name, value) in variables {
|
|
||||||
// Replace {{variable_name}} by the value
|
|
||||||
let pattern = format!("{{{{{variable_name}}}}}", variable_name = variable_name);
|
|
||||||
content = content.replace(&pattern, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::write(out_file, content)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut variables = HashMap::new();
|
|
||||||
variables.insert("PROJECT_NAME", project_name);
|
|
||||||
variables.insert("PROJECT_EXECUTABLE", executable_name);
|
|
||||||
variables.insert("PROJECT_EXECUTABLE_UPPERCASE", &executable_name_uppercase);
|
|
||||||
variables.insert("PROJECT_VERSION", PROJECT_VERSION);
|
|
||||||
|
|
||||||
let out_dir_env = std::env::var_os("OUT_DIR").expect("OUT_DIR to be set in build.rs");
|
|
||||||
let out_dir = Path::new(&out_dir_env);
|
|
||||||
|
|
||||||
fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
|
|
||||||
fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
|
|
||||||
|
|
||||||
template(
|
|
||||||
&variables,
|
|
||||||
"assets/manual/bat.1.in",
|
|
||||||
out_dir.join("assets/manual/bat.1"),
|
|
||||||
)?;
|
|
||||||
template(
|
|
||||||
&variables,
|
|
||||||
"assets/completions/bat.bash.in",
|
|
||||||
out_dir.join("assets/completions/bat.bash"),
|
|
||||||
)?;
|
|
||||||
template(
|
|
||||||
&variables,
|
|
||||||
"assets/completions/bat.fish.in",
|
|
||||||
out_dir.join("assets/completions/bat.fish"),
|
|
||||||
)?;
|
|
||||||
template(
|
|
||||||
&variables,
|
|
||||||
"assets/completions/_bat.ps1.in",
|
|
||||||
out_dir.join("assets/completions/_bat.ps1"),
|
|
||||||
)?;
|
|
||||||
template(
|
|
||||||
&variables,
|
|
||||||
"assets/completions/bat.zsh.in",
|
|
||||||
out_dir.join("assets/completions/bat.zsh"),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[macro_use]
|
|
||||||
// extern crate clap;
|
|
||||||
|
|
||||||
// use clap::Shell;
|
|
||||||
// use std::fs;
|
|
||||||
|
|
||||||
// include!("src/clap_app.rs");
|
|
||||||
|
|
||||||
// const BIN_NAME: &str = "bat";
|
|
||||||
|
|
||||||
// fn main() {
|
|
||||||
// let outdir = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
|
|
||||||
|
|
||||||
// let outdir = match outdir {
|
|
||||||
// None => return,
|
|
||||||
// Some(outdir) => outdir,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// fs::create_dir_all(&outdir).unwrap();
|
|
||||||
|
|
||||||
// let mut app = build_app(true);
|
|
||||||
// app.gen_completions(BIN_NAME, Shell::Bash, &outdir);
|
|
||||||
// app.gen_completions(BIN_NAME, Shell::Fish, &outdir);
|
|
||||||
// app.gen_completions(BIN_NAME, Shell::Zsh, &outdir);
|
|
||||||
// app.gen_completions(BIN_NAME, Shell::PowerShell, &outdir);
|
|
||||||
// }
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::util::render_template;
|
||||||
|
|
||||||
|
/// Generate manpage and shell completions for the bat application.
|
||||||
|
pub fn gen_man_and_comp() -> anyhow::Result<()> {
|
||||||
|
println!("cargo:rerun-if-changed=assets/manual/");
|
||||||
|
println!("cargo:rerun-if-changed=assets/completions/");
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-env-changed=PROJECT_NAME");
|
||||||
|
println!("cargo:rerun-if-env-changed=PROJECT_EXECUTABLE");
|
||||||
|
println!("cargo:rerun-if-env-changed=CARGO_PKG_VERSION");
|
||||||
|
println!("cargo:rerun-if-env-changed=BAT_ASSETS_GEN_DIR");
|
||||||
|
|
||||||
|
// Read environment variables.
|
||||||
|
let project_name = env::var("PROJECT_NAME").unwrap_or("bat".into());
|
||||||
|
let executable_name = env::var("PROJECT_EXECUTABLE").unwrap_or(project_name.clone());
|
||||||
|
let executable_name_uppercase = executable_name.to_uppercase();
|
||||||
|
let project_version = env::var("CARGO_PKG_VERSION")?;
|
||||||
|
|
||||||
|
let variables = [
|
||||||
|
("PROJECT_NAME", project_name),
|
||||||
|
("PROJECT_EXECUTABLE", executable_name),
|
||||||
|
("PROJECT_EXECUTABLE_UPPERCASE", executable_name_uppercase),
|
||||||
|
("PROJECT_VERSION", project_version),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let Some(out_dir) = env::var_os("BAT_ASSETS_GEN_DIR")
|
||||||
|
.or_else(|| env::var_os("OUT_DIR"))
|
||||||
|
.map(PathBuf::from)
|
||||||
|
else {
|
||||||
|
anyhow::bail!("BAT_ASSETS_GEN_DIR or OUT_DIR should be set for build.rs");
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
|
||||||
|
fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
|
||||||
|
|
||||||
|
render_template(
|
||||||
|
&variables,
|
||||||
|
"assets/manual/bat.1.in",
|
||||||
|
out_dir.join("assets/manual/bat.1"),
|
||||||
|
)?;
|
||||||
|
render_template(
|
||||||
|
&variables,
|
||||||
|
"assets/completions/bat.bash.in",
|
||||||
|
out_dir.join("assets/completions/bat.bash"),
|
||||||
|
)?;
|
||||||
|
render_template(
|
||||||
|
&variables,
|
||||||
|
"assets/completions/bat.fish.in",
|
||||||
|
out_dir.join("assets/completions/bat.fish"),
|
||||||
|
)?;
|
||||||
|
render_template(
|
||||||
|
&variables,
|
||||||
|
"assets/completions/_bat.ps1.in",
|
||||||
|
out_dir.join("assets/completions/_bat.ps1"),
|
||||||
|
)?;
|
||||||
|
render_template(
|
||||||
|
&variables,
|
||||||
|
"assets/completions/bat.zsh.in",
|
||||||
|
out_dir.join("assets/completions/bat.zsh"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=BAT_GENERATED_COMPLETION_BASH={}",
|
||||||
|
out_dir.join("assets/completions/bat.bash").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=BAT_GENERATED_COMPLETION_FISH={}",
|
||||||
|
out_dir.join("assets/completions/bat.fish").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=BAT_GENERATED_COMPLETION_PS1={}",
|
||||||
|
out_dir.join("assets/completions/_bat.ps1").display()
|
||||||
|
);
|
||||||
|
println!(
|
||||||
|
"cargo:rustc-env=BAT_GENERATED_COMPLETION_ZSH={}",
|
||||||
|
out_dir.join("assets/completions/bat.zsh").display()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#[cfg(feature = "application")]
|
||||||
|
mod application;
|
||||||
|
mod syntax_mapping;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
// only watch manually-designated files
|
||||||
|
// see: https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
|
||||||
|
println!("cargo:rerun-if-changed=build/");
|
||||||
|
|
||||||
|
syntax_mapping::build_static_mappings()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "application")]
|
||||||
|
application::gen_man_and_comp()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, bail};
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde_derive::Deserialize;
|
||||||
|
use serde_with::DeserializeFromStr;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
/// Known mapping targets.
|
||||||
|
///
|
||||||
|
/// Corresponds to `syntax_mapping::MappingTarget`.
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash, DeserializeFromStr)]
|
||||||
|
pub enum MappingTarget {
|
||||||
|
MapTo(String),
|
||||||
|
MapToUnknown,
|
||||||
|
MapExtensionToUnknown,
|
||||||
|
}
|
||||||
|
impl FromStr for MappingTarget {
|
||||||
|
type Err = Infallible;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"MappingTarget::MapToUnknown" => Ok(Self::MapToUnknown),
|
||||||
|
"MappingTarget::MapExtensionToUnknown" => Ok(Self::MapExtensionToUnknown),
|
||||||
|
syntax => Ok(Self::MapTo(syntax.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl MappingTarget {
|
||||||
|
fn codegen(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::MapTo(syntax) => format!(r###"MappingTarget::MapTo(r#"{syntax}"#)"###),
|
||||||
|
Self::MapToUnknown => "MappingTarget::MapToUnknown".into(),
|
||||||
|
Self::MapExtensionToUnknown => "MappingTarget::MapExtensionToUnknown".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, DeserializeFromStr)]
|
||||||
|
/// A single matcher.
|
||||||
|
///
|
||||||
|
/// Codegen converts this into a `Lazy<Option<GlobMatcher>>`.
|
||||||
|
struct Matcher(Vec<MatcherSegment>);
|
||||||
|
/// Parse a matcher.
|
||||||
|
///
|
||||||
|
/// Note that this implementation is rather strict: it will greedily interpret
|
||||||
|
/// every valid environment variable replacement as such, then immediately
|
||||||
|
/// hard-error if it finds a '$' anywhere in the remaining text segments.
|
||||||
|
///
|
||||||
|
/// The reason for this strictness is I currently cannot think of a valid reason
|
||||||
|
/// why you would ever need '$' as plaintext in a glob pattern. Therefore any
|
||||||
|
/// such occurrences are likely human errors.
|
||||||
|
///
|
||||||
|
/// If we later discover some edge cases, it's okay to make it more permissive.
|
||||||
|
///
|
||||||
|
/// Revision history:
|
||||||
|
/// - 2024-02-20: allow `{` and `}` (glob brace expansion)
|
||||||
|
impl FromStr for Matcher {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
use MatcherSegment as Seg;
|
||||||
|
static VAR_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\$\{([\w\d_]+)\}").unwrap());
|
||||||
|
|
||||||
|
let mut segments = vec![];
|
||||||
|
let mut text_start = 0;
|
||||||
|
for capture in VAR_REGEX.captures_iter(s) {
|
||||||
|
let match_0 = capture.get(0).unwrap();
|
||||||
|
|
||||||
|
// text before this var
|
||||||
|
let text_end = match_0.start();
|
||||||
|
segments.push(Seg::Text(s[text_start..text_end].into()));
|
||||||
|
text_start = match_0.end();
|
||||||
|
|
||||||
|
// this var
|
||||||
|
segments.push(Seg::Env(capture.get(1).unwrap().as_str().into()));
|
||||||
|
}
|
||||||
|
// possible trailing text
|
||||||
|
segments.push(Seg::Text(s[text_start..].into()));
|
||||||
|
|
||||||
|
// cleanup empty text segments
|
||||||
|
let non_empty_segments = segments
|
||||||
|
.into_iter()
|
||||||
|
.filter(|seg| seg.text().map(|t| !t.is_empty()).unwrap_or(true))
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
if non_empty_segments
|
||||||
|
.windows(2)
|
||||||
|
.any(|segs| segs[0].is_text() && segs[1].is_text())
|
||||||
|
{
|
||||||
|
unreachable!("Parsed into consecutive text segments: {non_empty_segments:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// guard empty case
|
||||||
|
if non_empty_segments.is_empty() {
|
||||||
|
bail!(r#"Parsed an empty matcher: "{s}""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
// guard variable syntax leftover fragments
|
||||||
|
if non_empty_segments
|
||||||
|
.iter()
|
||||||
|
.filter_map(Seg::text)
|
||||||
|
.any(|t| t.contains('$'))
|
||||||
|
{
|
||||||
|
bail!(r#"Invalid matcher: "{s}""#);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self(non_empty_segments))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Matcher {
|
||||||
|
fn codegen(&self) -> String {
|
||||||
|
match self.0.len() {
|
||||||
|
0 => unreachable!("0-length matcher should never be created"),
|
||||||
|
// if-let guard would be ideal here
|
||||||
|
// see: https://github.com/rust-lang/rust/issues/51114
|
||||||
|
1 if self.0[0].is_text() => {
|
||||||
|
let s = self.0[0].text().unwrap();
|
||||||
|
format!(r###"Lazy::new(|| Some(build_matcher_fixed(r#"{s}"#)))"###)
|
||||||
|
}
|
||||||
|
// parser logic ensures that this case can only happen when there are dynamic segments
|
||||||
|
_ => {
|
||||||
|
let segs = self.0.iter().map(MatcherSegment::codegen).join(", ");
|
||||||
|
format!(r###"Lazy::new(|| build_matcher_dynamic(&[{segs}]))"###)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A segment in a matcher.
|
||||||
|
///
|
||||||
|
/// Corresponds to `syntax_mapping::MatcherSegment`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum MatcherSegment {
|
||||||
|
Text(String),
|
||||||
|
Env(String),
|
||||||
|
}
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl MatcherSegment {
|
||||||
|
fn is_text(&self) -> bool {
|
||||||
|
matches!(self, Self::Text(_))
|
||||||
|
}
|
||||||
|
fn is_env(&self) -> bool {
|
||||||
|
matches!(self, Self::Env(_))
|
||||||
|
}
|
||||||
|
fn text(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Text(t) => Some(t),
|
||||||
|
Self::Env(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn env(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
Self::Text(_) => None,
|
||||||
|
Self::Env(t) => Some(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn codegen(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Text(s) => format!(r###"MatcherSegment::Text(r#"{s}"#)"###),
|
||||||
|
Self::Env(s) => format!(r###"MatcherSegment::Env(r#"{s}"#)"###),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct that models a single .toml file in /src/syntax_mapping/builtins/.
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
struct MappingDefModel {
|
||||||
|
mappings: IndexMap<MappingTarget, Vec<Matcher>>,
|
||||||
|
}
|
||||||
|
impl MappingDefModel {
|
||||||
|
fn into_mapping_list(self) -> MappingList {
|
||||||
|
let list = self
|
||||||
|
.mappings
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|(target, matchers)| {
|
||||||
|
matchers
|
||||||
|
.into_iter()
|
||||||
|
.map(|matcher| (matcher, target.clone()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
MappingList(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct MappingList(Vec<(Matcher, MappingTarget)>);
|
||||||
|
impl MappingList {
|
||||||
|
fn codegen(&self) -> String {
|
||||||
|
let array_items: Vec<_> = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|(matcher, target)| {
|
||||||
|
format!("({m}, {t})", m = matcher.codegen(), t = target.codegen())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let len = array_items.len();
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"/// Generated by build script from /src/syntax_mapping/builtins/.\n\
|
||||||
|
pub(crate) static BUILTIN_MAPPINGS: [(Lazy<Option<GlobMatcher>>, MappingTarget); {len}] = [\n{items}\n];",
|
||||||
|
items = array_items.join(",\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the list of paths to all mapping definition files that should be
|
||||||
|
/// included for the current target platform.
|
||||||
|
fn get_def_paths() -> anyhow::Result<Vec<PathBuf>> {
|
||||||
|
let source_subdirs = [
|
||||||
|
"common",
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
"unix-family",
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "macos"
|
||||||
|
))]
|
||||||
|
"bsd-family",
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
"linux",
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
"macos",
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
"windows",
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut toml_paths = vec![];
|
||||||
|
for subdir in source_subdirs {
|
||||||
|
let wd = WalkDir::new(Path::new("src/syntax_mapping/builtins").join(subdir));
|
||||||
|
let paths = wd
|
||||||
|
.into_iter()
|
||||||
|
.filter_map_ok(|entry| {
|
||||||
|
let path = entry.path();
|
||||||
|
(path.is_file() && path.extension().map(|ext| ext == "toml").unwrap_or(false))
|
||||||
|
.then(|| path.to_owned())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
toml_paths.extend(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
toml_paths.sort_by_key(|path| {
|
||||||
|
path.file_name()
|
||||||
|
.expect("file name should not terminate in ..")
|
||||||
|
.to_owned()
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(toml_paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_all_mappings() -> anyhow::Result<MappingList> {
|
||||||
|
let mut all_mappings = vec![];
|
||||||
|
|
||||||
|
for path in get_def_paths()? {
|
||||||
|
let toml_string = fs::read_to_string(path)?;
|
||||||
|
let mappings = toml::from_str::<MappingDefModel>(&toml_string)?.into_mapping_list();
|
||||||
|
all_mappings.extend(mappings.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let duplicates = all_mappings
|
||||||
|
.iter()
|
||||||
|
.duplicates_by(|(matcher, _)| matcher)
|
||||||
|
.collect_vec();
|
||||||
|
if !duplicates.is_empty() {
|
||||||
|
bail!("Rules with duplicate matchers found: {duplicates:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(MappingList(all_mappings))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the static syntax mappings defined in /src/syntax_mapping/builtins/
|
||||||
|
/// into a .rs source file, which is to be inserted with `include!`.
|
||||||
|
pub fn build_static_mappings() -> anyhow::Result<()> {
|
||||||
|
println!("cargo:rerun-if-changed=src/syntax_mapping/builtins/");
|
||||||
|
|
||||||
|
let mappings = read_all_mappings()?;
|
||||||
|
|
||||||
|
let codegen_path = Path::new(&env::var_os("OUT_DIR").ok_or(anyhow!("OUT_DIR is unset"))?)
|
||||||
|
.join("codegen_static_syntax_mappings.rs");
|
||||||
|
|
||||||
|
fs::write(codegen_path, mappings.codegen())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::{collections::HashMap, fs, path::Path};
|
||||||
|
|
||||||
|
/// Generates a file from a template.
|
||||||
|
pub fn render_template(
|
||||||
|
variables: &HashMap<&str, String>,
|
||||||
|
in_file: &str,
|
||||||
|
out_file: impl AsRef<Path>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut content = fs::read_to_string(in_file)?;
|
||||||
|
|
||||||
|
for (variable_name, value) in variables {
|
||||||
|
// Replace {{variable_name}} by the value
|
||||||
|
let pattern = format!("{{{{{variable_name}}}}}");
|
||||||
|
content = content.replace(&pattern, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(out_file, content)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+3
-3
@@ -181,7 +181,7 @@ man 2 select
|
|||||||
|
|
||||||
## インストール
|
## インストール
|
||||||
|
|
||||||
[](https://repology.org/project/bat-cat/versions)
|
[](https://repology.org/project/bat-cat/versions)
|
||||||
|
|
||||||
### On Ubuntu (`apt` を使用)
|
### On Ubuntu (`apt` を使用)
|
||||||
*... や他のDebianベースのLinuxディストリビューション*
|
*... や他のDebianベースのLinuxディストリビューション*
|
||||||
@@ -219,7 +219,7 @@ apk add bat
|
|||||||
|
|
||||||
### On Arch Linux
|
### On Arch Linux
|
||||||
|
|
||||||
[Arch Linuxの公式リソース](https://www.archlinux.org/packages/community/x86_64/bat/)
|
[Arch Linuxの公式リソース](https://www.archlinux.org/packages/extra/x86_64/bat/)
|
||||||
からインストールできます。
|
からインストールできます。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -366,7 +366,7 @@ ansible-galaxy install aeimer.install_bat
|
|||||||
### From source
|
### From source
|
||||||
|
|
||||||
|
|
||||||
`bat` をソースからビルドしたいならば、Rust 1.64.0 以上の環境が必要です。
|
`bat` をソースからビルドしたいならば、Rust 1.74.0 以上の環境が必要です。
|
||||||
`cargo` を使用してビルドすることができます:
|
`cargo` を使用してビルドすることができます:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+3
-3
@@ -214,7 +214,7 @@ man 2 select
|
|||||||
|
|
||||||
## 설치
|
## 설치
|
||||||
|
|
||||||
[](https://repology.org/project/bat-cat/versions)
|
[](https://repology.org/project/bat-cat/versions)
|
||||||
|
|
||||||
### Ubuntu에서 (`apt` 사용)
|
### Ubuntu에서 (`apt` 사용)
|
||||||
*... 그리고 다른 Debian 기반의 Linux 배포판들에서.*
|
*... 그리고 다른 Debian 기반의 Linux 배포판들에서.*
|
||||||
@@ -264,7 +264,7 @@ apk add bat
|
|||||||
### Arch Linux에서
|
### Arch Linux에서
|
||||||
|
|
||||||
공식 소스를 통해
|
공식 소스를 통해
|
||||||
[`bat` 패키지](https://www.archlinux.org/packages/community/x86_64/bat/)를
|
[`bat` 패키지](https://www.archlinux.org/packages/extra/x86_64/bat/)를
|
||||||
설치할 수 있습니다:
|
설치할 수 있습니다:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -416,7 +416,7 @@ scoop install bat
|
|||||||
|
|
||||||
### 소스에서
|
### 소스에서
|
||||||
|
|
||||||
`bat`의 소스를 빌드하기 위해서는, Rust 1.64.0 이상이 필요합니다.
|
`bat`의 소스를 빌드하기 위해서는, Rust 1.74.0 이상이 필요합니다.
|
||||||
`cargo`를 이용해 전부 빌드할 수 있습니다:
|
`cargo`를 이용해 전부 빌드할 수 있습니다:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+11
-11
@@ -12,7 +12,7 @@
|
|||||||
<a href="#установка">Установка</a> •
|
<a href="#установка">Установка</a> •
|
||||||
<a href="#кастомизация">Кастомизация</a> •
|
<a href="#кастомизация">Кастомизация</a> •
|
||||||
<a href="#цели-и-альтернативы">Цели и альтернативы </a><br>
|
<a href="#цели-и-альтернативы">Цели и альтернативы </a><br>
|
||||||
[<a href="../README.md">English]
|
[<a href="../README.md">English</a>]
|
||||||
[<a href="README-zh.md">中文</a>]
|
[<a href="README-zh.md">中文</a>]
|
||||||
[<a href="README-ja.md">日本語</a>]
|
[<a href="README-ja.md">日本語</a>]
|
||||||
[<a href="README-ko.md">한국어</a>]
|
[<a href="README-ko.md">한국어</a>]
|
||||||
@@ -130,8 +130,8 @@ git show v0.6.0:src/main.rs | bat -l rs
|
|||||||
|
|
||||||
#### `xclip`
|
#### `xclip`
|
||||||
|
|
||||||
Нумерация стро и отображение изменений затрудняет копирование содержимого файлов в буфер обмена.
|
Нумерация строк и отображение изменений затрудняет копирование содержимого файлов в буфер обмена.
|
||||||
Чтобы спроваиться с этим, используйте флаг `-p`/`--plain` или просто перенаправьте стандартный вывод в `xclip`:
|
Чтобы справиться с этим, используйте флаг `-p`/`--plain` или просто перенаправьте стандартный вывод в `xclip`:
|
||||||
```bash
|
```bash
|
||||||
bat main.cpp | xclip
|
bat main.cpp | xclip
|
||||||
```
|
```
|
||||||
@@ -160,7 +160,7 @@ man 2 select
|
|||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
[](https://repology.org/project/bat-cat/versions)
|
[](https://repology.org/project/bat-cat/versions)
|
||||||
|
|
||||||
### Ubuntu (с помощью `apt`)
|
### Ubuntu (с помощью `apt`)
|
||||||
*... и другие дистрибутивы основанные на Debian.*
|
*... и другие дистрибутивы основанные на Debian.*
|
||||||
@@ -201,7 +201,7 @@ apk add bat
|
|||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
Вы можете установить [`bat`](https://www.archlinux.org/packages/community/x86_64/bat/) из официального источника:
|
Вы можете установить [`bat`](https://www.archlinux.org/packages/extra/x86_64/bat/) из официального источника:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pacman -S bat
|
pacman -S bat
|
||||||
@@ -344,7 +344,7 @@ ansible-galaxy install aeimer.install_bat
|
|||||||
|
|
||||||
### Из исходников
|
### Из исходников
|
||||||
|
|
||||||
Если вы желаете установить `bat` из исходников, вам понадобится Rust 1.64.0 или выше. После этого используйте `cargo`, чтобы все скомпилировать:
|
Если вы желаете установить `bat` из исходников, вам понадобится Rust 1.74.0 или выше. После этого используйте `cargo`, чтобы все скомпилировать:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo install --locked bat
|
cargo install --locked bat
|
||||||
@@ -487,7 +487,7 @@ bat --generate-config-file
|
|||||||
# Использовать синтаксис C++ для всех Arduino .ino файлов
|
# Использовать синтаксис C++ для всех Arduino .ino файлов
|
||||||
--map-syntax "*.ino:C++"
|
--map-syntax "*.ino:C++"
|
||||||
|
|
||||||
# Использовать синтаксик Git Ignore для всех файлов .ignore
|
# Использовать синтаксис Git Ignore для всех файлов .ignore
|
||||||
--map-syntax ".ignore:Git Ignore"
|
--map-syntax ".ignore:Git Ignore"
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -535,7 +535,7 @@ bat() {
|
|||||||
`bat` поддерживает терминалы *с* и *без* поддержки truecolor. Однако подсветка синтаксиса не оптимизирована для терминалов с 8-битными цветами, и рекомендуется использовать терминалы с поддержкой 24-битных цветов (`terminator`, `konsole`, `iTerm2`, ...).
|
`bat` поддерживает терминалы *с* и *без* поддержки truecolor. Однако подсветка синтаксиса не оптимизирована для терминалов с 8-битными цветами, и рекомендуется использовать терминалы с поддержкой 24-битных цветов (`terminator`, `konsole`, `iTerm2`, ...).
|
||||||
Смотрите [эту статью](https://gist.github.com/XVilka/8346728) для полного списка терминалов.
|
Смотрите [эту статью](https://gist.github.com/XVilka/8346728) для полного списка терминалов.
|
||||||
|
|
||||||
Удостовертесь, что переменная `COLORTERM` равна `truecolor` или
|
Удостоверьтесь, что переменная `COLORTERM` равна `truecolor` или
|
||||||
`24bit`. Иначе `bat` не сможет определить поддержку 24-битных цветов (и будет использовать 8-битные).
|
`24bit`. Иначе `bat` не сможет определить поддержку 24-битных цветов (и будет использовать 8-битные).
|
||||||
|
|
||||||
### Текст и номера строк плохо видны
|
### Текст и номера строк плохо видны
|
||||||
@@ -550,7 +550,7 @@ bat() {
|
|||||||
``` bash
|
``` bash
|
||||||
iconv -f ISO-8859-1 -t UTF-8 my-file.php | bat
|
iconv -f ISO-8859-1 -t UTF-8 my-file.php | bat
|
||||||
```
|
```
|
||||||
Внимание: вам может понадобится флаг `-l`/`--language`, если `bat` не сможет автоматически определить синтаксис.
|
Внимание: вам может понадобиться флаг `-l`/`--language`, если `bat` не сможет автоматически определить синтаксис.
|
||||||
|
|
||||||
## Разработка
|
## Разработка
|
||||||
|
|
||||||
@@ -568,7 +568,7 @@ cargo test
|
|||||||
# Установка (релизная версия)
|
# Установка (релизная версия)
|
||||||
cargo install --locked
|
cargo install --locked
|
||||||
|
|
||||||
# Компилирование исполняего файла bat с другим синтаксисом и темами
|
# Компилирование исполняемого файла bat с другим синтаксисом и темами
|
||||||
bash assets/create.sh
|
bash assets/create.sh
|
||||||
cargo install --locked --force
|
cargo install --locked --force
|
||||||
```
|
```
|
||||||
@@ -592,6 +592,6 @@ cargo install --locked --force
|
|||||||
## Лицензия
|
## Лицензия
|
||||||
Copyright (c) 2018-2021 [Разработчики bat](https://github.com/sharkdp/bat).
|
Copyright (c) 2018-2021 [Разработчики bat](https://github.com/sharkdp/bat).
|
||||||
|
|
||||||
`bat` распостраняется под лицензями MIT License и Apache License 2.0 (на выбор пользователя).
|
`bat` распространяется под лицензиями MIT License и Apache License 2.0 (на выбор пользователя).
|
||||||
|
|
||||||
Смотрите [LICENSE-APACHE](LICENSE-APACHE) и [LICENSE-MIT](LICENSE-MIT) для более подробного ознакомления.
|
Смотрите [LICENSE-APACHE](LICENSE-APACHE) и [LICENSE-MIT](LICENSE-MIT) для более подробного ознакомления.
|
||||||
|
|||||||
+27
-31
@@ -191,7 +191,7 @@ man 2 select
|
|||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
[](https://repology.org/project/bat-cat/versions)
|
[](https://repology.org/project/bat-cat/versions)
|
||||||
|
|
||||||
### Ubuntu (使用 `apt`)
|
### Ubuntu (使用 `apt`)
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ apk add bat
|
|||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
你可以用下面下列命令从官方源中安装[`bat`包](https://www.archlinux.org/packages/community/x86_64/bat/):
|
你可以用下面下列命令从官方源中安装[`bat`包](https://www.archlinux.org/packages/extra/x86_64/bat/):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pacman -S bat
|
pacman -S bat
|
||||||
@@ -372,7 +372,7 @@ scoop install bat
|
|||||||
|
|
||||||
### 从源码编译
|
### 从源码编译
|
||||||
|
|
||||||
如果你想要自己构建`bat`,那么你需要安装有高于1.64.0版本的 Rust。
|
如果你想要自己构建`bat`,那么你需要安装有高于1.74.0版本的 Rust。
|
||||||
|
|
||||||
使用以下命令编译。
|
使用以下命令编译。
|
||||||
|
|
||||||
@@ -412,7 +412,7 @@ bat --list-themes | fzf --preview="bat --theme={} --color=always /path/to/file"
|
|||||||
|
|
||||||
### 输出样式
|
### 输出样式
|
||||||
|
|
||||||
你可以用`--style`参数来控制`bat`输出的样式。使用`--style=numbers,chanegs`可以只开启 Git 修改和行号显示而不添加其他内容。`BAT_STYLE`环境变量具有相同功能。
|
你可以用`--style`参数来控制`bat`输出的样式。使用`--style=numbers,changes`可以只开启 Git 修改和行号显示而不添加其他内容。`BAT_STYLE`环境变量具有相同功能。
|
||||||
|
|
||||||
### 添加新的语言和语法
|
### 添加新的语言和语法
|
||||||
|
|
||||||
@@ -616,63 +616,59 @@ iconv -f ISO-8859-1 -t UTF-8 my-file.php | bat
|
|||||||
|
|
||||||
注意: 当`bat`无法识别语言时你可能会需要`-l`/`--language`参数。
|
注意: 当`bat`无法识别语言时你可能会需要`-l`/`--language`参数。
|
||||||
|
|
||||||
## Development
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Recursive clone to retrieve all submodules
|
# 递归 clone 以获取所有子模块
|
||||||
git clone --recursive https://github.com/sharkdp/bat
|
git clone --recursive https://github.com/sharkdp/bat
|
||||||
|
|
||||||
# Build (debug version)
|
# 构建(调试版本)
|
||||||
cd bat
|
cd bat
|
||||||
cargo build --bins
|
cargo build --bins
|
||||||
|
|
||||||
# Run unit tests and integration tests
|
# 运行单元测试和集成测试
|
||||||
cargo test
|
cargo test
|
||||||
|
|
||||||
# Install (release version)
|
# 安装(发布版本)
|
||||||
cargo install --path . --locked
|
cargo install --path . --locked
|
||||||
|
|
||||||
# Build a bat binary with modified syntaxes and themes
|
# 使用修改后的语法和主题构建一个 bat 二进制文件
|
||||||
bash assets/create.sh
|
bash assets/create.sh
|
||||||
cargo install --path . --locked --force
|
cargo install --path . --locked --force
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to build an application that uses `bat`s pretty-printing
|
如果你想构建一个使用 `bat` 美化打印功能的应用程序,请查看 [API 文档](https://docs.rs/bat/)。请注意,当你依赖 `bat` 作为库时,必须使用 `regex-onig` 或 `regex-fancy` 作为特性。
|
||||||
features as a library, check out the [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.
|
|
||||||
|
|
||||||
## Contributing
|
## 贡献指南
|
||||||
|
|
||||||
Take a look at the [`CONTRIBUTING.md`](CONTRIBUTING.md) guide.
|
请查看 [`CONTRIBUTING.md`](CONTRIBUTING.md) 指南。
|
||||||
|
|
||||||
## Maintainers
|
## 维护者
|
||||||
|
|
||||||
- [sharkdp](https://github.com/sharkdp)
|
- [sharkdp](https://github.com/sharkdp)
|
||||||
- [eth-p](https://github.com/eth-p)
|
- [eth-p](https://github.com/eth-p)
|
||||||
- [keith-hall](https://github.com/keith-hall)
|
- [keith-hall](https://github.com/keith-hall)
|
||||||
- [Enselic](https://github.com/Enselic)
|
- [Enselic](https://github.com/Enselic)
|
||||||
|
|
||||||
## Security vulnerabilities
|
## 安全漏洞
|
||||||
|
|
||||||
Please contact [David Peter](https://david-peter.de/) via email if you want to report a vulnerability in `bat`.
|
如果你想报告 `bat` 中的漏洞,请通过邮件联系 [David Peter](https://david-peter.de/)。
|
||||||
|
|
||||||
## Project goals and alternatives
|
## 项目目标和替代方案
|
||||||
|
|
||||||
`bat` tries to achieve the following goals:
|
`bat` 试图实现以下目标:
|
||||||
|
|
||||||
- Provide beautiful, advanced syntax highlighting
|
- 提供美观的高级语法高亮
|
||||||
- Integrate with Git to show file modifications
|
- 与 Git 集成以显示文件修改
|
||||||
- Be a drop-in replacement for (POSIX) `cat`
|
- 成为 (POSIX) `cat` 的替代品
|
||||||
- Offer a user-friendly command-line interface
|
- 提供用户友好的命令行界面
|
||||||
|
|
||||||
There are a lot of alternatives, if you are looking for similar programs. See
|
如果你在寻找类似的程序,有很多替代方案。请参阅[本文档](doc/alternatives.md)进行比较。
|
||||||
[this document](doc/alternatives.md) for a comparison.
|
|
||||||
|
|
||||||
## License
|
## 许可证
|
||||||
|
|
||||||
Copyright (c) 2018-2021 [bat-developers](https://github.com/sharkdp/bat).
|
版权所有 (c) 2018-2021 [bat-developers](https://github.com/sharkdp/bat)。
|
||||||
|
|
||||||
`bat` is made available under the terms of either the MIT License or the Apache License 2.0, at your option.
|
`bat` 可根据 MIT 许可证或 Apache 许可证 2.0 的条款使用,任选其一。
|
||||||
|
|
||||||
See the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for license details.
|
有关许可证的详细信息,请参阅 [LICENSE-APACHE](LICENSE-APACHE) 和 [LICENSE-MIT](LICENSE-MIT) 文件。
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ in the `.sublime-syntax` format.
|
|||||||
2. If the Sublime Text syntax is only available as a `.tmLanguage` file, open the file in
|
2. If the Sublime Text syntax is only available as a `.tmLanguage` file, open the file in
|
||||||
Sublime Text and convert it to a `.sublime-syntax` file via *Tools* -> *Developer* ->
|
Sublime Text and convert it to a `.sublime-syntax` file via *Tools* -> *Developer* ->
|
||||||
*New Syntax from XXX.tmLanguage...*. Save the new file in the `assets/syntaxes` folder.
|
*New Syntax from XXX.tmLanguage...*. Save the new file in the `assets/syntaxes` folder.
|
||||||
|
If only `.tmLanguage.json` or `.tmLanguage.yml` file is available, use
|
||||||
|
[PackageDev](https://packagecontrol.io/packages/PackageDev) to convert it to `.tmLanguage.plist`
|
||||||
|
format and then rename the converted file to `.tmLanguage` file.
|
||||||
|
|
||||||
3. Run the `assets/create.sh` script. It calls `bat cache --build` to parse all available
|
3. Run the `assets/create.sh` script. It calls `bat cache --build` to parse all available
|
||||||
`.sublime-syntax` files and serialize them to a `syntaxes.bin` file.
|
`.sublime-syntax` files and serialize them to a `syntaxes.bin` file.
|
||||||
@@ -84,6 +87,7 @@ The following files have been manually modified after converting from a `.tmLang
|
|||||||
* `Org mode.sublime-syntax` => removed `task` file type.
|
* `Org mode.sublime-syntax` => removed `task` file type.
|
||||||
* `Robot.sublime_syntax` => changed name to "Robot Framework", added `.resource` extension.
|
* `Robot.sublime_syntax` => changed name to "Robot Framework", added `.resource` extension.
|
||||||
* `SML.sublime_syntax` => removed `ml` file type.
|
* `SML.sublime_syntax` => removed `ml` file type.
|
||||||
|
* `wgsl.sublime-syntax` => added `wgsl` file extension.
|
||||||
|
|
||||||
### Non-submodule additions
|
### Non-submodule additions
|
||||||
|
|
||||||
|
|||||||
+56
-2
@@ -20,6 +20,13 @@ Options:
|
|||||||
* unicode (␇, ␊, ␀, ..)
|
* unicode (␇, ␊, ␀, ..)
|
||||||
* caret (^G, ^J, ^@, ..)
|
* caret (^G, ^J, ^@, ..)
|
||||||
|
|
||||||
|
--binary <behavior>
|
||||||
|
How to treat binary content. (default: no-printing)
|
||||||
|
|
||||||
|
Possible values:
|
||||||
|
* no-printing: do not print any binary content
|
||||||
|
* as-text: treat binary content as normal text
|
||||||
|
|
||||||
-p, --plain...
|
-p, --plain...
|
||||||
Only show plain style, no decorations. This is an alias for '--style=plain'. When '-p' is
|
Only show plain style, no decorations. This is an alias for '--style=plain'. When '-p' is
|
||||||
used twice ('-pp'), it also disables automatic paging (alias for '--style=plain
|
used twice ('-pp'), it also disables automatic paging (alias for '--style=plain
|
||||||
@@ -89,8 +96,8 @@ Options:
|
|||||||
|
|
||||||
--paging <when>
|
--paging <when>
|
||||||
Specify when to use the pager. To disable the pager, use --paging=never' or its
|
Specify when to use the pager. To disable the pager, use --paging=never' or its
|
||||||
alias,'-P'. To disable the pager permanently, set BAT_PAGER to an empty string. To control
|
alias,'-P'. To disable the pager permanently, set BAT_PAGING to 'never'. To control which
|
||||||
which pager is used, see the '--pager' option. Possible values: *auto*, never, always.
|
pager is used, see the '--pager' option. Possible values: *auto*, never, always.
|
||||||
|
|
||||||
--pager <command>
|
--pager <command>
|
||||||
Determine which pager is used. This option will override the PAGER and BAT_PAGER
|
Determine which pager is used. This option will override the PAGER and BAT_PAGER
|
||||||
@@ -112,10 +119,42 @@ Options:
|
|||||||
Set the theme for syntax highlighting. Use '--list-themes' to see all available themes. To
|
Set the theme for syntax highlighting. Use '--list-themes' to see all available themes. To
|
||||||
set a default theme, add the '--theme="..."' option to the configuration file or export
|
set a default theme, add the '--theme="..."' option to the configuration file or export
|
||||||
the BAT_THEME environment variable (e.g.: export BAT_THEME="...").
|
the BAT_THEME environment variable (e.g.: export BAT_THEME="...").
|
||||||
|
|
||||||
|
Special values:
|
||||||
|
|
||||||
|
* auto: Picks a dark or light theme depending on the terminal's colors (default).
|
||||||
|
Use '--theme-light' and '--theme-dark' to customize the selected theme.
|
||||||
|
* auto:always: Detect the terminal's colors even when the output is redirected.
|
||||||
|
* auto:system: Detect the color scheme from the system-wide preference (macOS only).
|
||||||
|
* dark: Use the dark theme specified by '--theme-dark'.
|
||||||
|
* light: Use the light theme specified by '--theme-light'.
|
||||||
|
|
||||||
|
--theme-light <theme>
|
||||||
|
Sets the theme name for syntax highlighting used when the terminal uses a light
|
||||||
|
background. Use '--list-themes' to see all available themes. To set a default theme, add
|
||||||
|
the '--theme-light="..." option to the configuration file or export the BAT_THEME_LIGHT
|
||||||
|
environment variable (e.g. export BAT_THEME_LIGHT="...").
|
||||||
|
|
||||||
|
--theme-dark <theme>
|
||||||
|
Sets the theme name for syntax highlighting used when the terminal uses a dark background.
|
||||||
|
Use '--list-themes' to see all available themes. To set a default theme, add the
|
||||||
|
'--theme-dark="..." option to the configuration file or export the BAT_THEME_DARK
|
||||||
|
environment variable (e.g. export BAT_THEME_DARK="...").
|
||||||
|
|
||||||
--list-themes
|
--list-themes
|
||||||
Display a list of supported themes for syntax highlighting.
|
Display a list of supported themes for syntax highlighting.
|
||||||
|
|
||||||
|
-s, --squeeze-blank
|
||||||
|
Squeeze consecutive empty lines into a single empty line.
|
||||||
|
|
||||||
|
--squeeze-limit <squeeze-limit>
|
||||||
|
Set the maximum number of consecutive empty lines to be printed.
|
||||||
|
|
||||||
|
--strip-ansi <when>
|
||||||
|
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. Possible values:
|
||||||
|
auto, always, *never*.
|
||||||
|
|
||||||
--style <components>
|
--style <components>
|
||||||
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
|
Configure which elements (line numbers, file headers, grid borders, Git modifications, ..)
|
||||||
to display in addition to the file contents. The argument is a comma-separated list of
|
to display in addition to the file contents. The argument is a comma-separated list of
|
||||||
@@ -123,6 +162,15 @@ Options:
|
|||||||
set a default style, add the '--style=".."' option to the configuration file or export the
|
set a default style, add the '--style=".."' option to the configuration file or export the
|
||||||
BAT_STYLE environment variable (e.g.: export BAT_STYLE="..").
|
BAT_STYLE environment variable (e.g.: export BAT_STYLE="..").
|
||||||
|
|
||||||
|
When styles are specified in multiple places, the "nearest" set of styles take precedence.
|
||||||
|
The command-line arguments are the highest priority, followed by the BAT_STYLE environment
|
||||||
|
variable, and then the configuration file. If any set of styles consists entirely of
|
||||||
|
components prefixed with "+" or "-", it will modify the previous set of styles instead of
|
||||||
|
replacing them.
|
||||||
|
|
||||||
|
By default, the following components are enabled:
|
||||||
|
changes, grid, header-filename, numbers, snip
|
||||||
|
|
||||||
Possible values:
|
Possible values:
|
||||||
|
|
||||||
* default: enables recommended style components (default).
|
* default: enables recommended style components (default).
|
||||||
@@ -154,12 +202,18 @@ Options:
|
|||||||
This option exists for POSIX-compliance reasons ('u' is for 'unbuffered'). The output is
|
This option exists for POSIX-compliance reasons ('u' is for 'unbuffered'). The output is
|
||||||
always unbuffered - this option is simply ignored.
|
always unbuffered - this option is simply ignored.
|
||||||
|
|
||||||
|
--completion <SHELL>
|
||||||
|
Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]
|
||||||
|
|
||||||
--diagnostic
|
--diagnostic
|
||||||
Show diagnostic information for bug reports.
|
Show diagnostic information for bug reports.
|
||||||
|
|
||||||
--acknowledgements
|
--acknowledgements
|
||||||
Show acknowledgements.
|
Show acknowledgements.
|
||||||
|
|
||||||
|
--set-terminal-title
|
||||||
|
Sets terminal title to filenames when using a pager.
|
||||||
|
|
||||||
-h, --help
|
-h, --help
|
||||||
Print help (see a summary with '-h')
|
Print help (see a summary with '-h')
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,16 @@
|
|||||||
- [ ] Update the version and the min. supported Rust version in `README.md` and
|
- [ ] Update the version and the min. supported Rust version in `README.md` and
|
||||||
`doc/README-*.md`. Check with
|
`doc/README-*.md`. Check with
|
||||||
`git grep -i -e 'rust.*1\.' -e '1\..*rust' | grep README | grep -v tests/`.
|
`git grep -i -e 'rust.*1\.' -e '1\..*rust' | grep README | grep -v tests/`.
|
||||||
- [ ] Update `CHANGELOG.md`. Introduce a section for the new release.
|
|
||||||
|
## CHANGELOG.md updates
|
||||||
|
|
||||||
|
- [ ] Go to https://github.com/sharkdp/bat/releases/new, click "Choose a tag",
|
||||||
|
type the name of the tag that will be created later, click "Generate release
|
||||||
|
notes". DO NOT ACTUALLY CREATE ANY RELEASE IN THIS STEP.
|
||||||
|
- [ ] Compare current `CHANGELOG.md` with auto-generated release notes and add
|
||||||
|
missing entries. Expect in particular dependabot PRs to not be in
|
||||||
|
`CHANGELOG.md` since they are [auto-merged] if CI passes.
|
||||||
|
- [ ] Introduce a section for the new release and perform final touch-ups.
|
||||||
|
|
||||||
## Update syntaxes and themes (build assets)
|
## Update syntaxes and themes (build assets)
|
||||||
|
|
||||||
@@ -71,3 +80,5 @@
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[auto-merged]: https://github.com/sharkdp/bat/blob/master/.github/workflows/Auto-merge-dependabot-PRs.yml
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ Options:
|
|||||||
Show non-printable characters (space, tab, newline, ..).
|
Show non-printable characters (space, tab, newline, ..).
|
||||||
--nonprintable-notation <notation>
|
--nonprintable-notation <notation>
|
||||||
Set notation for non-printable characters.
|
Set notation for non-printable characters.
|
||||||
|
--binary <behavior>
|
||||||
|
How to treat binary content. (default: no-printing)
|
||||||
-p, --plain...
|
-p, --plain...
|
||||||
Show plain style (alias for '--style=plain').
|
Show plain style (alias for '--style=plain').
|
||||||
-l, --language <language>
|
-l, --language <language>
|
||||||
@@ -41,8 +43,14 @@ Options:
|
|||||||
Use the specified syntax for files matching the glob pattern ('*.cpp:C++').
|
Use the specified syntax for files matching the glob pattern ('*.cpp:C++').
|
||||||
--theme <theme>
|
--theme <theme>
|
||||||
Set the color theme for syntax highlighting.
|
Set the color theme for syntax highlighting.
|
||||||
|
--theme-light <theme>
|
||||||
|
Sets the color theme for syntax highlighting used for light backgrounds.
|
||||||
|
--theme-dark <theme>
|
||||||
|
Sets the color theme for syntax highlighting used for dark backgrounds.
|
||||||
--list-themes
|
--list-themes
|
||||||
Display all supported highlighting themes.
|
Display all supported highlighting themes.
|
||||||
|
-s, --squeeze-blank
|
||||||
|
Squeeze consecutive empty lines.
|
||||||
--style <components>
|
--style <components>
|
||||||
Comma-separated list of style elements to display (*default*, auto, full, plain, changes,
|
Comma-separated list of style elements to display (*default*, auto, full, plain, changes,
|
||||||
header, header-filename, header-filesize, grid, rule, numbers, snip).
|
header, header-filename, header-filesize, grid, rule, numbers, snip).
|
||||||
@@ -50,6 +58,8 @@ Options:
|
|||||||
Only print the lines from N to M.
|
Only print the lines from N to M.
|
||||||
-L, --list-languages
|
-L, --list-languages
|
||||||
Display all supported languages.
|
Display all supported languages.
|
||||||
|
--completion <SHELL>
|
||||||
|
Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]
|
||||||
-h, --help
|
-h, --help
|
||||||
Print help (see more with '--help')
|
Print help (see more with '--help')
|
||||||
-V, --version
|
-V, --version
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
use bat::{assets::HighlightingAssets, config::Config, controller::Controller, Input};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let config = Config {
|
||||||
|
colored_output: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let assets = HighlightingAssets::from_binary();
|
||||||
|
let controller = Controller::new(&config, &assets);
|
||||||
|
let input = Input::from_file(file!());
|
||||||
|
controller
|
||||||
|
.run(vec![input.into()], Some(&mut buffer))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
println!("{buffer}");
|
||||||
|
}
|
||||||
@@ -13,6 +13,6 @@ fn main() {
|
|||||||
|
|
||||||
println!("Themes:");
|
println!("Themes:");
|
||||||
for theme in printer.themes() {
|
for theme in printer.themes() {
|
||||||
println!("- {}", theme);
|
println!("- {theme}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -23,7 +23,8 @@ fn main() {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes = serde_yaml::to_vec(&person).unwrap();
|
let mut bytes = Vec::with_capacity(128);
|
||||||
|
serde_yaml::to_writer(&mut bytes, &person).unwrap();
|
||||||
PrettyPrinter::new()
|
PrettyPrinter::new()
|
||||||
.language("yaml")
|
.language("yaml")
|
||||||
.line_numbers(true)
|
.line_numbers(true)
|
||||||
|
|||||||
+10
-78
@@ -13,6 +13,7 @@ use crate::error::*;
|
|||||||
use crate::input::{InputReader, OpenedInput};
|
use crate::input::{InputReader, OpenedInput};
|
||||||
use crate::syntax_mapping::ignored_suffixes::IgnoredSuffixes;
|
use crate::syntax_mapping::ignored_suffixes::IgnoredSuffixes;
|
||||||
use crate::syntax_mapping::MappingTarget;
|
use crate::syntax_mapping::MappingTarget;
|
||||||
|
use crate::theme::{default_theme, ColorScheme};
|
||||||
use crate::{bat_warning, SyntaxMapping};
|
use crate::{bat_warning, SyntaxMapping};
|
||||||
|
|
||||||
use lazy_theme_set::LazyThemeSet;
|
use lazy_theme_set::LazyThemeSet;
|
||||||
@@ -69,57 +70,6 @@ impl HighlightingAssets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default theme.
|
|
||||||
///
|
|
||||||
/// ### Windows and Linux
|
|
||||||
///
|
|
||||||
/// Windows and most Linux distributions has a dark terminal theme by
|
|
||||||
/// default. On these platforms, this function always returns a theme that
|
|
||||||
/// looks good on a dark background.
|
|
||||||
///
|
|
||||||
/// ### macOS
|
|
||||||
///
|
|
||||||
/// On macOS the default terminal background is light, but it is common that
|
|
||||||
/// Dark Mode is active, which makes the terminal background dark. On this
|
|
||||||
/// platform, the default theme depends on
|
|
||||||
/// ```bash
|
|
||||||
/// defaults read -globalDomain AppleInterfaceStyle
|
|
||||||
/// ```
|
|
||||||
/// To avoid the overhead of the check on macOS, simply specify a theme
|
|
||||||
/// explicitly via `--theme`, `BAT_THEME`, or `~/.config/bat`.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/sharkdp/bat/issues/1746> and
|
|
||||||
/// <https://github.com/sharkdp/bat/issues/1928> for more context.
|
|
||||||
pub fn default_theme() -> &'static str {
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
{
|
|
||||||
Self::default_dark_theme()
|
|
||||||
}
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
if macos_dark_mode_active() {
|
|
||||||
Self::default_dark_theme()
|
|
||||||
} else {
|
|
||||||
Self::default_light_theme()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default theme that looks good on a dark background.
|
|
||||||
*/
|
|
||||||
fn default_dark_theme() -> &'static str {
|
|
||||||
"Monokai Extended"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default theme that looks good on a light background.
|
|
||||||
*/
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn default_light_theme() -> &'static str {
|
|
||||||
"Monokai Extended Light"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_cache(cache_path: &Path) -> Result<Self> {
|
pub fn from_cache(cache_path: &Path) -> Result<Self> {
|
||||||
Ok(HighlightingAssets::new(
|
Ok(HighlightingAssets::new(
|
||||||
SerializedSyntaxSet::FromFile(cache_path.join("syntaxes.bin")),
|
SerializedSyntaxSet::FromFile(cache_path.join("syntaxes.bin")),
|
||||||
@@ -248,7 +198,10 @@ impl HighlightingAssets {
|
|||||||
bat_warning!("Unknown theme '{}', using default.", theme)
|
bat_warning!("Unknown theme '{}', using default.", theme)
|
||||||
}
|
}
|
||||||
self.get_theme_set()
|
self.get_theme_set()
|
||||||
.get(self.fallback_theme.unwrap_or_else(Self::default_theme))
|
.get(
|
||||||
|
self.fallback_theme
|
||||||
|
.unwrap_or_else(|| default_theme(ColorScheme::Dark)),
|
||||||
|
)
|
||||||
.expect("something is very wrong if the default theme is missing")
|
.expect("something is very wrong if the default theme is missing")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,7 +333,7 @@ fn asset_from_contents<T: serde::de::DeserializeOwned>(
|
|||||||
} else {
|
} else {
|
||||||
bincode::deserialize_from(contents)
|
bincode::deserialize_from(contents)
|
||||||
}
|
}
|
||||||
.map_err(|_| format!("Could not parse {}", description).into())
|
.map_err(|_| format!("Could not parse {description}").into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn asset_from_cache<T: serde::de::DeserializeOwned>(
|
fn asset_from_cache<T: serde::de::DeserializeOwned>(
|
||||||
@@ -396,27 +349,7 @@ fn asset_from_cache<T: serde::de::DeserializeOwned>(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
asset_from_contents(&contents[..], description, compressed)
|
asset_from_contents(&contents[..], description, compressed)
|
||||||
.map_err(|_| format!("Could not parse cached {}", description).into())
|
.map_err(|_| format!("Could not parse cached {description}").into())
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn macos_dark_mode_active() -> bool {
|
|
||||||
const PREFERENCES_FILE: &str = "Library/Preferences/.GlobalPreferences.plist";
|
|
||||||
const STYLE_KEY: &str = "AppleInterfaceStyle";
|
|
||||||
|
|
||||||
let preferences_file = dirs::home_dir()
|
|
||||||
.map(|home| home.join(PREFERENCES_FILE))
|
|
||||||
.expect("Could not get home directory");
|
|
||||||
|
|
||||||
match plist::Value::from_file(preferences_file).map(|file| file.into_dictionary()) {
|
|
||||||
Ok(Some(preferences)) => match preferences.get(STYLE_KEY).and_then(|val| val.as_string()) {
|
|
||||||
Some(value) => value == "Dark",
|
|
||||||
// If the key does not exist, then light theme is currently in use.
|
|
||||||
None => false,
|
|
||||||
},
|
|
||||||
// Unreachable, in theory. All macOS users have a home directory and preferences file setup.
|
|
||||||
Ok(None) | Err(_) => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -441,7 +374,7 @@ mod tests {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
SyntaxDetectionTest {
|
SyntaxDetectionTest {
|
||||||
assets: HighlightingAssets::from_binary(),
|
assets: HighlightingAssets::from_binary(),
|
||||||
syntax_mapping: SyntaxMapping::builtin(),
|
syntax_mapping: SyntaxMapping::new(),
|
||||||
temp_dir: TempDir::new().expect("creation of temporary directory"),
|
temp_dir: TempDir::new().expect("creation of temporary directory"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,7 +399,7 @@ mod tests {
|
|||||||
let file_path = self.temp_dir.path().join(file_name);
|
let file_path = self.temp_dir.path().join(file_name);
|
||||||
{
|
{
|
||||||
let mut temp_file = File::create(&file_path).unwrap();
|
let mut temp_file = File::create(&file_path).unwrap();
|
||||||
writeln!(temp_file, "{}", first_line).unwrap();
|
writeln!(temp_file, "{first_line}").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = Input::ordinary_file(&file_path);
|
let input = Input::ordinary_file(&file_path);
|
||||||
@@ -514,8 +447,7 @@ mod tests {
|
|||||||
|
|
||||||
if !consistent {
|
if !consistent {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"Inconsistent syntax detection:\nFor File: {}\nFor Reader: {}",
|
"Inconsistent syntax detection:\nFor File: {as_file}\nFor Reader: {as_reader}"
|
||||||
as_file, as_reader
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ fn print_unlinked_contexts(syntax_set: &SyntaxSet) {
|
|||||||
if !missing_contexts.is_empty() {
|
if !missing_contexts.is_empty() {
|
||||||
println!("Some referenced contexts could not be found!");
|
println!("Some referenced contexts could not be found!");
|
||||||
for context in missing_contexts {
|
for context in missing_contexts {
|
||||||
println!("- {}", context);
|
println!("- {context}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ pub(crate) fn asset_to_contents<T: serde::Serialize>(
|
|||||||
} else {
|
} else {
|
||||||
bincode::serialize_into(&mut contents, asset)
|
bincode::serialize_into(&mut contents, asset)
|
||||||
}
|
}
|
||||||
.map_err(|_| format!("Could not serialize {}", description))?;
|
.map_err(|_| format!("Could not serialize {description}"))?;
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ fn handle_license(path: &Path) -> Result<Option<String>> {
|
|||||||
} else if license_not_needed_in_acknowledgements(&license_text) {
|
} else if license_not_needed_in_acknowledgements(&license_text) {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
Err(format!("ERROR: License is of unknown type: {:?}", path).into())
|
Err(format!("ERROR: License is of unknown type: {path:?}").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ fn append_to_acknowledgements(
|
|||||||
relative_path: &str,
|
relative_path: &str,
|
||||||
license_text: &str,
|
license_text: &str,
|
||||||
) {
|
) {
|
||||||
write!(acknowledgements, "## {}\n\n{}", relative_path, license_text).ok();
|
write!(acknowledgements, "## {relative_path}\n\n{license_text}").ok();
|
||||||
|
|
||||||
// Make sure the last char is a newline to not mess up formatting later
|
// Make sure the last char is a newline to not mess up formatting later
|
||||||
if acknowledgements
|
if acknowledgements
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use super::*;
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
|
|
||||||
@@ -89,7 +88,7 @@ impl TryFrom<ThemeSet> for LazyThemeSet {
|
|||||||
let lazy_theme = LazyTheme {
|
let lazy_theme = LazyTheme {
|
||||||
serialized: crate::assets::build_assets::asset_to_contents(
|
serialized: crate::assets::build_assets::asset_to_contents(
|
||||||
&theme,
|
&theme,
|
||||||
&format!("theme {}", name),
|
&format!("theme {name}"),
|
||||||
COMPRESS_LAZY_THEMES,
|
COMPRESS_LAZY_THEMES,
|
||||||
)?,
|
)?,
|
||||||
deserialized: OnceCell::new(),
|
deserialized: OnceCell::new(),
|
||||||
|
|||||||
+138
-47
@@ -1,20 +1,23 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::io::IsTerminal;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
use atty::{self, Stream};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
clap_app,
|
clap_app,
|
||||||
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
|
config::{get_args_from_config_file, get_args_from_env_opts_var, get_args_from_env_vars},
|
||||||
};
|
};
|
||||||
|
use bat::style::StyleComponentList;
|
||||||
|
use bat::theme::{theme, ThemeName, ThemeOptions, ThemePreference};
|
||||||
|
use bat::BinaryBehavior;
|
||||||
|
use bat::StripAnsiMode;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use console::Term;
|
use console::Term;
|
||||||
|
|
||||||
use crate::input::{new_file_input, new_stdin_input};
|
use crate::input::{new_file_input, new_stdin_input};
|
||||||
use bat::{
|
use bat::{
|
||||||
assets::HighlightingAssets,
|
|
||||||
bat_warning,
|
bat_warning,
|
||||||
config::{Config, VisibleLines},
|
config::{Config, VisibleLines},
|
||||||
error::*,
|
error::*,
|
||||||
@@ -30,6 +33,10 @@ fn is_truecolor_terminal() -> bool {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn env_no_color() -> bool {
|
||||||
|
env::var_os("NO_COLOR").is_some_and(|x| !x.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub matches: ArgMatches,
|
pub matches: ArgMatches,
|
||||||
interactive_output: bool,
|
interactive_output: bool,
|
||||||
@@ -40,7 +47,7 @@ impl App {
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let _ = nu_ansi_term::enable_ansi_support();
|
let _ = nu_ansi_term::enable_ansi_support();
|
||||||
|
|
||||||
let interactive_output = atty::is(Stream::Stdout);
|
let interactive_output = std::io::stdout().is_terminal();
|
||||||
|
|
||||||
Ok(App {
|
Ok(App {
|
||||||
matches: Self::matches(interactive_output)?,
|
matches: Self::matches(interactive_output)?,
|
||||||
@@ -82,7 +89,6 @@ impl App {
|
|||||||
|
|
||||||
// .. and the rest at the end
|
// .. and the rest at the end
|
||||||
cli_args.for_each(|a| args.push(a));
|
cli_args.for_each(|a| args.push(a));
|
||||||
|
|
||||||
args
|
args
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -92,19 +98,37 @@ impl App {
|
|||||||
pub fn config(&self, inputs: &[Input]) -> Result<Config> {
|
pub fn config(&self, inputs: &[Input]) -> Result<Config> {
|
||||||
let style_components = self.style_components()?;
|
let style_components = self.style_components()?;
|
||||||
|
|
||||||
|
let extra_plain = self.matches.get_count("plain") > 1;
|
||||||
|
let plain_last_index = self
|
||||||
|
.matches
|
||||||
|
.indices_of("plain")
|
||||||
|
.and_then(Iterator::max)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let paging_last_index = self
|
||||||
|
.matches
|
||||||
|
.indices_of("paging")
|
||||||
|
.and_then(Iterator::max)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let paging_mode = match self.matches.get_one::<String>("paging").map(|s| s.as_str()) {
|
let paging_mode = match self.matches.get_one::<String>("paging").map(|s| s.as_str()) {
|
||||||
Some("always") => PagingMode::Always,
|
Some("always") => {
|
||||||
|
// Disable paging if the second -p (or -pp) is specified after --paging=always
|
||||||
|
if extra_plain && plain_last_index > paging_last_index {
|
||||||
|
PagingMode::Never
|
||||||
|
} else {
|
||||||
|
PagingMode::Always
|
||||||
|
}
|
||||||
|
}
|
||||||
Some("never") => PagingMode::Never,
|
Some("never") => PagingMode::Never,
|
||||||
Some("auto") | None => {
|
Some("auto") | None => {
|
||||||
// If we have -pp as an option when in auto mode, the pager should be disabled.
|
// If we have -pp as an option when in auto mode, the pager should be disabled.
|
||||||
let extra_plain = self.matches.get_count("plain") > 1;
|
|
||||||
if extra_plain || self.matches.get_flag("no-paging") {
|
if extra_plain || self.matches.get_flag("no-paging") {
|
||||||
PagingMode::Never
|
PagingMode::Never
|
||||||
} else if inputs.iter().any(Input::is_stdin) {
|
} else if inputs.iter().any(Input::is_stdin) {
|
||||||
// If we are reading from stdin, only enable paging if we write to an
|
// If we are reading from stdin, only enable paging if we write to an
|
||||||
// interactive terminal and if we do not *read* from an interactive
|
// interactive terminal and if we do not *read* from an interactive
|
||||||
// terminal.
|
// terminal.
|
||||||
if self.interactive_output && !atty::is(Stream::Stdin) {
|
if self.interactive_output && !std::io::stdin().is_terminal() {
|
||||||
PagingMode::QuitIfOneScreen
|
PagingMode::QuitIfOneScreen
|
||||||
} else {
|
} else {
|
||||||
PagingMode::Never
|
PagingMode::Never
|
||||||
@@ -118,7 +142,11 @@ impl App {
|
|||||||
_ => unreachable!("other values for --paging are not allowed"),
|
_ => unreachable!("other values for --paging are not allowed"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut syntax_mapping = SyntaxMapping::builtin();
|
let mut syntax_mapping = SyntaxMapping::new();
|
||||||
|
// start building glob matchers for builtin mappings immediately
|
||||||
|
// this is an appropriate approach because it's statistically likely that
|
||||||
|
// all the custom mappings need to be checked
|
||||||
|
syntax_mapping.start_offload_build_all();
|
||||||
|
|
||||||
if let Some(values) = self.matches.get_many::<String>("ignored-suffix") {
|
if let Some(values) = self.matches.get_many::<String>("ignored-suffix") {
|
||||||
for suffix in values {
|
for suffix in values {
|
||||||
@@ -127,7 +155,9 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(values) = self.matches.get_many::<String>("map-syntax") {
|
if let Some(values) = self.matches.get_many::<String>("map-syntax") {
|
||||||
for from_to in values {
|
// later args take precedence over earlier ones, hence `.rev()`
|
||||||
|
// see: https://github.com/sharkdp/bat/pull/2755#discussion_r1456416875
|
||||||
|
for from_to in values.rev() {
|
||||||
let parts: Vec<_> = from_to.split(':').collect();
|
let parts: Vec<_> = from_to.split(':').collect();
|
||||||
|
|
||||||
if parts.len() != 2 {
|
if parts.len() != 2 {
|
||||||
@@ -182,6 +212,11 @@ impl App {
|
|||||||
Some("caret") => NonprintableNotation::Caret,
|
Some("caret") => NonprintableNotation::Caret,
|
||||||
_ => unreachable!("other values for --nonprintable-notation are not allowed"),
|
_ => unreachable!("other values for --nonprintable-notation are not allowed"),
|
||||||
},
|
},
|
||||||
|
binary: match self.matches.get_one::<String>("binary").map(|s| s.as_str()) {
|
||||||
|
Some("as-text") => BinaryBehavior::AsText,
|
||||||
|
Some("no-printing") => BinaryBehavior::NoPrinting,
|
||||||
|
_ => unreachable!("other values for --binary are not allowed"),
|
||||||
|
},
|
||||||
wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
|
wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
|
||||||
if !self.matches.get_flag("chop-long-lines") {
|
if !self.matches.get_flag("chop-long-lines") {
|
||||||
match self.matches.get_one::<String>("wrap").map(|s| s.as_str()) {
|
match self.matches.get_one::<String>("wrap").map(|s| s.as_str()) {
|
||||||
@@ -208,7 +243,7 @@ impl App {
|
|||||||
|| match self.matches.get_one::<String>("color").map(|s| s.as_str()) {
|
|| match self.matches.get_one::<String>("color").map(|s| s.as_str()) {
|
||||||
Some("always") => true,
|
Some("always") => true,
|
||||||
Some("never") => false,
|
Some("never") => false,
|
||||||
Some("auto") => env::var_os("NO_COLOR").is_none() && self.interactive_output,
|
Some("auto") => !env_no_color() && self.interactive_output,
|
||||||
_ => unreachable!("other values for --color are not allowed"),
|
_ => unreachable!("other values for --color are not allowed"),
|
||||||
},
|
},
|
||||||
paging_mode,
|
paging_mode,
|
||||||
@@ -233,18 +268,17 @@ impl App {
|
|||||||
4
|
4
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
theme: self
|
strip_ansi: match self
|
||||||
.matches
|
.matches
|
||||||
.get_one::<String>("theme")
|
.get_one::<String>("strip-ansi")
|
||||||
.map(String::from)
|
.map(|s| s.as_str())
|
||||||
.map(|s| {
|
{
|
||||||
if s == "default" {
|
Some("never") => StripAnsiMode::Never,
|
||||||
String::from(HighlightingAssets::default_theme())
|
Some("always") => StripAnsiMode::Always,
|
||||||
} else {
|
Some("auto") => StripAnsiMode::Auto,
|
||||||
s
|
_ => unreachable!("other values for --strip-ansi are not allowed"),
|
||||||
}
|
},
|
||||||
})
|
theme: theme(self.theme_options()).to_string(),
|
||||||
.unwrap_or_else(|| String::from(HighlightingAssets::default_theme())),
|
|
||||||
visible_lines: match self.matches.try_contains_id("diff").unwrap_or_default()
|
visible_lines: match self.matches.try_contains_id("diff").unwrap_or_default()
|
||||||
&& self.matches.get_flag("diff")
|
&& self.matches.get_flag("diff")
|
||||||
{
|
{
|
||||||
@@ -282,6 +316,19 @@ impl App {
|
|||||||
.map(HighlightedLineRanges)
|
.map(HighlightedLineRanges)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
use_custom_assets: !self.matches.get_flag("no-custom-assets"),
|
use_custom_assets: !self.matches.get_flag("no-custom-assets"),
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
use_lessopen: self.matches.get_flag("lessopen"),
|
||||||
|
set_terminal_title: self.matches.get_flag("set-terminal-title"),
|
||||||
|
squeeze_lines: if self.matches.get_flag("squeeze-blank") {
|
||||||
|
Some(
|
||||||
|
self.matches
|
||||||
|
.get_one::<usize>("squeeze-limit")
|
||||||
|
.map(|limit| limit.to_owned())
|
||||||
|
.unwrap_or(1),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,34 +378,57 @@ impl App {
|
|||||||
Ok(file_input)
|
Ok(file_input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn forced_style_components(&self) -> Option<StyleComponents> {
|
||||||
|
// No components if `--decorations=never``.
|
||||||
|
if self
|
||||||
|
.matches
|
||||||
|
.get_one::<String>("decorations")
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
== Some("never")
|
||||||
|
{
|
||||||
|
return Some(StyleComponents(HashSet::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only line numbers if `--number`.
|
||||||
|
if self.matches.get_flag("number") {
|
||||||
|
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])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behavior.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn style_components(&self) -> Result<StyleComponents> {
|
fn style_components(&self) -> Result<StyleComponents> {
|
||||||
let matches = &self.matches;
|
let matches = &self.matches;
|
||||||
let mut styled_components = StyleComponents(
|
let mut styled_components = match self.forced_style_components() {
|
||||||
if matches.get_one::<String>("decorations").map(|s| s.as_str()) == Some("never") {
|
Some(forced_components) => forced_components,
|
||||||
HashSet::new()
|
|
||||||
} else if matches.get_flag("number") {
|
// Parse the `--style` arguments and merge them.
|
||||||
[StyleComponent::LineNumbers].iter().cloned().collect()
|
None if matches.contains_id("style") => {
|
||||||
} else if 0 < matches.get_count("plain") {
|
let lists = matches
|
||||||
[StyleComponent::Plain].iter().cloned().collect()
|
.get_many::<String>("style")
|
||||||
} else {
|
.expect("styles present")
|
||||||
matches
|
.map(|v| StyleComponentList::from_str(v))
|
||||||
.get_one::<String>("style")
|
.collect::<Result<Vec<StyleComponentList>>>()?;
|
||||||
.map(|styles| {
|
|
||||||
styles
|
StyleComponentList::to_components(lists, self.interactive_output, true)
|
||||||
.split(',')
|
}
|
||||||
.map(|style| style.parse::<StyleComponent>())
|
|
||||||
.filter_map(|style| style.ok())
|
// Use the default.
|
||||||
.collect::<Vec<_>>()
|
None => StyleComponents(HashSet::from_iter(
|
||||||
})
|
StyleComponent::Default
|
||||||
.unwrap_or_else(|| vec![StyleComponent::Default])
|
.components(self.interactive_output)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|style| style.components(self.interactive_output))
|
.cloned(),
|
||||||
.fold(HashSet::new(), |mut acc, components| {
|
)),
|
||||||
acc.extend(components.iter().cloned());
|
};
|
||||||
acc
|
|
||||||
})
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// If `grid` is set, remove `rule` as it is a subset of `grid`, and print a warning.
|
// If `grid` is set, remove `rule` as it is a subset of `grid`, and print a warning.
|
||||||
if styled_components.grid() && styled_components.0.remove(&StyleComponent::Rule) {
|
if styled_components.grid() && styled_components.0.remove(&StyleComponent::Rule) {
|
||||||
@@ -367,4 +437,25 @@ impl App {
|
|||||||
|
|
||||||
Ok(styled_components)
|
Ok(styled_components)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn theme_options(&self) -> ThemeOptions {
|
||||||
|
let theme = self
|
||||||
|
.matches
|
||||||
|
.get_one::<String>("theme")
|
||||||
|
.map(|t| ThemePreference::from_str(t).unwrap())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let theme_dark = self
|
||||||
|
.matches
|
||||||
|
.get_one::<String>("theme-dark")
|
||||||
|
.map(|t| ThemeName::from_str(t).unwrap());
|
||||||
|
let theme_light = self
|
||||||
|
.matches
|
||||||
|
.get_one::<String>("theme-light")
|
||||||
|
.map(|t| ThemeName::from_str(t).unwrap());
|
||||||
|
ThemeOptions {
|
||||||
|
theme,
|
||||||
|
theme_dark,
|
||||||
|
theme_light,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ pub fn assets_from_cache_or_binary(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn clear_asset(path: PathBuf, description: &str) {
|
fn clear_asset(path: PathBuf, description: &str) {
|
||||||
print!("Clearing {} ... ", description);
|
print!("Clearing {description} ... ");
|
||||||
match fs::remove_file(&path) {
|
match fs::remove_file(&path) {
|
||||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||||
println!("skipped (not present)");
|
println!("skipped (not present)");
|
||||||
|
|||||||
+134
-30
@@ -1,9 +1,11 @@
|
|||||||
|
use bat::style::StyleComponentList;
|
||||||
use clap::{
|
use clap::{
|
||||||
crate_name, crate_version, value_parser, Arg, ArgAction, ArgGroup, ColorChoice, Command,
|
crate_name, crate_version, value_parser, Arg, ArgAction, ArgGroup, ColorChoice, Command,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
static VERSION: Lazy<String> = Lazy::new(|| {
|
static VERSION: Lazy<String> = Lazy::new(|| {
|
||||||
#[cfg(feature = "bugreport")]
|
#[cfg(feature = "bugreport")]
|
||||||
@@ -19,7 +21,7 @@ static VERSION: Lazy<String> = Lazy::new(|| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
pub fn build_app(interactive_output: bool) -> Command {
|
pub fn build_app(interactive_output: bool) -> Command {
|
||||||
let color_when = if interactive_output && env::var_os("NO_COLOR").is_none() {
|
let color_when = if interactive_output && !crate::app::env_no_color() {
|
||||||
ColorChoice::Auto
|
ColorChoice::Auto
|
||||||
} else {
|
} else {
|
||||||
ColorChoice::Never
|
ColorChoice::Never
|
||||||
@@ -75,6 +77,22 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
* caret (^G, ^J, ^@, ..)",
|
* caret (^G, ^J, ^@, ..)",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("binary")
|
||||||
|
.long("binary")
|
||||||
|
.action(ArgAction::Set)
|
||||||
|
.default_value("no-printing")
|
||||||
|
.value_parser(["no-printing", "as-text"])
|
||||||
|
.value_name("behavior")
|
||||||
|
.hide_default_value(true)
|
||||||
|
.help("How to treat binary content. (default: no-printing)")
|
||||||
|
.long_help(
|
||||||
|
"How to treat binary content. (default: no-printing)\n\n\
|
||||||
|
Possible values:\n \
|
||||||
|
* no-printing: do not print any binary content\n \
|
||||||
|
* as-text: treat binary content as normal text",
|
||||||
|
),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("plain")
|
Arg::new("plain")
|
||||||
.overrides_with("plain")
|
.overrides_with("plain")
|
||||||
@@ -311,7 +329,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.long_help(
|
.long_help(
|
||||||
"Specify when to use the pager. To disable the pager, use \
|
"Specify when to use the pager. To disable the pager, use \
|
||||||
--paging=never' or its alias,'-P'. To disable the pager permanently, \
|
--paging=never' or its alias,'-P'. To disable the pager permanently, \
|
||||||
set BAT_PAGER to an empty string. To control which pager is used, see the \
|
set BAT_PAGING to 'never'. To control which pager is used, see the \
|
||||||
'--pager' option. Possible values: *auto*, never, always."
|
'--pager' option. Possible values: *auto*, never, always."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -375,9 +393,40 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
see all available themes. To set a default theme, add the \
|
see all available themes. To set a default theme, add the \
|
||||||
'--theme=\"...\"' option to the configuration file or export the \
|
'--theme=\"...\"' option to the configuration file or export the \
|
||||||
BAT_THEME environment variable (e.g.: export \
|
BAT_THEME environment variable (e.g.: export \
|
||||||
BAT_THEME=\"...\").",
|
BAT_THEME=\"...\").\n\n\
|
||||||
|
Special values:\n\n \
|
||||||
|
* auto: Picks a dark or light theme depending on the terminal's colors (default).\n \
|
||||||
|
Use '--theme-light' and '--theme-dark' to customize the selected theme.\n \
|
||||||
|
* auto:always: Detect the terminal's colors even when the output is redirected.\n \
|
||||||
|
* auto:system: Detect the color scheme from the system-wide preference (macOS only).\n \
|
||||||
|
* dark: Use the dark theme specified by '--theme-dark'.\n \
|
||||||
|
* light: Use the light theme specified by '--theme-light'.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("theme-light")
|
||||||
|
.long("theme-light")
|
||||||
|
.overrides_with("theme-light")
|
||||||
|
.value_name("theme")
|
||||||
|
.help("Sets the color theme for syntax highlighting used for light backgrounds.")
|
||||||
|
.long_help(
|
||||||
|
"Sets the theme name for syntax highlighting used when the terminal uses a light background. \
|
||||||
|
Use '--list-themes' to see all available themes. To set a default theme, add the \
|
||||||
|
'--theme-light=\"...\" option to the configuration file or export the BAT_THEME_LIGHT \
|
||||||
|
environment variable (e.g. export BAT_THEME_LIGHT=\"...\")."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("theme-dark")
|
||||||
|
.long("theme-dark")
|
||||||
|
.overrides_with("theme-dark")
|
||||||
|
.value_name("theme")
|
||||||
|
.help("Sets the color theme for syntax highlighting used for dark backgrounds.")
|
||||||
|
.long_help(
|
||||||
|
"Sets the theme name for syntax highlighting used when the terminal uses a dark background. \
|
||||||
|
Use '--list-themes' to see all available themes. To set a default theme, add the \
|
||||||
|
'--theme-dark=\"...\" option to the configuration file or export the BAT_THEME_DARK \
|
||||||
|
environment variable (e.g. export BAT_THEME_DARK=\"...\")."),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("list-themes")
|
Arg::new("list-themes")
|
||||||
.long("list-themes")
|
.long("list-themes")
|
||||||
@@ -385,37 +434,45 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.help("Display all supported highlighting themes.")
|
.help("Display all supported highlighting themes.")
|
||||||
.long_help("Display a list of supported themes for syntax highlighting."),
|
.long_help("Display a list of supported themes for syntax highlighting."),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("squeeze-blank")
|
||||||
|
.long("squeeze-blank")
|
||||||
|
.short('s')
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Squeeze consecutive empty lines.")
|
||||||
|
.long_help("Squeeze consecutive empty lines into a single empty line.")
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("squeeze-limit")
|
||||||
|
.long("squeeze-limit")
|
||||||
|
.value_parser(|s: &str| s.parse::<usize>().map_err(|_| "Requires a non-negative number".to_owned()))
|
||||||
|
.long_help("Set the maximum number of consecutive empty lines to be printed.")
|
||||||
|
.hide_short_help(true)
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("strip-ansi")
|
||||||
|
.long("strip-ansi")
|
||||||
|
.overrides_with("strip-ansi")
|
||||||
|
.value_name("when")
|
||||||
|
.value_parser(["auto", "always", "never"])
|
||||||
|
.default_value("never")
|
||||||
|
.hide_default_value(true)
|
||||||
|
.help("Strip colors from the input (auto, always, *never*)")
|
||||||
|
.long_help("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. Possible values: auto, always, *never*.")
|
||||||
|
.hide_short_help(true)
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("style")
|
Arg::new("style")
|
||||||
.long("style")
|
.long("style")
|
||||||
|
.action(ArgAction::Append)
|
||||||
.value_name("components")
|
.value_name("components")
|
||||||
.overrides_with("style")
|
|
||||||
.overrides_with("plain")
|
|
||||||
.overrides_with("number")
|
|
||||||
// Cannot use claps built in validation because we have to turn off clap's delimiters
|
// Cannot use claps built in validation because we have to turn off clap's delimiters
|
||||||
.value_parser(|val: &str| {
|
.value_parser(|val: &str| {
|
||||||
let mut invalid_vals = val.split(',').filter(|style| {
|
match StyleComponentList::from_str(val) {
|
||||||
!&[
|
Err(err) => Err(err),
|
||||||
"auto",
|
Ok(_) => Ok(val.to_owned()),
|
||||||
"full",
|
|
||||||
"default",
|
|
||||||
"plain",
|
|
||||||
"header",
|
|
||||||
"header-filename",
|
|
||||||
"header-filesize",
|
|
||||||
"grid",
|
|
||||||
"rule",
|
|
||||||
"numbers",
|
|
||||||
"snip",
|
|
||||||
#[cfg(feature = "git")]
|
|
||||||
"changes",
|
|
||||||
].contains(style)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(invalid) = invalid_vals.next() {
|
|
||||||
Err(format!("Unknown style, '{}'", invalid))
|
|
||||||
} else {
|
|
||||||
Ok(val.to_owned())
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.help(
|
.help(
|
||||||
@@ -430,6 +487,14 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
pre-defined style ('full'). To set a default style, add the \
|
pre-defined style ('full'). To set a default style, add the \
|
||||||
'--style=\"..\"' option to the configuration file or export the \
|
'--style=\"..\"' option to the configuration file or export the \
|
||||||
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\
|
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\").\n\n\
|
||||||
|
When styles are specified in multiple places, the \"nearest\" set \
|
||||||
|
of styles take precedence. The command-line arguments are the highest \
|
||||||
|
priority, followed by the BAT_STYLE environment variable, and then \
|
||||||
|
the configuration file. If any set of styles consists entirely of \
|
||||||
|
components prefixed with \"+\" or \"-\", it will modify the \
|
||||||
|
previous set of styles instead of replacing them.\n\n\
|
||||||
|
By default, the following components are enabled:\n \
|
||||||
|
changes, grid, header-filename, numbers, snip\n\n\
|
||||||
Possible values:\n\n \
|
Possible values:\n\n \
|
||||||
* default: enables recommended style components (default).\n \
|
* default: enables recommended style components (default).\n \
|
||||||
* full: enables all available components.\n \
|
* full: enables all available components.\n \
|
||||||
@@ -497,7 +562,39 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
.hide(true)
|
.hide(true)
|
||||||
.help("Do not load custom assets"),
|
.help("Do not load custom assets"),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "application")]
|
||||||
|
{
|
||||||
|
app = app.arg(
|
||||||
|
Arg::new("completion")
|
||||||
|
.long("completion")
|
||||||
|
.value_name("SHELL")
|
||||||
|
.value_parser(["bash", "fish", "ps1", "zsh"])
|
||||||
|
.help("Show shell completion for a certain shell. [possible values: bash, fish, zsh, ps1]"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
{
|
||||||
|
app = app
|
||||||
|
.arg(
|
||||||
|
Arg::new("lessopen")
|
||||||
|
.long("lessopen")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.help("Enable the $LESSOPEN preprocessor"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("no-lessopen")
|
||||||
|
.long("no-lessopen")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.overrides_with("lessopen")
|
||||||
|
.hide(true)
|
||||||
|
.help("Disable the $LESSOPEN preprocessor if enabled (overrides --lessopen)"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
app = app
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("config-file")
|
Arg::new("config-file")
|
||||||
.long("config-file")
|
.long("config-file")
|
||||||
@@ -536,7 +633,7 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.alias("diagnostics")
|
.alias("diagnostics")
|
||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
.hide_short_help(true)
|
.hide_short_help(true)
|
||||||
.help("Show diagnostic information for bug reports.")
|
.help("Show diagnostic information for bug reports."),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new("acknowledgements")
|
Arg::new("acknowledgements")
|
||||||
@@ -544,6 +641,13 @@ pub fn build_app(interactive_output: bool) -> Command {
|
|||||||
.action(ArgAction::SetTrue)
|
.action(ArgAction::SetTrue)
|
||||||
.hide_short_help(true)
|
.hide_short_help(true)
|
||||||
.help("Show acknowledgements."),
|
.help("Show acknowledgements."),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("set-terminal-title")
|
||||||
|
.long("set-terminal-title")
|
||||||
|
.action(ArgAction::SetTrue)
|
||||||
|
.hide_short_help(true)
|
||||||
|
.help("Sets terminal title to filenames when using a pager."),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the current directory contains a file name cache. Otherwise,
|
// Check if the current directory contains a file name cache. Otherwise,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
pub const BASH_COMPLETION: &str = include_str!(env!("BAT_GENERATED_COMPLETION_BASH"));
|
||||||
|
pub const FISH_COMPLETION: &str = include_str!(env!("BAT_GENERATED_COMPLETION_FISH"));
|
||||||
|
pub const PS1_COMPLETION: &str = include_str!(env!("BAT_GENERATED_COMPLETION_PS1"));
|
||||||
|
pub const ZSH_COMPLETION: &str = include_str!(env!("BAT_GENERATED_COMPLETION_ZSH"));
|
||||||
@@ -140,13 +140,19 @@ fn get_args_from_str(content: &str) -> Result<Vec<OsString>, shell_words::ParseE
|
|||||||
pub fn get_args_from_env_vars() -> Vec<OsString> {
|
pub fn get_args_from_env_vars() -> Vec<OsString> {
|
||||||
[
|
[
|
||||||
("--tabs", "BAT_TABS"),
|
("--tabs", "BAT_TABS"),
|
||||||
("--theme", "BAT_THEME"),
|
("--theme", bat::theme::env::BAT_THEME),
|
||||||
|
("--theme-dark", bat::theme::env::BAT_THEME_DARK),
|
||||||
|
("--theme-light", bat::theme::env::BAT_THEME_LIGHT),
|
||||||
("--pager", "BAT_PAGER"),
|
("--pager", "BAT_PAGER"),
|
||||||
|
("--paging", "BAT_PAGING"),
|
||||||
("--style", "BAT_STYLE"),
|
("--style", "BAT_STYLE"),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(flag, key)| env::var(key).ok().map(|var| [flag.to_string(), var]))
|
.filter_map(|(flag, key)| {
|
||||||
.flatten()
|
env::var(key)
|
||||||
|
.ok()
|
||||||
|
.map(|var| [flag.to_string(), var].join("="))
|
||||||
|
})
|
||||||
.map(|a| a.into())
|
.map(|a| a.into())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-40
@@ -1,12 +1,11 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use etcetera::BaseStrategy;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
/// Wrapper for 'dirs' that treats MacOS more like Linux, by following the XDG specification.
|
/// Wrapper for 'etcetera' that checks BAT_CACHE_PATH and BAT_CONFIG_DIR and falls back to the
|
||||||
/// The `XDG_CACHE_HOME` environment variable is checked first. `BAT_CONFIG_DIR`
|
/// Windows known folder locations on Windows & the XDG Base Directory Specification everywhere else.
|
||||||
/// is then checked before the `XDG_CONFIG_HOME` environment variable.
|
|
||||||
/// The fallback directories are `~/.cache/bat` and `~/.config/bat`, respectively.
|
|
||||||
pub struct BatProjectDirs {
|
pub struct BatProjectDirs {
|
||||||
cache_dir: PathBuf,
|
cache_dir: PathBuf,
|
||||||
config_dir: PathBuf,
|
config_dir: PathBuf,
|
||||||
@@ -14,25 +13,24 @@ pub struct BatProjectDirs {
|
|||||||
|
|
||||||
impl BatProjectDirs {
|
impl BatProjectDirs {
|
||||||
fn new() -> Option<BatProjectDirs> {
|
fn new() -> Option<BatProjectDirs> {
|
||||||
let cache_dir = BatProjectDirs::get_cache_dir()?;
|
let basedirs = etcetera::choose_base_strategy().ok()?;
|
||||||
|
|
||||||
// Checks whether or not $BAT_CONFIG_DIR exists. If it doesn't, set our config dir
|
// Checks whether or not `$BAT_CACHE_PATH` exists. If it doesn't, set the cache dir to our
|
||||||
// to our system's default configuration home.
|
// system's default cache home.
|
||||||
let config_dir =
|
let cache_dir = if let Some(cache_dir) = env::var_os("BAT_CACHE_PATH").map(PathBuf::from) {
|
||||||
if let Some(config_dir_op) = env::var_os("BAT_CONFIG_DIR").map(PathBuf::from) {
|
cache_dir
|
||||||
config_dir_op
|
} else {
|
||||||
} else {
|
basedirs.cache_dir().join("bat")
|
||||||
#[cfg(target_os = "macos")]
|
};
|
||||||
let config_dir_op = env::var_os("XDG_CONFIG_HOME")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.filter(|p| p.is_absolute())
|
|
||||||
.or_else(|| dirs::home_dir().map(|d| d.join(".config")));
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
// Checks whether or not `$BAT_CONFIG_DIR` exists. If it doesn't, set the config dir to our
|
||||||
let config_dir_op = dirs::config_dir();
|
// system's default configuration home.
|
||||||
|
let config_dir = if let Some(config_dir) = env::var_os("BAT_CONFIG_DIR").map(PathBuf::from)
|
||||||
config_dir_op.map(|d| d.join("bat"))?
|
{
|
||||||
};
|
config_dir
|
||||||
|
} else {
|
||||||
|
basedirs.config_dir().join("bat")
|
||||||
|
};
|
||||||
|
|
||||||
Some(BatProjectDirs {
|
Some(BatProjectDirs {
|
||||||
cache_dir,
|
cache_dir,
|
||||||
@@ -40,25 +38,6 @@ impl BatProjectDirs {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cache_dir() -> Option<PathBuf> {
|
|
||||||
// on all OS prefer BAT_CACHE_PATH if set
|
|
||||||
let cache_dir_op = env::var_os("BAT_CACHE_PATH").map(PathBuf::from);
|
|
||||||
if cache_dir_op.is_some() {
|
|
||||||
return cache_dir_op;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let cache_dir_op = env::var_os("XDG_CACHE_HOME")
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.filter(|p| p.is_absolute())
|
|
||||||
.or_else(|| dirs::home_dir().map(|d| d.join(".cache")));
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
let cache_dir_op = dirs::cache_dir();
|
|
||||||
|
|
||||||
cache_dir_op.map(|d| d.join("bat"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cache_dir(&self) -> &Path {
|
pub fn cache_dir(&self) -> &Path {
|
||||||
&self.cache_dir
|
&self.cache_dir
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-25
@@ -3,6 +3,8 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod assets;
|
mod assets;
|
||||||
mod clap_app;
|
mod clap_app;
|
||||||
|
#[cfg(feature = "application")]
|
||||||
|
mod completions;
|
||||||
mod config;
|
mod config;
|
||||||
mod directories;
|
mod directories;
|
||||||
mod input;
|
mod input;
|
||||||
@@ -14,6 +16,7 @@ use std::io::{BufReader, Write};
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
|
use bat::theme::DetectColorScheme;
|
||||||
use nu_ansi_term::Color::Green;
|
use nu_ansi_term::Color::Green;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ use bat::{
|
|||||||
error::*,
|
error::*,
|
||||||
input::Input,
|
input::Input,
|
||||||
style::{StyleComponent, StyleComponents},
|
style::{StyleComponent, StyleComponents},
|
||||||
|
theme::{color_scheme, default_theme, ColorScheme},
|
||||||
MappingTarget, PagingMode,
|
MappingTarget, PagingMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -78,9 +82,11 @@ fn run_cache_subcommand(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_syntax_mapping_to_paths<'a>(
|
fn get_syntax_mapping_to_paths<'r, 't, I>(mappings: I) -> HashMap<&'t str, Vec<String>>
|
||||||
mappings: &[(GlobMatcher, MappingTarget<'a>)],
|
where
|
||||||
) -> HashMap<&'a str, Vec<String>> {
|
I: IntoIterator<Item = (&'r GlobMatcher, &'r MappingTarget<'t>)>,
|
||||||
|
't: 'r, // target text outlives rule
|
||||||
|
{
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
for mapping in mappings {
|
for mapping in mappings {
|
||||||
if let (matcher, MappingTarget::MapTo(s)) = mapping {
|
if let (matcher, MappingTarget::MapTo(s)) = mapping {
|
||||||
@@ -123,7 +129,7 @@ pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
|
|||||||
|
|
||||||
languages.sort_by_key(|lang| lang.name.to_uppercase());
|
languages.sort_by_key(|lang| lang.name.to_uppercase());
|
||||||
|
|
||||||
let configured_languages = get_syntax_mapping_to_paths(config.syntax_mapping.mappings());
|
let configured_languages = get_syntax_mapping_to_paths(config.syntax_mapping.all_mappings());
|
||||||
|
|
||||||
for lang in &mut languages {
|
for lang in &mut languages {
|
||||||
if let Some(additional_paths) = configured_languages.get(lang.name.as_str()) {
|
if let Some(additional_paths) = configured_languages.get(lang.name.as_str()) {
|
||||||
@@ -186,7 +192,12 @@ fn theme_preview_file<'a>() -> Input<'a> {
|
|||||||
Input::from_reader(Box::new(BufReader::new(THEME_PREVIEW_DATA)))
|
Input::from_reader(Box::new(BufReader::new(THEME_PREVIEW_DATA)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<()> {
|
pub fn list_themes(
|
||||||
|
cfg: &Config,
|
||||||
|
config_dir: &Path,
|
||||||
|
cache_dir: &Path,
|
||||||
|
detect_color_scheme: DetectColorScheme,
|
||||||
|
) -> Result<()> {
|
||||||
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
|
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
|
||||||
let mut config = cfg.clone();
|
let mut config = cfg.clone();
|
||||||
let mut style = HashSet::new();
|
let mut style = HashSet::new();
|
||||||
@@ -197,19 +208,35 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
|
|||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut stdout = stdout.lock();
|
let mut stdout = stdout.lock();
|
||||||
|
|
||||||
if config.colored_output {
|
let default_theme_name = default_theme(color_scheme(detect_color_scheme).unwrap_or_default());
|
||||||
for theme in assets.themes() {
|
for theme in assets.themes() {
|
||||||
|
let default_theme_info = if !config.loop_through && default_theme_name == theme {
|
||||||
|
" (default)"
|
||||||
|
} else if default_theme(ColorScheme::Dark) == theme {
|
||||||
|
" (default dark)"
|
||||||
|
} else if default_theme(ColorScheme::Light) == theme {
|
||||||
|
" (default light)"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
if config.colored_output {
|
||||||
writeln!(
|
writeln!(
|
||||||
stdout,
|
stdout,
|
||||||
"Theme: {}\n",
|
"Theme: {}{}\n",
|
||||||
Style::new().bold().paint(theme.to_string())
|
Style::new().bold().paint(theme.to_string()),
|
||||||
|
default_theme_info
|
||||||
)?;
|
)?;
|
||||||
config.theme = theme.to_string();
|
config.theme = theme.to_string();
|
||||||
Controller::new(&config, &assets)
|
Controller::new(&config, &assets)
|
||||||
.run(vec![theme_preview_file()])
|
.run(vec![theme_preview_file()], None)
|
||||||
.ok();
|
.ok();
|
||||||
writeln!(stdout)?;
|
writeln!(stdout)?;
|
||||||
|
} else {
|
||||||
|
writeln!(stdout, "{theme}{default_theme_info}")?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.colored_output {
|
||||||
writeln!(
|
writeln!(
|
||||||
stdout,
|
stdout,
|
||||||
"Further themes can be installed to '{}', \
|
"Further themes can be installed to '{}', \
|
||||||
@@ -218,19 +245,36 @@ pub fn list_themes(cfg: &Config, config_dir: &Path, cache_dir: &Path) -> Result<
|
|||||||
https://github.com/sharkdp/bat#adding-new-themes",
|
https://github.com/sharkdp/bat#adding-new-themes",
|
||||||
config_dir.join("themes").to_string_lossy()
|
config_dir.join("themes").to_string_lossy()
|
||||||
)?;
|
)?;
|
||||||
} else {
|
|
||||||
for theme in assets.themes() {
|
|
||||||
writeln!(stdout, "{}", theme)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_terminal_title_to(new_terminal_title: String) {
|
||||||
|
let osc_command_for_setting_terminal_title = "\x1b]0;";
|
||||||
|
let osc_end_command = "\x07";
|
||||||
|
print!("{osc_command_for_setting_terminal_title}{new_terminal_title}{osc_end_command}");
|
||||||
|
io::stdout().flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_new_terminal_title(inputs: &Vec<Input>) -> String {
|
||||||
|
let mut new_terminal_title = "bat: ".to_string();
|
||||||
|
for (index, input) in inputs.iter().enumerate() {
|
||||||
|
new_terminal_title += input.description().title();
|
||||||
|
if index < inputs.len() - 1 {
|
||||||
|
new_terminal_title += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_terminal_title
|
||||||
|
}
|
||||||
|
|
||||||
fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> {
|
fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> {
|
||||||
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
|
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
|
||||||
let controller = Controller::new(config, &assets);
|
let controller = Controller::new(config, &assets);
|
||||||
controller.run(inputs)
|
if config.paging_mode != PagingMode::Never && config.set_terminal_title {
|
||||||
|
set_terminal_title_to(get_new_terminal_title(&inputs));
|
||||||
|
}
|
||||||
|
controller.run(inputs, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bugreport")]
|
#[cfg(feature = "bugreport")]
|
||||||
@@ -249,23 +293,25 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) {
|
|||||||
.info(OperatingSystem::default())
|
.info(OperatingSystem::default())
|
||||||
.info(CommandLine::default())
|
.info(CommandLine::default())
|
||||||
.info(EnvironmentVariables::list(&[
|
.info(EnvironmentVariables::list(&[
|
||||||
"SHELL",
|
|
||||||
"PAGER",
|
|
||||||
"LESS",
|
|
||||||
"LANG",
|
|
||||||
"LC_ALL",
|
|
||||||
"BAT_PAGER",
|
|
||||||
"BAT_CACHE_PATH",
|
"BAT_CACHE_PATH",
|
||||||
"BAT_CONFIG_PATH",
|
"BAT_CONFIG_PATH",
|
||||||
"BAT_OPTS",
|
"BAT_OPTS",
|
||||||
|
"BAT_PAGER",
|
||||||
|
"BAT_PAGING",
|
||||||
"BAT_STYLE",
|
"BAT_STYLE",
|
||||||
"BAT_TABS",
|
"BAT_TABS",
|
||||||
"BAT_THEME",
|
"BAT_THEME",
|
||||||
"XDG_CONFIG_HOME",
|
|
||||||
"XDG_CACHE_HOME",
|
|
||||||
"COLORTERM",
|
"COLORTERM",
|
||||||
"NO_COLOR",
|
"LANG",
|
||||||
|
"LC_ALL",
|
||||||
|
"LESS",
|
||||||
"MANPAGER",
|
"MANPAGER",
|
||||||
|
"NO_COLOR",
|
||||||
|
"PAGER",
|
||||||
|
"SHELL",
|
||||||
|
"TERM",
|
||||||
|
"XDG_CACHE_HOME",
|
||||||
|
"XDG_CONFIG_HOME",
|
||||||
]))
|
]))
|
||||||
.info(FileContent::new("System Config file", system_config_file()))
|
.info(FileContent::new("System Config file", system_config_file()))
|
||||||
.info(FileContent::new("Config file", config_file()))
|
.info(FileContent::new("Config file", config_file()))
|
||||||
@@ -303,6 +349,18 @@ fn run() -> Result<bool> {
|
|||||||
return Ok(true);
|
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),
|
||||||
|
_ => unreachable!("No completion for shell '{}' available.", shell),
|
||||||
|
}
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
match app.matches.subcommand() {
|
match app.matches.subcommand() {
|
||||||
Some(("cache", cache_matches)) => {
|
Some(("cache", cache_matches)) => {
|
||||||
// If there is a file named 'cache' in the current working directory,
|
// If there is a file named 'cache' in the current working directory,
|
||||||
@@ -337,7 +395,7 @@ fn run() -> Result<bool> {
|
|||||||
};
|
};
|
||||||
run_controller(inputs, &plain_config, cache_dir)
|
run_controller(inputs, &plain_config, cache_dir)
|
||||||
} else if app.matches.get_flag("list-themes") {
|
} else if app.matches.get_flag("list-themes") {
|
||||||
list_themes(&config, config_dir, cache_dir)?;
|
list_themes(&config, config_dir, cache_dir, DetectColorScheme::default())?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else if app.matches.get_flag("config-file") {
|
} else if app.matches.get_flag("config-file") {
|
||||||
println!("{}", config_file().to_string_lossy());
|
println!("{}", config_file().to_string_lossy());
|
||||||
|
|||||||
+18
-1
@@ -1,10 +1,11 @@
|
|||||||
use crate::line_range::{HighlightedLineRanges, LineRanges};
|
use crate::line_range::{HighlightedLineRanges, LineRanges};
|
||||||
use crate::nonprintable_notation::NonprintableNotation;
|
use crate::nonprintable_notation::{BinaryBehavior, NonprintableNotation};
|
||||||
#[cfg(feature = "paging")]
|
#[cfg(feature = "paging")]
|
||||||
use crate::paging::PagingMode;
|
use crate::paging::PagingMode;
|
||||||
use crate::style::StyleComponents;
|
use crate::style::StyleComponents;
|
||||||
use crate::syntax_mapping::SyntaxMapping;
|
use crate::syntax_mapping::SyntaxMapping;
|
||||||
use crate::wrapping::WrappingMode;
|
use crate::wrapping::WrappingMode;
|
||||||
|
use crate::StripAnsiMode;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum VisibleLines {
|
pub enum VisibleLines {
|
||||||
@@ -43,6 +44,9 @@ pub struct Config<'a> {
|
|||||||
/// The configured notation for non-printable characters
|
/// The configured notation for non-printable characters
|
||||||
pub nonprintable_notation: NonprintableNotation,
|
pub nonprintable_notation: NonprintableNotation,
|
||||||
|
|
||||||
|
/// How to treat binary content
|
||||||
|
pub binary: BinaryBehavior,
|
||||||
|
|
||||||
/// The character width of the terminal
|
/// The character width of the terminal
|
||||||
pub term_width: usize,
|
pub term_width: usize,
|
||||||
|
|
||||||
@@ -90,6 +94,19 @@ pub struct Config<'a> {
|
|||||||
/// Whether or not to allow custom assets. If this is false or if custom assets (a.k.a.
|
/// Whether or not to allow custom assets. If this is false or if custom assets (a.k.a.
|
||||||
/// cached assets) are not available, assets from the binary will be used instead.
|
/// cached assets) are not available, assets from the binary will be used instead.
|
||||||
pub use_custom_assets: bool,
|
pub use_custom_assets: bool,
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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
|
||||||
|
pub strip_ansi: StripAnsiMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
#[cfg(all(feature = "minimal-application", feature = "paging"))]
|
||||||
|
|||||||
+51
-16
@@ -6,34 +6,48 @@ use crate::config::{Config, VisibleLines};
|
|||||||
use crate::diff::{get_git_diff, LineChanges};
|
use crate::diff::{get_git_diff, LineChanges};
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::input::{Input, InputReader, OpenedInput};
|
use crate::input::{Input, InputReader, OpenedInput};
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
use crate::lessopen::LessOpenPreprocessor;
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
use crate::line_range::LineRange;
|
use crate::line_range::LineRange;
|
||||||
use crate::line_range::{LineRanges, RangeCheckResult};
|
use crate::line_range::{LineRanges, RangeCheckResult};
|
||||||
use crate::output::OutputType;
|
use crate::output::OutputType;
|
||||||
#[cfg(feature = "paging")]
|
#[cfg(feature = "paging")]
|
||||||
use crate::paging::PagingMode;
|
use crate::paging::PagingMode;
|
||||||
use crate::printer::{InteractivePrinter, Printer, SimplePrinter};
|
use crate::printer::{InteractivePrinter, OutputHandle, Printer, SimplePrinter};
|
||||||
|
|
||||||
use clircle::{Clircle, Identifier};
|
use clircle::{Clircle, Identifier};
|
||||||
|
|
||||||
pub struct Controller<'a> {
|
pub struct Controller<'a> {
|
||||||
config: &'a Config<'a>,
|
config: &'a Config<'a>,
|
||||||
assets: &'a HighlightingAssets,
|
assets: &'a HighlightingAssets,
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
preprocessor: Option<LessOpenPreprocessor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b> Controller<'b> {
|
impl<'b> Controller<'b> {
|
||||||
pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
|
pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
|
||||||
Controller { config, assets }
|
Controller {
|
||||||
|
config,
|
||||||
|
assets,
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
preprocessor: LessOpenPreprocessor::new().ok(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
|
pub fn run(
|
||||||
self.run_with_error_handler(inputs, default_error_handler)
|
&self,
|
||||||
|
inputs: Vec<Input>,
|
||||||
|
output_buffer: Option<&mut dyn std::fmt::Write>,
|
||||||
|
) -> Result<bool> {
|
||||||
|
self.run_with_error_handler(inputs, output_buffer, default_error_handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_with_error_handler(
|
pub fn run_with_error_handler(
|
||||||
&self,
|
&self,
|
||||||
inputs: Vec<Input>,
|
inputs: Vec<Input>,
|
||||||
handle_error: impl Fn(&Error, &mut dyn Write),
|
output_buffer: Option<&mut dyn std::fmt::Write>,
|
||||||
|
mut handle_error: impl FnMut(&Error, &mut dyn Write),
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let mut output_type;
|
let mut output_type;
|
||||||
|
|
||||||
@@ -74,7 +88,10 @@ impl<'b> Controller<'b> {
|
|||||||
clircle::Identifier::stdout()
|
clircle::Identifier::stdout()
|
||||||
};
|
};
|
||||||
|
|
||||||
let writer = output_type.handle()?;
|
let mut writer = match output_buffer {
|
||||||
|
Some(buf) => OutputHandle::FmtWrite(buf),
|
||||||
|
None => OutputHandle::IoWrite(output_type.handle()?),
|
||||||
|
};
|
||||||
let mut no_errors: bool = true;
|
let mut no_errors: bool = true;
|
||||||
let stderr = io::stderr();
|
let stderr = io::stderr();
|
||||||
|
|
||||||
@@ -82,16 +99,23 @@ impl<'b> Controller<'b> {
|
|||||||
let identifier = stdout_identifier.as_ref();
|
let identifier = stdout_identifier.as_ref();
|
||||||
let is_first = index == 0;
|
let is_first = index == 0;
|
||||||
let result = if input.is_stdin() {
|
let result = if input.is_stdin() {
|
||||||
self.print_input(input, writer, io::stdin().lock(), identifier, is_first)
|
self.print_input(input, &mut writer, io::stdin().lock(), identifier, is_first)
|
||||||
} else {
|
} else {
|
||||||
// Use dummy stdin since stdin is actually not used (#1902)
|
// Use dummy stdin since stdin is actually not used (#1902)
|
||||||
self.print_input(input, writer, io::empty(), identifier, is_first)
|
self.print_input(input, &mut writer, io::empty(), identifier, is_first)
|
||||||
};
|
};
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
if attached_to_pager {
|
match writer {
|
||||||
handle_error(&error, writer);
|
// It doesn't make much sense to send errors straight to stderr if the user
|
||||||
} else {
|
// provided their own buffer, so we just return it.
|
||||||
handle_error(&error, &mut stderr.lock());
|
OutputHandle::FmtWrite(_) => return Err(error),
|
||||||
|
OutputHandle::IoWrite(ref mut writer) => {
|
||||||
|
if attached_to_pager {
|
||||||
|
handle_error(&error, writer);
|
||||||
|
} else {
|
||||||
|
handle_error(&error, &mut stderr.lock());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
no_errors = false;
|
no_errors = false;
|
||||||
}
|
}
|
||||||
@@ -103,12 +127,23 @@ impl<'b> Controller<'b> {
|
|||||||
fn print_input<R: BufRead>(
|
fn print_input<R: BufRead>(
|
||||||
&self,
|
&self,
|
||||||
input: Input,
|
input: Input,
|
||||||
writer: &mut dyn Write,
|
writer: &mut OutputHandle,
|
||||||
stdin: R,
|
stdin: R,
|
||||||
stdout_identifier: Option<&Identifier>,
|
stdout_identifier: Option<&Identifier>,
|
||||||
is_first: bool,
|
is_first: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut opened_input = input.open(stdin, stdout_identifier)?;
|
let mut opened_input = {
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
match self.preprocessor {
|
||||||
|
Some(ref preprocessor) if self.config.use_lessopen => {
|
||||||
|
preprocessor.open(input, stdin, stdout_identifier)?
|
||||||
|
}
|
||||||
|
_ => input.open(stdin, stdout_identifier)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "lessopen"))]
|
||||||
|
input.open(stdin, stdout_identifier)?
|
||||||
|
};
|
||||||
#[cfg(feature = "git")]
|
#[cfg(feature = "git")]
|
||||||
let line_changes = if self.config.visible_lines.diff_mode()
|
let line_changes = if self.config.visible_lines.diff_mode()
|
||||||
|| (!self.config.loop_through && self.config.style_components.changes())
|
|| (!self.config.loop_through && self.config.style_components.changes())
|
||||||
@@ -164,7 +199,7 @@ impl<'b> Controller<'b> {
|
|||||||
fn print_file(
|
fn print_file(
|
||||||
&self,
|
&self,
|
||||||
printer: &mut dyn Printer,
|
printer: &mut dyn Printer,
|
||||||
writer: &mut dyn Write,
|
writer: &mut OutputHandle,
|
||||||
input: &mut OpenedInput,
|
input: &mut OpenedInput,
|
||||||
add_header_padding: bool,
|
add_header_padding: bool,
|
||||||
#[cfg(feature = "git")] line_changes: &Option<LineChanges>,
|
#[cfg(feature = "git")] line_changes: &Option<LineChanges>,
|
||||||
@@ -202,7 +237,7 @@ impl<'b> Controller<'b> {
|
|||||||
fn print_file_ranges(
|
fn print_file_ranges(
|
||||||
&self,
|
&self,
|
||||||
printer: &mut dyn Printer,
|
printer: &mut dyn Printer,
|
||||||
writer: &mut dyn Write,
|
writer: &mut OutputHandle,
|
||||||
reader: &mut InputReader,
|
reader: &mut InputReader,
|
||||||
line_ranges: &LineRanges,
|
line_ranges: &LineRanges,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
|||||||
+2
-2
@@ -46,7 +46,7 @@ impl Decoration for LineNumberDecoration {
|
|||||||
_printer: &InteractivePrinter,
|
_printer: &InteractivePrinter,
|
||||||
) -> DecorationText {
|
) -> DecorationText {
|
||||||
if continuation {
|
if continuation {
|
||||||
if line_number > self.cached_wrap_invalid_at {
|
if line_number >= self.cached_wrap_invalid_at {
|
||||||
let new_width = self.cached_wrap.width + 1;
|
let new_width = self.cached_wrap.width + 1;
|
||||||
return DecorationText {
|
return DecorationText {
|
||||||
text: self.color.paint(" ".repeat(new_width)).to_string(),
|
text: self.color.paint(" ".repeat(new_width)).to_string(),
|
||||||
@@ -56,7 +56,7 @@ impl Decoration for LineNumberDecoration {
|
|||||||
|
|
||||||
self.cached_wrap.clone()
|
self.cached_wrap.clone()
|
||||||
} else {
|
} else {
|
||||||
let plain: String = format!("{:4}", line_number);
|
let plain: String = format!("{line_number:4}");
|
||||||
DecorationText {
|
DecorationText {
|
||||||
width: plain.len(),
|
width: plain.len(),
|
||||||
text: self.color.paint(plain).to_string(),
|
text: self.color.paint(plain).to_string(),
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ pub enum Error {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] ::std::io::Error),
|
Io(#[from] ::std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
Fmt(#[from] ::std::fmt::Error),
|
||||||
|
#[error(transparent)]
|
||||||
SyntectError(#[from] ::syntect::Error),
|
SyntectError(#[from] ::syntect::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SyntectLoadingError(#[from] ::syntect::LoadingError),
|
SyntectLoadingError(#[from] ::syntect::LoadingError),
|
||||||
@@ -26,6 +28,12 @@ pub enum Error {
|
|||||||
InvalidPagerValueBat,
|
InvalidPagerValueBat,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Msg(String),
|
Msg(String),
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
#[error(transparent)]
|
||||||
|
VarError(#[from] ::std::env::VarError),
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
#[error(transparent)]
|
||||||
|
CommandParseError(#[from] ::shell_words::ParseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'static str> for Error {
|
impl From<&'static str> for Error {
|
||||||
|
|||||||
+2
-2
@@ -197,7 +197,7 @@ impl<'a> Input<'a> {
|
|||||||
InputKind::StdIn => {
|
InputKind::StdIn => {
|
||||||
if let Some(stdout) = stdout_identifier {
|
if let Some(stdout) = stdout_identifier {
|
||||||
let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
|
let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
|
||||||
.map_err(|e| format!("Stdin: Error identifying file: {}", e))?;
|
.map_err(|e| format!("Stdin: Error identifying file: {e}"))?;
|
||||||
if stdout.surely_conflicts_with(&input_identifier) {
|
if stdout.surely_conflicts_with(&input_identifier) {
|
||||||
return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
|
return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ pub(crate) struct InputReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InputReader<'a> {
|
impl<'a> InputReader<'a> {
|
||||||
fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
|
pub(crate) fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
|
||||||
let mut first_line = vec![];
|
let mut first_line = vec![];
|
||||||
reader.read_until(b'\n', &mut first_line).ok();
|
reader.read_until(b'\n', &mut first_line).ok();
|
||||||
|
|
||||||
|
|||||||
+389
@@ -0,0 +1,389 @@
|
|||||||
|
#![cfg(feature = "lessopen")]
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::env;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, BufReader, Cursor, Read};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{ExitStatus, Stdio};
|
||||||
|
|
||||||
|
use clircle::{Clircle, Identifier};
|
||||||
|
use execute::{shell, Execute};
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::{
|
||||||
|
bat_warning,
|
||||||
|
input::{Input, InputKind, InputReader, OpenedInput, OpenedInputKind},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Preprocess files and/or stdin using $LESSOPEN and $LESSCLOSE
|
||||||
|
pub(crate) struct LessOpenPreprocessor {
|
||||||
|
lessopen: String,
|
||||||
|
lessclose: Option<String>,
|
||||||
|
kind: LessOpenKind,
|
||||||
|
/// Whether or not data piped via stdin is to be preprocessed
|
||||||
|
preprocess_stdin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LessOpenKind {
|
||||||
|
Piped,
|
||||||
|
PipedIgnoreExitCode,
|
||||||
|
TempFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LessOpenPreprocessor {
|
||||||
|
/// Create a new instance of LessOpenPreprocessor
|
||||||
|
/// Will return Ok if and only if $LESSOPEN is set and contains exactly one %s
|
||||||
|
pub(crate) fn new() -> Result<LessOpenPreprocessor> {
|
||||||
|
let lessopen = env::var("LESSOPEN")?;
|
||||||
|
|
||||||
|
// Ignore $LESSOPEN if it does not contains exactly one %s
|
||||||
|
// Note that $LESSCLOSE has no such requirement
|
||||||
|
if lessopen.match_indices("%s").count() != 1 {
|
||||||
|
let error_msg = "LESSOPEN ignored: must contain exactly one %s";
|
||||||
|
bat_warning!("{}", error_msg);
|
||||||
|
return Err(error_msg.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// "||" means pipe directly to bat without making a temporary file
|
||||||
|
// Also, if preprocessor output is empty and exit code is zero, use the empty output
|
||||||
|
// Otherwise, if output is empty and exit code is nonzero, use original file contents
|
||||||
|
let (kind, lessopen) = if lessopen.starts_with("||") {
|
||||||
|
(LessOpenKind::Piped, lessopen.chars().skip(2).collect())
|
||||||
|
// "|" means pipe as above, but ignore exit code and always use preprocessor output even if empty
|
||||||
|
} else if lessopen.starts_with('|') {
|
||||||
|
(
|
||||||
|
LessOpenKind::PipedIgnoreExitCode,
|
||||||
|
lessopen.chars().skip(1).collect(),
|
||||||
|
)
|
||||||
|
// If neither appear, write output to a temporary file and read from that
|
||||||
|
} else {
|
||||||
|
(LessOpenKind::TempFile, lessopen)
|
||||||
|
};
|
||||||
|
|
||||||
|
// "-" means that stdin is preprocessed along with files and may appear alongside "|" and "||"
|
||||||
|
let (stdin, lessopen) = if lessopen.starts_with('-') {
|
||||||
|
(true, lessopen.chars().skip(1).collect())
|
||||||
|
} else {
|
||||||
|
(false, lessopen)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
lessopen,
|
||||||
|
lessclose: env::var("LESSCLOSE").ok(),
|
||||||
|
kind,
|
||||||
|
preprocess_stdin: stdin,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open<'a, R: BufRead + 'a>(
|
||||||
|
&self,
|
||||||
|
input: Input<'a>,
|
||||||
|
mut stdin: R,
|
||||||
|
stdout_identifier: Option<&Identifier>,
|
||||||
|
) -> Result<OpenedInput<'a>> {
|
||||||
|
let (lessopen_stdout, path_str, kind) = match input.kind {
|
||||||
|
InputKind::OrdinaryFile(ref path) => {
|
||||||
|
let path_str = match path.to_str() {
|
||||||
|
Some(str) => str,
|
||||||
|
None => return input.open(stdin, stdout_identifier),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut lessopen_command = shell(self.lessopen.replacen("%s", path_str, 1));
|
||||||
|
lessopen_command.stdout(Stdio::piped());
|
||||||
|
|
||||||
|
let lessopen_output = match lessopen_command.execute_output() {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(_) => return input.open(stdin, stdout_identifier),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.fall_back_to_original_file(&lessopen_output.stdout, lessopen_output.status)
|
||||||
|
{
|
||||||
|
return input.open(stdin, stdout_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
lessopen_output.stdout,
|
||||||
|
path_str.to_string(),
|
||||||
|
OpenedInputKind::OrdinaryFile(path.to_path_buf()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
InputKind::StdIn => {
|
||||||
|
if self.preprocess_stdin {
|
||||||
|
if let Some(stdout) = stdout_identifier {
|
||||||
|
let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
|
||||||
|
.map_err(|e| format!("Stdin: Error identifying file: {}", e))?;
|
||||||
|
if stdout.surely_conflicts_with(&input_identifier) {
|
||||||
|
return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdin isn't Clone or AsRef<[u8]>, so move it into a cloneable buffer
|
||||||
|
// so the data can be used multiple times if necessary
|
||||||
|
// NOTE: stdin will be empty from this point onwards
|
||||||
|
let mut stdin_buffer = Vec::new();
|
||||||
|
stdin.read_to_end(&mut stdin_buffer)?;
|
||||||
|
|
||||||
|
let mut lessopen_command = shell(self.lessopen.replacen("%s", "-", 1));
|
||||||
|
lessopen_command.stdout(Stdio::piped());
|
||||||
|
|
||||||
|
let lessopen_output = match lessopen_command.execute_input_output(&stdin_buffer)
|
||||||
|
{
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(_) => {
|
||||||
|
return input.open(Cursor::new(stdin_buffer), stdout_identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self
|
||||||
|
.fall_back_to_original_file(&lessopen_output.stdout, lessopen_output.status)
|
||||||
|
{
|
||||||
|
return input.open(Cursor::new(stdin_buffer), stdout_identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
lessopen_output.stdout,
|
||||||
|
"-".to_string(),
|
||||||
|
OpenedInputKind::StdIn,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return input.open(stdin, stdout_identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InputKind::CustomReader(_) => {
|
||||||
|
return input.open(stdin, stdout_identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(OpenedInput {
|
||||||
|
kind,
|
||||||
|
reader: InputReader::new(BufReader::new(
|
||||||
|
if matches!(self.kind, LessOpenKind::TempFile) {
|
||||||
|
let lessopen_string = match String::from_utf8(lessopen_stdout) {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(_) => {
|
||||||
|
return input.open(stdin, stdout_identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Remove newline at end of temporary file path returned by $LESSOPEN
|
||||||
|
let stdout = match lessopen_string.strip_suffix("\n") {
|
||||||
|
Some(stripped) => stripped.to_owned(),
|
||||||
|
None => lessopen_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = match File::open(PathBuf::from(&stdout)) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => {
|
||||||
|
return input.open(stdin, stdout_identifier);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Preprocessed {
|
||||||
|
kind: PreprocessedKind::TempFile(file),
|
||||||
|
lessclose: self
|
||||||
|
.lessclose
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", &stdout, 1)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Preprocessed {
|
||||||
|
kind: PreprocessedKind::Piped(Cursor::new(lessopen_stdout)),
|
||||||
|
lessclose: self
|
||||||
|
.lessclose
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.replacen("%s", &path_str, 1).replacen("%s", "-", 1)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
metadata: input.metadata,
|
||||||
|
description: input.description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fall_back_to_original_file(&self, lessopen_stdout: &Vec<u8>, exit_code: ExitStatus) -> bool {
|
||||||
|
lessopen_stdout.is_empty()
|
||||||
|
&& (!exit_code.success() || matches!(self.kind, LessOpenKind::PipedIgnoreExitCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// For testing purposes only
|
||||||
|
/// Create an instance of LessOpenPreprocessor with specified valued for $LESSOPEN and $LESSCLOSE
|
||||||
|
fn mock_new(lessopen: Option<&str>, lessclose: Option<&str>) -> Result<LessOpenPreprocessor> {
|
||||||
|
if let Some(command) = lessopen {
|
||||||
|
env::set_var("LESSOPEN", command)
|
||||||
|
} else {
|
||||||
|
env::remove_var("LESSOPEN")
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(command) = lessclose {
|
||||||
|
env::set_var("LESSCLOSE", command)
|
||||||
|
} else {
|
||||||
|
env::remove_var("LESSCLOSE")
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PreprocessedKind {
|
||||||
|
Piped(Cursor<Vec<u8>>),
|
||||||
|
TempFile(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for PreprocessedKind {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
|
||||||
|
match self {
|
||||||
|
PreprocessedKind::Piped(data) => data.read(buf),
|
||||||
|
PreprocessedKind::TempFile(data) => data.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Preprocessed {
|
||||||
|
kind: PreprocessedKind,
|
||||||
|
lessclose: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Preprocessed {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::result::Result<usize, std::io::Error> {
|
||||||
|
self.kind.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Preprocessed {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(lessclose) = self.lessclose.clone() {
|
||||||
|
let mut lessclose_command = shell(lessclose);
|
||||||
|
|
||||||
|
let lessclose_output = match lessclose_command.execute_output() {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(_) => {
|
||||||
|
bat_warning!("failed to run $LESSCLOSE to clean up temporary file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if lessclose_output.status.success() {
|
||||||
|
bat_warning!("$LESSCLOSE exited with nonzero exit code",)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// All tests here are serial because they all involve reading and writing environment variables
|
||||||
|
// Running them in parallel causes these tests and some others to randomly fail
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Reset environment variables after each test as a precaution
|
||||||
|
fn reset_env_vars() {
|
||||||
|
env::remove_var("LESSOPEN");
|
||||||
|
env::remove_var("LESSCLOSE");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_just_lessopen() -> Result<()> {
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("|batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(preprocessor.lessclose.is_none());
|
||||||
|
|
||||||
|
reset_env_vars();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_just_lessclose() -> Result<()> {
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(None, Some("lessclose.sh %s %s"));
|
||||||
|
|
||||||
|
assert!(preprocessor.is_err());
|
||||||
|
|
||||||
|
reset_env_vars();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_both_lessopen_and_lessclose() -> Result<()> {
|
||||||
|
let preprocessor =
|
||||||
|
LessOpenPreprocessor::mock_new(Some("lessopen.sh %s"), Some("lessclose.sh %s %s"))?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "lessopen.sh %s");
|
||||||
|
assert_eq!(preprocessor.lessclose.unwrap(), "lessclose.sh %s %s");
|
||||||
|
|
||||||
|
reset_env_vars();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_lessopen_prefixes() -> Result<()> {
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(preprocessor.kind, LessOpenKind::TempFile));
|
||||||
|
assert!(!preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("|batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(
|
||||||
|
preprocessor.kind,
|
||||||
|
LessOpenKind::PipedIgnoreExitCode
|
||||||
|
));
|
||||||
|
assert!(!preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("||batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(preprocessor.kind, LessOpenKind::Piped));
|
||||||
|
assert!(!preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("-batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(preprocessor.kind, LessOpenKind::TempFile));
|
||||||
|
assert!(preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("|-batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(
|
||||||
|
preprocessor.kind,
|
||||||
|
LessOpenKind::PipedIgnoreExitCode
|
||||||
|
));
|
||||||
|
assert!(preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
let preprocessor = LessOpenPreprocessor::mock_new(Some("||-batpipe %s"), None)?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "batpipe %s");
|
||||||
|
assert!(matches!(preprocessor.kind, LessOpenKind::Piped));
|
||||||
|
assert!(preprocessor.preprocess_stdin);
|
||||||
|
|
||||||
|
reset_env_vars();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn replace_part_of_argument() -> Result<()> {
|
||||||
|
let preprocessor =
|
||||||
|
LessOpenPreprocessor::mock_new(Some("|echo File:%s"), Some("echo File:%s Temp:%s"))?;
|
||||||
|
|
||||||
|
assert_eq!(preprocessor.lessopen, "echo File:%s");
|
||||||
|
assert_eq!(preprocessor.lessclose.unwrap(), "echo File:%s Temp:%s");
|
||||||
|
|
||||||
|
reset_env_vars();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -34,6 +34,8 @@ mod diff;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
mod less;
|
mod less;
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
mod lessopen;
|
||||||
pub mod line_range;
|
pub mod line_range;
|
||||||
pub(crate) mod nonprintable_notation;
|
pub(crate) mod nonprintable_notation;
|
||||||
mod output;
|
mod output;
|
||||||
@@ -47,10 +49,12 @@ pub(crate) mod printer;
|
|||||||
pub mod style;
|
pub mod style;
|
||||||
pub(crate) mod syntax_mapping;
|
pub(crate) mod syntax_mapping;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
|
pub mod theme;
|
||||||
mod vscreen;
|
mod vscreen;
|
||||||
pub(crate) mod wrapping;
|
pub(crate) mod wrapping;
|
||||||
|
|
||||||
pub use nonprintable_notation::NonprintableNotation;
|
pub use nonprintable_notation::{BinaryBehavior, NonprintableNotation};
|
||||||
|
pub use preprocessor::StripAnsiMode;
|
||||||
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
pub use pretty_printer::{Input, PrettyPrinter, Syntax};
|
||||||
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
|
||||||
pub use wrapping::WrappingMode;
|
pub use wrapping::WrappingMode;
|
||||||
|
|||||||
+8
-1
@@ -53,7 +53,7 @@ impl LineRange {
|
|||||||
let more_lines = &line_numbers[1][1..]
|
let more_lines = &line_numbers[1][1..]
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| "Invalid character after +")?;
|
.map_err(|_| "Invalid character after +")?;
|
||||||
new_range.lower + more_lines
|
new_range.lower.saturating_add(*more_lines)
|
||||||
} else if first_byte == Some(b'-') {
|
} else if first_byte == Some(b'-') {
|
||||||
// this will prevent values like "-+5" even though "+5" is valid integer
|
// this will prevent values like "-+5" even though "+5" is valid integer
|
||||||
if line_numbers[1][1..].bytes().next() == Some(b'+') {
|
if line_numbers[1][1..].bytes().next() == Some(b'+') {
|
||||||
@@ -128,6 +128,13 @@ fn test_parse_plus() {
|
|||||||
assert_eq!(50, range.upper);
|
assert_eq!(50, range.upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_plus_overflow() {
|
||||||
|
let range = LineRange::from(&format!("{}:+1", usize::MAX)).expect("Shouldn't fail on test!");
|
||||||
|
assert_eq!(usize::MAX, range.lower);
|
||||||
|
assert_eq!(usize::MAX, range.upper);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_plus_fail() {
|
fn test_parse_plus_fail() {
|
||||||
let range = LineRange::from("40:+z");
|
let range = LineRange::from("40:+z");
|
||||||
|
|||||||
@@ -10,3 +10,15 @@ pub enum NonprintableNotation {
|
|||||||
#[default]
|
#[default]
|
||||||
Unicode,
|
Unicode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How to treat binary content
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum BinaryBehavior {
|
||||||
|
/// Do not print any binary content
|
||||||
|
#[default]
|
||||||
|
NoPrinting,
|
||||||
|
|
||||||
|
/// Treat binary content as normal text
|
||||||
|
AsText,
|
||||||
|
}
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ impl OutputType {
|
|||||||
p.args(args);
|
p.args(args);
|
||||||
}
|
}
|
||||||
p.env("LESSCHARSET", "UTF-8");
|
p.env("LESSCHARSET", "UTF-8");
|
||||||
|
|
||||||
|
#[cfg(feature = "lessopen")]
|
||||||
|
// Ensures that 'less' does not preprocess input again if '$LESSOPEN' is set.
|
||||||
|
p.arg("--no-lessopen");
|
||||||
} else {
|
} else {
|
||||||
p.args(args);
|
p.args(args);
|
||||||
};
|
};
|
||||||
|
|||||||
+65
-32
@@ -1,17 +1,18 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
use console::AnsiCodeIterator;
|
use crate::{
|
||||||
|
nonprintable_notation::NonprintableNotation,
|
||||||
use crate::nonprintable_notation::NonprintableNotation;
|
vscreen::{EscapeSequenceOffsets, EscapeSequenceOffsetsIterator},
|
||||||
|
};
|
||||||
|
|
||||||
/// Expand tabs like an ANSI-enabled expand(1).
|
/// Expand tabs like an ANSI-enabled expand(1).
|
||||||
pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
|
pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
|
||||||
let mut buffer = String::with_capacity(line.len() * 2);
|
let mut buffer = String::with_capacity(line.len() * 2);
|
||||||
|
|
||||||
for chunk in AnsiCodeIterator::new(line) {
|
for seq in EscapeSequenceOffsetsIterator::new(line) {
|
||||||
match chunk {
|
match seq {
|
||||||
(text, true) => buffer.push_str(text),
|
EscapeSequenceOffsets::Text { .. } => {
|
||||||
(mut text, false) => {
|
let mut text = &line[seq.index_of_start()..seq.index_past_end()];
|
||||||
while let Some(index) = text.find('\t') {
|
while let Some(index) = text.find('\t') {
|
||||||
// Add previous text.
|
// Add previous text.
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
@@ -31,6 +32,10 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
|
|||||||
*cursor += text.len();
|
*cursor += text.len();
|
||||||
buffer.push_str(text);
|
buffer.push_str(text);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
// Copy the ANSI escape sequence.
|
||||||
|
buffer.push_str(&line[seq.index_of_start()..seq.index_past_end()])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,31 +96,27 @@ pub fn replace_nonprintable(
|
|||||||
});
|
});
|
||||||
line_idx = 0;
|
line_idx = 0;
|
||||||
}
|
}
|
||||||
// carriage return
|
// ASCII control characters
|
||||||
'\x0D' => output.push_str(match nonprintable_notation {
|
'\x00'..='\x1F' => {
|
||||||
NonprintableNotation::Caret => "^M",
|
let c = u32::from(chr);
|
||||||
NonprintableNotation::Unicode => "␍",
|
|
||||||
}),
|
match nonprintable_notation {
|
||||||
// null
|
NonprintableNotation::Caret => {
|
||||||
'\x00' => output.push_str(match nonprintable_notation {
|
let caret_character = char::from_u32(0x40 + c).unwrap();
|
||||||
NonprintableNotation::Caret => "^@",
|
write!(output, "^{caret_character}").ok();
|
||||||
NonprintableNotation::Unicode => "␀",
|
}
|
||||||
}),
|
|
||||||
// bell
|
NonprintableNotation::Unicode => {
|
||||||
'\x07' => output.push_str(match nonprintable_notation {
|
let replacement_symbol = char::from_u32(0x2400 + c).unwrap();
|
||||||
NonprintableNotation::Caret => "^G",
|
output.push(replacement_symbol)
|
||||||
NonprintableNotation::Unicode => "␇",
|
}
|
||||||
}),
|
}
|
||||||
// backspace
|
}
|
||||||
'\x08' => output.push_str(match nonprintable_notation {
|
// delete
|
||||||
NonprintableNotation::Caret => "^H",
|
'\x7F' => match nonprintable_notation {
|
||||||
NonprintableNotation::Unicode => "␈",
|
NonprintableNotation::Caret => output.push_str("^?"),
|
||||||
}),
|
NonprintableNotation::Unicode => output.push('\u{2421}'),
|
||||||
// escape
|
},
|
||||||
'\x1B' => output.push_str(match nonprintable_notation {
|
|
||||||
NonprintableNotation::Caret => "^[",
|
|
||||||
NonprintableNotation::Unicode => "␛",
|
|
||||||
}),
|
|
||||||
// printable ASCII
|
// printable ASCII
|
||||||
c if c.is_ascii_alphanumeric()
|
c if c.is_ascii_alphanumeric()
|
||||||
|| c.is_ascii_punctuation()
|
|| c.is_ascii_punctuation()
|
||||||
@@ -135,6 +136,27 @@ pub fn replace_nonprintable(
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, Default)]
|
||||||
|
pub enum StripAnsiMode {
|
||||||
|
#[default]
|
||||||
|
Never,
|
||||||
|
Always,
|
||||||
|
Auto,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_try_parse_utf8_char() {
|
fn test_try_parse_utf8_char() {
|
||||||
assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
|
assert_eq!(try_parse_utf8_char(&[0x20]), Some((' ', 1)));
|
||||||
@@ -178,3 +200,14 @@ fn test_try_parse_utf8_char() {
|
|||||||
assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
|
assert_eq!(try_parse_utf8_char(&[0xef, 0x20]), None);
|
||||||
assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
|
assert_eq!(try_parse_utf8_char(&[0xf0, 0xf0]), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_strip_ansi() {
|
||||||
|
// The sequence detection is covered by the tests in the vscreen module.
|
||||||
|
assert_eq!(strip_ansi("no ansi"), "no ansi");
|
||||||
|
assert_eq!(strip_ansi("\x1B[33mone"), "one");
|
||||||
|
assert_eq!(
|
||||||
|
strip_ansi("\x1B]1\x07multiple\x1B[J sequences"),
|
||||||
|
"multiple sequences"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
+31
-3
@@ -11,7 +11,7 @@ use crate::{
|
|||||||
input,
|
input,
|
||||||
line_range::{HighlightedLineRanges, LineRange, LineRanges},
|
line_range::{HighlightedLineRanges, LineRange, LineRanges},
|
||||||
style::StyleComponent,
|
style::StyleComponent,
|
||||||
SyntaxMapping, WrappingMode,
|
StripAnsiMode, SyntaxMapping, WrappingMode,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "paging")]
|
#[cfg(feature = "paging")]
|
||||||
@@ -182,6 +182,15 @@ impl<'a> PrettyPrinter<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to remove ANSI escape sequences from the input (default: never)
|
||||||
|
///
|
||||||
|
/// If `Auto` is used, escape sequences will only be removed when the input
|
||||||
|
/// is not plain text.
|
||||||
|
pub fn strip_ansi(&mut self, mode: StripAnsiMode) -> &mut Self {
|
||||||
|
self.config.strip_ansi = mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Text wrapping mode (default: do not wrap)
|
/// Text wrapping mode (default: do not wrap)
|
||||||
pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
|
pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
|
||||||
self.config.wrapping_mode = mode;
|
self.config.wrapping_mode = mode;
|
||||||
@@ -230,7 +239,15 @@ impl<'a> PrettyPrinter<'a> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specify the highlighting theme
|
/// Specify the maximum number of consecutive empty lines to print.
|
||||||
|
pub fn squeeze_empty_lines(&mut self, maximum: Option<usize>) -> &mut Self {
|
||||||
|
self.config.squeeze_lines = maximum;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specify the highlighting theme.
|
||||||
|
/// You can use [`crate::theme::theme`] to pick a theme based on user preferences
|
||||||
|
/// and the terminal's background color.
|
||||||
pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
|
pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
|
||||||
self.config.theme = theme.as_ref().to_owned();
|
self.config.theme = theme.as_ref().to_owned();
|
||||||
self
|
self
|
||||||
@@ -264,6 +281,11 @@ impl<'a> PrettyPrinter<'a> {
|
|||||||
/// If you want to call 'print' multiple times, you have to call the appropriate
|
/// If you want to call 'print' multiple times, you have to call the appropriate
|
||||||
/// input_* methods again.
|
/// input_* methods again.
|
||||||
pub fn print(&mut self) -> Result<bool> {
|
pub fn print(&mut self) -> Result<bool> {
|
||||||
|
self.print_with_writer(None::<&mut dyn std::fmt::Write>)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pretty-print all specified inputs to a specified writer.
|
||||||
|
pub fn print_with_writer<W: std::fmt::Write>(&mut self, writer: Option<W>) -> Result<bool> {
|
||||||
let highlight_lines = std::mem::take(&mut self.highlighted_lines);
|
let highlight_lines = std::mem::take(&mut self.highlighted_lines);
|
||||||
self.config.highlighted_lines = HighlightedLineRanges(LineRanges::from(highlight_lines));
|
self.config.highlighted_lines = HighlightedLineRanges(LineRanges::from(highlight_lines));
|
||||||
self.config.term_width = self
|
self.config.term_width = self
|
||||||
@@ -300,7 +322,13 @@ impl<'a> PrettyPrinter<'a> {
|
|||||||
|
|
||||||
// Run the controller
|
// Run the controller
|
||||||
let controller = Controller::new(&self.config, &self.assets);
|
let controller = Controller::new(&self.config, &self.assets);
|
||||||
controller.run(inputs.into_iter().map(|i| i.into()).collect())
|
|
||||||
|
// If writer is provided, pass it to the controller, otherwise pass None
|
||||||
|
if let Some(mut w) = writer {
|
||||||
|
controller.run(inputs.into_iter().map(|i| i.into()).collect(), Some(&mut w))
|
||||||
|
} else {
|
||||||
|
controller.run(inputs.into_iter().map(|i| i.into()).collect(), None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+296
-122
@@ -1,4 +1,5 @@
|
|||||||
use std::io::Write;
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
use std::vec::Vec;
|
use std::vec::Vec;
|
||||||
|
|
||||||
use nu_ansi_term::Color::{Fixed, Green, Red, Yellow};
|
use nu_ansi_term::Color::{Fixed, Green, Red, Yellow};
|
||||||
@@ -6,17 +7,15 @@ use nu_ansi_term::Style;
|
|||||||
|
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
|
|
||||||
use console::AnsiCodeIterator;
|
|
||||||
|
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
use syntect::highlighting::Color;
|
use syntect::highlighting::Color;
|
||||||
|
use syntect::highlighting::FontStyle;
|
||||||
use syntect::highlighting::Theme;
|
use syntect::highlighting::Theme;
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
use content_inspector::ContentType;
|
use content_inspector::ContentType;
|
||||||
|
|
||||||
use encoding::all::{UTF_16BE, UTF_16LE};
|
use encoding_rs::{UTF_16BE, UTF_16LE};
|
||||||
use encoding::{DecoderTrap, Encoding};
|
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
@@ -30,27 +29,74 @@ use crate::diff::LineChanges;
|
|||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use crate::input::OpenedInput;
|
use crate::input::OpenedInput;
|
||||||
use crate::line_range::RangeCheckResult;
|
use crate::line_range::RangeCheckResult;
|
||||||
|
use crate::preprocessor::strip_ansi;
|
||||||
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
use crate::preprocessor::{expand_tabs, replace_nonprintable};
|
||||||
use crate::style::StyleComponent;
|
use crate::style::StyleComponent;
|
||||||
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
use crate::terminal::{as_terminal_escaped, to_ansi_color};
|
||||||
use crate::vscreen::AnsiStyle;
|
use crate::vscreen::{AnsiStyle, EscapeSequence, EscapeSequenceIterator};
|
||||||
use crate::wrapping::WrappingMode;
|
use crate::wrapping::WrappingMode;
|
||||||
|
use crate::BinaryBehavior;
|
||||||
|
use crate::StripAnsiMode;
|
||||||
|
|
||||||
|
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
|
||||||
|
raw_sequence: "\x1B[4m",
|
||||||
|
parameters: "4",
|
||||||
|
intermediates: "",
|
||||||
|
final_byte: "m",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ANSI_UNDERLINE_DISABLE: EscapeSequence = EscapeSequence::CSI {
|
||||||
|
raw_sequence: "\x1B[24m",
|
||||||
|
parameters: "24",
|
||||||
|
intermediates: "",
|
||||||
|
final_byte: "m",
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMPTY_SYNTECT_STYLE: syntect::highlighting::Style = syntect::highlighting::Style {
|
||||||
|
foreground: Color {
|
||||||
|
r: 127,
|
||||||
|
g: 127,
|
||||||
|
b: 127,
|
||||||
|
a: 255,
|
||||||
|
},
|
||||||
|
background: Color {
|
||||||
|
r: 127,
|
||||||
|
g: 127,
|
||||||
|
b: 127,
|
||||||
|
a: 255,
|
||||||
|
},
|
||||||
|
font_style: FontStyle::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum OutputHandle<'a> {
|
||||||
|
IoWrite(&'a mut dyn io::Write),
|
||||||
|
FmtWrite(&'a mut dyn fmt::Write),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OutputHandle<'a> {
|
||||||
|
fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::IoWrite(handle) => handle.write_fmt(args).map_err(Into::into),
|
||||||
|
Self::FmtWrite(handle) => handle.write_fmt(args).map_err(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) trait Printer {
|
pub(crate) trait Printer {
|
||||||
fn print_header(
|
fn print_header(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &mut dyn Write,
|
handle: &mut OutputHandle,
|
||||||
input: &OpenedInput,
|
input: &OpenedInput,
|
||||||
add_header_padding: bool,
|
add_header_padding: bool,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
fn print_footer(&mut self, handle: &mut dyn Write, input: &OpenedInput) -> Result<()>;
|
fn print_footer(&mut self, handle: &mut OutputHandle, input: &OpenedInput) -> Result<()>;
|
||||||
|
|
||||||
fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
|
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()>;
|
||||||
|
|
||||||
fn print_line(
|
fn print_line(
|
||||||
&mut self,
|
&mut self,
|
||||||
out_of_range: bool,
|
out_of_range: bool,
|
||||||
handle: &mut dyn Write,
|
handle: &mut OutputHandle,
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
line_buffer: &[u8],
|
line_buffer: &[u8],
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
@@ -58,39 +104,58 @@ pub(crate) trait Printer {
|
|||||||
|
|
||||||
pub struct SimplePrinter<'a> {
|
pub struct SimplePrinter<'a> {
|
||||||
config: &'a Config<'a>,
|
config: &'a Config<'a>,
|
||||||
|
consecutive_empty_lines: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SimplePrinter<'a> {
|
impl<'a> SimplePrinter<'a> {
|
||||||
pub fn new(config: &'a Config) -> Self {
|
pub fn new(config: &'a Config) -> Self {
|
||||||
SimplePrinter { config }
|
SimplePrinter {
|
||||||
|
config,
|
||||||
|
consecutive_empty_lines: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Printer for SimplePrinter<'a> {
|
impl<'a> Printer for SimplePrinter<'a> {
|
||||||
fn print_header(
|
fn print_header(
|
||||||
&mut self,
|
&mut self,
|
||||||
_handle: &mut dyn Write,
|
_handle: &mut OutputHandle,
|
||||||
_input: &OpenedInput,
|
_input: &OpenedInput,
|
||||||
_add_header_padding: bool,
|
_add_header_padding: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_footer(&mut self, _handle: &mut dyn Write, _input: &OpenedInput) -> Result<()> {
|
fn print_footer(&mut self, _handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_snip(&mut self, _handle: &mut dyn Write) -> Result<()> {
|
fn print_snip(&mut self, _handle: &mut OutputHandle) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_line(
|
fn print_line(
|
||||||
&mut self,
|
&mut self,
|
||||||
out_of_range: bool,
|
out_of_range: bool,
|
||||||
handle: &mut dyn Write,
|
handle: &mut OutputHandle,
|
||||||
_line_number: usize,
|
_line_number: usize,
|
||||||
line_buffer: &[u8],
|
line_buffer: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// Skip squeezed lines.
|
||||||
|
if let Some(squeeze_limit) = self.config.squeeze_lines {
|
||||||
|
if String::from_utf8_lossy(line_buffer)
|
||||||
|
.trim_end_matches(|c| c == '\r' || c == '\n')
|
||||||
|
.is_empty()
|
||||||
|
{
|
||||||
|
self.consecutive_empty_lines += 1;
|
||||||
|
if self.consecutive_empty_lines > squeeze_limit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.consecutive_empty_lines = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !out_of_range {
|
if !out_of_range {
|
||||||
if self.config.show_nonprintable {
|
if self.config.show_nonprintable {
|
||||||
let line = replace_nonprintable(
|
let line = replace_nonprintable(
|
||||||
@@ -98,9 +163,21 @@ impl<'a> Printer for SimplePrinter<'a> {
|
|||||||
self.config.tab_width,
|
self.config.tab_width,
|
||||||
self.config.nonprintable_notation,
|
self.config.nonprintable_notation,
|
||||||
);
|
);
|
||||||
write!(handle, "{}", line)?;
|
write!(handle, "{line}")?;
|
||||||
} else {
|
} else {
|
||||||
handle.write_all(line_buffer)?
|
match handle {
|
||||||
|
OutputHandle::IoWrite(handle) => handle.write_all(line_buffer)?,
|
||||||
|
OutputHandle::FmtWrite(handle) => {
|
||||||
|
write!(
|
||||||
|
handle,
|
||||||
|
"{}",
|
||||||
|
std::str::from_utf8(line_buffer).map_err(|_| Error::Msg(
|
||||||
|
"encountered invalid utf8 while printing to non-io buffer"
|
||||||
|
.to_string()
|
||||||
|
))?
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -132,6 +209,8 @@ pub(crate) struct InteractivePrinter<'a> {
|
|||||||
pub line_changes: &'a Option<LineChanges>,
|
pub line_changes: &'a Option<LineChanges>,
|
||||||
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
highlighter_from_set: Option<HighlighterFromSet<'a>>,
|
||||||
background_color_highlight: Option<Color>,
|
background_color_highlight: Option<Color>,
|
||||||
|
consecutive_empty_lines: usize,
|
||||||
|
strip_ansi: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InteractivePrinter<'a> {
|
impl<'a> InteractivePrinter<'a> {
|
||||||
@@ -184,24 +263,48 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
panel_width = 0;
|
panel_width = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let highlighter_from_set = if input
|
// Get the highlighter for the output.
|
||||||
|
let is_printing_binary = input
|
||||||
.reader
|
.reader
|
||||||
.content_type
|
.content_type
|
||||||
.map_or(false, |c| c.is_binary() && !config.show_nonprintable)
|
.map_or(false, |c| c.is_binary() && !config.show_nonprintable);
|
||||||
{
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
// Determine the type of syntax for highlighting
|
|
||||||
let syntax_in_set =
|
|
||||||
match assets.get_syntax(config.language, input, &config.syntax_mapping) {
|
|
||||||
Ok(syntax_in_set) => syntax_in_set,
|
|
||||||
Err(Error::UndetectedSyntax(_)) => assets
|
|
||||||
.find_syntax_by_name("Plain Text")?
|
|
||||||
.expect("A plain text syntax is available"),
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(HighlighterFromSet::new(syntax_in_set, theme))
|
let needs_to_match_syntax = (!is_printing_binary
|
||||||
|
|| matches!(config.binary, BinaryBehavior::AsText))
|
||||||
|
&& (config.colored_output || config.strip_ansi == StripAnsiMode::Auto);
|
||||||
|
|
||||||
|
let (is_plain_text, 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) {
|
||||||
|
Ok(syntax_in_set) => (
|
||||||
|
syntax_in_set.syntax.name == PLAIN_TEXT_SYNTAX,
|
||||||
|
Some(HighlighterFromSet::new(syntax_in_set, theme)),
|
||||||
|
),
|
||||||
|
|
||||||
|
Err(Error::UndetectedSyntax(_)) => (
|
||||||
|
true,
|
||||||
|
Some(
|
||||||
|
assets
|
||||||
|
.find_syntax_by_name(PLAIN_TEXT_SYNTAX)?
|
||||||
|
.map(|s| HighlighterFromSet::new(s, theme))
|
||||||
|
.expect("A plain text syntax is available"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(false, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine when to strip ANSI sequences
|
||||||
|
let strip_ansi = match config.strip_ansi {
|
||||||
|
_ if config.show_nonprintable => false,
|
||||||
|
StripAnsiMode::Always => true,
|
||||||
|
StripAnsiMode::Auto if is_plain_text => false, // Plain text may already contain escape sequences.
|
||||||
|
StripAnsiMode::Auto => true,
|
||||||
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(InteractivePrinter {
|
Ok(InteractivePrinter {
|
||||||
@@ -215,10 +318,16 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
line_changes,
|
line_changes,
|
||||||
highlighter_from_set,
|
highlighter_from_set,
|
||||||
background_color_highlight,
|
background_color_highlight,
|
||||||
|
consecutive_empty_lines: 0,
|
||||||
|
strip_ansi,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_horizontal_line_term(&mut self, handle: &mut dyn Write, style: Style) -> Result<()> {
|
fn print_horizontal_line_term(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut OutputHandle,
|
||||||
|
style: Style,
|
||||||
|
) -> Result<()> {
|
||||||
writeln!(
|
writeln!(
|
||||||
handle,
|
handle,
|
||||||
"{}",
|
"{}",
|
||||||
@@ -227,7 +336,7 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_horizontal_line(&mut self, handle: &mut dyn Write, grid_char: char) -> Result<()> {
|
fn print_horizontal_line(&mut self, handle: &mut OutputHandle, grid_char: char) -> Result<()> {
|
||||||
if self.panel_width == 0 {
|
if self.panel_width == 0 {
|
||||||
self.print_horizontal_line_term(handle, self.colors.grid)?;
|
self.print_horizontal_line_term(handle, self.colors.grid)?;
|
||||||
} else {
|
} else {
|
||||||
@@ -251,13 +360,21 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
" ".repeat(self.panel_width - 1 - text_truncated.len())
|
" ".repeat(self.panel_width - 1 - text_truncated.len())
|
||||||
);
|
);
|
||||||
if self.config.style_components.grid() {
|
if self.config.style_components.grid() {
|
||||||
format!("{} │ ", text_filled)
|
format!("{text_filled} │ ")
|
||||||
} else {
|
} else {
|
||||||
text_filled
|
text_filled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_header_component_indent(&mut self, handle: &mut dyn Write) -> std::io::Result<()> {
|
fn get_header_component_indent_length(&self) -> usize {
|
||||||
|
if self.config.style_components.grid() && self.panel_width > 0 {
|
||||||
|
self.panel_width + 2
|
||||||
|
} else {
|
||||||
|
self.panel_width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_header_component_indent(&mut self, handle: &mut OutputHandle) -> Result<()> {
|
||||||
if self.config.style_components.grid() {
|
if self.config.style_components.grid() {
|
||||||
write!(
|
write!(
|
||||||
handle,
|
handle,
|
||||||
@@ -272,6 +389,55 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_header_component_with_indent(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut OutputHandle,
|
||||||
|
content: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.print_header_component_indent(handle)?;
|
||||||
|
writeln!(handle, "{content}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_header_multiline_component(
|
||||||
|
&mut self,
|
||||||
|
handle: &mut OutputHandle,
|
||||||
|
content: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut content = content;
|
||||||
|
let content_width = self.config.term_width - self.get_header_component_indent_length();
|
||||||
|
while content.len() > content_width {
|
||||||
|
let (content_line, remaining) = content.split_at(content_width);
|
||||||
|
self.print_header_component_with_indent(handle, content_line)?;
|
||||||
|
content = remaining;
|
||||||
|
}
|
||||||
|
self.print_header_component_with_indent(handle, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_regions_for_line<'b>(
|
||||||
|
&mut self,
|
||||||
|
line: &'b str,
|
||||||
|
) -> Result<Vec<(syntect::highlighting::Style, &'b str)>> {
|
||||||
|
let highlighter_from_set = match self.highlighter_from_set {
|
||||||
|
Some(ref mut highlighter_from_set) => highlighter_from_set,
|
||||||
|
_ => return Ok(vec![(EMPTY_SYNTECT_STYLE, line)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
// skip syntax highlighting on long lines
|
||||||
|
let too_long = line.len() > 1024 * 16;
|
||||||
|
|
||||||
|
let for_highlighting: &str = if too_long { "\n" } else { line };
|
||||||
|
|
||||||
|
let mut highlighted_line = highlighter_from_set
|
||||||
|
.highlighter
|
||||||
|
.highlight_line(for_highlighting, highlighter_from_set.syntax_set)?;
|
||||||
|
|
||||||
|
if too_long {
|
||||||
|
highlighted_line[0].1 = &line;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(highlighted_line)
|
||||||
|
}
|
||||||
|
|
||||||
fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
|
fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
|
||||||
if self.config.tab_width > 0 {
|
if self.config.tab_width > 0 {
|
||||||
return expand_tabs(text, self.config.tab_width, cursor);
|
return expand_tabs(text, self.config.tab_width, cursor);
|
||||||
@@ -285,7 +451,7 @@ impl<'a> InteractivePrinter<'a> {
|
|||||||
impl<'a> Printer for InteractivePrinter<'a> {
|
impl<'a> Printer for InteractivePrinter<'a> {
|
||||||
fn print_header(
|
fn print_header(
|
||||||
&mut self,
|
&mut self,
|
||||||
handle: &mut dyn Write,
|
handle: &mut OutputHandle,
|
||||||
input: &OpenedInput,
|
input: &OpenedInput,
|
||||||
add_header_padding: bool,
|
add_header_padding: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -294,7 +460,10 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !self.config.style_components.header() {
|
if !self.config.style_components.header() {
|
||||||
if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
|
if Some(ContentType::BINARY) == self.content_type
|
||||||
|
&& !self.config.show_nonprintable
|
||||||
|
&& !matches!(self.config.binary, BinaryBehavior::AsText)
|
||||||
|
{
|
||||||
writeln!(
|
writeln!(
|
||||||
handle,
|
handle,
|
||||||
"{}: Binary content from {} will not be printed to the terminal \
|
"{}: Binary content from {} will not be printed to the terminal \
|
||||||
@@ -347,34 +516,38 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header_components.iter().try_for_each(|component| {
|
header_components
|
||||||
self.print_header_component_indent(handle)?;
|
.iter()
|
||||||
|
.try_for_each(|component| match component {
|
||||||
match component {
|
StyleComponent::HeaderFilename => {
|
||||||
StyleComponent::HeaderFilename => writeln!(
|
let header_filename = format!(
|
||||||
handle,
|
"{}{}{}",
|
||||||
"{}{}{}",
|
description
|
||||||
description
|
.kind()
|
||||||
.kind()
|
.map(|kind| format!("{kind}: "))
|
||||||
.map(|kind| format!("{}: ", kind))
|
.unwrap_or_else(|| "".into()),
|
||||||
.unwrap_or_else(|| "".into()),
|
self.colors.header_value.paint(description.title()),
|
||||||
self.colors.header_value.paint(description.title()),
|
mode
|
||||||
mode
|
);
|
||||||
),
|
self.print_header_multiline_component(handle, &header_filename)
|
||||||
|
}
|
||||||
StyleComponent::HeaderFilesize => {
|
StyleComponent::HeaderFilesize => {
|
||||||
let bsize = metadata
|
let bsize = metadata
|
||||||
.size
|
.size
|
||||||
.map(|s| format!("{}", ByteSize(s)))
|
.map(|s| format!("{}", ByteSize(s)))
|
||||||
.unwrap_or_else(|| "-".into());
|
.unwrap_or_else(|| "-".into());
|
||||||
writeln!(handle, "Size: {}", self.colors.header_value.paint(bsize))
|
let header_filesize =
|
||||||
|
format!("Size: {}", self.colors.header_value.paint(bsize));
|
||||||
|
self.print_header_multiline_component(handle, &header_filesize)
|
||||||
}
|
}
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
})?;
|
||||||
})?;
|
|
||||||
|
|
||||||
if self.config.style_components.grid() {
|
if self.config.style_components.grid() {
|
||||||
if self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable {
|
if self.content_type.map_or(false, |c| c.is_text())
|
||||||
|
|| self.config.show_nonprintable
|
||||||
|
|| matches!(self.config.binary, BinaryBehavior::AsText)
|
||||||
|
{
|
||||||
self.print_horizontal_line(handle, '┼')?;
|
self.print_horizontal_line(handle, '┼')?;
|
||||||
} else {
|
} else {
|
||||||
self.print_horizontal_line(handle, '┴')?;
|
self.print_horizontal_line(handle, '┴')?;
|
||||||
@@ -384,9 +557,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_footer(&mut self, handle: &mut dyn Write, _input: &OpenedInput) -> Result<()> {
|
fn print_footer(&mut self, handle: &mut OutputHandle, _input: &OpenedInput) -> Result<()> {
|
||||||
if self.config.style_components.grid()
|
if self.config.style_components.grid()
|
||||||
&& (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable)
|
&& (self.content_type.map_or(false, |c| c.is_text())
|
||||||
|
|| self.config.show_nonprintable
|
||||||
|
|| matches!(self.config.binary, BinaryBehavior::AsText))
|
||||||
{
|
{
|
||||||
self.print_horizontal_line(handle, '┴')
|
self.print_horizontal_line(handle, '┴')
|
||||||
} else {
|
} else {
|
||||||
@@ -394,7 +569,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()> {
|
fn print_snip(&mut self, handle: &mut OutputHandle) -> Result<()> {
|
||||||
let panel = self.create_fake_panel(" ...");
|
let panel = self.create_fake_panel(" ...");
|
||||||
let panel_count = panel.chars().count();
|
let panel_count = panel.chars().count();
|
||||||
|
|
||||||
@@ -412,7 +587,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
"{}",
|
"{}",
|
||||||
self.colors
|
self.colors
|
||||||
.grid
|
.grid
|
||||||
.paint(format!("{}{}{}{}", panel, snip_left, title, snip_right))
|
.paint(format!("{panel}{snip_left}{title}{snip_right}"))
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -421,7 +596,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
fn print_line(
|
fn print_line(
|
||||||
&mut self,
|
&mut self,
|
||||||
out_of_range: bool,
|
out_of_range: bool,
|
||||||
handle: &mut dyn Write,
|
handle: &mut OutputHandle,
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
line_buffer: &[u8],
|
line_buffer: &[u8],
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -431,58 +606,54 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
self.config.tab_width,
|
self.config.tab_width,
|
||||||
self.config.nonprintable_notation,
|
self.config.nonprintable_notation,
|
||||||
)
|
)
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
let line = match self.content_type {
|
let mut line = match self.content_type {
|
||||||
Some(ContentType::BINARY) | None => {
|
Some(ContentType::BINARY) | None
|
||||||
|
if !matches!(self.config.binary, BinaryBehavior::AsText) =>
|
||||||
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(ContentType::UTF_16LE) => UTF_16LE
|
Some(ContentType::UTF_16LE) => UTF_16LE.decode_with_bom_removal(line_buffer).0,
|
||||||
.decode(line_buffer, DecoderTrap::Replace)
|
Some(ContentType::UTF_16BE) => UTF_16BE.decode_with_bom_removal(line_buffer).0,
|
||||||
.map_err(|_| "Invalid UTF-16LE")?,
|
|
||||||
Some(ContentType::UTF_16BE) => UTF_16BE
|
|
||||||
.decode(line_buffer, DecoderTrap::Replace)
|
|
||||||
.map_err(|_| "Invalid UTF-16BE")?,
|
|
||||||
_ => String::from_utf8_lossy(line_buffer).to_string(),
|
|
||||||
};
|
|
||||||
// Remove byte order mark from the first line if it exists
|
|
||||||
if line_number == 1 {
|
|
||||||
match line.strip_prefix('\u{feff}') {
|
|
||||||
Some(stripped) => stripped.to_string(),
|
|
||||||
None => line,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
line
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let regions = {
|
|
||||||
let highlighter_from_set = match self.highlighter_from_set {
|
|
||||||
Some(ref mut highlighter_from_set) => highlighter_from_set,
|
|
||||||
_ => {
|
_ => {
|
||||||
return Ok(());
|
let line = String::from_utf8_lossy(line_buffer);
|
||||||
|
if line_number == 1 {
|
||||||
|
match line.strip_prefix('\u{feff}') {
|
||||||
|
Some(stripped) => stripped.to_string().into(),
|
||||||
|
None => line,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// skip syntax highlighting on long lines
|
// If ANSI escape sequences are supposed to be stripped, do it before syntax highlighting.
|
||||||
let too_long = line.len() > 1024 * 16;
|
if self.strip_ansi {
|
||||||
|
line = strip_ansi(&line).into()
|
||||||
let for_highlighting: &str = if too_long { "\n" } else { &line };
|
|
||||||
|
|
||||||
let mut highlighted_line = highlighter_from_set
|
|
||||||
.highlighter
|
|
||||||
.highlight_line(for_highlighting, highlighter_from_set.syntax_set)?;
|
|
||||||
|
|
||||||
if too_long {
|
|
||||||
highlighted_line[0].1 = &line;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
highlighted_line
|
line
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let regions = self.highlight_regions_for_line(&line)?;
|
||||||
if out_of_range {
|
if out_of_range {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip squeezed lines.
|
||||||
|
if let Some(squeeze_limit) = self.config.squeeze_lines {
|
||||||
|
if line.trim_end_matches(|c| c == '\r' || c == '\n').is_empty() {
|
||||||
|
self.consecutive_empty_lines += 1;
|
||||||
|
if self.consecutive_empty_lines > squeeze_limit {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.consecutive_empty_lines = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut cursor: usize = 0;
|
let mut cursor: usize = 0;
|
||||||
let mut cursor_max: usize = self.config.term_width;
|
let mut cursor_max: usize = self.config.term_width;
|
||||||
let mut cursor_total: usize = 0;
|
let mut cursor_total: usize = 0;
|
||||||
@@ -493,7 +664,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange;
|
self.config.highlighted_lines.0.check(line_number) == RangeCheckResult::InRange;
|
||||||
|
|
||||||
if highlight_this_line && self.config.theme == "ansi" {
|
if highlight_this_line && self.config.theme == "ansi" {
|
||||||
self.ansi_style.update("^[4m");
|
self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
let background_color = self
|
let background_color = self
|
||||||
@@ -520,23 +691,17 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
let italics = self.config.use_italic_text;
|
let italics = self.config.use_italic_text;
|
||||||
|
|
||||||
for &(style, region) in ®ions {
|
for &(style, region) in ®ions {
|
||||||
let ansi_iterator = AnsiCodeIterator::new(region);
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
||||||
for chunk in ansi_iterator {
|
for chunk in ansi_iterator {
|
||||||
match chunk {
|
match chunk {
|
||||||
// ANSI escape passthrough.
|
|
||||||
(ansi, true) => {
|
|
||||||
self.ansi_style.update(ansi);
|
|
||||||
write!(handle, "{}", ansi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text.
|
// Regular text.
|
||||||
(text, false) => {
|
EscapeSequence::Text(text) => {
|
||||||
let text = &*self.preprocess(text, &mut cursor_total);
|
let text = self.preprocess(text, &mut cursor_total);
|
||||||
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
|
let text_trimmed = text.trim_end_matches(|c| c == '\r' || c == '\n');
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
handle,
|
handle,
|
||||||
"{}",
|
"{}{}",
|
||||||
as_terminal_escaped(
|
as_terminal_escaped(
|
||||||
style,
|
style,
|
||||||
&format!("{}{}", self.ansi_style, text_trimmed),
|
&format!("{}{}", self.ansi_style, text_trimmed),
|
||||||
@@ -544,9 +709,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
colored_output,
|
colored_output,
|
||||||
italics,
|
italics,
|
||||||
background_color
|
background_color
|
||||||
)
|
),
|
||||||
|
self.ansi_style.to_reset_sequence(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Pad the rest of the line.
|
||||||
if text.len() != text_trimmed.len() {
|
if text.len() != text_trimmed.len() {
|
||||||
if let Some(background_color) = background_color {
|
if let Some(background_color) = background_color {
|
||||||
let ansi_style = Style {
|
let ansi_style = Style {
|
||||||
@@ -564,6 +731,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
write!(handle, "{}", &text[text_trimmed.len()..])?;
|
write!(handle, "{}", &text[text_trimmed.len()..])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANSI escape passthrough.
|
||||||
|
_ => {
|
||||||
|
write!(handle, "{}", chunk.raw())?;
|
||||||
|
self.ansi_style.update(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,17 +746,11 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for &(style, region) in ®ions {
|
for &(style, region) in ®ions {
|
||||||
let ansi_iterator = AnsiCodeIterator::new(region);
|
let ansi_iterator = EscapeSequenceIterator::new(region);
|
||||||
for chunk in ansi_iterator {
|
for chunk in ansi_iterator {
|
||||||
match chunk {
|
match chunk {
|
||||||
// ANSI escape passthrough.
|
|
||||||
(ansi, true) => {
|
|
||||||
self.ansi_style.update(ansi);
|
|
||||||
write!(handle, "{}", ansi)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular text.
|
// Regular text.
|
||||||
(text, false) => {
|
EscapeSequence::Text(text) => {
|
||||||
let text = self.preprocess(
|
let text = self.preprocess(
|
||||||
text.trim_end_matches(|c| c == '\r' || c == '\n'),
|
text.trim_end_matches(|c| c == '\r' || c == '\n'),
|
||||||
&mut cursor_total,
|
&mut cursor_total,
|
||||||
@@ -626,7 +793,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
// It wraps.
|
// It wraps.
|
||||||
write!(
|
write!(
|
||||||
handle,
|
handle,
|
||||||
"{}\n{}",
|
"{}{}\n{}",
|
||||||
as_terminal_escaped(
|
as_terminal_escaped(
|
||||||
style,
|
style,
|
||||||
&format!("{}{}", self.ansi_style, line_buf),
|
&format!("{}{}", self.ansi_style, line_buf),
|
||||||
@@ -635,6 +802,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
self.config.use_italic_text,
|
self.config.use_italic_text,
|
||||||
background_color
|
background_color
|
||||||
),
|
),
|
||||||
|
self.ansi_style.to_reset_sequence(),
|
||||||
panel_wrap.clone().unwrap()
|
panel_wrap.clone().unwrap()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -663,6 +831,12 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
)
|
)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ANSI escape passthrough.
|
||||||
|
_ => {
|
||||||
|
write!(handle, "{}", chunk.raw())?;
|
||||||
|
self.ansi_style.update(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -683,8 +857,8 @@ impl<'a> Printer for InteractivePrinter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if highlight_this_line && self.config.theme == "ansi" {
|
if highlight_this_line && self.config.theme == "ansi" {
|
||||||
self.ansi_style.update("^[24m");
|
write!(handle, "{}", ANSI_UNDERLINE_DISABLE.raw())?;
|
||||||
write!(handle, "\x1B[24m")?;
|
self.ansi_style.update(ANSI_UNDERLINE_DISABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
+225
-1
@@ -80,7 +80,7 @@ impl FromStr for StyleComponent {
|
|||||||
"full" => Ok(StyleComponent::Full),
|
"full" => Ok(StyleComponent::Full),
|
||||||
"default" => Ok(StyleComponent::Default),
|
"default" => Ok(StyleComponent::Default),
|
||||||
"plain" => Ok(StyleComponent::Plain),
|
"plain" => Ok(StyleComponent::Plain),
|
||||||
_ => Err(format!("Unknown style '{}'", s).into()),
|
_ => Err(format!("Unknown style '{s}'").into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,3 +138,227 @@ impl StyleComponents {
|
|||||||
self.0.clear();
|
self.0.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum ComponentAction {
|
||||||
|
Override,
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentAction {
|
||||||
|
fn extract_from_str(string: &str) -> (ComponentAction, &str) {
|
||||||
|
match string.chars().next() {
|
||||||
|
Some('-') => (ComponentAction::Remove, string.strip_prefix('-').unwrap()),
|
||||||
|
Some('+') => (ComponentAction::Add, string.strip_prefix('+').unwrap()),
|
||||||
|
_ => (ComponentAction::Override, string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of [StyleComponent] that can be parsed from a string.
|
||||||
|
pub struct StyleComponentList(Vec<(ComponentAction, StyleComponent)>);
|
||||||
|
|
||||||
|
impl StyleComponentList {
|
||||||
|
fn expand_into(&self, components: &mut HashSet<StyleComponent>, interactive_terminal: bool) {
|
||||||
|
for (action, component) in self.0.iter() {
|
||||||
|
let subcomponents = component.components(interactive_terminal);
|
||||||
|
|
||||||
|
use ComponentAction::*;
|
||||||
|
match action {
|
||||||
|
Override | Add => components.extend(subcomponents),
|
||||||
|
Remove => components.retain(|c| !subcomponents.contains(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if any component in the list was not prefixed with `+` or `-`.
|
||||||
|
fn contains_override(&self) -> bool {
|
||||||
|
self.0.iter().any(|(a, _)| *a == ComponentAction::Override)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Combines multiple [StyleComponentList]s into a single [StyleComponents] set.
|
||||||
|
///
|
||||||
|
/// ## Precedence
|
||||||
|
/// The most recent list will take precedence and override all previous lists
|
||||||
|
/// unless it only contains components prefixed with `-` or `+`. When this
|
||||||
|
/// happens, the list's components will be merged into the previous list.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```text
|
||||||
|
/// [numbers,grid] + [header,changes] -> [header,changes]
|
||||||
|
/// [numbers,grid] + [+header,-grid] -> [numbers,header]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Parameters
|
||||||
|
/// - `with_default`: If true, the styles lists will build upon the StyleComponent::Auto style.
|
||||||
|
pub fn to_components(
|
||||||
|
lists: impl IntoIterator<Item = StyleComponentList>,
|
||||||
|
interactive_terminal: bool,
|
||||||
|
with_default: bool,
|
||||||
|
) -> StyleComponents {
|
||||||
|
let mut components: HashSet<StyleComponent> = HashSet::new();
|
||||||
|
if with_default {
|
||||||
|
components.extend(StyleComponent::Auto.components(interactive_terminal))
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleComponents(lists.into_iter().fold(components, |mut components, list| {
|
||||||
|
if list.contains_override() {
|
||||||
|
components.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.expand_into(&mut components, interactive_terminal);
|
||||||
|
components
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StyleComponentList {
|
||||||
|
fn default() -> Self {
|
||||||
|
StyleComponentList(vec![(ComponentAction::Override, StyleComponent::Default)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for StyleComponentList {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
Ok(StyleComponentList(
|
||||||
|
s.split(",")
|
||||||
|
.map(|s| ComponentAction::extract_from_str(s)) // If the component starts with "-", it's meant to be removed
|
||||||
|
.map(|(a, s)| Ok((a, StyleComponent::from_str(s)?)))
|
||||||
|
.collect::<Result<Vec<(ComponentAction, StyleComponent)>>>()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::ComponentAction::*;
|
||||||
|
use super::StyleComponent;
|
||||||
|
use super::StyleComponent::*;
|
||||||
|
use super::StyleComponentList;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_parse() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::from_str("grid,+numbers,snip,-snip,header")
|
||||||
|
.expect("no error")
|
||||||
|
.0,
|
||||||
|
vec![
|
||||||
|
(Override, Grid),
|
||||||
|
(Add, LineNumbers),
|
||||||
|
(Override, Snip),
|
||||||
|
(Remove, Snip),
|
||||||
|
(Override, Header),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(StyleComponentList::from_str("not-a-component").is_err());
|
||||||
|
assert!(StyleComponentList::from_str("grid,not-a-component").is_err());
|
||||||
|
assert!(StyleComponentList::from_str("numbers,-not-a-component").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_to_components() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![StyleComponentList::from_str("grid,numbers").expect("no error")],
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
HashSet::from([Grid, LineNumbers])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_to_components_removes_negated() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![StyleComponentList::from_str("grid,numbers,-grid").expect("no error")],
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
HashSet::from([LineNumbers])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_to_components_expands_subcomponents() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![StyleComponentList::from_str("full").expect("no error")],
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
HashSet::from_iter(Full.components(true).to_owned())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_expand_negates_subcomponents() {
|
||||||
|
assert!(!StyleComponentList::to_components(
|
||||||
|
vec![StyleComponentList::from_str("full,-numbers").expect("no error")],
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.numbers());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_to_components_precedence_overrides_previous_lists() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![
|
||||||
|
StyleComponentList::from_str("grid").expect("no error"),
|
||||||
|
StyleComponentList::from_str("numbers").expect("no error"),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
HashSet::from([LineNumbers])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_to_components_precedence_merges_previous_lists() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![
|
||||||
|
StyleComponentList::from_str("grid,header").expect("no error"),
|
||||||
|
StyleComponentList::from_str("-grid").expect("no error"),
|
||||||
|
StyleComponentList::from_str("+numbers").expect("no error"),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
HashSet::from([HeaderFilename, LineNumbers])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn style_component_list_default_builds_on_auto() {
|
||||||
|
assert_eq!(
|
||||||
|
StyleComponentList::to_components(
|
||||||
|
vec![StyleComponentList::from_str("-numbers").expect("no error"),],
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
.0,
|
||||||
|
{
|
||||||
|
let mut expected: HashSet<StyleComponent> = HashSet::new();
|
||||||
|
expected.extend(Auto.components(true));
|
||||||
|
expected.remove(&LineNumbers);
|
||||||
|
expected
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+194
-197
@@ -1,12 +1,31 @@
|
|||||||
use std::path::Path;
|
use std::{
|
||||||
|
path::Path,
|
||||||
use crate::error::Result;
|
sync::{
|
||||||
use ignored_suffixes::IgnoredSuffixes;
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
use globset::{Candidate, GlobBuilder, GlobMatcher};
|
use globset::{Candidate, GlobBuilder, GlobMatcher};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use builtin::BUILTIN_MAPPINGS;
|
||||||
|
use ignored_suffixes::IgnoredSuffixes;
|
||||||
|
|
||||||
|
mod builtin;
|
||||||
pub mod ignored_suffixes;
|
pub mod ignored_suffixes;
|
||||||
|
|
||||||
|
fn make_glob_matcher(from: &str) -> Result<GlobMatcher> {
|
||||||
|
let matcher = GlobBuilder::new(from)
|
||||||
|
.case_insensitive(true)
|
||||||
|
.literal_separator(true)
|
||||||
|
.build()?
|
||||||
|
.compile_matcher();
|
||||||
|
Ok(matcher)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum MappingTarget<'a> {
|
pub enum MappingTarget<'a> {
|
||||||
@@ -29,175 +48,108 @@ pub enum MappingTarget<'a> {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct SyntaxMapping<'a> {
|
pub struct SyntaxMapping<'a> {
|
||||||
mappings: Vec<(GlobMatcher, MappingTarget<'a>)>,
|
/// User-defined mappings at run time.
|
||||||
|
///
|
||||||
|
/// Rules in front have precedence.
|
||||||
|
custom_mappings: Vec<(GlobMatcher, MappingTarget<'a>)>,
|
||||||
|
|
||||||
pub(crate) ignored_suffixes: IgnoredSuffixes<'a>,
|
pub(crate) ignored_suffixes: IgnoredSuffixes<'a>,
|
||||||
|
|
||||||
|
/// A flag to halt glob matcher building, which is offloaded to another thread.
|
||||||
|
///
|
||||||
|
/// We have this so that we can signal the thread to halt early when appropriate.
|
||||||
|
halt_glob_build: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Drop for SyntaxMapping<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// signal the offload thread to halt early
|
||||||
|
self.halt_glob_build.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SyntaxMapping<'a> {
|
impl<'a> SyntaxMapping<'a> {
|
||||||
pub fn empty() -> SyntaxMapping<'a> {
|
pub fn new() -> SyntaxMapping<'a> {
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn builtin() -> SyntaxMapping<'a> {
|
/// Start a thread to build the glob matchers for all builtin mappings.
|
||||||
let mut mapping = Self::empty();
|
///
|
||||||
mapping.insert("*.h", MappingTarget::MapTo("C++")).unwrap();
|
/// The use of this function while not necessary, is useful to speed up startup
|
||||||
mapping
|
/// times by starting this work early in parallel.
|
||||||
.insert(".clang-format", MappingTarget::MapTo("YAML"))
|
///
|
||||||
.unwrap();
|
/// The thread halts if/when `halt_glob_build` is set to true.
|
||||||
mapping.insert("*.fs", MappingTarget::MapTo("F#")).unwrap();
|
pub fn start_offload_build_all(&self) {
|
||||||
mapping
|
let halt = Arc::clone(&self.halt_glob_build);
|
||||||
.insert("build", MappingTarget::MapToUnknown)
|
thread::spawn(move || {
|
||||||
.unwrap();
|
for (matcher, _) in BUILTIN_MAPPINGS.iter() {
|
||||||
mapping
|
if halt.load(Ordering::Relaxed) {
|
||||||
.insert("**/.ssh/config", MappingTarget::MapTo("SSH Config"))
|
break;
|
||||||
.unwrap();
|
}
|
||||||
mapping
|
Lazy::force(matcher);
|
||||||
.insert(
|
}
|
||||||
"**/bat/config",
|
});
|
||||||
MappingTarget::MapTo("Bourne Again Shell (bash)"),
|
// Note that this thread is not joined upon completion because there's
|
||||||
)
|
// no shared resources that need synchronization to be safely dropped.
|
||||||
.unwrap();
|
// If we later add code into this thread that requires interesting
|
||||||
mapping
|
// resources (e.g. IO), it would be a good idea to store the handle
|
||||||
.insert(
|
// and join it on drop.
|
||||||
"/etc/profile",
|
|
||||||
MappingTarget::MapTo("Bourne Again Shell (bash)"),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
mapping
|
|
||||||
.insert("*.pac", MappingTarget::MapTo("JavaScript (Babel)"))
|
|
||||||
.unwrap();
|
|
||||||
mapping
|
|
||||||
.insert("fish_history", MappingTarget::MapTo("YAML"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// See #2151, https://nmap.org/book/nse-language.html
|
|
||||||
mapping
|
|
||||||
.insert("*.nse", MappingTarget::MapTo("Lua"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// See #1008
|
|
||||||
mapping
|
|
||||||
.insert("rails", MappingTarget::MapToUnknown)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Nginx and Apache syntax files both want to style all ".conf" files
|
|
||||||
// see #1131 and #1137
|
|
||||||
mapping
|
|
||||||
.insert("*.conf", MappingTarget::MapExtensionToUnknown)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
for glob in &[
|
|
||||||
"/etc/nginx/**/*.conf",
|
|
||||||
"/etc/nginx/sites-*/**/*",
|
|
||||||
"nginx.conf",
|
|
||||||
"mime.types",
|
|
||||||
] {
|
|
||||||
mapping.insert(glob, MappingTarget::MapTo("nginx")).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
for glob in &[
|
|
||||||
"/etc/apache2/**/*.conf",
|
|
||||||
"/etc/apache2/sites-*/**/*",
|
|
||||||
"httpd.conf",
|
|
||||||
] {
|
|
||||||
mapping
|
|
||||||
.insert(glob, MappingTarget::MapTo("Apache Conf"))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
for glob in &[
|
|
||||||
"**/systemd/**/*.conf",
|
|
||||||
"**/systemd/**/*.example",
|
|
||||||
"*.automount",
|
|
||||||
"*.device",
|
|
||||||
"*.dnssd",
|
|
||||||
"*.link",
|
|
||||||
"*.mount",
|
|
||||||
"*.netdev",
|
|
||||||
"*.network",
|
|
||||||
"*.nspawn",
|
|
||||||
"*.path",
|
|
||||||
"*.service",
|
|
||||||
"*.scope",
|
|
||||||
"*.slice",
|
|
||||||
"*.socket",
|
|
||||||
"*.swap",
|
|
||||||
"*.target",
|
|
||||||
"*.timer",
|
|
||||||
] {
|
|
||||||
mapping.insert(glob, MappingTarget::MapTo("INI")).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// unix mail spool
|
|
||||||
for glob in &["/var/spool/mail/*", "/var/mail/*"] {
|
|
||||||
mapping.insert(glob, MappingTarget::MapTo("Email")).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// pacman hooks
|
|
||||||
mapping
|
|
||||||
.insert("*.hook", MappingTarget::MapTo("INI"))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Global git config files rooted in `$XDG_CONFIG_HOME/git/` or `$HOME/.config/git/`
|
|
||||||
// See e.g. https://git-scm.com/docs/git-config#FILES
|
|
||||||
if let Some(xdg_config_home) =
|
|
||||||
std::env::var_os("XDG_CONFIG_HOME").filter(|val| !val.is_empty())
|
|
||||||
{
|
|
||||||
insert_git_config_global(&mut mapping, &xdg_config_home);
|
|
||||||
}
|
|
||||||
if let Some(default_config_home) = std::env::var_os("HOME")
|
|
||||||
.filter(|val| !val.is_empty())
|
|
||||||
.map(|home| Path::new(&home).join(".config"))
|
|
||||||
{
|
|
||||||
insert_git_config_global(&mut mapping, &default_config_home);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert_git_config_global(mapping: &mut SyntaxMapping, config_home: impl AsRef<Path>) {
|
|
||||||
let git_config_path = config_home.as_ref().join("git");
|
|
||||||
|
|
||||||
mapping
|
|
||||||
.insert(
|
|
||||||
&git_config_path.join("config").to_string_lossy(),
|
|
||||||
MappingTarget::MapTo("Git Config"),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
mapping
|
|
||||||
.insert(
|
|
||||||
&git_config_path.join("ignore").to_string_lossy(),
|
|
||||||
MappingTarget::MapTo("Git Ignore"),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
mapping
|
|
||||||
.insert(
|
|
||||||
&git_config_path.join("attributes").to_string_lossy(),
|
|
||||||
MappingTarget::MapTo("Git Attributes"),
|
|
||||||
)
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
mapping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
|
pub fn insert(&mut self, from: &str, to: MappingTarget<'a>) -> Result<()> {
|
||||||
let glob = GlobBuilder::new(from)
|
let matcher = make_glob_matcher(from)?;
|
||||||
.case_insensitive(false)
|
self.custom_mappings.push((matcher, to));
|
||||||
.literal_separator(true)
|
|
||||||
.build()?;
|
|
||||||
self.mappings.push((glob.compile_matcher(), to));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mappings(&self) -> &[(GlobMatcher, MappingTarget<'a>)] {
|
/// Returns an iterator over all mappings. User-defined mappings are listed
|
||||||
&self.mappings
|
/// before builtin mappings; mappings in front have higher precedence.
|
||||||
|
///
|
||||||
|
/// Builtin mappings' `GlobMatcher`s are lazily compiled.
|
||||||
|
///
|
||||||
|
/// Note that this function only returns mappings that are valid under the
|
||||||
|
/// current environment. For details see [`Self::builtin_mappings`].
|
||||||
|
pub fn all_mappings(&self) -> impl Iterator<Item = (&GlobMatcher, &MappingTarget<'a>)> {
|
||||||
|
self.custom_mappings()
|
||||||
|
.iter()
|
||||||
|
.map(|(matcher, target)| (matcher, target)) // as_ref
|
||||||
|
.chain(
|
||||||
|
// we need a map with a closure to "do" the lifetime variance
|
||||||
|
// see: https://discord.com/channels/273534239310479360/1120124565591425034/1170543402870382653
|
||||||
|
// also, clippy false positive:
|
||||||
|
// see: https://github.com/rust-lang/rust-clippy/issues/9280
|
||||||
|
#[allow(clippy::map_identity)]
|
||||||
|
self.builtin_mappings().map(|rule| rule),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> {
|
/// Returns an iterator over all valid builtin mappings. Mappings in front
|
||||||
|
/// have higher precedence.
|
||||||
|
///
|
||||||
|
/// The `GlabMatcher`s are lazily compiled.
|
||||||
|
///
|
||||||
|
/// Mappings that are invalid under the current environment (i.e. rule
|
||||||
|
/// requires environment variable(s) that is unset, or the joined string
|
||||||
|
/// after variable(s) replacement is not a valid glob expression) are
|
||||||
|
/// ignored.
|
||||||
|
pub fn builtin_mappings(
|
||||||
|
&self,
|
||||||
|
) -> impl Iterator<Item = (&'static GlobMatcher, &'static MappingTarget<'static>)> {
|
||||||
|
BUILTIN_MAPPINGS
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(matcher, target)| matcher.as_ref().map(|glob| (glob, target)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all user-defined mappings.
|
||||||
|
pub fn custom_mappings(&self) -> &[(GlobMatcher, MappingTarget<'a>)] {
|
||||||
|
&self.custom_mappings
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> {
|
||||||
// Try matching on the file name as-is.
|
// Try matching on the file name as-is.
|
||||||
let candidate = Candidate::new(&path);
|
let candidate = Candidate::new(&path);
|
||||||
let candidate_filename = path.as_ref().file_name().map(Candidate::new);
|
let candidate_filename = path.as_ref().file_name().map(Candidate::new);
|
||||||
for (ref glob, ref syntax) in self.mappings.iter().rev() {
|
for (glob, syntax) in self.all_mappings() {
|
||||||
if glob.is_match_candidate(&candidate)
|
if glob.is_match_candidate(&candidate)
|
||||||
|| candidate_filename
|
|| candidate_filename
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -220,48 +172,93 @@ impl<'a> SyntaxMapping<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn basic() {
|
mod tests {
|
||||||
let mut map = SyntaxMapping::empty();
|
use super::*;
|
||||||
map.insert("/path/to/Cargo.lock", MappingTarget::MapTo("TOML"))
|
|
||||||
.ok();
|
|
||||||
map.insert("/path/to/.ignore", MappingTarget::MapTo("Git Ignore"))
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
assert_eq!(
|
#[test]
|
||||||
map.get_syntax_for("/path/to/Cargo.lock"),
|
fn builtin_mappings_work() {
|
||||||
Some(MappingTarget::MapTo("TOML"))
|
let map = SyntaxMapping::new();
|
||||||
);
|
|
||||||
assert_eq!(map.get_syntax_for("/path/to/other.lock"), None);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
map.get_syntax_for("/path/to/.ignore"),
|
map.get_syntax_for("/path/to/build"),
|
||||||
Some(MappingTarget::MapTo("Git Ignore"))
|
Some(MappingTarget::MapToUnknown)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn user_can_override_builtin_mappings() {
|
fn all_fixed_builtin_mappings_can_compile() {
|
||||||
let mut map = SyntaxMapping::builtin();
|
let map = SyntaxMapping::new();
|
||||||
|
|
||||||
assert_eq!(
|
// collect call evaluates all lazy closures
|
||||||
map.get_syntax_for("/etc/profile"),
|
// fixed builtin mappings will panic if they fail to compile
|
||||||
Some(MappingTarget::MapTo("Bourne Again Shell (bash)"))
|
let _mappings = map.builtin_mappings().collect::<Vec<_>>();
|
||||||
);
|
}
|
||||||
map.insert("/etc/profile", MappingTarget::MapTo("My Syntax"))
|
|
||||||
.ok();
|
#[test]
|
||||||
assert_eq!(
|
fn builtin_mappings_matcher_only_compile_once() {
|
||||||
map.get_syntax_for("/etc/profile"),
|
let map = SyntaxMapping::new();
|
||||||
Some(MappingTarget::MapTo("My Syntax"))
|
|
||||||
);
|
let two_iterations: Vec<_> = (0..2)
|
||||||
}
|
.map(|_| {
|
||||||
|
// addresses of every matcher
|
||||||
#[test]
|
map.builtin_mappings()
|
||||||
fn builtin_mappings() {
|
.map(|(matcher, _)| matcher as *const _ as usize)
|
||||||
let map = SyntaxMapping::builtin();
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
assert_eq!(
|
.collect();
|
||||||
map.get_syntax_for("/path/to/build"),
|
|
||||||
Some(MappingTarget::MapToUnknown)
|
// if the matchers are only compiled once, their address should remain the same
|
||||||
);
|
assert_eq!(two_iterations[0], two_iterations[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_mappings_work() {
|
||||||
|
let mut map = SyntaxMapping::new();
|
||||||
|
map.insert("/path/to/Cargo.lock", MappingTarget::MapTo("TOML"))
|
||||||
|
.ok();
|
||||||
|
map.insert("/path/to/.ignore", MappingTarget::MapTo("Git Ignore"))
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
map.get_syntax_for("/path/to/Cargo.lock"),
|
||||||
|
Some(MappingTarget::MapTo("TOML"))
|
||||||
|
);
|
||||||
|
assert_eq!(map.get_syntax_for("/path/to/other.lock"), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
map.get_syntax_for("/path/to/.ignore"),
|
||||||
|
Some(MappingTarget::MapTo("Git Ignore"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_mappings_override_builtin() {
|
||||||
|
let mut map = SyntaxMapping::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
map.get_syntax_for("/path/to/httpd.conf"),
|
||||||
|
Some(MappingTarget::MapTo("Apache Conf"))
|
||||||
|
);
|
||||||
|
map.insert("httpd.conf", MappingTarget::MapTo("My Syntax"))
|
||||||
|
.ok();
|
||||||
|
assert_eq!(
|
||||||
|
map.get_syntax_for("/path/to/httpd.conf"),
|
||||||
|
Some(MappingTarget::MapTo("My Syntax"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_mappings_precedence() {
|
||||||
|
let mut map = SyntaxMapping::new();
|
||||||
|
|
||||||
|
map.insert("/path/to/foo", MappingTarget::MapTo("alpha"))
|
||||||
|
.ok();
|
||||||
|
map.insert("/path/to/foo", MappingTarget::MapTo("bravo"))
|
||||||
|
.ok();
|
||||||
|
assert_eq!(
|
||||||
|
map.get_syntax_for("/path/to/foo"),
|
||||||
|
Some(MappingTarget::MapTo("alpha"))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use globset::GlobMatcher;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::syntax_mapping::{make_glob_matcher, MappingTarget};
|
||||||
|
|
||||||
|
// Static syntax mappings generated from /src/syntax_mapping/builtins/ by the
|
||||||
|
// build script (/build/syntax_mapping.rs).
|
||||||
|
include!(concat!(
|
||||||
|
env!("OUT_DIR"),
|
||||||
|
"/codegen_static_syntax_mappings.rs"
|
||||||
|
));
|
||||||
|
|
||||||
|
// The defined matcher strings are analysed at compile time and converted into
|
||||||
|
// lazily-compiled `GlobMatcher`s. This is so that the string searches are moved
|
||||||
|
// from run time to compile time, thus improving startup performance.
|
||||||
|
//
|
||||||
|
// To any future maintainer (including possibly myself) wondering why there is
|
||||||
|
// not a `BuiltinMatcher` enum that looks like this:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// enum BuiltinMatcher {
|
||||||
|
// Fixed(&'static str),
|
||||||
|
// Dynamic(Lazy<Option<String>>),
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// Because there was. I tried it and threw it out.
|
||||||
|
//
|
||||||
|
// Naively looking at the problem from a distance, this may seem like a good
|
||||||
|
// design (strongly typed etc. etc.). It would also save on compiled size by
|
||||||
|
// extracting out common behaviour into functions. But while actually
|
||||||
|
// implementing the lazy matcher compilation logic, I realised that it's most
|
||||||
|
// convenient for `BUILTIN_MAPPINGS` to have the following type:
|
||||||
|
//
|
||||||
|
// `[(Lazy<Option<GlobMatcher>>, MappingTarget); N]`
|
||||||
|
//
|
||||||
|
// The benefit for this is that operations like listing all builtin mappings
|
||||||
|
// would be effectively memoised. The caller would not have to compile another
|
||||||
|
// `GlobMatcher` for rules that they have previously visited.
|
||||||
|
//
|
||||||
|
// Unfortunately, this means we are going to have to store a distinct closure
|
||||||
|
// for each rule anyway, which makes a `BuiltinMatcher` enum a pointless layer
|
||||||
|
// of indirection.
|
||||||
|
//
|
||||||
|
// In the current implementation, the closure within each generated rule simply
|
||||||
|
// calls either `build_matcher_fixed` or `build_matcher_dynamic`, depending on
|
||||||
|
// whether the defined matcher contains dynamic segments or not.
|
||||||
|
|
||||||
|
/// Compile a fixed glob string into a glob matcher.
|
||||||
|
///
|
||||||
|
/// A failure to compile is a fatal error.
|
||||||
|
///
|
||||||
|
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
|
||||||
|
fn build_matcher_fixed(from: &str) -> GlobMatcher {
|
||||||
|
make_glob_matcher(from).expect("A builtin fixed glob matcher failed to compile")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Join a list of matcher segments to create a glob string, replacing all
|
||||||
|
/// environment variables, then compile to a glob matcher.
|
||||||
|
///
|
||||||
|
/// Returns `None` if any replacement fails, or if the joined glob string fails
|
||||||
|
/// to compile.
|
||||||
|
///
|
||||||
|
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
|
||||||
|
fn build_matcher_dynamic(segs: &[MatcherSegment]) -> Option<GlobMatcher> {
|
||||||
|
// join segments
|
||||||
|
let mut buf = String::new();
|
||||||
|
for seg in segs {
|
||||||
|
match seg {
|
||||||
|
MatcherSegment::Text(s) => buf.push_str(s),
|
||||||
|
MatcherSegment::Env(var) => {
|
||||||
|
let replaced = env::var(var).ok()?;
|
||||||
|
buf.push_str(&replaced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// compile glob matcher
|
||||||
|
let matcher = make_glob_matcher(&buf).ok()?;
|
||||||
|
Some(matcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A segment of a dynamic builtin matcher.
|
||||||
|
///
|
||||||
|
/// Used internally by `Lazy<Option<GlobMatcher>>`'s lazy evaluation closure.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum MatcherSegment {
|
||||||
|
Text(&'static str),
|
||||||
|
Env(&'static str),
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# `/src/syntax_mapping/builtins`
|
||||||
|
|
||||||
|
The files in this directory define path/name-based syntax mappings, which amend
|
||||||
|
and take precedence over the extension/content-based syntax mappings provided by
|
||||||
|
[syntect](https://github.com/trishume/syntect).
|
||||||
|
|
||||||
|
## File organisation
|
||||||
|
|
||||||
|
Each TOML file should describe the syntax mappings of a single application, or
|
||||||
|
otherwise a set of logically-related rules.
|
||||||
|
|
||||||
|
What defines "a single application" here is deliberately vague, since the
|
||||||
|
file-splitting is purely for maintainability reasons. (Technically, we could
|
||||||
|
just as well use a single TOML file.) So just use common sense.
|
||||||
|
|
||||||
|
TOML files should reside in the corresponding subdirectory of the platform(s)
|
||||||
|
that they intend to target. At compile time, the build script will go through
|
||||||
|
each subdirectory that is applicable to the compilation target, collect the
|
||||||
|
syntax mappings defined by all TOML files, and embed them into the binary.
|
||||||
|
|
||||||
|
## File syntax
|
||||||
|
|
||||||
|
Each TOML file should contain a single section named `mappings`, with each of
|
||||||
|
its keys being a language identifier (first column of `bat -L`; also referred to
|
||||||
|
as "target").
|
||||||
|
|
||||||
|
The value of each key should be an array of strings, with each item being a glob
|
||||||
|
matcher. We will call each of these items a "rule".
|
||||||
|
|
||||||
|
For example, if `foo-application` uses both TOML and YAML configuration files,
|
||||||
|
we could write something like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# 30-foo-application.toml
|
||||||
|
[mappings]
|
||||||
|
"TOML" = [
|
||||||
|
# rules for TOML syntax go here
|
||||||
|
"/usr/share/foo-application/toml-config/*.conf",
|
||||||
|
"/etc/foo-application/toml-config/*.conf",
|
||||||
|
]
|
||||||
|
"YAML" = [
|
||||||
|
# rules for YAML syntax go here
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic environment variable replacement
|
||||||
|
|
||||||
|
In additional to the standard glob matcher syntax, rules also support dynamic
|
||||||
|
replacement of environment variables at runtime. This allows us to concisely
|
||||||
|
handle things like [XDG](https://specifications.freedesktop.org/basedir-spec/latest/).
|
||||||
|
|
||||||
|
All environment variables intended to be replaced at runtime must be enclosed in
|
||||||
|
`${}`, for example `"/foo/*/${YOUR_ENV}-suffix/*.log"`. Note that this is the
|
||||||
|
**only** admissible syntax; other variable substitution syntaxes are not
|
||||||
|
supported and will either cause a compile time error, or be treated as plain
|
||||||
|
text.
|
||||||
|
|
||||||
|
For example, if `foo-application` also supports per-user configuration files, we
|
||||||
|
could write something like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# 30-foo-application.toml
|
||||||
|
[mappings]
|
||||||
|
"TOML" = [
|
||||||
|
# rules for TOML syntax go here
|
||||||
|
"/usr/share/foo-application/toml-config/*.conf",
|
||||||
|
"/etc/foo-application/toml-config/*.conf",
|
||||||
|
"${XDG_CONFIG_HOME}/foo-application/toml-config/*.conf",
|
||||||
|
"${HOME}/.config/foo-application/toml-config/*.conf",
|
||||||
|
]
|
||||||
|
"YAML" = [
|
||||||
|
# rules for YAML syntax go here
|
||||||
|
# ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
If any environment variable replacement in a rule fails (for example when a
|
||||||
|
variable is unset), or if the glob string after replacements is invalid, the
|
||||||
|
entire rule will be ignored.
|
||||||
|
|
||||||
|
### Explicitly mapping to unknown
|
||||||
|
|
||||||
|
Sometimes it may be necessary to "unset" a particular syntect mapping - perhaps
|
||||||
|
a syntax's matching rules are "too greedy", and is claiming files that it should
|
||||||
|
not. In this case, there are two special identifiers:
|
||||||
|
`MappingTarget::MapToUnknown` and `MappingTarget::MapExtensionToUnknown`
|
||||||
|
(corresponding to the two variants of the `syntax_mapping::MappingTarget` enum).
|
||||||
|
|
||||||
|
An example of this would be `*.conf` files in general. So we may write something
|
||||||
|
like this:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# 99-unset-ambiguous-extensions.toml
|
||||||
|
[mappings]
|
||||||
|
"MappingTarget::MapExtensionToUnknown" = [
|
||||||
|
"*.conf",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ordering
|
||||||
|
|
||||||
|
At compile time, all TOML files applicable to the target are processed in
|
||||||
|
lexicographical filename order. So `00-foo.toml` takes precedence over
|
||||||
|
`10-bar.toml`, which takes precedence over `20-baz.toml`, and so on. Note that
|
||||||
|
**only** the filenames of the TOML files are taken into account; the
|
||||||
|
subdirectories they are placed in have no influence on ordering.
|
||||||
|
|
||||||
|
This behaviour can be occasionally useful for creating high/low priority rules,
|
||||||
|
such as in the aforementioned example of explicitly mapping `*.conf` files to
|
||||||
|
unknown. Generally this should not be much of a concern though, since rules
|
||||||
|
should be written as specifically as possible for each application.
|
||||||
|
|
||||||
|
Rules within each TOML file are processed (and therefore matched) in the order
|
||||||
|
in which they are defined. At runtime, the syntax selection algorithm will
|
||||||
|
short-circuit and return the target of the first matching rule.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"Bourne Again Shell (bash)" = ["/etc/os-release", "/var/run/os-release"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"Apache Conf" = ["httpd.conf"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"INI" = ["**/.aws/credentials", "**/.aws/config"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"Bourne Again Shell (bash)" = ["**/bat/config"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"YAML" = ["CITATION.cff"]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[mappings]
|
||||||
|
"Dockerfile" = ["Containerfile"]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user