1
0
mirror of https://github.com/sharkdp/bat synced 2026-08-03 18:51:44 +00:00

Compare commits

...

160 Commits

Author SHA1 Message Date
sharkdp 2596bfe7a7 Bump version to v0.15.1 2020-05-11 20:30:45 +02:00
sharkdp 406bffa8c8 Update syntaxes and themes 2020-05-11 20:29:49 +02:00
sharkdp 48441b99ef Patch Markdown syntax
closes #963
see #977
2020-05-11 20:22:50 +02:00
sharkdp ce4717144a Update CHANGELOG 2020-05-11 19:33:13 +02:00
Mitchell Kember f59d00d4c7 Fix base16, and combine 00 and 0f alpha encodings
This changes the base16 theme back from #RRGGBB0f to #RRGGBB00,
reverting part of #934. That PR used the 0f encoding to produce ANSI
escape sequences 30-37 and 40-47 rather than 38;5 and 48;5 which require
256-color support. Unfortunately, it resulted in base16 using the wrong
colors becuase ansi_term does not support the bright variants (90-97 and
100-107) so it simply mapped them to the non-bright colors.

This PR makes combines the 00 and 0f alpha encodings into 00, and makes
them use the Color enum for the first 8 colors and Fixed otherwise. This
means the ansi-light and ansi-dark themes will work on terminals without
256-color support, and base16 will render bright colors correctly.
2020-05-11 19:29:19 +02:00
Ethan P dcfdbf82dd Do not warn if both 'bat' and 'batcat' are available in info.sh 2020-05-02 10:39:58 +02:00
Ethan P 0cb884d501 Prevent batcat warning from being in output of info.sh 2020-05-02 10:39:58 +02:00
Ethan P 7bc4020a92 Check for 'batcat' in info.sh 2020-05-02 10:39:58 +02:00
sharkdp 0792195156 Properly add QML submodule 2020-05-01 10:24:19 +02:00
dependabot-preview[bot] 83f24ffcfb Bump git2 from 0.13.3 to 0.13.5
Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.13.3 to 0.13.5.
- [Release notes](https://github.com/rust-lang/git2-rs/releases)
- [Commits](https://github.com/rust-lang/git2-rs/compare/0.13.3...0.13.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-01 10:21:19 +02:00
pylipp 74ec390770 Add QML syntax
QML.sublime-syntax file generated from Support/QML.tmLanguage using
Sublime's built-in converter.
Fix sharkdp/bat#962
2020-04-30 09:59:14 +02:00
Mario Zaizar 9014ffcfd8 Adds Email (https://github.com/mariozaizar/email.sublime-syntax) 2020-04-29 19:38:39 +02:00
sharkdp 81af375289 Use console 0.11.2, see #955 2020-04-28 21:18:48 +02:00
sharkdp d707eab95c Revert "Use console 0.11.1, see #955"
This reverts commit d64c55ef11.
The build on Windows failed due to two broken tests:

    line_numbers
    tabs_numbers
2020-04-28 09:22:19 +02:00
sharkdp d64c55ef11 Use console 0.11.1, see #955 2020-04-28 00:00:59 +02:00
sharkdp 2b1d53fb27 Add '--locked' flag to all 'cargo install' commands 2020-04-27 23:43:18 +02:00
sharkdp bf696f58c8 Pin version of console to 0.10.0 2020-04-27 23:37:06 +02:00
sharkdp 296bad8ffd Update CHANGELOG 2020-04-27 22:01:41 +02:00
sharkdp 94310af30c Add Fortran syntax, closes #957 2020-04-27 21:45:36 +02:00
sharkdp 38c096bf79 Update CHANGELOG 2020-04-25 15:19:42 +02:00
sharkdp 696d1b3ed3 Update CHANGELOG 2020-04-25 13:17:07 +02:00
sharkdp 17d98724a5 Bump version to v0.15.0 2020-04-25 13:09:24 +02:00
sharkdp 7d7ee1b594 Update cached verison of themes 2020-04-25 13:08:30 +02:00
sharkdp 682b4209f2 Update solarized themes to braver/Solarized, closes #941 2020-04-25 13:08:30 +02:00
sharkdp 41ddcbd240 Remove old Sublime themes 2020-04-25 13:08:30 +02:00
sharkdp 48a7ce3bf2 Write error messages to pager, if attached
closes #946
2020-04-25 13:08:00 +02:00
Lzu Tao 3bcc4d0d55 lto=true and codegen-units=1 together 2020-04-24 17:39:25 +02:00
Lzu Tao 8821bca656 appveyor: Use rustup minimal profile 2020-04-24 17:39:25 +02:00
Lzu Tao 56111aa20d simplify build.rs 2020-04-24 17:39:25 +02:00
sharkdp 5fe8a8342b Add annotations again 2020-04-24 16:51:11 +02:00
sharkdp 989109145a Add '-d' for '--diff' 2020-04-24 16:12:38 +02:00
sharkdp 23afc8e90c Skip non-file inputs when using --diff 2020-04-24 16:12:38 +02:00
sharkdp 8e18786556 Run 'cargo fmt' 2020-04-24 16:11:05 +02:00
sharkdp 81488adf8b Use unreachable!(…) 2020-04-24 16:11:05 +02:00
Lzu Tao e37e9c1214 Fix some clippy lints
Some might actually improve perf
2020-04-24 16:11:05 +02:00
Lzu Tao a4828387c1 simplify build.rs 2020-04-24 16:11:05 +02:00
sharkdp abeac8b12e Bump min. required Rust version to 1.40 2020-04-24 09:57:57 +02:00
sharkdp 82e7786e74 Implementation of 'bat --diff'
This adds a new `--diff` option that can be used to only show lines
close to Git changes (added/removed/modified lines). The amount of
additional context can be controlled with `--diff-context=N`.

closes #23
2020-04-24 09:57:57 +02:00
sharkdp 0064321323 cargo fmt 2020-04-23 00:56:35 +02:00
sharkdp 9e622a1a5f Update version in README 2020-04-23 00:56:27 +02:00
sharkdp 59ca933aee Update dependencies 2020-04-22 23:59:35 +02:00
sharkdp 793af6b911 Add a syntax mapping for bats own config file 2020-04-22 23:58:41 +02:00
sharkdp c8abe3f235 Update CHANGELOG 2020-04-22 23:56:20 +02:00
sharkdp 5d71056209 Fix import in app 2020-04-22 23:55:28 +02:00
sharkdp eb7d4d002a Update documentation 2020-04-22 23:55:28 +02:00
sharkdp 62b32bd848 Make module private 2020-04-22 23:55:28 +02:00
sharkdp 8961f7aef8 Move PagingMode to separate module 2020-04-22 23:55:28 +02:00
sharkdp 17f3a3b95d Simpler highlight method 2020-04-22 23:55:28 +02:00
sharkdp 261a7ea154 Add *_with_name methods 2020-04-22 23:55:28 +02:00
sharkdp 53a973e9dd Add syntaxes and themes method 2020-04-22 23:55:28 +02:00
sharkdp cba9df746e Add paging to advanced example 2020-04-22 23:55:28 +02:00
sharkdp 12eee0c590 Fix warnings for --no-default-features mode 2020-04-22 23:55:28 +02:00
sharkdp b76f5e72d4 Use unwrap 2020-04-22 23:55:28 +02:00
sharkdp 702cb198da Rename error module 2020-04-22 23:55:28 +02:00
sharkdp 5f826419d1 Add YAML example 2020-04-22 23:55:28 +02:00
sharkdp 13f671b499 Update examples 2020-04-22 23:55:28 +02:00
sharkdp 49f4322054 Fix example 2020-04-22 23:55:28 +02:00
sharkdp 0c9e044e41 Update 'cat' example 2020-04-22 23:55:28 +02:00
sharkdp 74d4377ed2 Add advanced example 2020-04-22 23:55:28 +02:00
sharkdp 6a124591df Easier configuration of style components 2020-04-22 23:55:28 +02:00
sharkdp 7a87315b94 Fix #937 2020-04-22 23:55:28 +02:00
sharkdp 0cde4e9121 Numbers => LineNumbers 2020-04-22 23:55:28 +02:00
sharkdp 36dde9275a Simplify style_components 2020-04-22 23:55:28 +02:00
sharkdp a8f759c080 Rename wrap => wrapping 2020-04-22 23:55:28 +02:00
sharkdp f034be71e7 rename run => print 2020-04-22 23:55:28 +02:00
sharkdp eee35e54e9 Update documentation 2020-04-22 23:55:28 +02:00
sharkdp ec0ce05455 Better API for highlighting lines 2020-04-22 23:55:28 +02:00
sharkdp 13e6b3fac7 Reduce public API 2020-04-22 23:55:28 +02:00
sharkdp 26c951fec4 Fix warnings, sort imports, input from string 2020-04-22 23:55:28 +02:00
sharkdp 590960f7f5 Completely refactor 'input' module 2020-04-22 23:55:28 +02:00
sharkdp b4d54106fe Fix comment 2020-04-22 23:55:28 +02:00
sharkdp f3b90ddb38 Add InputDescription 2020-04-22 23:55:28 +02:00
sharkdp 3bacfc5184 Allow fluent style 2020-04-22 23:55:28 +02:00
sharkdp f8d0956893 Rename InputFile => Input 2020-04-22 23:55:28 +02:00
sharkdp 1dc328ad49 Separate inputs from config 2020-04-22 23:55:28 +02:00
sharkdp 5e5cb89da6 Add all builder options 2020-04-22 23:55:28 +02:00
sharkdp 057e4eced1 Large refactoring towards a better builder structure 2020-04-22 23:55:28 +02:00
sharkdp 27974616bf Initial verison of PrettyPrinter builder 2020-04-22 23:55:28 +02:00
sharkdp 319ab779ee Use 4-bit ANSI codes for base16 theme
closes #865
2020-04-22 10:22:11 +02:00
Brice Dutheil 37831cdcb3 Fix README phrasing about dark mode 2020-04-21 22:37:24 +02:00
sharkdp 70480ee9d4 Bump version to v0.14.0 2020-04-21 18:21:22 +02:00
sharkdp 371c929ede Update CHANGELOG 2020-04-21 18:21:12 +02:00
sharkdp 864656bd11 Pass --no-init on Windows if less version < 559
We used to call `less` with
``` bash
less --RAW-CONTROL-CHARS --quit-if-one-screen --no-init
```
We only passed `--no-init` because there was a bug with previous versions
of `less` which required the use of `--no-init` in combination with
`--quit-if-one-screen` to prevent this "no output" issue from happening.

Since bat 0.13, [we omit the `--no-init` option](https://github.com/sharkdp/bat/blob/0ecc94956b88beed27ca13a130c8ba09f1a220d8/src/output.rs#L85-L97)
if we can detect that the version of `less` is higher than or equal to 530. We
did that because `--no-init` breaks mouse support and because [less 530 fixed
the above-mentioned bug](http://www.greenwoodsoftware.com/less/news.530.html).
However, it seems that this bug was *not* fixed on Windows!

According to @gwsw, the issue should be fixed with less 559 on Windows.

closes #887
2020-04-21 18:18:03 +02:00
sharkdp 2e9cf63a5f Fix compilation of library 2020-04-21 18:14:31 +02:00
sharkdp 886b22e0ee Do not store metadata.yaml file in git 2020-04-21 18:14:31 +02:00
sharkdp 72618db179 Add metadata information to cached assets
When saving/reading user-provided syntaxes or themes, `bat` will now maintain a
`metadata.yaml` file which includes information about the `bat` version which was
used to create the cached files. When loading cached files, we now print an error
if they have been created with an incompatible version

closes #882
2020-04-21 18:14:31 +02:00
sharkdp c8273369cc Make get_cache_dir private 2020-04-21 18:14:31 +02:00
sharkdp 95a56a2795 Typo in CHANGELOG 2020-04-21 17:49:36 +02:00
sharkdp 859ff50766 Update default config file, add comment about tmux mouse scrolling support 2020-04-21 17:45:14 +02:00
sharkdp ce583eb9bf Revert "Pass '--mouse' to less versions >= 551"
This reverts commit cb21b74ecd.
2020-04-21 17:29:01 +02:00
sharkdp 3a195be14e Revert "Only enable --mouse when running from tmux, see #904"
This reverts commit 5f6e310152.
2020-04-21 17:28:37 +02:00
sharkdp 5f6e310152 Only enable --mouse when running from tmux, see #904 2020-04-21 16:15:02 +02:00
sharkdp 5449472f15 Remove invalid UTF-8 file from repo, use temp file instead 2020-04-21 16:02:28 +02:00
sharkdp 82e20bfe14 Fix bug for file with invalid-utf8 filenames 2020-04-21 14:09:18 +02:00
sharkdp 47abb192bc Patch Rust syntax file
closes #924
2020-04-21 13:45:26 +02:00
sharkdp 03a7d6be92 Disable Travis caching 2020-04-21 13:44:51 +02:00
sharkdp a927815292 Update CHANGELOG 2020-04-21 11:50:26 +02:00
sharkdp 3447a03433 Update dependencies 2020-04-21 11:50:26 +02:00
sharkdp 03c2281828 Add do-not-panic! regression tests 2020-04-21 11:50:26 +02:00
sharkdp c386cb35fb Pass '--mouse' to less versions >= 551
When using `less` version 551 or newer, `bat` will now pass the `--mouse` argument, allowing
for (better) mouse scrolling support in some terminals.

closes #904
2020-04-21 10:00:32 +02:00
sharkdp 97ea3dd4dc Update CHANGELOG 2020-04-21 08:58:38 +02:00
sharkdp a6d9d1551f Improve error message 2020-04-21 08:57:15 +02:00
sharkdp 34619a2e89 Small refactoring, handle invalid UTF-8 filenames 2020-04-21 08:57:15 +02:00
Kyle Criddle 83d408bab3 Extract common syntax functionality to helper fns 2020-04-21 08:57:15 +02:00
sharkdp 8067fd9eda Fix build on 1.37 2020-04-21 08:57:15 +02:00
sharkdp 98ba9e0101 Use to_string_lossy 2020-04-21 08:57:15 +02:00
sharkdp 1b8ce60054 Pass stdin as a generic BufRead, fix stdin tests 2020-04-21 08:57:15 +02:00
sharkdp d5a31dc2ec Remove commented-out code 2020-04-21 08:57:15 +02:00
sharkdp 051dd4ba87 Make filename method private 2020-04-21 08:57:15 +02:00
sharkdp 2ad1848859 Do not take optional as argument 2020-04-21 08:57:15 +02:00
sharkdp 90e7d2fe33 Rename field name, new constructors 2020-04-21 08:57:15 +02:00
Kyle Criddle 04fa84aea7 Moved user_provided_filename to be contained within OrdinaryFile struct 2020-04-21 08:57:15 +02:00
Kyle Criddle a3f8140fbe Use --file-name to detect syntax highlighting
Closes #891
2020-04-21 08:57:15 +02:00
Ethan P 96aedf6240 Add cat alias detection to info.sh (#911) 2020-04-21 08:33:44 +02:00
Michael Diamond 4cc2989fe9 Clarify the Ubuntu/Debian install instructions
A cursory reading of the existing text would lead a reader to think `bat`should be available on Debian, which isn't really true. Most users are not running Sid, so `bat` isn't yet available to them.
2020-04-17 14:10:08 +02:00
sharkdp 0ecc94956b Add performance patch for Makefile syntax
Thanks to @keith-hall!

closes #750
2020-04-13 10:38:22 +02:00
David Peter 61e47e0c13 Add Stylus syntax (#917)
closes #915
2020-04-12 19:42:36 +02:00
sharkdp f5145ef130 Move Rego syntax 2020-04-12 19:20:28 +02:00
sharkdp edd27645d0 Add a way to directly search for a pattern 2020-04-12 19:19:33 +02:00
sharkdp fc6f4f31dd Add documentation 2020-04-12 19:19:33 +02:00
sharkdp f2cef702a0 Add script to find slow-to-highlight files 2020-04-12 19:19:33 +02:00
sharkdp 8b3d54ce29 Update CHANGELOG 2020-04-11 23:55:27 +02:00
sharkdp 47a3721890 Add patch for C# syntax 2020-04-11 23:55:27 +02:00
sharkdp 495fab24a5 cargo fmt 2020-04-11 19:40:04 +02:00
sharkdp fccbe4f4f2 Add regex-onig feature to 'cargo check' tests 2020-04-11 10:33:53 +02:00
sharkdp 9793bb3938 Update to syntect 4.1, fancy-regex support 2020-04-11 10:33:53 +02:00
sharkdp 496dd29cb9 Add -i option for bash/zsh to output wrapper function 2020-04-09 09:20:03 +02:00
Ethan P 116d76a00f Reformat info.sh 2020-04-09 09:20:03 +02:00
Ethan P dd336e6fb2 Fix wrapper detection failing on non-English LANG in info.sh 2020-04-09 09:20:03 +02:00
Ethan P c33af3581a Fix missing shell '-i' option when checking for wrapper in info.sh 2020-04-09 09:20:03 +02:00
Ethan P 2f4ecf8fc5 Add zsh wrapper detection to info.sh 2020-04-09 09:20:03 +02:00
Ethan P e037afeaca Add custom theme/syntax detection to info.sh 2020-04-09 09:02:23 +02:00
sharkdp 7b87af1748 Update dependencies, closes #476 2020-04-08 09:31:50 +02:00
dependabot-preview[bot] 0eee26fb09 Bump assert_cmd from 0.12.0 to 1.0.1
Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 0.12.0 to 1.0.1.
- [Release notes](https://github.com/assert-rs/assert_cmd/releases)
- [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/assert_cmd/compare/v0.12.0...v1.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-01 07:50:24 +02:00
sharkdp 3355aeba22 Update CHANGELOG 2020-03-31 09:33:58 +02:00
David Tolnay 42e3825daf Updates for review of PR 899 2020-03-31 09:27:56 +02:00
David Tolnay 014d754588 Move paging support behind a feature 2020-03-31 09:27:56 +02:00
David Tolnay 4e11abdf9b Move git changes support behind a feature 2020-03-31 09:27:56 +02:00
David Tolnay 570805bc98 Strip dependencies of bat-as-a-library 2020-03-31 09:27:56 +02:00
David Tolnay e7e1967bb0 Allow using bat-as-a-library with older syntect 2020-03-30 22:01:18 +02:00
sharkdp 9a050cd87f Update CHANGELOG 2020-03-27 07:33:56 +01:00
Patrick East d39507889a Add OPA Rego syntax
This adds syntax support for Rego, the declarative policy language
used by the Open Policy Agent:

https://github.com/open-policy-agent/opa

Generated from:

https://github.com/open-policy-agent/opa/blob/master/misc/syntax/textmate/Rego.tmLanguage

Using:

https://github.com/aziz/SublimeSyntaxConvertor

Signed-off-by: Patrick East <east.patrick@gmail.com>
2020-03-27 07:33:07 +01:00
sharkdp 3e8d444c78 Fix for Rust 1.37 2020-03-26 10:01:02 +01:00
sharkdp 522ab7a83c CHANGELOG: Add entries for upcoming release 2020-03-26 09:55:21 +01:00
David Peter 0c5b4fcd4a Add CHANGELOG to the repository 2020-03-26 09:55:21 +01:00
David Peter 37b3b8730d Merge pull request #871 from neuronull/fix_654_stdin_filename
Implement --file-name<name> option
2020-03-26 09:35:01 +01:00
Josh Mickley 7c50fe5fec Document the --generate-config-file option 2020-03-26 09:14:40 +01:00
Josh Mickley 40a827ebcb Graceful handling of error conditions 2020-03-26 09:14:40 +01:00
Josh Mickley 376c556862 Add option to generate a default config file, fixes #870 2020-03-26 09:14:40 +01:00
Kyle Criddle 83772bd2cf Minor --file-name code hygeine 2020-03-25 18:58:05 -06:00
Marco Ieni 4aef8c180a README: fix manpage syntax link 2020-03-25 20:07:50 +01:00
Kyle Criddle 131d113ff5 Merge branch 'fix_654_stdin_filename' of github.com:neuronull/bat into fix_654_stdin_filename 2020-03-24 19:14:07 -06:00
Kyle Criddle 38178fedf4 Merge branch 'master' into fix_654_stdin_filename 2020-03-24 19:08:43 -06:00
Kyle Criddle 59f2e2d58d Implemented --file-name for multiple files + tests 2020-03-24 18:26:00 -06:00
Samuel FORESTIER 8d3136eb6f Adds Homepage to Debian package control file 2020-03-24 08:36:13 +01:00
Igor Raits 136a745c5d chore: Update liquid to 0.20
Signed-off-by: Igor Raits <i.gnatenko.brain@gmail.com>
2020-03-22 21:37:14 +01:00
Kyle Criddle fb3c775c8b --file-name for normal files. integration tests. 2020-03-22 11:10:27 +01:00
Kyle Criddle 8adce9fae8 Implement --file-name<name> option
- can specify filename to be displayed when printing.
- useful for when piping data from STDIN

Closes #654
2020-03-22 11:10:27 +01:00
Kyle Criddle cfa2cb6ec7 --file-name for normal files. integration tests. 2020-03-19 20:46:19 -06:00
Kyle Criddle 517be5c7bc Implement --file-name<name> option
- can specify filename to be displayed when printing.
- useful for when piping data from STDIN

Closes #654
2020-03-17 20:29:01 -06:00
76 changed files with 4153 additions and 923 deletions
+1
View File
@@ -4,3 +4,4 @@
# Generated files
/assets/completions/bat.fish
/assets/manual/bat.1
/assets/metadata.yaml
+15 -3
View File
@@ -166,9 +166,6 @@
[submodule "assets/themes/Nord-sublime"]
path = assets/themes/Nord-sublime
url = https://github.com/crabique/Nord-plist.git
[submodule "assets/themes/solarized-sublime"]
path = assets/themes/solarized-sublime
url = https://github.com/paulcpederson/solarized-sublime.git
[submodule "assets/syntaxes/Vue"]
path = assets/syntaxes/02_Extra/Vue
url = https://github.com/vuejs/vue-syntax-highlight.git
@@ -176,3 +173,18 @@
[submodule "assets/syntaxes/CoffeeScript"]
path = assets/syntaxes/02_Extra/CoffeeScript
url = https://github.com/sustained/CoffeeScript-Sublime-Plugin
[submodule "assets/syntaxes/02_Extra/Stylus"]
path = assets/syntaxes/02_Extra/Stylus
url = https://github.com/billymoon/Stylus
[submodule "assets/themes/Solarized"]
path = assets/themes/Solarized
url = https://github.com/braver/Solarized
[submodule "assets/syntaxes/02_Extra/Fortran"]
path = assets/syntaxes/02_Extra/Fortran
url = https://github.com/315234/SublimeFortran
[submodule "assets/syntaxes/02_Extra/Email"]
path = assets/syntaxes/02_Extra/Email
url = https://github.com/mariozaizar/email.sublime-syntax.git
[submodule "assets/syntaxes/02_Extra/QML"]
path = assets/syntaxes/02_Extra/QML
url = https://github.com/skozlovf/Sublime-QML
+3 -4
View File
@@ -1,5 +1,4 @@
language: rust
cache: cargo
matrix:
include:
@@ -33,15 +32,15 @@ matrix:
# Minimum Rust supported channel.
- os: linux
rust: 1.36.0
rust: 1.40.0
env: TARGET=x86_64-unknown-linux-gnu
- os: linux
rust: 1.36.0
rust: 1.40.0
env:
- TARGET=x86_64-unknown-linux-musl
- CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc
- os: osx
rust: 1.36.0
rust: 1.40.0
env: TARGET=x86_64-apple-darwin
# Disable nightly for now
+663
View File
@@ -0,0 +1,663 @@
# unreleased
## Features
## Bugfixes
## Other
## New syntaxes
## New themes
## `bat` as a library
## Packaging
# v0.15.1
## Bugfixes
- Fix highlighting of Markdown files, see #963 and #977
- Fix `base16` theme (was broken since in v0.14), see #972, #934 and #979 (@mk12).
Users suffering from #865 ("no color for bat in ssh from a Windows client") can use the `ansi-dark` and `ansi-light` themes from now on.
## New syntaxes
- Fortran, see #957
- Email (@mariozaizar)
- QML, see #962 (@pylipp)
# v0.15.0
## Features
- Add a new `--diff`/`-d` option that can be used to only show lines surrounding
Git changes, i.e. added, removed or modified lines. The amount of additional
context can be controlled with `--diff-context=N`. See #23 and #940
## Bugfixes
- Error message printed in the middle of the output for another file, see #946
- Performance improvements when using custom caches (via `bat cache --build`): the `bat` startup time should now be twice as fast (@lzutao).
## Themes
- Updated version of the Solarized dark/light themes, see #941
## `bat` as a library
- There are a few changes in the "low level" API (the `Config` struct has changed and
the error handler needs a new `&mut dyn Write` argument). The high-level API is not
affected.
# v0.14.0
## Features
- Added a new `--file-name <name>…` option to overwrite the displayed filename(s)
in the header. This is useful when piping input into `bat`. See #654 and #892 (@neuronull).
- Added a new `--generate-config-file` option to create an initial configuration file
at the right place. See #870 (@jmick414)
## Bugfixes
- Performance problems with C# source code have been fixed, see #677 (@keith-hall)
- Performance problems with Makefiles have been fixed, see #750 (@keith-hall)
- Fix bug when highlighting Ruby files with unindented heredocs, see #914 (@keith-hall)
- A highlighting problem with Rust source code has been fixed, see #924 (@keith-hall)
- Windows: short files that do not require paging are displayed and then lost, see #887
- `--highlight-line` did not work correctly in combination with `--tabs=0` and `--wrap=never`,
see #937
## Other
- When saving/reading user-provided syntaxes or themes, `bat` will now maintain a
`metadata.yaml` file which includes information about the `bat` version which was
used to create the cached files. When loading cached files, we now print an error
if they have been created with an incompatible version. See #882
- Updated `liquid` dependency to 0.20, see #880 (@ignatenkobrain)
## `bat` as a library
- A completely new "high level" API has been added that is much more convenient
to use. See the `examples` folder for the updated code. The older "low level"
API is still available (basically everything that is not in the root `bat`
module), but has been refactored quite a bit. It is recommended to only use
the new "high level" API, if possible. This will be much easier to keep stable.
Note that this should still be considered a "beta" release of `bat`-as-a-library.
For more details and some screenshots of the example programs, see #936.
- Stripped out a lot of binary-only dependencies, see #895 and #899 (@dtolnay)
This introduces a `features = ["application"]` which is enabled by default and pulls in
everything required by `bat` the application. When depending on bat as a library, downstream
`Cargo.toml` should disable this feature to cut out inapplicable heavy dependencies:
``` toml
[dependencies]
bat = { version = "0.14", default-features = false }
```
Other optional functionality has also been put behind features: `paging` and `git` support.
- Allow using the library with older syntect, see #896 and #898 (@dtolnay)
## New syntaxes
- Rego, see #872 (@patrick-east)
- Stylo, see #917
# v0.13.0
## `bat` as a library
Beginning with this release, `bat` can be used as a library (#423).
This was a huge effort and I want to thank all people who made this possible: @DrSensor, @mitsuhiko, @mre, @eth-p!
- Initial attempt in #469 (@mitsuhiko)
- Second attempt, complete restructuring of the `bat` crate, see #679 (@DrSensor)
- Updates to example, public API, error handling, further refactoring: #693 #873 #875 (@sharkdp)
I want to stress that this is the very first release of the library. Things are very likely to change. A lot of things are still missing (including the documentation).
That being said, you can start using it! See the example programs in [`examples/`](https://github.com/sharkdp/bat/tree/master/examples).
You can see the API documentation here: https://docs.rs/bat/
## Features
- (**Breaking change**) Glob-based syntax mapping, see #877 and #592. With this change,
users need to update their bat config files (`bat --config-file`), if they have any `--map-syntax` settings
present.
The option now works like this:
```bash
--map-syntax <glob-pattern>:<syntax-name>
```
For more information, see the `--help` text, the man page or the README.
This new feature allows us to properly highlight files like:
* `/etc/profile`
* `~/.ssh/config`
- `--highlight-line` now accepts line ranges, see #809 (@lkalir)
- Proper wrapping support for output with wide Unicode characters, see #811 #787 and #815 (@Kogia-sima)
- A lot of updates to existing syntaxes via #644 (@benwaffle, @keith-hall)
- `BAT_CACHE_PATH` can be used to place cached `bat` assets in a non-standard path, see #829 (@neuronull)
- Support combination of multiple styles at the same time, see #857 (@aslpavel)
## Bugfixes
- Do not pass '--no-init' on newer less versions, see #749 and #786 (@sharkdp)
- 'bat cache' still takes precedence over existing files, see #666 (@sharkdp)
- `.sc` files should be treated as scala files, see #443 (@benwaffle)
- Allow underscores and dashes in page names, see #670 (@LunarLambda)
- Keep empty lines empty, see #601 (@mbarbar)
- Wrapping does not work when piping, see #758 (@fusillicode, @allevo, @gildo)
- Allow for non-unicode filenames, see #225 (@sharkdp)
- Empty file without header produces incomplete grid, see #798 (@eth-p)
- Files named `build` don't respect shebang lines, see #685 (@sharkdp)
## Other
- Parametrizable names for man page and shell completion files, see #659 #673 #656 (@eth-p)
- Enabled LTO, making `bat` about 10% faster, see #719 (@bolinfest, @sharkdp)
- Suggestions non how to configure `bat` for MacOS dark mode, see README (@jerguslejko)
- Extended ["Integration with other tools"](https://github.com/sharkdp/bat#integration-with-other-tools) section (@eth-p)
- Updated [instrutions on how to use `bat` as a `man`-pager](https://github.com/sharkdp/bat#man), see #652, see #667 (@sharkdp)
- Add section concerning file encodings, see #688 and #568 (@sharkdp)
- Updated sort order of command-line options in `--help` text and manpage, see #653 and #700 (@hrlmartins)
- Updates to the man page syntax, see #718 (@sharkdp)
- Japanese documentation updates, see #863 (@k-ta-yamada, @sorairolake and @wt-l00)
- Accept "default" as a theme, see #757 (@fvictorio)
- Updated Windows installation instructions, see #852 (@sorenbug)
- Updated man page, see #573 (@sharkdp)
## New syntaxes
- Jinja2, see #648 (@Martin819)
- SaltStack SLS, see #658 (@Martin819)
- `/etc/fstab`, see #696 (@flopp and @eth-p)
- `/etc/group` and `/etc/passwd`, see #698 (@argentite)
- `/proc/cpuinfo` and `/proc/meminfo`, see #593 (@sharkdp)
- Nim, see #542 (@sharkdp)
- Vue, see #826 (@chaaaaarlotte)
- CoffeScript, see #833 (@sharkdp)
## New themes
- Dracula, see #687 (@clarfon)
- Nord, see #760 (@crabique)
- Solarized light and dark, see #768 (@hakamadare)
## Packaging
- `bat` is now in the official Ubuntu and Debian repositories, see #323 and #705 (@MarcoFalke)
- `bat` can now be installed via MacPorts, see #675 (@bn3t)
- Install fish completions into 'vendor_completions.d', see #651 (@sharkdp)
## Thanks
- To @eth-p for joining me as a maintainer! I'm very grateful for all the work you put into
managing and responding to issues, improving our deployment, adding PR/issue templates (#837) as
well as fixing bugs and implementing new features.
# v0.12.1
## Bugfixes
- Fixes a bug for older Windows versions (*"The procedure entry point `CreateFile2` could not be located"*), see #643 (@rivy)
# v0.12.0
## Features
- Binary file content can now be viewed with `bat -A`, see #623, #640 (@pjsier and @sharkdp)
- `bat` can now be used as a man pager. Take a look at the README and #523 for more details.
- Add new style component to separate multiple `--line-range`s, see #570 (@eth-p)
- Added `-L` as an alias for `--list-languages`
## Bugfixes
- Output looks unbalanced when using '--style=grid,numbers' without 'header', see #571 (@eth-p)
- issues with filenames starting with "cache", see #584
- Can't build cache with new theme without creating cache dir, see #576 (@frm)
- `--terminal-width -10` is parsed incorrectly, see #611
## Other
- Added fish completions to DEB package, see #554
## New syntaxes
- Emacs Org mode, see #36 (@bricewge)
- `requirements.txt`
- DotENV `.env`
- SSH config syntax (`-l ssh_config`), see #582 (@issmirnov)
- `/etc/hosts`, see #583 (@issmirnov)
- GraphQL, see #625 (@dandavison)
- Verilog, see #616
- SCSS and Sass, see #637
- `strace` syntax, see #599
## Packaging
- `bat` is now in the official Gentoo repositories, see #588 (@toku-sa-n)
- `bat` is now in the official Alpine Linux repositories, see #586 (@5paceToast)
- `bat` is in the official Fedora repositories, see #610 (@ignatenkobrain)
# v0.11.0
## Features
- Three new special color themes are available: `ansi-light`, `ansi-dark` and `base16`. These
are useful for people that often switch from dark to light themes in their terminal emulator
or for people that want the colors to match their terminal theme colors. For more details,
see #543 and #490 (@mk12, implementation idea by @trishume)
- Hand-written auto completion script for Fish shell, see #524 and #555 (@ev-dev and @eth-p)
- The `-p`/`--plain` option can now be used twice (typically `-pp`). The first `-p` switches the
`--style` to "plain". The second `-p` disables the pager. See #560 and #552 (@eth-p)
## Bugfixes
- Do not replace arguments to `less` when using `--pager`, see #509
- Binary files will now be indicated by a warning in interactive mode, see #530 #466 #550 (@maxfilov)
- Empty files are (once again) printed with a single header line, see #504 and #566 (@reidwagner
and @sharkdp)
- `--terminal-width=0` is now disallowed, see #559 (@eth-p)
- Accidental printing of files named `cache`, see #557
## Other
- New integration tests, see #500 and #502 (@reidwagner and @sharkdp)
- New ["Integration with other tools"](https://github.com/sharkdp/bat#integration-with-other-tools) section in the README.
- Migrated to Rust 2018 (@expobrain)
## New syntaxes
- F# syntax has been updated, see #531 (@stroborobo)
- Fish shell, see #548 (@sanga)
## Packaging
- `bat` is now available on Chocolatey, see #541 (@rasmuskriest)
# v0.10.0
## Features
- Added new `--highlight-line <N>` option, see #453, #346 and #175 (@tskinn and @sharkdp)
## Changes
- **Change the default configuration directory on macOS** to `~/.config/bat`, see #442 (@lavifb). If you are on macOS, you need to copy your configuration directory from the previous place (`~/Library/Preferences/bat`) to the new place (`~/.config/bat`).
- Completely disabled the generation of shell completion files, see #372
- Properly set arguments to `less` if `PAGER` environment variable contains something like `less -F` (which is missing the `-R` option), see #430 (@majecty)
- Report the name of missing files, see #444 (@ufuji1984)
- Don't start pager if file doesn't exist, see #387
- Rename `bat cache --init` to `bat cache --build`, see #498
- Move the `--config-dir` and `--cache-dir` options from `bat cache` to `bat` and hide them from the help text.
## Bugfixes
- Blank line at the end of output when using `--style=plain`, see #379
- EOF must be sent twice on stdin if no other input is sent, see #477 (@reidwagner)
## New syntaxes
- Twig (@ahmedelgabri)
- `.desktop` files (@jleclanche)
- AsciiDoc (@markusthoemmes)
- Assembly (x86_64 and ARM)
- Log files (@caos21)
- Protobuf and ProtobufText (@caos21)
- Terraform (@caos21)
- Jsonnet (@hfm)
- Varlink (@haraldh)
## Other
- Added Japanese version of README (@sh-tech and @object1037)
- Code improvements (@barskern)
# v0.9.0
## Features
- A new `-A`/`--show-all` option has been added to show and highlight non-printable characters (in analogy to GNU `cat`s option):
![](https://camo.githubusercontent.com/c3e769482ef3184f6be6adaa34bdc8d19c378254/68747470733a2f2f692e696d6775722e636f6d2f324b54486859542e706e67)
see #395 and #381 for more details.
- Added `--pager` option (to configure the pager from the configuration file), see #362 (@majecty)
- Added `BAT_CONFIG_PATH` environment variable to set a non-default path for `bat`s configuration file, see #375 (@deg4uss3r)
- Allow for multiple occurences of `--style` to allow for the configuration
of styles from the config file, see #367 (@sindreij)
- Allow for multiple `--line-range` arguments, see #23
- The `--terminal-width` option can now also accept offsets, see #376
## Changes
- Use of italics is now *disabled by default* (see #389 for details). They can be
re-enabled by adding `--italic-text=always` to your configuration file.
- The default tab-width has been set to 4.
- Added new "Sublime Snazzy" theme.
- Shell completions are currently *not* shipped anymore, see #372 for details.
## Bugfixes
- Avoid endless recursion when `PAGER="bat"`, see #383 (@rodorgas)
## Other
- `bat` is now available on openSUSE, see #405 (@dmarcoux)
- Added section about the new configuration file in the README (@deg4uss3r)
- Chinese translation of README (@chinanf-boy)
- Re-written tests for `--tabs` (@choznerol)
- Speed up integration tests, see #394
# v0.8.0
## Features
- Support for a configuration file with the following simple format:
```bash
--tabs=4
--theme="Sublime Snazzy"
# A line-comment
--map-syntax .ignore:.gitignore
--map-syntax PKGBUILD:bash
--map-syntax Podfile:ruby
# Flags and options can also be on a single line:
--wrap=never --paging=never
```
The configuration file path can be accessed via `bat --config-file`. On Linux,
it is stored in `~/.config/bat/config`.
- Support for the `BAT_OPTS` environment variable with the same format as specified
above (in a single line). This takes precedence over the configuration file.
See also #310.
- Support for custom syntax mappings via the `-m`/`--max-syntax` option.
This allows users to (re)map certain file extensions or file names to an existing syntax:
``` bash
bat --map-syntax .config:json ...
```
The option can be use multiple times. Note that you can easily make these mappings permanent by using bats new configuration file.
See #169
- Support pager command-line arguments in `PAGER` and `BAT_PAGER`, see #352 (@Foxboron)
- Add support for wildcards in Windows CMD, see #309 (@zxey)
- First-line syntax detection for all input types, see #205
- Encoding support for UTF-16LE and UTF-16BE, see #285
- New syntaxes: Robot framework (@sanga)
## Changes
- Binary files are now detected and not displayed when the output goes to an interactive terminal, see #205
## Bugfixes
- JavaDoc comments break syntax highlighting in .java files, see #81
- Bat Panics on Haskell Source Code, see #314
## Other
- Better `-h` and `--help` texts.
- Updated documentation on how to configure `bat`s pager
- Updated documentation for light backgrounds, see #328 (@russtaylor)
- Generate shell completions during build, see #115 (@davideGiovannini)
- A lot of new tests have been written
- `bat` is now available via [Termux](https://termux.com/), see #341 (@fornwall)
- `bat` is now available via [nix](https://nixos.org/nix), see #344 (@mgttlinger)
- `bat` is now available via [Docker](https://hub.docker.com/r/danlynn/bat/), see #331 (@danlynn)
# v0.7.1
## Features
- Use the `ansi_colours` package by @mina86 for better true-color approximation on 8 bit color terminals, see #319 and #202.
## Bugfixes
- Bat Panics on Haskell Source Code, see #314
- Disable wrapping when `--style=plain`/`-p` is used, see #289
## Other
- Added Ansible install instructions (@aeimer)
- Added section about Cygwin to the README (@eth-p)
# v0.7.0
## Features
- Tabs are now (optionally) expanded to spaces. This can be controlled with the new
`--tabs` command-line option or the `BAT_TABS` environment variable. The
new feature also closes two bugs #166 and #184. For more information, see #302 (@eth-p).
- Added support for the `BAT_STYLE` environment variable, see #208 (@ms2300)
- Added `OneHalf` theme for terminals with a light-gray background, see #256
- Added new syntaxes for CSV, JSX in JavaScript and TypeScript, Cabal, Dart,
F#, PureScript, Swift, Crystal, PowerShell (Many Thanks to @tobenna and @mimadrid)
## Changes
- Query `git diff` only when needed, see #303 (@ShikChen)
- Disable wrapping when `--plain` is used, see #289 (@eth-p)
## Bugfixes
- Can read files named `cache`, see #275 (@BK1603)
- A lot of bugfixes for Windows, see #252, #264
- Detect `less` reliably and in a portable way, see #271 and #290 (@Aankhen)
- last decoration line is not formatted properly with `--wrap never`, see #299 (@Rogach)
- Do not show file header for directories, see #292
## Other
- Enabled a new `aarch64` build target, see #258 (@rachitchokshi)
- Provide Debian packages for `armhf`, see #280 (@rachitchokshi)
- Added README section about "`bat` on Windows" (@Aankhen)
- Windows installation via scoop (@meltinglava)
# v0.6.1
## Bugfixes
- Fixed panic when running `bat --list-languages | head`, see #232 (@mchlrhw)
- Respect `--color` settings for `--list-themes` and `--list-languages`, see #233
- Git modifications now work on Windows
## Other
- There will be auto-generated Windows releases, starting with this version (@anykao)
# v0.6.0
## Features
- The `--list-themes` option now shows a preview for each highlighting theme (@ms2300)
- Added `-p`/`--plain` as an alias for `--style=plain`, see #212 (@ms2300)
- Major refactorings, enabling some progress on #150. In non-interactive mode, `bat` will now copy input bytes 1:1.
- New languages: Elm, Kotlin, Puppet, TypeScript, see #215 #216 #217 #218
- New syntax highlighting theme: zenburn (@colindean)
## Changes
- New themes in `$BAT_CONFIG_DIR/themes` are now loaded *in addition* to
the default themes (they may also override), see #172
- The `Default.tmTheme` symlink is not necessary anymore.
## Bugfixes
* Using `bat cache --init` leads to duplicated syntaxes, see #206
## Other
* Extended and cleaned-up `--help` text.
* Added initial version of a man page, see #52
* New README sections: *Development* and *Troubleshooting*, see #220
# v0.5.0
## Features
- Added `--line-range n:m` option to print a range of lines, see #159 (@tskinn)
- The syntax highlighting theme can now be controlled by the `BAT_THEME` environment variable, see [README](https://github.com/sharkdp/bat#highlighting-theme) and #177 (@mandx)
- The `PAGER` and `BAT_PAGER` environment variables can be used to control the pager that `bat` uses, see #158 and the [new README section](https://github.com/sharkdp/bat#using-a-different-pager)
- Added syntax highlighting for Nix, see #180
- Added syntax highlighting for AWK (Gert Hulselmans)
## Changes
- The customization of syntax sets and theme sets is now separated. Syntax definitions are now loaded *in addition* to the ones that are stored in the `bat` binary by default. Please refer to these new sections in the README: [Adding new syntaxes](https://github.com/sharkdp/bat#adding-new-syntaxes--language-definitions), [Adding new themes](https://github.com/sharkdp/bat#adding-new-themes), also see #172
- The color for the filename is now the default foreground color. The colors for the grid and the line numbers is now determined from the syntax highlighting theme, which now also works for light backgrounds, see #178.
## Bugfixes
- Escape Sequences get partially stripped, see #182 (@eth-p)
- Use separate Git repository for snapshot testing, see #165 and #161
- Markdown breaking on JavaScript, see #183
## Other
- Binaries for armv7 are now provided, see #196
- `bat` is now in the official [Arch package repositories](https://www.archlinux.org/packages/community/x86_64/bat/).
- Optimizations in the RGB => 8-bit conversion (@mina86)
# v0.4.1
(this is just a small bugfix release, see 0.4.0 for all features and changes)
## Bugfixes
- Fix problem with `cargo test` when `bat` is not checked out in a Git repository, see #161
# v0.4.0
## Features
* Support for line-wrapping, see #54 and #102 (@eth-p)
* New and updated `--style` parameter, see #74 and README (@pitkley)
* Added `--theme` and `--list-themes` options, see #89 (@rleungx)
* Added syntax highlighting for: Julia (@iamed2), Dockerfiles, VimL, CMake, INI, Less
* Added a few popular Sublime Text highlighting themes, see #133
* Support for bold, italic and underline font styles, see #96
* Support for 32bit systems is now available, see #84
* Added `-u` and `-n` options, see #134
* ANSI color support on Windows 10
## Changes
* The customization folder for own syntaxes has been renamed from `syntax` to `syntaxes`, see README.
* Changed Markdown syntax to the default Sublime Text syntax, see #157
* Sorted language listing (@rleungx)
* Command line arguments like `--theme` or `--color` can now override themselves.
* Improved `--help` text.
## Bugfixes
- Fixed crash for (really) small terminal sizes, see #117 (@eth-p)
- Syntax detection for `.bashrc`, `CMakeLists.txt` etc., see #100
- Properly handle lines with invalid UTF-8, see #7 (@BrainMaestro)
- Better error handling, see #17 (@rleungx and @sharkdp)
- Proper handling of UTF-8 characters in `less`, see #98 (@ghuls)
- Build fix on nightly, see #148 (@tathanhdinh)
## Other
- [Comparison with alternative projects](https://github.com/sharkdp/bat/blob/master/doc/alternatives.md).
- New "bat" logo in the README, see #119 (@jraulhernandezi)
- Output test cases (@BrainMaestro)
- Lots of great refactoring work (@BrainMaestro)
# v0.3.0
## Features
* Automatic paging by integrating with `less`, see #29 (@BrainMaestro)
* Added support for reading from standard input, see #2
* Added support for writing to non-interactive terminals (pipes, files, ..); new
`--color=auto/always/never` option, see #26 (@BrainMaestro)
* Added `--list-languages` option to print all available syntaxes, see #69 (@connorkuehl)
* New option to specify the syntax via `-l`/`--language`, see #19 (@BrainMaestro)
* New option to control the output style (`--style`), see #5 (@nakulcg)
* Added syntax highlighting support for TOML files, see #37
## Changes
* The `init-cache` sub-command has been removed. The cache can now be controlled via
`bat cache`. See `bat cache -h` for all available commands.
## Bug fixes
* Get git repository from file path instead of current directory, see #22 (@nakulcg)
* Process substitution can now be used with bat (`bat <(echo a) <(echo b)`), see #80
## Thanks
I'd like to say a big THANK YOU to all contributors and everyone that has given us
some form of feedback.
Special thanks go to @BrainMaestro for his huge support with new features, bug reports
and code reviews!
# v0.2.3
- Added a new statically linked version of bat (`..-musl-..`)
# v0.2.2
- Remove openssl dependency completely, see #30.
# v0.2.1
- Added Elixir syntax, see #25.
- Use libcurl-openssl instead of libcurl-gnutls, see #30.
# v0.2.0
- Support for custom syntaxes, add 'Markdown extended' theme
- Bugfix: Git modifications not shown from child folder
# v0.1.0
Initial release
Generated
+258 -218
View File
File diff suppressed because it is too large Load Diff
+37 -14
View File
@@ -7,7 +7,7 @@ license = "MIT/Apache-2.0"
name = "bat"
readme = "README.md"
repository = "https://github.com/sharkdp/bat"
version = "0.13.0"
version = "0.15.1"
exclude = [
"assets/syntaxes/*",
"assets/themes/*",
@@ -15,48 +15,71 @@ exclude = [
build = "build.rs"
edition = '2018'
[features]
default = ["application"]
# Feature required for bat the application. Should be disabled when depending on
# bat as a library.
application = [
"atty",
"clap",
"dirs",
"git",
"lazy_static",
"liquid",
"paging",
"wild",
"regex-onig",
]
git = ["git2"] # Support indicating git modifications
paging = ["shell-words"] # Support applying a pager on the output
regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine
regex-fancy = ["syntect/regex-fancy"] # Use the rust-only "fancy-regex" engine
[dependencies]
atty = "0.2.14"
atty = { version = "0.2.14", optional = true }
ansi_term = "^0.12.1"
ansi_colours = "^1.0"
console = "0.10"
dirs = "2.0"
lazy_static = "1.4"
wild = "2.0"
console = "0.11.2"
dirs = { version = "2.0", optional = true }
lazy_static = { version = "1.4", optional = true }
wild = { version = "2.0", optional = true }
content_inspector = "0.2.4"
encoding = "0.2"
shell-words = "0.1.0"
shell-words = { version = "0.1.0", optional = true }
unicode-width = "0.1.7"
globset = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8"
semver = "0.9"
[dependencies.git2]
version = "0.13"
optional = true
default-features = false
features = []
[dependencies.syntect]
version = "3.3.0"
version = "4.1.0"
default-features = false
features = ["parsing", "yaml-load", "dump-load", "dump-create"]
[dependencies.clap]
version = "2.33"
optional = true
default-features = false
features = ["suggestions", "color", "wrap_help"]
[dependencies.error-chain]
version = "0.12"
default-features = false
features = []
[dev-dependencies]
tempdir = "0.3"
assert_cmd = "0.12.0"
assert_cmd = "1.0.1"
[build-dependencies]
clap = "2.33"
liquid = "0.19"
lazy_static = "1.4"
clap = { version = "2.33", optional = true }
liquid = { version = "0.20", optional = true }
[profile.release]
lto = true
codegen-units = 1
+23 -11
View File
@@ -157,7 +157,7 @@ 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).
Note that the [Manpage syntax](assets/syntaxes/Manpage.sublime-syntax) is developed in this repository and still needs some work.
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`
@@ -171,16 +171,23 @@ The [`prettybat`](https://github.com/eth-p/bat-extras/blob/master/doc/prettybat.
### On Ubuntu
*... and other Debian-based Linux distributions.*
You can install [the Ubuntu `bat` package](https://packages.ubuntu.com/eoan/bat) or [the Debian `bat` package](https://packages.debian.org/sid/bat) since Ubuntu Eoan 19.10 or Debian unstable sid.
`bat` is making its way through the [Ubuntu](https://packages.ubuntu.com/eoan/bat) and
[Debian](https://packages.debian.org/sid/bat) package release process, and is available
for Ubuntu as of Eoan 19.10. On Debian `bat` is currently only available on the unstable
"Sid" branch.
If your Ubuntu/Debian installation is new enough you can simply run:
```bash
apt install bat
```
If you want to run the latest release of bat or if you are on older versions of Ubuntu/Debian, download the latest `.deb` package from the [release page](https://github.com/sharkdp/bat/releases)
and install it via:
If the package has not yet been promoted to your Ubuntu/Debian installation, or you want
the most recent release of `bat`, download the latest `.deb` package from the
[release page](https://github.com/sharkdp/bat/releases) and install it via:
```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture
sudo dpkg -i bat_0.15.1_amd64.deb # adapt version number and architecture
```
### On Alpine Linux
@@ -341,11 +348,11 @@ binaries are also available: look for archives with `musl` in the file name.
### From source
If you want to build `bat` from source, you need Rust 1.36 or
If you want to build `bat` from source, you need Rust 1.40 or
higher. You can then use `cargo` to build everything:
```bash
cargo install bat
cargo install --locked bat
```
On some platforms, you might need to install `llvm` and/or `libclang-dev`.
@@ -464,8 +471,8 @@ it should work out of the box.
### Dark mode
If you make use of the dark mode feature in macOS, you might want to configure `bat` to use a different
theme based on the OS theme. The following snippet uses the `default` theme when in the light mode
and the `GitHub` theme when in the dark mode.
theme based on the OS theme. The following snippet uses the `default` theme when in the _dark mode_
and the `GitHub` theme when in the _light mode_.
```bash
alias cat="bat --theme=\$(defaults read -globalDomain AppleInterfaceStyle &> /dev/null && echo default || echo GitHub)"
@@ -485,6 +492,11 @@ non-default location of the configuration file:
export BAT_CONFIG_PATH="/path/to/bat.conf"
```
A default configuration file can be created with the `--generate-config-file` option.
```bash
bat --generate-config-file
```
### Format
The configuration file is a simple list of command line arguments. Use `bat --help` to see a full list of possible options and values. In addition, you can add comments by prepending a line with the `#` character.
@@ -594,11 +606,11 @@ cargo build --bins
cargo test
# Install (release version)
cargo install
cargo install --locked
# Build a bat binary with modified syntaxes and themes
bash assets/create.sh
cargo install -f
cargo install --locked --force
```
## Maintainers
+1 -1
View File
@@ -8,7 +8,7 @@ environment:
install:
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init.exe -y --default-host %TARGET%
- rustup-init.exe -y --default-host %TARGET% --profile minimal
- set PATH=%PATH%;C:\cargo\bin
- rustc -Vv
- cargo -V
+3
View File
@@ -39,3 +39,6 @@ The following files have been manually modified after converting from a `.tmLang
* `Assembly (x86_64)` has been manually added from https://github.com/13xforever/x86-assembly-textmate-bundle due to `git clone` recursion problems
* `Nim.sublime-syntax` has been added manually from https://github.com/getzola/zola/blob/master/sublime_syntaxes/Nim.sublime-syntax as there was no suitable Git repository for it. The original syntax seems to originate from https://github.com/Varriount/NimLime
* `Rego.sublime-syntax` has been added manually from https://github.com/open-policy-agent/opa/blob/master/misc/syntax/sublime/rego.sublime-syntax
as it is not kept in a standalone repository. The file is generated from
https://github.com/open-policy-agent/opa/blob/master/misc/syntax/textmate/Rego.tmLanguage
+16 -6
View File
@@ -42,12 +42,22 @@ fi
# Always remove the local cache to avoid any confusion
bat cache --clear
# TODO: Remove this (and the reverse part below) when
# https://github.com/trishume/syntect/issues/222 has been fixed
JAVADOC_FILE="${ASSET_DIR}/syntaxes/01_Packages/Java/JavaDoc.sublime-syntax"
JAVADOC_PATCH="${ASSET_DIR}/JavaDoc.sublime-syntax.patch"
patch "$JAVADOC_FILE" "$JAVADOC_PATCH"
# TODO:
# - Remove the JavaDoc patch once https://github.com/trishume/syntect/issues/222 has been fixed
# - Remove the C# patch once https://github.com/sublimehq/Packages/pull/2331 has been merged
(
cd "$ASSET_DIR"
for patch in patches/*.patch; do
patch --strip=0 < "$patch"
done
)
bat cache --build --blank --source="$ASSET_DIR" --target="$ASSET_DIR"
patch -R "$JAVADOC_FILE" "$JAVADOC_PATCH"
(
cd "$ASSET_DIR"
for patch in patches/*.patch; do
patch --strip=0 --reverse < "$patch"
done
)
+13
View File
@@ -0,0 +1,13 @@
diff --git syntaxes/01_Packages/C#/C#.sublime-syntax syntaxes/01_Packages/C#/C#.sublime-syntax
index ed494f8b..01b710e8 100644
--- syntaxes/01_Packages/C#/C#.sublime-syntax
+++ syntaxes/01_Packages/C#/C#.sublime-syntax
@@ -1312,7 +1312,7 @@ contexts:
2: punctuation.separator.cs
3: punctuation.section.brackets.end.cs
4: keyword.operator.pointer.cs
- - match: \((?=(?:[^,)(]*|\([^\)]*\))*,)
+ - match: \((?=(?:[^,)(]|\([^\)]*\))*,)
scope: punctuation.section.group.begin.cs
push:
- meta_scope: meta.group.tuple.cs
+81
View File
@@ -0,0 +1,81 @@
diff --git syntaxes/01_Packages/Makefile/Makefile.sublime-syntax syntaxes/01_Packages/Makefile/Makefile.sublime-syntax
index 3cc3a97e..0c7a3f24 100644
--- syntaxes/01_Packages/Makefile/Makefile.sublime-syntax
+++ syntaxes/01_Packages/Makefile/Makefile.sublime-syntax
@@ -44,64 +44,50 @@ variables:
# variable substitutions anywhere. We try to remedy this by hacking in a
# regex that matches up to four levels of nested parentheses, and ignores
# whatever's inside the parentheses.
- nps: '[^()]*'
- open: '(?:\('
- close: '\))?' # ignore this invalid.illegal
+ nps_unnested: '[^()]*'
+ nps: '[^()]*(?=[()])'
+ open: '(?:{{nps}}\('
+ close: '\){{nps_unnested}})?' # ignore this invalid.illegal
just_eat: | # WARNING: INSANITY FOLLOWS!
- (?x) # ignore whitespace in this regex
- {{nps}} # level 0
+ (?x)(?: # ignore whitespace in this regex
{{open}} # start level 1 __
- {{nps}} # level 1 _______ /*_>-<
{{open}} # start level 2 ___/ _____ \__/ /
- {{nps}} # level 2 <____/ \____/
{{open}} # start level 3 is like snek... (by Valerie Haecky)
- {{nps}} # level 3
{{open}} # start level 4
{{nps}} # level 4
{{close}} # end level 4
- {{nps}} # level 3
{{close}} # end level 3
- {{nps}} # level 2
{{open}} # start level 3
- {{nps}} # level 3
{{open}} # start level 4
{{nps}} # level 4
{{close}} # end level 4
- {{nps}} # level 3
{{close}} # end level 3
- {{nps}} # level 2
+ {{nps}}
{{close}} # end level 2
- {{nps}} # level 1
{{open}} # start level 2
- {{nps}} # level 2
{{open}} # start level 3
- {{nps}} # level 3
{{open}} # start level 4
{{nps}} # level 4
{{close}} # end level 4
- {{nps}} # level 3
+ {{nps}}
{{close}} # end level 3
- {{nps}} # level 2
{{open}} # start level 3
- {{nps}} # level 3
{{open}} # start level 4
{{nps}} # level 4
{{close}} # end level 4
- {{nps}} # level 3
+ {{nps}}
{{close}} # end level 3
- {{nps}} # level 2
{{open}} # start level 3
- {{nps}} # level 3
{{open}} # start level 4
{{nps}} # level 4
{{close}} # end level 4
- {{nps}} # level 3
+ {{nps}}
{{close}} # end level 3
- {{nps}} # level 2
+ {{nps}}
{{close}} # end level 2
- {{nps}} # level 1
+ {{nps}}
{{close}} # end level 1
- {{nps}} # level 0
+ |{{nps_unnested}})
rule_lookahead: '{{just_eat}}{{ruleassign}}{{just_eat}}'
var_lookahead_base: '{{just_eat}}({{varassign}}|{{shellassign}}){{just_eat}}'
+168
View File
@@ -0,0 +1,168 @@
diff --git syntaxes/01_Packages/Markdown/Markdown.sublime-syntax syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
index 19dc685d..6afd87ae 100644
--- syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
+++ syntaxes/01_Packages/Markdown/Markdown.sublime-syntax
@@ -24,7 +24,6 @@ variables:
)
[ \t]*$ # followed by any number of tabs or spaces, followed by the end of the line
)
- setext_escape: ^(?=\s{0,3}(?:---+|===+)\s*$)
block_quote: (?:[ ]{,3}>(?:.|$)) # between 0 and 3 spaces, followed by a greater than sign, followed by any character or the end of the line
atx_heading: (?:[#]{1,6}\s*) # between 1 and 6 hashes, followed by any amount of whitespace
indented_code_block: (?:[ ]{4}|\t) # 4 spaces or a tab
@@ -277,69 +276,40 @@ contexts:
8: markup.underline.link.markdown
push: [link-ref-def, after-link-title, link-title]
- match: '^(?=\S)(?![=-]{3,}\s*$)'
- branch_point: heading2-branch
- branch:
- - not-heading2
- - heading2
-
- not-paragraph:
- - match: |-
- (?x) # pop out of this context when one of the following conditions are met:
- ^(?:
- \s*$ # the line is blank (or only contains whitespace)
- | (?=
- {{block_quote}} # a block quote begins the line
- | [ ]{,3}[*+-][ ] # an unordered list item begins the line
- | [ ]{,3}1[.][ ] # an ordered list item with number "1" begins the line
- | \# # an ATX heading begins the line
- | [ ]{,3}<( # all types of HTML blocks except type 7 may interrupt a paragraph
- {{html_tag_block_end_at_close_tag}} # 1
- | !-- # 2
- | \? # 3
- | ![A-Z] # 4
- | !\[CDATA\[ # 5
- | {{html_tag_block_end_at_blank_line}} # 6
+ push:
+ - meta_scope: meta.paragraph.markdown
+ - match: |-
+ (?x) # pop out of this context when one of the following conditions are met:
+ ^(?:
+ \s*$ # the line is blank (or only contains whitespace)
+ | (?=
+ {{block_quote}} # a block quote begins the line
+ | [ ]{,3}[*+-][ ] # an unordered list item begins the line
+ | [ ]{,3}1[.][ ] # an ordered list item with number "1" begins the line
+ | \# # an ATX heading begins the line
+ | [ ]{,3}<( # all types of HTML blocks except type 7 may interrupt a paragraph
+ {{html_tag_block_end_at_close_tag}} # 1
+ | !-- # 2
+ | \? # 3
+ | ![A-Z] # 4
+ | !\[CDATA\[ # 5
+ | {{html_tag_block_end_at_blank_line}} # 6
+ )
)
)
- )
- pop: true
-
- not-heading2:
- - include: not-paragraph
- - match: (?=\S)
- branch_point: heading1-branch
- branch:
- - paragraph
- - heading1
- - match: ''
- pop: true
-
- paragraph:
- - meta_scope: meta.paragraph.markdown
- - match: ^\s{0,3}===+\s*$
- fail: heading1-branch
- - match: ^\s{0,3}---+\s*$
- fail: heading2-branch
- - include: not-paragraph
- - include: inline-bold-italic-linebreak
- - include: scope:text.html.basic
-
- heading1:
- - meta_scope: markup.heading.1.markdown
- - include: inline-bold-italic-linebreak
- - match: '^[ \t]{0,3}(={3,})(?=[ \t]*$)'
- captures:
- 1: markup.heading.1.setext.markdown punctuation.definition.heading.setext.markdown
- pop: true
-
- heading2:
- - meta_scope: markup.heading.2.markdown
- - include: inline-bold-italic-linebreak
- - match: '^[ \t]{0,3}(-{3,})(?=[ \t]*$)'
- captures:
- 1: markup.heading.2.setext.markdown punctuation.definition.heading.setext.markdown
- pop: true
-
+ pop: true
+ - include: inline-bold-italic-linebreak
+ - include: scope:text.html.basic
+ - match: '^(={3,})(?=[ \t]*$)'
+ scope: markup.heading.1.setext.markdown
+ captures:
+ 1: punctuation.definition.heading.setext.markdown
+ pop: true
+ - match: '^(-{3,})(?=[ \t]*$)'
+ scope: markup.heading.2.setext.markdown
+ captures:
+ 1: punctuation.definition.heading.setext.markdown
+ pop: true
link-ref-def:
- meta_scope: meta.link.reference.def.markdown
- match: ''
@@ -430,8 +400,6 @@ contexts:
push:
- meta_scope: markup.bold.markdown
- meta_content_scope: markup.italic.markdown
- - match: '{{setext_escape}}'
- pop: true
- match: |-
(?x)
[ \t]*\*{4,} # if there are more than 3 its not applicable to be bold or italic
@@ -446,8 +414,6 @@ contexts:
scope: punctuation.definition.bold.end.markdown
set:
- meta_content_scope: markup.italic.markdown
- - match: '{{setext_escape}}'
- pop: true
- match: |-
(?x)
[ \t]*\*{3,} # if there are more than 3 its not applicable to be bold or italic
@@ -463,8 +429,6 @@ contexts:
scope: punctuation.definition.italic.end.markdown
set:
- meta_content_scope: markup.bold.markdown
- - match: '{{setext_escape}}'
- pop: true
- match: |-
(?x)
[ \t]*\*{3,} # if there are more than 3 its not applicable to be bold or italic
@@ -727,8 +691,6 @@ contexts:
scope: punctuation.definition.italic.begin.markdown
push:
- meta_scope: markup.italic.markdown
- - match: '{{setext_escape}}'
- pop: true
- match: |-
(?x)
[ \t]*\*{4,} # if there are more than 3 its not applicable to be bold or italic
@@ -745,8 +707,6 @@ contexts:
scope: punctuation.definition.italic.begin.markdown
push:
- meta_scope: markup.italic.markdown
- - match: '{{setext_escape}}'
- pop: true
- match: |-
(?x)
[ \t]*_{4,} # if there are more than 3 its not applicable to be bold or italic
@@ -773,8 +733,6 @@ contexts:
- include: bold-italic-trailing
bold-italic-trailing:
- include: scope:text.html.basic
- - match: '{{setext_escape}}'
- pop: true
- match: ^\s*$\n?
scope: invalid.illegal.non-terminated.bold-italic.markdown
pop: true
+13
View File
@@ -0,0 +1,13 @@
diff --git syntaxes/01_Packages/Rust/Rust.sublime-syntax syntaxes/01_Packages/Rust/Rust.sublime-syntax
index 3c354486..24727a4e 100644
--- syntaxes/01_Packages/Rust/Rust.sublime-syntax
+++ syntaxes/01_Packages/Rust/Rust.sublime-syntax
@@ -907,6 +907,8 @@ contexts:
- include: type-any-identifier
- match: ':'
scope: punctuation.separator.rust
+ - match: (?=;)
+ pop: true
fn-body:
- meta_scope: meta.function.rust
BIN
View File
Binary file not shown.
+103
View File
@@ -0,0 +1,103 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: QML
file_extensions:
- qml
- qmlproject
scope: source.qml
contexts:
main:
- match: /\*(?!/)
comment: Block comment.
push:
- meta_scope: comment.block.documentation.qml
- match: \*/
pop: true
- match: //.*$
comment: Line comment.
scope: comment.line.double-slash.qml
- match: \b(import)\s+
comment: import statement.
captures:
1: keyword.other.import.qml
push:
- meta_scope: meta.import.qml
- match: $
pop: true
- match: '([\w\d\.]+)\s+(\d+\.\d+)(?:\s+(as)\s+([A-Z][\w\d]*))?'
comment: "import Namespace VersionMajor.VersionMinor [as SingletonTypeIdentifier]"
scope: meta.import.namespace.qml
captures:
1: entity.name.class.qml
2: constant.numeric.qml
3: keyword.other.import.qml
4: entity.name.class.qml
- match: '(\"[^\"]+\")(?:\s+(as)\s+([A-Z][\w\d]*))?'
comment: "import <string> [as Script]"
scope: meta.import.dirjs.qml
captures:
1: string.quoted.double.qml
2: keyword.other.import.qml
3: entity.name.class.qml
- match: '\b[A-Z]\w*\b'
comment: Capitalized word (class or enum).
scope: support.class.qml
- match: '(((^|\{)\s*)|\b)on[A-Z]\w*\b'
comment: onSomething - handler.
scope: support.class.qml
- match: '(?:^|\{)\s*(id)\s*\:\s*([^;\s]+)\b'
comment: "id: <something>"
scope: meta.id.qml
captures:
1: keyword.other.qml
2: storage.modifier.qml
- match: '^\s*(?:(default|readonly)\s+)?(property)\s+(?:(alias)|([\w\<\>]+))\s+(\w+)'
comment: property definition.
scope: meta.propertydef.qml
captures:
1: keyword.other.qml
2: keyword.other.qml
3: keyword.other.qml
4: storage.type.qml
5: entity.other.attribute-name.qml
- match: \b(signal)\s+(\w+)\s*
comment: "signal <signalName>[([<type> <parameter>[, ...]])]"
captures:
1: keyword.other.qml
2: support.function.qml
push:
- meta_scope: meta.signal.qml
- match: ;|(?=/)|$
pop: true
- match: (\w+)\s+(\w+)
scope: meta.signal.parameters.qml
captures:
1: storage.type.qml
2: variable.parameter.qml
- match: (?:\b|\s+)(?:(true|false|null|undefined)|(var|void)|(on|as|enum|connect|break|case|catch|continue|debugger|default|delete|do|else|finally|for|if|in|instanceof|new|return|switch|this|throw|try|typeof|while|with))\b
comment: js keywords.
scope: meta.keyword.qml
captures:
1: constant.language.qml
2: storage.type.qml
3: keyword.control.qml
- match: '\b(function)\s+([\w_]+)\s*(?=\()'
comment: function definition.
scope: meta.function.qml
captures:
1: storage.type.qml
2: entity.name.function.untitled
- match: '\b[\w_]+\s*(?=\()'
comment: function call.
scope: support.function.qml
- match: '(?:^|\{|;)\s*[a-z][\w\.]*\s*(?=\:)'
comment: "property (property: <something>)."
scope: entity.other.attribute-name.qml
- match: (?<=\.)\b\w*
comment: property of the variable (name.property).
scope: entity.other.attribute-name.qml
- match: '\b([a-z_]\w*)\b'
comment: All non colored words are assumed to be variables.
scope: variable.parameter
- include: scope:source.js
+97
View File
@@ -0,0 +1,97 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Rego
file_extensions:
- rego
scope: source.rego
contexts:
main:
- include: comment
- include: keyword
- include: operator
- include: head
- include: term
comment:
- match: (#).*$\n?
scope: comment.line.number-sign.rego
captures:
1: punctuation.definition.comment.rego
call:
- match: '([a-zA-Z_][a-zA-Z0-9_]*)\('
scope: meta.function-call.rego
captures:
1: support.function.any-method.rego
constant:
- match: \b(?:true|false|null)\b
scope: constant.language.rego
head:
- match: "^([[:alpha:]_][[:alnum:]_]*)"
captures:
1: entity.name.function.declaration
push:
- meta_scope: meta.function.rego
- match: '(=|{|\n)'
pop: true
- include: term
keyword:
- match: (^|\s+)(?:(default|not|package|import|as|with|else|some))\s+
scope: keyword.other.rego
number:
- match: |-
(?x: # turn on extended mode
-? # an optional minus
(?:
0 # a zero
| # ...or...
[1-9] # a 1-9 character
\d* # followed by zero or more digits
)
(?:
(?:
\. # a period
\d+ # followed by one or more digits
)?
(?:
[eE] # an e character
[+-]? # followed by an option +/-
\d+ # followed by one or more digits
)? # make exponent optional
)? # make decimal portion optional
)
scope: constant.numeric.rego
operator:
- match: \=|\!\=|>|<|<\=|>\=|\+|-|\*|%|/|\||&|:\=
scope: keyword.operator.comparison.rego
string:
- match: '"'
captures:
0: punctuation.definition.string.begin.rego
push:
- meta_scope: string.quoted.double.rego
- match: '"'
captures:
0: punctuation.definition.string.end.rego
pop: true
- match: |-
(?x: # turn on extended mode
\\ # a literal backslash
(?: # ...followed by...
["\\/bfnrt] # one of these characters
| # ...or...
u # a u
[0-9a-fA-F]{4} # and four hex digits
)
)
scope: constant.character.escape.rego
- match: \\.
scope: invalid.illegal.unrecognized-string-escape.rego
term:
- include: constant
- include: string
- include: number
- include: call
- include: variable
variable:
- match: '\b[[:alpha:]_][[:alnum:]_]*\b'
scope: meta.identifier.rego
+711
View File
@@ -0,0 +1,711 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: Stylus
file_extensions:
- styl
- stylus
scope: source.stylus
contexts:
main:
- include: comments
- match: '^\s*(@(?:import|charset|css|font-face|(?:-webkit-)?keyframes)(?:\s+([\w-]+))?)\b'
captures:
1: keyword.control.at-rule.other.stylus
2: variable.other.animation-name.stylus
push:
- match: '$|;|(?=\{)'
pop: true
- include: string-quoted
- match: ^\s*(@media)\s*
captures:
1: keyword.control.at-rule.media.stylus
push:
- match: '$|(?=\{)'
pop: true
- include: media-query
- match: |-
(?x)
(?<=^|;|})
\s*
(?=
[\[\]'".\w$-]+
\s*
([?:]?=)
(?![^\[]*\])
)
push:
- match: $|;
pop: true
- include: expression
- include: iteration
- include: conditionals
- include: return
- match: |-
(?x) # multi-line regex definition mode
^(\s*) # starts at the beginning of line
([\w$-]+) # identifier (name)
(\() # start of argument list
(?=
.*?
\)\s*\{ # we see a curly brace afterwards
) # which means this is a function definition
captures:
2: entity.name.function.stylus
3: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
- match: |-
(?x) # multi-line regex definition mode
(
(^|;) # starts at the beginning of line or at a ;
\s*
(\+?\s* # for block mixins
[\w$-]+) # identifier (name)
(\() # start of argument list
(?=
.*?
\)\s*;?\s* # if there are only spaces and semicolons
$|; # then this a
)
)
captures:
3: entity.other.attribute-name.mixin.stylus
4: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
- match: |-
(?x) # multi-line regex definition mode
(^|(?<=\*/|\}))\s*
(?=
font(?!
\s*:\s
|
-
|
.*?
(?:
\/|normal|bold|light(er?)|serif|sans|monospace|
\b\d+(?:\b|px|r?em|%)|
var\s*\(|
['"][^\]]*$
)
) | # we need to distinguish between tag and property `cursor`
cursor(?!
\s*[:;]\s
|
-
|
.*?
(?:
(?:url\s*\()|
(?:-moz-|-webkit-|-ms-)?
(?:auto|default|none|context-menu|help|pointer|progress|
wait|cell|crosshair|text|vertical-text|alias|copy|
move|no-drop|not-allowed|e-resize|n-resize|ne-resize|
nw-resize|s-resize|se-resize|sw-resize|w-resize|
ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|
row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing
normal|bold|light(er?)|serif|sans|monospace)
)
) | (
(
altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|
animateMotion|animateTransform|circle|clipPath|color-profile|
defs|desc|ellipse|feBlend|feColorMatrix|
feComponentTransfer|feComposite|feConvolveMatrix|
feDiffuseLighting|feDisplacementMap|feDistantLight|feFlood|
feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|
feMergeNode|feMorphology|feOffset|fePointLight|
feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|
font-face|font-face-format|font-face-name|font-face-src|
font-face-uri|foreignObject|g|glyph|glyphRef|hkern|image|line|
linearGradient|marker|mask|metadata|missing-glyph|mpath|path|
pattern|polygon|polyline|radialGradient|rect|set|stop|svg|
switch|symbol|text|textPath|tref|tspan|use|view|vkern|
a|abbr|acronym|address|applet|area|article|aside|audio|b|base|
basefont|bdi|bdo|bgsound|big|blink|blockquote|body|br|button|
canvas|caption|center|cite|code|col|colgroup|data|
datalist|dd|decorator|del|details|dfn|dir|div|dl|dt|element|
em|embed|fieldset|figcaption|figure|footer|form|frame|
frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|
img|input|ins|isindex|kbd|keygen|label|legend|li|link|listing|
main|map|mark|marquee|menu|menuitem|meta|meter|nav|nobr|
noframes|noscript|object|ol|optgroup|option|output|p|param|
plaintext|pre|progress|q|rp|rt|ruby|s|samp|script|section|
select|shadow|small|source|spacer|span|strike|strong|style|
sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|
thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp)
\s*([\s,.#\[]|:[^\s]|(?=\{|$))
) | (
[:~>\[*\/] # symbols but they are valid for selector
) | (
\+\s*[\w$-]+\b\s* # are an identifier starting with $
(?!\() # and they can't have anything besides
) | ( # for animtions
\d+(\.\d+)?%|(from|to)\b
) | ( # Placeholder selectors
\$[\w$-]+\b\s* # are an identifier starting with $
(?=$|\{) # and they can't have anything besides
) | ( # CSS class
\.[a-zA-Z0-9_-]+
) | ( # CSS id
\#[a-zA-Z0-9_-]+
) | ( # Reference to parent
([\w\d_-]+)? # matching any word right before &
(&) # & itself, escaped because of plist
([\w\d_-]+)? # matching any word right after &
)
)
push:
- meta_scope: meta.selector.stylus
- match: |-
|$|(?=\{\s*\}.*$)|(?=\{.*?[:;])|(?=\{)(?!.+\}.*$)
pop: true
- include: comma
- match: \d+(\.\d+)?%|from|to
scope: entity.other.animation-keyframe.stylus
- include: selector-components
- match: .
scope: entity.other.attribute-name.stylus
- match: |-
(?x) # multi-line regex definition mode
(?<=^|;|{)\s* # starts after begining of line, '{' or ';''
(?= # lookahead for
(
[a-zA-Z0-9_-] # then a letter
| # or
(\{(.*?)\}) # interpolation
| # or
(/\*.*?\*/) # comment
)+
\s*[:\s]\s* # value is separted by colon or space
(?!(\s*\{)) # if there are only spaces afterwards
(?!
[^}]*? # checking for an unclosed curly braces on this
\{ # line because if one exists it means that
[^}]* # this is a selector and not a property
($|\})
)
)
push:
- match: '(?=\}|;)|(?<!,)\s*\n'
pop: true
- include: comments
- include: interpolation
- match: '(?<!^|;|{)\s*(?:(:)|\s)'
captures:
1: punctuation.separator.key-value.stylus
push:
- match: '(;)|(?=\})|(?=(?<!\,)\s*\n)'
captures:
1: punctuation.terminator.rule.stylus
pop: true
- include: comments
- include: expression
- match: "-(moz|o|ms|webkit|khtml)-"
scope: support.type.vendor-prefix.stylus
- match: .
scope: meta.property-name.stylus support.type.property-name.stylus
- match: '@extends?\s'
captures:
0: keyword.language.stylus
push:
- match: (?=$|;)
pop: true
- include: selector-components
- include: string-quoted
- include: escape
- include: language-constants
- include: language-operators
- include: language-keywords
- include: property-reference
- include: function-call
- match: '\{'
scope: punctuation.section.start.stylus
- match: '\}'
scope: punctuation.section.end.stylus
attribute-selector:
- match: '\[(?=[^\]]*\])'
captures:
0: punctuation.definition.entity.start.stylus
push:
- meta_scope: meta.attribute-selector.stylus
- match: '\]'
captures:
0: punctuation.definition.entity.end.stylus
pop: true
- match: '(?<=\[)|(?<=\{)'
push:
- match: '(?=[|~=\]\s])'
pop: true
- include: interpolation
- match: .
captures:
0: entity.other.attribute-name.stylus
- include: interpolation
- match: "([|~]?=)"
captures:
1: keyword.operator.stylus
- include: string-quoted
- match: .
captures:
0: string.unquoted.stylus
color-values:
- match: \b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\b
scope: constant.color.w3c-standard-color-name.stylus
- match: (hsla?|rgba?)\s*(\()
captures:
1: keyword.language.function.misc.stylus
2: punctuation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: punctuation.definition.parameters.end.stylus
pop: true
- match: |-
(?x) # multi-line regex definition mode
\b
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\s*(,)\s*)
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\s*(,)\s*)
(?:0*((?:1?[0-9]{1,2})|(?:2(?:[0-4][0-9]|5[0-5])))\b)
captures:
1: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.red.stylus
2: punctuation.delimiter.comma.stylus
3: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.green.stylus
4: punctuation.delimiter.comma.stylus
5: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.blue.stylus
- match: |-
(?x) # multi-line regex definition mode
\b
((?:[0-9]{1,2}|100)%)(,) # red
\s*
((?:[0-9]{1,2}|100)%)(,) # green
\s*
((?:[0-9]{1,2}|100)%) # blue
captures:
1: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.red.stylus
2: punctuation.delimiter.comma.stylus
3: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.green.stylus
4: punctuation.delimiter.comma.stylus
5: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.blue.stylus
- match: |-
(?x) # multi-line regex definition mode
(?:\s*(,)\s*((0?\.[0-9]+)|[0-1]))?
captures:
1: punctuation.delimiter.comma.stylus
2: constant.other.color.rgb-value.stylus constant.other.color.rgb-value.alpha.stylus
- include: numeric-values
- include: numeric-values
comma:
- match: \s*,\s*
scope: punctuation.delimiter.comma.stylus
comments:
- include: single-line-comment
- match: \/\*
captures:
0: punctuation.definition.comment.stylus
push:
- meta_scope: comment.block.stylus
- match: \*\/
captures:
0: punctuation.definition.comment.stylus
pop: true
conditionals:
- match: '(^\s*|\s+)(if|unless|else)(?=[\s({]|$)\s*'
captures:
2: keyword.control.stylus
push:
- match: '(?=$|\{)'
pop: true
- include: expression
escape:
- match: \\.
scope: constant.character.escape.stylus
expression:
- include: single-line-comment
- include: comma
- include: iteration
- include: conditionals
- include: language-operators
- include: language-keywords
- include: hash-definition
- include: color-values
- include: url
- include: function-call
- include: string-quoted
- include: escape
- include: hash-access
- include: language-constants
- include: language-property-value-constants
- include: property-reference
- include: variable
function-call:
- match: '([\w$-]+)(\()'
captures:
1: entity.function-name.stylus
2: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: expression
hash-access:
- match: '(?=[\w$-]+(?:\.|\[[^\]=]*\]))'
push:
- meta_scope: meta.hash-access.stylus
- match: '(?=[^''''""\[\]\w.$-]|\s|$)'
pop: true
- match: \.
scope: punctuation.delimiter.hash.stylus
- match: '\['
scope: punctuation.definition.entity.start.stylus
- match: '\]'
scope: punctuation.definition.entity.end.stylus
- include: string-quoted
- include: variable
hash-definition:
- match: '\{'
captures:
0: punctuation.section.embedded.start.stylus
push:
- meta_scope: meta.hash.stylus
- match: '\}'
captures:
0: punctuation.section.embedded.end.stylus
pop: true
- include: single-line-comment
- match: |-
(?x)
(?:
([\w$-]+)
|
('[^']*')
|
("[^"]*")
)
\s*
(:)
\s*
captures:
1: support.type.property-name.stylus
2: string.quoted.single.stylus
3: string.quoted.double.stylus
4: punctuation.separator.key-value.stylus
push:
- match: '(;)|(?=\}|$)'
captures:
1: punctuation.terminator.statement.stylus
pop: true
- include: expression
interpolation:
- match: '\{'
captures:
0: punctuation.section.embedded.start.stylus
push:
- meta_scope: stylus.embedded.source
- match: '\}'
captures:
0: punctuation.section.embedded.end.stylus
pop: true
- include: expression
iteration:
- match: (^\s*|\s+)(for)\s+(?=.*?\s+in\s+)
captures:
2: keyword.control.stylus
push:
- match: '$|\{'
pop: true
- include: expression
language-constants:
- match: \b(true|false|null)\b
scope: constant.language.stylus
language-keywords:
- match: (\b|\s)(return|else)\b
scope: keyword.control.stylus
- match: (\b|\s)(!important|in|is defined|is a)\b
scope: keyword.other.stylus
- match: \barguments\b
scope: variable.language.stylus
language-operators:
- match: ((?:\?|:|!|~|\+|-|(?:\*)?\*|\/|%|(\.)?\.\.|<|>|(?:=|:|\?|\+|-|\*|\/|%|<|>)?=|!=)|\b(?:in|is(?:nt)?|(?<!:)not)\b)
scope: keyword.operator.stylus
language-property-value-constants:
- match: \b(absolute|all(-scroll)?|always|armenian|auto|avoid|baseline|below|bidi-override|block|bold(er)?|(border|content|padding)-box|both|bottom|break-all|break-word|capitalize|center|char|circle|cjk-ideographic|col-resize|collapse|crosshair|cursive|dashed|decimal-leading-zero|decimal|default|disabled|disc|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ellipsis|fantasy|fixed|geometricPrecision|georgian|groove|hand|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|inactive|inherit|inline-block|inline|inset|inside|inter-ideograph|inter-word|italic|justify|katakana-iroha|katakana|keep-all|left|lighter|line-edge|line-through|line|list-item|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|medium|middle|move|monospace|n-resize|ne-resize|newspaper|no-drop|no-repeat|nw-resize|none|normal|not-allowed|nowrap|oblique|optimize(Legibility|Quality|Speed)|outset|outside|overline|pointer|pre(-(wrap|line))?|progress|relative|repeat-x|repeat-y|repeat|right|ridge|row-resize|rtl|(sans-)?serif|s-resize|scroll|se-resize|separate|small-caps|solid|square|static|strict|sub|super|sw-resize|table(-(row|cell|footer-group|header-group))?|tb-rl|text-bottom|text-top|text|thick|thin|top|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|vertical(-(ideographic|text))?|visible(Painted|Fill|Stroke)?|w-resize|wait|whitespace|zero|smaller|larger|((xx?-)?(small(er)?|large(r)?))|painted|fill|stroke)\b
scope: constant.property-value.stylus
media-query:
- match: '\s*(?![{;]|$)'
push:
- meta_scope: meta.at-rule.media.stylus
- match: '\s*(?=[{;]|$)'
pop: true
- match: '(?i)\s*(only|not)?\s*(all|aural|braille|embossed|handheld|print|projection|screen|tty|tv)?(?![\w\d$-]+)'
captures:
1: keyword.operator.logic.media.stylus
2: support.constant.media.stylus
push:
- match: '\s*(?:(,)|(?=[{;]|$))'
pop: true
- include: media-query-list
- include: variable
media-query-list:
- match: \s*(and)?\s*(\()\s*
captures:
1: keyword.operator.logic.media.stylus
2: punctuation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: punctuation.definition.parameters.end.stylus
pop: true
- include: media-query-properties
- include: numeric-values
media-query-properties:
- match: \s*:\s*
captures:
0: punctuation.separator.key-value.stylus
- match: |-
(?x)
(
((min|max)-)?
(
((device-)?(height|width|aspect-ratio))|
(color(-index)?)|monochrome|resolution
)
)|grid|scan|orientation
captures:
0: support.type.property-name.media.stylus
- match: \b(portrait|landscape|progressive|interlace)\b
captures:
1: support.constant.property-value.stylus
numeric-values:
- match: |-
(?x) # multi-line regex definition mode
(\#)(?:
([0-9a-fA-F])
([0-9a-fA-F])
([0-9a-fA-F])
([0-9a-fA-F])?
| ([0-9a-fA-F]{2})
([0-9a-fA-F]{2})
([0-9a-fA-F]{2})
([0-9a-fA-F]{2})?
)\b
scope: constant.other.color.rgb-value.stylus
captures:
1: punctuation.definition.constant.stylus
2: constant.other.color.rgb-value.red.stylus
3: constant.other.color.rgb-value.green.stylus
4: constant.other.color.rgb-value.blue.stylus
5: constant.other.color.rgb-value.alpha.stylus
6: constant.other.color.rgb-value.red.stylus
7: constant.other.color.rgb-value.green.stylus
8: constant.other.color.rgb-value.blue.stylus
9: constant.other.color.rgb-value.alpha.stylus
- match: |-
(?x) # multi-line regex definition mode
(?:-|\+)? # negative / positive
(?:
(?:
[0-9]+ # integer part
(?:\.[0-9]+)? # fraction
) |
(?:\.[0-9]+) # fraction without leading zero
)
((?: # units
px|pt|ch|cm|mm|in|
r?em|ex|pc|vw|vh|vmin|vmax|deg|
g?rad|turn|dpi|dpcm|dppx|m?s|k?Hz
)\b|%)?
scope: constant.numeric.stylus
captures:
1: keyword.other.unit.stylus
property-reference:
- match: "@[a-z-]+"
scope: variable.other.property.stylus
pseudo:
- match: (:)(active|checked|default|disabled|empty|enabled|first-child|first-of-type|first|fullscreen|focus|hover|indeterminate|in-range|invalid|last-child|last-of-type|left|link|only-child|only-of-type|optional|out-of-range|read-only|read-write|required|right|root|scope|target|valid|visited)\b
scope: entity.other.attribute-name.pseudo-class.stylus
captures:
1: puncutation.definition.entity.stylus
- match: (:?:)(before|after)\b
scope: entity.other.attribute-name.pseudo-element.stylus
captures:
1: puncutation.definition.entity.stylus
- match: (::)(first-letter|first-number|selection)\b
scope: entity.other.attribute-name.pseudo-element.stylus
captures:
1: puncutation.definition.entity.stylus
- match: ((:)dir)\s*(?:(\()(ltr|rtl)?(\)))?
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
4: constant.language.stylus
5: puncutation.definition.parameters.end.stylus
- match: ((:)lang)\s*(?:(\()(\w+(-\w+)?)?(\)))?
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
4: constant.language.stylus
5: puncutation.definition.parameters.end.stylus
- include: pseudo-nth
- include: pseudo-not
pseudo-not:
- match: ((:)not)\s*(\()
captures:
1: entity.other.attribute-name.pseudo-element.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: puncutation.definition.parameters.end.stylus
pop: true
- include: selector-components
pseudo-nth:
- match: ((:)(?:nth-child|nth-last-child|nth-of-type|nth-last-of-type|nth-match|nth-last-match|nth-column|nth-last-column))\s*(\()
captures:
1: entity.other.attribute-name.pseudo-class.stylus
2: puncutation.definition.entity.stylus
3: puncutation.definition.parameters.start.stylus
push:
- match: \)
captures:
0: puncutation.definition.parameters.end.stylus
pop: true
- include: language-operators
- include: interpolation
- match: \b(odd|even)\b
scope: constant.language.stylus
- match: \b(\d+)?n\b
scope: variable.language.stylus
captures:
1: constant.numeric.stylus
- match: \d+
scope: constant.numeric.stylus
return:
- match: ^\s*(return)
captures:
1: keyword.control.stylus
push:
- match: (;)|(?=$)
captures:
1: punctuation.terminator.statement.stylus
pop: true
- include: expression
selector-components:
- include: comments
- include: interpolation
- include: attribute-selector
- include: pseudo
- match: '\$[\w$-]+\b'
scope: entity.other.placeholder.stylus
- match: "[:~>]"
scope: keyword.operator.selector.stylus
- match: |-
(?x) # multi-line regex definition mode
\b(
altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|
animateMotion|animateTransform|circle|clipPath|color-profile|
defs|desc|ellipse|feBlend|feColorMatrix|
feComponentTransfer|feComposite|feConvolveMatrix|
feDiffuseLighting|feDisplacementMap|feDistantLight|feFlood|
feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|
feMergeNode|feMorphology|feOffset|fePointLight|
feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|
font-face|font-face-format|font-face-name|font-face-src|
font-face-uri|foreignObject|g|glyph|glyphRef|hkern|image|line|
linearGradient|marker|mask|metadata|missing-glyph|mpath|path|
pattern|polygon|polyline|radialGradient|rect|set|stop|svg|
switch|symbol|text|textPath|tref|tspan|use|view|vkern|
a|abbr|acronym|address|applet|area|article|aside|audio|b|base|
basefont|bdi|bdo|bgsound|big|blink|blockquote|body|br|button|
canvas|caption|center|cite|code|col|colgroup|content|data|
datalist|dd|decorator|del|details|dfn|dir|div|dl|dt|element|
em|embed|fieldset|figcaption|figure|font|footer|form|frame|
frameset|h1|h2|h3|h4|h5|h6|head|header|hgroup|hr|html|i|iframe|
img|input|ins|isindex|kbd|keygen|label|legend|li|link|listing|
main|map|mark|marquee|menu|menuitem|meta|meter|nav|nobr|
noframes|noscript|object|ol|optgroup|option|output|p|param|
plaintext|pre|progress|q|rp|rt|ruby|s|samp|script|section|
select|shadow|small|source|spacer|span|strike|strong|style|
sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|
thead|time|title|tr|track|tt|u|ul|var|video|wbr|xmp
)\b
scope: entity.name.tag.stylus
- match: '\.[a-zA-Z0-9_-]+'
scope: entity.other.attribute-name.class.stylus
- match: "#[a-zA-Z0-9_-]+"
scope: entity.other.attribute-name.id.stylus
- match: |-
(?x) # multi-line regex definition mode
([\w\d_-]+)? # matching any word right before &
(&) # & itself, escaped because of plist
([\w\d_-]+)? # matching any word right after &
captures:
1: entity.other.attribute-name.stylus
2: variable.language.stylus
3: entity.other.attribute-name.stylus
single-line-comment:
- match: (\/\/).*$
scope: comment.line.stylus
captures:
1: punctuation.definition.comment.stylus
string-quoted:
- match: "'[^']*'"
scope: string.quoted.single.stylus
- match: '"[^"]*"'
scope: string.quoted.double.stylus
url:
- match: (url)\s*(\()
captures:
1: entity.function-name.stylus
2: punctuation.definition.parameters.start.stylus
push:
- meta_scope: meta.function-call.stylus
- match: (\))
captures:
1: punctuation.definition.parameters.end.stylus
pop: true
- include: string-quoted
- include: language-constants
- include: language-property-value-constants
- include: property-reference
- include: variable
variable:
- match: '([\w$-]+\b)'
scope: variable.other.stylus
BIN
View File
Binary file not shown.
Vendored Submodule
+1
Submodule assets/themes/Solarized added at 87e01090cf
+32 -51
View File
@@ -1,66 +1,47 @@
// 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
#[macro_use]
extern crate lazy_static;
extern crate liquid;
// 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() {}
use std::error::Error;
use std::fs;
use std::path::Path;
#[cfg(feature = "application")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::error::Error;
use std::fs;
use std::path::Path;
// Read environment variables.
lazy_static! {
pub static ref PROJECT_NAME: &'static str = option_env!("PROJECT_NAME").unwrap_or("bat");
pub static ref PROJECT_VERSION: &'static str = option_env!("CARGO_PKG_VERSION").unwrap();
pub static ref EXECUTABLE_NAME: &'static str = option_env!("PROJECT_EXECUTABLE")
.or(option_env!("PROJECT_NAME"))
.unwrap_or("bat");
}
// Read environment variables.
let project_name = option_env!("PROJECT_NAME").unwrap_or("bat");
let executable_name = option_env!("PROJECT_EXECUTABLE").unwrap_or(project_name);
static PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
fn init_template() -> liquid::value::Object {
let mut globals = liquid::value::Object::new();
/// Generates a file from a liquid template.
fn template(
variables: &liquid::Object,
in_file: &str,
out_file: impl AsRef<Path>,
) -> Result<(), Box<dyn Error>> {
let template = liquid::ParserBuilder::with_stdlib()
.build()?
.parse(&fs::read_to_string(in_file)?)?;
globals.insert(
"PROJECT_NAME".into(),
liquid::value::Value::scalar(PROJECT_NAME.to_owned()),
);
fs::write(out_file, template.render(variables)?)?;
Ok(())
}
globals.insert(
"PROJECT_EXECUTABLE".into(),
liquid::value::Value::scalar(EXECUTABLE_NAME.to_owned()),
);
globals.insert(
"PROJECT_VERSION".into(),
liquid::value::Value::scalar(PROJECT_VERSION.to_owned()),
);
globals
}
/// Generates a file from a liquid template.
fn template(
variables: &liquid::value::Object,
in_file: &str,
out_file: impl AsRef<Path>,
) -> Result<(), Box<dyn Error>> {
let template = liquid::ParserBuilder::with_liquid()
.build()?
.parse(&fs::read_to_string(in_file)?)?;
fs::write(out_file, template.render(variables)?)?;
Ok(())
}
fn main() -> Result<(), Box<dyn Error>> {
let variables = init_template();
let variables = liquid::object!({
"PROJECT_NAME": project_name,
"PROJECT_EXECUTABLE": executable_name,
"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);
std::fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
std::fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
fs::create_dir_all(out_dir.join("assets/manual")).unwrap();
fs::create_dir_all(out_dir.join("assets/completions")).unwrap();
template(
&variables,
+2 -1
View File
@@ -164,7 +164,8 @@ Package: $dpkgname
Version: $version
Section: utils
Priority: optional
Maintainer: David Peter <mail@david-peter.de>
Maintainer: $maintainer
Homepage: $homepage
Architecture: $architecture
Provides: $PROJECT_NAME
Conflicts: $conflictname
+6
View File
@@ -12,3 +12,9 @@ if [[ $TARGET != arm-unknown-linux-gnueabihf ]] && [[ $TARGET != aarch64-unknown
# Run 'bat' on its own source code and the README
cargo run --target "$TARGET" -- src/bin/bat/main.rs README.md --paging=never
fi
# Check bat-as-a-library, which has a smaller set of dependencies
cargo check --target "$TARGET" --verbose --lib --no-default-features --features regex-onig
cargo check --target "$TARGET" --verbose --lib --no-default-features --features regex-onig,git
cargo check --target "$TARGET" --verbose --lib --no-default-features --features regex-onig,paging
cargo check --target "$TARGET" --verbose --lib --no-default-features --features regex-onig,git,paging
+102 -37
View File
@@ -2,6 +2,24 @@
_modules=('system' 'bat' 'bat_config' 'bat_wrapper' 'bat_wrapper_function' 'tool')
_modules_consented=()
set -o pipefail
export LC_ALL=C
export LANG=C
BAT="bat"
if ! command -v bat &>/dev/null; then
if command -v batcat &> /dev/null; then
BAT="batcat"
else
tput setaf 1
printf "%s\n%s\n" \
"Unable to find a bat executable on your PATH." \
"Please ensure that 'bat' exists and is not named something else."
tput sgr0
exit 1
fi
fi
# -----------------------------------------------------------------------------
# Modules:
@@ -9,6 +27,7 @@ _modules_consented=()
_bat_:description() {
_collects "Version information for 'bat'."
_collects "Custom syntaxes and themes for 'bat'."
}
_bat_config_:description() {
@@ -33,59 +52,92 @@ _tool_:description() {
_collects "Version information for 'less'."
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
_bat_:run() {
_out bat --version
_out "$BAT" --version
_out env | grep '^BAT_\|^PAGER='
local cache_dir="$(bat --cache-dir)"
if [[ -f "${cache_dir}/syntaxes.bin" ]]; then
_print_command "$BAT" "--list-languages"
echo "Found custom syntax set."
fi
if [[ -f "${cache_dir}/themes.bin" ]]; then
_print_command "$BAT" "--list-themes"
echo "Found custom theme set."
fi
}
_bat_config_:run() {
if [[ -f "$(bat --config-file)" ]]; then
_out_fence cat "$(bat --config-file)";
if [[ -f "$("$BAT" --config-file)" ]]; then
_out_fence cat "$("$BAT" --config-file)"
fi
}
_bat_wrapper_:run() {
if file "$(which bat)" | grep "text executable" &>/dev/null; then
_out_fence cat "$(which bat)"
return
_bat_wrapper_:detect_wrapper() {
local bat="$1"
if file "$(which "${bat}")" | grep "text executable" &> /dev/null; then
_out_fence cat "$(which "${bat}")"
return
fi
printf "\nNo wrapper script for '%s'.\n" "${bat}"
}
_bat_wrapper_:detect_wrapper bat
if [[ "$BAT" != "bat" ]]; then
_bat_wrapper_:detect_wrapper "$BAT"
fi
printf "\nNo wrapper script.\n"
}
_bat_wrapper_function_:run() {
case "$("$SHELL" --version | head -n 1)" in
*fish*)
if "$SHELL" --login -c 'type bat' 2>&1 | grep 'function' &>/dev/null; then
_out_fence "$SHELL" --login -c 'functions bat'
fi ;;
_bat_wrapper_function_:detect_wrapper() {
local command="$1"
case "$("$SHELL" --version | head -n 1)" in
*fish*)
if "$SHELL" --login -i -c "type ${command}" 2>&1 | grep 'function' &> /dev/null; then
_out_fence "$SHELL" --login -i -c "functions ${command}"
return
fi ;;
*bash*)
if "$SHELL" --login -c 'type bat' 2>&1 | grep 'function' &>/dev/null; then
_out_fence "$SHELL" --login -c 'declare -f bat'
fi ;;
*bash* | *zsh*)
local type="$("$SHELL" --login -i -c "type ${command}" 2>&1)"
if grep 'function' <<< "$type" &> /dev/null; then
_out_fence "$SHELL" --login -i -c "declare -f ${command}"
return
elif grep 'alias' <<< "$type" &> /dev/null; then
_out_fence "$SHELL" --login -i -c "type ${command}"
return
fi ;;
*)
echo "Unable to determine if a wrapper function is set."
return ;;
esac
printf "\nNo wrapper function.\n"
*)
echo "Unable to determine if a wrapper function for '${command}' is set."
return ;;
esac
printf "\nNo wrapper function for '%s'.\n" "${command}"
}
_bat_wrapper_function_:detect_wrapper bat
_bat_wrapper_function_:detect_wrapper cat
if [[ "$BAT" != "bat" ]]; then
_bat_wrapper_function_:detect_wrapper "$BAT"
fi
}
_system_:run() {
_out uname -srm
if command -v "sw_vers" &>/dev/null; then _out sw_vers; fi
if command -v "lsb_release" &>/dev/null; then _out lsb_release -a; fi
if command -v "sw_vers" &> /dev/null; then _out sw_vers; fi
if command -v "lsb_release" &> /dev/null; then _out lsb_release -a; fi
}
_tool_:run() {
_out less --version | head -n1
}
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
@@ -109,7 +161,7 @@ _out_fence() {
}
_tput() {
tput "$@" 1>&2 2>/dev/null
tput "$@" 1>&2 2> /dev/null
}
_collects() {
@@ -120,7 +172,7 @@ _ask_module() {
_tput clear
_tput cup 0 0
cat 1>&2 <<EOF
cat 1>&2 << EOF
--------------------------------------------------------------------------------
This script runs some harmless commands to collect information about your
system and bat configuration. It will give you a small preview of the commands
@@ -135,7 +187,7 @@ EOF
_tput sgr0
"_$1_:description"
_tput sgr0
# Print preview.
_tput setaf 3
printf "\nThe following commands will be run:\n" 1>&2
@@ -143,19 +195,20 @@ EOF
declare -f "_$1_:run" \
| sed 's/^ *//; s/;$//' \
| grep '^_out[^ ]* ' \
| sed 's/^_out[^ ]* //' 1>&2
| sed 's/^_out[^ ]* //' \
| sed "s/\"\$BAT\"/$BAT/" 1>&2
# Prompt
printf "\n" 1>&2
local response
while true; do
_tput cup "$(( $(tput lines || echo 22) - 2 ))"
_tput cup "$(($( tput lines || echo 22) - 2))"
_tput el
read -er -p "Collect $(sed 's/_/ /' <<< "$1") data? [Y/n] " response
case "$response" in
Y|y|yes|'') return 0 ;;
N|n|no) return 1 ;;
*) continue
Y | y | yes | '') return 0 ;;
N | n | no) return 1 ;;
*) continue ;;
esac
done
}
@@ -166,13 +219,27 @@ _run_module() {
"_$1_:run"
}
# -----------------------------------------------------------------------------
# Functions:
# -----------------------------------------------------------------------------
# Tell the user if their executable isn't named "bat".
if [[ "$BAT" != "bat" ]] && [[ "$1" != '-y' ]]; then
trap '_tput rmcup; exit 1' INT
_tput smcup
_tput clear
_tput cup 0 0
_tput setaf 1
printf "The %s executable on your system is named '%s'.\n%s\n" "bat" "$BAT" \
"If your issue is related to installation, please check that this isn't the issue."
_tput sgr0
printf "Press any key to continue...\n"
read -rsn1
_tput rmcup
fi
# Ask for consent.
if [[ "$1" = '-y' ]]; then
if [[ "$1" == '-y' ]]; then
_modules_consented=("${_modules[@]}")
else
trap '_tput rmcup; exit 1' INT
@@ -190,5 +257,3 @@ for _module in "${_modules_consented[@]}"; do
_run_module "$_module" 2>&1
printf "\n"
done
+4 -4
View File
@@ -180,7 +180,7 @@ apt install bat
batの最新リリースを実行する場合、または Ubuntu/Debian の古いバージョンを使用している場合は、[release page](https://github.com/sharkdp/bat/releases) から最新の `.deb` パッケージをダウンロードし、
次の方法でインストールします:
```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture
sudo dpkg -i bat_0.15.1_amd64.deb # adapt version number and architecture
```
### On Alpine Linux
@@ -345,7 +345,7 @@ ansible-galaxy install aeimer.install_bat
そして `cargo` をビルドに対して使用します:
```bash
cargo install bat
cargo install --locked bat
```
一部のプラットフォームでは `llvm` および/または `libclang-dev` のインストールが必要になる場合があります。
@@ -594,11 +594,11 @@ cargo build --bins
cargo test
# Install (release version)
cargo install
cargo install --locked
# Build a bat binary with modified syntaxes and themes
bash assets/create.sh
cargo install -f
cargo install --locked --force
```
## Maintainers
+4 -4
View File
@@ -170,7 +170,7 @@ apt install bat
만약 최근 릴리즈된 bat을 사용을 원하거나 buntu/Debian 예전 버전을 사용하는 경우, [릴리즈 페이지](https://github.com/sharkdp/bat/releases)에서 다음과 같이 `.deb` 패키지를 받아 설치 할 수도 있습니다:
```bash
sudo dpkg -i bat_0.13.0_amd64.deb # adapt version number and architecture
sudo dpkg -i bat_0.15.1_amd64.deb # adapt version number and architecture
```
### On Alpine Linux
@@ -327,7 +327,7 @@ ansible-galaxy install aeimer.install_bat
`bat`의 소스를 직접 빌드하기 위해서는, Rust 1.36 이상이 필요하며 `cargo`를 이용해 빌드할 수 있습니다.
```bash
cargo install bat
cargo install --locked bat
```
일부 플랫폼에서는 `llvm` 그리고/또는 `libclang-dev` 설치가 필요할 수도 있습니다.
@@ -528,11 +528,11 @@ cargo build --bins
cargo test
# Install (release version)
cargo install
cargo install --locked
# Build a bat binary with modified syntaxes and themes
bash assets/create.sh
cargo install -f
cargo install --locked --force
```
## 메인테이너들
+18
View File
@@ -0,0 +1,18 @@
/// A program that prints its own source code using the bat library
use bat::{PagingMode, PrettyPrinter, WrappingMode};
fn main() {
PrettyPrinter::new()
.header(true)
.grid(true)
.line_numbers(true)
.use_italics(true)
// The following line will be highlighted in the output:
.highlight(line!() as usize)
.theme("1337")
.wrapping_mode(WrappingMode::Character)
.paging_mode(PagingMode::QuitIfOneScreen)
.input_file(file!())
.print()
.unwrap();
}
+8 -28
View File
@@ -1,33 +1,13 @@
/// A very simple colorized `cat` clone, using `bat` as a library.
/// See `src/bin/bat` for the full `bat` application.
use bat::{
config::{Config, InputFile, StyleComponent, StyleComponents},
Controller, HighlightingAssets,
};
use console::Term;
use std::process;
use bat::PrettyPrinter;
fn main() {
let files = std::env::args_os().skip(1).collect::<Vec<_>>();
if files.is_empty() {
eprintln!("No input files specified");
process::exit(1);
}
let config = Config {
term_width: Term::stdout().size().1 as usize,
colored_output: true,
true_color: true,
style_components: StyleComponents::new(&[
StyleComponent::Header,
StyleComponent::Grid,
StyleComponent::Numbers,
]),
files: files.iter().map(|file| InputFile::Ordinary(file)).collect(),
..Default::default()
};
let assets = HighlightingAssets::from_binary();
Controller::new(&config, &assets).run().expect("no errors");
PrettyPrinter::new()
.header(true)
.grid(true)
.line_numbers(true)
.input_files(std::env::args_os().skip(1))
.print()
.unwrap();
}
+18
View File
@@ -0,0 +1,18 @@
/// A simple program that prints its own source code using the bat library
use bat::PrettyPrinter;
fn main() {
let printer = PrettyPrinter::new();
println!("Syntaxes:");
for syntax in printer.syntaxes() {
println!("- {} ({})", syntax.name, syntax.file_extensions.join(", "));
}
println!();
println!("Themes:");
for theme in printer.themes() {
println!("- {}", theme);
}
}
+2 -16
View File
@@ -1,20 +1,6 @@
/// A simple program that prints its own source code using the bat library
use bat::{
config::{Config, InputFile},
Controller, HighlightingAssets,
};
use std::ffi::OsStr;
use bat::PrettyPrinter;
fn main() {
let path_to_this_file = OsStr::new(file!());
let config = Config {
files: vec![InputFile::Ordinary(path_to_this_file)],
colored_output: true,
true_color: true,
..Default::default()
};
let assets = HighlightingAssets::from_binary();
Controller::new(&config, &assets).run().expect("no errors");
PrettyPrinter::new().input_file(file!()).print().unwrap();
}
+35
View File
@@ -0,0 +1,35 @@
/// A program that serializes a Rust structure to YAML and pretty-prints the result
use bat::PrettyPrinter;
use serde::Serialize;
#[derive(Serialize)]
struct Person {
name: String,
height: f64,
adult: bool,
children: Vec<Person>,
}
fn main() {
let person = Person {
name: String::from("Anne Mustermann"),
height: 1.76f64,
adult: true,
children: vec![Person {
name: String::from("Max Mustermann"),
height: 1.32f64,
adult: false,
children: vec![],
}],
};
let bytes = serde_yaml::to_vec(&person).unwrap();
PrettyPrinter::new()
.language("yaml")
.line_numbers(true)
.grid(true)
.header(true)
.input_from_bytes_with_name(&bytes, "person.yaml")
.print()
.unwrap();
}
+131 -53
View File
@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::BufReader;
use std::path::Path;
@@ -7,8 +8,9 @@ use syntect::dumps::{dump_to_file, from_binary, from_reader};
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::{SyntaxReference, SyntaxSet, SyntaxSetBuilder};
use crate::errors::*;
use crate::inputfile::{InputFile, InputFileReader};
use crate::assets_metadata::AssetsMetadata;
use crate::error::*;
use crate::input::{InputReader, OpenedInput, OpenedInputKind};
use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
#[derive(Debug)]
@@ -67,8 +69,11 @@ impl HighlightingAssets {
})
}
pub fn from_cache(theme_set_path: &Path, syntax_set_path: &Path) -> Result<Self> {
let syntax_set_file = File::open(syntax_set_path).chain_err(|| {
pub fn from_cache(cache_path: &Path) -> Result<Self> {
let syntax_set_path = cache_path.join("syntaxes.bin");
let theme_set_path = cache_path.join("themes.bin");
let syntax_set_file = File::open(&syntax_set_path).chain_err(|| {
format!(
"Could not load cached syntax set '{}'",
syntax_set_path.to_string_lossy()
@@ -112,7 +117,7 @@ impl HighlightingAssets {
}
}
pub fn save_to_cache(&self, target_dir: &Path) -> Result<()> {
pub fn save_to_cache(&self, target_dir: &Path, current_version: &str) -> Result<()> {
let _ = fs::create_dir_all(target_dir);
let theme_set_path = target_dir.join("themes.bin");
let syntax_set_path = target_dir.join("syntaxes.bin");
@@ -141,6 +146,13 @@ impl HighlightingAssets {
})?;
println!("okay");
print!(
"Writing metadata to folder {} ... ",
target_dir.to_string_lossy()
);
AssetsMetadata::new(current_version).save_to_folder(target_dir)?;
println!("okay");
Ok(())
}
@@ -152,8 +164,8 @@ impl HighlightingAssets {
self.syntax_set.syntaxes()
}
pub fn themes(&self) -> impl Iterator<Item = &String> {
self.theme_set.themes.keys()
pub fn themes(&self) -> impl Iterator<Item = &str> {
self.theme_set.themes.keys().map(|s| s.as_ref())
}
pub(crate) fn get_theme(&self, theme: &str) -> &Theme {
@@ -168,7 +180,7 @@ impl HighlightingAssets {
theme
);
}
&self.theme_set.themes[self.fallback_theme.unwrap_or(Self::default_theme())]
&self.theme_set.themes[self.fallback_theme.unwrap_or_else(|| Self::default_theme())]
}
}
}
@@ -176,59 +188,82 @@ impl HighlightingAssets {
pub(crate) fn get_syntax(
&self,
language: Option<&str>,
filename: InputFile,
reader: &mut InputFileReader,
input: &mut OpenedInput,
mapping: &SyntaxMapping,
) -> &SyntaxReference {
let syntax = match (language, filename) {
(Some(language), _) => self.syntax_set.find_syntax_by_token(language),
(None, InputFile::Ordinary(filename)) => {
let path = Path::new(filename);
let syntax = if let Some(language) = language {
self.syntax_set.find_syntax_by_token(language)
} else {
match input.kind {
OpenedInputKind::OrdinaryFile(ref actual_path) => {
let path_str = input
.metadata
.user_provided_name
.as_ref()
.unwrap_or(actual_path);
let path = Path::new(path_str);
let line_syntax = self.get_first_line_syntax(&mut input.reader);
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");
let ext_syntax = self
.syntax_set
.find_syntax_by_extension(&file_name)
.or_else(|| self.syntax_set.find_syntax_by_extension(&extension));
let line_syntax = String::from_utf8(reader.first_line.clone())
.ok()
.and_then(|l| self.syntax_set.find_syntax_by_first_line(&l));
let absolute_path = path.canonicalize().ok().unwrap_or(path.to_owned());
match mapping.get_syntax_for(absolute_path) {
Some(MappingTarget::MapTo(syntax_name)) => {
// TODO: we should probably return an error here if this syntax can not be
// found. Currently, we just fall back to 'plain'.
self.syntax_set.find_syntax_by_name(syntax_name)
let absolute_path = path.canonicalize().ok().unwrap_or_else(|| path.to_owned());
match mapping.get_syntax_for(absolute_path) {
Some(MappingTarget::MapTo(syntax_name)) => {
// TODO: we should probably return an error here if this syntax can not be
// found. Currently, we just fall back to 'plain'.
self.syntax_set.find_syntax_by_name(syntax_name)
}
Some(MappingTarget::MapToUnknown) => line_syntax,
None => {
let file_name = path.file_name().unwrap_or_default();
self.get_extension_syntax(file_name).or(line_syntax)
}
}
Some(MappingTarget::MapToUnknown) => line_syntax,
None => ext_syntax.or(line_syntax),
}
OpenedInputKind::StdIn | OpenedInputKind::CustomReader => {
if let Some(ref name) = input.metadata.user_provided_name {
self.get_extension_syntax(&name)
.or_else(|| self.get_first_line_syntax(&mut input.reader))
} else {
self.get_first_line_syntax(&mut input.reader)
}
}
OpenedInputKind::ThemePreviewFile => self.syntax_set.find_syntax_by_name("Rust"),
}
(None, InputFile::StdIn) => String::from_utf8(reader.first_line.clone())
.ok()
.and_then(|l| self.syntax_set.find_syntax_by_first_line(&l)),
(_, InputFile::ThemePreviewFile) => self.syntax_set.find_syntax_by_name("Rust"),
};
syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
}
fn get_extension_syntax(&self, file_name: &OsStr) -> Option<&SyntaxReference> {
self.syntax_set
.find_syntax_by_extension(file_name.to_str().unwrap_or_default())
.or_else(|| {
self.syntax_set.find_syntax_by_extension(
Path::new(file_name)
.extension()
.and_then(|x| x.to_str())
.unwrap_or_default(),
)
})
}
fn get_first_line_syntax(&self, reader: &mut InputReader) -> Option<&SyntaxReference> {
String::from_utf8(reader.first_line.clone())
.ok()
.and_then(|l| self.syntax_set.find_syntax_by_first_line(&l))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::io::Write;
use tempdir::TempDir;
use crate::assets::HighlightingAssets;
use crate::inputfile::InputFile;
use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
use crate::input::Input;
struct SyntaxDetectionTest<'a> {
assets: HighlightingAssets,
@@ -246,26 +281,43 @@ mod tests {
}
}
fn synax_for_file_with_content(&self, file_name: &str, first_line: &str) -> String {
fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
let file_path = self.temp_dir.path().join(file_name);
{
let mut temp_file = File::create(&file_path).unwrap();
writeln!(temp_file, "{}", first_line).unwrap();
}
let input_file = InputFile::Ordinary(OsStr::new(&file_path));
let syntax = self.assets.get_syntax(
None,
input_file,
&mut input_file.get_reader(&io::stdin()).unwrap(),
&self.syntax_mapping,
);
let input = Input::ordinary_file(file_path.as_os_str());
let dummy_stdin: &[u8] = &[];
let mut opened_input = input.open(dummy_stdin).unwrap();
let syntax = self
.assets
.get_syntax(None, &mut opened_input, &self.syntax_mapping);
syntax.name.clone()
}
fn syntax_for_file_os(&self, file_name: &OsStr) -> String {
self.syntax_for_file_with_content_os(file_name, "")
}
fn syntax_for_file_with_content(&self, file_name: &str, first_line: &str) -> String {
self.syntax_for_file_with_content_os(OsStr::new(file_name), first_line)
}
fn syntax_for_file(&self, file_name: &str) -> String {
self.synax_for_file_with_content(file_name, "")
self.syntax_for_file_with_content(file_name, "")
}
fn syntax_for_stdin_with_content(&self, file_name: &str, content: &[u8]) -> String {
let input = Input::stdin().with_name(Some(OsStr::new(file_name)));
let mut opened_input = input.open(content).unwrap();
let syntax = self
.assets
.get_syntax(None, &mut opened_input, &self.syntax_mapping);
syntax.name.clone()
}
}
@@ -284,6 +336,19 @@ mod tests {
assert_eq!(test.syntax_for_file("Makefile"), "Makefile");
}
#[cfg(unix)]
#[test]
fn syntax_detection_invalid_utf8() {
use std::os::unix::ffi::OsStrExt;
let test = SyntaxDetectionTest::new();
assert_eq!(
test.syntax_for_file_os(OsStr::from_bytes(b"invalid_\xFEutf8_filename.rs")),
"Rust"
);
}
#[test]
fn syntax_detection_well_defined_mapping_for_duplicate_extensions() {
let test = SyntaxDetectionTest::new();
@@ -299,15 +364,15 @@ mod tests {
let test = SyntaxDetectionTest::new();
assert_eq!(
test.synax_for_file_with_content("my_script", "#!/bin/bash"),
test.syntax_for_file_with_content("my_script", "#!/bin/bash"),
"Bourne Again Shell (bash)"
);
assert_eq!(
test.synax_for_file_with_content("build", "#!/bin/bash"),
test.syntax_for_file_with_content("build", "#!/bin/bash"),
"Bourne Again Shell (bash)"
);
assert_eq!(
test.synax_for_file_with_content("my_script", "<?php"),
test.syntax_for_file_with_content("my_script", "<?php"),
"PHP"
);
}
@@ -333,4 +398,17 @@ mod tests {
.ok();
assert_eq!(test.syntax_for_file("README.MD"), "Markdown");
}
#[test]
fn syntax_detection_stdin_filename() {
let test = SyntaxDetectionTest::new();
// from file extension
assert_eq!(test.syntax_for_stdin_with_content("test.cpp", b"a"), "C++");
// from first line (fallback)
assert_eq!(
test.syntax_for_stdin_with_content("my_script", b"#!/bin/bash"),
"Bourne Again Shell (bash)"
);
}
}
+81
View File
@@ -0,0 +1,81 @@
use std::fs::File;
use std::path::Path;
use std::time::SystemTime;
use semver::Version;
use serde::{Deserialize, Serialize};
use crate::error::*;
#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct AssetsMetadata {
bat_version: Option<String>,
creation_time: Option<SystemTime>,
}
const FILENAME: &str = "metadata.yaml";
impl AssetsMetadata {
pub(crate) fn new(current_version: &str) -> AssetsMetadata {
AssetsMetadata {
bat_version: Some(current_version.to_owned()),
creation_time: Some(SystemTime::now()),
}
}
pub(crate) fn save_to_folder(&self, path: &Path) -> Result<()> {
let file = File::create(path.join(FILENAME))?;
serde_yaml::to_writer(file, self)?;
Ok(())
}
fn try_load_from_folder(path: &Path) -> Result<Self> {
let file = File::open(path.join(FILENAME))?;
Ok(serde_yaml::from_reader(file)?)
}
/// Load metadata about the stored cache file from the given folder.
///
/// There are several possibilities:
/// - We find a metadata.yaml file and are able to parse it
/// => return the contained information
/// - We find a metadata.yaml file and but are not able to parse it
/// => return a SerdeYamlError
/// - We do not find a metadata.yaml file but a syntaxes.bin or themes.bin file
/// => assume that these were created by an old version of bat and return
/// AssetsMetadata::default() without version information
/// - We do not find a metadata.yaml file and no cached assets
/// => no user provided assets are available, return None
pub fn load_from_folder(path: &Path) -> Result<Option<Self>> {
match Self::try_load_from_folder(path) {
Ok(metadata) => Ok(Some(metadata)),
Err(e) => match e.kind() {
ErrorKind::SerdeYamlError(_) => Err(e),
_ => {
if path.join("syntaxes.bin").exists() || path.join("themes.bin").exists() {
Ok(Some(Self::default()))
} else {
Ok(None)
}
}
},
}
}
pub fn is_compatible_with(&self, current_version: &str) -> bool {
let current_version =
Version::parse(current_version).expect("bat follows semantic versioning");
let stored_version = self
.bat_version
.as_ref()
.and_then(|ver| Version::parse(ver).ok());
if let Some(stored_version) = stored_version {
current_version.major == stored_version.major
&& current_version.minor == stored_version.minor
} else {
false
}
}
}
+80 -45
View File
@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::env;
use std::ffi::OsStr;
use std::str::FromStr;
use atty::{self, Stream};
@@ -9,20 +10,17 @@ use crate::{
config::{get_args_from_config_file, get_args_from_env_var},
};
use clap::ArgMatches;
use wild;
use console::Term;
#[cfg(windows)]
use ansi_term;
use bat::{
config::{
Config, HighlightedLineRanges, InputFile, LineRange, LineRanges, MappingTarget, OutputWrap,
PagingMode, StyleComponent, StyleComponents, SyntaxMapping,
},
errors::*,
HighlightingAssets,
assets::HighlightingAssets,
config::{Config, VisibleLines},
error::*,
input::Input,
line_range::{HighlightedLineRanges, LineRange, LineRanges},
style::{StyleComponent, StyleComponents},
MappingTarget, PagingMode, SyntaxMapping, WrappingMode,
};
fn is_truecolor_terminal() -> bool {
@@ -76,18 +74,17 @@ impl App {
Ok(clap_app::build_app(interactive_output).get_matches_from(args))
}
pub fn config(&self) -> Result<Config> {
let files = self.files();
pub fn config(&self, inputs: &[Input]) -> Result<Config> {
let style_components = self.style_components()?;
let paging_mode = match self.matches.value_of("paging") {
Some("always") => PagingMode::Always,
Some("never") => PagingMode::Never,
Some("auto") | _ => {
Some("auto") | None => {
if self.matches.occurrences_of("plain") > 1 {
// If we have -pp as an option when in auto mode, the pager should be disabled.
PagingMode::Never
} else if files.contains(&InputFile::StdIn) {
} else if inputs.iter().any(Input::is_stdin) {
// 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
// terminal.
@@ -102,6 +99,7 @@ impl App {
PagingMode::Never
}
}
_ => unreachable!("other values for --paging are not allowed"),
};
let mut syntax_mapping = SyntaxMapping::builtin();
@@ -146,22 +144,23 @@ impl App {
}
}),
show_nonprintable: self.matches.is_present("show-all"),
output_wrap: if self.interactive_output || maybe_term_width.is_some() {
wrapping_mode: if self.interactive_output || maybe_term_width.is_some() {
match self.matches.value_of("wrap") {
Some("character") => OutputWrap::Character,
Some("never") => OutputWrap::None,
Some("auto") | _ => {
Some("character") => WrappingMode::Character,
Some("never") => WrappingMode::NoWrapping,
Some("auto") | None => {
if style_components.plain() {
OutputWrap::None
WrappingMode::NoWrapping
} else {
OutputWrap::Character
WrappingMode::Character
}
}
_ => unreachable!("other values for --paging are not allowed"),
}
} else {
// We don't have the tty width when piping to another program.
// There's no point in wrapping when this is the case.
OutputWrap::None
WrappingMode::NoWrapping
},
colored_output: match self.matches.value_of("color") {
Some("always") => true,
@@ -173,7 +172,6 @@ impl App {
loop_through: !(self.interactive_output
|| self.matches.value_of("color") == Some("always")
|| self.matches.value_of("decorations") == Some("always")),
files,
tab_width: self
.matches
.value_of("tabs")
@@ -200,13 +198,23 @@ impl App {
}
})
.unwrap_or_else(|| String::from(HighlightingAssets::default_theme())),
line_ranges: self
.matches
.values_of("line-range")
.map(|vs| vs.map(LineRange::from).collect())
.transpose()?
.map(LineRanges::from)
.unwrap_or_default(),
visible_lines: if self.matches.is_present("diff") {
VisibleLines::DiffContext(
self.matches
.value_of("diff-context")
.and_then(|t| t.parse().ok())
.unwrap_or(2),
)
} else {
VisibleLines::Ranges(
self.matches
.values_of("line-range")
.map(|vs| vs.map(LineRange::from).collect())
.transpose()?
.map(LineRanges::from)
.unwrap_or_default(),
)
},
style_components,
syntax_mapping,
pager: self.matches.value_of("pager"),
@@ -225,21 +233,48 @@ impl App {
})
}
fn files(&self) -> Vec<InputFile> {
self.matches
.values_of_os("FILE")
.map(|values| {
values
.map(|filename| {
if filename == "-" {
InputFile::StdIn
} else {
InputFile::Ordinary(filename)
}
})
.collect()
})
.unwrap_or_else(|| vec![InputFile::StdIn])
pub fn inputs(&self) -> Result<Vec<Input>> {
// verify equal length of file-names and input FILEs
match self.matches.values_of("file-name") {
Some(ref filenames)
if self.matches.values_of_os("FILE").is_some()
&& filenames.len() != self.matches.values_of_os("FILE").unwrap().len() =>
{
return Err("Must be one file name per input type.".into());
}
_ => {}
}
let filenames: Option<Vec<&str>> = self
.matches
.values_of("file-name")
.map(|values| values.collect());
let mut filenames_or_none: Box<dyn Iterator<Item = _>> = match filenames {
Some(ref filenames) => Box::new(filenames.iter().map(|name| Some(OsStr::new(*name)))),
None => Box::new(std::iter::repeat(None)),
};
let files: Option<Vec<&OsStr>> = self.matches.values_of_os("FILE").map(|vs| vs.collect());
if files.is_none() {
let input = Input::stdin().with_name(filenames_or_none.next().unwrap_or(None));
return Ok(vec![input]);
}
let files_or_none: Box<dyn Iterator<Item = _>> = match files {
Some(ref files) => Box::new(files.iter().map(|name| Some(*name))),
None => Box::new(std::iter::repeat(None)),
};
let mut file_input = Vec::new();
for (filepath, provided_name) in files_or_none.zip(filenames_or_none) {
if let Some(filepath) = filepath {
if filepath.to_str().unwrap_or_default() == "-" {
file_input.push(Input::stdin().with_name(provided_name));
} else {
file_input.push(Input::ordinary_file(filepath).with_name(provided_name));
}
}
}
Ok(file_input)
}
fn style_components(&self) -> Result<StyleComponents> {
@@ -248,7 +283,7 @@ impl App {
if matches.value_of("decorations") == Some("never") {
HashSet::new()
} else if matches.is_present("number") {
[StyleComponent::Numbers].iter().cloned().collect()
[StyleComponent::LineNumbers].iter().cloned().collect()
} else if matches.is_present("plain") {
[StyleComponent::Plain].iter().cloned().collect()
} else {
+35 -15
View File
@@ -1,18 +1,13 @@
use std::borrow::Cow;
use std::fs;
use std::path::PathBuf;
use clap::crate_version;
use crate::directories::PROJECT_DIRS;
use bat::HighlightingAssets;
fn theme_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("themes.bin")
}
fn syntax_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("syntaxes.bin")
}
use bat::assets::HighlightingAssets;
use bat::assets_metadata::AssetsMetadata;
use bat::error::*;
pub fn config_dir() -> Cow<'static, str> {
PROJECT_DIRS.config_dir().to_string_lossy()
@@ -23,16 +18,41 @@ pub fn cache_dir() -> Cow<'static, str> {
}
pub fn clear_assets() {
let theme_set_path = PROJECT_DIRS.cache_dir().join("themes.bin");
let syntax_set_path = PROJECT_DIRS.cache_dir().join("syntaxes.bin");
let metadata_file = PROJECT_DIRS.cache_dir().join("metadata.yaml");
print!("Clearing theme set cache ... ");
fs::remove_file(theme_set_path()).ok();
fs::remove_file(theme_set_path).ok();
println!("okay");
print!("Clearing syntax set cache ... ");
fs::remove_file(syntax_set_path()).ok();
fs::remove_file(syntax_set_path).ok();
println!("okay");
print!("Clearing metadata file ... ");
fs::remove_file(metadata_file).ok();
println!("okay");
}
pub fn assets_from_cache_or_binary() -> HighlightingAssets {
HighlightingAssets::from_cache(&theme_set_path(), &syntax_set_path())
.unwrap_or(HighlightingAssets::from_binary())
pub fn assets_from_cache_or_binary() -> Result<HighlightingAssets> {
let cache_dir = PROJECT_DIRS.cache_dir();
if let Some(metadata) = AssetsMetadata::load_from_folder(&cache_dir)? {
if !metadata.is_compatible_with(crate_version!()) {
return Err(format!(
"The binary caches for the user-customized syntaxes and themes \
in '{}' are not compatible with this version of bat ({}). To solve this, \
either rebuild the cache (bat cache --build) or remove \
the custom syntaxes/themes (bat cache --clear).\n\
For more information, see:\n\n \
https://github.com/sharkdp/bat#adding-new-syntaxes--language-definitions",
cache_dir.to_string_lossy(),
crate_version!()
)
.into());
}
}
Ok(HighlightingAssets::from_cache(&cache_dir)
.unwrap_or_else(|_| HighlightingAssets::from_binary()))
}
+52 -2
View File
@@ -1,4 +1,4 @@
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, SubCommand};
use clap::{crate_name, crate_version, App as ClapApp, AppSettings, Arg, ArgGroup, SubCommand};
use std::path::Path;
pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
@@ -93,6 +93,47 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
'--highlight-line 40:' highlights lines 40 to the end of the file"
),
)
.arg(
Arg::with_name("file-name")
.long("file-name")
.takes_value(true)
.number_of_values(1)
.multiple(true)
.value_name("name")
.help("Specify the name to display for a file.")
.long_help("Specify the name to display for a file. Useful when piping \
data to bat from STDIN when bat does not otherwise know \
the filename."),
)
.arg(
Arg::with_name("diff")
.long("diff")
.short("d")
.help("Only show lines that have been added/removed/modified.")
.long_help(
"Only show lines that have been added/removed/modified with respect \
to the Git index. Use --diff-context=N to control how much context you want to see.",
),
)
.arg(
Arg::with_name("diff-context")
.long("diff-context")
.overrides_with("diff-context")
.takes_value(true)
.value_name("N")
.validator(
|n| {
n.parse::<usize>()
.map_err(|_| "must be a number")
.map(|_| ()) // Convert to Result<(), &str>
.map_err(|e| e.to_string())
}, // Convert to Result<(), String>
)
.hidden_short_help(true)
.long_help(
"Include N lines of context around added/removed/modified lines when using '--diff'.",
),
)
.arg(
Arg::with_name("tabs")
.long("tabs")
@@ -140,7 +181,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
t.parse::<i32>()
.map_err(|_e| "must be an offset or number")
.and_then(|v| if v == 0 && !is_offset {
Err("terminal width cannot be zero".into())
Err("terminal width cannot be zero")
} else {
Ok(())
})
@@ -327,6 +368,7 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.takes_value(true)
.number_of_values(1)
.value_name("N:M")
.conflicts_with("diff")
.help("Only print the lines from N to M.")
.long_help(
"Only print the specified range of lines for each file. \
@@ -370,6 +412,14 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.hidden(true)
.help("Show path to the configuration file."),
)
.arg(
Arg::with_name("generate-config-file")
.long("generate-config-file")
.conflicts_with("list-languages")
.conflicts_with("list-themes")
.hidden(true)
.help("Generates a default configuration file."),
)
.arg(
Arg::with_name("config-dir")
.long("config-dir")
+68 -2
View File
@@ -1,10 +1,9 @@
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
use shell_words;
use crate::directories::PROJECT_DIRS;
pub fn config_file() -> PathBuf {
@@ -15,6 +14,73 @@ pub fn config_file() -> PathBuf {
.unwrap_or_else(|| PROJECT_DIRS.config_dir().join("config"))
}
pub fn generate_config_file() -> bat::error::Result<()> {
let config_file = config_file();
if config_file.exists() {
println!(
"A config file already exists at: {}",
config_file.to_string_lossy()
);
print!("Overwrite? (y/N): ");
io::stdout().flush()?;
let mut decision = String::new();
io::stdin().read_line(&mut decision)?;
if !decision.trim().eq_ignore_ascii_case("Y") {
return Ok(());
}
} else {
let config_dir = config_file.parent();
match config_dir {
Some(path) => fs::create_dir_all(path)?,
None => {
return Err(format!(
"Unable to write config file to: {}",
config_file.to_string_lossy()
)
.into());
}
}
}
let default_config =
r#"# This is `bat`s configuration file. Each line either contains a comment or
# a command-line option that you want to pass to `bat` by default. You can
# run `bat --help` to get a list of all possible configuration options.
# Specify desired highlighting theme (e.g. "TwoDark"). Run `bat --list-themes`
# for a list of all available themes
#--theme="TwoDark"
# Enable this to use italic text on the terminal. This is not supported on all
# terminal emulators (like tmux, by default):
#--italic-text=always
# Uncomment the following line to disable automatic paging:
#--paging=never
# Uncomment the following line if you are using less version >= 551 and want to
# enable mouse scrolling support in `bat` when running inside tmux. This might
# disable text selection, unless you press shift.
#--pager="--RAW-CONTROL-CHARS --quit-if-one-screen --mouse"
# Syntax mappings: map a certain filename pattern to a language.
# Example 1: use the C++ syntax for .ino files
# Example 2: Use ".gitignore"-style highlighting for ".ignore" files
#--map-syntax "*.ino:C++"
#--map-syntax ".ignore:Git Ignore"
"#;
fs::write(&config_file, default_config)?;
println!(
"Success! Config file written to {}",
config_file.to_string_lossy()
);
Ok(())
}
pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> {
Ok(fs::read_to_string(config_file())
.ok()
+1 -2
View File
@@ -1,7 +1,6 @@
use std::env;
use std::path::{Path, PathBuf};
use dirs;
use lazy_static::lazy_static;
/// Wrapper for 'dirs' that treats MacOS more like Linux, by following the XDG specification.
@@ -33,7 +32,7 @@ impl BatProjectDirs {
})
}
pub fn get_cache_dir() -> Option<PathBuf> {
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() {
+32 -28
View File
@@ -1,11 +1,6 @@
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]
#[macro_use]
extern crate clap;
extern crate dirs as dirs_rs;
mod app;
mod assets;
mod clap_app;
@@ -22,15 +17,21 @@ use std::process;
use ansi_term::Colour::Green;
use ansi_term::Style;
use crate::{app::App, config::config_file};
use crate::{
app::App,
config::{config_file, generate_config_file},
};
use assets::{assets_from_cache_or_binary, cache_dir, clear_assets, config_dir};
use bat::Controller;
use clap::crate_version;
use directories::PROJECT_DIRS;
use bat::{
config::{Config, InputFile, StyleComponent, StyleComponents},
errors::*,
HighlightingAssets,
assets::HighlightingAssets,
config::Config,
controller::Controller,
error::*,
input::Input,
style::{StyleComponent, StyleComponents},
};
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
@@ -47,7 +48,7 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
let blank = matches.is_present("blank");
let assets = HighlightingAssets::from_files(source_dir, !blank)?;
assets.save_to_cache(target_dir)?;
assets.save_to_cache(target_dir, crate_version!())?;
} else if matches.is_present("clear") {
clear_assets();
}
@@ -56,7 +57,7 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
}
pub fn list_languages(config: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary();
let assets = assets_from_cache_or_binary()?;
let mut languages = assets
.syntaxes()
.iter()
@@ -69,7 +70,7 @@ pub fn list_languages(config: &Config) -> Result<()> {
if config.loop_through {
for lang in languages {
write!(stdout, "{}:{}\n", lang.name, lang.file_extensions.join(","))?;
writeln!(stdout, "{}:{}", lang.name, lang.file_extensions.join(","))?;
}
} else {
let longest = languages
@@ -118,11 +119,10 @@ pub fn list_languages(config: &Config) -> Result<()> {
}
pub fn list_themes(cfg: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary();
let assets = assets_from_cache_or_binary()?;
let mut config = cfg.clone();
let mut style = HashSet::new();
style.insert(StyleComponent::Plain);
config.files = vec![InputFile::ThemePreviewFile];
config.style_components = StyleComponents(style);
let stdout = io::stdout();
@@ -136,7 +136,9 @@ pub fn list_themes(cfg: &Config) -> Result<()> {
Style::new().bold().paint(theme.to_string())
)?;
config.theme = theme.to_string();
let _controller = Controller::new(&config, &assets).run();
Controller::new(&config, &assets)
.run(vec![Input::theme_preview_file()])
.ok();
writeln!(stdout)?;
}
} else {
@@ -148,10 +150,10 @@ pub fn list_themes(cfg: &Config) -> Result<()> {
Ok(())
}
fn run_controller(config: &Config) -> Result<bool> {
let assets = assets_from_cache_or_binary();
fn run_controller(inputs: Vec<Input>, config: &Config) -> Result<bool> {
let assets = assets_from_cache_or_binary()?;
let controller = Controller::new(&config, &assets);
controller.run()
controller.run(inputs)
}
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
@@ -168,26 +170,27 @@ fn run() -> Result<bool> {
run_cache_subcommand(cache_matches)?;
Ok(true)
} else {
let mut config = app.config()?;
config.files = vec![InputFile::Ordinary(OsStr::new("cache"))];
let inputs = vec![Input::ordinary_file(OsStr::new("cache"))];
let config = app.config(&inputs)?;
run_controller(&config)
run_controller(inputs, &config)
}
}
_ => {
let config = app.config()?;
let inputs = app.inputs()?;
let config = app.config(&inputs)?;
if app.matches.is_present("list-languages") {
list_languages(&config)?;
Ok(true)
} else if app.matches.is_present("list-themes") {
list_themes(&config)?;
Ok(true)
} else if app.matches.is_present("config-file") {
println!("{}", config_file().to_string_lossy());
Ok(true)
} else if app.matches.is_present("generate-config-file") {
generate_config_file()?;
Ok(true)
} else if app.matches.is_present("config-dir") {
writeln!(io::stdout(), "{}", config_dir())?;
@@ -196,7 +199,7 @@ fn run() -> Result<bool> {
writeln!(io::stdout(), "{}", cache_dir())?;
Ok(true)
} else {
run_controller(&config)
run_controller(inputs, &config)
}
}
}
@@ -207,7 +210,8 @@ fn main() {
match result {
Err(error) => {
default_error_handler(&error);
let stderr = std::io::stderr();
default_error_handler(&error, &mut stderr.lock());
process::exit(1);
}
Ok(false) => {
+32 -23
View File
@@ -1,27 +1,38 @@
pub use crate::inputfile::InputFile;
pub use crate::line_range::{HighlightedLineRanges, LineRange, LineRanges};
pub use crate::style::{StyleComponent, StyleComponents};
pub use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
pub use crate::wrap::OutputWrap;
use crate::line_range::{HighlightedLineRanges, LineRanges};
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
use crate::style::StyleComponents;
use crate::syntax_mapping::SyntaxMapping;
use crate::wrapping::WrappingMode;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PagingMode {
Always,
QuitIfOneScreen,
Never,
#[derive(Debug, Clone)]
pub enum VisibleLines {
/// Show all lines which are included in the line ranges
Ranges(LineRanges),
#[cfg(feature = "git")]
/// Only show lines surrounding added/deleted/modified lines
DiffContext(usize),
}
impl Default for PagingMode {
impl VisibleLines {
pub fn diff_mode(&self) -> bool {
match self {
Self::Ranges(_) => false,
#[cfg(feature = "git")]
Self::DiffContext(_) => true,
}
}
}
impl Default for VisibleLines {
fn default() -> Self {
PagingMode::Never
VisibleLines::Ranges(LineRanges::default())
}
}
#[derive(Debug, Clone, Default)]
pub struct Config<'a> {
/// List of files to print
pub files: Vec<InputFile<'a>>,
/// The explicitly configured language, if any
pub language: Option<&'a str>,
@@ -47,14 +58,15 @@ pub struct Config<'a> {
/// Style elements (grid, line numbers, ...)
pub style_components: StyleComponents,
/// Text wrapping mode
pub output_wrap: OutputWrap,
/// If and how text should be wrapped
pub wrapping_mode: WrappingMode,
/// Pager or STDOUT
#[cfg(feature = "paging")]
pub paging_mode: PagingMode,
/// Specifies the lines that should be printed
pub line_ranges: LineRanges,
/// Specifies which lines should be printed
pub visible_lines: VisibleLines,
/// The syntax highlighting theme
pub theme: String,
@@ -76,10 +88,7 @@ pub struct Config<'a> {
fn default_config_should_include_all_lines() {
use crate::line_range::RangeCheckResult;
assert_eq!(
Config::default().line_ranges.check(17),
RangeCheckResult::InRange
);
assert_eq!(LineRanges::default().check(17), RangeCheckResult::InRange);
}
#[test]
+131 -47
View File
@@ -1,12 +1,17 @@
use std::io::{self, Write};
use std::path::Path;
use crate::assets::HighlightingAssets;
use crate::config::{Config, PagingMode};
use crate::errors::*;
use crate::inputfile::{InputFile, InputFileReader};
use crate::config::{Config, VisibleLines};
#[cfg(feature = "git")]
use crate::diff::{get_git_diff, LineChanges};
use crate::error::*;
use crate::input::{Input, InputReader, OpenedInput};
#[cfg(feature = "git")]
use crate::line_range::LineRange;
use crate::line_range::{LineRanges, RangeCheckResult};
use crate::output::OutputType;
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
use crate::printer::{InteractivePrinter, Printer, SimplePrinter};
pub struct Controller<'a> {
@@ -19,54 +24,116 @@ impl<'b> Controller<'b> {
Controller { config, assets }
}
pub fn run(&self) -> Result<bool> {
self.run_with_error_handler(default_error_handler)
pub fn run(&self, inputs: Vec<Input>) -> Result<bool> {
self.run_with_error_handler(inputs, default_error_handler)
}
pub fn run_with_error_handler(&self, handle_error: impl Fn(&Error)) -> Result<bool> {
// Do not launch the pager if NONE of the input files exist
let mut paging_mode = self.config.paging_mode;
if self.config.paging_mode != PagingMode::Never {
let call_pager = self.config.files.iter().any(|file| {
if let InputFile::Ordinary(path) = file {
return Path::new(path).exists();
} else {
return true;
pub fn run_with_error_handler(
&self,
inputs: Vec<Input>,
handle_error: impl Fn(&Error, &mut dyn Write),
) -> Result<bool> {
let mut output_type;
#[cfg(feature = "paging")]
{
use crate::input::InputKind;
use std::path::Path;
// Do not launch the pager if NONE of the input files exist
let mut paging_mode = self.config.paging_mode;
if self.config.paging_mode != PagingMode::Never {
let call_pager = inputs.iter().any(|ref input| {
if let InputKind::OrdinaryFile(ref path) = input.kind {
Path::new(path).exists()
} else {
true
}
});
if !call_pager {
paging_mode = PagingMode::Never;
}
});
if !call_pager {
paging_mode = PagingMode::Never;
}
output_type = OutputType::from_mode(paging_mode, self.config.pager)?;
}
let mut output_type = OutputType::from_mode(paging_mode, self.config.pager)?;
#[cfg(not(feature = "paging"))]
{
output_type = OutputType::stdout();
}
let attached_to_pager = output_type.is_pager();
let writer = output_type.handle()?;
let mut no_errors: bool = true;
let stdin = io::stdin();
let stderr = io::stderr();
let print_error = |error: &Error, write: &mut dyn Write| {
if attached_to_pager {
handle_error(error, write);
} else {
handle_error(error, &mut stderr.lock());
}
};
for input_file in &self.config.files {
match input_file.get_reader(&stdin) {
for input in inputs.into_iter() {
match input.open(io::stdin().lock()) {
Err(error) => {
handle_error(&error);
print_error(&error, writer);
no_errors = false;
}
Ok(mut reader) => {
let result = if self.config.loop_through {
let mut printer = SimplePrinter::new();
self.print_file(reader, &mut printer, writer, *input_file)
Ok(mut opened_input) => {
#[cfg(feature = "git")]
let line_changes = if self.config.visible_lines.diff_mode()
|| (!self.config.loop_through && self.config.style_components.changes())
{
match opened_input.kind {
crate::input::OpenedInputKind::OrdinaryFile(ref path) => {
let diff = get_git_diff(path);
// Skip files without Git modifications
if self.config.visible_lines.diff_mode()
&& diff
.as_ref()
.map(|changes| changes.is_empty())
.unwrap_or(false)
{
continue;
}
diff
}
_ if self.config.visible_lines.diff_mode() => {
// Skip non-file inputs in diff mode
continue;
}
_ => None,
}
} else {
let mut printer = InteractivePrinter::new(
&self.config,
&self.assets,
*input_file,
&mut reader,
);
self.print_file(reader, &mut printer, writer, *input_file)
None
};
let mut printer: Box<dyn Printer> = if self.config.loop_through {
Box::new(SimplePrinter::new())
} else {
Box::new(InteractivePrinter::new(
&self.config,
&self.assets,
&mut opened_input,
#[cfg(feature = "git")]
&line_changes,
))
};
let result = self.print_file(
&mut *printer,
writer,
&mut opened_input,
#[cfg(feature = "git")]
&line_changes,
);
if let Err(error) = result {
handle_error(&error);
print_error(&error, writer);
no_errors = false;
}
}
@@ -76,30 +143,47 @@ impl<'b> Controller<'b> {
Ok(no_errors)
}
fn print_file<'a, P: Printer>(
fn print_file<'a>(
&self,
reader: InputFileReader,
printer: &mut P,
printer: &mut dyn Printer,
writer: &mut dyn Write,
input_file: InputFile<'a>,
input: &mut OpenedInput,
#[cfg(feature = "git")] line_changes: &Option<LineChanges>,
) -> Result<()> {
if !reader.first_line.is_empty() || self.config.style_components.header() {
printer.print_header(writer, input_file)?;
if !input.reader.first_line.is_empty() || self.config.style_components.header() {
printer.print_header(writer, input)?;
}
if !reader.first_line.is_empty() {
self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?;
if !input.reader.first_line.is_empty() {
let line_ranges = match self.config.visible_lines {
VisibleLines::Ranges(ref line_ranges) => line_ranges.clone(),
#[cfg(feature = "git")]
VisibleLines::DiffContext(context) => {
let mut line_ranges: Vec<LineRange> = vec![];
if let Some(line_changes) = line_changes {
for line in line_changes.keys() {
let line = *line as usize;
line_ranges.push(LineRange::new(line - context, line + context));
}
}
LineRanges::from(line_ranges)
}
};
self.print_file_ranges(printer, writer, &mut input.reader, &line_ranges)?;
}
printer.print_footer(writer)?;
printer.print_footer(writer, input)?;
Ok(())
}
fn print_file_ranges<P: Printer>(
fn print_file_ranges(
&self,
printer: &mut P,
printer: &mut dyn Printer,
writer: &mut dyn Write,
mut reader: InputFileReader,
reader: &mut InputReader,
line_ranges: &LineRanges,
) -> Result<()> {
let mut line_buffer = Vec::new();
+12 -8
View File
@@ -1,14 +1,15 @@
#[cfg(feature = "git")]
use crate::diff::LineChange;
use crate::printer::{Colors, InteractivePrinter};
use ansi_term::Style;
#[derive(Debug, Clone)]
pub struct DecorationText {
pub(crate) struct DecorationText {
pub width: usize,
pub text: String,
}
pub trait Decoration {
pub(crate) trait Decoration {
fn generate(
&self,
line_number: usize,
@@ -18,14 +19,14 @@ pub trait Decoration {
fn width(&self) -> usize;
}
pub struct LineNumberDecoration {
pub(crate) struct LineNumberDecoration {
color: Style,
cached_wrap: DecorationText,
cached_wrap_invalid_at: usize,
}
impl LineNumberDecoration {
pub fn new(colors: &Colors) -> Self {
pub(crate) fn new(colors: &Colors) -> Self {
LineNumberDecoration {
color: colors.line_number,
cached_wrap_invalid_at: 10000,
@@ -68,7 +69,8 @@ impl Decoration for LineNumberDecoration {
}
}
pub struct LineChangesDecoration {
#[cfg(feature = "git")]
pub(crate) struct LineChangesDecoration {
cached_none: DecorationText,
cached_added: DecorationText,
cached_removed_above: DecorationText,
@@ -76,6 +78,7 @@ pub struct LineChangesDecoration {
cached_modified: DecorationText,
}
#[cfg(feature = "git")]
impl LineChangesDecoration {
#[inline]
fn generate_cached(style: Style, text: &str) -> DecorationText {
@@ -85,7 +88,7 @@ impl LineChangesDecoration {
}
}
pub fn new(colors: &Colors) -> Self {
pub(crate) fn new(colors: &Colors) -> Self {
LineChangesDecoration {
cached_none: Self::generate_cached(Style::default(), " "),
cached_added: Self::generate_cached(colors.git_added, "+"),
@@ -96,6 +99,7 @@ impl LineChangesDecoration {
}
}
#[cfg(feature = "git")]
impl Decoration for LineChangesDecoration {
fn generate(
&self,
@@ -123,12 +127,12 @@ impl Decoration for LineChangesDecoration {
}
}
pub struct GridBorderDecoration {
pub(crate) struct GridBorderDecoration {
cached: DecorationText,
}
impl GridBorderDecoration {
pub fn new(colors: &Colors) -> Self {
pub(crate) fn new(colors: &Colors) -> Self {
GridBorderDecoration {
cached: DecorationText {
text: colors.grid.paint("").to_string(),
+2
View File
@@ -1,3 +1,5 @@
#![cfg(feature = "git")]
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs;
+37
View File
@@ -0,0 +1,37 @@
use error_chain::error_chain;
use std::io::Write;
error_chain! {
foreign_links {
Clap(::clap::Error) #[cfg(feature = "application")];
Io(::std::io::Error);
SyntectError(::syntect::LoadingError);
ParseIntError(::std::num::ParseIntError);
GlobParsingError(::globset::Error);
SerdeYamlError(::serde_yaml::Error);
}
}
pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
use ansi_term::Colour::Red;
match error {
Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == ::std::io::ErrorKind::BrokenPipe =>
{
::std::process::exit(0);
}
Error(ErrorKind::SerdeYamlError(_), _) => {
writeln!(
output,
"{}: Error while parsing metadata.yaml file: {}",
Red.paint("[bat error]"),
error
)
.ok();
}
_ => {
writeln!(output, "{}: {}", Red.paint("[bat error]"), error).ok();
}
};
}
-25
View File
@@ -1,25 +0,0 @@
use error_chain::error_chain;
error_chain! {
foreign_links {
Clap(::clap::Error);
Io(::std::io::Error);
SyntectError(::syntect::LoadingError);
ParseIntError(::std::num::ParseIntError);
GlobParsingError(::globset::Error);
}
}
pub fn default_error_handler(error: &Error) {
match error {
Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == ::std::io::ErrorKind::BrokenPipe =>
{
::std::process::exit(0);
}
_ => {
use ansi_term::Colour::Red;
eprintln!("{}: {}", Red.paint("[bat error]"), error);
}
};
}
+258
View File
@@ -0,0 +1,258 @@
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::io::{self, BufRead, BufReader, Read};
use content_inspector::{self, ContentType};
use crate::error::*;
const THEME_PREVIEW_FILE: &[u8] = include_bytes!("../assets/theme_preview.rs");
#[derive(Debug, Clone)]
pub(crate) struct InputDescription {
pub full: String,
pub prefix: String,
pub name: String,
}
pub(crate) enum InputKind<'a> {
OrdinaryFile(OsString),
StdIn,
ThemePreviewFile,
CustomReader(Box<dyn Read + 'a>),
}
#[derive(Clone, Default)]
pub(crate) struct InputMetadata {
pub(crate) user_provided_name: Option<OsString>,
}
pub struct Input<'a> {
pub(crate) kind: InputKind<'a>,
pub(crate) metadata: InputMetadata,
}
pub(crate) enum OpenedInputKind {
OrdinaryFile(OsString),
StdIn,
ThemePreviewFile,
CustomReader,
}
pub(crate) struct OpenedInput<'a> {
pub(crate) kind: OpenedInputKind,
pub(crate) metadata: InputMetadata,
pub(crate) reader: InputReader<'a>,
}
impl<'a> Input<'a> {
pub fn ordinary_file(path: &OsStr) -> Self {
Input {
kind: InputKind::OrdinaryFile(path.to_os_string()),
metadata: InputMetadata::default(),
}
}
pub fn stdin() -> Self {
Input {
kind: InputKind::StdIn,
metadata: InputMetadata::default(),
}
}
pub fn theme_preview_file() -> Self {
Input {
kind: InputKind::ThemePreviewFile,
metadata: InputMetadata::default(),
}
}
pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
Input {
kind: InputKind::CustomReader(reader),
metadata: InputMetadata::default(),
}
}
pub fn is_stdin(&self) -> bool {
if let InputKind::StdIn = self.kind {
true
} else {
false
}
}
pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
self
}
pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
match self.kind {
InputKind::StdIn => Ok(OpenedInput {
kind: OpenedInputKind::StdIn,
metadata: self.metadata,
reader: InputReader::new(stdin),
}),
InputKind::OrdinaryFile(path) => Ok(OpenedInput {
kind: OpenedInputKind::OrdinaryFile(path.clone()),
metadata: self.metadata,
reader: {
let file = File::open(&path)
.map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
if file.metadata()?.is_dir() {
return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
}
InputReader::new(BufReader::new(file))
},
}),
InputKind::ThemePreviewFile => Ok(OpenedInput {
kind: OpenedInputKind::ThemePreviewFile,
metadata: self.metadata,
reader: InputReader::new(THEME_PREVIEW_FILE),
}),
InputKind::CustomReader(reader) => Ok(OpenedInput {
kind: OpenedInputKind::CustomReader,
metadata: self.metadata,
reader: InputReader::new(BufReader::new(reader)),
}),
}
}
}
impl<'a> OpenedInput<'a> {
pub fn description(&self) -> InputDescription {
if let Some(ref name) = self.metadata.user_provided_name {
InputDescription {
full: format!("file '{}'", name.to_string_lossy()),
prefix: "File: ".to_owned(),
name: name.to_string_lossy().into_owned(),
}
} else {
match self.kind {
OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
full: format!("file '{}'", path.to_string_lossy()),
prefix: "File: ".to_owned(),
name: path.to_string_lossy().into_owned(),
},
OpenedInputKind::StdIn => InputDescription {
full: "STDIN".to_owned(),
prefix: "".to_owned(),
name: "STDIN".to_owned(),
},
OpenedInputKind::ThemePreviewFile => InputDescription {
full: "".to_owned(),
prefix: "".to_owned(),
name: "".to_owned(),
},
OpenedInputKind::CustomReader => InputDescription {
full: "reader".to_owned(),
prefix: "".to_owned(),
name: "READER".into(),
},
}
}
}
}
pub(crate) struct InputReader<'a> {
inner: Box<dyn BufRead + 'a>,
pub(crate) first_line: Vec<u8>,
pub(crate) content_type: Option<ContentType>,
}
impl<'a> InputReader<'a> {
fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
let mut first_line = vec![];
reader.read_until(b'\n', &mut first_line).ok();
let content_type = if first_line.is_empty() {
None
} else {
Some(content_inspector::inspect(&first_line[..]))
};
if content_type == Some(ContentType::UTF_16LE) {
reader.read_until(0x00, &mut first_line).ok();
}
InputReader {
inner: Box::new(reader),
first_line,
content_type,
}
}
pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
if self.first_line.is_empty() {
let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
if self.content_type == Some(ContentType::UTF_16LE) {
self.inner.read_until(0x00, buf).ok();
}
Ok(res)
} else {
buf.append(&mut self.first_line);
Ok(true)
}
}
}
#[test]
fn basic() {
let content = b"#!/bin/bash\necho hello";
let mut reader = InputReader::new(&content[..]);
assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
let mut buffer = vec![];
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"#!/bin/bash\n", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"echo hello", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(false, res.unwrap());
assert!(buffer.is_empty());
}
#[test]
fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
let mut reader = InputReader::new(&content[..]);
assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
let mut buffer = vec![];
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"\x64\x00", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(false, res.unwrap());
assert!(buffer.is_empty());
}
-137
View File
@@ -1,137 +0,0 @@
use std::ffi::OsStr;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use content_inspector::{self, ContentType};
use crate::errors::*;
const THEME_PREVIEW_FILE: &[u8] = include_bytes!("../assets/theme_preview.rs");
pub struct InputFileReader<'a> {
inner: Box<dyn BufRead + 'a>,
pub(crate) first_line: Vec<u8>,
pub(crate) content_type: Option<ContentType>,
}
impl<'a> InputFileReader<'a> {
fn new<R: BufRead + 'a>(mut reader: R) -> InputFileReader<'a> {
let mut first_line = vec![];
reader.read_until(b'\n', &mut first_line).ok();
let content_type = if first_line.is_empty() {
None
} else {
Some(content_inspector::inspect(&first_line[..]))
};
if content_type == Some(ContentType::UTF_16LE) {
reader.read_until(0x00, &mut first_line).ok();
}
InputFileReader {
inner: Box::new(reader),
first_line,
content_type,
}
}
pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
if self.first_line.is_empty() {
let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
if self.content_type == Some(ContentType::UTF_16LE) {
self.inner.read_until(0x00, buf).ok();
}
Ok(res)
} else {
buf.append(&mut self.first_line);
Ok(true)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum InputFile<'a> {
StdIn,
Ordinary(&'a OsStr),
ThemePreviewFile,
}
impl<'a> InputFile<'a> {
pub(crate) fn get_reader(&self, stdin: &'a io::Stdin) -> Result<InputFileReader> {
match self {
InputFile::StdIn => Ok(InputFileReader::new(stdin.lock())),
InputFile::Ordinary(filename) => {
let file = File::open(filename)
.map_err(|e| format!("'{}': {}", filename.to_string_lossy(), e))?;
if file.metadata()?.is_dir() {
return Err(format!("'{}' is a directory.", filename.to_string_lossy()).into());
}
Ok(InputFileReader::new(BufReader::new(file)))
}
InputFile::ThemePreviewFile => Ok(InputFileReader::new(THEME_PREVIEW_FILE)),
}
}
}
#[test]
fn basic() {
let content = b"#!/bin/bash\necho hello";
let mut reader = InputFileReader::new(&content[..]);
assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
let mut buffer = vec![];
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"#!/bin/bash\n", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"echo hello", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(false, res.unwrap());
assert!(buffer.is_empty());
}
#[test]
fn utf16le() {
let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
let mut reader = InputFileReader::new(&content[..]);
assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
let mut buffer = vec![];
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(true, res.unwrap());
assert_eq!(b"\x64\x00", &buffer[..]);
buffer.clear();
let res = reader.read_line(&mut buffer);
assert!(res.is_ok());
assert_eq!(false, res.unwrap());
assert!(buffer.is_empty());
}
+2
View File
@@ -1,3 +1,5 @@
#![cfg(feature = "paging")]
use std::process::Command;
pub fn retrieve_less_version() -> Option<usize> {
+37 -23
View File
@@ -1,34 +1,48 @@
// `error_chain!` can recurse deeply
#![recursion_limit = "1024"]
//! `bat` is a library to print syntax highlighted content.
//!
//! The main struct of this crate is `PrettyPrinter` which can be used to
//! configure and run the syntax highlighting.
//!
//! If you need more control, you can also use the structs in the submodules
//! (start with `controller::Controller`), but note that the API of these
//! internal modules is much more likely to change. Some or all of these
//! modules might be removed in the future.
//!
//! "Hello world" example:
//! ```
//! use bat::PrettyPrinter;
//!
//! PrettyPrinter::new()
//! .input_from_bytes(b"<span style=\"color: #ff00cc\">Hello world!</span>\n")
//! .language("html")
//! .print()
//! .unwrap();
//! ```
extern crate ansi_term;
extern crate atty;
extern crate console;
extern crate content_inspector;
extern crate dirs as dirs_rs;
extern crate encoding;
extern crate git2;
extern crate shell_words;
extern crate syntect;
extern crate wild;
pub(crate) mod assets;
pub mod assets;
pub mod assets_metadata;
pub mod config;
pub(crate) mod controller;
pub mod controller;
mod decorations;
mod diff;
pub mod errors;
pub(crate) mod inputfile;
pub mod error;
pub mod input;
mod less;
pub(crate) mod line_range;
pub mod line_range;
mod output;
#[cfg(feature = "paging")]
pub(crate) mod paging;
mod preprocessor;
mod pretty_printer;
pub(crate) mod printer;
pub(crate) mod style;
pub mod style;
pub(crate) mod syntax_mapping;
mod terminal;
pub(crate) mod wrap;
pub(crate) mod wrapping;
pub use assets::HighlightingAssets;
pub use controller::Controller;
pub use printer::{InteractivePrinter, Printer, SimplePrinter};
pub use pretty_printer::PrettyPrinter;
pub use syntax_mapping::{MappingTarget, SyntaxMapping};
pub use wrapping::WrappingMode;
#[cfg(feature = "paging")]
pub use paging::PagingMode;
+11 -4
View File
@@ -1,9 +1,9 @@
use crate::errors::*;
use crate::error::*;
#[derive(Debug, Clone)]
pub struct LineRange {
pub lower: usize,
pub upper: usize,
lower: usize,
upper: usize,
}
impl Default for LineRange {
@@ -16,6 +16,13 @@ impl Default for LineRange {
}
impl LineRange {
pub fn new(from: usize, to: usize) -> Self {
LineRange {
lower: from,
upper: to,
}
}
pub fn from(range_raw: &str) -> Result<LineRange> {
LineRange::parse_range(range_raw)
}
@@ -23,7 +30,7 @@ impl LineRange {
fn parse_range(range_raw: &str) -> Result<LineRange> {
let mut new_range = LineRange::default();
if range_raw.bytes().nth(0).ok_or("Empty line range")? == b':' {
if range_raw.bytes().next().ok_or("Empty line range")? == b':' {
new_range.upper = range_raw[1..].parse()?;
return Ok(new_range);
} else if range_raw.bytes().last().ok_or("Empty line range")? == b':' {
+37 -10
View File
@@ -1,22 +1,22 @@
use std::env;
use std::ffi::OsString;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
#[cfg(feature = "paging")]
use std::process::Child;
use shell_words;
use crate::config::PagingMode;
use crate::errors::*;
use crate::error::*;
#[cfg(feature = "paging")]
use crate::less::retrieve_less_version;
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
#[derive(Debug)]
pub enum OutputType {
#[cfg(feature = "paging")]
Pager(Child),
Stdout(io::Stdout),
}
impl OutputType {
#[cfg(feature = "paging")]
pub fn from_mode(mode: PagingMode, pager: Option<&str>) -> Result<Self> {
use self::PagingMode::*;
Ok(match mode {
@@ -27,7 +27,13 @@ impl OutputType {
}
/// Try to launch the pager. Fall back to stdout in case of errors.
#[cfg(feature = "paging")]
fn try_pager(quit_if_one_screen: bool, pager_from_config: Option<&str>) -> Result<Self> {
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::{Command, Stdio};
let mut replace_arguments_to_less = false;
let pager_from_env = match (env::var("BAT_PAGER"), env::var("PAGER")) {
@@ -80,11 +86,16 @@ impl OutputType {
// versions of 'less'. Unfortunately, it also breaks mouse-wheel support.
//
// See: http://www.greenwoodsoftware.com/less/news.530.html
//
// For newer versions (530 or 558 on Windows), we omit '--no-init' as it
// is not needed anymore.
match retrieve_less_version() {
None => {
p.arg("--no-init");
}
Some(version) if version < 530 => {
Some(version)
if (version < 530 || (cfg!(windows) && version < 558)) =>
{
p.arg("--no-init");
}
_ => {}
@@ -110,12 +121,27 @@ impl OutputType {
}
}
fn stdout() -> Self {
pub(crate) fn stdout() -> Self {
OutputType::Stdout(io::stdout())
}
#[cfg(feature = "paging")]
pub(crate) fn is_pager(&self) -> bool {
if let OutputType::Pager(_) = self {
true
} else {
false
}
}
#[cfg(not(feature = "paging"))]
pub(crate) fn is_pager(&self) -> bool {
false
}
pub fn handle(&mut self) -> Result<&mut dyn Write> {
Ok(match *self {
#[cfg(feature = "paging")]
OutputType::Pager(ref mut command) => command
.stdin
.as_mut()
@@ -125,6 +151,7 @@ impl OutputType {
}
}
#[cfg(feature = "paging")]
impl Drop for OutputType {
fn drop(&mut self) {
if let OutputType::Pager(ref mut command) = *self {
+12
View File
@@ -0,0 +1,12 @@
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PagingMode {
Always,
QuitIfOneScreen,
Never,
}
impl Default for PagingMode {
fn default() -> Self {
PagingMode::Never
}
}
+8 -8
View File
@@ -36,15 +36,15 @@ pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
let str_from_utf8 = |seq| std::str::from_utf8(seq).ok();
let decoded = None
.or(input.get(0..1).and_then(str_from_utf8).map(|c| (c, 1)))
.or(input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2)))
.or(input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3)))
.or(input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4)));
let decoded = input
.get(0..1)
.and_then(str_from_utf8)
.map(|c| (c, 1))
.or_else(|| input.get(0..2).and_then(str_from_utf8).map(|c| (c, 2)))
.or_else(|| input.get(0..3).and_then(str_from_utf8).map(|c| (c, 3)))
.or_else(|| input.get(0..4).and_then(str_from_utf8).map(|c| (c, 4)));
let decoded_char = decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n));
decoded_char
decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n))
}
pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
+282
View File
@@ -0,0 +1,282 @@
use std::ffi::OsStr;
use std::io::Read;
use console::Term;
use syntect::parsing::SyntaxReference;
use crate::{
assets::HighlightingAssets,
config::{Config, VisibleLines},
controller::Controller,
error::Result,
input::Input,
line_range::{HighlightedLineRanges, LineRange, LineRanges},
style::{StyleComponent, StyleComponents},
SyntaxMapping, WrappingMode,
};
#[cfg(feature = "paging")]
use crate::paging::PagingMode;
#[derive(Default)]
struct ActiveStyleComponents {
header: bool,
vcs_modification_markers: bool,
grid: bool,
line_numbers: bool,
snip: bool,
}
pub struct PrettyPrinter<'a> {
inputs: Vec<Input<'a>>,
config: Config<'a>,
assets: HighlightingAssets,
highlighted_lines: Vec<LineRange>,
term_width: Option<usize>,
active_style_components: ActiveStyleComponents,
}
impl<'a> PrettyPrinter<'a> {
pub fn new() -> Self {
let mut config = Config::default();
config.colored_output = true;
config.true_color = true;
PrettyPrinter {
inputs: vec![],
config,
assets: HighlightingAssets::from_binary(),
highlighted_lines: vec![],
term_width: None,
active_style_components: ActiveStyleComponents::default(),
}
}
/// Add a file which should be pretty-printed
pub fn input_file(&mut self, path: impl AsRef<OsStr>) -> &mut Self {
self.inputs.push(Input::ordinary_file(path.as_ref()));
self
}
/// Add multiple files which should be pretty-printed
pub fn input_files<I, P>(&mut self, paths: I) -> &mut Self
where
I: IntoIterator<Item = P>,
P: AsRef<OsStr>,
{
for path in paths {
self.inputs.push(Input::ordinary_file(path.as_ref()));
}
self
}
/// Add STDIN as an input
pub fn input_stdin(&mut self) -> &mut Self {
self.inputs.push(Input::stdin());
self
}
/// Add STDIN as an input (with customized name)
pub fn input_stdin_with_name(&mut self, name: impl AsRef<OsStr>) -> &mut Self {
self.inputs
.push(Input::stdin().with_name(Some(name.as_ref())));
self
}
/// Add a byte string as an input
pub fn input_from_bytes(&mut self, content: &'a [u8]) -> &mut Self {
self.input_from_reader(content)
}
/// Add a byte string as an input (with customized name)
pub fn input_from_bytes_with_name(
&mut self,
content: &'a [u8],
name: impl AsRef<OsStr>,
) -> &mut Self {
self.input_from_reader_with_name(content, name)
}
/// Add a custom reader as an input
pub fn input_from_reader<R: Read + 'a>(&mut self, reader: R) -> &mut Self {
self.inputs.push(Input::from_reader(Box::new(reader)));
self
}
/// Add a custom reader as an input (with customized name)
pub fn input_from_reader_with_name<R: Read + 'a>(
&mut self,
reader: R,
name: impl AsRef<OsStr>,
) -> &mut Self {
self.inputs
.push(Input::from_reader(Box::new(reader)).with_name(Some(name.as_ref())));
self
}
/// Specify the syntax file which should be used (default: auto-detect)
pub fn language(&mut self, language: &'a str) -> &mut Self {
self.config.language = Some(language);
self
}
/// The character width of the terminal (default: autodetect)
pub fn term_width(&mut self, width: usize) -> &mut Self {
self.term_width = Some(width);
self
}
/// The width of tab characters (default: None - do not turn tabs to spaces)
pub fn tab_width(&mut self, tab_width: Option<usize>) -> &mut Self {
self.config.tab_width = tab_width.unwrap_or(0);
self
}
/// Whether or not the output should be colorized (default: true)
pub fn colored_output(&mut self, yes: bool) -> &mut Self {
self.config.colored_output = yes;
self
}
/// Whether or not to output 24bit colors (default: true)
pub fn true_color(&mut self, yes: bool) -> &mut Self {
self.config.true_color = yes;
self
}
/// Whether to show a header with the file name
pub fn header(&mut self, yes: bool) -> &mut Self {
self.active_style_components.header = yes;
self
}
/// Whether to show line numbers
pub fn line_numbers(&mut self, yes: bool) -> &mut Self {
self.active_style_components.line_numbers = yes;
self
}
/// Whether to paint a grid, separating line numbers, git changes and the code
pub fn grid(&mut self, yes: bool) -> &mut Self {
self.active_style_components.grid = yes;
self
}
/// Whether to show modification markers for VCS changes
pub fn vcs_modification_markers(&mut self, yes: bool) -> &mut Self {
self.active_style_components.vcs_modification_markers = yes;
self
}
/// Whether to show "snip" markers between visible line ranges (default: no)
pub fn snip(&mut self, yes: bool) -> &mut Self {
self.active_style_components.snip = yes;
self
}
/// Text wrapping mode (default: do not wrap)
pub fn wrapping_mode(&mut self, mode: WrappingMode) -> &mut Self {
self.config.wrapping_mode = mode;
self
}
/// Whether or not to use ANSI italics (default: off)
pub fn use_italics(&mut self, yes: bool) -> &mut Self {
self.config.use_italic_text = yes;
self
}
/// If and how to use a pager (default: no paging)
#[cfg(feature = "paging")]
pub fn paging_mode(&mut self, mode: PagingMode) -> &mut Self {
self.config.paging_mode = mode;
self
}
/// Specify the command to start the pager (default: use "less")
#[cfg(feature = "paging")]
pub fn pager(&mut self, cmd: &'a str) -> &mut Self {
self.config.pager = Some(cmd);
self
}
/// Specify the lines that should be printed (default: all)
pub fn line_ranges(&mut self, ranges: LineRanges) -> &mut Self {
self.config.visible_lines = VisibleLines::Ranges(ranges);
self
}
/// Specify a line that should be highlighted (default: none).
/// This can be called multiple times to highlight more than one
/// line. See also: highlight_range.
pub fn highlight(&mut self, line: usize) -> &mut Self {
self.highlighted_lines.push(LineRange::new(line, line));
self
}
/// Specify a range of lines that should be highlighted (default: none).
/// This can be called multiple times to highlight more than one range
/// of lines.
pub fn highlight_range(&mut self, from: usize, to: usize) -> &mut Self {
self.highlighted_lines.push(LineRange::new(from, to));
self
}
/// Specify the highlighting theme
pub fn theme(&mut self, theme: impl AsRef<str>) -> &mut Self {
self.config.theme = theme.as_ref().to_owned();
self
}
/// Specify custom file extension / file name to syntax mappings
pub fn syntax_mapping(&mut self, mapping: SyntaxMapping<'a>) -> &mut Self {
self.config.syntax_mapping = mapping;
self
}
pub fn themes(&self) -> impl Iterator<Item = &str> {
self.assets.themes()
}
pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
self.assets.syntaxes().iter()
}
/// Pretty-print all specified inputs. This method will "use" all stored inputs.
/// If you want to call 'print' multiple times, you have to call the appropriate
/// input_* methods again.
pub fn print(&mut self) -> Result<bool> {
self.config.highlighted_lines =
HighlightedLineRanges(LineRanges::from(self.highlighted_lines.clone()));
self.config.term_width = self
.term_width
.unwrap_or_else(|| Term::stdout().size().1 as usize);
let mut style_components = vec![];
if self.active_style_components.grid {
style_components.push(StyleComponent::Grid);
}
if self.active_style_components.header {
style_components.push(StyleComponent::Header);
}
if self.active_style_components.line_numbers {
style_components.push(StyleComponent::LineNumbers);
}
if self.active_style_components.snip {
style_components.push(StyleComponent::Snip);
}
if self.active_style_components.vcs_modification_markers {
style_components.push(StyleComponent::Changes);
}
self.config.style_components = StyleComponents::new(&style_components);
let mut inputs: Vec<Input> = vec![];
std::mem::swap(&mut inputs, &mut self.inputs);
let controller = Controller::new(&self.config, &self.assets);
controller.run(inputs)
}
}
+43 -60
View File
@@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::io::Write;
use std::vec::Vec;
@@ -21,21 +20,21 @@ use unicode_width::UnicodeWidthChar;
use crate::assets::HighlightingAssets;
use crate::config::Config;
use crate::decorations::{
Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration,
};
use crate::diff::get_git_diff;
#[cfg(feature = "git")]
use crate::decorations::LineChangesDecoration;
use crate::decorations::{Decoration, GridBorderDecoration, LineNumberDecoration};
#[cfg(feature = "git")]
use crate::diff::LineChanges;
use crate::errors::*;
use crate::inputfile::{InputFile, InputFileReader};
use crate::error::*;
use crate::input::OpenedInput;
use crate::line_range::RangeCheckResult;
use crate::preprocessor::{expand_tabs, replace_nonprintable};
use crate::terminal::{as_terminal_escaped, to_ansi_color};
use crate::wrap::OutputWrap;
use crate::wrapping::WrappingMode;
pub trait Printer {
fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()>;
fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()>;
pub(crate) trait Printer {
fn print_header(&mut self, handle: &mut dyn Write, input: &OpenedInput) -> Result<()>;
fn print_footer(&mut self, handle: &mut dyn Write, input: &OpenedInput) -> Result<()>;
fn print_snip(&mut self, handle: &mut dyn Write) -> Result<()>;
@@ -57,11 +56,11 @@ impl SimplePrinter {
}
impl Printer for SimplePrinter {
fn print_header(&mut self, _handle: &mut dyn Write, _file: InputFile) -> Result<()> {
fn print_header(&mut self, _handle: &mut dyn Write, _input: &OpenedInput) -> Result<()> {
Ok(())
}
fn print_footer(&mut self, _handle: &mut dyn Write) -> Result<()> {
fn print_footer(&mut self, _handle: &mut dyn Write, _input: &OpenedInput) -> Result<()> {
Ok(())
}
@@ -83,25 +82,27 @@ impl Printer for SimplePrinter {
}
}
pub struct InteractivePrinter<'a> {
pub(crate) struct InteractivePrinter<'a> {
colors: Colors,
config: &'a Config<'a>,
decorations: Vec<Box<dyn Decoration>>,
panel_width: usize,
ansi_prefix_sgr: String,
content_type: Option<ContentType>,
pub line_changes: Option<LineChanges>,
#[cfg(feature = "git")]
pub line_changes: &'a Option<LineChanges>,
highlighter: Option<HighlightLines<'a>>,
syntax_set: &'a SyntaxSet,
background_color_highlight: Option<Color>,
}
impl<'a> InteractivePrinter<'a> {
pub fn new(
pub(crate) fn new(
config: &'a Config,
assets: &'a HighlightingAssets,
file: InputFile,
reader: &mut InputFileReader,
input: &mut OpenedInput,
#[cfg(feature = "git")]
line_changes: &'a Option<LineChanges>,
) -> Self {
let theme = assets.get_theme(&config.theme);
@@ -120,8 +121,11 @@ impl<'a> InteractivePrinter<'a> {
decorations.push(Box::new(LineNumberDecoration::new(&colors)));
}
if config.style_components.changes() {
decorations.push(Box::new(LineChangesDecoration::new(&colors)));
#[cfg(feature = "git")]
{
if config.style_components.changes() {
decorations.push(Box::new(LineChangesDecoration::new(&colors)));
}
}
let mut panel_width: usize =
@@ -143,26 +147,15 @@ impl<'a> InteractivePrinter<'a> {
panel_width = 0;
}
let mut line_changes = None;
let highlighter = if reader
let highlighter = if input
.reader
.content_type
.map_or(false, |c| c.is_binary() && !config.show_nonprintable)
{
None
} else {
// Get the Git modifications
line_changes = if config.style_components.changes() {
match file {
InputFile::Ordinary(filename) => get_git_diff(filename),
_ => None,
}
} else {
None
};
// Determine the type of syntax for highlighting
let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping);
let syntax = assets.get_syntax(config.language, input, &config.syntax_mapping);
Some(HighlightLines::new(syntax, theme))
};
@@ -171,8 +164,9 @@ impl<'a> InteractivePrinter<'a> {
colors,
config,
decorations,
content_type: reader.content_type,
content_type: input.reader.content_type,
ansi_prefix_sgr: String::new(),
#[cfg(feature = "git")]
line_changes,
highlighter,
syntax_set: &assets.syntax_set,
@@ -209,7 +203,7 @@ impl<'a> InteractivePrinter<'a> {
if self.config.style_components.grid() {
format!("{}", text_filled)
} else {
format!("{}", text_filled)
text_filled
}
}
}
@@ -218,34 +212,26 @@ impl<'a> InteractivePrinter<'a> {
if self.config.tab_width > 0 {
expand_tabs(text, self.config.tab_width, cursor)
} else {
*cursor += text.len();
text.to_string()
}
}
}
impl<'a> Printer for InteractivePrinter<'a> {
fn print_header(&mut self, handle: &mut dyn Write, file: InputFile) -> Result<()> {
fn print_header(&mut self, handle: &mut dyn Write, input: &OpenedInput) -> Result<()> {
if !self.config.style_components.header() {
if Some(ContentType::BINARY) == self.content_type && !self.config.show_nonprintable {
let input = match file {
InputFile::Ordinary(filename) => {
format!("file '{}'", filename.to_string_lossy())
}
_ => "STDIN".into(),
};
writeln!(
handle,
"{}: Binary content from {} will not be printed to the terminal \
(but will be present if the output of 'bat' is piped). You can use 'bat -A' \
to show the binary file contents.",
Yellow.paint("[bat warning]"),
input
input.description().full,
)?;
} else {
if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;
}
} else if self.config.style_components.grid() {
self.print_horizontal_line(handle, '┬')?;
}
return Ok(());
}
@@ -265,11 +251,6 @@ impl<'a> Printer for InteractivePrinter<'a> {
write!(handle, "{}", " ".repeat(self.panel_width))?;
}
let (prefix, name) = match file {
InputFile::Ordinary(filename) => ("File: ", filename.to_string_lossy()),
_ => ("", Cow::from("STDIN")),
};
let mode = match self.content_type {
Some(ContentType::BINARY) => " <BINARY>",
Some(ContentType::UTF_16LE) => " <UTF-16LE>",
@@ -278,11 +259,13 @@ impl<'a> Printer for InteractivePrinter<'a> {
_ => "",
};
let description = input.description();
writeln!(
handle,
"{}{}{}",
prefix,
self.colors.filename.paint(name),
description.prefix,
self.colors.filename.paint(&description.name),
mode
)?;
@@ -297,7 +280,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
Ok(())
}
fn print_footer(&mut self, handle: &mut dyn Write) -> Result<()> {
fn print_footer(&mut self, handle: &mut dyn Write, _input: &OpenedInput) -> Result<()> {
if self.config.style_components.grid()
&& (self.content_type.map_or(false, |c| c.is_text()) || self.config.show_nonprintable)
{
@@ -320,9 +303,9 @@ impl<'a> Printer for InteractivePrinter<'a> {
let snip_right =
"".repeat((self.config.term_width - panel_count - snip_left_count - title_count) / 2);
write!(
writeln!(
handle,
"{}\n",
"{}",
self.colors
.grid
.paint(format!("{}{}{}{}", panel, snip_left, title, snip_right))
@@ -397,7 +380,7 @@ impl<'a> Printer for InteractivePrinter<'a> {
}
// Line contents.
if self.config.output_wrap == OutputWrap::None {
if self.config.wrapping_mode == WrappingMode::NoWrapping {
let true_color = self.config.true_color;
let colored_output = self.config.colored_output;
let italics = self.config.use_italic_text;
+7 -6
View File
@@ -1,7 +1,7 @@
use std::collections::HashSet;
use std::str::FromStr;
use crate::errors::*;
use crate::error::*;
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum StyleComponent {
@@ -9,7 +9,7 @@ pub enum StyleComponent {
Changes,
Grid,
Header,
Numbers,
LineNumbers,
Snip,
Full,
Plain,
@@ -28,13 +28,13 @@ impl StyleComponent {
StyleComponent::Changes => &[StyleComponent::Changes],
StyleComponent::Grid => &[StyleComponent::Grid],
StyleComponent::Header => &[StyleComponent::Header],
StyleComponent::Numbers => &[StyleComponent::Numbers],
StyleComponent::LineNumbers => &[StyleComponent::LineNumbers],
StyleComponent::Snip => &[StyleComponent::Snip],
StyleComponent::Full => &[
StyleComponent::Changes,
StyleComponent::Grid,
StyleComponent::Header,
StyleComponent::Numbers,
StyleComponent::LineNumbers,
StyleComponent::Snip,
],
StyleComponent::Plain => &[],
@@ -51,7 +51,7 @@ impl FromStr for StyleComponent {
"changes" => Ok(StyleComponent::Changes),
"grid" => Ok(StyleComponent::Grid),
"header" => Ok(StyleComponent::Header),
"numbers" => Ok(StyleComponent::Numbers),
"numbers" => Ok(StyleComponent::LineNumbers),
"snip" => Ok(StyleComponent::Snip),
"full" => Ok(StyleComponent::Full),
"plain" => Ok(StyleComponent::Plain),
@@ -68,6 +68,7 @@ impl StyleComponents {
StyleComponents(components.iter().cloned().collect())
}
#[cfg(feature = "git")]
pub fn changes(&self) -> bool {
self.0.contains(&StyleComponent::Changes)
}
@@ -81,7 +82,7 @@ impl StyleComponents {
}
pub fn numbers(&self) -> bool {
self.0.contains(&StyleComponent::Numbers)
self.0.contains(&StyleComponent::LineNumbers)
}
pub fn snip(&self) -> bool {
+7 -1
View File
@@ -1,6 +1,6 @@
use std::path::Path;
use crate::errors::Result;
use crate::error::Result;
use globset::{Candidate, GlobBuilder, GlobMatcher};
@@ -29,6 +29,12 @@ impl<'a> SyntaxMapping<'a> {
mapping
.insert("**/.ssh/config", MappingTarget::MapTo("SSH Config"))
.unwrap();
mapping
.insert(
"**/bat/config",
MappingTarget::MapTo("Bourne Again Shell (bash)"),
)
.unwrap();
mapping
.insert(
"/etc/profile",
+26 -6
View File
@@ -1,17 +1,37 @@
extern crate ansi_colours;
use ansi_term::Colour::{Fixed, RGB};
use ansi_term::Color::{self, Fixed, RGB};
use ansi_term::{self, Style};
use syntect::highlighting::{self, FontStyle};
pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Colour {
pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Color {
if color.a == 0 {
// Themes can specify one of the user-configurable terminal colors by
// encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set
// to the color palette number. The built-in themes ansi-light,
// to the 8-bit color palette number. The built-in themes ansi-light,
// ansi-dark, and base16 use this.
Fixed(color.r)
match color.r {
// For the first 8 colors, use the Color enum to produce ANSI escape
// sequences using codes 30-37 (foreground) and 40-47 (background).
// For example, red foreground is \x1b[31m. This works on terminals
// without 256-color support.
0x00 => Color::Black,
0x01 => Color::Red,
0x02 => Color::Green,
0x03 => Color::Yellow,
0x04 => Color::Blue,
0x05 => Color::Purple,
0x06 => Color::Cyan,
0x07 => Color::White,
// For all other colors, use Fixed to produce escape sequences using
// codes 38;5 (foreground) and 48;5 (background). For example,
// bright red foreground is \x1b[38;5;9m. This only works on
// terminals with 256-color support.
//
// TODO: When ansi_term adds support for bright variants using codes
// 90-97 (foreground) and 100-107 (background), we should use those
// for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff.
n => Fixed(n),
}
} else if true_color {
RGB(color.r, color.g, color.b)
} else {
-11
View File
@@ -1,11 +0,0 @@
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum OutputWrap {
Character,
None,
}
impl Default for OutputWrap {
fn default() -> Self {
OutputWrap::None
}
}
+11
View File
@@ -0,0 +1,11 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WrappingMode {
Character,
NoWrapping,
}
impl Default for WrappingMode {
fn default() -> Self {
WrappingMode::NoWrapping
}
}
@@ -0,0 +1,3 @@
```javascript
var test = "boom";
```
@@ -0,0 +1,4 @@
Test
<div>
</div>
@@ -0,0 +1,4 @@
module Main where
main :: IO ()
main = putStrLn "Please show my file :c"
@@ -0,0 +1,3 @@
<<END_DESC
Test
END_DESC
@@ -0,0 +1,2 @@
<style lang="stylus">
</style>
BIN
View File
Binary file not shown.
+129
View File
@@ -541,3 +541,132 @@ fn empty_file_leads_to_empty_output_with_grid_enabled() {
.success()
.stdout("");
}
#[test]
fn filename_basic() {
bat()
.arg("test.txt")
.arg("--decorations=always")
.arg("--style=header")
.arg("-r=0:0")
.arg("--file-name=foo")
.assert()
.success()
.stdout("File: foo\n")
.stderr("");
}
#[test]
fn filename_binary() {
bat()
.arg("test.binary")
.arg("--decorations=always")
.arg("--style=header")
.arg("-r=0:0")
.arg("--file-name=foo")
.assert()
.success()
.stdout("File: foo <BINARY>\n")
.stderr("");
}
#[test]
fn filename_stdin() {
bat()
.arg("--decorations=always")
.arg("--style=header")
.arg("-r=0:0")
.arg("-")
.write_stdin("stdin\n")
.arg("--file-name=foo")
.assert()
.success()
.stdout("File: foo\n")
.stderr("");
}
#[test]
fn filename_stdin_binary() {
let vec = vec![0; 1];
bat_with_config()
.arg("--decorations=always")
.arg("--style=header")
.write_stdin(vec)
.arg("--file-name=foo")
.assert()
.success()
.stdout("File: foo <BINARY>\n")
.stderr("");
}
#[test]
fn filename_multiple_ok() {
bat()
.arg("--decorations=always")
.arg("--style=header")
.arg("-r=0:0")
.arg("test.txt")
.arg("--file-name=foo")
.arg("single-line.txt")
.arg("--file-name=bar")
.assert()
.success()
.stdout("File: foo\nFile: bar\n")
.stderr("");
}
#[test]
fn filename_multiple_err() {
bat()
.arg("--decorations=always")
.arg("--style=header")
.arg("-r=0:0")
.arg("test.txt")
.arg("--file-name=foo")
.arg("single-line.txt")
.assert()
.failure();
}
#[cfg(target_os = "linux")]
#[test]
fn file_with_invalid_utf8_filename() {
use std::ffi::OsStr;
use std::fs::File;
use std::io::Write;
use std::os::unix::ffi::OsStrExt;
use tempdir::TempDir;
let tmp_dir = TempDir::new("bat_test").expect("can create temporary directory");
let file_path = tmp_dir
.path()
.join(OsStr::from_bytes(b"test-invalid-utf8-\xC3(.rs"));
{
let mut file = File::create(&file_path).expect("can create temporary file");
writeln!(file, "dummy content").expect("can write to file");
}
bat()
.arg(file_path.as_os_str())
.assert()
.success()
.stdout("dummy content\n");
}
#[test]
fn do_not_panic_regression_tests() {
for filename in &[
"issue_28.md",
"issue_190.md",
"issue_314.hs",
"issue_914.rb",
"issue_915.vue",
] {
bat()
.arg("--color=always")
.arg(&format!("regression_tests/{}", filename))
.assert()
.success();
}
}
+2 -2
View File
@@ -1,10 +1,10 @@
use std::collections::HashSet;
use bat::HighlightingAssets;
use bat::assets::HighlightingAssets;
#[test]
fn no_duplicate_extensions() {
const KNOWN_EXCEPTIONS: &[&'static str] = &[
const KNOWN_EXCEPTIONS: &[&str] = &[
// The '.h' extension currently appears in multiple syntaxes: C, C++, Objective C,
// Objective C++
"h",
@@ -0,0 +1,125 @@
#!/usr/bin/env python
#
# This script goes through all languages that are supported by 'bat'. For each
# language, it loops over the correspoinding file extensions and searches a
# given folder for matching files. It calls 'bat' for each of these files and
# measures the highlighting speed (number of characters per second). The script
# reports files which lead to slow highlighting speeds or errors during the
# execution of 'bat'.
#
# Requirements (external programs):
# - bat (in the $PATH)
# - fd (https://github.com/sharkdp/fd)
# - wc
import sys
import time
import os
import subprocess as sp
# Threshold speed, characters per second
THRESHOLD_SPEED = 20000
# Maximum time we allow `bat` to run
BAT_TIMEOUT_SEC = 10
# Maximum number of files to measure
MAX_NUM_FILES = 100
# Root folder for the search
SEARCH_ROOT = os.getenv("HOME")
def find_slow_files(startup_time, glob_pattern, language=None):
out = sp.check_output(
[
"fd",
"--hidden",
"--no-ignore",
"--type=file",
"--max-results",
str(MAX_NUM_FILES),
"--glob",
glob_pattern,
SEARCH_ROOT,
]
)
paths = out.split(b"\n")[:-1]
language_text = f"Language {language}, " if language else ""
print(f"{language_text}glob pattern: {glob_pattern} ({len(paths)} matches)")
for path in paths:
num_chars = int(sp.check_output(["wc", "-c", path]).split(b" ")[0].decode())
if num_chars < 500:
# It is hard to measure the exact speed for short files
continue
try:
start = time.time()
sp.check_output(["bat", "--color=always", path], timeout=BAT_TIMEOUT_SEC)
duration = time.time() - start - startup_time
if duration <= 0:
continue
highlighting_speed = num_chars / duration
if highlighting_speed < THRESHOLD_SPEED:
print(f" {highlighting_speed:10.0f} chars/s: {path.decode()}")
except sp.CalledProcessError:
print(f" Error while highlighting file '{path.decode()}'.")
except sp.TimeoutExpired:
if num_chars < THRESHOLD_SPEED * BAT_TIMEOUT_SEC:
print(f" Error: bat timed out on file '{path.decode()}'.")
else:
print(
f" Warning: bat timed out on file '{path.decode()} (but the file is large)."
)
def measure_bat_startup_speed():
min_duration = None
for _ in range(20):
start = time.time()
p = sp.Popen(
["bat", "--color=always", "--language=py"], stdin=sp.PIPE, stdout=sp.PIPE
)
p.communicate(input=b"test")
duration = time.time() - start
if not min_duration or duration < min_duration:
min_duration = duration
return min_duration
def traverse_all_languages(startup_time):
output = sp.check_output(["bat", "--list-languages"]).decode()
for line in output.strip().split("\n"):
language, extensions = line.split(":")
for ext in extensions.split(","):
find_slow_files(startup_time, ext, language)
if not ext.startswith("."):
find_slow_files(startup_time, f"*.{ext}", language)
def main():
print("Measuring 'bat' startup speed ... ", flush=True, end="")
startup_time = measure_bat_startup_speed()
print(f"{startup_time * 1000:.1f} ms")
if len(sys.argv) == 1:
traverse_all_languages(startup_time)
else:
pattern = sys.argv[1]
find_slow_files(startup_time, pattern)
if __name__ == "__main__":
main()
+4 -6
View File
@@ -4,13 +4,11 @@ use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
extern crate tempdir;
use self::tempdir::TempDir;
use tempdir::TempDir;
extern crate git2;
use self::git2::build::CheckoutBuilder;
use self::git2::Repository;
use self::git2::Signature;
use git2::build::CheckoutBuilder;
use git2::Repository;
use git2::Signature;
pub struct BatTester {
/// Temporary working directory