mirror of
https://github.com/sharkdp/bat.git
synced 2026-10-31 11:05:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c073d4a118 | ||
|
|
e79c5516bf | ||
|
|
4776c2a575 | ||
|
|
f98a96c1ed | ||
|
|
8a3446d219 | ||
|
|
5d5bf616ca | ||
|
|
b68c038db8 | ||
|
|
1117d2f9cb | ||
|
|
d0ba5bcb32 | ||
|
|
f8d39d5624 | ||
|
|
9998e2eac6 | ||
|
|
7cac07d845 | ||
|
|
148caa1381 | ||
|
|
ab27cac0cc | ||
|
|
01fcbf2183 | ||
|
|
8a0c30b385 | ||
|
|
bc21c4d0e0 | ||
|
|
0800562ba2 | ||
|
|
1f9befbcd1 | ||
|
|
a5c779c518 | ||
|
|
f37f100352 | ||
|
|
18d1a3b973 | ||
|
|
dc10246bd2 | ||
|
|
44bdae0212 | ||
|
|
1ece38a4c4 | ||
|
|
e90308e1f8 | ||
|
|
d702d6740c | ||
|
|
c6a526f99c | ||
|
|
278d841408 | ||
|
|
5f576f1ecf | ||
|
|
48c6ea6b25 | ||
|
|
558134f6c8 | ||
|
|
1dd57e6d7e | ||
|
|
20ba84d1fa | ||
|
|
e81f9b23e6 | ||
|
|
50dc4a79b0 | ||
|
|
8fa8f44706 | ||
|
|
ecd862d9ff | ||
|
|
cbed338c3a | ||
|
|
4492d99556 | ||
|
|
d96ee2261f | ||
|
|
dda27b253b | ||
|
|
78f26d0caf | ||
|
|
abcd09f869 | ||
|
|
41a5ff039b | ||
|
|
155bad3862 | ||
|
|
7c98a1c901 | ||
|
|
c1246fcd53 | ||
|
|
9f401ea122 | ||
|
|
9c782fb692 | ||
|
|
154186a58d | ||
|
|
b22a9f8fe3 | ||
|
|
5219c40ed8 | ||
|
|
a25ee0e6fd | ||
|
|
496e0bc046 | ||
|
|
7082fd09f0 | ||
|
|
857cc215b4 | ||
|
|
481268f018 | ||
|
|
f7d9f49503 | ||
|
|
88fcfd76aa | ||
|
|
76e34b29ec | ||
|
|
01984e1d02 | ||
|
|
536390509f | ||
|
|
58198d0700 | ||
|
|
afc5aacb28 | ||
|
|
30b742e984 | ||
|
|
671deeef8c | ||
|
|
a892caeb33 | ||
|
|
a770cbf25e | ||
|
|
931c6e9414 | ||
|
|
8dc7e2efa3 | ||
|
|
10965a6122 | ||
|
|
e43d97dc15 | ||
|
|
2c7087b8de | ||
|
|
bb1f5aa841 | ||
|
|
2109a7830b | ||
|
|
ec27c78a8a | ||
|
|
67fe833bc8 | ||
|
|
e956225b4d | ||
|
|
314ec881ef | ||
|
|
25a6a55437 | ||
|
|
5bff4548be | ||
|
|
866b9e16a8 | ||
|
|
5af176c94f | ||
|
|
9e7da05459 | ||
|
|
1ae02c65fb | ||
|
|
495e7fd3b1 | ||
|
|
b48f0fe389 | ||
|
|
c19c704a43 | ||
|
|
693bd5929d | ||
|
|
8275b0436d | ||
|
|
278bde5cee | ||
|
|
eee7e5a575 | ||
|
|
ad7b634ee6 | ||
|
|
1310f83c8d | ||
|
|
897b9e7030 | ||
|
|
1891e194b5 | ||
|
|
d2d01b9fe8 | ||
|
|
5842d58c01 | ||
|
|
e3c71adba7 | ||
|
|
504f28b3a1 | ||
|
|
d977ba9194 | ||
|
|
9c09799e1f | ||
|
|
d83c93c805 | ||
|
|
fc8fd1e63f | ||
|
|
404d6a4a81 | ||
|
|
25d96da4a2 | ||
|
|
e97095b724 | ||
|
|
ce96df00b6 | ||
|
|
f98fc5f06a | ||
|
|
1dbb4ef683 | ||
|
|
0502a3bd4a | ||
|
|
869cf6368c | ||
|
|
078228deac | ||
|
|
e09d7dabb8 | ||
|
|
6d1cc8c2c8 | ||
|
|
87f021078e | ||
|
|
d5b0502419 | ||
|
|
860f3e9006 | ||
|
|
1be346a038 | ||
|
|
f9fd5e4851 | ||
|
|
da09f3877b | ||
|
|
e30e1bab2e | ||
|
|
f140f6da46 | ||
|
|
3446fdf5f2 | ||
|
|
e1345cdc2a | ||
|
|
a2676ccc55 | ||
|
|
b39e28d2c8 | ||
|
|
0d71968615 | ||
|
|
80da0dc619 | ||
|
|
64903bb858 | ||
|
|
3456ce1187 | ||
|
|
886a5b3d24 | ||
|
|
238482e5f3 | ||
|
|
79b960e17e | ||
|
|
6aa626f1c4 | ||
|
|
1b30539493 | ||
|
|
96b24d8a7e | ||
|
|
20b02e72b3 | ||
|
|
254efac9c2 | ||
|
|
dcec322117 | ||
|
|
d781156327 | ||
|
|
732750c274 | ||
|
|
6b6baa3118 | ||
|
|
54c434934e | ||
|
|
ad6e0a2581 | ||
|
|
bd619db3ca | ||
|
|
e098eb43a2 | ||
|
|
ea369ee17f | ||
|
|
63d32bc818 | ||
|
|
dccf8d8221 | ||
|
|
97129ab9d8 | ||
|
|
52d0d6ca30 | ||
|
|
a05494e9d9 | ||
|
|
84ac92efbb | ||
|
|
b4c6e412dc | ||
|
|
3e21d69a92 | ||
|
|
b7690d4bb1 | ||
|
|
d404139ff7 | ||
|
|
9ef1e9f792 | ||
|
|
503fe0b641 | ||
|
|
1807f9653c | ||
|
|
1e74f0e2a9 | ||
|
|
c1e1f753cf | ||
|
|
d90797f8e9 | ||
|
|
b4096e5627 | ||
|
|
4e3ab4d399 | ||
|
|
f0d936763c | ||
|
|
7897260bf0 | ||
|
|
eb6e43b9a9 | ||
|
|
7cdcdbb31d | ||
|
|
9159341714 | ||
|
|
b23ff24ebc | ||
|
|
e1ecc17f69 | ||
|
|
0fe3badf19 | ||
|
|
89d4cb951a | ||
|
|
e9681bf22d | ||
|
|
1b6df8a480 | ||
|
|
cf24986edb | ||
|
|
b9c556a0b6 | ||
|
|
9d6bde48fa | ||
|
|
d6c8fee044 | ||
|
|
8b4abb03db | ||
|
|
375cf76e15 | ||
|
|
1a4acc435e | ||
|
|
d8030626f8 | ||
|
|
fb61aa4f60 | ||
|
|
76df41fa97 | ||
|
|
97efd75cee | ||
|
|
53d0c1deca | ||
|
|
155179a07a | ||
|
|
ba00caf9c3 | ||
|
|
ac418ba21c | ||
|
|
acde83af31 | ||
|
|
021a68eb21 | ||
|
|
566aab3b05 | ||
|
|
f88eba6476 | ||
|
|
1d9e7ac4c2 | ||
|
|
80ae2ecbf8 | ||
|
|
96d4492d27 | ||
|
|
c90f0b8e21 | ||
|
|
4b866ab6af | ||
|
|
2dbd91e543 | ||
|
|
785da3d9df | ||
|
|
d455cb7c32 | ||
|
|
d2b4766f92 | ||
|
|
5cf92b4f94 | ||
|
|
7b803a50af | ||
|
|
67ec5fe2b7 | ||
|
|
3447ed4def | ||
|
|
532fb921a6 | ||
|
|
ba4d5a2e7d | ||
|
|
026273a05c | ||
|
|
8903b64830 | ||
|
|
194155f062 | ||
|
|
a6ff3b900d | ||
|
|
8a52bcf92d | ||
|
|
0b1535f63b | ||
|
|
0a5b3eaf3e | ||
|
|
11294249f8 | ||
|
|
35a1236ad0 | ||
|
|
af8da50c2c | ||
|
|
649d1e3df2 | ||
|
|
199cfd4519 | ||
|
|
c7cce0752b | ||
|
|
3a89486189 | ||
|
|
720a2fb7a5 | ||
|
|
e3c990f11e | ||
|
|
90c7d0c365 | ||
|
|
56002267d2 | ||
|
|
84734eac9d | ||
|
|
acdea97e71 | ||
|
|
839e4ee611 | ||
|
|
c7afcdebf8 | ||
|
|
08e6db1581 | ||
|
|
4d08ecd828 | ||
|
|
28e1d0e613 | ||
|
|
d11839ad03 | ||
|
|
8cacd9b432 | ||
|
|
6f67444c99 | ||
|
|
fd2b376ea0 | ||
|
|
cda54e4f61 | ||
|
|
3b90ada735 | ||
|
|
1837d364fe | ||
|
|
4ea3e6bbb7 | ||
|
|
38544ed735 | ||
|
|
5444b0dfed | ||
|
|
f3dde0185d | ||
|
|
2e3784aa30 | ||
|
|
1088455702 | ||
|
|
4f812c32ab | ||
|
|
b6c59a5bd4 | ||
|
|
b682679776 | ||
|
|
81771432fb | ||
|
|
627181bcb6 | ||
|
|
226d9a573a | ||
|
|
246cf79dbd | ||
|
|
ea955c734d | ||
|
|
6223ad6d52 | ||
|
|
31a9335bd5 | ||
|
|
9316f2a758 | ||
|
|
c884c3cc12 | ||
|
|
08850f0474 | ||
|
|
8e6c5bc562 | ||
|
|
052425b12f | ||
|
|
2df3305b94 | ||
|
|
6882fc1512 |
No files matched your search
+52
-1
@@ -6,7 +6,7 @@
|
||||
url = https://github.com/sublimehq/Packages/
|
||||
[submodule "assets/syntaxes/TOML"]
|
||||
path = assets/syntaxes/TOML
|
||||
url = https://github.com/sharkdp/sublime_toml_highlighting
|
||||
url = https://github.com/jasonwilliams/sublime_toml_highlighting
|
||||
[submodule "assets/syntaxes/Julia"]
|
||||
path = assets/syntaxes/Julia
|
||||
url = https://github.com/JuliaEditorSupport/Julia-sublime
|
||||
@@ -46,3 +46,54 @@
|
||||
[submodule "assets/syntaxes/Nix"]
|
||||
path = assets/syntaxes/Nix
|
||||
url = https://github.com/wmertens/sublime-nix
|
||||
[submodule "assets/themes/zenburn"]
|
||||
path = assets/themes/zenburn
|
||||
url = https://github.com/colinta/zenburn.git
|
||||
[submodule "assets/syntaxes/Kotlin"]
|
||||
path = assets/syntaxes/Kotlin
|
||||
url = https://github.com/vkostyukov/kotlin-sublime-package
|
||||
[submodule "assets/syntaxes/Elm"]
|
||||
path = assets/syntaxes/Elm
|
||||
url = https://github.com/elm-community/SublimeElmLanguageSupport
|
||||
[submodule "assets/syntaxes/TypeScript"]
|
||||
path = assets/syntaxes/TypeScript
|
||||
url = https://github.com/Microsoft/TypeScript-Sublime-Plugin
|
||||
[submodule "assets/syntaxes/Puppet"]
|
||||
path = assets/syntaxes/Puppet
|
||||
url = https://github.com/russCloak/SublimePuppet
|
||||
[submodule "assets/syntaxes/CSV"]
|
||||
path = assets/syntaxes/CSV
|
||||
url = https://github.com/wadetb/Sublime-Text-Advanced-CSV
|
||||
[submodule "assets/themes/onehalf"]
|
||||
path = assets/themes/onehalf
|
||||
url = https://github.com/sonph/onehalf
|
||||
[submodule "assets/syntaxes/JavaScript (Babel)"]
|
||||
path = assets/syntaxes/JavaScript (Babel)
|
||||
url = https://github.com/babel/babel-sublime
|
||||
[submodule "assets/syntaxes/Cabal"]
|
||||
path = assets/syntaxes/Cabal
|
||||
url = https://github.com/SublimeHaskell/SublimeHaskell
|
||||
[submodule "assets/syntaxes/Dart"]
|
||||
path = assets/syntaxes/Dart
|
||||
url = https://github.com/guillermooo/dart-sublime-bundle
|
||||
[submodule "assets/syntaxes/FSharp"]
|
||||
path = assets/syntaxes/FSharp
|
||||
url = https://github.com/hoest/sublimetext-fsharp
|
||||
[submodule "assets/syntaxes/PureScript"]
|
||||
path = assets/syntaxes/PureScript
|
||||
url = https://github.com/tellnobody1/sublime-purescript-syntax
|
||||
[submodule "assets/syntaxes/Swift"]
|
||||
path = assets/syntaxes/Swift
|
||||
url = https://github.com/quiqueg/Swift-Sublime-Package
|
||||
[submodule "assets/syntaxes/Crystal"]
|
||||
path = assets/syntaxes/Crystal
|
||||
url = https://github.com/crystal-lang-tools/sublime-crystal.git
|
||||
[submodule "assets/syntaxes/PowerShell"]
|
||||
path = assets/syntaxes/PowerShell
|
||||
url = https://github.com/PowerShell/EditorSyntax
|
||||
[submodule "assets/syntaxes/Robot"]
|
||||
path = assets/syntaxes/Robot
|
||||
url = https://github.com/andriyko/sublime-robot-framework-assistant.git
|
||||
[submodule "assets/themes/sublime-snazzy"]
|
||||
path = assets/themes/sublime-snazzy
|
||||
url = https://github.com/greggb/sublime-snazzy
|
||||
+17
-4
@@ -1,5 +1,5 @@
|
||||
language: rust
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Stable channel.
|
||||
@@ -23,18 +23,24 @@ matrix:
|
||||
- TARGET=arm-unknown-linux-gnueabihf
|
||||
- CC_arm_unknown_linux_gnueabihf=/usr/bin/arm-linux-gnueabihf-gcc-4.8
|
||||
- CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc-4.8
|
||||
- os: linux
|
||||
rust: stable
|
||||
env:
|
||||
- TARGET=aarch64-unknown-linux-gnu
|
||||
- CC_aarch64-unknown-linux-gnu=/usr/bin/aarch64-linux-gnu-gcc-4.8
|
||||
- CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc-4.8
|
||||
|
||||
# Minimum Rust supported channel.
|
||||
- os: linux
|
||||
rust: 1.26.0
|
||||
rust: 1.29.0
|
||||
env: TARGET=x86_64-unknown-linux-gnu
|
||||
- os: linux
|
||||
rust: 1.26.0
|
||||
rust: 1.29.0
|
||||
env:
|
||||
- TARGET=x86_64-unknown-linux-musl
|
||||
- CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc
|
||||
- os: osx
|
||||
rust: 1.26.0
|
||||
rust: 1.29.0
|
||||
env: TARGET=x86_64-apple-darwin
|
||||
|
||||
# Code formatting check
|
||||
@@ -97,3 +103,10 @@ deploy:
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
|
||||
# Caching
|
||||
cache:
|
||||
directories:
|
||||
- /home/travis/.cargo
|
||||
before_cache:
|
||||
- rm -rf /home/travis/.cargo/registry
|
||||
Generated
+481
-217
File diff suppressed because it is too large.
Load diff
+15
-4
@@ -7,18 +7,24 @@ license = "MIT/Apache-2.0"
|
||||
name = "bat"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/bat"
|
||||
version = "0.5.0"
|
||||
version = "0.9.0"
|
||||
exclude = [
|
||||
"assets/syntaxes/*",
|
||||
"assets/themes/*",
|
||||
]
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
atty = "0.2.2"
|
||||
ansi_term = "0.11"
|
||||
ansi_colours = "^1.0"
|
||||
console = "0.6"
|
||||
directories = "1.0"
|
||||
lazy_static = "1.0"
|
||||
lazy_static = "1.2"
|
||||
wild = "2.0"
|
||||
content_inspector = "0.2.4"
|
||||
encoding = "0.2"
|
||||
shell-words = "0.1.0"
|
||||
|
||||
[dependencies.git2]
|
||||
version = "0.7"
|
||||
@@ -26,12 +32,12 @@ default-features = false
|
||||
features = []
|
||||
|
||||
[dependencies.syntect]
|
||||
version = "2.1"
|
||||
version = "3.0.2"
|
||||
default-features = false
|
||||
features = ["parsing", "yaml-load", "dump-load", "dump-create"]
|
||||
|
||||
[dependencies.clap]
|
||||
version = "2"
|
||||
version = "2.32"
|
||||
default-features = false
|
||||
features = ["suggestions", "color", "wrap_help"]
|
||||
|
||||
@@ -42,3 +48,8 @@ features = []
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3"
|
||||
assert_cmd = "0.10.1"
|
||||
escargot = "0.3.1"
|
||||
|
||||
[build-dependencies]
|
||||
clap = "2.32"
|
||||
@@ -1,6 +1,7 @@
|
||||
<p align="center">
|
||||
<img src="doc/logo-header.svg" alt="bat - a cat clone with wings"><br>
|
||||
<a href="https://travis-ci.org/sharkdp/bat"><img src="https://travis-ci.org/sharkdp/bat.svg?branch=master" alt="Build Status"></a>
|
||||
<a href="https://ci.appveyor.com/project/sharkdp/bat"><img src="https://ci.appveyor.com/api/projects/status/cptsmtbiwbnr2vhf?svg=true"></a>
|
||||
<img src="https://img.shields.io/crates/l/bat.svg" alt="license">
|
||||
<a href="https://crates.io/crates/bat"><img src="https://img.shields.io/crates/v/bat.svg?colorB=319e8c" alt="Version info"></a><br>
|
||||
A <i>cat(1)</i> clone with syntax highlighting and Git integration.
|
||||
@@ -11,7 +12,8 @@
|
||||
<a href="#how-to-use">How To Use</a> •
|
||||
<a href="#installation">Installation</a> •
|
||||
<a href="#customization">Customization</a> •
|
||||
<a href="#project-goals-and-alternatives">Project goals, alternatives</a>
|
||||
<a href="#project-goals-and-alternatives">Project goals, alternatives</a> •
|
||||
Translation [<a href="https://github.com/chinanf-boy/bat-zh">中文</a>]
|
||||
</p>
|
||||
|
||||
### Syntax highlighting
|
||||
@@ -28,6 +30,10 @@ languages:
|
||||
|
||||

|
||||
|
||||
### Show non-printable characters
|
||||
|
||||

|
||||
|
||||
### Automatic paging
|
||||
|
||||
`bat` can pipe its own output to `less` if the output is too large for one screen.
|
||||
@@ -52,15 +58,18 @@ Display multiple files at once
|
||||
> bat src/*.rs
|
||||
```
|
||||
|
||||
Read from stdin, explicitly specify the language
|
||||
Read from stdin, determine the syntax automatically
|
||||
|
||||
```bash
|
||||
> curl -s https://sh.rustup.rs | bat
|
||||
```
|
||||
|
||||
Read from stdin, specify the language explicitly
|
||||
|
||||
```bash
|
||||
> yaml2json .travis.yml | json_pp | bat -l json
|
||||
```
|
||||
|
||||
```bash
|
||||
> curl -s https://raw.githubusercontent.com/sharkdp/bat/master/src/main.rs | bat -l rs
|
||||
```
|
||||
|
||||
As a replacement for `cat`:
|
||||
|
||||
@@ -76,20 +85,32 @@ bat f - g # output 'f', then stdin, then 'g'.
|
||||
|
||||
## Installation
|
||||
|
||||
### From binaries
|
||||
### On Ubuntu
|
||||
*... and other Debian-based Linux distributions.*
|
||||
|
||||
Check out the [Release page](https://github.com/sharkdp/bat/releases) for
|
||||
binary builds and Debian packages.
|
||||
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.9.0_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
#### On Arch Linux
|
||||
### On Arch Linux
|
||||
|
||||
You can install via Pacman:
|
||||
You can install [the `bat` package](https://www.archlinux.org/packages/community/x86_64/bat/)
|
||||
from the official sources:
|
||||
|
||||
```bash
|
||||
pacman -S bat
|
||||
```
|
||||
|
||||
#### On FreeBSD
|
||||
### On Void Linux
|
||||
|
||||
You can install `bat` via xbps-install:
|
||||
```
|
||||
xbps-install -S bat
|
||||
```
|
||||
|
||||
### On FreeBSD
|
||||
|
||||
You can install a precompiled [`bat` package](https://www.freshports.org/textproc/bat) with pkg:
|
||||
|
||||
@@ -104,7 +125,23 @@ cd /usr/ports/textproc/bat
|
||||
make install
|
||||
```
|
||||
|
||||
#### On macOS
|
||||
### Via nix
|
||||
|
||||
You can install `bat` using the [nix package manager](https://nixos.org/nix):
|
||||
|
||||
```bash
|
||||
nix-env -i bat
|
||||
```
|
||||
|
||||
### On openSUSE
|
||||
|
||||
You can install `bat` with zypper:
|
||||
|
||||
```
|
||||
zypper install bat
|
||||
```
|
||||
|
||||
### On macOS
|
||||
|
||||
You can install `bat` with [Homebrew](http://braumeister.org/formula/bat):
|
||||
|
||||
@@ -112,17 +149,71 @@ You can install `bat` with [Homebrew](http://braumeister.org/formula/bat):
|
||||
brew install bat
|
||||
```
|
||||
|
||||
### On Windows
|
||||
|
||||
You can download prebuilt binaries from the [Release page](https://github.com/sharkdp/bat/releases),
|
||||
or install it with [scoop](https://scoop.sh/):
|
||||
|
||||
```bash
|
||||
scoop install bat
|
||||
```
|
||||
|
||||
[See below](#using-bat-on-windows) for notes.
|
||||
|
||||
### Via Docker
|
||||
|
||||
There is a [Docker image](https://hub.docker.com/r/danlynn/bat/) that you can use to run `bat` in a container:
|
||||
```bash
|
||||
docker pull danlynn/bat
|
||||
alias bat='docker run -it --rm -e BAT_THEME -e BAT_STYLE -e BAT_TABS -v "$(pwd):/myapp" danlynn/bat'
|
||||
```
|
||||
|
||||
### Via Ansible
|
||||
|
||||
You can install `bat` with [Ansible](https://www.ansible.com/):
|
||||
|
||||
```bash
|
||||
# Install role on local machine
|
||||
ansible-galaxy install aeimer.install_bat
|
||||
```
|
||||
|
||||
```yaml
|
||||
---
|
||||
# Playbook to install bat
|
||||
- host: all
|
||||
roles:
|
||||
- aeimer.install_bat
|
||||
```
|
||||
|
||||
- [Ansible Galaxy](https://galaxy.ansible.com/aeimer/install_bat)
|
||||
- [GitHub](https://github.com/aeimer/ansible-install-bat)
|
||||
|
||||
This should work with the following distributions:
|
||||
- Debian/Ubuntu
|
||||
- ARM (eg. Raspberry PI)
|
||||
- Arch Linux
|
||||
- Void Linux
|
||||
- FreeBSD
|
||||
- MacOS
|
||||
|
||||
### From binaries
|
||||
|
||||
Check out the [Release page](https://github.com/sharkdp/bat/releases) for
|
||||
prebuilt versions of `bat` for many different architectures. Statically-linked
|
||||
binaries are available, just look for releases with `musl` in the name on the
|
||||
releases page.
|
||||
|
||||
### From source
|
||||
|
||||
If you want to build to compile `bat` from source, you need Rust 1.26 or
|
||||
If you want to build `bat` from source, you need Rust 1.29 or
|
||||
higher. You can then use `cargo` to build everything:
|
||||
|
||||
```bash
|
||||
cargo install bat
|
||||
```
|
||||
|
||||
On macOS, you might have to install `cmake` (`brew install cmake`) in order for
|
||||
some dependencies to be built.
|
||||
You may have to install `cmake` and the `libz` development package
|
||||
(`libz-dev` or `libz-devel`) in order for the build to succeed.
|
||||
|
||||
## Customization
|
||||
|
||||
@@ -131,14 +222,26 @@ some dependencies to be built.
|
||||
Use `bat --list-themes` to get a list of all available themes for syntax
|
||||
highlighting. To select the `TwoDark` theme, call `bat` with the
|
||||
`--theme=TwoDark` option or set the `BAT_THEME` environment variable to
|
||||
`TwoDark`. Use `export BAT_THEME="TwoDark"` in your shells startup file to
|
||||
`TwoDark`. Use `export BAT_THEME="TwoDark"` in your shell's startup file to
|
||||
make the change permanent.
|
||||
|
||||
If you want to preview the different themes on a custom file, you can use
|
||||
the following command (you need [`fzf`](https://github.com/junegunn/fzf) for this):
|
||||
``` bash
|
||||
bat --list-themes | fzf --preview="bat --theme={} --color=always /path/to/file"
|
||||
```
|
||||
|
||||
`bat` looks good on a dark background by default. However, if your terminal uses a
|
||||
light background, some themes will work better for you. For example, the `GitHub`
|
||||
theme is made for light backgrounds. You can also make your own theme by following the
|
||||
['Adding new themes' section below](https://github.com/sharkdp/bat#adding-new-themes).
|
||||
|
||||
### Output style
|
||||
|
||||
You can use the `--style` option to control the appearance of `bat`s output.
|
||||
You can use `--style=numbers,changes`, for example, to show only Git changes
|
||||
and line numbers but no grid and no file header.
|
||||
and line numbers but no grid and no file header. Use the `BAT_STYLE` environment
|
||||
variable to make these changes permanent.
|
||||
|
||||
### Adding new syntaxes / language definitions
|
||||
|
||||
@@ -188,30 +291,158 @@ cd "$BAT_CONFIG_DIR/themes"
|
||||
# Download a theme in '.tmTheme' format, for example:
|
||||
git clone https://github.com/greggb/sublime-snazzy
|
||||
|
||||
# Create a link to specify the new default theme
|
||||
ln -sf "sublime-snazzy/Sublime Snazzy.tmTheme" Default.tmTheme
|
||||
|
||||
# Update the binary cache
|
||||
bat cache --init
|
||||
```
|
||||
|
||||
Finally, use `bat --list-themes` to check if the new themes are available.
|
||||
|
||||
**Note:** Unlike for syntax definitions, adding custom themes currently *removes all default
|
||||
themes*. If you want to go back to the default themes, call `bat cache --clear`.
|
||||
|
||||
### Using a different pager
|
||||
|
||||
`bat` uses the pager that is specified in the `PAGER` environment variable. If this variable is not
|
||||
set, `less` is used by default. If you want to use a different pager, you can either modify the
|
||||
`PAGER` variable or set the `BAT_PAGER` environment variable to override what is specified in
|
||||
`PAGER`. If you want to pass command-line arguments to the pager, you need to create a small shell
|
||||
script as a wrapper, for example:
|
||||
`PAGER`.
|
||||
|
||||
If you want to pass command-line arguments to the pager, you can also set them via the
|
||||
`PAGER`/`BAT_PAGER` variables:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
export BAT_PAGER="less -RF"
|
||||
```
|
||||
|
||||
less --tabs 4 -RF "$@"
|
||||
**Note**: By default, if the pager is set to `less` (any no command-line options are specified),
|
||||
`bat` will pass the following command line
|
||||
options to the pager: `-R`/`--RAW-CONTROL-CHARS`, `-F`/`--quit-if-one-screen` and `-X`/`--no-init`.
|
||||
The first (`-R`) is needed to interpret ANSI colors correctly. The second option (`-F`) instructs
|
||||
less to exit immediately if the output size is smaller than the vertical size of the terminal.
|
||||
This is convenient for small files because you do not have to press `q` to quit the pager. The
|
||||
third option (`-X`) is needed to fix a bug with the `--quit-if-one-screen` feature in old versions
|
||||
of `less`. Unfortunately, it also breaks mouse-wheel support in `less`. If you want to enable
|
||||
mouse-wheel scrolling, you can either pass just `-R` (as in the example above, this will disable
|
||||
the quit-if-one-screen feature), or you can use a recent version of `less` and pass `-RF` which
|
||||
will hopefully enable both quit-if-one-screen and mouse-wheel scrolling.
|
||||
|
||||
If scrolling still doesn't work for you, you can try to pass the `-S` option in addition.
|
||||
|
||||
### Configuration file
|
||||
|
||||
`bat` can also be customized with a configuration file. The location of the file is dependent on you operating system. To get the default path for your system, call
|
||||
```
|
||||
bat --config-file
|
||||
```
|
||||
|
||||
Alternatively, you can use the `BAT_CONFIG_PATH` environment variable to point `bat` to a non-default location of the configuration file:
|
||||
```bash
|
||||
export BAT_CONFIG_PATH="/path/to/bat.conf"
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
Example configuration file:
|
||||
```bash
|
||||
# Set the theme to "TwoDark"
|
||||
--theme="TwoDark"
|
||||
|
||||
# Show line numbers, Git modifications and file header (but no grid)
|
||||
--style="numbers,changes,header"
|
||||
|
||||
# Use italic text on the terminal (not supported on all terminals)
|
||||
--italic-text=always
|
||||
|
||||
# Add mouse scrolling support in less (does not work with older
|
||||
# versions of "less")
|
||||
--pager="less -FR"
|
||||
|
||||
# Use C++ syntax (instead of C) for .h header files
|
||||
--map-syntax h:cpp
|
||||
|
||||
# Use "gitignore" highlighting for ".ignore" files
|
||||
--map-syntax .ignore:.gitignore
|
||||
```
|
||||
|
||||
## Using `bat` on Windows
|
||||
|
||||
`bat` mostly works out-of-the-box on Windows, but a few features may need extra configuration.
|
||||
|
||||
### Paging
|
||||
|
||||
Windows only includes a very limited pager in the form of `more`. You can download a Windows binary
|
||||
for `less` [from its homepage](http://www.greenwoodsoftware.com/less/download.html) or [through
|
||||
Chocolatey](https://chocolatey.org/packages/Less). To use it, place the binary in a directory in
|
||||
your `PATH` or [define an environment variable](#using-a-different-pager).
|
||||
|
||||
### Colours
|
||||
|
||||
Windows 10 natively supports colours in both `conhost.exe` (Command Prompt) and PowerShell since
|
||||
[v1511](https://en.wikipedia.org/wiki/Windows_10_version_history#Version_1511_(November_Update)), as
|
||||
well as in newer versions of bash. On earlier versions of Windows, you can use
|
||||
[Cmder](http://cmder.net/), which includes [ConEmu](https://conemu.github.io/).
|
||||
|
||||
**Note:** The Git and MSYS versions of `less` do not correctly interpret colours on Windows. If you
|
||||
don’t have any other pagers installed, you can disable paging entirely by passing `--paging=never`
|
||||
or by setting `BAT_PAGER` to an empty string.
|
||||
|
||||
### Cygwin
|
||||
|
||||
`bat` on Windows does not natively support Cygwin's unix-style paths (`/cygdrive/*`). When passed an absolute cygwin path as an argument, `bat` will encounter the following error: `The system cannot find the path specified. (os error 3)`
|
||||
|
||||
This can be solved by creating a wrapper or adding the following function to your `.bash_profile` file:
|
||||
|
||||
```shell
|
||||
bat() {
|
||||
local index
|
||||
local args=("$@")
|
||||
for index in $(seq 0 ${#args[@]}) ; do
|
||||
case "${args[index]}" in
|
||||
-*) continue;;
|
||||
*) [ -e "${args[index]}" ] && args[index]="$(cygpath --windows "${args[index]}")";;
|
||||
esac
|
||||
done
|
||||
command bat "${args[@]}"
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Terminals & colors
|
||||
|
||||
`bat` handles terminals *with* and *without* truecolor support. However, the colors in the syntax
|
||||
highlighting themes are not optimized for 8-bit colors and it is therefore strongly recommended
|
||||
that you use a terminal with 24-bit truecolor support (`terminator`, `konsole`, `iTerm2`, ...).
|
||||
See [this article](https://gist.github.com/XVilka/8346728) for more details and a full list of
|
||||
terminals with truecolor support.
|
||||
|
||||
Make sure that your truecolor terminal sets the `COLORTERM` variable to either `truecolor` or
|
||||
`24bit`. Otherwise, `bat` will not be able to determine whether or not 24-bit escape sequences
|
||||
are supported (and fall back to 8-bit colors).
|
||||
|
||||
### Line numbers and grid are hardly visible
|
||||
|
||||
Please try a different theme (see `bat --list-themes` for a list). The `OneHalfDark` and
|
||||
`OneHalfLight` themes provide grid and line colors that are brighter.
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Recursive clone to retrieve all submodules
|
||||
git clone --recursive https://github.com/sharkdp/bat
|
||||
|
||||
# Build (debug version)
|
||||
cd bat
|
||||
cargo build
|
||||
|
||||
# Run unit tests and integration tests
|
||||
cargo test
|
||||
|
||||
# Install (release version)
|
||||
cargo install
|
||||
|
||||
# Build a bat binary with modified syntaxes and themes
|
||||
bash assets/create.sh
|
||||
cargo install -f
|
||||
```
|
||||
|
||||
## Project goals and alternatives
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
environment:
|
||||
global:
|
||||
TARGET: x86_64-pc-windows-msvc
|
||||
RUST_VERSION: stable
|
||||
CRATE_NAME: bat
|
||||
CARGO_HOME: "c:\\cargo"
|
||||
RUSTUP_HOME: "c:\\rustup"
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init.exe -y --default-host %TARGET%
|
||||
- set PATH=%PATH%;C:\cargo\bin
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
test_script:
|
||||
- cargo test --target %TARGET% --verbose
|
||||
- cargo run --target %TARGET% -- src/main.rs README.md --paging=never
|
||||
|
||||
before_deploy:
|
||||
# Generate artifacts for release
|
||||
- cargo build --release --verbose
|
||||
- ps: ci\before_deploy.ps1
|
||||
|
||||
deploy:
|
||||
description: 'Automatically deployed release'
|
||||
artifact: /.*\.zip/
|
||||
# Here's how:
|
||||
# - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
|
||||
# `public_repo` scope enabled
|
||||
# - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
|
||||
# - Enter the "encrypted value" below
|
||||
auth_token:
|
||||
secure: oO4/J+hcFXgXcEqEc8gzlQNqLG0IxU78s4EyH+uczGdsyWajb3yipxPR6nXUvmoj
|
||||
provider: GitHub
|
||||
on:
|
||||
RUST_VERSION: stable
|
||||
appveyor_repo_tag: true
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.cargo\registry
|
||||
- target
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
on_build_success: false
|
||||
on_build_failure: false
|
||||
on_build_status_changed: false
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git syntaxes/Packages/Java/JavaDoc.sublime-syntax syntaxes/Packages/Java/JavaDoc.sublime-syntax
|
||||
index 422a6a9..40a741e 100644
|
||||
--- syntaxes/Packages/Java/JavaDoc.sublime-syntax
|
||||
+++ syntaxes/Packages/Java/JavaDoc.sublime-syntax
|
||||
@@ -13,7 +13,7 @@ variables:
|
||||
contexts:
|
||||
prototype:
|
||||
# https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#leadingasterisks
|
||||
- - match: ^\s*(\*)\s*(?!\s*@)
|
||||
+ - match: ^\s*(\*)(?!/)\s*(?!\s*@)
|
||||
captures:
|
||||
1: punctuation.definition.comment.javadoc
|
||||
|
||||
+2
-1
@@ -28,4 +28,5 @@ themes (`bat cache --clear`).
|
||||
|
||||
The following files have been manually modified after converting from a `.tmLanguage` file:
|
||||
|
||||
* `VimL.sublime-syntax` => added `.vimrc` file type.
|
||||
* `Dart.sublime-syntax` => removed `#regex.dart` include.
|
||||
* `INI.sublime-syntax` => added `.hgrc` and `hgrc` file types.
|
||||
Regular → Executable
+12
-1
@@ -2,4 +2,15 @@
|
||||
|
||||
ASSET_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
bat cache --init --source="$ASSET_DIR" --target="$ASSET_DIR"
|
||||
# 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/Packages/Java/JavaDoc.sublime-syntax"
|
||||
JAVADOC_PATCH="${ASSET_DIR}/JavaDoc.sublime-syntax.patch"
|
||||
patch "$JAVADOC_FILE" "$JAVADOC_PATCH"
|
||||
|
||||
bat cache --init --blank --source="$ASSET_DIR" --target="$ASSET_DIR"
|
||||
|
||||
patch -R "$JAVADOC_FILE" "$JAVADOC_PATCH"
|
||||
Binary file not shown.
Submodule
+1
Submodule assets/syntaxes/CSV added at 4786d037a7.
@@ -0,0 +1,46 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Advanced CSV
|
||||
file_extensions:
|
||||
- csv
|
||||
- tsv
|
||||
scope: text.advanced_csv
|
||||
contexts:
|
||||
main:
|
||||
- match: (\")
|
||||
captures:
|
||||
1: string.quoted.double.advanced_csv
|
||||
push:
|
||||
- meta_scope: meta.quoted.advanced_csv
|
||||
- match: (\")
|
||||
captures:
|
||||
1: string.quoted.double.advanced_csv
|
||||
pop: true
|
||||
- include: main
|
||||
- match: '(\[([+-]?\d*)(\:)?([+-]?\d*)(\,)?([+-]?\d*)(\:)?([+-]?\d*)\])?\s*([<>v^])?\s*(=)'
|
||||
captures:
|
||||
1: keyword.operator.advanced_csv
|
||||
2: constant.numeric.formula.advanced_csv
|
||||
4: constant.numeric.formula.advanced_csv
|
||||
6: constant.numeric.formula.advanced_csv
|
||||
8: constant.numeric.formula.advanced_csv
|
||||
9: keyword.operator.advanced_csv
|
||||
10: keyword.operator.advanced_csv
|
||||
push:
|
||||
- meta_scope: meta.range.advanced_csv
|
||||
- match: (?=(\")|$)
|
||||
pop: true
|
||||
- include: scope:source.python
|
||||
- match: '(?<=^|,|\s|\")([0-9.eE+-]+)(?=$|,|\s|\")'
|
||||
scope: meta.number.advanced_csv
|
||||
captures:
|
||||
1: constant.numeric.advanced_csv
|
||||
- match: '(?<=^|,|\s|\")([^, \t\"]+)(?=$|,|\s|\")'
|
||||
scope: meta.nonnumber.advanced_csv
|
||||
captures:
|
||||
1: storage.type.advanced_csv
|
||||
- match: (\,)
|
||||
scope: meta.delimiter.advanced_csv
|
||||
captures:
|
||||
1: keyword.operator.advanced_csv
|
||||
Submodule
+1
Submodule assets/syntaxes/Cabal added at 1c054d9b36.
@@ -0,0 +1,47 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Cabal
|
||||
file_extensions:
|
||||
- cabal
|
||||
scope: source.cabal
|
||||
contexts:
|
||||
main:
|
||||
- match: ^(\s*)(exposed-modules):$
|
||||
captures:
|
||||
2: constant.other.cabal
|
||||
push:
|
||||
- meta_scope: exposed.modules.cabal
|
||||
- match: ^(?!\1\s)
|
||||
pop: true
|
||||
- include: module_name
|
||||
- match: ^(\s*)(build-depends):$
|
||||
captures:
|
||||
2: constant.other.cabal
|
||||
push:
|
||||
- meta_scope: exposed.modules.cabal
|
||||
- match: ^(?!\1\s)
|
||||
pop: true
|
||||
- match: "([<>=]+)|([&|]+)"
|
||||
scope: keyword.operator.haskell
|
||||
- match: ((\d+|\*)\.)*(\d+|\*)
|
||||
scope: constant.numeric.haskell
|
||||
- match: '([\w\-]+)'
|
||||
scope: support.function.haskell
|
||||
- match: '^\s*([a-zA-Z_-]+)(:)\s+'
|
||||
scope: entity.cabal
|
||||
captures:
|
||||
1: constant.other.cabal
|
||||
2: punctuation.entity.cabal
|
||||
- match: '^(?i)(executable|library|test-suite|benchmark|flag|source-repository)\s+([^\s,]+)\s*$'
|
||||
scope: entity.cabal
|
||||
captures:
|
||||
1: keyword.entity.cabal
|
||||
2: string.cabal
|
||||
- match: ^(?i)library\s*$
|
||||
scope: keyword.entity.cabal
|
||||
- match: "--.*$"
|
||||
scope: comment.cabal
|
||||
module_name:
|
||||
- match: '([A-Z][A-Za-z_''0-9]*)(\.[A-Z][A-Za-z_''0-9]*)*'
|
||||
scope: storage.module.haskell
|
||||
Submodule
+1
Submodule assets/syntaxes/Crystal added at 2f96cec2ca.
File diff suppressed because it is too large.
Load diff
Submodule
+1
Submodule assets/syntaxes/Dart added at d891fb36c9.
@@ -0,0 +1,369 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Dart
|
||||
file_extensions:
|
||||
- dart
|
||||
scope: source.dart
|
||||
contexts:
|
||||
main:
|
||||
- match: ^(#!.*)$
|
||||
scope: meta.preprocessor.script.dart
|
||||
- match: ^\s*\b(library|import|export|part of|part)\b
|
||||
captures:
|
||||
0: keyword.other.import.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.dart
|
||||
- match: ;
|
||||
captures:
|
||||
0: punctuation.terminator.dart
|
||||
pop: true
|
||||
- include: strings
|
||||
- match: \b(as|show|hide|deferred)\b
|
||||
scope: keyword.other.import.dart
|
||||
- include: comments
|
||||
- include: constants-and-special-vars
|
||||
- include: annotations
|
||||
- include: decl-typedef
|
||||
- include: decl-class
|
||||
- include: decl-enum
|
||||
- include: decl-function
|
||||
- include: keywords
|
||||
- include: strings
|
||||
annotations:
|
||||
- match: '^(?:\s*)((@)([a-zA-Z0-9_]+))'
|
||||
captures:
|
||||
1: annotation.dart
|
||||
2: entity.name.function.annotation.dart
|
||||
3: support.type.dart
|
||||
comments:
|
||||
- match: /\*\*/
|
||||
scope: comment.block.empty.dart
|
||||
captures:
|
||||
0: punctuation.definition.comment.dart
|
||||
- include: comments-inline
|
||||
comments-inline:
|
||||
- match: /\*
|
||||
push:
|
||||
- meta_scope: comment.block.dart
|
||||
- match: \*/
|
||||
pop: true
|
||||
- include: scope:text.dart-doccomments
|
||||
- match: (///)
|
||||
captures:
|
||||
1: marker.dart
|
||||
push:
|
||||
- meta_scope: comment.line.triple-slash.dart
|
||||
- match: $
|
||||
pop: true
|
||||
- include: scope:text.dart-doccomments
|
||||
- match: (//)
|
||||
captures:
|
||||
1: marker.dart
|
||||
push:
|
||||
- meta_scope: comment.line.double-slash.dart
|
||||
- match: $
|
||||
pop: true
|
||||
- include: scope:text.dart-doccomments
|
||||
constants-and-special-vars:
|
||||
- match: \b(true|false|null)\b
|
||||
scope: constant.language.dart
|
||||
- match: \b(this|super)\b
|
||||
scope: variable.language.dart
|
||||
- match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)\b'
|
||||
scope: constant.numeric.dart
|
||||
decl-class:
|
||||
- match: \bclass\b
|
||||
captures:
|
||||
0: keyword.control.new.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.class.dart
|
||||
- match: "(?={)"
|
||||
pop: true
|
||||
- include: keywords
|
||||
- match: "[A-Za-z_][A-Za-z0-9_]*"
|
||||
scope: class.name.dart
|
||||
decl-enum:
|
||||
- match: \benum\b
|
||||
captures:
|
||||
0: keyword.declaration.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.enum.dart
|
||||
- match: "(?={)"
|
||||
pop: true
|
||||
- include: keywords
|
||||
- match: "[A-Za-z_][A-Za-z0-9_]*"
|
||||
scope: enum.name.dart
|
||||
decl-function:
|
||||
- match: ^\s*(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(get)\s+(\w+)\s+(?==>)
|
||||
comment: A getter with a primitive return type.
|
||||
scope: meta.declaration.function.dart
|
||||
captures:
|
||||
1: storage.type.primitive.dart
|
||||
2: keyword.declaration.dart
|
||||
3: function.name.dart
|
||||
- match: ^\s*(?:\b(\w+)\b\s+)?(get)\s+(\w+)\s+(?==>)
|
||||
comment: A getter with a user-defined return type or no return type.
|
||||
scope: meta.declaration.function.dart
|
||||
captures:
|
||||
1: type.user-defined.dart
|
||||
2: keyword.declaration.dart
|
||||
3: function.name.dart
|
||||
- match: ^\s*(set)\s+(\w+)(?=\()
|
||||
comment: A setter.
|
||||
captures:
|
||||
1: keyword.declaration.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: comments-inline
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: ^\s*(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(\w+)(?=\()
|
||||
comment: A function with a primitive return type.
|
||||
captures:
|
||||
1: storage.type.primitive.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: comments-inline
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: ^\s*(?:\b(return)\b)\s+(\w+)(?=\()
|
||||
comment: A function invocation after 'return'
|
||||
captures:
|
||||
1: keyword.control.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.invocation.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: comments-inline
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: ^\s*\b(new)\b\s+(\w+)(?=\()
|
||||
comment: A class instantiation after 'new'
|
||||
captures:
|
||||
1: keyword.declaration.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.invocation.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: comments-inline
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
decl-function-parameter:
|
||||
- include: constants-and-special-vars
|
||||
- match: (?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b)\s+(\w+)(?=\()
|
||||
comment: A function with a primitive return type.
|
||||
captures:
|
||||
1: storage.type.primitive.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.parameter.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: \b(new)\b\s+(\w+)(?=\()
|
||||
comment: A class instantiation after 'new'
|
||||
captures:
|
||||
1: keyword.declaration.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.invocation.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: (?:\b(\w+)\b)\s+(\w+)(?=\()
|
||||
comment: A function with a user-defined return type.
|
||||
captures:
|
||||
1: type.user-defined.dart
|
||||
2: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.parameter.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
- match: (\w+)(?=\()
|
||||
comment: A function with no return type.
|
||||
captures:
|
||||
1: function.name.dart
|
||||
push:
|
||||
- meta_scope: meta.parameter.function.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: decl-function-parameter
|
||||
- include: strings
|
||||
- include: keywords
|
||||
decl-typedef:
|
||||
- match: typedef
|
||||
captures:
|
||||
0: keyword.control.new.dart
|
||||
push:
|
||||
- meta_scope: meta.declaration.typedef.dart
|
||||
- match: ;
|
||||
captures:
|
||||
0: punctuation.terminator.dart
|
||||
pop: true
|
||||
- match: '(?:\b(void|bool|num|int|double|dynamic|var|String|List|Map)\b|([a-zA-Z_][a-zA-Z0-9_]*))\s+([a-zA-Z_][a-zA-Z0-9_]+)'
|
||||
captures:
|
||||
1: storage.type.primitive.dart
|
||||
2: typedef.return.dart
|
||||
3: typedef.name.dart
|
||||
- match: \(
|
||||
push:
|
||||
- meta_scope: typedef.params.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: keywords
|
||||
keywords:
|
||||
- match: \bassert\b
|
||||
scope: keyword.control.assert.dart
|
||||
- match: \bas\b
|
||||
scope: keyword.cast.dart
|
||||
- match: \b(try|catch|finally|throw|on|rethrow)\b
|
||||
scope: keyword.control.catch-exception.dart
|
||||
- match: \s+\?\s+|\s+:\s+
|
||||
scope: keyword.control.ternary.dart
|
||||
- match: \b(break|case|continue|default|do|else|for|if|in|return|switch|while)\b
|
||||
scope: keyword.control.dart
|
||||
- match: \b(async\*|async|await\*|await|yield)\b
|
||||
scope: keyword.control.async.dart
|
||||
- match: \b(new)\b
|
||||
scope: keyword.control.new.dart
|
||||
- match: \b(abstract|extends|external|factory|implements|with|interface|get|native|operator|set|typedef)\b
|
||||
scope: keyword.declaration.dart
|
||||
- match: \b(is\!?)\b
|
||||
scope: keyword.operator.dart
|
||||
- match: (<<|>>>?|~|\^|\||&)
|
||||
scope: keyword.operator.bitwise.dart
|
||||
- match: ((&|\^|\||<<|>>>?)=)
|
||||
scope: keyword.operator.assignment.bitwise.dart
|
||||
- match: (===?|!==?|<=?|>=?)
|
||||
scope: keyword.operator.comparison.dart
|
||||
- match: '(([+*/%-]|\~)=)'
|
||||
scope: keyword.operator.assignment.arithmetic.dart
|
||||
- match: (=)
|
||||
scope: keyword.operator.assignment.dart
|
||||
- match: (\-\-|\+\+)
|
||||
scope: keyword.operator.increment-decrement.dart
|
||||
- match: (\-|\+|\*|\/|\~\/|%)
|
||||
scope: keyword.operator.arithmetic.dart
|
||||
- match: (!|&&|\|\|)
|
||||
scope: keyword.operator.logical.dart
|
||||
- match: ;
|
||||
scope: punctuation.terminator.dart
|
||||
- match: \b(static|final|const)\b
|
||||
scope: storage.modifier.dart
|
||||
- match: \b(?:void|bool|num|int|double|dynamic|var|String|List|Map)\b
|
||||
scope: storage.type.primitive.dart
|
||||
regexp:
|
||||
- match: '\\[^''"]'
|
||||
scope: constant.character.escaped.regex.dart
|
||||
- match: \(
|
||||
push:
|
||||
- meta_content_scope: meta.capture.regex.dart
|
||||
- match: \)
|
||||
pop: true
|
||||
- match: \?(:|=|!)
|
||||
scope: ignore.capture.regex.dart
|
||||
- match: \*|\+|\?|\.|\|
|
||||
scope: keyword.other.regex.dart
|
||||
- match: \^|\$
|
||||
scope: keyword.other.regex.dart
|
||||
- match: \.
|
||||
scope: constant.other.regex.dart
|
||||
- match: '\[(\^)?'
|
||||
captures:
|
||||
1: keyword.other.negation.regex.dart
|
||||
push:
|
||||
- meta_scope: constant.character.range.regex.dart
|
||||
- match: '\]'
|
||||
pop: true
|
||||
- match: '\\[^"'']'
|
||||
scope: constant.character.escaped.regex.dart
|
||||
- match: '\{(?:\d+)?,(?:\d+)?\}'
|
||||
scope: keyword.other.regex.dart
|
||||
string-interp:
|
||||
- match: '(\$)(\{)'
|
||||
captures:
|
||||
1: keyword.other.dart
|
||||
2: keyword.other.dart
|
||||
push:
|
||||
- meta_scope: interpolation.dart
|
||||
- meta_content_scope: source.dart
|
||||
- match: '(\})'
|
||||
captures:
|
||||
1: keyword.other.dart
|
||||
pop: true
|
||||
- include: main
|
||||
- match: (\$)(\w+)
|
||||
captures:
|
||||
1: keyword.other.dart
|
||||
2: variable.parameter.dart
|
||||
- match: \\.
|
||||
scope: constant.character.escape.dart
|
||||
strings:
|
||||
- match: (?<!r)"""
|
||||
push:
|
||||
- meta_scope: string.interpolated.triple.double.dart
|
||||
- match: '"""(?!")'
|
||||
pop: true
|
||||
- include: string-interp
|
||||
- match: (?<!r)'''
|
||||
push:
|
||||
- meta_scope: string.interpolated.triple.single.dart
|
||||
- match: "'''(?!')"
|
||||
pop: true
|
||||
- include: string-interp
|
||||
- match: r"""
|
||||
push:
|
||||
- meta_scope: string.quoted.triple.double.dart
|
||||
- match: '"""(?!")'
|
||||
pop: true
|
||||
- match: r'''
|
||||
push:
|
||||
- meta_scope: string.quoted.triple.single.dart
|
||||
- match: "'''(?!')"
|
||||
pop: true
|
||||
- match: (?<!\\|r)"
|
||||
push:
|
||||
- meta_scope: string.interpolated.double.dart
|
||||
- match: '"'
|
||||
pop: true
|
||||
- match: \n
|
||||
scope: invalid.string.newline
|
||||
- include: string-interp
|
||||
- match: r"
|
||||
push:
|
||||
- meta_scope: string.quoted.double.dart
|
||||
- match: '"'
|
||||
pop: true
|
||||
- include: regexp
|
||||
- match: (?<!\|r)'
|
||||
push:
|
||||
- meta_scope: string.interpolated.single.dart
|
||||
- match: "'"
|
||||
pop: true
|
||||
- include: string-interp
|
||||
- match: r'
|
||||
push:
|
||||
- meta_scope: string.quoted.single.dart
|
||||
- match: "'"
|
||||
pop: true
|
||||
- include: regexp
|
||||
Submodule
+1
Submodule assets/syntaxes/Elm added at dfbb8bdc9a.
Submodule
+1
Submodule assets/syntaxes/FSharp added at da846f4227.
@@ -0,0 +1,234 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: "F#"
|
||||
file_extensions:
|
||||
- fs
|
||||
- fsi
|
||||
- fsx
|
||||
scope: source.fsharp
|
||||
contexts:
|
||||
main:
|
||||
- include: comments
|
||||
- include: constants
|
||||
- include: structure
|
||||
- include: attributes
|
||||
- include: strings
|
||||
- include: definition
|
||||
- include: method_calls
|
||||
- include: modules
|
||||
- include: anonymous_functions
|
||||
- include: keywords
|
||||
anonymous_functions:
|
||||
- match: \b(fun)\b
|
||||
captures:
|
||||
1: keyword.other.function-definition.fsharp
|
||||
push:
|
||||
- meta_scope: meta.function.anonymous
|
||||
- match: (->)
|
||||
captures:
|
||||
1: keyword.other.fsharp
|
||||
pop: true
|
||||
- include: variables
|
||||
attributes:
|
||||
- match: '\[\<'
|
||||
push:
|
||||
- meta_scope: support.function.attribute.fsharp
|
||||
- match: '\>\]'
|
||||
pop: true
|
||||
- include: main
|
||||
characters:
|
||||
- match: (')
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.fsharp
|
||||
push:
|
||||
- meta_scope: string.quoted.single.fsharp
|
||||
- match: (')
|
||||
captures:
|
||||
1: punctuation.definition.string.end.fsharp
|
||||
pop: true
|
||||
- match: '\\$[ \t]*'
|
||||
scope: punctuation.separator.string.ignore-eol.fsharp
|
||||
- match: '\\([\\""ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})'
|
||||
scope: constant.character.string.escape.fsharp
|
||||
- match: '\\(?![\\''ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8}).'
|
||||
scope: invalid.illeagal.character.string.fsharp
|
||||
comments:
|
||||
- match: \(\*\*?(\*)\)
|
||||
scope: comment.block.fsharp
|
||||
captures:
|
||||
1: comment.block.empty.fsharp
|
||||
- match: '\(\*[^\)]'
|
||||
push:
|
||||
- meta_scope: comment.block.fsharp
|
||||
- match: \*\)
|
||||
pop: true
|
||||
- include: comments
|
||||
- match: //.*$
|
||||
scope: comment.line.double-slash.fsharp
|
||||
constants:
|
||||
- match: \(\)
|
||||
scope: constant.language.unit.fsharp
|
||||
- match: '\b-?[0-9][0-9_]*((\.([0-9][0-9_]*([eE][+-]??[0-9][0-9_]*)?)?)|([eE][+-]??[0-9][0-9_]*))'
|
||||
scope: constant.numeric.floating-point.fsharp
|
||||
- match: '\b(-?((0(x|X)[0-9a-fA-F][0-9a-fA-F_]*)|(0(o|O)[0-7][0-7_]*)|(0(b|B)[01][01_]*)|([0-9][0-9_]*)))'
|
||||
scope: constant.numeric.integer.nativeint.fsharp
|
||||
definition:
|
||||
- match: '\b(val|val|let|and|member|override|use)\s?(rec|inline|mutable)?(\s+\(?([a-zA-Z.\|_][a-zA-Z0-9.|_]*)\)?\w*)\b'
|
||||
captures:
|
||||
1: keyword.other.binding.fsharp
|
||||
2: keyword.other.function-recursive.fsharp
|
||||
3: variable.other.binding.fsharp
|
||||
push:
|
||||
- meta_scope: meta.binding.fsharp
|
||||
- match: "=|$"
|
||||
captures:
|
||||
1: punctuation.separator.function.type-constraint.fsharp
|
||||
pop: true
|
||||
- include: variables
|
||||
- match: \b(let)\s+((\(\))|(_))\s+=
|
||||
scope: meta.expression.fsharp
|
||||
captures:
|
||||
1: keyword.other.binding.fsharp
|
||||
2: keyword.other.function-recursive.fsharp
|
||||
4: constant.language.unit.fsharp
|
||||
5: constant.language.ignored.fsharp
|
||||
keywords:
|
||||
- match: ^\s*#\s*(light|if|else|endif|r|I|load|time|help|quit)\b
|
||||
push:
|
||||
- meta_scope: meta.preprocessor.fsharp
|
||||
- match: (\s|$)
|
||||
pop: true
|
||||
- match: \b(new|in|as|if|then|else|elif|for|begin|end|match|with|type|inherit|true|false|null|do)\b
|
||||
scope: keyword.other.fsharp
|
||||
- match: '(\|>|\|?>|\->|\<\-|:>|:|\[|\]|\;|\||_)'
|
||||
scope: entity.name
|
||||
method_calls:
|
||||
- match: '(?<!\w)([a-z]\w*)(\.)'
|
||||
push:
|
||||
- meta_scope: meta.method-call.fsharp
|
||||
- match: '[A-Z]\w*(\.)'
|
||||
scope: meta.method.fsharp
|
||||
captures:
|
||||
1: punctuation.separator.method-call.fsharp
|
||||
- match: (?=.)
|
||||
pop: true
|
||||
modules:
|
||||
- match: '\b(namespace|module)\s+([a-zA-Z][a-zA-Z0-9''_.]*)'
|
||||
captures:
|
||||
1: keyword.other.fsharp
|
||||
2: entity.name.section.fsharp
|
||||
push:
|
||||
- meta_scope: meta.module.namespace.fsharp
|
||||
- match: (\s|$)
|
||||
pop: true
|
||||
- match: '(\.)([A-Z][a-zA-Z0-9''_]*)'
|
||||
scope: support.other.module.fsharp
|
||||
captures:
|
||||
1: punctuation.separator.module-reference.fsharp
|
||||
2: support.other.module.fsharp
|
||||
- match: '\b(open)\s+([A-Z][a-zA-Z0-9''_]*)(?=(\.[A-Z][a-zA-Z0-9_]*)*)'
|
||||
captures:
|
||||
1: keyword.other.fsharp
|
||||
2: support.other.module.fsharp
|
||||
push:
|
||||
- meta_scope: meta.module.open.fsharp
|
||||
- match: (\s|$)
|
||||
pop: true
|
||||
- match: '(\.)([A-Z][a-zA-Z0-9''_]*)'
|
||||
scope: support.other.module.fsharp
|
||||
captures:
|
||||
1: punctuation.separator.module-reference.fsharp
|
||||
2: support.other.module.fsharp
|
||||
- match: '^\s*(module)\s+([A-Z][a-zA-Z0-9''_]*)\s*(=)\s*([A-Z][a-zA-Z0-9''_]*)'
|
||||
captures:
|
||||
1: keyword.other.module-definition.fsharp
|
||||
2: entity.name.type.module.fsharp
|
||||
3: punctuation.separator.module-definition.fsharp
|
||||
4: support.other.module.fsharp
|
||||
push:
|
||||
- meta_scope: meta.module.alias.fsharp
|
||||
- match: (\s|$)
|
||||
pop: true
|
||||
- match: '(\.)([A-Z][a-zA-Z0-9''_]*)'
|
||||
scope: support.other.module.fsharp
|
||||
captures:
|
||||
1: punctuation.separator.module-reference.fsharp
|
||||
2: support.other.module.fsharp
|
||||
- match: '(?<!\w)([A-Z][a-zA-Z0-9_]*)(\.)'
|
||||
captures:
|
||||
1: support.other.module.fsharp
|
||||
2: punctuation.separator.module-reference.fsharp
|
||||
push:
|
||||
- meta_scope: meta.module.reference.fsharp
|
||||
- match: '[A-Z][a-zA-Z0-9_]+(\.)'
|
||||
scope: support.other.module.fsharp
|
||||
captures:
|
||||
1: punctuation.separator.module-reference.fsharp
|
||||
- match: (?=.)
|
||||
pop: true
|
||||
strings:
|
||||
- match: '(?=[^\\])(""")'
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.fsharp
|
||||
push:
|
||||
- meta_scope: string.quoted.double.fsharp
|
||||
- match: (""")
|
||||
captures:
|
||||
1: punctuation.definition.string.end.fsharp
|
||||
pop: true
|
||||
- match: '\\$[ \t]*'
|
||||
scope: punctuation.separator.string.ignore-eol.fsharp
|
||||
- match: '(?=[^\\])(@")'
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.fsharp
|
||||
push:
|
||||
- meta_scope: string.quoted.double.fsharp
|
||||
- match: (")
|
||||
captures:
|
||||
1: punctuation.definition.string.end.fsharp
|
||||
pop: true
|
||||
- match: '\\$[ \t]*'
|
||||
scope: punctuation.separator.string.ignore-eol.fsharp
|
||||
- match: '(?=[^\\])(")'
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.fsharp
|
||||
push:
|
||||
- meta_scope: string.quoted.double.fsharp
|
||||
- match: (")
|
||||
captures:
|
||||
1: punctuation.definition.string.end.fsharp
|
||||
pop: true
|
||||
- match: '\\$[ \t]*'
|
||||
scope: punctuation.separator.string.ignore-eol.fsharp
|
||||
- match: '\\([\\''ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})'
|
||||
scope: constant.character.string.escape.fsharp
|
||||
- match: '\\(?![\\''ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8}).'
|
||||
scope: invalid.illeagal.character.string.fsharp
|
||||
- match: '(?=[^\\])('')'
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.fsharp
|
||||
push:
|
||||
- meta_scope: string.quoted.double.fsharp
|
||||
- match: (')
|
||||
captures:
|
||||
1: punctuation.definition.string.end.fsharp
|
||||
pop: true
|
||||
- match: '\\$[ \t]*'
|
||||
scope: punctuation.separator.string.ignore-eol.fsharp
|
||||
- match: '\\([\\"ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})'
|
||||
scope: constant.character.string.escape.fsharp
|
||||
- match: '\\(?![\\"ntbr]|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8}).'
|
||||
scope: invalid.illeagal.character.string.fsharp
|
||||
structure:
|
||||
- match: \(
|
||||
push:
|
||||
- meta_scope: meta.paren-group.fsharp
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: main
|
||||
variables:
|
||||
- match: \(\)
|
||||
scope: variable.parameter.unit.fsharp
|
||||
- match: '[a-zA-Z]\w*'
|
||||
scope: variable.parameter.fsharp
|
||||
@@ -15,6 +15,8 @@ file_extensions:
|
||||
- url
|
||||
- URL
|
||||
- .editorconfig
|
||||
- .hgrc
|
||||
- hgrc
|
||||
scope: source.ini
|
||||
contexts:
|
||||
main:
|
||||
|
||||
Submodule
+1
Submodule assets/syntaxes/JavaScript (Babel) added at a9a908fe1d.
File diff suppressed because it is too large.
Load diff
Submodule
+1
Submodule assets/syntaxes/Kotlin added at aeeed2780b.
@@ -0,0 +1,398 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Kotlin
|
||||
file_extensions:
|
||||
- kt
|
||||
- kts
|
||||
scope: source.Kotlin
|
||||
contexts:
|
||||
main:
|
||||
- include: comments
|
||||
- match: '^\s*(package)\b(?:\s*([^ ;$]+)\s*)?'
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
2: entity.name.package.kotlin
|
||||
- include: imports
|
||||
- include: statements
|
||||
classes:
|
||||
- match: (?<!::)(?=\b(?:companion|class|object|interface)\b)
|
||||
push:
|
||||
- match: '(?=$|\})'
|
||||
pop: true
|
||||
- include: comments
|
||||
- match: \b(companion\s*)?(class|object|interface)\b
|
||||
captures:
|
||||
1: storage.modifier.kotlin
|
||||
2: storage.modifier.kotlin
|
||||
push:
|
||||
- match: '(?=<|\{|\(|:|$)'
|
||||
pop: true
|
||||
- include: comments
|
||||
- match: \w+
|
||||
scope: entity.name.type.class.kotlin
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: "(?={|$)"
|
||||
pop: true
|
||||
- match: \w+
|
||||
scope: entity.other.inherited-class.kotlin
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
comments:
|
||||
- match: /\*
|
||||
captures:
|
||||
0: punctuation.definition.comment.kotlin
|
||||
push:
|
||||
- meta_scope: comment.block.kotlin
|
||||
- match: \*/
|
||||
captures:
|
||||
0: punctuation.definition.comment.kotlin
|
||||
pop: true
|
||||
- match: \s*((//).*$\n?)
|
||||
captures:
|
||||
1: comment.line.double-slash.kotlin
|
||||
2: punctuation.definition.comment.kotlin
|
||||
constants:
|
||||
- match: \b(true|false|null|this|super)\b
|
||||
scope: constant.language.kotlin
|
||||
- match: '\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)([LlFf])?\b'
|
||||
scope: constant.numeric.kotlin
|
||||
- match: '\b([A-Z][A-Z0-9_]+)\b'
|
||||
scope: constant.other.kotlin
|
||||
expressions:
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: types
|
||||
- include: strings
|
||||
- include: constants
|
||||
- include: comments
|
||||
- include: keywords
|
||||
functions:
|
||||
- match: (?=\s*\b(?:fun)\b)
|
||||
push:
|
||||
- match: '(?=$|\})'
|
||||
pop: true
|
||||
- match: \b(fun)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=\()
|
||||
pop: true
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: '([\.<\?>\w]+\.)?(\w+)'
|
||||
captures:
|
||||
2: entity.name.function.kotlin
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: "(?={|=|$)"
|
||||
pop: true
|
||||
- include: types
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '(?=\})'
|
||||
pop: true
|
||||
- include: statements
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
generics:
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|>)
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: \w+
|
||||
scope: storage.type.generic.kotlin
|
||||
getters-and-setters:
|
||||
- match: \b(get)\b\s*\(\s*\)
|
||||
captures:
|
||||
1: entity.name.function.kotlin
|
||||
push:
|
||||
- match: '\}|(?=\bset\b)|$'
|
||||
pop: true
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$|\bset\b)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: \b(set)\b\s*(?=\()
|
||||
captures:
|
||||
1: entity.name.function.kotlin
|
||||
push:
|
||||
- match: '\}|(?=\bget\b)|$'
|
||||
pop: true
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: parameters
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$|\bset\b)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: expressions
|
||||
imports:
|
||||
- match: '^\s*(import)\s+[^ $]+\s+(as)?'
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
2: keyword.other.kotlin
|
||||
keywords:
|
||||
- match: \b(var|val|public|private|protected|abstract|final|sealed|enum|open|attribute|annotation|override|inline|vararg|in|out|internal|data|tailrec|operator|infix|const|yield|typealias|typeof|reified|suspend)\b
|
||||
scope: storage.modifier.kotlin
|
||||
- match: \b(try|catch|finally|throw)\b
|
||||
scope: keyword.control.catch-exception.kotlin
|
||||
- match: \b(if|else|while|for|do|return|when|where|break|continue)\b
|
||||
scope: keyword.control.kotlin
|
||||
- match: \b(in|is|!in|!is|as|as\?|assert)\b
|
||||
scope: keyword.operator.kotlin
|
||||
- match: (==|!=|===|!==|<=|>=|<|>)
|
||||
scope: keyword.operator.comparison.kotlin
|
||||
- match: (=)
|
||||
scope: keyword.operator.assignment.kotlin
|
||||
- match: (::)
|
||||
scope: keyword.operator.kotlin
|
||||
- match: (:)
|
||||
scope: keyword.operator.declaration.kotlin
|
||||
- match: \b(by)\b
|
||||
scope: keyword.other.by.kotlin
|
||||
- match: (\?\.)
|
||||
scope: keyword.operator.safenav.kotlin
|
||||
- match: (\.)
|
||||
scope: keyword.operator.dot.kotlin
|
||||
- match: (\?:)
|
||||
scope: keyword.operator.elvis.kotlin
|
||||
- match: (\-\-|\+\+)
|
||||
scope: keyword.operator.increment-decrement.kotlin
|
||||
- match: (\+=|\-=|\*=|\/=)
|
||||
scope: keyword.operator.arithmetic.assign.kotlin
|
||||
- match: (\.\.)
|
||||
scope: keyword.operator.range.kotlin
|
||||
- match: (\-|\+|\*|\/|%)
|
||||
scope: keyword.operator.arithmetic.kotlin
|
||||
- match: (!|&&|\|\|)
|
||||
scope: keyword.operator.logical.kotlin
|
||||
- match: (;)
|
||||
scope: punctuation.terminator.kotlin
|
||||
namespaces:
|
||||
- match: \b(namespace)\b
|
||||
scope: keyword.other.kotlin
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
parameters:
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|\)|=)
|
||||
pop: true
|
||||
- include: types
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?=,|\))
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: keywords
|
||||
- match: \w+
|
||||
scope: variable.parameter.function.kotlin
|
||||
statements:
|
||||
- include: namespaces
|
||||
- include: typedefs
|
||||
- include: classes
|
||||
- include: functions
|
||||
- include: variables
|
||||
- include: getters-and-setters
|
||||
- include: expressions
|
||||
strings:
|
||||
- match: '"""'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.third.kotlin
|
||||
- match: '"""'
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: '(\$\w+|\$\{[^\}]+\})'
|
||||
scope: variable.parameter.template.kotlin
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: '"'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.double.kotlin
|
||||
- match: '"'
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: '(\$\w+|\$\{[^\}]+\})'
|
||||
scope: variable.parameter.template.kotlin
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: "'"
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.single.kotlin
|
||||
- match: "'"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
- match: \\.
|
||||
scope: constant.character.escape.kotlin
|
||||
- match: "`"
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.kotlin
|
||||
push:
|
||||
- meta_scope: string.quoted.single.kotlin
|
||||
- match: "`"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.kotlin
|
||||
pop: true
|
||||
typedefs:
|
||||
- match: (?=\s*(?:type))
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- match: \b(type)\b
|
||||
scope: keyword.other.kotlin
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- include: expressions
|
||||
types:
|
||||
- match: \b(Nothing|Any|Unit|String|CharSequence|Int|Boolean|Char|Long|Double|Float|Short|Byte|dynamic)\b
|
||||
scope: storage.type.buildin.kotlin
|
||||
- match: \b(IntArray|BooleanArray|CharArray|LongArray|DoubleArray|FloatArray|ShortArray|ByteArray)\b
|
||||
scope: storage.type.buildin.array.kotlin
|
||||
- match: \b(Array|Collection|List|Map|Set|MutableList|MutableMap|MutableSet|Sequence)<\b
|
||||
captures:
|
||||
1: storage.type.buildin.collection.kotlin
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: \w+<
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: types
|
||||
- include: keywords
|
||||
- match: '\{'
|
||||
push:
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- include: statements
|
||||
- match: \(
|
||||
push:
|
||||
- match: \)
|
||||
pop: true
|
||||
- include: types
|
||||
- match: (->)
|
||||
scope: keyword.operator.declaration.kotlin
|
||||
variables:
|
||||
- match: (?=\s*\b(?:var|val)\b)
|
||||
push:
|
||||
- match: (?=:|=|(\b(by)\b)|$)
|
||||
pop: true
|
||||
- match: \b(var|val)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=:|=|(\b(by)\b)|$)
|
||||
pop: true
|
||||
- match: <
|
||||
push:
|
||||
- match: ">"
|
||||
pop: true
|
||||
- include: generics
|
||||
- match: '([\.<\?>\w]+\.)?(\w+)'
|
||||
captures:
|
||||
2: entity.name.variable.kotlin
|
||||
- match: (:)
|
||||
captures:
|
||||
1: keyword.operator.declaration.kotlin
|
||||
push:
|
||||
- match: (?==|$)
|
||||
pop: true
|
||||
- include: types
|
||||
- include: getters-and-setters
|
||||
- match: \b(by)\b
|
||||
captures:
|
||||
1: keyword.other.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- match: (=)
|
||||
captures:
|
||||
1: keyword.operator.assignment.kotlin
|
||||
push:
|
||||
- match: (?=$)
|
||||
pop: true
|
||||
- include: expressions
|
||||
- include: getters-and-setters
|
||||
Submodule
+1
Submodule assets/syntaxes/PowerShell added at 12b7d7257e.
@@ -0,0 +1,466 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: PowerShell
|
||||
file_extensions:
|
||||
- ps1
|
||||
- psm1
|
||||
- psd1
|
||||
scope: source.powershell
|
||||
contexts:
|
||||
main:
|
||||
- match: "<#"
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.begin.powershell
|
||||
push:
|
||||
- meta_scope: comment.block.powershell
|
||||
- match: "#>"
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.end.powershell
|
||||
pop: true
|
||||
- include: commentEmbeddedDocs
|
||||
- match: '[2-6]>&1|>>|>|<<|<|>|>\||[1-6]>|[1-6]>>'
|
||||
scope: keyword.operator.redirection.powershell
|
||||
- include: commands
|
||||
- include: commentLine
|
||||
- include: variable
|
||||
- include: interpolatedStringContent
|
||||
- include: function
|
||||
- include: attribute
|
||||
- include: UsingDirective
|
||||
- include: type
|
||||
- include: hashtable
|
||||
- include: doubleQuotedString
|
||||
- include: scriptblock
|
||||
- include: doubleQuotedStringEscapes
|
||||
- match: (?<!')'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.powershell
|
||||
push:
|
||||
- meta_scope: string.quoted.single.powershell
|
||||
- match: "'(?!')"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.powershell
|
||||
pop: true
|
||||
- match: "''"
|
||||
scope: constant.character.escape.powershell
|
||||
- match: \@"(?=$)
|
||||
push:
|
||||
- meta_scope: string.quoted.double.heredoc.powershell
|
||||
- match: ^"@
|
||||
pop: true
|
||||
- include: variableNoProperty
|
||||
- include: doubleQuotedStringEscapes
|
||||
- include: interpolation
|
||||
- match: \@'(?=$)
|
||||
push:
|
||||
- meta_scope: string.quoted.single.heredoc.powershell
|
||||
- match: ^'@
|
||||
pop: true
|
||||
- match: "''"
|
||||
scope: constant.character.escape.powershell
|
||||
- include: numericConstant
|
||||
- match: (@)(\()
|
||||
captures:
|
||||
1: keyword.other.array.begin.powershell
|
||||
2: punctuation.section.group.begin.powershell
|
||||
push:
|
||||
- meta_scope: meta.group.array-expression.powershell
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.section.group.end.powershell
|
||||
pop: true
|
||||
- include: main
|
||||
- match: (\$)(\()
|
||||
comment: "TODO: move to repo; make recursive."
|
||||
captures:
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: punctuation.section.group.begin.powershell
|
||||
push:
|
||||
- meta_scope: meta.group.complex.subexpression.powershell
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.section.group.end.powershell
|
||||
pop: true
|
||||
- include: main
|
||||
- match: '(\b(([A-Za-z0-9\-_\.]+)\.(?i:exe|com|cmd|bat))\b)'
|
||||
scope: support.function.powershell
|
||||
- match: (?<!\w|-|\.)((?i:begin|break|catch|continue|data|default|define|do|dynamicparam|else|elseif|end|exit|finally|for|from|if|in|inlinescript|parallel|param|process|return|sequence|switch|throw|trap|try|until|var|while)|%|\?)(?!\w)
|
||||
scope: keyword.control.powershell
|
||||
- match: '(?<!\w|-|[^\)]\.)((?i:(foreach|where)(?!-object))|%|\?)(?!\w)'
|
||||
scope: keyword.control.powershell
|
||||
- match: (?<!\w)(--%)(?!\w)
|
||||
comment: This should be moved to the repository at some point.
|
||||
captures:
|
||||
1: keyword.control.powershell
|
||||
push:
|
||||
- match: $
|
||||
pop: true
|
||||
- match: .+
|
||||
scope: string.unquoted.powershell
|
||||
- match: (?<!\w)((?i:hidden|static))(?!\w)
|
||||
comment: This should only be relevant inside a class but will require a rework of how classes are matched. This is a temp fix.
|
||||
scope: storage.modifier.powershell
|
||||
- match: '(?<!\w|-)((?i:class)|%|\?)(?:\s)+((?:\p{L}|\d|_|-|)+)\b'
|
||||
comment: capture should be entity.name.type, but it doesn't provide a good color in the default schema.
|
||||
captures:
|
||||
1: storage.type.powershell
|
||||
2: entity.name.function
|
||||
- match: (?<!\w)-(?i:is(?:not)?|as)\b
|
||||
scope: keyword.operator.comparison.powershell
|
||||
- match: '(?<!\w)-(?i:[ic]?(?:eq|ne|[gl][te]|(?:not)?(?:like|match|contains|in)|replace))(?!\p{L})'
|
||||
scope: keyword.operator.comparison.powershell
|
||||
- match: '(?<!\w)-(?i:join|split)(?!\p{L})|!'
|
||||
scope: keyword.operator.unary.powershell
|
||||
- match: '(?<!\w)-(?i:and|or|not|xor)(?!\p{L})|!'
|
||||
scope: keyword.operator.logical.powershell
|
||||
- match: '(?<!\w)-(?i:band|bor|bnot|bxor|shl|shr)(?!\p{L})'
|
||||
scope: keyword.operator.bitwise.powershell
|
||||
- match: '(?<!\w)-(?i:f)(?!\p{L})'
|
||||
scope: keyword.operator.string-format.powershell
|
||||
- match: "[+%*/-]?=|[+/*%-]"
|
||||
scope: keyword.operator.assignment.powershell
|
||||
- match: '\|{2}|&{2}|;'
|
||||
scope: punctuation.terminator.statement.powershell
|
||||
- match: '&|(?<!\w)\.(?= )|`|,|\|'
|
||||
scope: keyword.operator.other.powershell
|
||||
- match: (?<!\s|^)\.\.(?=\-?\d|\(|\$)
|
||||
comment: This is very imprecise, is there a syntax for 'must come after...'
|
||||
scope: keyword.operator.range.powershell
|
||||
RequiresDirective:
|
||||
- match: (?<=#)(?i:(requires))\s
|
||||
captures:
|
||||
0: keyword.control.requires.powershell
|
||||
push:
|
||||
- meta_scope: meta.requires.powershell
|
||||
- match: $
|
||||
pop: true
|
||||
- match: \-(?i:Modules|PSSnapin|RunAsAdministrator|ShellId|Version)
|
||||
scope: keyword.other.powershell
|
||||
- match: '(?<!-)\b\p{L}+|\d+(?:\.\d+)*'
|
||||
scope: variable.parameter.powershell
|
||||
- include: hashtable
|
||||
UsingDirective:
|
||||
- match: (?<!\w)(?i:(using))\s+(?i:(namespace|module))\s+(?i:((?:\w+(?:\.)?)+))
|
||||
captures:
|
||||
1: keyword.control.using.powershell
|
||||
2: keyword.other.powershell
|
||||
3: variable.parameter.powershell
|
||||
attribute:
|
||||
- match: '(\[)\s*\b(?i)(cmdletbinding|alias|outputtype|parameter|validatenotnull|validatenotnullorempty|validatecount|validateset|allownull|allowemptycollection|allowemptystring|validatescript|validaterange|validatepattern|validatelength)\b'
|
||||
captures:
|
||||
1: punctuation.section.bracket.begin.powershell
|
||||
2: support.function.attribute.powershell
|
||||
push:
|
||||
- meta_scope: meta.attribute.powershell
|
||||
- match: '(\])'
|
||||
captures:
|
||||
1: punctuation.section.bracket.end.powershell
|
||||
pop: true
|
||||
- match: \(
|
||||
captures:
|
||||
0: punctuation.section.group.begin.powershell
|
||||
push:
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.section.group.end.powershell
|
||||
pop: true
|
||||
- include: variable
|
||||
- include: variableNoProperty
|
||||
- include: hashtable
|
||||
- include: scriptblock
|
||||
- include: doubleQuotedStringEscapes
|
||||
- include: doubleQuotedString
|
||||
- include: type
|
||||
- include: numericConstant
|
||||
- include: doubleQuotedString
|
||||
- include: main
|
||||
- match: (?i)\b(mandatory|valuefrompipeline|valuefrompipelinebypropertyname|valuefromremainingarguments|position|parametersetname|defaultparametersetname|supportsshouldprocess|supportspaging|positionalbinding|helpuri|confirmimpact|helpmessage)\b(?:\s+)?(=)?
|
||||
captures:
|
||||
1: variable.parameter.attribute.powershell
|
||||
2: keyword.operator.assignment.powershell
|
||||
- match: (?<!')'
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.powershell
|
||||
push:
|
||||
- meta_scope: string.quoted.single.powershell
|
||||
- match: "'(?!')"
|
||||
captures:
|
||||
0: punctuation.definition.string.end.powershell
|
||||
pop: true
|
||||
- match: "''"
|
||||
scope: constant.character.escape.powershell
|
||||
commands:
|
||||
- match: '(?:(\p{L}|\d|_|-|\\|\:)*\\)?\b(?i:Add|Approve|Assert|Backup|Block|Build|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Deploy|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Mount|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Write)\-.+?(?:\.(?i:exe|cmd|bat|ps1))?\b'
|
||||
comment: "Verb-Noun pattern:"
|
||||
scope: support.function.powershell
|
||||
- match: (?<!\w)(?i:foreach-object)(?!\w)
|
||||
comment: Builtin cmdlets with reserved verbs
|
||||
scope: support.function.powershell
|
||||
- match: (?<!\w)(?i:where-object)(?!\w)
|
||||
comment: Builtin cmdlets with reserved verbs
|
||||
scope: support.function.powershell
|
||||
- match: (?<!\w)(?i:sort-object)(?!\w)
|
||||
comment: Builtin cmdlets with reserved verbs
|
||||
scope: support.function.powershell
|
||||
- match: (?<!\w)(?i:tee-object)(?!\w)
|
||||
comment: Builtin cmdlets with reserved verbs
|
||||
scope: support.function.powershell
|
||||
commentEmbeddedDocs:
|
||||
- match: ^(?i:(?:\s?|#)+(\.)(COMPONENT|DESCRIPTION|EXAMPLE|EXTERNALHELP|FORWARDHELPCATEGORY|FORWARDHELPTARGETNAME|FUNCTIONALITY|INPUTS|LINK|NOTES|OUTPUTS|REMOTEHELPRUNSPACE|ROLE|SYNOPSIS))
|
||||
scope: comment.documentation.embedded.powershell
|
||||
captures:
|
||||
1: constant.string.documentation.powershell
|
||||
2: keyword.operator.documentation.powershell
|
||||
- match: '(?i:\s?(\.)(PARAMETER|FORWARDHELPTARGETNAME|FORWARDHELPCATEGORY|REMOTEHELPRUNSPACE|EXTERNALHELP)\s+([a-z0-9-_]+))'
|
||||
scope: comment.documentation.embedded.powershell
|
||||
captures:
|
||||
1: constant.string.documentation.powershell
|
||||
2: keyword.operator.documentation.powershell
|
||||
3: keyword.operator.documentation.powershell
|
||||
commentLine:
|
||||
- match: '(?<![`\\-])#'
|
||||
captures:
|
||||
0: punctuation.definition.comment.powershell
|
||||
push:
|
||||
- meta_scope: comment.line.powershell
|
||||
- match: $\n?
|
||||
captures:
|
||||
0: punctuation.definition.comment.powershell
|
||||
pop: true
|
||||
- include: commentEmbeddedDocs
|
||||
- include: RequiresDirective
|
||||
doubleQuotedString:
|
||||
- match: (?<!(?<!`)")"
|
||||
captures:
|
||||
0: punctuation.definition.string.begin.powershell
|
||||
push:
|
||||
- meta_scope: string.quoted.double.powershell
|
||||
- match: '"(?!")'
|
||||
captures:
|
||||
0: punctuation.definition.string.end.powershell
|
||||
pop: true
|
||||
- match: '(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,64}\b'
|
||||
- include: variableNoProperty
|
||||
- include: variable
|
||||
- include: doubleQuotedStringEscapes
|
||||
- include: interpolation
|
||||
- match: '`\s*$'
|
||||
scope: keyword.other.powershell
|
||||
doubleQuotedStringEscapes:
|
||||
- match: '`[0abnfrvt"''$`]'
|
||||
scope: constant.character.escape.powershell
|
||||
- match: '""'
|
||||
scope: constant.character.escape.powershell
|
||||
function:
|
||||
- match: '^(?:\s*+)(?i)(function|filter|configuration|workflow)\s+(?:(global|local|script|private):)?((?:\p{L}|\d|_|-|\.)+)'
|
||||
captures:
|
||||
0: meta.function.powershell
|
||||
1: storage.type.powershell
|
||||
2: storage.modifier.scope.powershell
|
||||
3: entity.name.function.powershell
|
||||
push:
|
||||
- match: '(?=\{|\()'
|
||||
pop: true
|
||||
- include: commentLine
|
||||
hashtable:
|
||||
- match: '(@)(\{)'
|
||||
captures:
|
||||
1: keyword.other.hashtable.begin.powershell
|
||||
2: punctuation.section.braces.begin.powershell
|
||||
push:
|
||||
- meta_scope: meta.hashtable.powershell
|
||||
- match: '(\})'
|
||||
captures:
|
||||
1: punctuation.section.braces.end.powershell
|
||||
pop: true
|
||||
- match: \b((?:\'|\")?)(\w+)((?:\'|\")?)(?:\s+)?(=)(?:\s+)?
|
||||
scope: meta.hashtable.assignment.powershell
|
||||
captures:
|
||||
1: punctuation.definition.string.begin.powershell
|
||||
2: variable.other.readwrite.powershell
|
||||
3: punctuation.definition.string.end.powershell
|
||||
4: keyword.operator.assignment.powershell
|
||||
- include: scriptblock
|
||||
- include: main
|
||||
interpolatedStringContent:
|
||||
- match: \(
|
||||
captures:
|
||||
0: punctuation.section.group.begin.powershell
|
||||
push:
|
||||
- meta_content_scope: interpolated.simple.source.powershell
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.section.group.end.powershell
|
||||
pop: true
|
||||
- include: main
|
||||
- include: interpolation
|
||||
- include: interpolatedStringContent
|
||||
interpolation:
|
||||
- match: (\$)(\()
|
||||
captures:
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: punctuation.section.group.begin.powershell
|
||||
push:
|
||||
- meta_content_scope: interpolated.complex.source.powershell
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.section.group.end.powershell
|
||||
pop: true
|
||||
- include: main
|
||||
- include: interpolation
|
||||
- include: interpolatedStringContent
|
||||
numericConstant:
|
||||
- match: '(?<!\w)([-+]?0(?:x|X)[0-9a-fA-F_]+(?:U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.hex.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?(?:[0-9_]+)?\.[0-9_]+(?:(?:e|E)[0-9]+)?(?:F|f|D|d|M|m)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.integer.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?0(?:b|B)[01_]+(?:U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.octal.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?[0-9_]+(?:e|E)(?:[0-9_])?+(?:F|f|D|d|M|m)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.integer.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?[0-9_]+\.(?:e|E)(?:[0-9_])?+(?:F|f|D|d|M|m)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.integer.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?[0-9_]+[\.]?(?:F|f|D|d|M|m))((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.integer.powershell
|
||||
2: keyword.other.powershell
|
||||
- match: '(?<!\w)([-+]?[0-9_]+[\.]?(?:U|u|L|l|UL|Ul|uL|ul|LU|Lu|lU|lu)?)((?i:[kmgtp]b)?)\b'
|
||||
captures:
|
||||
1: constant.numeric.integer.powershell
|
||||
2: keyword.other.powershell
|
||||
scriptblock:
|
||||
- match: '\{'
|
||||
captures:
|
||||
0: punctuation.section.braces.begin.powershell
|
||||
push:
|
||||
- meta_scope: meta.scriptblock.powershell
|
||||
- match: '\}'
|
||||
captures:
|
||||
0: punctuation.section.braces.end.powershell
|
||||
pop: true
|
||||
- include: main
|
||||
type:
|
||||
- match: '\['
|
||||
captures:
|
||||
0: punctuation.section.bracket.begin.powershell
|
||||
push:
|
||||
- match: '\]'
|
||||
captures:
|
||||
0: punctuation.section.bracket.end.powershell
|
||||
pop: true
|
||||
- match: '(?!\d+|\.)(?:\p{L}|\p{N}|\.)+'
|
||||
scope: storage.type.powershell
|
||||
- include: main
|
||||
variable:
|
||||
- match: (\$)(?i:(False|Null|True))\b
|
||||
comment: These are special constants.
|
||||
captures:
|
||||
0: constant.language.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
- match: '(\$)(?i:(Error|ExecutionContext|Host|Home|PID|PsHome|PsVersionTable|ShellID))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
|
||||
comment: These are the other built-in constants.
|
||||
captures:
|
||||
0: support.constant.variable.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: '(\$)(?i:(\$|\^|\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
|
||||
comment: Automatic variables are not constants, but they are read-only. In monokai (default) color schema support.variable doesn't have color, so we use constant.
|
||||
captures:
|
||||
0: support.constant.automatic.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: '(\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))((?:\.(?:\p{L}|\d|_)+)*\b)?\b'
|
||||
comment: Style preference variables as language variables so that they stand out.
|
||||
captures:
|
||||
0: variable.language.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: '(?i:(\$|@)(global|local|private|script|using|workflow):((?:\p{L}|\d|_)+))((?:\.(?:\p{L}|\d|_)+)*\b)?'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: storage.modifier.scope.powershell
|
||||
4: variable.other.member.powershell
|
||||
- match: '(?i:(\$)(\{)(global|local|private|script|using|workflow):([^}]*[^}`])(\}))((?:\.(?:\p{L}|\d|_)+)*\b)?'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: punctuation.section.braces.begin.powershell
|
||||
3: storage.modifier.scope.powershell
|
||||
5: punctuation.section.braces.end.powershell
|
||||
6: variable.other.member.powershell
|
||||
- match: '(?i:(\$|@)((?:\p{L}|\d|_)+:)?((?:\p{L}|\d|_)+))((?:\.(?:\p{L}|\d|_)+)*\b)?'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: support.variable.drive.powershell
|
||||
4: variable.other.member.powershell
|
||||
- match: '(?i:(\$)(\{)((?:\p{L}|\d|_)+:)?([^}]*[^}`])(\}))((?:\.(?:\p{L}|\d|_)+)*\b)?'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: punctuation.section.braces.begin.powershell
|
||||
3: support.variable.drive.powershell
|
||||
5: punctuation.section.braces.end.powershell
|
||||
6: variable.other.member.powershell
|
||||
variableNoProperty:
|
||||
- match: (\$)(?i:(False|Null|True))\b
|
||||
comment: These are special constants.
|
||||
captures:
|
||||
0: constant.language.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
- match: (\$)(?i:(Error|ExecutionContext|Host|Home|PID|PsHome|PsVersionTable|ShellID))\b
|
||||
comment: These are the other built-in constants.
|
||||
captures:
|
||||
0: support.constant.variable.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: (\$)(?i:(\$|\^|\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))\b
|
||||
comment: Automatic variables are not constants, but they are read-only...
|
||||
captures:
|
||||
0: support.variable.automatic.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: (\$)(?i:(ConfirmPreference|DebugPreference|ErrorActionPreference|ErrorView|FormatEnumerationLimit|MaximumAliasCount|MaximumDriveCount|MaximumErrorCount|MaximumFunctionCount|MaximumHistoryCount|MaximumVariableCount|OFS|OutputEncoding|ProgressPreference|PsCulture|PSDebugContext|PSDefaultParameterValues|PSEmailServer|PSItem|PSModuleAutoloadingPreference|PSSenderInfo|PSSessionApplicationName|PSSessionConfigurationName|PSSessionOption|VerbosePreference|WarningPreference|WhatIfPreference))\b
|
||||
comment: Style preference variables as language variables so that they stand out.
|
||||
captures:
|
||||
0: variable.language.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
3: variable.other.member.powershell
|
||||
- match: '(?i:(\$|@)(global|local|private|script|using|workflow):((?:\p{L}|\d|_)+))'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: storage.modifier.scope.powershell
|
||||
4: variable.other.member.powershell
|
||||
- match: '(?i:(\$)(\{)(global|local|private|script|using|workflow):([^}]*[^}`])(\}))'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: storage.modifier.scope.powershell
|
||||
4: keyword.other.powershell
|
||||
5: variable.other.member.powershell
|
||||
- match: '(?i:(\$)((?:\p{L}|\d|_)+:)?((?:\p{L}|\d|_)+))'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: support.variable.drive.powershell
|
||||
4: variable.other.member.powershell
|
||||
- match: '(?i:(\$)(\{)((?:\p{L}|\d|_)+:)?([^}]*[^}`])(\}))'
|
||||
captures:
|
||||
0: variable.other.readwrite.powershell
|
||||
1: punctuation.definition.variable.powershell
|
||||
2: punctuation.section.braces.begin
|
||||
3: support.variable.drive.powershell
|
||||
5: punctuation.section.braces.end
|
||||
Submodule
+1
Submodule assets/syntaxes/Puppet added at 2a224343d2.
Submodule
+1
Submodule assets/syntaxes/PureScript added at 42a61e39ad.
Submodule
+1
Submodule assets/syntaxes/Robot added at 8e5a349a3e.
@@ -0,0 +1,54 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Robot Framework syntax highlighting.
|
||||
file_extensions:
|
||||
- robot
|
||||
scope: source.robot
|
||||
contexts:
|
||||
main:
|
||||
- match: '\$\{(\d+|\d+\.\d*|0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+)\}'
|
||||
comment: "Robot Framework numbers, like ${1} or ${12.3}"
|
||||
scope: constant.numeric.robot
|
||||
- match: '(?i)(%\{[\w|\s]+\})'
|
||||
comment: "Robot Framework environment variable, like %{USER}"
|
||||
scope: variable.parameter.robot
|
||||
- match: '((?<!\\)|(?<=\\\\))[$&]{'
|
||||
comment: Robot Framework scalar and dictionary variables
|
||||
push:
|
||||
- meta_scope: entity.name.class
|
||||
- match: '\}'
|
||||
pop: true
|
||||
- match: '((?<!\\)(@{)|(?<=\\\\)(@{))'
|
||||
comment: Robot Framework list variables
|
||||
push:
|
||||
- meta_scope: entity.name.class
|
||||
- match: '(}\[.+])|(})'
|
||||
pop: true
|
||||
- match: '(?i)^(\|\s*)?(\*{1,3} ?)(settings?|variables?|keywords?|test cases?)( ?\*{1,3})?(\s*\|)?'
|
||||
comment: Robot Framework data tables
|
||||
scope: string.robot.header
|
||||
- match: '(?i)(^\|\s*)?\[?Documentation\]?\s+'
|
||||
comment: Test case, keyword and settings table documentation
|
||||
push:
|
||||
- meta_scope: comment
|
||||
- match: ^(?!\s*+\.\.\.)
|
||||
pop: true
|
||||
- match: '(^| {2,}|\t|\\| {1,})(?<!\\\\)#'
|
||||
comment: Comment character
|
||||
push:
|
||||
- meta_scope: comment
|
||||
- match: $
|
||||
pop: true
|
||||
- match: '(?i)\s+\[(Tags|Setup|Teardown|Template|Timeout|Arguments|Return)\]'
|
||||
comment: Tests case and keyword settings
|
||||
scope: storage.type
|
||||
- match: (?i)^(\|\s*)?(Library|Resource|Test Timeout|Test Template|Test Teardown|Test Setup|Default Tags|Force Tags|Metadata|Variables|Suite Setup|Suite Teardown)(?:( )|( \| ))
|
||||
comment: Settings table settings, like Library
|
||||
scope: constant.language
|
||||
- match: '^(\|\s+)?(?!^\.{3})(?![\|$&])\S+'
|
||||
comment: Keywords and test cases
|
||||
push:
|
||||
- meta_scope: keyword.control.robot
|
||||
- match: '($|\||\s{2,})'
|
||||
pop: true
|
||||
Submodule
+1
Submodule assets/syntaxes/Swift added at fef17119ce.
@@ -0,0 +1,371 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Swift
|
||||
file_extensions:
|
||||
- swift
|
||||
first_line_match: ^#!/.*\bswift
|
||||
scope: source.swift
|
||||
contexts:
|
||||
main:
|
||||
- include: shebang-line
|
||||
- include: comment
|
||||
- include: attribute
|
||||
- include: literal
|
||||
- include: operator
|
||||
- include: declaration
|
||||
- include: storage-type
|
||||
- include: keyword
|
||||
- include: type
|
||||
- include: boolean
|
||||
comment:
|
||||
- include: documentation-comment
|
||||
- include: block-comment
|
||||
- include: in-line-comment
|
||||
access-level-modifier:
|
||||
- match: \b(open|public|internal|fileprivate|private)\b(?:\(set\))?
|
||||
comment: access-level-modifier
|
||||
scope: keyword.other.access-level-modifier.swift
|
||||
arithmetic-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(\+|\-|\*|\/)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.arithmetic.swift
|
||||
array-type:
|
||||
- match: \b(Array)(<)
|
||||
captures:
|
||||
1: support.type.array.swift
|
||||
2: punctuation.array.begin.swift
|
||||
push:
|
||||
- meta_scope: meta.array.swift
|
||||
- match: (>)
|
||||
captures:
|
||||
1: punctuation.array.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
assignment-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(\+|\-|\*|\/|%|<<|>>|&|\^|\||&&|\|\|)?=(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.assignment.swift
|
||||
attribute:
|
||||
- match: '((@)(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B))(\()'
|
||||
captures:
|
||||
1: storage.modifier.attribute.swift
|
||||
2: punctuation.definition.attribute.swift
|
||||
3: punctuation.definition.attribute-arguments.begin.swift
|
||||
push:
|
||||
- meta_content_scope: meta.attribute.arguments.swift
|
||||
- match: \)
|
||||
captures:
|
||||
0: punctuation.definition.attribute-arguments.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
- match: '((@)(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B))'
|
||||
captures:
|
||||
1: storage.modifier.attribute.swift
|
||||
2: punctuation.definition.attribute.swift
|
||||
bitwise-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(&|\||\^|<<|>>)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.bitwise.swift
|
||||
block-comment:
|
||||
- match: /\*
|
||||
comment: Block comment
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.begin.swift
|
||||
push:
|
||||
- meta_scope: comment.block.swift
|
||||
- match: \*/
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.end.swift
|
||||
pop: true
|
||||
boolean:
|
||||
- match: \b(true|false)\b
|
||||
scope: keyword.constant.boolean.swift
|
||||
branch-statement-keyword:
|
||||
- include: if-statement-keyword
|
||||
- include: switch-statement-keyword
|
||||
catch-statement-keyword:
|
||||
- match: \b(catch|do)\b
|
||||
comment: catch-statement
|
||||
scope: kewyord.control.catch.swift
|
||||
code-block:
|
||||
- match: '(\{)'
|
||||
comment: code-block
|
||||
captures:
|
||||
1: punctuation.definition.code-block.begin.swift
|
||||
push:
|
||||
- match: '(\})'
|
||||
captures:
|
||||
1: punctuation.definition.code-block.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
collection-type:
|
||||
- include: array-type
|
||||
- include: dictionary-type
|
||||
- match: \b(Array|Dictionary)\b
|
||||
scope: support.type.swift
|
||||
comparative-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])((=|!)==?|(<|>)=?|~=)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.comparative.swift
|
||||
control-transfer-statement-keyword:
|
||||
- match: \b(continue|break|fallthrough|return)\b
|
||||
comment: control-transfer-statement
|
||||
scope: keyword.control.transfer.swift
|
||||
custom-operator:
|
||||
- match: '(?<=[\s(\[{,;:])([/=\-+!*%<>&|\^~.]++)(?![\s)\]},;:])'
|
||||
scope: keyword.operator.custom.prefix.unary.swift
|
||||
- match: '(?<![\s(\[{,;:])([/=\-+!*%<>&|\^~.]++)(?![\s)\]},;:\.])'
|
||||
scope: keyword.operator.custom.postfix.unary.swift
|
||||
- match: '(?<=[\s(\[{,;:])([/=\-+!*%<>&|\^~.]++)(?=[\s)\]},;:])'
|
||||
scope: keyword.operator.custom.binary.swift
|
||||
declaration:
|
||||
- include: import-declaration
|
||||
- include: function-declaration
|
||||
declaration-modifier:
|
||||
- match: \b(class|convenience|dynamic|final|lazy|(non)?mutating|optional|override|required|static|unowned((un)?safe)?|weak)\b
|
||||
comment: declaration-modifier
|
||||
scope: keyword.other.declaration-modifier.swift
|
||||
dictionary-type:
|
||||
- match: \b(Dictionary)(<)
|
||||
captures:
|
||||
1: support.type.dictionary.swift
|
||||
2: punctuation.dictionary.begin.swift
|
||||
push:
|
||||
- meta_scope: meta.dictionary.swift
|
||||
- match: (>)
|
||||
captures:
|
||||
1: punctuation.dictionary.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
documentation-comment:
|
||||
- match: /\*\*
|
||||
comment: Documentation comment
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.documentation.begin.swift
|
||||
push:
|
||||
- meta_scope: comment.block.documentation.swift
|
||||
- match: \*/
|
||||
captures:
|
||||
0: punctuation.definition.comment.block.documentation.end.swift
|
||||
pop: true
|
||||
floating-point-literal:
|
||||
- match: '\b([0-9][0-9_]*)(\.([0-9][0-9_]*))?([eE][+\-]?([0-9][0-9_]*))?\b'
|
||||
comment: floating-point-literal -> (decimal-literal)(decimal-fraction)?(decimal-exponent)?
|
||||
- match: '\b(0x\h[\h_]*)(\.(0x\h[\h_]*))?([pP][+\-]?(0x\h[\h_]*))\b'
|
||||
comment: floating-point-literal -> (hexadecimal-literal)(hexadecimal-fraction)?(hexadecimal-exponent)
|
||||
function-body:
|
||||
- include: code-block
|
||||
function-declaration:
|
||||
- match: '\b(func)\s+(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+)\s*(?=\(|<)'
|
||||
comment: function-declaration
|
||||
captures:
|
||||
1: storage.type.function.swift
|
||||
2: entity.type.function.swift
|
||||
push:
|
||||
- meta_scope: meta.function-declaration.swift
|
||||
- match: '(?<=\})'
|
||||
pop: true
|
||||
- include: generic-parameter-clause
|
||||
- include: parameter-clause
|
||||
- include: function-result
|
||||
- include: function-body
|
||||
function-result:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(\->)(?![/=\-+!*%<>&|\^~.])\s*'
|
||||
comment: function-result
|
||||
captures:
|
||||
1: keyword.operator.function-result.swift
|
||||
push:
|
||||
- meta_scope: meta.function-result.swift
|
||||
- match: '\s*(?=\{)'
|
||||
pop: true
|
||||
- include: type
|
||||
generic-parameter-clause:
|
||||
- match: (<)
|
||||
comment: generic-parameter-clause
|
||||
captures:
|
||||
1: punctuation.definition.generic-parameter-clause.begin.swift
|
||||
push:
|
||||
- meta_scope: meta.generic-parameter-clause.swift
|
||||
- match: (>)
|
||||
captures:
|
||||
1: punctuation.definition.generic-parameter-clause.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
identifier:
|
||||
- match: '(\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B)'
|
||||
comment: identifier
|
||||
scope: meta.identifier.swift
|
||||
if-statement-keyword:
|
||||
- match: \b(if|else)\b
|
||||
comment: if-statement
|
||||
scope: keyword.control.if.swift
|
||||
import-declaration:
|
||||
- match: '\b(import)\s+(?:(typealias|struct|class|enum|protocol|var|func)\s+)?((?:\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+)(?:\.(?:\B\$[0-9]+|\b[\w^\d][\w\d]*\b|\B`[\w^\d][\w\d]*`\B|[/=\-+!*%<>&|\^~.]+))*)'
|
||||
comment: import-declaration
|
||||
scope: meta.import.swift
|
||||
captures:
|
||||
1: keyword.other.import.swift
|
||||
2: storage.modifier.swift
|
||||
3: support.type.module.import.swift
|
||||
in-line-comment:
|
||||
- match: (//).*
|
||||
comment: In-line comment
|
||||
scope: comment.line.double-slash.swift
|
||||
captures:
|
||||
1: punctuation.definition.comment.line.double-slash.swift
|
||||
increment-decrement-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(\+\+|\-\-)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.increment-or-decrement.swift
|
||||
integer-literal:
|
||||
- match: '(\B\-|\b)(0b[01][01_]*)\b'
|
||||
comment: binary-literal
|
||||
scope: constant.numeric.integer.binary.swift
|
||||
- match: '(\B\-|\b)(0o[0-7][0-7_]*)\b'
|
||||
comment: octal-literal
|
||||
scope: constant.numeric.integer.octal.swift
|
||||
- match: '(\B\-|\b)([0-9][0-9_]*)\b'
|
||||
comment: decimal-literal
|
||||
scope: constant.numeric.integer.decimal.swift
|
||||
- match: '(\B\-|\b)(0x\h[\h_]*)\b'
|
||||
comment: hexadecimal-literal
|
||||
scope: constant.numeric.integer.hexadecimal.swift
|
||||
integer-type:
|
||||
- match: \bU?Int(8|16|32|64)?\b
|
||||
comment: Int types
|
||||
scope: support.type.swift
|
||||
keyword:
|
||||
- include: branch-statement-keyword
|
||||
- include: control-transfer-statement-keyword
|
||||
- include: loop-statement-keyword
|
||||
- include: catch-statement-keyword
|
||||
- include: operator-declaration-modifier
|
||||
- include: declaration-modifier
|
||||
- include: access-level-modifier
|
||||
- match: \b(class|deinit|enum|extension|func|import|init|let|protocol|static|struct|subscript|typealias|var|throws|rethrows)\b
|
||||
comment: declaration keyword
|
||||
scope: keyword.declaration.swift
|
||||
- match: \b(break|case|continue|default|do|else|fallthrough|if|in|for|return|switch|where|while|repeat|catch|guard|defer|try|throw)\b
|
||||
comment: statement keyword
|
||||
scope: keyword.statement.swift
|
||||
- match: \b(as|dynamicType|is|new|super|self|Self|Type)\b
|
||||
comment: expression and type keyword
|
||||
scope: keyword.other.statement.swift
|
||||
- match: \b(associativity|didSet|get|infix|inout|left|mutating|none|nonmutating|operator|override|postfix|precedence|prefix|right|set|unowned((un)?safe)?|weak|willSet)\b
|
||||
comment: other keyword
|
||||
scope: keyword.other.swift
|
||||
literal:
|
||||
- include: integer-literal
|
||||
- include: floating-point-literal
|
||||
- include: nil-literal
|
||||
- include: string-literal
|
||||
- include: special-literal
|
||||
logical-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])(!|&&|\|\|)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.logical.swift
|
||||
loop-statement-keyword:
|
||||
- match: \b(while|repeat|for|in)\b
|
||||
comment: loop-statement
|
||||
scope: keyword.control.loop.swift
|
||||
nil-literal:
|
||||
- match: \bnil\b
|
||||
comment: nil-literal
|
||||
scope: constant.nil.swift
|
||||
operator:
|
||||
- include: comparative-operator
|
||||
- include: assignment-operator
|
||||
- include: logical-operator
|
||||
- include: remainder-operator
|
||||
- include: increment-decrement-operator
|
||||
- include: overflow-operator
|
||||
- include: range-operator
|
||||
- include: bitwise-operator
|
||||
- include: arithmetic-operator
|
||||
- include: ternary-operator
|
||||
- include: type-casting-operator
|
||||
- include: custom-operator
|
||||
operator-declaration-modifier:
|
||||
- match: \b(operator|prefix|infix|postfix)\b
|
||||
comment: operator-declaration
|
||||
scope: keyword.other.operator.swift
|
||||
optional-type:
|
||||
- match: \b(Optional)(<)
|
||||
scope: meta.optional.swift
|
||||
overflow-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])\&(\+|\-|\*|\/|%)(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.overflow.swift
|
||||
parameter-clause:
|
||||
- match: (\()
|
||||
comment: parameter-clause
|
||||
captures:
|
||||
1: punctuation.definition.function-arguments.begin.swift
|
||||
push:
|
||||
- meta_scope: meta.parameter-clause.swift
|
||||
- match: (\))
|
||||
captures:
|
||||
1: punctuation.definition.function-arguments.end.swift
|
||||
pop: true
|
||||
- include: main
|
||||
primitive-type:
|
||||
- match: \b(Int|Float|Double|String|Bool|Character|Void)\b
|
||||
comment: Primitive types
|
||||
scope: support.type.swift
|
||||
protocol-composition-type:
|
||||
- match: \b(protocol)(<)
|
||||
scope: meta.protocol.swift
|
||||
range-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])\.\.(?:\.)?(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.range.swift
|
||||
remainder-operator:
|
||||
- match: '(?<![/=\-+!*%<>&|\^~.])\%(?![/=\-+!*%<>&|\^~.])'
|
||||
scope: keyword.operator.remainder.swift
|
||||
shebang-line:
|
||||
- match: ^(#!).*$
|
||||
comment: Shebang line
|
||||
scope: comment.line.shebang.swift
|
||||
captures:
|
||||
1: punctuation.definition.comment.line.shebang.swift
|
||||
special-literal:
|
||||
- match: \b__(FILE|LINE|COLUMN|FUNCTION)__\b
|
||||
scope: keyword.other.literal.swift
|
||||
storage-type:
|
||||
- match: \b(var|func|let|class|enum|struct|protocol|extension|typealias)\b
|
||||
scope: storage.type.swift
|
||||
string-literal:
|
||||
- match: \"
|
||||
captures:
|
||||
0: string.quoted.double.swift
|
||||
push:
|
||||
- meta_scope: meta.literal.string.swift
|
||||
- match: \"
|
||||
captures:
|
||||
0: string.quoted.double.swift
|
||||
pop: true
|
||||
- match: '\\([0tnr\"\''\\]|x\h{2}|u\h{4}|U\h{8})'
|
||||
scope: constant.character.escape.swift
|
||||
- match: (\\\()
|
||||
captures:
|
||||
1: support.punctuation.expression.begin.swift
|
||||
push:
|
||||
- meta_content_scope: meta.expression.swift
|
||||
- match: (\))
|
||||
captures:
|
||||
1: support.punctuation.expression.end.swift
|
||||
pop: true
|
||||
- include: scope:source.swift
|
||||
- match: (\"|\\)
|
||||
scope: invalid.illegal.swift
|
||||
- match: (.)
|
||||
scope: string.quoted.double.swift
|
||||
switch-statement-keyword:
|
||||
- match: \b(switch|case|default|where)\b
|
||||
comment: switch-statement
|
||||
scope: keyword.control.switch.swift
|
||||
ternary-operator:
|
||||
- match: '(?<=[\s(\[{,;:])(\?|:)(?=[\s)\]},;:])'
|
||||
scope: keyword.operator.ternary.swift
|
||||
type:
|
||||
- include: primitive-type
|
||||
- include: integer-type
|
||||
- include: collection-type
|
||||
- include: optional-type
|
||||
- include: protocol-composition-type
|
||||
type-casting-operator:
|
||||
- match: \b(is\b|as(\?\B|\b))
|
||||
scope: keyword.operator.type-casting.swift
|
||||
+1
-1
Submodule assets/syntaxes/TOML updated: b5e3064e99...4e74b2b41b.
Submodule
+1
Submodule assets/syntaxes/TypeScript added at 19a599ad0e.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+1
-1
Submodule assets/syntaxes/VimL updated: b453aff6f7...ed40c3bc81.
@@ -63,7 +63,7 @@ contexts:
|
||||
- match: \b(if|while|for|try|return|throw|end(if|for|while|try)?|au(g|group)|else(if|)?|do|in|catch|finally|:)\b
|
||||
scope: keyword.control.viml
|
||||
number_hex:
|
||||
- match: "#[0-9a-f]{6}"
|
||||
- match: "0x[0-9a-f]+"
|
||||
scope: constant.numeric.hex
|
||||
number_int:
|
||||
- match: '-?\d+'
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
%YAML 1.2
|
||||
---
|
||||
# http://www.sublimetext.com/docs/3/syntax.html
|
||||
name: Highlight non-printables
|
||||
file_extensions:
|
||||
- show-nonprintable
|
||||
scope: whitespace
|
||||
contexts:
|
||||
main:
|
||||
- match: "•"
|
||||
scope: support.function.show-nonprintable.space
|
||||
- match: "├─*┤"
|
||||
scope: constant.character.escape.show-nonprintable.tab
|
||||
- match: "↹"
|
||||
scope: constant.character.escape.show-nonprintable.tab
|
||||
- match: "␊"
|
||||
scope: keyword.operator.show-nonprintable.line-feed
|
||||
- match: "␍"
|
||||
scope: string.show-nonprintable.carriage-return
|
||||
- match: "␀"
|
||||
scope: entity.other.attribute-name.show-nonprintable.null
|
||||
- match: "␇"
|
||||
scope: entity.other.attribute-name.show-nonprintable.bell
|
||||
- match: "␛"
|
||||
scope: entity.other.attribute-name.show-nonprintable.escape
|
||||
- match: "␈"
|
||||
scope: entity.other.attribute-name.show-nonprintable.backspace
|
||||
@@ -0,0 +1,5 @@
|
||||
// Output the square of a number.
|
||||
fn print_square(num: f64) {
|
||||
let result = f64::powf(num, 2.0);
|
||||
println!("The square of {:.2} is {:.2}.", num, result);
|
||||
}
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
sublime-monokai-extended/Monokai Extended.tmTheme
|
||||
Submodule
+1
Submodule assets/themes/onehalf added at 2516b37c3f.
Submodule
+1
Submodule assets/themes/sublime-snazzy added at 70343201f1.
Submodule
+1
Submodule assets/themes/zenburn added at b7f27e2f65.
@@ -0,0 +1,26 @@
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
use clap::Shell;
|
||||
use std::fs;
|
||||
|
||||
include!("src/clap_app.rs");
|
||||
|
||||
const BIN_NAME: &str = "bat";
|
||||
|
||||
fn main() {
|
||||
let outdir = std::env::var_os("SHELL_COMPLETIONS_DIR").or(std::env::var_os("OUT_DIR"));
|
||||
|
||||
let outdir = match outdir {
|
||||
None => return,
|
||||
Some(outdir) => outdir,
|
||||
};
|
||||
|
||||
fs::create_dir_all(&outdir).unwrap();
|
||||
|
||||
let mut app = build_app(true);
|
||||
app.gen_completions(BIN_NAME, Shell::Bash, &outdir);
|
||||
app.gen_completions(BIN_NAME, Shell::Fish, &outdir);
|
||||
app.gen_completions(BIN_NAME, Shell::Zsh, &outdir);
|
||||
app.gen_completions(BIN_NAME, Shell::PowerShell, &outdir);
|
||||
}
|
||||
+28
-2
@@ -19,22 +19,32 @@ pack() {
|
||||
|
||||
if [[ $TARGET == "arm-unknown-linux-gnueabihf" ]]; then
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
elif [[ $TARGET == "aarch64-unknown-linux-gnu" ]]; then
|
||||
gcc_prefix="aarch64-linux-gnu-"
|
||||
else
|
||||
gcc_prefix=""
|
||||
fi
|
||||
|
||||
# create a "staging" directory
|
||||
mkdir "$tempdir/$package_name"
|
||||
mkdir "$tempdir/$package_name/autocomplete"
|
||||
|
||||
# copying the main binary
|
||||
cp "target/$TARGET/release/$PROJECT_NAME" "$tempdir/$package_name/"
|
||||
"${gcc_prefix}"strip "$tempdir/$package_name/$PROJECT_NAME"
|
||||
|
||||
# readme and license
|
||||
# manpage, readme and license
|
||||
cp "doc/$PROJECT_NAME.1" "$tempdir/$package_name"
|
||||
cp README.md "$tempdir/$package_name"
|
||||
cp LICENSE-MIT "$tempdir/$package_name"
|
||||
cp LICENSE-APACHE "$tempdir/$package_name"
|
||||
|
||||
# various autocomplete
|
||||
# TODO: disabled for now, see issue #372
|
||||
# cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".bash "$tempdir/$package_name/autocomplete/${PROJECT_NAME}.bash-completion"
|
||||
# cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/"$PROJECT_NAME".fish "$tempdir/$package_name/autocomplete"
|
||||
# cp target/"$TARGET"/release/build/"$PROJECT_NAME"-*/out/_"$PROJECT_NAME" "$tempdir/$package_name/autocomplete"
|
||||
|
||||
# archiving
|
||||
pushd "$tempdir"
|
||||
tar czf "$out_dir/$package_name.tar.gz" "$package_name"/*
|
||||
@@ -48,13 +58,24 @@ make_deb() {
|
||||
local version
|
||||
local dpkgname
|
||||
local conflictname
|
||||
local gcc_prefix
|
||||
|
||||
case $TARGET in
|
||||
x86_64*)
|
||||
architecture=amd64
|
||||
gcc_prefix=""
|
||||
;;
|
||||
i686*)
|
||||
architecture=i386
|
||||
gcc_prefix=""
|
||||
;;
|
||||
aarch64*)
|
||||
architecture=arm64
|
||||
gcc_prefix="aarch64-linux-gnu-"
|
||||
;;
|
||||
arm*hf)
|
||||
architecture=armhf
|
||||
gcc_prefix="arm-linux-gnueabihf-"
|
||||
;;
|
||||
*)
|
||||
echo "make_deb: skipping target '${TARGET}'" >&2
|
||||
@@ -74,7 +95,11 @@ make_deb() {
|
||||
|
||||
# copy the main binary
|
||||
install -Dm755 "target/$TARGET/release/$PROJECT_NAME" "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
"${gcc_prefix}"strip "$tempdir/usr/bin/$PROJECT_NAME"
|
||||
|
||||
# manpage
|
||||
install -Dm644 "doc/$PROJECT_NAME.1" "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
gzip --best "$tempdir/usr/share/man/man1/$PROJECT_NAME.1"
|
||||
|
||||
# readme and license
|
||||
install -Dm644 README.md "$tempdir/usr/share/doc/$PROJECT_NAME/README.md"
|
||||
@@ -93,6 +118,7 @@ Architecture: $architecture
|
||||
Provides: $PROJECT_NAME
|
||||
Conflicts: $conflictname
|
||||
Description: A cat(1) clone with wings.
|
||||
A cat(1) clone with syntax highlighting and Git integration.
|
||||
EOF
|
||||
|
||||
fakeroot dpkg-deb --build "$tempdir" "${dpkgname}_${version}_${architecture}.deb"
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# This script takes care of packaging the build artifacts that will go in the
|
||||
# release zipfile
|
||||
|
||||
$SRC_DIR = $PWD.Path
|
||||
$STAGE = [System.Guid]::NewGuid().ToString()
|
||||
|
||||
Set-Location $ENV:Temp
|
||||
New-Item -Type Directory -Name $STAGE
|
||||
Set-Location $STAGE
|
||||
|
||||
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
|
||||
|
||||
Copy-Item "$SRC_DIR\target\release\bat.exe" '.\'
|
||||
# TODO: disabled for now, see issue #372
|
||||
# Copy-Item $SRC_DIR\target\release\build\bat-*\out\_bat.ps1 '.\'
|
||||
|
||||
# readme and license
|
||||
Copy-Item $SRC_DIR\README.md '.\'
|
||||
Copy-Item $SRC_DIR\LICENSE-MIT '.\'
|
||||
Copy-Item $SRC_DIR\LICENSE-APACHE '.\'
|
||||
|
||||
7z a "$ZIP" *
|
||||
|
||||
Push-AppveyorArtifact "$ZIP"
|
||||
|
||||
Set-Location ..
|
||||
Remove-Item $STAGE -Force -Recurse
|
||||
Set-Location $SRC_DIR
|
||||
@@ -27,3 +27,11 @@ if [[ $TARGET == arm-unknown-linux-gnueabihf ]]; then
|
||||
libc6-armhf-cross \
|
||||
libc6-dev-armhf-cross
|
||||
fi
|
||||
|
||||
# needed for cross-compiling for arm64
|
||||
if [[ $TARGET == aarch64-unknown-linux-gnu ]]; then
|
||||
sudo apt-get install -y \
|
||||
gcc-4.8-aarch64-linux-gnu \
|
||||
binutils-aarch64-linux-gnu \
|
||||
gcc-aarch64-linux-gnu
|
||||
fi
|
||||
+1
-1
@@ -6,7 +6,7 @@ set -ex
|
||||
cargo build --target "$TARGET" --verbose
|
||||
|
||||
# We cannot run arm executables on linux
|
||||
if [[ $TARGET != arm-unknown-linux-gnueabihf ]]; then
|
||||
if [[ $TARGET != arm-unknown-linux-gnueabihf ]] && [[ $TARGET != aarch64-unknown-linux-gnu ]]; then
|
||||
cargo test --target "$TARGET" --verbose
|
||||
|
||||
# Run 'bat' on its own source code and the README
|
||||
|
||||
+5
-2
@@ -6,10 +6,10 @@ if you are not looking for a program like `bat`, this comparison might not be fo
|
||||
|
||||
| | bat | [pygments](http://pygments.org/) | [highlight](http://www.andre-simon.de/doku/highlight/highlight.php) | [ccat](https://github.com/jingweno/ccat) | [source-highlight](https://www.gnu.org/software/src-highlite/) | [hicat](https://github.com/rstacruz/hicat) | [coderay](https://github.com/rubychan/coderay) | [rouge](https://github.com/jneen/rouge) |
|
||||
|----------------------------------------------|---------------------------------------------------------------------|----------------------------------|---------------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| Drop-in `cat` replacement | (:heavy_check_mark:) [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: |
|
||||
| Drop-in `cat` replacement | :heavy_check_mark: [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: |
|
||||
| Git integration | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: |
|
||||
| Automatic paging | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: |
|
||||
| Languages (circa) | 60 | 300 | 200 | 7 | 80 | 130 | 30 | 130 |
|
||||
| Languages (circa) | 100 | 300 | 200 | 7 | 80 | 130 | 30 | 130 |
|
||||
| Extensible (languages, themes) | :heavy_check_mark: | (:heavy_check_mark:) | (:heavy_check_mark:) | :x: | (:heavy_check_mark:) | :x: | :x: | :x: |
|
||||
| Advanced highlighting (e.g. nested syntaxes) | :heavy_check_mark: | :heavy_check_mark: | (:heavy_check_mark:) ? | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Execution time [ms] (`jquery-3.3.1.js`) | 624 | 789 | 400 | 80 | 300 | 316 | 157 | 695 |
|
||||
@@ -18,3 +18,6 @@ if you are not looking for a program like `bat`, this comparison might not be fo
|
||||
|
||||
If you think that some entries in this table are outdated or wrong, please open a ticket or pull
|
||||
request.
|
||||
|
||||
Some other alternatives that are also related, but not yet included in the table:
|
||||
- [lesspipe](https://github.com/wofr06/lesspipe)
|
||||
@@ -0,0 +1,137 @@
|
||||
.TH BAT "1"
|
||||
.SH NAME
|
||||
bat \- manual page for bat
|
||||
.SH DESCRIPTION
|
||||
bat - a cat(1) clone with syntax highlighting and Git integration.
|
||||
.SH "USAGE"
|
||||
.IP
|
||||
bat [OPTIONS] [FILE]...
|
||||
.IP
|
||||
bat <SUBCOMMAND>
|
||||
.SH "OPTIONS"
|
||||
.HP
|
||||
\fB\-l\fR, \fB\-\-language\fR <language>
|
||||
.IP
|
||||
Explicitly set the language for syntax highlighting. The language can be specified as a
|
||||
name (like 'C++' or 'LaTeX') or possible file extension (like 'cpp', 'hpp' or 'md'). Use
|
||||
\&'\-\-list\-languages' to show all supported language names and file extensions.
|
||||
.HP
|
||||
\fB\-\-list\-languages\fR
|
||||
.IP
|
||||
Display a list of supported languages for syntax highlighting.
|
||||
.HP
|
||||
\fB\-m\fR, \fB\-\-map\-syntax\fR <from:to>...
|
||||
.IP
|
||||
Map a file extension or file name to an existing syntax. For example, to highlight
|
||||
*.conf files with the INI syntax, use '\-m conf:ini'. To highlight files named
|
||||
\&'.myignore' with the Git Ignore syntax, use '\-m .myignore:gitignore'.
|
||||
.HP
|
||||
\fB\-\-theme\fR <theme>
|
||||
.IP
|
||||
Set the theme for syntax highlighting. Use '\-\-list\-themes' to see all available themes.
|
||||
To set a default theme, add the '\-\-theme="..."' option to the configuration file or
|
||||
export the BAT_THEME environment variable (e.g.: export BAT_THEME="...").
|
||||
.HP
|
||||
\fB\-\-list\-themes\fR
|
||||
.IP
|
||||
Display a list of supported themes for syntax highlighting.
|
||||
.HP
|
||||
\fB\-\-style\fR <style\-components>
|
||||
.IP
|
||||
Configure which elements (line numbers, file headers, grid borders, Git modifications,
|
||||
\&..) to display in addition to the file contents. The argument is a comma\-separated list
|
||||
of components to display (e.g. 'numbers,changes,grid') or a pre\-defined style ('full').
|
||||
To set a default style, add the '\-\-style=".."' option to the configuration file or
|
||||
export the BAT_STYLE environment variable (e.g.: export BAT_STYLE=".."). Possible
|
||||
values: *auto*, full, plain, changes, header, grid, numbers.
|
||||
.HP
|
||||
\fB\-p\fR, \fB\-\-plain\fR
|
||||
.IP
|
||||
Only show plain style, no decorations. This is an alias for '\-\-style=plain'
|
||||
.HP
|
||||
\fB\-n\fR, \fB\-\-number\fR
|
||||
.IP
|
||||
Only show line numbers, no other decorations. This is an alias for '\-\-style=numbers'
|
||||
.HP
|
||||
\fB\-A\fR, \fB\-\-show\-all\fR
|
||||
.IP
|
||||
Show non\-printable characters like space, tab or newline. Use '\-\-tabs' to control the
|
||||
width of the tab\-placeholders.
|
||||
.HP
|
||||
\fB\-\-line\-range\fR <N:M>...
|
||||
.IP
|
||||
Only print the specified range of lines for each file. For example:
|
||||
.RS
|
||||
.IP "\-\-line\-range 30:40"
|
||||
prints lines 30 to 40
|
||||
.IP "\-\-line\-range :40"
|
||||
prints lines 1 to 40
|
||||
.IP "\-\-line\-range 40:"
|
||||
prints lines 40 to the end of the file
|
||||
.RE
|
||||
.HP
|
||||
\fB\-\-color\fR <when>
|
||||
.IP
|
||||
Specify when to use colored output. The automatic mode only enables colors if an
|
||||
interactive terminal is detected. Possible values: *auto*, never, always.
|
||||
.HP
|
||||
\fB\-\-italic\-text\fR <when>
|
||||
.IP
|
||||
Specify when to use ANSI sequences for italic text in the output. Possible values:
|
||||
always, *never*.
|
||||
.HP
|
||||
\fB\-\-decorations\fR <when>
|
||||
.IP
|
||||
Specify when to use the decorations that have been specified via '\-\-style'. The
|
||||
automatic mode only enables decorations if an interactive terminal is detected. Possible
|
||||
values: *auto*, never, always.
|
||||
.HP
|
||||
\fB\-\-paging\fR <when>
|
||||
.IP
|
||||
Specify when to use the pager. To control which pager is used, set the PAGER or
|
||||
BAT_PAGER environment variables (the latter takes precedence) or use the '\-\-pager'
|
||||
option. To disable the pager permanently, set BAT_PAGER to an empty string or set
|
||||
\&'\-\-paging=never' in the configuration file. Possible values: *auto*, never, always.
|
||||
.HP
|
||||
\fB\-\-pager\fR <command>
|
||||
.IP
|
||||
Determine which pager is used. This option will overwrite the PAGER and BAT_PAGER
|
||||
environment variables. The default pager is 'less'. To disable the pager completely, use
|
||||
the '\-\-paging' option. Example: '\-\-pager "less \fB\-RF\fR"'.
|
||||
.HP
|
||||
\fB\-\-wrap\fR <mode>
|
||||
.IP
|
||||
Specify the text\-wrapping mode (*auto*, never, character).
|
||||
.HP
|
||||
\fB\-\-tabs\fR <T>
|
||||
.IP
|
||||
Set the tab width to T spaces. Use a width of 0 to pass tabs through directly
|
||||
.HP
|
||||
\fB\-u\fR, \fB\-\-unbuffered\fR
|
||||
.IP
|
||||
This option exists for POSIX\-compliance reasons ('u' is for 'unbuffered'). The output is
|
||||
always unbuffered \- this option is simply ignored.
|
||||
.HP
|
||||
\fB\-\-terminal\-width\fR <width>
|
||||
.IP
|
||||
Explicitly set the width of the terminal instead of determining it automatically. If
|
||||
prefixed with '+' or '\-', the value will be treated as an offset to the actual terminal
|
||||
width.
|
||||
.HP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
.IP
|
||||
Print this help message.
|
||||
.HP
|
||||
\fB\-V\fR, \fB\-\-version\fR
|
||||
.IP
|
||||
Show version information.
|
||||
.SH "ARGS"
|
||||
.IP
|
||||
<FILE>...
|
||||
.IP
|
||||
File(s) to print / concatenate. Use a dash ('\-') or no argument at all to read
|
||||
from standard input.
|
||||
.SH "SUBCOMMANDS"
|
||||
.IP
|
||||
cache
|
||||
Modify the syntax\-definition and theme cache
|
||||
+262
-340
@@ -1,247 +1,145 @@
|
||||
use atty::{self, Stream};
|
||||
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
|
||||
use console::Term;
|
||||
use errors::*;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use style::{OutputComponent, OutputComponents, OutputWrap};
|
||||
use std::str::FromStr;
|
||||
|
||||
use atty::{self, Stream};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use clap_app;
|
||||
use wild;
|
||||
|
||||
use console::Term;
|
||||
|
||||
#[cfg(windows)]
|
||||
use ansi_term;
|
||||
|
||||
use assets::BAT_THEME_DEFAULT;
|
||||
use config::{get_args_from_config_file, get_args_from_env_var};
|
||||
use errors::*;
|
||||
use inputfile::InputFile;
|
||||
use line_range::{LineRange, LineRanges};
|
||||
use style::{OutputComponent, OutputComponents, OutputWrap};
|
||||
use syntax_mapping::SyntaxMapping;
|
||||
use util::transpose;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PagingMode {
|
||||
Always,
|
||||
QuitIfOneScreen,
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
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>,
|
||||
|
||||
/// Whether or not to show/replace non-printable characters like space, tab and newline.
|
||||
pub show_nonprintable: bool,
|
||||
|
||||
/// The character width of the terminal
|
||||
pub term_width: usize,
|
||||
|
||||
/// The width of tab characters.
|
||||
/// Currently, a value of 0 will cause tabs to be passed through without expanding them.
|
||||
pub tab_width: usize,
|
||||
|
||||
/// Whether or not to simply loop through all input (`cat` mode)
|
||||
pub loop_through: bool,
|
||||
|
||||
/// Whether or not the output should be colorized
|
||||
pub colored_output: bool,
|
||||
|
||||
/// Whether or not the output terminal supports true color
|
||||
pub true_color: bool,
|
||||
|
||||
/// Style elements (grid, line numbers, ...)
|
||||
pub output_components: OutputComponents,
|
||||
|
||||
/// Text wrapping mode
|
||||
pub output_wrap: OutputWrap,
|
||||
|
||||
/// Pager or STDOUT
|
||||
pub paging_mode: PagingMode,
|
||||
|
||||
/// Specifies the lines that should be printed
|
||||
pub line_ranges: LineRanges,
|
||||
|
||||
/// The syntax highlighting theme
|
||||
pub theme: String,
|
||||
|
||||
/// File extension/name mappings
|
||||
pub syntax_mapping: SyntaxMapping,
|
||||
|
||||
/// Command to start the pager
|
||||
pub pager: Option<&'a str>,
|
||||
|
||||
pub use_italic_text: bool,
|
||||
}
|
||||
|
||||
fn is_truecolor_terminal() -> bool {
|
||||
env::var("COLORTERM")
|
||||
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub matches: ArgMatches<'static>,
|
||||
interactive_output: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new() -> Self {
|
||||
pub fn new() -> Result<Self> {
|
||||
#[cfg(windows)]
|
||||
let _ = ansi_term::enable_ansi_support();
|
||||
|
||||
let interactive_output = atty::is(Stream::Stdout);
|
||||
|
||||
#[cfg(windows)]
|
||||
let interactive_output = interactive_output && ansi_term::enable_ansi_support().is_ok();
|
||||
|
||||
App {
|
||||
matches: Self::matches(interactive_output),
|
||||
Ok(App {
|
||||
matches: Self::matches(interactive_output)?,
|
||||
interactive_output,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn matches(interactive_output: bool) -> ArgMatches<'static> {
|
||||
let clap_color_setting = if interactive_output {
|
||||
AppSettings::ColoredHelp
|
||||
fn matches(interactive_output: bool) -> Result<ArgMatches<'static>> {
|
||||
let args = if wild::args_os().nth(1) == Some("cache".into())
|
||||
|| wild::args_os().any(|arg| arg == "--no-config")
|
||||
{
|
||||
// Skip the arguments in bats config file
|
||||
|
||||
wild::args_os().collect::<Vec<_>>()
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
let mut cli_args = wild::args_os();
|
||||
|
||||
// Read arguments from bats config file
|
||||
let mut args = get_args_from_env_var()
|
||||
.unwrap_or_else(|| get_args_from_config_file())
|
||||
.chain_err(|| "Could not parse configuration file")?;
|
||||
|
||||
// Put the zero-th CLI argument (program name) first
|
||||
args.insert(0, cli_args.next().unwrap());
|
||||
|
||||
// .. and the rest at the end
|
||||
cli_args.for_each(|a| args.push(a));
|
||||
|
||||
args
|
||||
};
|
||||
|
||||
ClapApp::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.global_setting(clap_color_setting)
|
||||
.global_setting(AppSettings::DeriveDisplayOrder)
|
||||
.global_setting(AppSettings::UnifiedHelpMessage)
|
||||
.global_setting(AppSettings::NextLineHelp)
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.setting(AppSettings::DisableHelpSubcommand)
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.max_term_width(90)
|
||||
.about(
|
||||
"A cat(1) clone with wings.\n\n\
|
||||
Use '--help' instead of '-h' to see a more detailed version of the help text.",
|
||||
).long_about("A cat(1) clone with syntax highlighting and Git integration.")
|
||||
.arg(
|
||||
Arg::with_name("language")
|
||||
.short("l")
|
||||
.long("language")
|
||||
.overrides_with("language")
|
||||
.help("Set the language for highlighting")
|
||||
.long_help(
|
||||
"Set the language for syntax highlighting. The language can be \
|
||||
specified as a name (like 'C++' or 'LaTeX') or possible file \
|
||||
extension (like 'cpp', 'hpp' or 'md'). Use '--list-languages' \
|
||||
to show all supported language names and file extensions",
|
||||
).takes_value(true),
|
||||
).arg(
|
||||
Arg::with_name("FILE")
|
||||
.help("File(s) to print / concatenate. Use '-' for standard input.")
|
||||
.long_help(
|
||||
"File(s) to print. Use no argument or '-' to read from standard \
|
||||
input",
|
||||
).multiple(true)
|
||||
.empty_values(false),
|
||||
).arg(
|
||||
Arg::with_name("style")
|
||||
.long("style")
|
||||
.value_name("style-components")
|
||||
.use_delimiter(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"auto", "full", "plain", "changes", "header", "grid", "numbers",
|
||||
]).default_value("auto")
|
||||
.help("Comma-separated list of style elements to display")
|
||||
.long_help(
|
||||
"Configure which elements (line numbers, file headers, grid \
|
||||
borders, Git modifications, ..) to display in addition to the \
|
||||
file contents. The argument is a comma-separated list of \
|
||||
components to display (e.g. 'numbers,changes,grid') or a \
|
||||
pre-defined style ('full')",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("color")
|
||||
.long("color")
|
||||
.overrides_with("color")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use colors"),
|
||||
).arg(
|
||||
Arg::with_name("paging")
|
||||
.long("paging")
|
||||
.overrides_with("paging")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.help("When to use the pager")
|
||||
.long_help("Specify when to use the pager (less)"),
|
||||
).arg(
|
||||
Arg::with_name("wrap")
|
||||
.long("wrap")
|
||||
.overrides_with("wrap")
|
||||
.takes_value(true)
|
||||
.value_name("mode")
|
||||
.possible_values(&["character", "never"])
|
||||
.default_value("character")
|
||||
.help("When to wrap text"),
|
||||
).arg(
|
||||
Arg::with_name("list-languages")
|
||||
.long("list-languages")
|
||||
.help("Displays supported languages")
|
||||
.long_help("Display a list of supported languages"),
|
||||
).arg(
|
||||
Arg::with_name("theme")
|
||||
.long("theme")
|
||||
.overrides_with("theme")
|
||||
.takes_value(true)
|
||||
.help("Set the theme for highlighting")
|
||||
.long_help(
|
||||
"Set the theme for syntax highlighting. Use '--list-themes' to \
|
||||
see all available themes",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("line-range")
|
||||
.long("line-range")
|
||||
.overrides_with("line-range")
|
||||
.takes_value(true)
|
||||
.value_name("n:m")
|
||||
.help("Only print the lines from n to m")
|
||||
.long_help(
|
||||
"Print a specified range or ranges of lines from the files. \
|
||||
For example: '--line-range 30:40' will print lines 30 to 40 \n\
|
||||
'--line-range :40' will print lines 1 to 40 \n\
|
||||
'--line-range 40:' will print lines 40 to the end of the file",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("list-themes")
|
||||
.long("list-themes")
|
||||
.help("Displays supported themes")
|
||||
.help("Display a list of supported themes for syntax highlighting"),
|
||||
).arg(
|
||||
Arg::with_name("number")
|
||||
.long("number")
|
||||
.overrides_with("number")
|
||||
.short("n")
|
||||
.conflicts_with("style")
|
||||
.help("Show line numbers (alias for '--style=numbers')")
|
||||
.long_help(
|
||||
"Show line numbers (no other decorations). This is an alias for \
|
||||
'--style=numbers'",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("unbuffered")
|
||||
.short("u")
|
||||
.help("(ignored)")
|
||||
// TODO: use '.hidden_short_help(true)' when the next clap version is released
|
||||
.long_help(
|
||||
"This option exists for POSIX-compliance reasons ('u' is for \
|
||||
'unbuffered'). The output is always unbuffered - this option \
|
||||
is simply ignored.",
|
||||
),
|
||||
).subcommand(
|
||||
SubCommand::with_name("cache")
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
.arg(
|
||||
Arg::with_name("init")
|
||||
.long("init")
|
||||
.short("i")
|
||||
.help("Initialize the syntax/theme cache")
|
||||
.long_help(
|
||||
"Initialize the syntax/theme cache by loading from the \
|
||||
source directory (default: the configuration directory)",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("clear")
|
||||
.long("clear")
|
||||
.short("c")
|
||||
.help("Reset the cache"),
|
||||
).arg(
|
||||
Arg::with_name("config-dir")
|
||||
.long("config-dir")
|
||||
.short("d")
|
||||
.help("Show the configuration directory"),
|
||||
).group(
|
||||
ArgGroup::with_name("cache-actions")
|
||||
.args(&["init", "clear", "config-dir"])
|
||||
.required(true),
|
||||
).arg(
|
||||
Arg::with_name("source")
|
||||
.long("source")
|
||||
.requires("init")
|
||||
.takes_value(true)
|
||||
.value_name("dir")
|
||||
.help("Use a different source for loading syntaxes and themes from"),
|
||||
).arg(
|
||||
Arg::with_name("target")
|
||||
.long("target")
|
||||
.requires("init")
|
||||
.takes_value(true)
|
||||
.value_name("dir")
|
||||
.help(
|
||||
"Use a different source to store the cached syntax and theme set",
|
||||
),
|
||||
),
|
||||
).help_message("Print this help message.")
|
||||
.version_message("Show version information.")
|
||||
.get_matches()
|
||||
Ok(clap_app::build_app(interactive_output).get_matches_from(args))
|
||||
}
|
||||
|
||||
pub fn config(&self) -> Result<Config> {
|
||||
let files = self.files();
|
||||
let output_components = self.output_components()?;
|
||||
|
||||
Ok(Config {
|
||||
true_color: is_truecolor_terminal(),
|
||||
output_components: self.output_components()?,
|
||||
language: self.matches.value_of("language"),
|
||||
output_wrap: if !self.interactive_output {
|
||||
// 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
|
||||
} else {
|
||||
match self.matches.value_of("wrap") {
|
||||
Some("character") => OutputWrap::Character,
|
||||
Some("never") | _ => OutputWrap::None,
|
||||
}
|
||||
},
|
||||
colored_output: match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
},
|
||||
paging_mode: match self.matches.value_of("paging") {
|
||||
Some("always") => PagingMode::Always,
|
||||
Some("never") => PagingMode::Never,
|
||||
Some("auto") | _ => if files.contains(&None) {
|
||||
let paging_mode = match self.matches.value_of("paging") {
|
||||
Some("always") => PagingMode::Always,
|
||||
Some("never") => PagingMode::Never,
|
||||
Some("auto") | _ => {
|
||||
if files.contains(&InputFile::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.
|
||||
@@ -256,148 +154,172 @@ impl App {
|
||||
} else {
|
||||
PagingMode::Never
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut syntax_mapping = SyntaxMapping::new();
|
||||
|
||||
if let Some(values) = self.matches.values_of("map-syntax") {
|
||||
for from_to in values {
|
||||
let parts: Vec<_> = from_to.split(":").collect();
|
||||
|
||||
if parts.len() != 2 {
|
||||
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is 'from:to'.".into());
|
||||
}
|
||||
|
||||
syntax_mapping.insert(parts[0].into(), parts[1].into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Config {
|
||||
true_color: is_truecolor_terminal(),
|
||||
language: self.matches.value_of("language").or_else(|| {
|
||||
if self.matches.is_present("show-all") {
|
||||
Some("show-nonprintable")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
show_nonprintable: self.matches.is_present("show-all"),
|
||||
output_wrap: if !self.interactive_output {
|
||||
// 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
|
||||
} else {
|
||||
match self.matches.value_of("wrap") {
|
||||
Some("character") => OutputWrap::Character,
|
||||
Some("never") => OutputWrap::None,
|
||||
Some("auto") | _ => {
|
||||
if output_components.plain() {
|
||||
OutputWrap::None
|
||||
} else {
|
||||
OutputWrap::Character
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
term_width: Term::stdout().size().1 as usize,
|
||||
colored_output: match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
},
|
||||
paging_mode,
|
||||
term_width: self
|
||||
.matches
|
||||
.value_of("terminal-width")
|
||||
.and_then(|w| {
|
||||
if w.starts_with("+") || w.starts_with("-") {
|
||||
// Treat argument as a delta to the current terminal width
|
||||
w.parse().ok().map(|delta: i16| {
|
||||
let old_width: u16 = Term::stdout().size().1;
|
||||
let new_width: i32 = old_width as i32 + delta as i32;
|
||||
|
||||
if new_width <= 0 {
|
||||
old_width as usize
|
||||
} else {
|
||||
new_width as usize
|
||||
}
|
||||
})
|
||||
} else {
|
||||
w.parse().ok()
|
||||
}
|
||||
})
|
||||
.unwrap_or(Term::stdout().size().1 as usize),
|
||||
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")
|
||||
.map(String::from)
|
||||
.or_else(|| env::var("BAT_TABS").ok())
|
||||
.and_then(|t| t.parse().ok())
|
||||
.unwrap_or(
|
||||
if output_components.plain() && paging_mode == PagingMode::Never {
|
||||
0
|
||||
} else {
|
||||
4
|
||||
},
|
||||
),
|
||||
theme: self
|
||||
.matches
|
||||
.value_of("theme")
|
||||
.map(String::from)
|
||||
.or_else(|| env::var("BAT_THEME").ok())
|
||||
.unwrap_or(String::from("Default")),
|
||||
line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
|
||||
.unwrap_or(String::from(BAT_THEME_DEFAULT)),
|
||||
line_ranges: LineRanges::from(
|
||||
transpose(
|
||||
self.matches
|
||||
.values_of("line-range")
|
||||
.map(|vs| vs.map(LineRange::from).collect()),
|
||||
)?
|
||||
.unwrap_or(vec![]),
|
||||
),
|
||||
output_components,
|
||||
syntax_mapping,
|
||||
pager: self.matches.value_of("pager"),
|
||||
use_italic_text: match self.matches.value_of("italic-text") {
|
||||
Some("always") => true,
|
||||
_ => false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn files(&self) -> Vec<Option<&str>> {
|
||||
fn files(&self) -> Vec<InputFile> {
|
||||
self.matches
|
||||
.values_of("FILE")
|
||||
.map(|values| {
|
||||
values
|
||||
.map(|filename| {
|
||||
if filename == "-" {
|
||||
None
|
||||
InputFile::StdIn
|
||||
} else {
|
||||
Some(filename)
|
||||
InputFile::Ordinary(filename)
|
||||
}
|
||||
}).collect()
|
||||
}).unwrap_or_else(|| vec![None]) // read from stdin (None) if no args are given
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| vec![InputFile::StdIn])
|
||||
}
|
||||
|
||||
fn output_components(&self) -> Result<OutputComponents> {
|
||||
let matches = &self.matches;
|
||||
Ok(OutputComponents(if matches.is_present("number") {
|
||||
[OutputComponent::Numbers].iter().cloned().collect()
|
||||
} else {
|
||||
values_t!(matches.values_of("style"), OutputComponent)?
|
||||
.into_iter()
|
||||
.map(|style| style.components(self.interactive_output))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
})
|
||||
}))
|
||||
Ok(OutputComponents(
|
||||
if matches.value_of("decorations") == Some("never") {
|
||||
HashSet::new()
|
||||
} else if matches.is_present("number") {
|
||||
[OutputComponent::Numbers].iter().cloned().collect()
|
||||
} else if matches.is_present("plain") {
|
||||
[OutputComponent::Plain].iter().cloned().collect()
|
||||
} else {
|
||||
let env_style_components: Option<Vec<OutputComponent>> =
|
||||
transpose(env::var("BAT_STYLE").ok().map(|style_str| {
|
||||
style_str
|
||||
.split(",")
|
||||
.map(|x| OutputComponent::from_str(&x))
|
||||
.collect::<Result<Vec<OutputComponent>>>()
|
||||
}))?;
|
||||
|
||||
matches
|
||||
.value_of("style")
|
||||
.map(|styles| {
|
||||
styles
|
||||
.split(",")
|
||||
.map(|style| style.parse::<OutputComponent>())
|
||||
.filter_map(|style| style.ok())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.or(env_style_components)
|
||||
.unwrap_or(vec![OutputComponent::Full])
|
||||
.into_iter()
|
||||
.map(|style| style.components(self.interactive_output))
|
||||
.fold(HashSet::new(), |mut acc, components| {
|
||||
acc.extend(components.iter().cloned());
|
||||
acc
|
||||
})
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PagingMode {
|
||||
Always,
|
||||
QuitIfOneScreen,
|
||||
Never,
|
||||
}
|
||||
|
||||
pub struct Config<'a> {
|
||||
pub true_color: bool,
|
||||
pub output_wrap: OutputWrap,
|
||||
pub output_components: OutputComponents,
|
||||
pub language: Option<&'a str>,
|
||||
pub colored_output: bool,
|
||||
pub paging_mode: PagingMode,
|
||||
pub term_width: usize,
|
||||
pub files: Vec<Option<&'a str>>,
|
||||
pub theme: String,
|
||||
pub line_range: Option<LineRange>,
|
||||
}
|
||||
|
||||
fn is_truecolor_terminal() -> bool {
|
||||
env::var("COLORTERM")
|
||||
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub struct LineRange {
|
||||
pub lower: usize,
|
||||
pub upper: usize,
|
||||
}
|
||||
|
||||
impl LineRange {
|
||||
pub fn from(range_raw: &str) -> Result<LineRange> {
|
||||
LineRange::parse_range(range_raw)
|
||||
}
|
||||
|
||||
pub fn new() -> LineRange {
|
||||
LineRange {
|
||||
lower: usize::min_value(),
|
||||
upper: usize::max_value(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_range(range_raw: &str) -> Result<LineRange> {
|
||||
let mut new_range = LineRange::new();
|
||||
|
||||
if range_raw.bytes().nth(0).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':' {
|
||||
new_range.lower = range_raw[..range_raw.len() - 1].parse()?;
|
||||
return Ok(new_range);
|
||||
}
|
||||
|
||||
let line_numbers: Vec<&str> = range_raw.split(':').collect();
|
||||
if line_numbers.len() == 2 {
|
||||
new_range.lower = line_numbers[0].parse()?;
|
||||
new_range.upper = line_numbers[1].parse()?;
|
||||
return Ok(new_range);
|
||||
}
|
||||
Err("expected single ':' character".into())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_line_range_full() {
|
||||
let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
|
||||
assert_eq!(40, range.lower);
|
||||
assert_eq!(50, range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_line_range_partial_min() {
|
||||
let range = LineRange::from(":50").expect("Shouldn't fail on test!");
|
||||
assert_eq!(usize::min_value(), range.lower);
|
||||
assert_eq!(50, range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_line_range_partial_max() {
|
||||
let range = LineRange::from("40:").expect("Shouldn't fail on test!");
|
||||
assert_eq!(40, range.lower);
|
||||
assert_eq!(usize::max_value(), range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_line_range_fail() {
|
||||
let range = LineRange::from("40:50:80");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from("40::80");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from(":40:");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from("40");
|
||||
assert!(range.is_err());
|
||||
}
|
||||
|
||||
fn transpose<T>(opt: Option<Result<T>>) -> Result<Option<T>> {
|
||||
opt.map_or(Ok(None), |res| res.map(Some))
|
||||
}
|
||||
+77
-49
@@ -1,19 +1,20 @@
|
||||
use directories::ProjectDirs;
|
||||
use errors::*;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io::BufReader;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use syntect::dumps::{dump_to_file, from_binary, from_reader};
|
||||
use syntect::highlighting::{Theme, ThemeSet};
|
||||
use syntect::parsing::{SyntaxDefinition, SyntaxSet};
|
||||
use syntect::parsing::{SyntaxReference, SyntaxSet, SyntaxSetBuilder};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use dirs::PROJECT_DIRS;
|
||||
|
||||
lazy_static! {
|
||||
static ref PROJECT_DIRS: ProjectDirs =
|
||||
ProjectDirs::from("", "", crate_name!()).expect("Could not get home directory");
|
||||
}
|
||||
use errors::*;
|
||||
use inputfile::{InputFile, InputFileReader};
|
||||
use syntax_mapping::SyntaxMapping;
|
||||
|
||||
pub const BAT_THEME_DEFAULT: &str = "Monokai Extended";
|
||||
|
||||
pub struct HighlightingAssets {
|
||||
pub syntax_set: SyntaxSet,
|
||||
@@ -25,27 +26,38 @@ impl HighlightingAssets {
|
||||
Self::from_cache().unwrap_or_else(|_| Self::from_binary())
|
||||
}
|
||||
|
||||
pub fn from_files(dir: Option<&Path>) -> Result<Self> {
|
||||
pub fn from_files(dir: Option<&Path>, start_empty: bool) -> Result<Self> {
|
||||
let source_dir = dir.unwrap_or_else(|| PROJECT_DIRS.config_dir());
|
||||
|
||||
let mut assets = Self::from_binary_unlinked();
|
||||
let mut theme_set = if start_empty {
|
||||
ThemeSet {
|
||||
themes: BTreeMap::new(),
|
||||
}
|
||||
} else {
|
||||
Self::get_integrated_themeset()
|
||||
};
|
||||
|
||||
let theme_dir = source_dir.join("themes");
|
||||
|
||||
if let Ok(theme_set) = ThemeSet::load_from_folder(&theme_dir) {
|
||||
// TODO: If syntect would support this, it would be great to
|
||||
// load the new themes in addition to the ones in the binary.
|
||||
assets.theme_set = theme_set;
|
||||
} else {
|
||||
let res = theme_set.add_from_folder(&theme_dir);
|
||||
if !res.is_ok() {
|
||||
println!(
|
||||
"No themes were found in '{}', using the default set",
|
||||
theme_dir.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
let mut syntax_set_builder = if start_empty {
|
||||
let mut builder = SyntaxSetBuilder::new();
|
||||
builder.add_plain_text_syntax();
|
||||
builder
|
||||
} else {
|
||||
Self::get_integrated_syntaxset().into_builder()
|
||||
};
|
||||
|
||||
let syntax_dir = source_dir.join("syntaxes");
|
||||
if syntax_dir.exists() {
|
||||
assets.syntax_set.load_syntaxes(syntax_dir, true)?;
|
||||
syntax_set_builder.add_from_folder(syntax_dir, true)?;
|
||||
} else {
|
||||
println!(
|
||||
"No syntaxes were found in '{}', using the default set.",
|
||||
@@ -53,7 +65,10 @@ impl HighlightingAssets {
|
||||
);
|
||||
}
|
||||
|
||||
Ok(assets)
|
||||
Ok(HighlightingAssets {
|
||||
syntax_set: syntax_set_builder.build(),
|
||||
theme_set,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_cache() -> Result<Self> {
|
||||
@@ -64,9 +79,8 @@ impl HighlightingAssets {
|
||||
syntax_set_path().to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
let mut syntax_set: SyntaxSet =
|
||||
from_reader(syntax_set_file).chain_err(|| "Could not parse cached syntax set")?;
|
||||
syntax_set.link_syntaxes();
|
||||
let syntax_set: SyntaxSet = from_reader(BufReader::new(syntax_set_file))
|
||||
.chain_err(|| "Could not parse cached syntax set")?;
|
||||
|
||||
let theme_set_file = File::open(&theme_set_path).chain_err(|| {
|
||||
format!(
|
||||
@@ -74,8 +88,8 @@ impl HighlightingAssets {
|
||||
theme_set_path.to_string_lossy()
|
||||
)
|
||||
})?;
|
||||
let theme_set: ThemeSet =
|
||||
from_reader(theme_set_file).chain_err(|| "Could not parse cached theme set")?;
|
||||
let theme_set: ThemeSet = from_reader(BufReader::new(theme_set_file))
|
||||
.chain_err(|| "Could not parse cached theme set")?;
|
||||
|
||||
Ok(HighlightingAssets {
|
||||
syntax_set,
|
||||
@@ -83,9 +97,17 @@ impl HighlightingAssets {
|
||||
})
|
||||
}
|
||||
|
||||
fn from_binary_unlinked() -> Self {
|
||||
let syntax_set: SyntaxSet = from_binary(include_bytes!("../assets/syntaxes.bin"));
|
||||
let theme_set: ThemeSet = from_binary(include_bytes!("../assets/themes.bin"));
|
||||
fn get_integrated_syntaxset() -> SyntaxSet {
|
||||
from_binary(include_bytes!("../assets/syntaxes.bin"))
|
||||
}
|
||||
|
||||
fn get_integrated_themeset() -> ThemeSet {
|
||||
from_binary(include_bytes!("../assets/themes.bin"))
|
||||
}
|
||||
|
||||
fn from_binary() -> Self {
|
||||
let syntax_set = Self::get_integrated_syntaxset();
|
||||
let theme_set = Self::get_integrated_themeset();
|
||||
|
||||
HighlightingAssets {
|
||||
syntax_set,
|
||||
@@ -93,12 +115,6 @@ impl HighlightingAssets {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_binary() -> Self {
|
||||
let mut assets = Self::from_binary_unlinked();
|
||||
assets.syntax_set.link_syntaxes();
|
||||
assets
|
||||
}
|
||||
|
||||
pub fn save(&self, dir: Option<&Path>) -> Result<()> {
|
||||
let target_dir = dir.unwrap_or_else(|| PROJECT_DIRS.cache_dir());
|
||||
let _ = fs::create_dir(target_dir);
|
||||
@@ -142,34 +158,46 @@ impl HighlightingAssets {
|
||||
Yellow.paint("[bat warning]"),
|
||||
theme
|
||||
);
|
||||
&self.theme_set.themes["Default"]
|
||||
&self.theme_set.themes[BAT_THEME_DEFAULT]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_syntax(&self, language: Option<&str>, filename: Option<&str>) -> &SyntaxDefinition {
|
||||
pub fn get_syntax(
|
||||
&self,
|
||||
language: Option<&str>,
|
||||
filename: InputFile,
|
||||
reader: &mut InputFileReader,
|
||||
mapping: &SyntaxMapping,
|
||||
) -> &SyntaxReference {
|
||||
let syntax = match (language, filename) {
|
||||
(Some(language), _) => self.syntax_set.find_syntax_by_token(language),
|
||||
(None, Some(filename)) => {
|
||||
#[cfg(not(unix))]
|
||||
let may_read_from_file = true;
|
||||
(None, InputFile::Ordinary(filename)) => {
|
||||
let path = Path::new(filename);
|
||||
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("");
|
||||
|
||||
// Do not peek at the file (to determine the syntax) if it is a FIFO because they can
|
||||
// only be read once.
|
||||
#[cfg(unix)]
|
||||
let may_read_from_file = !fs::metadata(filename)
|
||||
.map(|m| m.file_type().is_fifo())
|
||||
.unwrap_or(false);
|
||||
let file_name = mapping.replace(file_name);
|
||||
let extension = mapping.replace(extension);
|
||||
|
||||
if may_read_from_file {
|
||||
self.syntax_set
|
||||
.find_syntax_for_file(filename)
|
||||
.unwrap_or(None)
|
||||
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 = if ext_syntax.is_none() {
|
||||
String::from_utf8(reader.first_line.clone())
|
||||
.ok()
|
||||
.and_then(|l| self.syntax_set.find_syntax_by_first_line(&l))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let syntax = ext_syntax.or(line_syntax);
|
||||
syntax
|
||||
}
|
||||
(None, None) => None,
|
||||
(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())
|
||||
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, SubCommand};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
|
||||
let clap_color_setting = if interactive_output {
|
||||
AppSettings::ColoredHelp
|
||||
} else {
|
||||
AppSettings::ColorNever
|
||||
};
|
||||
|
||||
// Check if the current directory contains a file name cache, if it does
|
||||
// do not make the arguements for subcommand 'cache' required.
|
||||
let arg_group_required = !Path::new("cache").exists();
|
||||
|
||||
ClapApp::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.global_setting(clap_color_setting)
|
||||
.global_setting(AppSettings::DeriveDisplayOrder)
|
||||
.global_setting(AppSettings::UnifiedHelpMessage)
|
||||
.global_setting(AppSettings::HidePossibleValuesInHelp)
|
||||
.setting(AppSettings::InferSubcommands)
|
||||
.setting(AppSettings::ArgsNegateSubcommands)
|
||||
.setting(AppSettings::DisableHelpSubcommand)
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.max_term_width(100)
|
||||
.about(
|
||||
"A cat(1) clone with wings.\n\n\
|
||||
Use '--help' instead of '-h' to see a more detailed version of the help text.",
|
||||
)
|
||||
.long_about("A cat(1) clone with syntax highlighting and Git integration.")
|
||||
.arg(
|
||||
Arg::with_name("FILE")
|
||||
.help("File(s) to print / concatenate. Use '-' for standard input.")
|
||||
.long_help(
|
||||
"File(s) to print / concatenate. Use a dash ('-') or no argument at all \
|
||||
to read from standard input.",
|
||||
)
|
||||
.multiple(true)
|
||||
.empty_values(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("language")
|
||||
.short("l")
|
||||
.long("language")
|
||||
.overrides_with("language")
|
||||
.help("Set the language for syntax highlighting.")
|
||||
.long_help(
|
||||
"Explicitly set the language for syntax highlighting. The language can be \
|
||||
specified as a name (like 'C++' or 'LaTeX') or possible file extension \
|
||||
(like 'cpp', 'hpp' or 'md'). Use '--list-languages' to show all supported \
|
||||
language names and file extensions.",
|
||||
)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-languages")
|
||||
.long("list-languages")
|
||||
.conflicts_with("list-themes")
|
||||
.help("Display all supported languages.")
|
||||
.long_help("Display a list of supported languages for syntax highlighting."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("map-syntax")
|
||||
.short("m")
|
||||
.long("map-syntax")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.value_name("from:to")
|
||||
.help("Map a file extension or name to an existing syntax.")
|
||||
.long_help(
|
||||
"Map a file extension or file name to an existing syntax. For example, \
|
||||
to highlight *.conf files with the INI syntax, use '-m conf:ini'. \
|
||||
To highlight files named '.myignore' with the Git Ignore syntax, use \
|
||||
'-m .myignore:gitignore'.",
|
||||
)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("theme")
|
||||
.long("theme")
|
||||
.overrides_with("theme")
|
||||
.takes_value(true)
|
||||
.help("Set the color theme for syntax highlighting.")
|
||||
.long_help(
|
||||
"Set the theme for syntax highlighting. Use '--list-themes' to \
|
||||
see all available themes. To set a default theme, add the \
|
||||
'--theme=\"...\"' option to the configuration file or export the \
|
||||
BAT_THEME environment variable (e.g.: export \
|
||||
BAT_THEME=\"...\").",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("list-themes")
|
||||
.long("list-themes")
|
||||
.help("Display all supported highlighting themes.")
|
||||
.long_help("Display a list of supported themes for syntax highlighting."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("style")
|
||||
.long("style")
|
||||
.value_name("style-components")
|
||||
// Need to turn this off for overrides_with to work as we want. See the bottom most
|
||||
// example at https://docs.rs/clap/2.32.0/clap/struct.Arg.html#method.overrides_with
|
||||
.use_delimiter(false)
|
||||
.takes_value(true)
|
||||
.overrides_with("style")
|
||||
.overrides_with("plain")
|
||||
.overrides_with("number")
|
||||
// Cannot use clap's built in validation because we have to turn off clap's delimiters
|
||||
.validator(|val| {
|
||||
let mut invalid_vals = val.split(",").filter(|style| {
|
||||
!&[
|
||||
"auto", "full", "plain", "changes", "header", "grid", "numbers",
|
||||
]
|
||||
.contains(style)
|
||||
});
|
||||
|
||||
if let Some(invalid) = invalid_vals.next() {
|
||||
Err(format!("Unknown style, '{}'", invalid))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.help(
|
||||
"Comma-separated list of style elements to display \
|
||||
(*auto*, full, plain, changes, header, grid, numbers).",
|
||||
)
|
||||
.long_help(
|
||||
"Configure which elements (line numbers, file headers, grid \
|
||||
borders, Git modifications, ..) to display in addition to the \
|
||||
file contents. The argument is a comma-separated list of \
|
||||
components to display (e.g. 'numbers,changes,grid') or a \
|
||||
pre-defined style ('full'). To set a default style, add the \
|
||||
'--style=\"..\"' option to the configuration file or export the \
|
||||
BAT_STYLE environment variable (e.g.: export BAT_STYLE=\"..\"). \
|
||||
Possible values: *auto*, full, plain, changes, header, grid, numbers.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("plain")
|
||||
.overrides_with("plain")
|
||||
.overrides_with("number")
|
||||
.short("p")
|
||||
.long("plain")
|
||||
.help("Show plain style (alias for '--style=plain').")
|
||||
.long_help(
|
||||
"Only show plain style, no decorations. This is an alias for \
|
||||
'--style=plain'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("number")
|
||||
.long("number")
|
||||
.overrides_with("number")
|
||||
.short("n")
|
||||
.help("Show line numbers (alias for '--style=numbers').")
|
||||
.long_help(
|
||||
"Only show line numbers, no other decorations. This is an alias for \
|
||||
'--style=numbers'",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("show-all")
|
||||
.long("show-all")
|
||||
.alias("show-nonprintable")
|
||||
.short("A")
|
||||
.conflicts_with("language")
|
||||
.help("Show non-printable characters (space, tab, newline, ..).")
|
||||
.long_help(
|
||||
"Show non-printable characters like space, tab or newline. \
|
||||
Use '--tabs' to control the width of the tab-placeholders.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("line-range")
|
||||
.long("line-range")
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.number_of_values(1)
|
||||
.value_name("N:M")
|
||||
.help("Only print the lines from N to M.")
|
||||
.long_help(
|
||||
"Only print the specified range of lines for each file. \
|
||||
For example:\n \
|
||||
'--line-range 30:40' prints lines 30 to 40\n \
|
||||
'--line-range :40' prints lines 1 to 40\n \
|
||||
'--line-range 40:' prints lines 40 to the end of the file",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("color")
|
||||
.long("color")
|
||||
.overrides_with("color")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.hide_default_value(true)
|
||||
.default_value("auto")
|
||||
.help("When to use colors (*auto*, never, always).")
|
||||
.long_help(
|
||||
"Specify when to use colored output. The automatic mode \
|
||||
only enables colors if an interactive terminal is detected. \
|
||||
Possible values: *auto*, never, always.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("italic-text")
|
||||
.long("italic-text")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["always", "never"])
|
||||
.default_value("never")
|
||||
.hide_default_value(true)
|
||||
.help("Use italics in output (always, *never*)")
|
||||
.long_help("Specify when to use ANSI sequences for italic text in the output. Possible values: always, *never*."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("decorations")
|
||||
.long("decorations")
|
||||
.overrides_with("decorations")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.hide_default_value(true)
|
||||
.help("When to show the decorations (*auto*, never, always).")
|
||||
.long_help(
|
||||
"Specify when to use the decorations that have been specified \
|
||||
via '--style'. The automatic mode only enables decorations if \
|
||||
an interactive terminal is detected. Possible values: *auto*, never, always.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("paging")
|
||||
.long("paging")
|
||||
.overrides_with("paging")
|
||||
.takes_value(true)
|
||||
.value_name("when")
|
||||
.possible_values(&["auto", "never", "always"])
|
||||
.default_value("auto")
|
||||
.hide_default_value(true)
|
||||
.help("Specify when to use the pager (*auto*, never, always).")
|
||||
.long_help(
|
||||
"Specify when to use the pager. To control which pager \
|
||||
is used, set the PAGER or BAT_PAGER environment \
|
||||
variables (the latter takes precedence) or use the '--pager' option. \
|
||||
To disable the pager permanently, set BAT_PAGER to an empty string \
|
||||
or set '--paging=never' in the configuration file. \
|
||||
Possible values: *auto*, never, always.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("pager")
|
||||
.long("pager")
|
||||
.overrides_with("pager")
|
||||
.takes_value(true)
|
||||
.value_name("command")
|
||||
.hidden_short_help(true)
|
||||
.help("Determine which pager to use.")
|
||||
.long_help(
|
||||
"Determine which pager is used. This option will overwrite \
|
||||
the PAGER and BAT_PAGER environment variables. The default \
|
||||
pager is 'less'. To disable the pager completely, use the \
|
||||
'--paging' option. \
|
||||
Example: '--pager \"less -RF\"'.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("wrap")
|
||||
.long("wrap")
|
||||
.overrides_with("wrap")
|
||||
.takes_value(true)
|
||||
.value_name("mode")
|
||||
.possible_values(&["auto", "never", "character"])
|
||||
.default_value("auto")
|
||||
.hide_default_value(true)
|
||||
.help("Specify the text-wrapping mode (*auto*, never, character).")
|
||||
.long_help("Specify the text-wrapping mode (*auto*, never, character)."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("tabs")
|
||||
.long("tabs")
|
||||
.overrides_with("tabs")
|
||||
.takes_value(true)
|
||||
.value_name("T")
|
||||
.validator(
|
||||
|t| {
|
||||
t.parse::<u32>()
|
||||
.map_err(|_t| "must be a number")
|
||||
.map(|_t| ()) // Convert to Result<(), &str>
|
||||
.map_err(|e| e.to_string())
|
||||
}, // Convert to Result<(), String>
|
||||
)
|
||||
.help("Set the tab width to T spaces.")
|
||||
.long_help(
|
||||
"Set the tab width to T spaces. Use a width of 0 to pass tabs through \
|
||||
directly",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("unbuffered")
|
||||
.short("u")
|
||||
.long("unbuffered")
|
||||
.hidden_short_help(true)
|
||||
.long_help(
|
||||
"This option exists for POSIX-compliance reasons ('u' is for \
|
||||
'unbuffered'). The output is always unbuffered - this option \
|
||||
is simply ignored.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("terminal-width")
|
||||
.long("terminal-width")
|
||||
.takes_value(true)
|
||||
.value_name("width")
|
||||
.hidden_short_help(true)
|
||||
.help(
|
||||
"Explicitly set the width of the terminal instead of determining it \
|
||||
automatically. If prefixed with '+' or '-', the value will be treated \
|
||||
as an offset to the actual terminal width.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no-config")
|
||||
.long("no-config")
|
||||
.hidden(true)
|
||||
.help("Do not use the configuration file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config-file")
|
||||
.long("config-file")
|
||||
.conflicts_with("list-languages")
|
||||
.conflicts_with("list-themes")
|
||||
.hidden(true)
|
||||
.help("Show path to the configuration file."),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("cache")
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
.arg(
|
||||
Arg::with_name("init")
|
||||
.long("init")
|
||||
.short("i")
|
||||
.help("Initialize the syntax/theme cache.")
|
||||
.long_help(
|
||||
"Initialize the syntax/theme cache by loading from the \
|
||||
source directory (default: the configuration directory).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("clear")
|
||||
.long("clear")
|
||||
.short("c")
|
||||
.help("Remove the cached syntax definitions and themes."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("config-dir")
|
||||
.long("config-dir")
|
||||
.short("d")
|
||||
.help("Show bat's configuration directory."),
|
||||
)
|
||||
.group(
|
||||
ArgGroup::with_name("cache-actions")
|
||||
.args(&["init", "clear", "config-dir"])
|
||||
.required(arg_group_required),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("source")
|
||||
.long("source")
|
||||
.requires("init")
|
||||
.takes_value(true)
|
||||
.value_name("dir")
|
||||
.help("Use a different directory to load syntaxes and themes from."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("target")
|
||||
.long("target")
|
||||
.requires("init")
|
||||
.takes_value(true)
|
||||
.value_name("dir")
|
||||
.help(
|
||||
"Use a different directory to store the cached syntax and theme set.",
|
||||
),
|
||||
)
|
||||
.arg(Arg::with_name("blank").long("blank").requires("init").help(
|
||||
"Create completely new syntax and theme sets \
|
||||
(instead of appending to the default sets).",
|
||||
)),
|
||||
)
|
||||
.help_message("Print this help message.")
|
||||
.version_message("Show version information.")
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use shell_words;
|
||||
|
||||
use dirs::PROJECT_DIRS;
|
||||
use util::transpose;
|
||||
|
||||
pub fn config_file() -> PathBuf {
|
||||
env::var("BAT_CONFIG_PATH")
|
||||
.ok()
|
||||
.map(PathBuf::from)
|
||||
.filter(|config_path| config_path.is_file())
|
||||
.unwrap_or(PROJECT_DIRS.config_dir().join("config"))
|
||||
}
|
||||
|
||||
pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseError> {
|
||||
Ok(transpose(
|
||||
fs::read_to_string(config_file())
|
||||
.ok()
|
||||
.map(|content| get_args_from_str(&content)),
|
||||
)?
|
||||
.unwrap_or(vec![]))
|
||||
}
|
||||
|
||||
pub fn get_args_from_env_var() -> Option<Result<Vec<OsString>, shell_words::ParseError>> {
|
||||
env::var("BAT_OPTS").ok().map(|s| get_args_from_str(&s))
|
||||
}
|
||||
|
||||
fn get_args_from_str<'a>(content: &'a str) -> Result<Vec<OsString>, shell_words::ParseError> {
|
||||
let args_per_line = content
|
||||
.split('\n')
|
||||
.map(|line| line.trim())
|
||||
.filter(|line| !line.is_empty())
|
||||
.filter(|line| !line.starts_with("#"))
|
||||
.map(|line| shell_words::split(line))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
Ok(args_per_line
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|line| line.into())
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let args = get_args_from_str("").unwrap();
|
||||
assert!(args.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single() {
|
||||
assert_eq!(vec!["--plain"], get_args_from_str("--plain").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple() {
|
||||
assert_eq!(
|
||||
vec!["--plain", "--language=cpp"],
|
||||
get_args_from_str("--plain --language=cpp").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotes() {
|
||||
assert_eq!(
|
||||
vec!["--theme", "Sublime Snazzy"],
|
||||
get_args_from_str("--theme \"Sublime Snazzy\"").unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line() {
|
||||
let config = "
|
||||
-p
|
||||
--style numbers,changes
|
||||
|
||||
--color=always
|
||||
";
|
||||
assert_eq!(
|
||||
vec!["-p", "--style", "numbers,changes", "--color=always"],
|
||||
get_args_from_str(config).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comments() {
|
||||
let config = "
|
||||
# plain style
|
||||
-p
|
||||
|
||||
# show line numbers and Git modifications
|
||||
--style numbers,changes
|
||||
|
||||
# Always show ANSI colors
|
||||
--color=always
|
||||
";
|
||||
assert_eq!(
|
||||
vec!["-p", "--style", "numbers,changes", "--color=always"],
|
||||
get_args_from_str(config).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use app::Config;
|
||||
use assets::HighlightingAssets;
|
||||
use errors::*;
|
||||
use inputfile::{InputFile, InputFileReader};
|
||||
use line_range::{LineRanges, RangeCheckResult};
|
||||
use output::OutputType;
|
||||
use printer::{InteractivePrinter, Printer, SimplePrinter};
|
||||
|
||||
pub struct Controller<'a> {
|
||||
config: &'a Config<'a>,
|
||||
assets: &'a HighlightingAssets,
|
||||
}
|
||||
|
||||
impl<'b> Controller<'b> {
|
||||
pub fn new<'a>(config: &'a Config, assets: &'a HighlightingAssets) -> Controller<'a> {
|
||||
Controller { config, assets }
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Result<bool> {
|
||||
let mut output_type = OutputType::from_mode(self.config.paging_mode, self.config.pager)?;
|
||||
let writer = output_type.handle()?;
|
||||
let mut no_errors: bool = true;
|
||||
|
||||
let stdin = io::stdin();
|
||||
|
||||
for input_file in &self.config.files {
|
||||
match input_file.get_reader(&stdin) {
|
||||
Err(error) => {
|
||||
handle_error(&error);
|
||||
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)
|
||||
} else {
|
||||
let mut printer = InteractivePrinter::new(
|
||||
&self.config,
|
||||
&self.assets,
|
||||
*input_file,
|
||||
&mut reader,
|
||||
);
|
||||
self.print_file(reader, &mut printer, writer, *input_file)
|
||||
};
|
||||
|
||||
if let Err(error) = result {
|
||||
handle_error(&error);
|
||||
no_errors = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(no_errors)
|
||||
}
|
||||
|
||||
fn print_file<'a, P: Printer>(
|
||||
&self,
|
||||
reader: InputFileReader,
|
||||
printer: &mut P,
|
||||
writer: &mut Write,
|
||||
input_file: InputFile<'a>,
|
||||
) -> Result<()> {
|
||||
printer.print_header(writer, input_file)?;
|
||||
self.print_file_ranges(printer, writer, reader, &self.config.line_ranges)?;
|
||||
printer.print_footer(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_file_ranges<'a, P: Printer>(
|
||||
&self,
|
||||
printer: &mut P,
|
||||
writer: &mut Write,
|
||||
mut reader: InputFileReader,
|
||||
line_ranges: &LineRanges,
|
||||
) -> Result<()> {
|
||||
let mut line_buffer = Vec::new();
|
||||
let mut line_number: usize = 1;
|
||||
|
||||
while reader.read_line(&mut line_buffer)? {
|
||||
match line_ranges.check(line_number) {
|
||||
RangeCheckResult::OutsideRange => {
|
||||
// Call the printer in case we need to call the syntax highlighter
|
||||
// for this line. However, set `out_of_range` to `true`.
|
||||
printer.print_line(true, writer, line_number, &line_buffer)?;
|
||||
}
|
||||
RangeCheckResult::InRange => {
|
||||
printer.print_line(false, writer, line_number, &line_buffer)?;
|
||||
}
|
||||
RangeCheckResult::AfterLastRange => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
line_number += 1;
|
||||
line_buffer.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+10
-9
@@ -1,6 +1,6 @@
|
||||
use ansi_term::Style;
|
||||
use diff::LineChange;
|
||||
use printer::{Colors, Printer};
|
||||
use printer::{Colors, InteractivePrinter};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DecorationText {
|
||||
@@ -9,12 +9,15 @@ pub struct DecorationText {
|
||||
}
|
||||
|
||||
pub trait Decoration {
|
||||
fn generate(&self, line_number: usize, continuation: bool, printer: &Printer)
|
||||
-> DecorationText;
|
||||
fn generate(
|
||||
&self,
|
||||
line_number: usize,
|
||||
continuation: bool,
|
||||
printer: &InteractivePrinter,
|
||||
) -> DecorationText;
|
||||
fn width(&self) -> usize;
|
||||
}
|
||||
|
||||
// Line number decoration.
|
||||
pub struct LineNumberDecoration {
|
||||
color: Style,
|
||||
cached_wrap: DecorationText,
|
||||
@@ -39,7 +42,7 @@ impl Decoration for LineNumberDecoration {
|
||||
&self,
|
||||
line_number: usize,
|
||||
continuation: bool,
|
||||
_printer: &Printer,
|
||||
_printer: &InteractivePrinter,
|
||||
) -> DecorationText {
|
||||
if continuation {
|
||||
if line_number > self.cached_wrap_invalid_at {
|
||||
@@ -65,7 +68,6 @@ impl Decoration for LineNumberDecoration {
|
||||
}
|
||||
}
|
||||
|
||||
// Line changes decoration.
|
||||
pub struct LineChangesDecoration {
|
||||
cached_none: DecorationText,
|
||||
cached_added: DecorationText,
|
||||
@@ -99,7 +101,7 @@ impl Decoration for LineChangesDecoration {
|
||||
&self,
|
||||
line_number: usize,
|
||||
continuation: bool,
|
||||
printer: &Printer,
|
||||
printer: &InteractivePrinter,
|
||||
) -> DecorationText {
|
||||
if !continuation {
|
||||
if let Some(ref changes) = printer.line_changes {
|
||||
@@ -121,7 +123,6 @@ impl Decoration for LineChangesDecoration {
|
||||
}
|
||||
}
|
||||
|
||||
// Grid border decoration.
|
||||
pub struct GridBorderDecoration {
|
||||
cached: DecorationText,
|
||||
}
|
||||
@@ -142,7 +143,7 @@ impl Decoration for GridBorderDecoration {
|
||||
&self,
|
||||
_line_number: usize,
|
||||
_continuation: bool,
|
||||
_printer: &Printer,
|
||||
_printer: &InteractivePrinter,
|
||||
) -> DecorationText {
|
||||
self.cached.clone()
|
||||
}
|
||||
|
||||
+7
-4
@@ -15,11 +15,14 @@ pub type LineChanges = HashMap<u32, LineChange>;
|
||||
|
||||
pub fn get_git_diff(filename: &str) -> Option<LineChanges> {
|
||||
let repo = Repository::discover(&filename).ok()?;
|
||||
let path_absolute = fs::canonicalize(&filename).ok()?;
|
||||
let path_relative_to_repo = path_absolute.strip_prefix(repo.workdir()?).ok()?;
|
||||
|
||||
let repo_path_absolute = fs::canonicalize(repo.workdir()?).ok()?;
|
||||
|
||||
let filepath_absolute = fs::canonicalize(&filename).ok()?;
|
||||
let filepath_relative_to_repo = filepath_absolute.strip_prefix(&repo_path_absolute).ok()?;
|
||||
|
||||
let mut diff_options = DiffOptions::new();
|
||||
let pathspec = path_relative_to_repo.into_c_string().ok()?;
|
||||
let pathspec = filepath_relative_to_repo.into_c_string().ok()?;
|
||||
diff_options.pathspec(pathspec);
|
||||
diff_options.context_lines(0);
|
||||
|
||||
@@ -42,7 +45,7 @@ pub fn get_git_diff(filename: &str) -> Option<LineChanges> {
|
||||
Some(&mut |delta, hunk| {
|
||||
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
|
||||
|
||||
if path_relative_to_repo != path {
|
||||
if filepath_relative_to_repo != path {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
use directories::ProjectDirs;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PROJECT_DIRS: ProjectDirs =
|
||||
ProjectDirs::from("", "", crate_name!()).expect("Could not get home directory");
|
||||
}
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
use ansi_term::Colour::Green;
|
||||
use app::{Config, LineRange};
|
||||
use assets::HighlightingAssets;
|
||||
use diff::get_git_diff;
|
||||
use errors::*;
|
||||
use output::OutputType;
|
||||
use printer::Printer;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::Theme;
|
||||
use syntect::parsing::SyntaxDefinition;
|
||||
|
||||
pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
|
||||
let mut languages = assets
|
||||
.syntax_set
|
||||
.syntaxes()
|
||||
.iter()
|
||||
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
languages.sort_by_key(|lang| lang.name.to_uppercase());
|
||||
|
||||
let longest = languages
|
||||
.iter()
|
||||
.map(|syntax| syntax.name.len())
|
||||
.max()
|
||||
.unwrap_or(32); // Fallback width if they have no language definitions.
|
||||
|
||||
let comma_separator = ", ";
|
||||
let separator = " ";
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
let desired_width = term_width - longest - separator.len();
|
||||
|
||||
for lang in languages {
|
||||
print!("{:width$}{}", lang.name, separator, width = longest);
|
||||
|
||||
// Number of characters on this line so far, wrap before `desired_width`
|
||||
let mut num_chars = 0;
|
||||
|
||||
let mut extension = lang.file_extensions.iter().peekable();
|
||||
while let Some(word) = extension.next() {
|
||||
// If we can't fit this word in, then create a line break and align it in.
|
||||
let new_chars = word.len() + comma_separator.len();
|
||||
if num_chars + new_chars >= desired_width {
|
||||
num_chars = 0;
|
||||
print!("\n{:width$}{}", "", separator, width = longest);
|
||||
}
|
||||
|
||||
num_chars += new_chars;
|
||||
print!("{}", Green.paint(&word[..]));
|
||||
if extension.peek().is_some() {
|
||||
print!("{}", comma_separator);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result<bool> {
|
||||
let theme = assets.get_theme(&config.theme);
|
||||
|
||||
let mut output_type = OutputType::from_mode(config.paging_mode);
|
||||
let handle = output_type.handle()?;
|
||||
let mut printer = Printer::new(handle, &config, &theme);
|
||||
let mut no_errors: bool = true;
|
||||
|
||||
for file in &config.files {
|
||||
printer.ansi_prefix_sgr.clear();
|
||||
printer.line_changes = file.and_then(|filename| get_git_diff(filename));
|
||||
let syntax = assets.get_syntax(config.language, *file);
|
||||
|
||||
let result = print_file(theme, &syntax, &mut printer, *file);
|
||||
|
||||
if let Err(error) = result {
|
||||
handle_error(&error);
|
||||
no_errors = false;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(no_errors)
|
||||
}
|
||||
|
||||
fn print_file(
|
||||
theme: &Theme,
|
||||
syntax: &SyntaxDefinition,
|
||||
printer: &mut Printer,
|
||||
filename: Option<&str>,
|
||||
) -> Result<()> {
|
||||
let stdin = io::stdin(); // TODO: this is not always needed
|
||||
{
|
||||
let reader: Box<BufRead> = match filename {
|
||||
None => Box::new(stdin.lock()),
|
||||
Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
|
||||
};
|
||||
|
||||
let highlighter = HighlightLines::new(syntax, theme);
|
||||
|
||||
printer.print_header(filename)?;
|
||||
print_file_ranges(printer, reader, highlighter, &printer.config.line_range)?;
|
||||
printer.print_footer()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_file_ranges<'a>(
|
||||
printer: &mut Printer,
|
||||
mut reader: Box<BufRead + 'a>,
|
||||
mut highlighter: HighlightLines,
|
||||
line_ranges: &Option<LineRange>,
|
||||
) -> Result<()> {
|
||||
let mut buffer = Vec::new();
|
||||
|
||||
while reader.read_until(b'\n', &mut buffer)? > 0 {
|
||||
{
|
||||
let line = String::from_utf8_lossy(&buffer);
|
||||
let regions = highlighter.highlight(line.as_ref());
|
||||
|
||||
match line_ranges {
|
||||
&Some(ref range) => {
|
||||
if printer.line_number + 1 < range.lower {
|
||||
// skip line
|
||||
printer.line_number += 1;
|
||||
} else if printer.line_number >= range.upper {
|
||||
// no more lines in range
|
||||
break;
|
||||
} else {
|
||||
printer.print_line(®ions)?;
|
||||
}
|
||||
}
|
||||
&None => {
|
||||
printer.print_line(®ions)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
buffer.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, BufReader};
|
||||
|
||||
use content_inspector::{self, ContentType};
|
||||
|
||||
use errors::*;
|
||||
|
||||
const THEME_PREVIEW_FILE: &[u8] = include_bytes!("../assets/theme_preview.rs");
|
||||
|
||||
pub struct InputFileReader<'a> {
|
||||
inner: Box<dyn BufRead + 'a>,
|
||||
pub first_line: Vec<u8>,
|
||||
pub content_type: 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 = content_inspector::inspect(&first_line[..]);
|
||||
|
||||
if content_type == ContentType::UTF_16LE {
|
||||
reader.read_until(0x00, &mut first_line).ok();
|
||||
}
|
||||
|
||||
InputFileReader {
|
||||
inner: Box::new(reader),
|
||||
first_line,
|
||||
content_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub 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 == 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 str),
|
||||
ThemePreviewFile,
|
||||
}
|
||||
|
||||
impl<'a> InputFile<'a> {
|
||||
pub 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)?;
|
||||
|
||||
if file.metadata()?.is_dir() {
|
||||
return Err(format!("'{}' is a directory.", filename).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());
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use errors::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LineRange {
|
||||
pub lower: usize,
|
||||
pub upper: usize,
|
||||
}
|
||||
|
||||
impl LineRange {
|
||||
pub fn from(range_raw: &str) -> Result<LineRange> {
|
||||
LineRange::parse_range(range_raw)
|
||||
}
|
||||
|
||||
pub fn new() -> LineRange {
|
||||
LineRange {
|
||||
lower: usize::min_value(),
|
||||
upper: usize::max_value(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_range(range_raw: &str) -> Result<LineRange> {
|
||||
let mut new_range = LineRange::new();
|
||||
|
||||
if range_raw.bytes().nth(0).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':' {
|
||||
new_range.lower = range_raw[..range_raw.len() - 1].parse()?;
|
||||
return Ok(new_range);
|
||||
}
|
||||
|
||||
let line_numbers: Vec<&str> = range_raw.split(':').collect();
|
||||
if line_numbers.len() == 2 {
|
||||
new_range.lower = line_numbers[0].parse()?;
|
||||
new_range.upper = line_numbers[1].parse()?;
|
||||
return Ok(new_range);
|
||||
}
|
||||
|
||||
Err("expected single ':' character".into())
|
||||
}
|
||||
|
||||
pub fn is_inside(&self, line: usize) -> bool {
|
||||
line >= self.lower && line <= self.upper
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_full() {
|
||||
let range = LineRange::from("40:50").expect("Shouldn't fail on test!");
|
||||
assert_eq!(40, range.lower);
|
||||
assert_eq!(50, range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_partial_min() {
|
||||
let range = LineRange::from(":50").expect("Shouldn't fail on test!");
|
||||
assert_eq!(usize::min_value(), range.lower);
|
||||
assert_eq!(50, range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_partial_max() {
|
||||
let range = LineRange::from("40:").expect("Shouldn't fail on test!");
|
||||
assert_eq!(40, range.lower);
|
||||
assert_eq!(usize::max_value(), range.upper);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fail() {
|
||||
let range = LineRange::from("40:50:80");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from("40::80");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from(":40:");
|
||||
assert!(range.is_err());
|
||||
let range = LineRange::from("40");
|
||||
assert!(range.is_err());
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum RangeCheckResult {
|
||||
// Within one of the given ranges
|
||||
InRange,
|
||||
|
||||
// Before the first range or within two ranges
|
||||
OutsideRange,
|
||||
|
||||
// Line number is outside of all ranges and larger than the last range.
|
||||
AfterLastRange,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LineRanges {
|
||||
ranges: Vec<LineRange>,
|
||||
largest_upper_bound: usize,
|
||||
}
|
||||
|
||||
impl LineRanges {
|
||||
pub fn from(ranges: Vec<LineRange>) -> LineRanges {
|
||||
let largest_upper_bound = ranges
|
||||
.iter()
|
||||
.map(|r| r.upper)
|
||||
.max()
|
||||
.unwrap_or(usize::max_value());
|
||||
LineRanges {
|
||||
ranges,
|
||||
largest_upper_bound,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(&self, line: usize) -> RangeCheckResult {
|
||||
if self.ranges.is_empty() {
|
||||
RangeCheckResult::InRange
|
||||
} else {
|
||||
if self.ranges.iter().any(|r| r.is_inside(line)) {
|
||||
RangeCheckResult::InRange
|
||||
} else {
|
||||
if line < self.largest_upper_bound {
|
||||
RangeCheckResult::OutsideRange
|
||||
} else {
|
||||
RangeCheckResult::AfterLastRange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn ranges(rs: &[&str]) -> LineRanges {
|
||||
LineRanges::from(rs.iter().map(|r| LineRange::from(r).unwrap()).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_simple() {
|
||||
let ranges = ranges(&["3:8"]);
|
||||
|
||||
assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(5));
|
||||
assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_advanced() {
|
||||
let ranges = ranges(&["3:8", "11:20", "25:30"]);
|
||||
|
||||
assert_eq!(RangeCheckResult::OutsideRange, ranges.check(2));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(5));
|
||||
assert_eq!(RangeCheckResult::OutsideRange, ranges.check(9));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(11));
|
||||
assert_eq!(RangeCheckResult::OutsideRange, ranges.check(22));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(28));
|
||||
assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(31));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_open_low() {
|
||||
let ranges = ranges(&["3:8", ":5"]);
|
||||
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(1));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(3));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(7));
|
||||
assert_eq!(RangeCheckResult::AfterLastRange, ranges.check(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_open_high() {
|
||||
let ranges = ranges(&["3:", "2:5"]);
|
||||
|
||||
assert_eq!(RangeCheckResult::OutsideRange, ranges.check(1));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(3));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(5));
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ranges_empty() {
|
||||
let ranges = ranges(&[]);
|
||||
|
||||
assert_eq!(RangeCheckResult::InRange, ranges.check(1));
|
||||
}
|
||||
+159
-27
@@ -13,27 +13,47 @@ extern crate lazy_static;
|
||||
extern crate ansi_term;
|
||||
extern crate atty;
|
||||
extern crate console;
|
||||
extern crate content_inspector;
|
||||
extern crate directories;
|
||||
extern crate encoding;
|
||||
extern crate git2;
|
||||
extern crate shell_words;
|
||||
extern crate syntect;
|
||||
extern crate wild;
|
||||
|
||||
mod app;
|
||||
mod assets;
|
||||
mod clap_app;
|
||||
mod config;
|
||||
mod controller;
|
||||
mod decorations;
|
||||
mod diff;
|
||||
mod features;
|
||||
mod dirs;
|
||||
mod inputfile;
|
||||
mod line_range;
|
||||
mod output;
|
||||
mod preprocessor;
|
||||
mod printer;
|
||||
mod style;
|
||||
mod syntax_mapping;
|
||||
mod terminal;
|
||||
mod util;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
use app::App;
|
||||
use ansi_term::Colour::Green;
|
||||
use ansi_term::Style;
|
||||
|
||||
use app::{App, Config};
|
||||
use assets::{clear_assets, config_dir, HighlightingAssets};
|
||||
use features::{list_languages, print_files};
|
||||
use config::config_file;
|
||||
use controller::Controller;
|
||||
use inputfile::InputFile;
|
||||
use style::{OutputComponent, OutputComponents};
|
||||
|
||||
mod errors {
|
||||
error_chain! {
|
||||
@@ -62,45 +82,157 @@ mod errors {
|
||||
|
||||
use errors::*;
|
||||
|
||||
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
|
||||
if matches.is_present("init") {
|
||||
let source_dir = matches.value_of("source").map(Path::new);
|
||||
let target_dir = matches.value_of("target").map(Path::new);
|
||||
|
||||
let blank = matches.is_present("blank");
|
||||
|
||||
let assets = HighlightingAssets::from_files(source_dir, blank)?;
|
||||
assets.save(target_dir)?;
|
||||
} else if matches.is_present("clear") {
|
||||
clear_assets();
|
||||
} else if matches.is_present("config-dir") {
|
||||
writeln!(io::stdout(), "{}", config_dir())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_languages(config: &Config) -> Result<()> {
|
||||
let assets = HighlightingAssets::new();
|
||||
let mut languages = assets
|
||||
.syntax_set
|
||||
.syntaxes()
|
||||
.iter()
|
||||
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
languages.sort_by_key(|lang| lang.name.to_uppercase());
|
||||
|
||||
let longest = languages
|
||||
.iter()
|
||||
.map(|syntax| syntax.name.len())
|
||||
.max()
|
||||
.unwrap_or(32); // Fallback width if they have no language definitions.
|
||||
|
||||
let comma_separator = ", ";
|
||||
let separator = " ";
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
let desired_width = config.term_width - longest - separator.len();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let style = if config.colored_output {
|
||||
Green.normal()
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
for lang in languages {
|
||||
write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
|
||||
|
||||
// Number of characters on this line so far, wrap before `desired_width`
|
||||
let mut num_chars = 0;
|
||||
|
||||
let mut extension = lang.file_extensions.iter().peekable();
|
||||
while let Some(word) = extension.next() {
|
||||
// If we can't fit this word in, then create a line break and align it in.
|
||||
let new_chars = word.len() + comma_separator.len();
|
||||
if num_chars + new_chars >= desired_width {
|
||||
num_chars = 0;
|
||||
write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
|
||||
}
|
||||
|
||||
num_chars += new_chars;
|
||||
write!(stdout, "{}", style.paint(&word[..]))?;
|
||||
if extension.peek().is_some() {
|
||||
write!(stdout, "{}", comma_separator)?;
|
||||
}
|
||||
}
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_themes(cfg: &Config) -> Result<()> {
|
||||
let assets = HighlightingAssets::new();
|
||||
let themes = &assets.theme_set.themes;
|
||||
let mut config = cfg.clone();
|
||||
let mut style = HashSet::new();
|
||||
style.insert(OutputComponent::Plain);
|
||||
config.files = vec![InputFile::ThemePreviewFile];
|
||||
config.output_components = OutputComponents(style);
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
if config.colored_output {
|
||||
for (theme, _) in themes.iter() {
|
||||
writeln!(
|
||||
stdout,
|
||||
"Theme: {}\n",
|
||||
Style::new().bold().paint(theme.to_string())
|
||||
)?;
|
||||
config.theme = theme.to_string();
|
||||
let _controller = Controller::new(&config, &assets).run();
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
} else {
|
||||
for (theme, _) in themes.iter() {
|
||||
writeln!(stdout, "{}", theme)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_controller(config: &Config) -> Result<bool> {
|
||||
let assets = HighlightingAssets::new();
|
||||
let controller = Controller::new(&config, &assets);
|
||||
controller.run()
|
||||
}
|
||||
|
||||
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
|
||||
/// `Some(false)` if any intermediate errors occurred (were printed).
|
||||
fn run() -> Result<bool> {
|
||||
let app = App::new();
|
||||
let app = App::new()?;
|
||||
|
||||
match app.matches.subcommand() {
|
||||
("cache", Some(cache_matches)) => {
|
||||
if cache_matches.is_present("init") {
|
||||
let source_dir = cache_matches.value_of("source").map(Path::new);
|
||||
let target_dir = cache_matches.value_of("target").map(Path::new);
|
||||
// If there is a file named 'cache' in the current working directory,
|
||||
// arguments for subcommand 'cache' are not mandatory.
|
||||
// If there are non-zero arguments, execute the subcommand cache, else, open the file cache.
|
||||
if !cache_matches.args.is_empty() {
|
||||
run_cache_subcommand(cache_matches)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
let mut config = app.config()?;
|
||||
config.files = vec![InputFile::Ordinary(&"cache")];
|
||||
|
||||
let assets = HighlightingAssets::from_files(source_dir)?;
|
||||
assets.save(target_dir)?;
|
||||
} else if cache_matches.is_present("clear") {
|
||||
clear_assets();
|
||||
} else if cache_matches.is_present("config-dir") {
|
||||
println!("{}", config_dir());
|
||||
run_controller(&config)
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {
|
||||
let config = app.config()?;
|
||||
let assets = HighlightingAssets::new();
|
||||
|
||||
if app.matches.is_present("list-languages") {
|
||||
list_languages(&assets, config.term_width);
|
||||
return Ok(true);
|
||||
}
|
||||
list_languages(&config)?;
|
||||
|
||||
if app.matches.is_present("list-themes") {
|
||||
let themes = &assets.theme_set.themes;
|
||||
for (theme, _) in themes.iter() {
|
||||
println!("{}", theme);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(true)
|
||||
} else if app.matches.is_present("list-themes") {
|
||||
list_themes(&config)?;
|
||||
|
||||
print_files(&assets, &config)
|
||||
Ok(true)
|
||||
} else if app.matches.is_present("config-file") {
|
||||
println!("{}", config_file().to_string_lossy());
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
run_controller(&config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+55
-28
@@ -1,8 +1,13 @@
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
use shell_words;
|
||||
|
||||
use app::PagingMode;
|
||||
use errors::*;
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
|
||||
pub enum OutputType {
|
||||
Pager(Child),
|
||||
@@ -10,39 +15,61 @@ pub enum OutputType {
|
||||
}
|
||||
|
||||
impl OutputType {
|
||||
pub fn from_mode(mode: PagingMode) -> Self {
|
||||
pub fn from_mode(mode: PagingMode, pager: Option<&str>) -> Result<Self> {
|
||||
use self::PagingMode::*;
|
||||
match mode {
|
||||
Always => OutputType::try_pager(false),
|
||||
QuitIfOneScreen => OutputType::try_pager(true),
|
||||
Ok(match mode {
|
||||
Always => OutputType::try_pager(false, pager)?,
|
||||
QuitIfOneScreen => OutputType::try_pager(true, pager)?,
|
||||
_ => OutputType::stdout(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Try to launch the pager. Fall back to stdout in case of errors.
|
||||
fn try_pager(quit_if_one_screen: bool) -> Self {
|
||||
let pager = env::var("BAT_PAGER")
|
||||
.or_else(|_| env::var("PAGER"))
|
||||
fn try_pager(quit_if_one_screen: bool, pager_from_config: Option<&str>) -> Result<Self> {
|
||||
let pager_from_env = env::var("BAT_PAGER").or_else(|_| env::var("PAGER"));
|
||||
|
||||
let pager = pager_from_config
|
||||
.map(|p| p.to_string())
|
||||
.or(pager_from_env.ok())
|
||||
.unwrap_or(String::from("less"));
|
||||
|
||||
let mut process = if pager == "less" {
|
||||
let mut args = vec!["--RAW-CONTROL-CHARS", "--no-init"];
|
||||
if quit_if_one_screen {
|
||||
args.push("--quit-if-one-screen");
|
||||
let pagerflags = shell_words::split(&pager)
|
||||
.chain_err(|| "Could not parse (BAT_)PAGER environment variable.")?;
|
||||
|
||||
match pagerflags.split_first() {
|
||||
Some((pager_name, mut args)) => {
|
||||
let mut pager_path = PathBuf::from(pager_name);
|
||||
|
||||
if pager_path.file_stem() == Some(&OsString::from("bat")) {
|
||||
pager_path = PathBuf::from("less");
|
||||
args = &[];
|
||||
}
|
||||
|
||||
let is_less = pager_path.file_stem() == Some(&OsString::from("less"));
|
||||
|
||||
let mut process = if is_less {
|
||||
let mut p = Command::new(&pager_path);
|
||||
if args.is_empty() {
|
||||
p.args(vec!["--RAW-CONTROL-CHARS", "--no-init"]);
|
||||
if quit_if_one_screen {
|
||||
p.arg("--quit-if-one-screen");
|
||||
}
|
||||
}
|
||||
p.env("LESSCHARSET", "UTF-8");
|
||||
p
|
||||
} else {
|
||||
Command::new(&pager_path)
|
||||
};
|
||||
|
||||
Ok(process
|
||||
.args(args)
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.map(OutputType::Pager)
|
||||
.unwrap_or_else(|_| OutputType::stdout()))
|
||||
}
|
||||
|
||||
let mut p = Command::new("less");
|
||||
p.args(&args).env("LESSCHARSET", "UTF-8");
|
||||
p
|
||||
} else {
|
||||
Command::new(pager)
|
||||
};
|
||||
|
||||
process
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.map(OutputType::Pager)
|
||||
.unwrap_or_else(|_| OutputType::stdout())
|
||||
None => Ok(OutputType::stdout()),
|
||||
}
|
||||
}
|
||||
|
||||
fn stdout() -> Self {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use console::AnsiCodeIterator;
|
||||
|
||||
/// Expand tabs like an ANSI-enabled expand(1).
|
||||
pub fn expand_tabs(line: &str, width: usize, cursor: &mut usize) -> String {
|
||||
let mut buffer = String::with_capacity(line.len() * 2);
|
||||
|
||||
for chunk in AnsiCodeIterator::new(line) {
|
||||
match chunk {
|
||||
(text, true) => buffer.push_str(text),
|
||||
(mut text, false) => {
|
||||
while let Some(index) = text.find('\t') {
|
||||
// Add previous text.
|
||||
if index > 0 {
|
||||
*cursor += index;
|
||||
buffer.push_str(&text[0..index]);
|
||||
}
|
||||
|
||||
// Add tab.
|
||||
let spaces = width - (*cursor % width);
|
||||
*cursor += spaces;
|
||||
buffer.push_str(&*" ".repeat(spaces));
|
||||
|
||||
// Next.
|
||||
text = &text[index + 1..text.len()];
|
||||
}
|
||||
|
||||
*cursor += text.len();
|
||||
buffer.push_str(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
pub fn replace_nonprintable(input: &str, tab_width: usize) -> String {
|
||||
let mut output = String::new();
|
||||
|
||||
let tab_width = if tab_width == 0 { 4 } else { tab_width };
|
||||
|
||||
for chr in input.chars() {
|
||||
match chr {
|
||||
// space
|
||||
' ' => output.push('•'),
|
||||
// tab
|
||||
'\t' => {
|
||||
if tab_width == 1 {
|
||||
output.push('↹');
|
||||
} else {
|
||||
output.push('├');
|
||||
output.push_str(&"─".repeat(tab_width - 2));
|
||||
output.push('┤');
|
||||
}
|
||||
}
|
||||
// line feed
|
||||
'\x0A' => output.push('␊'),
|
||||
// carriage return
|
||||
'\x0D' => output.push('␍'),
|
||||
// null
|
||||
'\x00' => output.push('␀'),
|
||||
// bell
|
||||
'\x07' => output.push('␇'),
|
||||
// backspace
|
||||
'\x08' => output.push('␈'),
|
||||
// escape
|
||||
'\x1B' => output.push('␛'),
|
||||
// anything else
|
||||
_ => output.push(chr),
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
+232
-78
@@ -1,30 +1,95 @@
|
||||
use ansi_term::Colour::{Fixed, Green, Red, Yellow};
|
||||
use ansi_term::Style;
|
||||
use app::Config;
|
||||
use console::AnsiCodeIterator;
|
||||
use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
|
||||
use diff::LineChanges;
|
||||
use errors::*;
|
||||
use std::boxed::Box;
|
||||
use std::io::Write;
|
||||
use std::vec::Vec;
|
||||
|
||||
use ansi_term::Colour::{Fixed, Green, Red, Yellow};
|
||||
use ansi_term::Style;
|
||||
|
||||
use console::AnsiCodeIterator;
|
||||
|
||||
use syntect::easy::HighlightLines;
|
||||
use syntect::highlighting::Theme;
|
||||
use syntect::parsing::SyntaxSet;
|
||||
|
||||
use content_inspector::ContentType;
|
||||
|
||||
use encoding::all::{UTF_16BE, UTF_16LE};
|
||||
use encoding::{DecoderTrap, Encoding};
|
||||
|
||||
use app::Config;
|
||||
use assets::HighlightingAssets;
|
||||
use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
|
||||
use diff::get_git_diff;
|
||||
use diff::LineChanges;
|
||||
use errors::*;
|
||||
use inputfile::{InputFile, InputFileReader};
|
||||
use preprocessor::{expand_tabs, replace_nonprintable};
|
||||
use style::OutputWrap;
|
||||
use syntect::highlighting::{self, Theme};
|
||||
use terminal::{as_terminal_escaped, to_ansi_color};
|
||||
|
||||
pub struct Printer<'a> {
|
||||
handle: &'a mut Write,
|
||||
colors: Colors,
|
||||
pub config: &'a Config<'a>,
|
||||
decorations: Vec<Box<Decoration>>,
|
||||
panel_width: usize,
|
||||
pub ansi_prefix_sgr: String,
|
||||
pub line_number: usize,
|
||||
pub line_changes: Option<LineChanges>,
|
||||
pub trait Printer {
|
||||
fn print_header(&mut self, handle: &mut Write, file: InputFile) -> Result<()>;
|
||||
fn print_footer(&mut self, handle: &mut Write) -> Result<()>;
|
||||
fn print_line(
|
||||
&mut self,
|
||||
out_of_range: bool,
|
||||
handle: &mut Write,
|
||||
line_number: usize,
|
||||
line_buffer: &[u8],
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<'a> Printer<'a> {
|
||||
pub fn new(handle: &'a mut Write, config: &'a Config, theme: &Theme) -> Self {
|
||||
pub struct SimplePrinter;
|
||||
|
||||
impl SimplePrinter {
|
||||
pub fn new() -> Self {
|
||||
SimplePrinter {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Printer for SimplePrinter {
|
||||
fn print_header(&mut self, _handle: &mut Write, _file: InputFile) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_footer(&mut self, _handle: &mut Write) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_line(
|
||||
&mut self,
|
||||
out_of_range: bool,
|
||||
handle: &mut Write,
|
||||
_line_number: usize,
|
||||
line_buffer: &[u8],
|
||||
) -> Result<()> {
|
||||
if !out_of_range {
|
||||
handle.write(line_buffer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InteractivePrinter<'a> {
|
||||
colors: Colors,
|
||||
config: &'a Config<'a>,
|
||||
decorations: Vec<Box<dyn Decoration>>,
|
||||
panel_width: usize,
|
||||
ansi_prefix_sgr: String,
|
||||
content_type: ContentType,
|
||||
pub line_changes: Option<LineChanges>,
|
||||
highlighter: Option<HighlightLines<'a>>,
|
||||
syntax_set: &'a SyntaxSet,
|
||||
}
|
||||
|
||||
impl<'a> InteractivePrinter<'a> {
|
||||
pub fn new(
|
||||
config: &'a Config,
|
||||
assets: &'a HighlightingAssets,
|
||||
file: InputFile,
|
||||
reader: &mut InputFileReader,
|
||||
) -> Self {
|
||||
let theme = assets.get_theme(&config.theme);
|
||||
|
||||
let colors = if config.colored_output {
|
||||
Colors::colored(theme, config.true_color)
|
||||
} else {
|
||||
@@ -32,7 +97,7 @@ impl<'a> Printer<'a> {
|
||||
};
|
||||
|
||||
// Create decorations.
|
||||
let mut decorations: Vec<Box<Decoration>> = Vec::new();
|
||||
let mut decorations: Vec<Box<dyn Decoration>> = Vec::new();
|
||||
|
||||
if config.output_components.numbers() {
|
||||
decorations.push(Box::new(LineNumberDecoration::new(&colors)));
|
||||
@@ -61,31 +126,75 @@ impl<'a> Printer<'a> {
|
||||
panel_width = 0;
|
||||
}
|
||||
|
||||
// Create printer.
|
||||
Printer {
|
||||
let mut line_changes = None;
|
||||
|
||||
let highlighter = if reader.content_type.is_binary() {
|
||||
None
|
||||
} else {
|
||||
// Get the Git modifications
|
||||
line_changes = if config.output_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);
|
||||
Some(HighlightLines::new(syntax, theme))
|
||||
};
|
||||
|
||||
InteractivePrinter {
|
||||
panel_width,
|
||||
handle,
|
||||
colors,
|
||||
config,
|
||||
decorations,
|
||||
content_type: reader.content_type,
|
||||
ansi_prefix_sgr: String::new(),
|
||||
line_number: 0,
|
||||
line_changes: None,
|
||||
line_changes,
|
||||
highlighter,
|
||||
syntax_set: &assets.syntax_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
|
||||
self.line_number = 0;
|
||||
fn print_horizontal_line(&mut self, handle: &mut Write, grid_char: char) -> Result<()> {
|
||||
if self.panel_width == 0 {
|
||||
writeln!(
|
||||
handle,
|
||||
"{}",
|
||||
self.colors.grid.paint("─".repeat(self.config.term_width))
|
||||
)?;
|
||||
} else {
|
||||
let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
|
||||
let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
|
||||
writeln!(handle, "{}", self.colors.grid.paint(hline))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn preprocess(&self, text: &str, cursor: &mut usize) -> String {
|
||||
if self.config.tab_width > 0 {
|
||||
expand_tabs(text, self.config.tab_width, cursor)
|
||||
} else {
|
||||
text.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Printer for InteractivePrinter<'a> {
|
||||
fn print_header(&mut self, handle: &mut Write, file: InputFile) -> Result<()> {
|
||||
if !self.config.output_components.header() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┬')?;
|
||||
self.print_horizontal_line(handle, '┬')?;
|
||||
|
||||
write!(
|
||||
self.handle,
|
||||
handle,
|
||||
"{}{}",
|
||||
" ".repeat(self.panel_width),
|
||||
self.colors
|
||||
@@ -93,35 +202,89 @@ impl<'a> Printer<'a> {
|
||||
.paint(if self.panel_width > 0 { "│ " } else { "" }),
|
||||
)?;
|
||||
} else {
|
||||
write!(self.handle, "{}", " ".repeat(self.panel_width))?;
|
||||
write!(handle, "{}", " ".repeat(self.panel_width))?;
|
||||
}
|
||||
|
||||
let (prefix, name) = match file {
|
||||
InputFile::Ordinary(filename) => ("File: ", filename),
|
||||
_ => ("", "STDIN"),
|
||||
};
|
||||
|
||||
let mode = match self.content_type {
|
||||
ContentType::BINARY => " <BINARY>",
|
||||
ContentType::UTF_16LE => " <UTF-16LE>",
|
||||
ContentType::UTF_16BE => " <UTF-16BE>",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
writeln!(
|
||||
self.handle,
|
||||
"{}{}",
|
||||
filename.map_or("", |_| "File: "),
|
||||
self.colors.filename.paint(filename.unwrap_or("STDIN"))
|
||||
handle,
|
||||
"{}{}{}",
|
||||
prefix,
|
||||
self.colors.filename.paint(name),
|
||||
mode
|
||||
)?;
|
||||
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┼')?;
|
||||
if self.content_type.is_text() {
|
||||
self.print_horizontal_line(handle, '┼')?;
|
||||
} else {
|
||||
self.print_horizontal_line(handle, '┴')?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn print_footer(&mut self) -> Result<()> {
|
||||
if self.config.output_components.grid() {
|
||||
self.print_horizontal_line('┴')
|
||||
fn print_footer(&mut self, handle: &mut Write) -> Result<()> {
|
||||
if self.config.output_components.grid() && self.content_type.is_text() {
|
||||
self.print_horizontal_line(handle, '┴')
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_line(&mut self, regions: &[(highlighting::Style, &str)]) -> Result<()> {
|
||||
self.line_number += 1;
|
||||
fn print_line(
|
||||
&mut self,
|
||||
out_of_range: bool,
|
||||
handle: &mut Write,
|
||||
line_number: usize,
|
||||
line_buffer: &[u8],
|
||||
) -> Result<()> {
|
||||
let mut line = match self.content_type {
|
||||
ContentType::BINARY => {
|
||||
return Ok(());
|
||||
}
|
||||
ContentType::UTF_16LE => UTF_16LE
|
||||
.decode(&line_buffer, DecoderTrap::Strict)
|
||||
.unwrap_or("Invalid UTF-16LE".into()),
|
||||
ContentType::UTF_16BE => UTF_16BE
|
||||
.decode(&line_buffer, DecoderTrap::Strict)
|
||||
.unwrap_or("Invalid UTF-16BE".into()),
|
||||
_ => String::from_utf8_lossy(&line_buffer).to_string(),
|
||||
};
|
||||
|
||||
if self.config.show_nonprintable {
|
||||
line = replace_nonprintable(&mut line, self.config.tab_width);
|
||||
}
|
||||
|
||||
let regions = {
|
||||
let highlighter = match self.highlighter {
|
||||
Some(ref mut highlighter) => highlighter,
|
||||
_ => {
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
highlighter.highlight(line.as_ref(), self.syntax_set)
|
||||
};
|
||||
|
||||
if out_of_range {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cursor: usize = 0;
|
||||
let mut cursor_max: usize = self.config.term_width;
|
||||
let mut cursor_total: usize = 0;
|
||||
let mut panel_wrap: Option<String> = None;
|
||||
|
||||
// Line decorations.
|
||||
@@ -129,11 +292,11 @@ impl<'a> Printer<'a> {
|
||||
let decorations = self
|
||||
.decorations
|
||||
.iter()
|
||||
.map(|ref d| d.generate(self.line_number, false, self))
|
||||
.map(|ref d| d.generate(line_number, false, self))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for deco in decorations {
|
||||
write!(self.handle, "{} ", deco.text)?;
|
||||
write!(handle, "{} ", deco.text)?;
|
||||
cursor_max -= deco.width + 1;
|
||||
}
|
||||
}
|
||||
@@ -142,20 +305,20 @@ impl<'a> Printer<'a> {
|
||||
if self.config.output_wrap == OutputWrap::None {
|
||||
let true_color = self.config.true_color;
|
||||
let colored_output = self.config.colored_output;
|
||||
let italics = self.config.use_italic_text;
|
||||
|
||||
write!(
|
||||
self.handle,
|
||||
"{}",
|
||||
regions
|
||||
.iter()
|
||||
.map(|&(style, text)| as_terminal_escaped(
|
||||
style,
|
||||
text,
|
||||
true_color,
|
||||
colored_output,
|
||||
)).collect::<Vec<_>>()
|
||||
.join("")
|
||||
)?;
|
||||
for &(style, region) in regions.iter() {
|
||||
let text = &*self.preprocess(region, &mut cursor_total);
|
||||
write!(
|
||||
handle,
|
||||
"{}",
|
||||
as_terminal_escaped(style, &*text, true_color, colored_output, italics,)
|
||||
)?;
|
||||
}
|
||||
|
||||
if line.bytes().next_back() != Some(b'\n') {
|
||||
write!(handle, "\n")?;
|
||||
}
|
||||
} else {
|
||||
for &(style, region) in regions.iter() {
|
||||
let mut ansi_iterator = AnsiCodeIterator::new(region);
|
||||
@@ -178,7 +341,11 @@ impl<'a> Printer<'a> {
|
||||
|
||||
// Regular text.
|
||||
(text, false) => {
|
||||
let text = text.trim_right_matches(|c| c == '\r' || c == '\n');
|
||||
let text = self.preprocess(
|
||||
text.trim_right_matches(|c| c == '\r' || c == '\n'),
|
||||
&mut cursor_total,
|
||||
);
|
||||
|
||||
let mut chars = text.chars();
|
||||
let mut remaining = text.chars().count();
|
||||
|
||||
@@ -191,7 +358,7 @@ impl<'a> Printer<'a> {
|
||||
cursor += remaining;
|
||||
|
||||
write!(
|
||||
self.handle,
|
||||
handle,
|
||||
"{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
@@ -201,6 +368,7 @@ impl<'a> Printer<'a> {
|
||||
),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text
|
||||
)
|
||||
)?;
|
||||
break;
|
||||
@@ -214,8 +382,9 @@ impl<'a> Printer<'a> {
|
||||
self.decorations
|
||||
.iter()
|
||||
.map(|ref d| d
|
||||
.generate(self.line_number, true, self)
|
||||
.text).collect::<Vec<String>>()
|
||||
.generate(line_number, true, self)
|
||||
.text)
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
))
|
||||
} else {
|
||||
@@ -229,7 +398,7 @@ impl<'a> Printer<'a> {
|
||||
remaining -= available;
|
||||
|
||||
write!(
|
||||
self.handle,
|
||||
handle,
|
||||
"{}\n{}",
|
||||
as_terminal_escaped(
|
||||
style,
|
||||
@@ -239,6 +408,7 @@ impl<'a> Printer<'a> {
|
||||
),
|
||||
self.config.true_color,
|
||||
self.config.colored_output,
|
||||
self.config.use_italic_text
|
||||
),
|
||||
panel_wrap.clone().unwrap()
|
||||
)?;
|
||||
@@ -251,23 +421,7 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.handle, "\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
|
||||
if self.panel_width == 0 {
|
||||
writeln!(
|
||||
self.handle,
|
||||
"{}",
|
||||
self.colors.grid.paint("─".repeat(self.config.term_width))
|
||||
)?;
|
||||
} else {
|
||||
let hline = "─".repeat(self.config.term_width - (self.panel_width + 1));
|
||||
let hline = format!("{}{}{}", "─".repeat(self.panel_width), grid_char, hline);
|
||||
writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
|
||||
write!(handle, "\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+16
-7
@@ -1,7 +1,8 @@
|
||||
use errors::*;
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
|
||||
use errors::*;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
|
||||
pub enum OutputComponent {
|
||||
Auto,
|
||||
@@ -22,11 +23,13 @@ pub enum OutputWrap {
|
||||
impl OutputComponent {
|
||||
pub fn components(&self, interactive_terminal: bool) -> &'static [OutputComponent] {
|
||||
match *self {
|
||||
OutputComponent::Auto => if interactive_terminal {
|
||||
OutputComponent::Full.components(interactive_terminal)
|
||||
} else {
|
||||
OutputComponent::Plain.components(interactive_terminal)
|
||||
},
|
||||
OutputComponent::Auto => {
|
||||
if interactive_terminal {
|
||||
OutputComponent::Full.components(interactive_terminal)
|
||||
} else {
|
||||
OutputComponent::Plain.components(interactive_terminal)
|
||||
}
|
||||
}
|
||||
OutputComponent::Changes => &[OutputComponent::Changes],
|
||||
OutputComponent::Grid => &[OutputComponent::Grid],
|
||||
OutputComponent::Header => &[OutputComponent::Header],
|
||||
@@ -53,11 +56,13 @@ impl FromStr for OutputComponent {
|
||||
"header" => Ok(OutputComponent::Header),
|
||||
"numbers" => Ok(OutputComponent::Numbers),
|
||||
"full" => Ok(OutputComponent::Full),
|
||||
"plain" | _ => Ok(OutputComponent::Plain),
|
||||
"plain" => Ok(OutputComponent::Plain),
|
||||
_ => Err(format!("Unknown style '{}'", s).into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OutputComponents(pub HashSet<OutputComponent>);
|
||||
|
||||
impl OutputComponents {
|
||||
@@ -76,4 +81,8 @@ impl OutputComponents {
|
||||
pub fn numbers(&self) -> bool {
|
||||
self.0.contains(&OutputComponent::Numbers)
|
||||
}
|
||||
|
||||
pub fn plain(&self) -> bool {
|
||||
self.0.iter().all(|c| c == &OutputComponent::Plain)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SyntaxMapping(HashMap<String, String>);
|
||||
|
||||
impl SyntaxMapping {
|
||||
pub fn new() -> SyntaxMapping {
|
||||
SyntaxMapping(HashMap::new())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, from: String, to: String) -> Option<String> {
|
||||
self.0.insert(from, to)
|
||||
}
|
||||
|
||||
pub fn replace<'a>(&self, input: &'a str) -> Cow<'a, str> {
|
||||
let mut out = Cow::from(input);
|
||||
if let Some(value) = self.0.get(input) {
|
||||
out = Cow::from(value.clone())
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let mut map = SyntaxMapping::new();
|
||||
map.insert("Cargo.lock".into(), "toml".into());
|
||||
map.insert(".ignore".into(), ".gitignore".into());
|
||||
|
||||
assert_eq!("toml", map.replace("Cargo.lock"));
|
||||
assert_eq!("other.lock", map.replace("other.lock"));
|
||||
|
||||
assert_eq!(".gitignore", map.replace(".ignore"));
|
||||
}
|
||||
+6
-45
@@ -1,31 +1,15 @@
|
||||
extern crate ansi_colours;
|
||||
|
||||
use ansi_term::Colour::{Fixed, RGB};
|
||||
use ansi_term::{self, Style};
|
||||
|
||||
use syntect::highlighting::{self, FontStyle};
|
||||
|
||||
/// Approximate a 24 bit color value by a 8 bit ANSI code
|
||||
fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 {
|
||||
const BLACK: u8 = 16;
|
||||
const WHITE: u8 = 231;
|
||||
|
||||
if r == g && g == b {
|
||||
if r < 8 {
|
||||
BLACK
|
||||
} else if r > 248 {
|
||||
WHITE
|
||||
} else {
|
||||
((r - 8) as u16 * 24 / 247) as u8 + 232
|
||||
}
|
||||
} else {
|
||||
36 * (r / 51) + 6 * (g / 51) + (b / 51) + 16
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Colour {
|
||||
if true_color {
|
||||
RGB(color.r, color.g, color.b)
|
||||
} else {
|
||||
let ansi_code = rgb2ansi(color.r, color.g, color.b);
|
||||
Fixed(ansi_code)
|
||||
Fixed(ansi_colours::ansi256_from_rgb((color.r, color.g, color.b)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +18,7 @@ pub fn as_terminal_escaped(
|
||||
text: &str,
|
||||
true_color: bool,
|
||||
colored: bool,
|
||||
italics: bool,
|
||||
) -> String {
|
||||
let style = if !colored {
|
||||
Style::default()
|
||||
@@ -44,7 +29,7 @@ pub fn as_terminal_escaped(
|
||||
color.bold()
|
||||
} else if style.font_style.contains(FontStyle::UNDERLINE) {
|
||||
color.underline()
|
||||
} else if style.font_style.contains(FontStyle::ITALIC) {
|
||||
} else if italics && style.font_style.contains(FontStyle::ITALIC) {
|
||||
color.italic()
|
||||
} else {
|
||||
color.normal()
|
||||
@@ -53,27 +38,3 @@ pub fn as_terminal_escaped(
|
||||
|
||||
style.paint(text).to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb2ansi_black_white() {
|
||||
assert_eq!(16, rgb2ansi(0x00, 0x00, 0x00));
|
||||
assert_eq!(231, rgb2ansi(0xff, 0xff, 0xff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb2ansi_gray() {
|
||||
assert_eq!(241, rgb2ansi(0x6c, 0x6c, 0x6c));
|
||||
assert_eq!(233, rgb2ansi(0x1c, 0x1c, 0x1c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb2ansi_color() {
|
||||
assert_eq!(96, rgb2ansi(0x87, 0x5f, 0x87));
|
||||
assert_eq!(141, rgb2ansi(0xaf, 0x87, 0xff));
|
||||
assert_eq!(193, rgb2ansi(0xd7, 0xff, 0xaf));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rgb2ansi_approx() {
|
||||
assert_eq!(231, rgb2ansi(0xfe, 0xfe, 0xfe));
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/// Helper function that might appear in Rust stable at some point
|
||||
/// (https://doc.rust-lang.org/stable/std/option/enum.Option.html#method.transpose)
|
||||
pub fn transpose<T, E>(opt: Option<Result<T, E>>) -> Result<Option<T>, E> {
|
||||
opt.map_or(Ok(None), |res| res.map(Some))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::transpose;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct TestError;
|
||||
|
||||
type TestResult<T> = Result<T, TestError>;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let a: Option<TestResult<i32>> = Some(Ok(2));
|
||||
assert_eq!(Ok(Some(2)), transpose(a));
|
||||
|
||||
let b: Option<TestResult<i32>> = Some(Err(TestError));
|
||||
assert_eq!(Err(TestError), transpose(b));
|
||||
|
||||
let c: Option<TestResult<i32>> = None;
|
||||
assert_eq!(Ok(None), transpose(c));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ SRC="test-src/jquery-3.3.1.js"
|
||||
|
||||
|
||||
cmd_bat="bat --style=full --color=always --paging=never '$SRC'"
|
||||
cmd_bat_simple="bat --plain --wrap=never --tabs=0 --color=always --paging=never '$SRC'"
|
||||
cmd_pygmentize="pygmentize -g '$SRC'"
|
||||
cmd_highlight="highlight -O truecolor '$SRC'"
|
||||
cmd_ccat="ccat --color=always '$SRC'"
|
||||
@@ -20,6 +21,7 @@ cmd_rouge="rougify '$SRC'"
|
||||
|
||||
hyperfine --warmup 3 \
|
||||
"$cmd_bat" \
|
||||
"$cmd_bat_simple" \
|
||||
"$cmd_pygmentize" \
|
||||
"$cmd_highlight" \
|
||||
"$cmd_ccat" \
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Make sure that the pager gets executed
|
||||
--paging=always
|
||||
|
||||
# Output a dummy message for the integration test.
|
||||
--pager="echo dummy-pager-from-config"
|
||||
@@ -0,0 +1,4 @@
|
||||
line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
Whitespace-only changes.
@@ -0,0 +1,9 @@
|
||||
1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
@@ -0,0 +1 @@
|
||||
hello world
|
||||
@@ -0,0 +1,365 @@
|
||||
extern crate assert_cmd;
|
||||
extern crate escargot;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use assert_cmd::prelude::*;
|
||||
use escargot::CargoRun;
|
||||
use std::process::Command;
|
||||
|
||||
lazy_static! {
|
||||
static ref CARGO_RUN: CargoRun = escargot::CargoBuild::new()
|
||||
.bin("bat")
|
||||
.current_release()
|
||||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn bat_with_config() -> Command {
|
||||
let mut cmd = CARGO_RUN.command();
|
||||
cmd.current_dir("tests/examples");
|
||||
cmd.env_remove("PAGER");
|
||||
cmd.env_remove("BAT_PAGER");
|
||||
cmd.env_remove("BAT_CONFIG_PATH");
|
||||
cmd.env_remove("BAT_STYLE");
|
||||
cmd.env_remove("BAT_THEME");
|
||||
cmd.env_remove("BAT_TABS");
|
||||
cmd
|
||||
}
|
||||
|
||||
fn bat() -> Command {
|
||||
let mut cmd = bat_with_config();
|
||||
cmd.arg("--no-config");
|
||||
cmd
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
bat()
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\n")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin() {
|
||||
bat()
|
||||
.with_stdin()
|
||||
.buffer("foo\nbar\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("foo\nbar\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concatenate() {
|
||||
bat()
|
||||
.arg("test.txt")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\nhello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concatenate_stdin() {
|
||||
bat()
|
||||
.arg("test.txt")
|
||||
.arg("-")
|
||||
.arg("test.txt")
|
||||
.with_stdin()
|
||||
.buffer("stdin\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\nstdin\nhello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_numbers() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--style=numbers")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(" 1 line 1\n 2 line 2\n 3 line 3\n 4 line 4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_range_2_3() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--line-range=2:3")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("line 2\nline 3\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_range_first_two() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--line-range=:2")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("line 1\nline 2\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_range_last_3() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--line-range=2:")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("line 2\nline 3\nline 4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_range_multiple() {
|
||||
bat()
|
||||
.arg("multiline.txt")
|
||||
.arg("--line-range=1:2")
|
||||
.arg("--line-range=4:4")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("line 1\nline 2\nline 4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_numbers() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=4")
|
||||
.arg("--style=numbers")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 1 2 3 4
|
||||
2 1 ?
|
||||
3 22 ?
|
||||
4 333 ?
|
||||
5 4444 ?
|
||||
6 55555 ?
|
||||
7 666666 ?
|
||||
8 7777777 ?
|
||||
9 88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_passthrough_wrapped() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=0")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_4_wrapped() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=4")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_8_wrapped() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=8")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_passthrough() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=0")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_4() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=4")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tabs_8() {
|
||||
bat()
|
||||
.arg("tabs.txt")
|
||||
.arg("--tabs=8")
|
||||
.arg("--style=plain")
|
||||
.arg("--decorations=always")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
" 1 2 3 4
|
||||
1 ?
|
||||
22 ?
|
||||
333 ?
|
||||
4444 ?
|
||||
55555 ?
|
||||
666666 ?
|
||||
7777777 ?
|
||||
88888888 ?
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_non_existing() {
|
||||
bat().arg("non-existing-file").assert().failure();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_directory() {
|
||||
bat().arg("sub_directory").assert().failure();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_exit_directory() {
|
||||
bat()
|
||||
.arg("sub_directory")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.stdout("hello world\n")
|
||||
.failure();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pager_basic() {
|
||||
bat()
|
||||
.env("PAGER", "echo pager-output")
|
||||
.arg("--paging=always")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("pager-output\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pager_overwrite() {
|
||||
bat()
|
||||
.env("PAGER", "echo other-pager")
|
||||
.env("BAT_PAGER", "echo pager-output")
|
||||
.arg("--paging=always")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("pager-output\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pager_disable() {
|
||||
bat()
|
||||
.env("PAGER", "echo other-pager")
|
||||
.env("BAT_PAGER", "")
|
||||
.arg("--paging=always")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("hello world\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_location_test() {
|
||||
bat_with_config()
|
||||
.env("BAT_CONFIG_PATH", "bat.conf")
|
||||
.arg("--config-file")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("bat.conf\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_read_arguments_from_file() {
|
||||
bat_with_config()
|
||||
.env("BAT_CONFIG_PATH", "bat.conf")
|
||||
.arg("test.txt")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout("dummy-pager-from-config\n");
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
mod tester;
|
||||
|
||||
use tester::BatTester;
|
||||
|
||||
macro_rules! snapshot_tests {
|
||||
($($test_name: ident: $style: expr,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
let bat_tester = BatTester::new();
|
||||
bat_tester.test_snapshot(stringify!($test_name), $style);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
snapshot_tests! {
|
||||
changes: "changes",
|
||||
grid: "grid",
|
||||
header: "header",
|
||||
numbers: "numbers",
|
||||
changes_grid: "changes,grid",
|
||||
changes_header: "changes,header",
|
||||
changes_numbers: "changes,numbers",
|
||||
grid_header: "grid,header",
|
||||
grid_numbers: "grid,numbers",
|
||||
header_numbers: "header,numbers",
|
||||
changes_grid_header: "changes,grid,header",
|
||||
changes_grid_numbers: "changes,grid,numbers",
|
||||
changes_header_numbers: "changes,header,numbers",
|
||||
grid_header_numbers: "grid,header,numbers",
|
||||
changes_grid_header_numbers: "changes,grid,header,numbers",
|
||||
full: "full",
|
||||
plain: "plain",
|
||||
}
|
||||
@@ -1,43 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import itertools
|
||||
import subprocess
|
||||
import pathlib
|
||||
import shutil
|
||||
|
||||
|
||||
def generate_snapshots():
|
||||
single_styles = ["changes", "grid", "header", "numbers"]
|
||||
collective_styles = ["full", "plain"]
|
||||
|
||||
for num in range(len(single_styles)):
|
||||
for grouped in itertools.combinations(single_styles, num + 1):
|
||||
generate_snapshot(",".join(grouped))
|
||||
generate_style_snapshot(",".join(grouped))
|
||||
|
||||
for style in collective_styles:
|
||||
generate_snapshot(style)
|
||||
generate_style_snapshot(style)
|
||||
|
||||
def generate_snapshot(option):
|
||||
command = "../../target/debug/bat --style={0} sample.rs > output/{0}.snapshot.txt".format(
|
||||
option
|
||||
|
||||
def generate_style_snapshot(style):
|
||||
generate_snapshot(style.replace(",", "_"), "--style={}".format(style))
|
||||
|
||||
|
||||
def generate_snapshot(name, arguments):
|
||||
command = "cargo run -- --paging=never --color=never --decorations=always "
|
||||
command += "{args} sample.rs > output/{name}.snapshot.txt".format(
|
||||
name=name,
|
||||
args=arguments
|
||||
)
|
||||
print("generating snapshot for {}".format(option))
|
||||
print("generating snapshot for {}".format(name))
|
||||
subprocess.call(command, shell=True)
|
||||
|
||||
|
||||
def build_bat():
|
||||
print("building bat")
|
||||
subprocess.call("cargo build", cwd="../..", shell=True)
|
||||
|
||||
|
||||
def prepare_output_dir():
|
||||
shutil.rmtree("output", ignore_errors=True)
|
||||
pathlib.Path("output").mkdir()
|
||||
|
||||
|
||||
def modify_sample_file():
|
||||
print("modifying sample.rs to show changes")
|
||||
shutil.copyfile("sample.modified.rs", "sample.rs")
|
||||
|
||||
|
||||
def undo_sample_file_modification():
|
||||
print("undoing sample.rs modifications")
|
||||
subprocess.call("git checkout -- sample.rs", shell=True)
|
||||
|
||||
|
||||
build_bat()
|
||||
prepare_output_dir()
|
||||
modify_sample_file()
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+11
-3
@@ -38,10 +38,18 @@ impl BatTester {
|
||||
BatTester { temp_dir, exe }
|
||||
}
|
||||
|
||||
pub fn test_snapshot(&self, style: &str) {
|
||||
pub fn test_snapshot(&self, name: &str, style: &str) {
|
||||
let output = Command::new(&self.exe)
|
||||
.current_dir(self.temp_dir.path())
|
||||
.args(&["sample.rs", &format!("--style={}", style)])
|
||||
.args(&[
|
||||
"sample.rs",
|
||||
"--no-config",
|
||||
"--paging=never",
|
||||
"--color=never",
|
||||
"--decorations=always",
|
||||
"--terminal-width=80",
|
||||
&format!("--style={}", style),
|
||||
])
|
||||
.output()
|
||||
.expect("bat failed");
|
||||
|
||||
@@ -51,7 +59,7 @@ impl BatTester {
|
||||
.replace("tests/snapshots/", "");
|
||||
|
||||
let mut expected = String::new();
|
||||
let mut file = File::open(format!("tests/snapshots/output/{}.snapshot.txt", style))
|
||||
let mut file = File::open(format!("tests/snapshots/output/{}.snapshot.txt", name))
|
||||
.expect("snapshot file missing");
|
||||
file.read_to_string(&mut expected)
|
||||
.expect("could not read snapshot file");
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
mod tester;
|
||||
|
||||
use tester::BatTester;
|
||||
|
||||
static STYLES: &'static [&'static str] = &[
|
||||
"changes",
|
||||
"grid",
|
||||
"header",
|
||||
"numbers",
|
||||
"changes,grid",
|
||||
"changes,header",
|
||||
"changes,numbers",
|
||||
"grid,header",
|
||||
"grid,numbers",
|
||||
"header,numbers",
|
||||
"changes,grid,header",
|
||||
"changes,grid,numbers",
|
||||
"changes,header,numbers",
|
||||
"grid,header,numbers",
|
||||
"changes,grid,header,numbers",
|
||||
"full",
|
||||
"plain",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_snapshots() {
|
||||
let bat_tester = BatTester::new();
|
||||
|
||||
for style in STYLES {
|
||||
println!("testing {}", style);
|
||||
bat_tester.test_snapshot(&*style);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user