Merge branch 'master' of github.com:helix-editor/helix

imgbot
trivernis 2 years ago
commit 151ac52741
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

11
.gitattributes vendored

@ -0,0 +1,11 @@
# Auto detect text files and perform normalization
* text=auto
*.rs text diff=rust
*.toml text diff=toml
*.scm text diff=scheme
*.md text diff=markdown
book/theme/highlight.js linguist-vendored
Cargo.lock text

@ -64,10 +64,12 @@ jobs:
rust: stable rust: stable
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
cross: false cross: false
# - build: aarch64-macos - build: aarch64-macos
# os: macos-latest os: macos-latest
# rust: stable rust: stable
# target: aarch64-apple-darwin target: aarch64-apple-darwin
cross: false
skip_tests: true # x86_64 host can't run aarch64 code
# - build: x86_64-win-gnu # - build: x86_64-win-gnu
# os: windows-2019 # os: windows-2019
# rust: stable-x86_64-gnu # rust: stable-x86_64-gnu
@ -100,6 +102,7 @@ jobs:
- name: Run cargo test - name: Run cargo test
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
if: "!matrix.skip_tests"
with: with:
use-cross: ${{ matrix.cross }} use-cross: ${{ matrix.cross }}
command: test command: test
@ -113,7 +116,7 @@ jobs:
args: --release --locked --target ${{ matrix.target }} args: --release --locked --target ${{ matrix.target }}
- name: Strip release binary (linux and macos) - name: Strip release binary (linux and macos)
if: matrix.build == 'x86_64-linux' || matrix.build == 'x86_64-macos' if: matrix.build == 'x86_64-linux' || endsWith(matrix.build, 'macos')
run: strip "target/${{ matrix.target }}/release/hx" run: strip "target/${{ matrix.target }}/release/hx"
- name: Strip release binary (arm) - name: Strip release binary (arm)

@ -0,0 +1,5 @@
# Things that we don't want ripgrep to search that we do want in git
# https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#automatic-filtering
# Minified JS vendored from mdbook
book/theme/highlight.js

380
Cargo.lock generated

@ -11,23 +11,32 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android_system_properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.58" version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -46,17 +55,23 @@ dependencies = [
"regex-automata", "regex-automata",
] ]
[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]] [[package]]
name = "bytecount" name = "bytecount"
version = "0.6.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.1.0" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]] [[package]]
name = "cassowary" name = "cassowary"
@ -89,11 +104,11 @@ dependencies = [
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.19" version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [ dependencies = [
"libc", "iana-time-zone",
"num-integer", "num-integer",
"num-traits", "num-traits",
"winapi", "winapi",
@ -119,21 +134,27 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.7" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"lazy_static", "once_cell",
] ]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.24.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9f7409c70a38a56216480fba371ee460207dd8926ccf5b4160591759559170" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"crossterm_winapi", "crossterm_winapi",
@ -178,9 +199,9 @@ dependencies = [
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@ -223,9 +244,9 @@ dependencies = [
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [ dependencies = [
"instant", "instant",
] ]
@ -257,15 +278,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.21" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -274,15 +295,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.21" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.21" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -302,20 +323,20 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.4" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi 0.10.2+wasi-snapshot-preview1", "wasi",
] ]
[[package]] [[package]]
name = "globset" name = "globset"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bstr", "bstr",
@ -527,6 +548,19 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"
@ -558,9 +592,9 @@ dependencies = [
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
[[package]] [[package]]
name = "instant" name = "instant"
@ -573,9 +607,18 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.1" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
@ -585,9 +628,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.125" version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -601,10 +644,11 @@ dependencies = [
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.6" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
dependencies = [ dependencies = [
"autocfg",
"scopeguard", "scopeguard",
] ]
@ -638,9 +682,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
@ -653,21 +697,21 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi",
"windows-sys 0.36.1", "windows-sys",
] ]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.44" version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"num-traits", "num-traits",
@ -675,9 +719,9 @@ dependencies = [
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -694,15 +738,15 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.0" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -710,15 +754,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.1" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-sys 0.32.0", "windows-sys",
] ]
[[package]] [[package]]
@ -729,9 +773,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.8" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -741,18 +785,18 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6" checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"memchr", "memchr",
@ -770,18 +814,18 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.4" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"rand_core", "rand_core",
] ]
@ -797,21 +841,22 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.10" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.0" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"redox_syscall", "redox_syscall",
"thiserror",
] ]
[[package]] [[package]]
@ -848,9 +893,9 @@ dependencies = [
[[package]] [[package]]
name = "retain_mut" name = "retain_mut"
version = "0.1.7" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c31b5c4033f8fdde8700e4657be2c497e7288f01515be52168c631e2e4d4086" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
[[package]] [[package]]
name = "ropey" name = "ropey"
@ -864,9 +909,9 @@ dependencies = [
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.9" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -885,18 +930,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.139" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.139" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -905,9 +950,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.82" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -916,9 +961,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.7" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -969,15 +1014,18 @@ dependencies = [
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.1.0" version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.5" version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "slotmap" name = "slotmap"
@ -1029,9 +1077,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "str-buf" name = "str-buf"
version = "1.0.5" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]] [[package]]
name = "str_indices" name = "str_indices"
@ -1041,9 +1089,9 @@ checksum = "9d9199fa80c817e074620be84374a520062ebac833f358d74b37060ce4a0f2c0"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1077,18 +1125,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.31" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1115,9 +1163,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.5.1" version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -1130,10 +1178,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.19.2" version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
dependencies = [ dependencies = [
"autocfg",
"bytes", "bytes",
"libc", "libc",
"memchr", "memchr",
@ -1150,9 +1199,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1200,9 +1249,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]] [[package]]
name = "unicode-general-category" name = "unicode-general-category"
@ -1212,9 +1261,9 @@ checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.1" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]] [[package]]
name = "unicode-linebreak" name = "unicode-linebreak"
@ -1227,9 +1276,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.19" version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
dependencies = [ dependencies = [
"tinyvec", "tinyvec",
] ]
@ -1278,15 +1327,63 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.10.2+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasi" name = "wasm-bindgen"
version = "0.11.0+wasi-snapshot-preview1" version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
[[package]] [[package]]
name = "which" name = "which"
@ -1330,86 +1427,43 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [ dependencies = [
"windows_aarch64_msvc 0.36.1", "windows_aarch64_msvc",
"windows_i686_gnu 0.36.1", "windows_i686_gnu",
"windows_i686_msvc 0.36.1", "windows_i686_msvc",
"windows_x86_64_gnu 0.36.1", "windows_x86_64_gnu",
"windows_x86_64_msvc 0.36.1", "windows_x86_64_msvc",
] ]
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.36.1" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.36.1" version = "0.36.1"

@ -1,6 +1,5 @@
# Helix # Helix
[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions) [![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
![Screenshot](./screenshot.png) ![Screenshot](./screenshot.png)
@ -49,11 +48,11 @@ tree-sitter grammars may be manually fetched and built with `hx --grammar fetch`
Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the
config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows). config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
| OS | command | | OS | Command |
|-------------------|-----------| | -------------------- | -------------------------------------------- |
|windows(cmd.exe) |`xcopy runtime %AppData%/helix/runtime` | | Windows (cmd.exe) | `xcopy /e runtime %AppData%\helix\runtime` |
|windows(powershell)|`xcopy runtime $Env:AppData\helix\runtime` | | Windows (PowerShell) | `xcopy /e runtime $Env:AppData\helix\runtime` |
|linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`| | Linux/macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
This location can be overridden via the `HELIX_RUNTIME` environment variable. This location can be overridden via the `HELIX_RUNTIME` environment variable.
@ -77,7 +76,7 @@ Helix can be installed on MacOS through homebrew via:
brew tap helix-editor/helix brew tap helix-editor/helix
brew install helix brew install helix
``` ```
# Contributing # Contributing
Contributing guidelines can be found [here](./docs/CONTRIBUTING.md). Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).

@ -25,6 +25,9 @@ select = "underline"
hidden = false hidden = false
``` ```
You may also specify a file to use for configuration with the `-c` or
`--config` CLI argument: `hx -c path/to/custom-config.toml`.
## Editor ## Editor
### `[editor]` Section ### `[editor]` Section
@ -38,7 +41,7 @@ hidden = false
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` | | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
| `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` | | `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
| `cursorline` | Highlight all lines with a cursor. | `false` | | `cursorline` | Highlight all lines with a cursor. | `false` |
| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `padding`, note that `diagnostics` also includes other features like breakpoints | `["diagnostics", "line-numbers", "padding"]` | | `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "line-numbers"]` |
| `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
| `auto-format` | Enable automatic formatting on save. | `true` | | `auto-format` | Enable automatic formatting on save. | `true` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
@ -63,6 +66,7 @@ Statusline elements can be defined as follows:
left = ["mode", "spinner"] left = ["mode", "spinner"]
center = ["file-name"] center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"] right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
``` ```
The following elements can be configured: The following elements can be configured:
@ -78,6 +82,9 @@ The following elements can be configured:
| `diagnostics` | The number of warnings and/or errors | | `diagnostics` | The number of warnings and/or errors |
| `selections` | The number of active selections | | `selections` | The number of active selections |
| `position` | The cursor position | | `position` | The cursor position |
| `position-percentage` | The cursor position as a percentage of the total number of lines |
| `separator` | The string defined in `editor.statusline.separator` (defaults to `"│"`) |
| `spacer` | Inserts a space between elements (multiple/contiguous spacers may be specified) |
### `[editor.lsp]` Section ### `[editor.lsp]` Section
@ -113,6 +120,8 @@ files and files listed within ignore files are ignored by (not visible in) the
helix file picker and global search. There is also one other key, `max-depth` helix file picker and global search. There is also one other key, `max-depth`
available, which is not defined by default. available, which is not defined by default.
All git related options are only enabled in a git repository.
| Key | Description | Default | | Key | Description | Default |
|--|--|---------| |--|--|---------|
|`hidden` | Enables ignoring hidden files. | true |`hidden` | Enables ignoring hidden files. | true
@ -185,7 +194,7 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r
| Key | Description | Default | | Key | Description | Default |
|-----|-------------|---------| |-----|-------------|---------|
| `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `tab`, and `newline`. | `"none"` | | `render` | Whether to render whitespace. May either be `"all"` or `"none"`, or a table with sub-keys `space`, `tab`, and `newline`. | `"none"` |
| `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp` or `newline` | See example below | | `characters` | Literal characters to use when rendering whitespace. Sub-keys may be any of `tab`, `space`, `nbsp`, `newline` or `tabpad` | See example below |
Example Example
@ -203,6 +212,7 @@ space = "·"
nbsp = "⍽" nbsp = "⍽"
tab = "→" tab = "→"
newline = "⏎" newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width)
``` ```
### `[editor.indent-guides]` Section ### `[editor.indent-guides]` Section

@ -1,6 +1,7 @@
| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP | | Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| bash | ✓ | | | `bash-language-server` | | bash | ✓ | | | `bash-language-server` |
| beancount | ✓ | | | |
| c | ✓ | ✓ | ✓ | `clangd` | | c | ✓ | ✓ | ✓ | `clangd` |
| c-sharp | ✓ | | | `OmniSharp` | | c-sharp | ✓ | | | `OmniSharp` |
| cairo | ✓ | | | | | cairo | ✓ | | | |
@ -10,9 +11,11 @@
| cpon | ✓ | | ✓ | | | cpon | ✓ | | ✓ | |
| cpp | ✓ | ✓ | ✓ | `clangd` | | cpp | ✓ | ✓ | ✓ | `clangd` |
| css | ✓ | | | `vscode-css-language-server` | | css | ✓ | | | `vscode-css-language-server` |
| cue | ✓ | | | `cuelsp` |
| dart | ✓ | | ✓ | `dart` | | dart | ✓ | | ✓ | `dart` |
| devicetree | ✓ | | ✓ | | | devicetree | ✓ | | ✓ | |
| dockerfile | ✓ | | | `docker-langserver` | | dockerfile | ✓ | | | `docker-langserver` |
| dot | ✓ | | | `dot-language-server` |
| edoc | ✓ | | | | | edoc | ✓ | | | |
| eex | ✓ | | | | | eex | ✓ | | | |
| ejs | ✓ | | | | | ejs | ✓ | | | |
@ -34,23 +37,24 @@
| glsl | ✓ | ✓ | ✓ | | | glsl | ✓ | ✓ | ✓ | |
| go | ✓ | ✓ | ✓ | `gopls` | | go | ✓ | ✓ | ✓ | `gopls` |
| gomod | ✓ | | | `gopls` | | gomod | ✓ | | | `gopls` |
| gotmpl | ✓ | | | `gopls` |
| gowork | ✓ | | | `gopls` | | gowork | ✓ | | | `gopls` |
| graphql | ✓ | | | | | graphql | ✓ | | | |
| hare | ✓ | | ✓ | | | hare | ✓ | | ✓ | |
| haskell | ✓ | | | `haskell-language-server-wrapper` | | haskell | ✓ | | | `haskell-language-server-wrapper` |
| hcl | ✓ | | ✓ | `terraform-ls` | | hcl | ✓ | | ✓ | `terraform-ls` |
| heex | ✓ | | | | | heex | ✓ | | | |
| html | ✓ | | | `vscode-html-language-server` | | html | ✓ | | | `vscode-html-language-server` |
| idris | | | | `idris2-lsp` | | idris | | | | `idris2-lsp` |
| iex | ✓ | | | | | iex | ✓ | | | |
| java | ✓ | | | `jdtls` | | java | ✓ | | | `jdtls` |
| javascript | ✓ | | ✓ | `typescript-language-server` | | javascript | ✓ | | ✓ | `typescript-language-server` |
| jsdoc | ✓ | | | | | jsdoc | ✓ | | | |
| json | ✓ | | ✓ | `vscode-json-language-server` | | json | ✓ | | ✓ | `vscode-json-language-server` |
| jsx | ✓ | | ✓ | `typescript-language-server` | | jsx | ✓ | | ✓ | `typescript-language-server` |
| julia | ✓ | | | `julia` | | julia | ✓ | | | `julia` |
| kotlin | ✓ | | | `kotlin-language-server` | | kotlin | ✓ | | | `kotlin-language-server` |
| latex | ✓ | | | `texlab` | | latex | ✓ | | | `texlab` |
| lean | ✓ | | | `lean` | | lean | ✓ | | | `lean` |
| ledger | ✓ | | | | | ledger | ✓ | | | |
| llvm | ✓ | ✓ | ✓ | | | llvm | ✓ | ✓ | ✓ | |
@ -59,6 +63,7 @@
| lua | ✓ | | ✓ | `lua-language-server` | | lua | ✓ | | ✓ | `lua-language-server` |
| make | ✓ | | | | | make | ✓ | | | |
| markdown | ✓ | | | | | markdown | ✓ | | | |
| markdown.inline | ✓ | | | |
| meson | ✓ | | ✓ | | | meson | ✓ | | ✓ | |
| mint | | | | `mint` | | mint | | | | `mint` |
| nickel | ✓ | | ✓ | `nls` | | nickel | ✓ | | ✓ | `nls` |
@ -66,7 +71,7 @@
| nu | ✓ | | | | | nu | ✓ | | | |
| ocaml | ✓ | | ✓ | `ocamllsp` | | ocaml | ✓ | | ✓ | `ocamllsp` |
| ocaml-interface | ✓ | | | `ocamllsp` | | ocaml-interface | ✓ | | | `ocamllsp` |
| odin | ✓ | | | | | odin | ✓ | | | `ols` |
| openscad | ✓ | | | `openscad-language-server` | | openscad | ✓ | | | `openscad-language-server` |
| org | ✓ | | | | | org | ✓ | | | |
| perl | ✓ | ✓ | ✓ | | | perl | ✓ | ✓ | ✓ | |
@ -86,6 +91,7 @@
| scala | ✓ | | ✓ | `metals` | | scala | ✓ | | ✓ | `metals` |
| scheme | ✓ | | | | | scheme | ✓ | | | |
| scss | ✓ | | | `vscode-css-language-server` | | scss | ✓ | | | `vscode-css-language-server` |
| slint | ✓ | | ✓ | `slint-lsp` |
| solidity | ✓ | | | `solc` | | solidity | ✓ | | | `solc` |
| sql | ✓ | | | | | sql | ✓ | | | |
| sshclientconfig | ✓ | | | | | sshclientconfig | ✓ | | | |
@ -93,12 +99,13 @@
| svelte | ✓ | | ✓ | `svelteserver` | | svelte | ✓ | | ✓ | `svelteserver` |
| swift | ✓ | | | `sourcekit-lsp` | | swift | ✓ | | | `sourcekit-lsp` |
| tablegen | ✓ | ✓ | ✓ | | | tablegen | ✓ | ✓ | ✓ | |
| task | ✓ | | | |
| tfvars | | | | `terraform-ls` | | tfvars | | | | `terraform-ls` |
| toml | ✓ | | | `taplo` | | toml | ✓ | | | `taplo` |
| tsq | ✓ | | | | | tsq | ✓ | | | |
| tsx | ✓ | | | `typescript-language-server` | | tsx | ✓ | | | `typescript-language-server` |
| twig | ✓ | | | | | twig | ✓ | | | |
| typescript | ✓ | | ✓ | `typescript-language-server` | | typescript | ✓ | | ✓ | `typescript-language-server` |
| ungrammar | ✓ | | | | | ungrammar | ✓ | | | |
| v | ✓ | | | `vls` | | v | ✓ | | | `vls` |
| vala | ✓ | | | `vala-language-server` | | vala | ✓ | | | `vala-language-server` |

@ -1,18 +1,18 @@
| Name | Description | | Name | Description |
| --- | --- | | --- | --- |
| `:quit`, `:q` | Close the current view. | | `:quit`, `:q` | Close the current view. |
| `:quit!`, `:q!` | Close the current view forcefully (ignoring unsaved changes). | | `:quit!`, `:q!` | Force close the current view, ignoring unsaved changes. |
| `:open`, `:o` | Open a file from disk into the current view. | | `:open`, `:o` | Open a file from disk into the current view. |
| `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. | | `:buffer-close`, `:bc`, `:bclose` | Close the current buffer. |
| `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully (ignoring unsaved changes). | | `:buffer-close!`, `:bc!`, `:bclose!` | Close the current buffer forcefully, ignoring unsaved changes. |
| `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. | | `:buffer-close-others`, `:bco`, `:bcloseother` | Close all buffers but the currently focused one. |
| `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Close all buffers but the currently focused one. | | `:buffer-close-others!`, `:bco!`, `:bcloseother!` | Force close all buffers but the currently focused one. |
| `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers, without quitting. | | `:buffer-close-all`, `:bca`, `:bcloseall` | Close all buffers without quitting. |
| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Close all buffers forcefully (ignoring unsaved changes), without quitting. | | `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Force close all buffers ignoring unsaved changes without quitting. |
| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. | | `:buffer-next`, `:bn`, `:bnext` | Goto next buffer. |
| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. | | `:buffer-previous`, `:bp`, `:bprev` | Goto previous buffer. |
| `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) | | `:write`, `:w` | Write changes to disk. Accepts an optional path (:write some/path.txt) |
| `:write!`, `:w!` | Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt) | | `:write!`, `:w!` | Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt) |
| `:new`, `:n` | Create a new scratch buffer. | | `:new`, `:n` | Create a new scratch buffer. |
| `:format`, `:fmt` | Format the file using the LSP formatter. | | `:format`, `:fmt` | Format the file using the LSP formatter. |
| `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) | | `:indent-style` | Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.) |
@ -25,9 +25,9 @@
| `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. | | `:write-quit-all`, `:wqa`, `:xa` | Write changes from all buffers to disk and close all views. |
| `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). | | `:write-quit-all!`, `:wqa!`, `:xa!` | Write changes from all buffers to disk and close all views forcefully (ignoring unsaved changes). |
| `:quit-all`, `:qa` | Close all views. | | `:quit-all`, `:qa` | Close all views. |
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). | | `:quit-all!`, `:qa!` | Force close all views ignoring unsaved changes. |
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). | | `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). | | `:cquit!`, `:cq!` | Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2). |
| `:theme` | Change the editor theme. | | `:theme` | Change the editor theme. |
| `:clipboard-yank` | Yank main selection into system clipboard. | | `:clipboard-yank` | Yank main selection into system clipboard. |
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. | | `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |
@ -42,7 +42,7 @@
| `:show-clipboard-provider` | Show clipboard provider name in status bar. | | `:show-clipboard-provider` | Show clipboard provider name in status bar. |
| `:change-current-directory`, `:cd` | Change the current working directory. | | `:change-current-directory`, `:cd` | Change the current working directory. |
| `:show-directory`, `:pwd` | Show the current working directory. | | `:show-directory`, `:pwd` | Show the current working directory. |
| `:encoding` | Set encoding based on `https://encoding.spec.whatwg.org` | | `:encoding` | Set encoding. Based on `https://encoding.spec.whatwg.org`. |
| `:reload` | Discard changes and reload from the source file. | | `:reload` | Discard changes and reload from the source file. |
| `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. | | `:tree-sitter-scopes` | Display tree sitter scopes, primarily for theming and development. |
| `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. | | `:debug-start`, `:dbg` | Start a debug session from a given template with given parameters. |
@ -53,7 +53,7 @@
| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. | | `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
| `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. | | `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. |
| `:tutor` | Open the tutorial. | | `:tutor` | Open the tutorial. |
| `:goto`, `:g` | Go to line number. | | `:goto`, `:g` | Goto line number. |
| `:set-language`, `:lang` | Set the language of current buffer. | | `:set-language`, `:lang` | Set the language of current buffer. |
| `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. | | `:set-option`, `:set` | Set a config option at runtime.<br>For example to disable smart case search, use `:set search.smart-case false`. |
| `:get-option`, `:get` | Get the current value of a config option. | | `:get-option`, `:get` | Get the current value of a config option. |
@ -61,8 +61,8 @@
| `:rsort` | Sort ranges in selection in reverse order. | | `:rsort` | Sort ranges in selection in reverse order. |
| `:reflow` | Hard-wrap the current selection of lines to a given width. | | `:reflow` | Hard-wrap the current selection of lines to a given width. |
| `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. | | `:tree-sitter-subtree`, `:ts-subtree` | Display tree sitter subtree under cursor, primarily for debugging queries. |
| `:config-reload` | Refreshes helix's config. | | `:config-reload` | Refresh user config. |
| `:config-open` | Open the helix config.toml file. | | `:config-open` | Open the user config.toml file. |
| `:log-open` | Open the helix log file. | | `:log-open` | Open the helix log file. |
| `:insert-output` | Run shell command, inserting output after each selection. | | `:insert-output` | Run shell command, inserting output after each selection. |
| `:append-output` | Run shell command, appending output after each selection. | | `:append-output` | Run shell command, appending output after each selection. |

@ -67,8 +67,8 @@ via the `HELIX_RUNTIME` environment variable.
| OS | command | | OS | command |
|-------------------|-----------| |-------------------|-----------|
|windows(cmd.exe) |`xcopy runtime %AppData%/helix/runtime` | |windows(cmd.exe) |`xcopy /e runtime %AppData%/helix/runtime` |
|windows(powershell)|`xcopy runtime $Env:AppData\helix\runtime` | |windows(powershell)|`xcopy /e runtime $Env:AppData\helix\runtime` |
|linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`| |linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`|
## Finishing up the installation ## Finishing up the installation

@ -1,7 +1,27 @@
# Keymap # Keymap
- Mappings marked (**LSP**) require an active language server for the file. - [Normal mode](#normal-mode)
- Mappings marked (**TS**) require a tree-sitter grammar for the filetype. - [Movement](#movement)
- [Changes](#changes)
- [Shell](#shell)
- [Selection manipulation](#selection-manipulation)
- [Search](#search)
- [Minor modes](#minor-modes)
- [View mode](#view-mode)
- [Goto mode](#goto-mode)
- [Match mode](#match-mode)
- [Window mode](#window-mode)
- [Space mode](#space-mode)
- [Popup](#popup)
- [Unimpaired](#unimpaired)
- [Insert Mode](#insert-mode)
- [Select / extend mode](#select--extend-mode)
- [Picker](#picker)
- [Prompt](#prompt)
> 💡 Mappings marked (**LSP**) require an active language server for the file.
> 💡 Mappings marked (**TS**) require a tree-sitter grammar for the filetype.
## Normal mode ## Normal mode
@ -339,15 +359,15 @@ mode before pressing `n` or `N` makes it possible to keep the current
selection. Toggling it on and off during your iterative searching allows selection. Toggling it on and off during your iterative searching allows
you to selectively add search terms to your selections. you to selectively add search terms to your selections.
# Picker ## Picker
Keys to use within picker. Remapping currently not supported. Keys to use within picker. Remapping currently not supported.
| Key | Description | | Key | Description |
| ----- | ------------- | | ----- | ------------- |
| `Up`, `Ctrl-p` | Previous entry | | `Tab`, `Up`, `Ctrl-p` | Previous entry |
| `PageUp`, `Ctrl-u` | Page up | | `PageUp`, `Ctrl-u` | Page up |
| `Down`, `Ctrl-n` | Next entry | | `Shift-tab`, `Down`, `Ctrl-n`| Next entry |
| `PageDown`, `Ctrl-d` | Page down | | `PageDown`, `Ctrl-d` | Page down |
| `Home` | Go to first entry | | `Home` | Go to first entry |
| `End` | Go to last entry | | `End` | Go to last entry |
@ -358,7 +378,7 @@ Keys to use within picker. Remapping currently not supported.
| `Ctrl-t` | Toggle preview | | `Ctrl-t` | Toggle preview |
| `Escape`, `Ctrl-c` | Close picker | | `Escape`, `Ctrl-c` | Close picker |
# Prompt ## Prompt
Keys to use within prompt, Remapping currently not supported. Keys to use within prompt, Remapping currently not supported.

@ -40,6 +40,7 @@ file-types = ["mylang", "myl"]
comment-token = "#" comment-token = "#"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
language-server = { command = "mylang-lsp", args = ["--stdio"] } language-server = { command = "mylang-lsp", args = ["--stdio"] }
formatter = { command = "mylang-formatter" , args = ["--stdin"] }
``` ```
These configuration keys are available: These configuration keys are available:
@ -59,6 +60,7 @@ These configuration keys are available:
| `language-server` | The Language Server to run. See the Language Server configuration section below. | | `language-server` | The Language Server to run. See the Language Server configuration section below. |
| `config` | Language Server configuration | | `config` | Language Server configuration |
| `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) | | `grammar` | The tree-sitter grammar to use (defaults to the value of `name`) |
| `formatter` | The formatter for the language, it will take precedence over the lsp when defined. The formatter must be able to take the original file as input from stdin and write the formatted file to stdout |
### Language Server configuration ### Language Server configuration

@ -103,6 +103,8 @@ We use a similar set of scopes as
[SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also [SublimeText](https://www.sublimetext.com/docs/scope_naming.html). See also
[TextMate](https://macromates.com/manual/en/language_grammars) scopes. [TextMate](https://macromates.com/manual/en/language_grammars) scopes.
- `attribute` - Class attributes, html tag attributes
- `type` - Types - `type` - Types
- `builtin` - Primitive types provided by the language (`int`, `usize`) - `builtin` - Primitive types provided by the language (`int`, `usize`)
- `constructor` - `constructor`
@ -133,13 +135,13 @@ We use a similar set of scopes as
- `parameter` - Function parameters - `parameter` - Function parameters
- `other` - `other`
- `member` - Fields of composite data types (e.g. structs, unions) - `member` - Fields of composite data types (e.g. structs, unions)
- `function` (TODO: ?)
- `label` - `label`
- `punctuation` - `punctuation`
- `delimiter` - Commas, colons - `delimiter` - Commas, colons
- `bracket` - Parentheses, angle brackets, etc. - `bracket` - Parentheses, angle brackets, etc.
- `special` - String interpolation brackets.
- `keyword` - `keyword`
- `control` - `control`
@ -224,6 +226,7 @@ These scopes are used for theming the editor interface.
| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | | `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) |
| `ui.statusline.separator` | Separator character in statusline |
| `ui.popup` | Documentation popups (e.g space-k) | | `ui.popup` | Documentation popups (e.g space-k) |
| `ui.popup.info` | Prompt for multiple key options | | `ui.popup.info` | Prompt for multiple key options |
| `ui.window` | Border lines separating splits | | `ui.window` | Border lines separating splits |

@ -16,7 +16,7 @@ _hx() {
COMPREPLY=($(compgen -W "$languages" -- $2)) COMPREPLY=($(compgen -W "$languages" -- $2))
;; ;;
*) *)
COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit" -- $2)) COMPREPLY=($(compgen -fd -W "-h --help --tutor -V --version -v -vv -vvv --health -g --grammar --vsplit --hsplit -c --config" -- $2))
;; ;;
esac esac
} && complete -F _hx hx } && complete -F _hx hx

@ -0,0 +1,49 @@
# You can move it here ~/.config/elvish/lib/hx.elv
# Or add `eval (slurp < ~/$REPOS/helix/contrib/completion/hx.elv)`
# Be sure to replace `$REPOS` with something that makes sense for you!
### Renders a pretty completion candidate
var candidate = { | _stem _desc |
edit:complex-candidate $_stem &display=(styled $_stem bold)(styled " "$_desc dim)
}
### These commands will invalidate further input (i.e. not react to them)
var skips = [ "--tutor" "--help" "--version" "-V" "--health" ]
### Grammar commands
var grammar = [ "--grammar" "-g" ]
### Config commands
var config = [ "--config" "-c" ]
### Set an arg-completer for the `hx` binary
set edit:completion:arg-completer[hx] = {|@args|
var n = (count $args)
if (>= $n 3) {
# Stop completions if passed arg will take presedence
# and invalidate further input
if (has-value $skips $args[-2]) {
return
}
# If the previous arg == --grammar, then only suggest:
if (has-value $grammar $args[-2]) {
$candidate "fetch" "Fetch the tree-sitter grammars"
$candidate "build" "Build the tree-sitter grammars"
return
}
# When we have --config, we need a file
if (has-values $config $args[-2]) {
edit:complete-filename $args[-1] | each { |v| put $v[stem] }
return
}
}
edit:complete-filename $args[-1] | each { |v| put $v[stem]}
$candidate "--help" "(Prints help information)"
$candidate "--version" "(Prints version information)"
$candidate "--tutor" "(Loads the tutorial)"
$candidate "--health" "(Checks for errors in editor setup)"
$candidate "--grammar" "(Fetch or build the tree-sitter grammars)"
$candidate "--vsplit" "(Splits all given files vertically)"
$candidate "--hsplit" "(Splits all given files horizontally)"
$candidate "--config" "(Specifies a file to use for configuration)"
}

@ -11,3 +11,4 @@ complete -c hx -s v -o vv -o vvv -d "Increases logging verbosity"
complete -c hx -s V -l version -d "Prints version information" complete -c hx -s V -l version -d "Prints version information"
complete -c hx -l vsplit -d "Splits all given files vertically into different windows" complete -c hx -l vsplit -d "Splits all given files vertically into different windows"
complete -c hx -l hsplit -d "Splits all given files horizontally into different windows" complete -c hx -l hsplit -d "Splits all given files horizontally into different windows"
complete -c hx -s c -l config -d "Specifies a file to use for completion"

@ -16,6 +16,8 @@ _hx() {
"--grammar[Fetches or builds tree-sitter grammars]:action:->grammar" \ "--grammar[Fetches or builds tree-sitter grammars]:action:->grammar" \
"--vsplit[Splits all given files vertically into different windows]" \ "--vsplit[Splits all given files vertically into different windows]" \
"--hsplit[Splits all given files horizontally into different windows]" \ "--hsplit[Splits all given files horizontally into different windows]" \
"-c[Specifies a file to use for configuration]" \
"--config[Specifies a file to use for configuration]" \
"*:file:_files" "*:file:_files"
case "$state" in case "$state" in

@ -30,15 +30,16 @@ inside the project. We use [xtask][xtask] as an ad-hoc task runner and
thus do not require any dependencies other than `cargo` (You don't have thus do not require any dependencies other than `cargo` (You don't have
to `cargo install` anything either). to `cargo install` anything either).
[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
[architecture.md]: ./architecture.md
[docs]: https://docs.helix-editor.com/
[xtask]: https://github.com/matklad/cargo-xtask
# Integration tests # Integration tests
Integration tests for helix-term can be run with `cargo integration-test`. Code Integration tests for helix-term can be run with `cargo integration-test`. Code
contributors are strongly encouraged to write integration tests for their code. contributors are strongly encouraged to write integration tests for their code.
Existing tests can be used as examples. Helpers can be found in Existing tests can be used as examples. Helpers can be found in
[helpers.rs][../helix-term/tests/test/helpers.rs]. [helpers.rs][helpers.rs]
[good-first-issue]: https://github.com/helix-editor/helix/labels/E-easy
[log-file]: https://github.com/helix-editor/helix/wiki/FAQ#access-the-log-file
[architecture.md]: ./architecture.md
[docs]: https://docs.helix-editor.com/
[xtask]: https://github.com/matklad/cargo-xtask
[helpers.rs]: ../helix-term/tests/test/helpers.rs

@ -18,83 +18,117 @@
nixpkgs, nixpkgs,
nixCargoIntegration, nixCargoIntegration,
... ...
}: }: let
nixCargoIntegration.lib.makeOutputs { outputs = config:
root = ./.; nixCargoIntegration.lib.makeOutputs {
renameOutputs = {"helix-term" = "helix";}; root = ./.;
# Set default app to hx (binary is from helix-term release build) renameOutputs = {"helix-term" = "helix";};
# Set default package to helix-term release build # Set default app to hx (binary is from helix-term release build)
defaultOutputs = { # Set default package to helix-term release build
app = "hx"; defaultOutputs = {
package = "helix"; app = "hx";
}; package = "helix";
overrides = { };
cCompiler = common: overrides = {
with common.pkgs; cCompiler = common:
if stdenv.isLinux with common.pkgs;
then gcc if stdenv.isLinux
else clang; then gcc
crateOverrides = common: _: { else clang;
helix-term = prev: let crateOverrides = common: _: {
inherit (common) pkgs; helix-term = prev: let
mkRootPath = rel: inherit (common) pkgs;
builtins.path { mkRootPath = rel:
path = "${common.root}/${rel}"; builtins.path {
name = rel; path = "${common.root}/${rel}";
}; name = rel;
grammars = pkgs.callPackage ./grammars.nix {}; };
runtimeDir = pkgs.runCommandNoCC "helix-runtime" {} '' grammars = pkgs.callPackage ./grammars.nix config;
mkdir -p $out runtimeDir = pkgs.runCommandNoCC "helix-runtime" {} ''
ln -s ${mkRootPath "runtime"}/* $out mkdir -p $out
rm -r $out/grammars ln -s ${mkRootPath "runtime"}/* $out
ln -s ${grammars} $out/grammars rm -r $out/grammars
''; ln -s ${grammars} $out/grammars
in { '';
# disable fetching and building of tree-sitter grammars in the helix-term build.rs overridedAttrs = {
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1"; # disable fetching and building of tree-sitter grammars in the helix-term build.rs
# link languages and theme toml files since helix-term expects them (for tests) HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
preConfigure = # link languages and theme toml files since helix-term expects them (for tests)
pkgs.lib.concatMapStringsSep preConfigure =
"\n" pkgs.lib.concatMapStringsSep
(path: "ln -sf ${mkRootPath path} ..") "\n"
["languages.toml" "theme.toml" "base16_theme.toml"]; (path: "ln -sf ${mkRootPath path} ..")
buildInputs = (prev.buildInputs or []) ++ [common.cCompiler.cc.lib]; ["languages.toml" "theme.toml" "base16_theme.toml"];
nativeBuildInputs = [pkgs.makeWrapper]; buildInputs = (prev.buildInputs or []) ++ [common.cCompiler.cc.lib];
nativeBuildInputs = [pkgs.makeWrapper];
postFixup = '' postFixup = ''
if [ -f "$out/bin/hx" ]; then if [ -f "$out/bin/hx" ]; then
wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}" wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
fi fi
''; '';
};
in
overridedAttrs
// (
pkgs.lib.optionalAttrs
(config ? makeWrapperArgs)
{inherit (config) makeWrapperArgs;}
);
};
shell = common: prev: {
packages =
prev.packages
++ (
with common.pkgs;
[lld_13 lldb cargo-flamegraph rust-analyzer] ++
(lib.optional (stdenv.isx86_64 && stdenv.isLinux) cargo-tarpaulin)
);
env =
prev.env
++ [
{
name = "HELIX_RUNTIME";
eval = "$PWD/runtime";
}
{
name = "RUST_BACKTRACE";
value = "1";
}
{
name = "RUSTFLAGS";
value =
if common.pkgs.stdenv.isLinux
then "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment"
else "";
}
];
}; };
}; };
shell = common: prev: {
packages =
prev.packages
++ (
with common.pkgs; [lld_13 lldb cargo-tarpaulin cargo-flamegraph rust-analyzer]
);
env =
prev.env
++ [
{
name = "HELIX_RUNTIME";
eval = "$PWD/runtime";
}
{
name = "RUST_BACKTRACE";
value = "1";
}
{
name = "RUSTFLAGS";
value =
if common.pkgs.stdenv.isLinux
then "-C link-arg=-fuse-ld=lld -C target-cpu=native -Clink-arg=-Wl,--no-rosegment"
else "";
}
];
};
}; };
defaultOutputs = outputs {};
makeOverridableHelix = system: old:
old
// {
override = args:
makeOverridableHelix
system
(outputs args).packages.${system}.helix;
};
in
defaultOutputs
// {
packages =
nixpkgs.lib.mapAttrs
(
system: packages:
packages
// rec {
default = helix;
helix = makeOverridableHelix system packages.helix;
}
)
defaultOutputs.packages;
}; };
nixConfig = { nixConfig = {

@ -4,6 +4,8 @@
runCommandLocal, runCommandLocal,
runCommandNoCC, runCommandNoCC,
yj, yj,
includeGrammarIf ? _: true,
...
}: let }: let
# HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON # HACK: nix < 2.6 has a bug in the toml parser, so we convert to JSON
# before parsing # before parsing
@ -102,12 +104,13 @@
runHook postFixup runHook postFixup
''; '';
}; };
grammarsToBuild = builtins.filter includeGrammarIf gitGrammars;
builtGrammars = builtGrammars =
builtins.map (grammar: { builtins.map (grammar: {
inherit (grammar) name; inherit (grammar) name;
artifact = buildGrammar grammar; artifact = buildGrammar grammar;
}) })
gitGrammars; grammarsToBuild;
grammarLinks = grammarLinks =
builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so") builtins.map (grammar: "ln -s ${grammar.artifact}/${grammar.name}.so $out/${grammar.name}.so")
builtGrammars; builtGrammars;

@ -35,7 +35,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
toml = "0.5" toml = "0.5"
similar = "2.1" similar = "2.2"
encoding_rs = "0.8" encoding_rs = "0.8"

@ -23,6 +23,12 @@ pub struct Range {
pub end: usize, pub end: usize,
} }
#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
pub enum NumberOrString {
Number(i32),
String(String),
}
/// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) /// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html)
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Diagnostic { pub struct Diagnostic {
@ -30,4 +36,5 @@ pub struct Diagnostic {
pub line: usize, pub line: usize,
pub message: String, pub message: String,
pub severity: Option<Severity>, pub severity: Option<Severity>,
pub code: Option<NumberOrString>,
} }

@ -5,6 +5,7 @@ use ropey::RopeSlice;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp; use std::cmp;
use std::fmt::Write;
use super::Increment; use super::Increment;
use crate::{Range, Tendril}; use crate::{Range, Tendril};
@ -162,7 +163,7 @@ impl Format {
fields.push(field); fields.push(field);
max_len += field.max_len + remaining[..i].len(); max_len += field.max_len + remaining[..i].len();
regex += &remaining[..i]; regex += &remaining[..i];
regex += &format!("({})", field.regex); write!(regex, "({})", field.regex).unwrap();
remaining = &after[spec_len..]; remaining = &after[spec_len..];
} }

@ -305,8 +305,17 @@ mod line_ending_tests {
fn line_end_char_index_rope_slice() { fn line_end_char_index_rope_slice() {
let r = Rope::from_str("Hello\rworld\nhow\r\nare you?"); let r = Rope::from_str("Hello\rworld\nhow\r\nare you?");
let s = &r.slice(..); let s = &r.slice(..);
assert_eq!(line_end_char_index(s, 0), 11); #[cfg(not(feature = "unicode-lines"))]
assert_eq!(line_end_char_index(s, 1), 15); {
assert_eq!(line_end_char_index(s, 2), 25); assert_eq!(line_end_char_index(s, 0), 11);
assert_eq!(line_end_char_index(s, 1), 15);
assert_eq!(line_end_char_index(s, 2), 25);
}
#[cfg(feature = "unicode-lines")]
{
assert_eq!(line_end_char_index(s, 0), 5);
assert_eq!(line_end_char_index(s, 1), 11);
assert_eq!(line_end_char_index(s, 2), 15);
}
} }
} }

@ -222,9 +222,23 @@ impl Range {
// groupAt // groupAt
/// Returns the text inside this range given the text of the whole buffer.
///
/// The returned `Cow` is a reference if the range of text is inside a single
/// chunk of the rope. Otherwise a copy of the text is returned. Consider
/// using `slice` instead if you do not need a `Cow` or `String` to avoid copying.
#[inline] #[inline]
pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> { pub fn fragment<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> Cow<'b, str> {
text.slice(self.from()..self.to()).into() self.slice(text).into()
}
/// Returns the text inside this range given the text of the whole buffer.
///
/// The returned value is a reference to the passed slice. This method never
/// copies any contents.
#[inline]
pub fn slice<'a, 'b: 'a>(&'a self, text: RopeSlice<'b>) -> RopeSlice<'b> {
text.slice(self.from()..self.to())
} }
//-------------------------------- //--------------------------------
@ -548,6 +562,10 @@ impl Selection {
self.ranges.iter().map(move |range| range.fragment(text)) self.ranges.iter().map(move |range| range.fragment(text))
} }
pub fn slices<'a>(&'a self, text: RopeSlice<'a>) -> impl Iterator<Item = RopeSlice> + 'a {
self.ranges.iter().map(move |range| range.slice(text))
}
#[inline(always)] #[inline(always)]
pub fn iter(&self) -> std::slice::Iter<'_, Range> { pub fn iter(&self) -> std::slice::Iter<'_, Range> {
self.ranges.iter() self.ranges.iter()

@ -79,6 +79,9 @@ pub struct LanguageConfiguration {
#[serde(default)] #[serde(default)]
pub auto_format: bool, pub auto_format: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub formatter: Option<FormatterConfiguration>,
#[serde(default)] #[serde(default)]
pub diagnostic_severity: Severity, pub diagnostic_severity: Severity,
@ -126,6 +129,15 @@ pub struct LanguageServerConfiguration {
pub language_id: Option<String>, pub language_id: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FormatterConfiguration {
pub command: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>,
}
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub struct AdvancedCompletion { pub struct AdvancedCompletion {
@ -752,7 +764,7 @@ impl Syntax {
); );
let mut injections = Vec::new(); let mut injections = Vec::new();
for mat in matches { for mat in matches {
let (language_name, content_node, include_children) = injection_for_match( let (language_name, content_node, included_children) = injection_for_match(
&layer.config, &layer.config,
&layer.config.injections_query, &layer.config.injections_query,
&mat, &mat,
@ -769,7 +781,7 @@ impl Syntax {
{ {
if let Some(config) = (injection_callback)(&language_name) { if let Some(config) = (injection_callback)(&language_name) {
let ranges = let ranges =
intersect_ranges(&layer.ranges, &[content_node], include_children); intersect_ranges(&layer.ranges, &[content_node], included_children);
if !ranges.is_empty() { if !ranges.is_empty() {
injections.push((config, ranges)); injections.push((config, ranges));
@ -781,7 +793,10 @@ impl Syntax {
// Process combined injections. // Process combined injections.
if let Some(combined_injections_query) = &layer.config.combined_injections_query { if let Some(combined_injections_query) = &layer.config.combined_injections_query {
let mut injections_by_pattern_index = let mut injections_by_pattern_index =
vec![(None, Vec::new(), false); combined_injections_query.pattern_count()]; vec![
(None, Vec::new(), IncludedChildren::default());
combined_injections_query.pattern_count()
];
let matches = cursor.matches( let matches = cursor.matches(
combined_injections_query, combined_injections_query,
layer.tree().root_node(), layer.tree().root_node(),
@ -789,7 +804,7 @@ impl Syntax {
); );
for mat in matches { for mat in matches {
let entry = &mut injections_by_pattern_index[mat.pattern_index]; let entry = &mut injections_by_pattern_index[mat.pattern_index];
let (language_name, content_node, include_children) = injection_for_match( let (language_name, content_node, included_children) = injection_for_match(
&layer.config, &layer.config,
combined_injections_query, combined_injections_query,
&mat, &mat,
@ -801,16 +816,16 @@ impl Syntax {
if let Some(content_node) = content_node { if let Some(content_node) = content_node {
entry.1.push(content_node); entry.1.push(content_node);
} }
entry.2 = include_children; entry.2 = included_children;
} }
for (lang_name, content_nodes, includes_children) in injections_by_pattern_index for (lang_name, content_nodes, included_children) in injections_by_pattern_index
{ {
if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) { if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) {
if let Some(config) = (injection_callback)(&lang_name) { if let Some(config) = (injection_callback)(&lang_name) {
let ranges = intersect_ranges( let ranges = intersect_ranges(
&layer.ranges, &layer.ranges,
&content_nodes, &content_nodes,
includes_children, included_children,
); );
if !ranges.is_empty() { if !ranges.is_empty() {
injections.push((config, ranges)); injections.push((config, ranges));
@ -1341,8 +1356,8 @@ impl HighlightConfiguration {
/// Tree-sitter syntax-highlighting queries specify highlights in the form of dot-separated /// Tree-sitter syntax-highlighting queries specify highlights in the form of dot-separated
/// highlight names like `punctuation.bracket` and `function.method.builtin`. Consumers of /// highlight names like `punctuation.bracket` and `function.method.builtin`. Consumers of
/// these queries can choose to recognize highlights with different levels of specificity. /// these queries can choose to recognize highlights with different levels of specificity.
/// For example, the string `function.builtin` will match against `function.method.builtin` /// For example, the string `function.builtin` will match against `function.builtin.constructor`
/// and `function.builtin.constructor`, but will not match `function.method`. /// but will not match `function.method.builtin` and `function.method`.
/// ///
/// When highlighting, results are returned as `Highlight` values, which contain the index /// When highlighting, results are returned as `Highlight` values, which contain the index
/// of the matched highlight this list of highlight names. /// of the matched highlight this list of highlight names.
@ -1362,11 +1377,13 @@ impl HighlightConfiguration {
let recognized_name = recognized_name; let recognized_name = recognized_name;
let mut len = 0; let mut len = 0;
let mut matches = true; let mut matches = true;
for part in recognized_name.split('.') { for (i, part) in recognized_name.split('.').enumerate() {
len += 1; match capture_parts.get(i) {
if !capture_parts.contains(&part) { Some(capture_part) if *capture_part == part => len += 1,
matches = false; _ => {
break; matches = false;
break;
}
} }
} }
if matches && len > best_match_len { if matches && len > best_match_len {
@ -1408,6 +1425,19 @@ impl<'a> HighlightIterLayer<'a> {
} }
} }
#[derive(Clone)]
enum IncludedChildren {
None,
All,
Unnamed,
}
impl Default for IncludedChildren {
fn default() -> Self {
Self::None
}
}
// Compute the ranges that should be included when parsing an injection. // Compute the ranges that should be included when parsing an injection.
// This takes into account three things: // This takes into account three things:
// * `parent_ranges` - The ranges must all fall within the *current* layer's ranges. // * `parent_ranges` - The ranges must all fall within the *current* layer's ranges.
@ -1420,7 +1450,7 @@ impl<'a> HighlightIterLayer<'a> {
fn intersect_ranges( fn intersect_ranges(
parent_ranges: &[Range], parent_ranges: &[Range],
nodes: &[Node], nodes: &[Node],
includes_children: bool, included_children: IncludedChildren,
) -> Vec<Range> { ) -> Vec<Range> {
let mut cursor = nodes[0].walk(); let mut cursor = nodes[0].walk();
let mut result = Vec::new(); let mut result = Vec::new();
@ -1444,11 +1474,15 @@ fn intersect_ranges(
for excluded_range in node for excluded_range in node
.children(&mut cursor) .children(&mut cursor)
.filter_map(|child| { .filter_map(|child| match included_children {
if includes_children { IncludedChildren::None => Some(child.range()),
None IncludedChildren::All => None,
} else { IncludedChildren::Unnamed => {
Some(child.range()) if child.is_named() {
Some(child.range())
} else {
None
}
} }
}) })
.chain([following_range].iter().cloned()) .chain([following_range].iter().cloned())
@ -1777,7 +1811,7 @@ fn injection_for_match<'a>(
query: &'a Query, query: &'a Query,
query_match: &QueryMatch<'a, 'a>, query_match: &QueryMatch<'a, 'a>,
source: RopeSlice<'a>, source: RopeSlice<'a>,
) -> (Option<Cow<'a, str>>, Option<Node<'a>>, bool) { ) -> (Option<Cow<'a, str>>, Option<Node<'a>>, IncludedChildren) {
let content_capture_index = config.injection_content_capture_index; let content_capture_index = config.injection_content_capture_index;
let language_capture_index = config.injection_language_capture_index; let language_capture_index = config.injection_language_capture_index;
@ -1793,7 +1827,7 @@ fn injection_for_match<'a>(
} }
} }
let mut include_children = false; let mut included_children = IncludedChildren::default();
for prop in query.property_settings(query_match.pattern_index) { for prop in query.property_settings(query_match.pattern_index) {
match prop.key.as_ref() { match prop.key.as_ref() {
// In addition to specifying the language name via the text of a // In addition to specifying the language name via the text of a
@ -1809,12 +1843,17 @@ fn injection_for_match<'a>(
// `injection.content` node - only the ranges that belong to the // `injection.content` node - only the ranges that belong to the
// node itself. This can be changed using a `#set!` predicate that // node itself. This can be changed using a `#set!` predicate that
// sets the `injection.include-children` key. // sets the `injection.include-children` key.
"injection.include-children" => include_children = true, "injection.include-children" => included_children = IncludedChildren::All,
// Some queries might only exclude named children but include unnamed
// children in their `injection.content` node. This can be enabled using
// a `#set!` predicate that sets the `injection.include-unnamed-children` key.
"injection.include-unnamed-children" => included_children = IncludedChildren::Unnamed,
_ => {} _ => {}
} }
} }
(language_name, content_node, include_children) (language_name, content_node, included_children)
} }
pub struct Merge<I> { pub struct Merge<I> {

@ -198,26 +198,26 @@ pub fn textobject_paragraph(
Range::new(anchor, head) Range::new(anchor, head)
} }
pub fn textobject_surround( pub fn textobject_pair_surround(
slice: RopeSlice, slice: RopeSlice,
range: Range, range: Range,
textobject: TextObject, textobject: TextObject,
ch: char, ch: char,
count: usize, count: usize,
) -> Range { ) -> Range {
textobject_surround_impl(slice, range, textobject, Some(ch), count) textobject_pair_surround_impl(slice, range, textobject, Some(ch), count)
} }
pub fn textobject_surround_closest( pub fn textobject_pair_surround_closest(
slice: RopeSlice, slice: RopeSlice,
range: Range, range: Range,
textobject: TextObject, textobject: TextObject,
count: usize, count: usize,
) -> Range { ) -> Range {
textobject_surround_impl(slice, range, textobject, None, count) textobject_pair_surround_impl(slice, range, textobject, None, count)
} }
fn textobject_surround_impl( fn textobject_pair_surround_impl(
slice: RopeSlice, slice: RopeSlice,
range: Range, range: Range,
textobject: TextObject, textobject: TextObject,
@ -562,7 +562,7 @@ mod test {
let slice = doc.slice(..); let slice = doc.slice(..);
for &case in scenario { for &case in scenario {
let (pos, objtype, expected_range, ch, count) = case; let (pos, objtype, expected_range, ch, count) = case;
let result = textobject_surround(slice, Range::point(pos), objtype, ch, count); let result = textobject_pair_surround(slice, Range::point(pos), objtype, ch, count);
assert_eq!( assert_eq!(
result, result,
expected_range.into(), expected_range.into(),

@ -19,7 +19,23 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
.into_iter() .into_iter()
.chain([default_lang_config()].into_iter()) .chain([default_lang_config()].into_iter())
.fold(toml::Value::Table(toml::value::Table::default()), |a, b| { .fold(toml::Value::Table(toml::value::Table::default()), |a, b| {
crate::merge_toml_values(b, a, true) // combines for example
// b:
// [[language]]
// name = "toml"
// language-server = { command = "taplo", args = ["lsp", "stdio"] }
//
// a:
// [[language]]
// language-server = { command = "/usr/bin/taplo" }
//
// into:
// [[language]]
// name = "toml"
// language-server = { command = "/usr/bin/taplo" }
//
// thus it overrides the third depth-level of b with values of a if they exist, but otherwise merges their values
crate::merge_toml_values(b, a, 3)
}); });
Ok(config) Ok(config)

@ -89,11 +89,102 @@ pub fn fetch_grammars() -> Result<()> {
let mut grammars = get_grammar_configs()?; let mut grammars = get_grammar_configs()?;
grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. })); grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));
run_parallel(grammars, fetch_grammar, "fetch") println!("Fetching {} grammars", grammars.len());
let results = run_parallel(grammars, fetch_grammar);
let mut errors = Vec::new();
let mut git_updated = Vec::new();
let mut git_up_to_date = 0;
let mut non_git = Vec::new();
for res in results {
match res {
Ok(FetchStatus::GitUpToDate) => git_up_to_date += 1,
Ok(FetchStatus::GitUpdated {
grammar_id,
revision,
}) => git_updated.push((grammar_id, revision)),
Ok(FetchStatus::NonGit { grammar_id }) => non_git.push(grammar_id),
Err(e) => errors.push(e),
}
}
non_git.sort_unstable();
git_updated.sort_unstable_by(|a, b| a.0.cmp(&b.0));
if git_up_to_date != 0 {
println!("{} up to date git grammars", git_up_to_date);
}
if !non_git.is_empty() {
println!("{} non git grammars", non_git.len());
println!("\t{:?}", non_git);
}
if !git_updated.is_empty() {
println!("{} updated grammars", git_updated.len());
// We checked the vec is not empty, unwrapping will not panic
let longest_id = git_updated.iter().map(|x| x.0.len()).max().unwrap();
for (id, rev) in git_updated {
println!(
"\t{id:width$} now on {rev}",
id = id,
width = longest_id,
rev = rev
);
}
}
if !errors.is_empty() {
let len = errors.len();
println!("{} grammars failed to fetch", len);
for (i, error) in errors.into_iter().enumerate() {
println!("\tFailure {}/{}: {}", i, len, error);
}
}
Ok(())
} }
pub fn build_grammars() -> Result<()> { pub fn build_grammars(target: Option<String>) -> Result<()> {
run_parallel(get_grammar_configs()?, build_grammar, "build") let grammars = get_grammar_configs()?;
println!("Building {} grammars", grammars.len());
let results = run_parallel(grammars, move |grammar| {
build_grammar(grammar, target.as_deref())
});
let mut errors = Vec::new();
let mut already_built = 0;
let mut built = Vec::new();
for res in results {
match res {
Ok(BuildStatus::AlreadyBuilt) => already_built += 1,
Ok(BuildStatus::Built { grammar_id }) => built.push(grammar_id),
Err(e) => errors.push(e),
}
}
built.sort_unstable();
if already_built != 0 {
println!("{} grammars already built", already_built);
}
if !built.is_empty() {
println!("{} grammars built now", built.len());
println!("\t{:?}", built);
}
if !errors.is_empty() {
let len = errors.len();
println!("{} grammars failed to build", len);
for (i, error) in errors.into_iter().enumerate() {
println!("\tFailure {}/{}: {}", i, len, error);
}
}
Ok(())
} }
// Returns the set of grammar configurations the user requests. // Returns the set of grammar configurations the user requests.
@ -122,15 +213,17 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
Ok(grammars) Ok(grammars)
} }
fn run_parallel<F>(grammars: Vec<GrammarConfiguration>, job: F, action: &'static str) -> Result<()> fn run_parallel<F, Res>(grammars: Vec<GrammarConfiguration>, job: F) -> Vec<Result<Res>>
where where
F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Copy, F: Fn(GrammarConfiguration) -> Result<Res> + Send + 'static + Clone,
Res: Send + 'static,
{ {
let pool = threadpool::Builder::new().build(); let pool = threadpool::Builder::new().build();
let (tx, rx) = channel(); let (tx, rx) = channel();
for grammar in grammars { for grammar in grammars {
let tx = tx.clone(); let tx = tx.clone();
let job = job.clone();
pool.execute(move || { pool.execute(move || {
// Ignore any SendErrors, if any job in another thread has encountered an // Ignore any SendErrors, if any job in another thread has encountered an
@ -141,14 +234,21 @@ where
drop(tx); drop(tx);
// TODO: print all failures instead of the first one found. rx.iter().collect()
rx.iter()
.find(|result| result.is_err())
.map(|err| err.with_context(|| format!("Failed to {} some grammar(s)", action)))
.unwrap_or(Ok(()))
} }
fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> { enum FetchStatus {
GitUpToDate,
GitUpdated {
grammar_id: String,
revision: String,
},
NonGit {
grammar_id: String,
},
}
fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
if let GrammarSource::Git { if let GrammarSource::Git {
remote, revision, .. remote, revision, ..
} = grammar.source } = grammar.source
@ -184,16 +284,18 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
)?; )?;
git(&grammar_dir, ["checkout", &revision])?; git(&grammar_dir, ["checkout", &revision])?;
println!( Ok(FetchStatus::GitUpdated {
"Grammar '{}' checked out at '{}'.", grammar_id: grammar.grammar_id,
grammar.grammar_id, revision revision,
); })
} else { } else {
println!("Grammar '{}' is already up to date.", grammar.grammar_id); Ok(FetchStatus::GitUpToDate)
} }
} else {
Ok(FetchStatus::NonGit {
grammar_id: grammar.grammar_id,
})
} }
Ok(())
} }
// Sets the remote for a repository to the given URL, creating the remote if // Sets the remote for a repository to the given URL, creating the remote if
@ -240,7 +342,12 @@ where
} }
} }
fn build_grammar(grammar: GrammarConfiguration) -> Result<()> { enum BuildStatus {
AlreadyBuilt,
Built { grammar_id: String },
}
fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<BuildStatus> {
let grammar_dir = if let GrammarSource::Local { path } = &grammar.source { let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
PathBuf::from(&path) PathBuf::from(&path)
} else { } else {
@ -273,10 +380,14 @@ fn build_grammar(grammar: GrammarConfiguration) -> Result<()> {
} }
.join("src"); .join("src");
build_tree_sitter_library(&path, grammar) build_tree_sitter_library(&path, grammar, target)
} }
fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) -> Result<()> { fn build_tree_sitter_library(
src_path: &Path,
grammar: GrammarConfiguration,
target: Option<&str>,
) -> Result<BuildStatus> {
let header_path = src_path; let header_path = src_path;
let parser_path = src_path.join("parser.c"); let parser_path = src_path.join("parser.c");
let mut scanner_path = src_path.join("scanner.c"); let mut scanner_path = src_path.join("scanner.c");
@ -299,27 +410,25 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) ->
.context("Failed to compare source and binary timestamps")?; .context("Failed to compare source and binary timestamps")?;
if !recompile { if !recompile {
println!("Grammar '{}' is already built.", grammar.grammar_id); return Ok(BuildStatus::AlreadyBuilt);
return Ok(());
} }
println!("Building grammar '{}'", grammar.grammar_id);
let mut config = cc::Build::new(); let mut config = cc::Build::new();
config config
.cpp(true) .cpp(true)
.opt_level(3) .opt_level(3)
.cargo_metadata(false) .cargo_metadata(false)
.host(BUILD_TARGET) .host(BUILD_TARGET)
.target(BUILD_TARGET); .target(target.unwrap_or(BUILD_TARGET));
let compiler = config.get_compiler(); let compiler = config.get_compiler();
let mut command = Command::new(compiler.path()); let mut command = Command::new(compiler.path());
command.current_dir(src_path); command.current_dir(src_path);
for (key, value) in compiler.env() { for (key, value) in compiler.env() {
command.env(key, value); command.env(key, value);
} }
command.args(compiler.args());
if cfg!(windows) { if cfg!(all(windows, target_env = "msvc")) {
command command
.args(&["/nologo", "/LD", "/I"]) .args(&["/nologo", "/LD", "/I"])
.arg(header_path) .arg(header_path)
@ -371,7 +480,9 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) ->
)); ));
} }
Ok(()) Ok(BuildStatus::Built {
grammar_id: grammar.grammar_id,
})
} }
fn needs_recompile( fn needs_recompile(

@ -2,11 +2,28 @@ pub mod config;
pub mod grammar; pub mod grammar;
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
use std::path::PathBuf;
pub static RUNTIME_DIR: once_cell::sync::Lazy<std::path::PathBuf> = pub static RUNTIME_DIR: once_cell::sync::Lazy<PathBuf> = once_cell::sync::Lazy::new(runtime_dir);
once_cell::sync::Lazy::new(runtime_dir);
pub fn runtime_dir() -> std::path::PathBuf { static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
pub fn initialize_config_file(specified_file: Option<PathBuf>) {
let config_file = specified_file.unwrap_or_else(|| {
let config_dir = config_dir();
if !config_dir.exists() {
std::fs::create_dir_all(&config_dir).ok();
}
config_dir.join("config.toml")
});
// We should only initialize this value once.
CONFIG_FILE.set(config_file).ok();
}
pub fn runtime_dir() -> PathBuf {
if let Ok(dir) = std::env::var("HELIX_RUNTIME") { if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
return dir.into(); return dir.into();
} }
@ -31,7 +48,7 @@ pub fn runtime_dir() -> std::path::PathBuf {
.unwrap() .unwrap()
} }
pub fn config_dir() -> std::path::PathBuf { pub fn config_dir() -> PathBuf {
// TODO: allow env var override // TODO: allow env var override
let strategy = choose_base_strategy().expect("Unable to find the config directory!"); let strategy = choose_base_strategy().expect("Unable to find the config directory!");
let mut path = strategy.config_dir(); let mut path = strategy.config_dir();
@ -39,7 +56,7 @@ pub fn config_dir() -> std::path::PathBuf {
path path
} }
pub fn local_config_dirs() -> Vec<std::path::PathBuf> { pub fn local_config_dirs() -> Vec<PathBuf> {
let directories = find_root_impl(None, &[".helix".to_string()]) let directories = find_root_impl(None, &[".helix".to_string()])
.into_iter() .into_iter()
.map(|path| path.join(".helix")) .map(|path| path.join(".helix"))
@ -48,7 +65,7 @@ pub fn local_config_dirs() -> Vec<std::path::PathBuf> {
directories directories
} }
pub fn cache_dir() -> std::path::PathBuf { pub fn cache_dir() -> PathBuf {
// TODO: allow env var override // TODO: allow env var override
let strategy = choose_base_strategy().expect("Unable to find the config directory!"); let strategy = choose_base_strategy().expect("Unable to find the config directory!");
let mut path = strategy.cache_dir(); let mut path = strategy.cache_dir();
@ -56,19 +73,22 @@ pub fn cache_dir() -> std::path::PathBuf {
path path
} }
pub fn config_file() -> std::path::PathBuf { pub fn config_file() -> PathBuf {
config_dir().join("config.toml") CONFIG_FILE
.get()
.map(|path| path.to_path_buf())
.unwrap_or_else(|| config_dir().join("config.toml"))
} }
pub fn lang_config_file() -> std::path::PathBuf { pub fn lang_config_file() -> PathBuf {
config_dir().join("languages.toml") config_dir().join("languages.toml")
} }
pub fn log_file() -> std::path::PathBuf { pub fn log_file() -> PathBuf {
cache_dir().join("helix.log") cache_dir().join("helix.log")
} }
pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::path::PathBuf> { pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<PathBuf> {
let current_dir = std::env::current_dir().expect("unable to determine current directory"); let current_dir = std::env::current_dir().expect("unable to determine current directory");
let mut directories = Vec::new(); let mut directories = Vec::new();
@ -113,11 +133,7 @@ pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::p
/// documents that use a top-level array of values like the `languages.toml`, /// documents that use a top-level array of values like the `languages.toml`,
/// where one usually wants to override or add to the array instead of /// where one usually wants to override or add to the array instead of
/// replacing it altogether. /// replacing it altogether.
pub fn merge_toml_values( pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usize) -> toml::Value {
left: toml::Value,
right: toml::Value,
merge_toplevel_arrays: bool,
) -> toml::Value {
use toml::Value; use toml::Value;
fn get_name(v: &Value) -> Option<&str> { fn get_name(v: &Value) -> Option<&str> {
@ -131,7 +147,7 @@ pub fn merge_toml_values(
// that you can specify a sub-set of languages in an overriding // that you can specify a sub-set of languages in an overriding
// `languages.toml` but that nested arrays like Language Server // `languages.toml` but that nested arrays like Language Server
// arguments are replaced instead of merged. // arguments are replaced instead of merged.
if merge_toplevel_arrays { if merge_depth > 0 {
left_items.reserve(right_items.len()); left_items.reserve(right_items.len());
for rvalue in right_items { for rvalue in right_items {
let lvalue = get_name(&rvalue) let lvalue = get_name(&rvalue)
@ -140,7 +156,7 @@ pub fn merge_toml_values(
}) })
.map(|lpos| left_items.remove(lpos)); .map(|lpos| left_items.remove(lpos));
let mvalue = match lvalue { let mvalue = match lvalue {
Some(lvalue) => merge_toml_values(lvalue, rvalue, false), Some(lvalue) => merge_toml_values(lvalue, rvalue, merge_depth - 1),
None => rvalue, None => rvalue,
}; };
left_items.push(mvalue); left_items.push(mvalue);
@ -151,18 +167,22 @@ pub fn merge_toml_values(
} }
} }
(Value::Table(mut left_map), Value::Table(right_map)) => { (Value::Table(mut left_map), Value::Table(right_map)) => {
for (rname, rvalue) in right_map { if merge_depth > 0 {
match left_map.remove(&rname) { for (rname, rvalue) in right_map {
Some(lvalue) => { match left_map.remove(&rname) {
let merged_value = merge_toml_values(lvalue, rvalue, merge_toplevel_arrays); Some(lvalue) => {
left_map.insert(rname, merged_value); let merged_value = merge_toml_values(lvalue, rvalue, merge_depth - 1);
} left_map.insert(rname, merged_value);
None => { }
left_map.insert(rname, rvalue); None => {
left_map.insert(rname, rvalue);
}
} }
} }
Value::Table(left_map)
} else {
Value::Table(right_map)
} }
Value::Table(left_map)
} }
// Catch everything else we didn't handle, and use the right value // Catch everything else we didn't handle, and use the right value
(_, value) => value, (_, value) => value,
@ -187,7 +207,7 @@ mod merge_toml_tests {
.expect("Couldn't parse built-in languages config"); .expect("Couldn't parse built-in languages config");
let user: Value = toml::from_str(USER).unwrap(); let user: Value = toml::from_str(USER).unwrap();
let merged = merge_toml_values(base, user, true); let merged = merge_toml_values(base, user, 3);
let languages = merged.get("language").unwrap().as_array().unwrap(); let languages = merged.get("language").unwrap().as_array().unwrap();
let nix = languages let nix = languages
.iter() .iter()
@ -220,7 +240,7 @@ mod merge_toml_tests {
.expect("Couldn't parse built-in languages config"); .expect("Couldn't parse built-in languages config");
let user: Value = toml::from_str(USER).unwrap(); let user: Value = toml::from_str(USER).unwrap();
let merged = merge_toml_values(base, user, true); let merged = merge_toml_values(base, user, 3);
let languages = merged.get("language").unwrap().as_array().unwrap(); let languages = merged.get("language").unwrap().as_array().unwrap();
let ts = languages let ts = languages
.iter() .iter()

@ -295,6 +295,10 @@ impl Client {
}), }),
workspace_folders: Some(true), workspace_folders: Some(true),
apply_edit: Some(true), apply_edit: Some(true),
symbol: Some(lsp::WorkspaceSymbolClientCapabilities {
dynamic_registration: Some(false),
..Default::default()
}),
..Default::default() ..Default::default()
}), }),
text_document: Some(lsp::TextDocumentClientCapabilities { text_document: Some(lsp::TextDocumentClientCapabilities {

@ -58,7 +58,7 @@ pub enum OffsetEncoding {
pub mod util { pub mod util {
use super::*; use super::*;
use helix_core::{Range, Rope, Transaction}; use helix_core::{diagnostic::NumberOrString, Range, Rope, Transaction};
/// Converts a diagnostic in the document to [`lsp::Diagnostic`]. /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
/// ///
@ -78,11 +78,19 @@ pub mod util {
Error => lsp::DiagnosticSeverity::ERROR, Error => lsp::DiagnosticSeverity::ERROR,
}); });
let code = match diag.code.clone() {
Some(x) => match x {
NumberOrString::Number(x) => Some(lsp::NumberOrString::Number(x)),
NumberOrString::String(x) => Some(lsp::NumberOrString::String(x)),
},
None => None,
};
// TODO: add support for Diagnostic.data // TODO: add support for Diagnostic.data
lsp::Diagnostic::new( lsp::Diagnostic::new(
range_to_lsp_range(doc, range, offset_encoding), range_to_lsp_range(doc, range, offset_encoding),
severity, severity,
None, code,
None, None,
diag.message.to_owned(), diag.message.to_owned(),
None, None,
@ -205,22 +213,6 @@ pub mod util {
}), }),
) )
} }
/// The result of asking the language server to format the document. This can be turned into a
/// `Transaction`, but the advantage of not doing that straight away is that this one is
/// `Send` and `Sync`.
#[derive(Clone, Debug)]
pub struct LspFormatting {
pub doc: Rope,
pub edits: Vec<lsp::TextEdit>,
pub offset_encoding: OffsetEncoding,
}
impl From<LspFormatting> for Transaction {
fn from(fmt: LspFormatting) -> Transaction {
generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding)
}
}
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]

@ -10,6 +10,7 @@ repository = "https://github.com/helix-editor/helix"
homepage = "https://helix-editor.com" homepage = "https://helix-editor.com"
include = ["src/**/*", "README.md"] include = ["src/**/*", "README.md"]
default-run = "hx" default-run = "hx"
rust-version = "1.57"
[package.metadata.nix] [package.metadata.nix]
build = true build = true
@ -37,11 +38,11 @@ which = "4.2"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
crossterm = { version = "0.24", features = ["event-stream"] } crossterm = { version = "0.25", features = ["event-stream"] }
signal-hook = "0.3" signal-hook = "0.3"
tokio-stream = "0.1" tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
arc-swap = { version = "1.5.0" } arc-swap = { version = "1.5.1" }
# Logging # Logging
fern = "0.6" fern = "0.6"

@ -19,7 +19,8 @@ fn main() {
if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() { if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() {
fetch_grammars().expect("Failed to fetch tree-sitter grammars"); fetch_grammars().expect("Failed to fetch tree-sitter grammars");
build_grammars().expect("Failed to compile tree-sitter grammars"); build_grammars(Some(std::env::var("TARGET").unwrap()))
.expect("Failed to compile tree-sitter grammars");
} }
println!("cargo:rerun-if-changed=../runtime/grammars/"); println!("cargo:rerun-if-changed=../runtime/grammars/");

@ -2,6 +2,7 @@ use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream; use futures_util::Stream;
use helix_core::{ use helix_core::{
config::{default_syntax_loader, user_syntax_loader}, config::{default_syntax_loader, user_syntax_loader},
diagnostic::NumberOrString,
pos_at_coords, syntax, Selection, pos_at_coords, syntax, Selection,
}; };
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
@ -11,7 +12,7 @@ use serde_json::json;
use crate::{ use crate::{
args::Args, args::Args,
commands::apply_workspace_edit, commands::apply_workspace_edit,
compositor::Compositor, compositor::{Compositor, Event},
config::Config, config::Config,
job::Jobs, job::Jobs,
keymap::Keymaps, keymap::Keymaps,
@ -28,7 +29,7 @@ use std::{
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use crossterm::{ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event}, event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent},
execute, terminal, execute, terminal,
tty::IsTty, tty::IsTty,
}; };
@ -88,9 +89,8 @@ impl Application {
use helix_view::editor::Action; use helix_view::editor::Action;
let config_dir = helix_loader::config_dir();
let theme_loader = std::sync::Arc::new(theme::Loader::new( let theme_loader = std::sync::Arc::new(theme::Loader::new(
&config_dir, &helix_loader::config_dir(),
&helix_loader::runtime_dir(), &helix_loader::runtime_dir(),
)); ));
@ -418,7 +418,7 @@ impl Application {
} }
} }
pub fn handle_terminal_events(&mut self, event: Result<Event, crossterm::ErrorKind>) { pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
let mut cx = crate::compositor::Context { let mut cx = crate::compositor::Context {
editor: &mut self.editor, editor: &mut self.editor,
jobs: &mut self.jobs, jobs: &mut self.jobs,
@ -426,13 +426,12 @@ impl Application {
}; };
// Handle key events // Handle key events
let should_redraw = match event { let should_redraw = match event {
Ok(Event::Resize(width, height)) => { Ok(CrosstermEvent::Resize(width, height)) => {
self.compositor.resize(width, height); self.compositor.resize(width, height);
self.compositor self.compositor
.handle_event(Event::Resize(width, height), &mut cx) .handle_event(Event::Resize(width, height), &mut cx)
} }
Ok(event) => self.compositor.handle_event(event, &mut cx), Ok(event) => self.compositor.handle_event(event.into(), &mut cx),
Err(x) => panic!("{}", x), Err(x) => panic!("{}", x),
}; };
@ -556,12 +555,24 @@ impl Application {
} }
}; };
let code = match diagnostic.code.clone() {
Some(x) => match x {
lsp::NumberOrString::Number(x) => {
Some(NumberOrString::Number(x))
}
lsp::NumberOrString::String(x) => {
Some(NumberOrString::String(x))
}
},
None => None,
};
Some(Diagnostic { Some(Diagnostic {
range: Range { start, end }, range: Range { start, end },
line: diagnostic.range.start.line as usize, line: diagnostic.range.start.line as usize,
message: diagnostic.message.clone(), message: diagnostic.message.clone(),
severity, severity,
// code code,
// source // source
}) })
}) })
@ -788,7 +799,7 @@ impl Application {
fn restore_term(&mut self) -> Result<(), Error> { fn restore_term(&mut self) -> Result<(), Error> {
let mut stdout = stdout(); let mut stdout = stdout();
// reset cursor shape // reset cursor shape
write!(stdout, "\x1B[2 q")?; write!(stdout, "\x1B[0 q")?;
// Ignore errors on disabling, this might trigger on windows if we call // Ignore errors on disabling, this might trigger on windows if we call
// disable without calling enable previously // disable without calling enable previously
let _ = execute!(stdout, DisableMouseCapture); let _ = execute!(stdout, DisableMouseCapture);

@ -14,6 +14,7 @@ pub struct Args {
pub build_grammars: bool, pub build_grammars: bool,
pub split: Option<Layout>, pub split: Option<Layout>,
pub verbosity: u64, pub verbosity: u64,
pub config_file: Option<PathBuf>,
pub files: Vec<(PathBuf, Position)>, pub files: Vec<(PathBuf, Position)>,
} }
@ -43,6 +44,10 @@ impl Args {
anyhow::bail!("--grammar must be followed by either 'fetch' or 'build'") anyhow::bail!("--grammar must be followed by either 'fetch' or 'build'")
} }
}, },
"-c" | "--config" => match argv.next().as_deref() {
Some(path) => args.config_file = Some(path.into()),
None => anyhow::bail!("--config must specify a path to read"),
},
arg if arg.starts_with("--") => { arg if arg.starts_with("--") => {
anyhow::bail!("unexpected double dash argument: {}", arg) anyhow::bail!("unexpected double dash argument: {}", arg)
} }

@ -28,11 +28,12 @@ use helix_core::{
}; };
use helix_view::{ use helix_view::{
clipboard::ClipboardType, clipboard::ClipboardType,
document::{Mode, SCRATCH_BUFFER_NAME}, document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion}, editor::{Action, Motion},
info::Info, info::Info,
input::KeyEvent, input::KeyEvent,
keyboard::KeyCode, keyboard::KeyCode,
tree,
view::View, view::View,
Document, DocumentId, Editor, ViewId, Document, DocumentId, Editor, ViewId,
}; };
@ -204,17 +205,17 @@ impl MappableCommand {
extend_line_down, "Extend down", extend_line_down, "Extend down",
copy_selection_on_next_line, "Copy selection on next line", copy_selection_on_next_line, "Copy selection on next line",
copy_selection_on_prev_line, "Copy selection on previous line", copy_selection_on_prev_line, "Copy selection on previous line",
move_next_word_start, "Move to beginning of next word", move_next_word_start, "Move to start of next word",
move_prev_word_start, "Move to beginning of previous word", move_prev_word_start, "Move to start of previous word",
move_prev_word_end, "Move to end of previous word", move_prev_word_end, "Move to end of previous word",
move_next_word_end, "Move to end of next word", move_next_word_end, "Move to end of next word",
move_next_long_word_start, "Move to beginning of next long word", move_next_long_word_start, "Move to start of next long word",
move_prev_long_word_start, "Move to beginning of previous long word", move_prev_long_word_start, "Move to start of previous long word",
move_next_long_word_end, "Move to end of next long word", move_next_long_word_end, "Move to end of next long word",
extend_next_word_start, "Extend to beginning of next word", extend_next_word_start, "Extend to start of next word",
extend_prev_word_start, "Extend to beginning of previous word", extend_prev_word_start, "Extend to start of previous word",
extend_next_long_word_start, "Extend to beginning of next long word", extend_next_long_word_start, "Extend to start of next long word",
extend_prev_long_word_start, "Extend to beginning of previous long word", extend_prev_long_word_start, "Extend to start of previous long word",
extend_next_long_word_end, "Extend to end of next long word", extend_next_long_word_end, "Extend to end of next long word",
extend_next_word_end, "Extend to end of next word", extend_next_word_end, "Extend to end of next word",
find_till_char, "Move till next occurrence of char", find_till_char, "Move till next occurrence of char",
@ -225,7 +226,7 @@ impl MappableCommand {
find_prev_char, "Move to previous occurrence of char", find_prev_char, "Move to previous occurrence of char",
extend_till_prev_char, "Extend till previous occurrence of char", extend_till_prev_char, "Extend till previous occurrence of char",
extend_prev_char, "Extend to previous occurrence of char", extend_prev_char, "Extend to previous occurrence of char",
repeat_last_motion, "repeat last motion(extend_next_char, extend_till_char, find_next_char, find_till_char...)", repeat_last_motion, "Repeat last motion",
replace, "Replace with new char", replace, "Replace with new char",
switch_case, "Switch (toggle) case", switch_case, "Switch (toggle) case",
switch_to_uppercase, "Switch to uppercase", switch_to_uppercase, "Switch to uppercase",
@ -236,7 +237,7 @@ impl MappableCommand {
half_page_down, "Move half page down", half_page_down, "Move half page down",
select_all, "Select whole document", select_all, "Select whole document",
select_regex, "Select all regex matches inside selections", select_regex, "Select all regex matches inside selections",
split_selection, "Split selection into subselections on regex matches", split_selection, "Split selections on regex matches",
split_selection_on_newline, "Split selection on newlines", split_selection_on_newline, "Split selection on newlines",
search, "Search for regex pattern", search, "Search for regex pattern",
rsearch, "Reverse search for regex pattern", rsearch, "Reverse search for regex pattern",
@ -245,20 +246,20 @@ impl MappableCommand {
extend_search_next, "Add next search match to selection", extend_search_next, "Add next search match to selection",
extend_search_prev, "Add previous search match to selection", extend_search_prev, "Add previous search match to selection",
search_selection, "Use current selection as search pattern", search_selection, "Use current selection as search pattern",
global_search, "Global Search in workspace folder", global_search, "Global search in workspace folder",
extend_line, "Select current line, if already selected, extend to next line", extend_line, "Select current line, if already selected, extend to next line",
extend_line_above, "Select current line, if already selected, extend to previous line", extend_line_above, "Select current line, if already selected, extend to previous line",
extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", extend_to_line_bounds, "Extend selection to line bounds",
shrink_to_line_bounds, "Shrink selection to line bounds (line-wise selection)", shrink_to_line_bounds, "Shrink selection to line bounds",
delete_selection, "Delete selection", delete_selection, "Delete selection",
delete_selection_noyank, "Delete selection, without yanking", delete_selection_noyank, "Delete selection without yanking",
change_selection, "Change selection (delete and enter insert mode)", change_selection, "Change selection",
change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)", change_selection_noyank, "Change selection without yanking",
collapse_selection, "Collapse selection onto a single cursor", collapse_selection, "Collapse selection into single cursor",
flip_selections, "Flip selection cursor and anchor", flip_selections, "Flip selection cursor and anchor",
ensure_selections_forward, "Ensure the selection is in forward direction", ensure_selections_forward, "Ensure all selections face forward",
insert_mode, "Insert before selection", insert_mode, "Insert before selection",
append_mode, "Insert after selection (append)", append_mode, "Append after selection",
command_mode, "Enter command mode", command_mode, "Enter command mode",
file_picker, "Open file picker", file_picker, "Open file picker",
file_picker_in_current_directory, "Open file picker at current working directory", file_picker_in_current_directory, "Open file picker at current working directory",
@ -272,7 +273,7 @@ impl MappableCommand {
workspace_diagnostics_picker, "Open workspace diagnostic picker", workspace_diagnostics_picker, "Open workspace diagnostic picker",
last_picker, "Open last picker", last_picker, "Open last picker",
prepend_to_line, "Insert at start of line", prepend_to_line, "Insert at start of line",
append_to_line, "Insert at end of line", append_to_line, "Append to end of line",
open_below, "Open new line below selection", open_below, "Open new line below selection",
open_above, "Open new line above selection", open_above, "Open new line above selection",
normal_mode, "Enter normal mode", normal_mode, "Enter normal mode",
@ -319,13 +320,13 @@ impl MappableCommand {
delete_char_forward, "Delete next char", delete_char_forward, "Delete next char",
delete_word_backward, "Delete previous word", delete_word_backward, "Delete previous word",
delete_word_forward, "Delete next word", delete_word_forward, "Delete next word",
kill_to_line_start, "Delete content till the start of the line", kill_to_line_start, "Delete till start of line",
kill_to_line_end, "Delete content till the end of the line", kill_to_line_end, "Delete till end of line",
undo, "Undo change", undo, "Undo change",
redo, "Redo change", redo, "Redo change",
earlier, "Move backward in history", earlier, "Move backward in history",
later, "Move forward in history", later, "Move forward in history",
commit_undo_checkpoint, "Commit changes to a new checkpoint", commit_undo_checkpoint, "Commit changes to new checkpoint",
yank, "Yank selection", yank, "Yank selection",
yank_joined_to_clipboard, "Join and yank selections to clipboard", yank_joined_to_clipboard, "Join and yank selections to clipboard",
yank_main_selection_to_clipboard, "Yank main selection to clipboard", yank_main_selection_to_clipboard, "Yank main selection to clipboard",
@ -333,7 +334,7 @@ impl MappableCommand {
yank_main_selection_to_primary_clipboard, "Yank main selection to primary clipboard", yank_main_selection_to_primary_clipboard, "Yank main selection to primary clipboard",
replace_with_yanked, "Replace with yanked text", replace_with_yanked, "Replace with yanked text",
replace_selections_with_clipboard, "Replace selections by clipboard content", replace_selections_with_clipboard, "Replace selections by clipboard content",
replace_selections_with_primary_clipboard, "Replace selections by primary clipboard content", replace_selections_with_primary_clipboard, "Replace selections by primary clipboard",
paste_after, "Paste after selection", paste_after, "Paste after selection",
paste_before, "Paste before selection", paste_before, "Paste before selection",
paste_clipboard_after, "Paste clipboard after selections", paste_clipboard_after, "Paste clipboard after selections",
@ -358,19 +359,19 @@ impl MappableCommand {
rotate_selection_contents_backward, "Rotate selections contents backward", rotate_selection_contents_backward, "Rotate selections contents backward",
expand_selection, "Expand selection to parent syntax node", expand_selection, "Expand selection to parent syntax node",
shrink_selection, "Shrink selection to previously expanded syntax node", shrink_selection, "Shrink selection to previously expanded syntax node",
select_next_sibling, "Select the next sibling in the syntax tree", select_next_sibling, "Select next sibling in syntax tree",
select_prev_sibling, "Select the previous sibling in the syntax tree", select_prev_sibling, "Select previous sibling in syntax tree",
jump_forward, "Jump forward on jumplist", jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist", jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist", save_selection, "Save current selection to jumplist",
jump_view_right, "Jump to the split to the right", jump_view_right, "Jump to right split",
jump_view_left, "Jump to the split to the left", jump_view_left, "Jump to left split",
jump_view_up, "Jump to the split above", jump_view_up, "Jump to split above",
jump_view_down, "Jump to the split below", jump_view_down, "Jump to split below",
swap_view_right, "Swap with the split to the right", swap_view_right, "Swap with right split",
swap_view_left, "Swap with the split to the left", swap_view_left, "Swap with left split",
swap_view_up, "Swap with the split above", swap_view_up, "Swap with split above",
swap_view_down, "Swap with the split below", swap_view_down, "Swap with split below",
transpose_view, "Transpose splits", transpose_view, "Transpose splits",
rotate_view, "Goto next window", rotate_view, "Goto next window",
hsplit, "Horizontal bottom split", hsplit, "Horizontal bottom split",
@ -378,7 +379,7 @@ impl MappableCommand {
vsplit, "Vertical right split", vsplit, "Vertical right split",
vsplit_new, "Vertical right split scratch buffer", vsplit_new, "Vertical right split scratch buffer",
wclose, "Close window", wclose, "Close window",
wonly, "Current window only", wonly, "Close windows except current",
select_register, "Select register", select_register, "Select register",
insert_register, "Insert register", insert_register, "Insert register",
align_view_middle, "Align view middle", align_view_middle, "Align view middle",
@ -414,21 +415,21 @@ impl MappableCommand {
dap_next, "Step to next", dap_next, "Step to next",
dap_variables, "List variables", dap_variables, "List variables",
dap_terminate, "End debug session", dap_terminate, "End debug session",
dap_edit_condition, "Edit condition of the breakpoint on the current line", dap_edit_condition, "Edit breakpoint condition on current line",
dap_edit_log, "Edit log message of the breakpoint on the current line", dap_edit_log, "Edit breakpoint log message on current line",
dap_switch_thread, "Switch current thread", dap_switch_thread, "Switch current thread",
dap_switch_stack_frame, "Switch stack frame", dap_switch_stack_frame, "Switch stack frame",
dap_enable_exceptions, "Enable exception breakpoints", dap_enable_exceptions, "Enable exception breakpoints",
dap_disable_exceptions, "Disable exception breakpoints", dap_disable_exceptions, "Disable exception breakpoints",
shell_pipe, "Pipe selections through shell command", shell_pipe, "Pipe selections through shell command",
shell_pipe_to, "Pipe selections into shell command, ignoring command output", shell_pipe_to, "Pipe selections into shell command ignoring output",
shell_insert_output, "Insert output of shell command before each selection", shell_insert_output, "Insert shell command output before selections",
shell_append_output, "Append output of shell command after each selection", shell_append_output, "Append shell command output after selections",
shell_keep_pipe, "Filter selections with shell predicate", shell_keep_pipe, "Filter selections with shell predicate",
suspend, "Suspend", suspend, "Suspend and return to shell",
rename_symbol, "Rename symbol", rename_symbol, "Rename symbol",
increment, "Increment", increment, "Increment item under cursor",
decrement, "Decrement", decrement, "Decrement item under cursor",
record_macro, "Record macro", record_macro, "Record macro",
replay_macro, "Replay macro", replay_macro, "Replay macro",
command_palette, "Open command pallete", command_palette, "Open command pallete",
@ -769,7 +770,7 @@ fn trim_selections(cx: &mut Context) {
.selection(view.id) .selection(view.id)
.iter() .iter()
.filter_map(|range| { .filter_map(|range| {
if range.is_empty() || range.fragment(text).chars().all(|ch| ch.is_whitespace()) { if range.is_empty() || range.slice(text).chars().all(|ch| ch.is_whitespace()) {
return None; return None;
} }
let mut start = range.from(); let mut start = range.from();
@ -803,13 +804,14 @@ fn align_selections(cx: &mut Context) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let tab_width = doc.tab_width();
let mut column_widths: Vec<Vec<_>> = Vec::new(); let mut column_widths: Vec<Vec<_>> = Vec::new();
let mut last_line = text.len_lines() + 1; let mut last_line = text.len_lines() + 1;
let mut col = 0; let mut col = 0;
for range in selection { for range in selection {
let coords = coords_at_pos(text, range.head); let coords = visual_coords_at_pos(text, range.head, tab_width);
let anchor_coords = coords_at_pos(text, range.anchor); let anchor_coords = visual_coords_at_pos(text, range.anchor, tab_width);
if coords.row != anchor_coords.row { if coords.row != anchor_coords.row {
cx.editor cx.editor
@ -877,8 +879,8 @@ fn goto_window(cx: &mut Context, align: Align) {
let last_line = view.last_line(doc); let last_line = view.last_line(doc);
let line = match align { let line = match align {
Align::Top => (view.offset.row + scrolloff + count), Align::Top => view.offset.row + scrolloff + count,
Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)), Align::Center => view.offset.row + ((last_line - view.offset.row) / 2),
Align::Bottom => last_line.saturating_sub(scrolloff + count), Align::Bottom => last_line.saturating_sub(scrolloff + count),
} }
.max(view.offset.row + scrolloff) .max(view.offset.row + scrolloff)
@ -1291,12 +1293,12 @@ fn replace(cx: &mut Context) {
fn switch_case_impl<F>(cx: &mut Context, change_fn: F) fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
where where
F: Fn(Cow<str>) -> Tendril, F: Fn(RopeSlice) -> Tendril,
{ {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
let text: Tendril = change_fn(range.fragment(doc.text().slice(..))); let text: Tendril = change_fn(range.slice(doc.text().slice(..)));
(range.from(), range.to(), Some(text)) (range.from(), range.to(), Some(text))
}); });
@ -1322,11 +1324,15 @@ fn switch_case(cx: &mut Context) {
} }
fn switch_to_uppercase(cx: &mut Context) { fn switch_to_uppercase(cx: &mut Context) {
switch_case_impl(cx, |string| string.to_uppercase().into()); switch_case_impl(cx, |string| {
string.chunks().map(|chunk| chunk.to_uppercase()).collect()
});
} }
fn switch_to_lowercase(cx: &mut Context) { fn switch_to_lowercase(cx: &mut Context) {
switch_case_impl(cx, |string| string.to_lowercase().into()); switch_case_impl(cx, |string| {
string.chunks().map(|chunk| chunk.to_lowercase()).collect()
});
} }
pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
@ -1751,10 +1757,16 @@ fn extend_search_prev(cx: &mut Context) {
fn search_selection(cx: &mut Context) { fn search_selection(cx: &mut Context) {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
let contents = doc.text().slice(..); let contents = doc.text().slice(..);
let query = doc.selection(view.id).primary().fragment(contents);
let regex = regex::escape(&query); let regex = doc
.selection(view.id)
.iter()
.map(|selection| regex::escape(&selection.fragment(contents)))
.collect::<Vec<_>>()
.join("|");
let msg = format!("register '{}' set to '{}'", '/', &regex);
cx.editor.registers.get_mut('/').push(regex); cx.editor.registers.get_mut('/').push(regex);
let msg = format!("register '{}' set to '{}'", '/', query);
cx.editor.set_status(msg); cx.editor.set_status(msg);
} }
@ -2504,14 +2516,14 @@ async fn make_format_callback(
doc_id: DocumentId, doc_id: DocumentId,
doc_version: i32, doc_version: i32,
modified: Modified, modified: Modified,
format: impl Future<Output = helix_lsp::util::LspFormatting> + Send + 'static, format: impl Future<Output = Result<Transaction, FormatterError>> + Send + 'static,
) -> anyhow::Result<job::Callback> { ) -> anyhow::Result<job::Callback> {
let format = format.await; let format = format.await?;
let call: job::Callback = Box::new(move |editor, _compositor| { let call: job::Callback = Box::new(move |editor, _compositor| {
let view_id = view!(editor).id; let view_id = view!(editor).id;
if let Some(doc) = editor.document_mut(doc_id) { if let Some(doc) = editor.document_mut(doc_id) {
if doc.version() == doc_version { if doc.version() == doc_version {
doc.apply(&Transaction::from(format), view_id); doc.apply(&format, view_id);
doc.append_changes_to_history(view_id); doc.append_changes_to_history(view_id);
doc.detect_indent_and_line_ending(); doc.detect_indent_and_line_ending();
if let Modified::SetUnmodified = modified { if let Modified::SetUnmodified = modified {
@ -3936,8 +3948,8 @@ fn rotate_selection_contents(cx: &mut Context, direction: Direction) {
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let mut fragments: Vec<_> = selection let mut fragments: Vec<_> = selection
.fragments(text) .slices(text)
.map(|fragment| Tendril::from(fragment.as_ref())) .map(|fragment| fragment.chunks().collect())
.collect(); .collect();
let group = count let group = count
@ -4109,35 +4121,35 @@ fn rotate_view(cx: &mut Context) {
} }
fn jump_view_right(cx: &mut Context) { fn jump_view_right(cx: &mut Context) {
cx.editor.focus_right() cx.editor.focus_direction(tree::Direction::Right)
} }
fn jump_view_left(cx: &mut Context) { fn jump_view_left(cx: &mut Context) {
cx.editor.focus_left() cx.editor.focus_direction(tree::Direction::Left)
} }
fn jump_view_up(cx: &mut Context) { fn jump_view_up(cx: &mut Context) {
cx.editor.focus_up() cx.editor.focus_direction(tree::Direction::Up)
} }
fn jump_view_down(cx: &mut Context) { fn jump_view_down(cx: &mut Context) {
cx.editor.focus_down() cx.editor.focus_direction(tree::Direction::Down)
} }
fn swap_view_right(cx: &mut Context) { fn swap_view_right(cx: &mut Context) {
cx.editor.swap_right() cx.editor.swap_split_in_direction(tree::Direction::Right)
} }
fn swap_view_left(cx: &mut Context) { fn swap_view_left(cx: &mut Context) {
cx.editor.swap_left() cx.editor.swap_split_in_direction(tree::Direction::Left)
} }
fn swap_view_up(cx: &mut Context) { fn swap_view_up(cx: &mut Context) {
cx.editor.swap_up() cx.editor.swap_split_in_direction(tree::Direction::Up)
} }
fn swap_view_down(cx: &mut Context) { fn swap_view_down(cx: &mut Context) {
cx.editor.swap_down() cx.editor.swap_split_in_direction(tree::Direction::Down)
} }
fn transpose_view(cx: &mut Context) { fn transpose_view(cx: &mut Context) {
@ -4361,10 +4373,12 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
'o' => textobject_treesitter("comment", range), 'o' => textobject_treesitter("comment", range),
't' => textobject_treesitter("test", range), 't' => textobject_treesitter("test", range),
'p' => textobject::textobject_paragraph(text, range, objtype, count), 'p' => textobject::textobject_paragraph(text, range, objtype, count),
'm' => textobject::textobject_surround_closest(text, range, objtype, count), 'm' => textobject::textobject_pair_surround_closest(
text, range, objtype, count,
),
// TODO: cancel new ranges if inconsistent surround matches across lines // TODO: cancel new ranges if inconsistent surround matches across lines
ch if !ch.is_ascii_alphanumeric() => { ch if !ch.is_ascii_alphanumeric() => {
textobject::textobject_surround(text, range, objtype, ch, count) textobject::textobject_pair_surround(text, range, objtype, ch, count)
} }
_ => range, _ => range,
} }
@ -4390,7 +4404,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
("a", "Argument/parameter (tree-sitter)"), ("a", "Argument/parameter (tree-sitter)"),
("o", "Comment (tree-sitter)"), ("o", "Comment (tree-sitter)"),
("t", "Test (tree-sitter)"), ("t", "Test (tree-sitter)"),
("m", "Matching delimiter under cursor"), ("m", "Closest surrounding pair to cursor"),
(" ", "... or any character acting as a pair"), (" ", "... or any character acting as a pair"),
]; ];
@ -4543,8 +4557,8 @@ fn shell_keep_pipe(cx: &mut Context) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
for (i, range) in selection.ranges().iter().enumerate() { for (i, range) in selection.ranges().iter().enumerate() {
let fragment = range.fragment(text); let fragment = range.slice(text);
let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) { let (_output, success) = match shell_impl(shell, input, Some(fragment)) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
cx.editor.set_error(err.to_string()); cx.editor.set_error(err.to_string());
@ -4575,20 +4589,24 @@ fn shell_keep_pipe(cx: &mut Context) {
fn shell_impl( fn shell_impl(
shell: &[String], shell: &[String],
cmd: &str, cmd: &str,
input: Option<&[u8]>, input: Option<RopeSlice>,
) -> anyhow::Result<(Tendril, bool)> { ) -> anyhow::Result<(Tendril, bool)> {
use std::io::Write; use std::io::Write;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
ensure!(!shell.is_empty(), "No shell set"); ensure!(!shell.is_empty(), "No shell set");
let mut process = match Command::new(&shell[0]) let mut process = Command::new(&shell[0]);
process
.args(&shell[1..]) .args(&shell[1..])
.arg(cmd) .arg(cmd)
.stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped());
.spawn()
{ if input.is_some() || cfg!(windows) {
process.stdin(Stdio::piped());
}
let mut process = match process.spawn() {
Ok(process) => process, Ok(process) => process,
Err(e) => { Err(e) => {
log::error!("Failed to start shell: {}", e); log::error!("Failed to start shell: {}", e);
@ -4597,7 +4615,9 @@ fn shell_impl(
}; };
if let Some(input) = input { if let Some(input) = input {
let mut stdin = process.stdin.take().unwrap(); let mut stdin = process.stdin.take().unwrap();
stdin.write_all(input)?; for chunk in input.chunks() {
stdin.write_all(chunk.as_bytes())?;
}
} }
let output = process.wait_with_output()?; let output = process.wait_with_output()?;
@ -4626,8 +4646,8 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
for range in selection.ranges() { for range in selection.ranges() {
let fragment = range.fragment(text); let fragment = range.slice(text);
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) { let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment)) {
Ok(result) => result, Ok(result) => result,
Err(err) => { Err(err) => {
cx.editor.set_error(err.to_string()); cx.editor.set_error(err.to_string());
@ -4869,7 +4889,7 @@ fn replay_macro(cx: &mut Context) {
cx.callback = Some(Box::new(move |compositor, cx| { cx.callback = Some(Box::new(move |compositor, cx| {
for _ in 0..count { for _ in 0..count {
for &key in keys.iter() { for &key in keys.iter() {
compositor.handle_event(crossterm::event::Event::Key(key.into()), cx); compositor.handle_event(compositor::Event::Key(key), cx);
} }
} }
// The macro under replay is cleared at the end of the callback, not in the // The macro under replay is cleared at the end of the callback, not in the

@ -216,6 +216,8 @@ pub fn dap_start_impl(
} }
} }
args.insert("cwd", to_value(std::env::current_dir().unwrap())?);
let args = to_value(args).unwrap(); let args = to_value(args).unwrap();
let callback = |_editor: &mut Editor, _compositor: &mut Compositor, _response: Value| { let callback = |_editor: &mut Editor, _compositor: &mut Compositor, _response: Value| {

@ -904,7 +904,12 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
Some((start, start + string.len())) Some((start, start + string.len()))
} }
lsp::ParameterLabel::LabelOffsets([start, end]) => { lsp::ParameterLabel::LabelOffsets([start, end]) => {
Some((*start as usize, *end as usize)) // LS sends offsets based on utf-16 based string representation
// but highlighting in helix is done using byte offset.
use helix_core::str_utils::char_to_byte_idx;
let from = char_to_byte_idx(&signature.label, *start as usize);
let to = char_to_byte_idx(&signature.label, *end as usize);
Some((from, to))
} }
} }
}; };

@ -1254,6 +1254,7 @@ fn language(
let doc = doc_mut!(cx.editor); let doc = doc_mut!(cx.editor);
doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone()); doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone());
doc.detect_indent_and_line_ending();
let id = doc.id(); let id = doc.id();
cx.editor.refresh_language_server(id); cx.editor.refresh_language_server(id);
@ -1291,8 +1292,8 @@ fn sort_impl(
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let mut fragments: Vec<_> = selection let mut fragments: Vec<_> = selection
.fragments(text) .slices(text)
.map(|fragment| Tendril::from(fragment.as_ref())) .map(|fragment| fragment.chunks().collect())
.collect(); .collect();
fragments.sort_by(match reverse { fragments.sort_by(match reverse {
@ -1535,7 +1536,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "quit!", name: "quit!",
aliases: &["q!"], aliases: &["q!"],
doc: "Close the current view forcefully (ignoring unsaved changes).", doc: "Force close the current view, ignoring unsaved changes.",
fun: force_quit, fun: force_quit,
completer: None, completer: None,
}, },
@ -1556,7 +1557,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "buffer-close!", name: "buffer-close!",
aliases: &["bc!", "bclose!"], aliases: &["bc!", "bclose!"],
doc: "Close the current buffer forcefully (ignoring unsaved changes).", doc: "Close the current buffer forcefully, ignoring unsaved changes.",
fun: force_buffer_close, fun: force_buffer_close,
completer: Some(completers::buffer), completer: Some(completers::buffer),
}, },
@ -1570,35 +1571,35 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "buffer-close-others!", name: "buffer-close-others!",
aliases: &["bco!", "bcloseother!"], aliases: &["bco!", "bcloseother!"],
doc: "Close all buffers but the currently focused one.", doc: "Force close all buffers but the currently focused one.",
fun: force_buffer_close_others, fun: force_buffer_close_others,
completer: None, completer: None,
}, },
TypableCommand { TypableCommand {
name: "buffer-close-all", name: "buffer-close-all",
aliases: &["bca", "bcloseall"], aliases: &["bca", "bcloseall"],
doc: "Close all buffers, without quitting.", doc: "Close all buffers without quitting.",
fun: buffer_close_all, fun: buffer_close_all,
completer: None, completer: None,
}, },
TypableCommand { TypableCommand {
name: "buffer-close-all!", name: "buffer-close-all!",
aliases: &["bca!", "bcloseall!"], aliases: &["bca!", "bcloseall!"],
doc: "Close all buffers forcefully (ignoring unsaved changes), without quitting.", doc: "Force close all buffers ignoring unsaved changes without quitting.",
fun: force_buffer_close_all, fun: force_buffer_close_all,
completer: None, completer: None,
}, },
TypableCommand { TypableCommand {
name: "buffer-next", name: "buffer-next",
aliases: &["bn", "bnext"], aliases: &["bn", "bnext"],
doc: "Go to next buffer.", doc: "Goto next buffer.",
fun: buffer_next, fun: buffer_next,
completer: None, completer: None,
}, },
TypableCommand { TypableCommand {
name: "buffer-previous", name: "buffer-previous",
aliases: &["bp", "bprev"], aliases: &["bp", "bprev"],
doc: "Go to previous buffer.", doc: "Goto previous buffer.",
fun: buffer_previous, fun: buffer_previous,
completer: None, completer: None,
}, },
@ -1612,7 +1613,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "write!", name: "write!",
aliases: &["w!"], aliases: &["w!"],
doc: "Write changes to disk forcefully (creating necessary subdirectories). Accepts an optional path (:write some/path.txt)", doc: "Force write changes to disk creating necessary subdirectories. Accepts an optional path (:write some/path.txt)",
fun: force_write, fun: force_write,
completer: Some(completers::filename), completer: Some(completers::filename),
}, },
@ -1706,7 +1707,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "quit-all!", name: "quit-all!",
aliases: &["qa!"], aliases: &["qa!"],
doc: "Close all views forcefully (ignoring unsaved changes).", doc: "Force close all views ignoring unsaved changes.",
fun: force_quit_all, fun: force_quit_all,
completer: None, completer: None,
}, },
@ -1720,7 +1721,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "cquit!", name: "cquit!",
aliases: &["cq!"], aliases: &["cq!"],
doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).", doc: "Force quit with exit code (default 1) ignoring unsaved changes. Accepts an optional integer exit code (:cq! 2).",
fun: force_cquit, fun: force_cquit,
completer: None, completer: None,
}, },
@ -1825,7 +1826,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "encoding", name: "encoding",
aliases: &[], aliases: &[],
doc: "Set encoding based on `https://encoding.spec.whatwg.org`", doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
fun: set_encoding, fun: set_encoding,
completer: None, completer: None,
}, },
@ -1902,7 +1903,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "goto", name: "goto",
aliases: &["g"], aliases: &["g"],
doc: "Go to line number.", doc: "Goto line number.",
fun: goto_line_number, fun: goto_line_number,
completer: None, completer: None,
}, },
@ -1958,14 +1959,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand { TypableCommand {
name: "config-reload", name: "config-reload",
aliases: &[], aliases: &[],
doc: "Refreshes helix's config.", doc: "Refresh user config.",
fun: refresh_config, fun: refresh_config,
completer: None, completer: None,
}, },
TypableCommand { TypableCommand {
name: "config-open", name: "config-open",
aliases: &[], aliases: &[],
doc: "Open the helix config.toml file.", doc: "Open the user config.toml file.",
fun: open_config, fun: open_config,
completer: None, completer: None,
}, },

@ -4,8 +4,6 @@
use helix_core::Position; use helix_core::Position;
use helix_view::graphics::{CursorKind, Rect}; use helix_view::graphics::{CursorKind, Rect};
use crossterm::event::Event;
#[cfg(feature = "integration")] #[cfg(feature = "integration")]
use tui::backend::TestBackend; use tui::backend::TestBackend;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
@ -18,9 +16,10 @@ pub enum EventResult {
Consumed(Option<Callback>), Consumed(Option<Callback>),
} }
use crate::job::Jobs;
use helix_view::Editor; use helix_view::Editor;
use crate::job::Jobs; pub use helix_view::input::Event;
pub struct Context<'a> { pub struct Context<'a> {
pub editor: &'a mut Editor, pub editor: &'a mut Editor,
@ -161,7 +160,7 @@ impl Compositor {
pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool {
// If it is a key event and a macro is being recorded, push the key event to the recording. // If it is a key event and a macro is being recorded, push the key event to the recording.
if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) {
keys.push(key.into()); keys.push(key);
} }
let mut callbacks = Vec::new(); let mut callbacks = Vec::new();

@ -4,6 +4,7 @@ use crossterm::{
}; };
use helix_core::config::{default_syntax_loader, user_syntax_loader}; use helix_core::config::{default_syntax_loader, user_syntax_loader};
use helix_loader::grammar::load_runtime_file; use helix_loader::grammar::load_runtime_file;
use helix_view::clipboard::get_clipboard_provider;
use std::io::Write; use std::io::Write;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -52,6 +53,7 @@ pub fn general() -> std::io::Result<()> {
let lang_file = helix_loader::lang_config_file(); let lang_file = helix_loader::lang_config_file();
let log_file = helix_loader::log_file(); let log_file = helix_loader::log_file();
let rt_dir = helix_loader::runtime_dir(); let rt_dir = helix_loader::runtime_dir();
let clipboard_provider = get_clipboard_provider();
if config_file.exists() { if config_file.exists() {
writeln!(stdout, "Config file: {}", config_file.display())?; writeln!(stdout, "Config file: {}", config_file.display())?;
@ -76,6 +78,7 @@ pub fn general() -> std::io::Result<()> {
if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) { if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) {
writeln!(stdout, "{}", "Runtime directory is empty.".red())?; writeln!(stdout, "{}", "Runtime directory is empty.".red())?;
} }
writeln!(stdout, "Clipboard provider: {}", clipboard_provider.name())?;
Ok(()) Ok(())
} }
@ -139,7 +142,7 @@ pub fn languages_all() -> std::io::Result<()> {
let check_binary = |cmd: Option<String>| match cmd { let check_binary = |cmd: Option<String>| match cmd {
Some(cmd) => match which::which(&cmd) { Some(cmd) => match which::which(&cmd) {
Ok(_) => column(&format!(" {}", cmd), Color::Green), Ok(_) => column(&format!(" {}", cmd), Color::Green),
Err(_) => column(&format!("✘ {}", cmd), Color::Red), Err(_) => column(&format!("✘ {}", cmd), Color::Red),
}, },
None => column("None", Color::Yellow), None => column("None", Color::Yellow),
@ -159,7 +162,7 @@ pub fn languages_all() -> std::io::Result<()> {
for ts_feat in TsFeature::all() { for ts_feat in TsFeature::all() {
match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() { match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() {
true => column("", Color::Green), true => column("", Color::Green),
false => column("✘", Color::Red), false => column("✘", Color::Red),
} }
} }
@ -268,7 +271,7 @@ fn probe_treesitter_feature(lang: &str, feature: TsFeature) -> std::io::Result<(
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
let found = match load_runtime_file(lang, feature.runtime_filename()).is_ok() { let found = match load_runtime_file(lang, feature.runtime_filename()).is_ok() {
true => "".green(), true => "".green(),
false => "✘".red(), false => "✘".red(),
}; };
writeln!(stdout, "{} queries: {}", feature.short_title(), found)?; writeln!(stdout, "{} queries: {}", feature.short_title(), found)?;

@ -360,9 +360,7 @@ pub fn default() -> HashMap<Mode, Keymap> {
"left" => move_char_left, "left" => move_char_left,
"C-b" => move_char_left, "C-b" => move_char_left,
"down" => move_line_down, "down" => move_line_down,
"C-n" => move_line_down,
"up" => move_line_up, "up" => move_line_up,
"C-p" => move_line_up,
"right" => move_char_right, "right" => move_char_right,
"C-f" => move_char_right, "C-f" => move_char_right,
"A-b" => move_prev_word_end, "A-b" => move_prev_word_end,

@ -64,6 +64,7 @@ FLAGS:
--health [LANG] Checks for potential errors in editor setup --health [LANG] Checks for potential errors in editor setup
If given, checks for config errors in language LANG If given, checks for config errors in language LANG
-g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml
-c, --config <file> Specifies a file to use for configuration
-v Increases logging verbosity each use for up to 3 times -v Increases logging verbosity each use for up to 3 times
(default file: {}) (default file: {})
-V, --version Prints version information -V, --version Prints version information
@ -108,7 +109,7 @@ FLAGS:
} }
if args.build_grammars { if args.build_grammars {
helix_loader::grammar::build_grammars()?; helix_loader::grammar::build_grammars(None)?;
return Ok(0); return Ok(0);
} }
@ -119,14 +120,15 @@ FLAGS:
std::fs::create_dir_all(&config_dir).ok(); std::fs::create_dir_all(&config_dir).ok();
} }
let config = match std::fs::read_to_string(config_dir.join("config.toml")) { helix_loader::initialize_config_file(args.config_file.clone());
let config = match std::fs::read_to_string(helix_loader::config_file()) {
Ok(config) => toml::from_str(&config) Ok(config) => toml::from_str(&config)
.map(helix_term::keymap::merge_keys) .map(helix_term::keymap::merge_keys)
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
eprintln!("Bad config: {}", err); eprintln!("Bad config: {}", err);
eprintln!("Press <ENTER> to continue with default config"); eprintln!("Press <ENTER> to continue with default config");
use std::io::Read; use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []); let _ = std::io::stdin().read(&mut []);
Config::default() Config::default()
}), }),

@ -1,5 +1,4 @@
use crate::compositor::{Component, Context, EventResult}; use crate::compositor::{Component, Context, Event, EventResult};
use crossterm::event::{Event, KeyCode, KeyEvent};
use helix_view::editor::CompleteAction; use helix_view::editor::CompleteAction;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use tui::text::Spans; use tui::text::Spans;
@ -7,7 +6,11 @@ use tui::text::Spans;
use std::borrow::Cow; use std::borrow::Cow;
use helix_core::{Change, Transaction}; use helix_core::{Change, Transaction};
use helix_view::{graphics::Rect, Document, Editor}; use helix_view::{
graphics::Rect,
input::{KeyCode, KeyEvent},
Document, Editor,
};
use crate::commands; use crate::commands;
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};

@ -1,6 +1,6 @@
use crate::{ use crate::{
commands, commands,
compositor::{Component, Context, EventResult}, compositor::{Component, Context, Event, EventResult},
job, key, job, key,
keymap::{KeymapResult, Keymaps}, keymap::{KeymapResult, Keymaps},
ui::{overlay::Overlay, Completion, Explorer, ProgressSpinners}, ui::{overlay::Overlay, Completion, Explorer, ProgressSpinners},
@ -19,13 +19,12 @@ use helix_view::{
document::Mode, document::Mode,
editor::{CompleteAction, CursorShapeConfig}, editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style}, graphics::{Color, CursorKind, Modifier, Rect, Style},
input::KeyEvent, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers}, keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View, Document, Editor, Theme, View,
}; };
use std::borrow::Cow; use std::borrow::Cow;
use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind};
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use super::lsp::SignatureHelp; use super::lsp::SignatureHelp;
@ -393,19 +392,23 @@ impl EditorView {
// of times than it is to always call Rope::slice/get_slice (it will internally always hit RSEnum::Light). // of times than it is to always call Rope::slice/get_slice (it will internally always hit RSEnum::Light).
let text = doc.text().slice(..); let text = doc.text().slice(..);
let characters = &whitespace.characters;
let mut spans = Vec::new(); let mut spans = Vec::new();
let mut visual_x = 0u16; let mut visual_x = 0u16;
let mut line = 0u16; let mut line = 0u16;
let tab_width = doc.tab_width(); let tab_width = doc.tab_width();
let tab = if whitespace.render.tab() == WhitespaceRenderValue::All { let tab = if whitespace.render.tab() == WhitespaceRenderValue::All {
(1..tab_width).fold(whitespace.characters.tab.to_string(), |s, _| s + " ") std::iter::once(characters.tab)
.chain(std::iter::repeat(characters.tabpad).take(tab_width - 1))
.collect()
} else { } else {
" ".repeat(tab_width) " ".repeat(tab_width)
}; };
let space = whitespace.characters.space.to_string(); let space = characters.space.to_string();
let nbsp = whitespace.characters.nbsp.to_string(); let nbsp = characters.nbsp.to_string();
let newline = if whitespace.render.newline() == WhitespaceRenderValue::All { let newline = if whitespace.render.newline() == WhitespaceRenderValue::All {
whitespace.characters.newline.to_string() characters.newline.to_string()
} else { } else {
" ".to_string() " ".to_string()
}; };
@ -416,7 +419,13 @@ impl EditorView {
let mut is_in_indent_area = true; let mut is_in_indent_area = true;
let mut last_line_indent_level = 0; let mut last_line_indent_level = 0;
let indent_style = theme.get("ui.virtual.indent-guide");
// use whitespace style as fallback for indent-guide
let indent_guide_style = text_style.patch(
theme
.try_get("ui.virtual.indent-guide")
.unwrap_or_else(|| theme.get("ui.virtual.whitespace")),
);
let draw_indent_guides = |indent_level, line, surface: &mut Surface| { let draw_indent_guides = |indent_level, line, surface: &mut Surface| {
if !config.indent_guides.render { if !config.indent_guides.render {
@ -432,7 +441,7 @@ impl EditorView {
viewport.x + (i * tab_width as u16) - offset.col as u16, viewport.x + (i * tab_width as u16) - offset.col as u16,
viewport.y + line, viewport.y + line,
&indent_guide_char, &indent_guide_char,
indent_style, indent_guide_style,
); );
} }
}; };
@ -489,14 +498,7 @@ impl EditorView {
); );
} }
// This is an empty line; draw indent guides at previous line's draw_indent_guides(last_line_indent_level, line, surface);
// indent level to avoid breaking the guides on blank lines.
if visual_x == 0 {
draw_indent_guides(last_line_indent_level, line, surface);
} else if is_in_indent_area {
// A line with whitespace only
draw_indent_guides(visual_x, line, surface);
}
visual_x = 0; visual_x = 0;
line += 1; line += 1;
@ -532,6 +534,8 @@ impl EditorView {
(grapheme.as_ref(), width) (grapheme.as_ref(), width)
}; };
let cut_off_start = offset.col.saturating_sub(visual_x as usize);
if !out_of_bounds { if !out_of_bounds {
// if we're offscreen just keep going until we hit a new line // if we're offscreen just keep going until we hit a new line
surface.set_string( surface.set_string(
@ -544,7 +548,24 @@ impl EditorView {
style style
}, },
); );
} else if cut_off_start != 0 && cut_off_start < width {
// partially on screen
let rect = Rect::new(
viewport.x as u16,
viewport.y + line,
(width - cut_off_start) as u16,
1,
);
surface.set_style(
rect,
if is_whitespace {
style.patch(whitespace_style)
} else {
style
},
);
} }
if is_in_indent_area && !(grapheme == " " || grapheme == "\t") { if is_in_indent_area && !(grapheme == " " || grapheme == "\t") {
draw_indent_guides(visual_x, line, surface); draw_indent_guides(visual_x, line, surface);
is_in_indent_area = false; is_in_indent_area = false;
@ -950,23 +971,22 @@ impl EditorView {
if let Some((pos, view_id)) = pos_and_view(editor, row, column) { if let Some((pos, view_id)) = pos_and_view(editor, row, column) {
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
if modifiers == crossterm::event::KeyModifiers::ALT { if modifiers == KeyModifiers::ALT {
let selection = doc.selection(view_id).clone(); let selection = doc.selection(view_id).clone();
doc.set_selection(view_id, selection.push(Range::point(pos))); doc.set_selection(view_id, selection.push(Range::point(pos)));
} else { } else {
doc.set_selection(view_id, Selection::point(pos)); doc.set_selection(view_id, Selection::point(pos));
} }
editor.tree.focus = view_id; editor.focus(view_id);
return EventResult::Consumed(None); return EventResult::Consumed(None);
} }
if let Some((coords, view_id)) = gutter_coords_and_view(editor, row, column) { if let Some((coords, view_id)) = gutter_coords_and_view(editor, row, column) {
editor.tree.focus = view_id; editor.focus(view_id);
let view = editor.tree.get(view_id); let (view, doc) = current!(cxt.editor);
let doc = editor.documents.get_mut(&view.doc).unwrap();
let path = match doc.path() { let path = match doc.path() {
Some(path) => path.clone(), Some(path) => path.clone(),
@ -1013,7 +1033,7 @@ impl EditorView {
None => return EventResult::Ignored(None), None => return EventResult::Ignored(None),
} }
let offset = config.scroll_lines.abs() as usize; let offset = config.scroll_lines.unsigned_abs();
commands::scroll(cxt, offset, direction); commands::scroll(cxt, offset, direction);
cxt.editor.tree.focus = current_view; cxt.editor.tree.focus = current_view;
@ -1031,8 +1051,8 @@ impl EditorView {
if doc if doc
.selection(view.id) .selection(view.id)
.primary() .primary()
.fragment(doc.text().slice(..)) .slice(doc.text().slice(..))
.width() .len_chars()
<= 1 <= 1
{ {
return EventResult::Ignored(None); return EventResult::Ignored(None);
@ -1045,14 +1065,13 @@ impl EditorView {
MouseEventKind::Up(MouseButton::Right) => { MouseEventKind::Up(MouseButton::Right) => {
if let Some((coords, view_id)) = gutter_coords_and_view(cxt.editor, row, column) { if let Some((coords, view_id)) = gutter_coords_and_view(cxt.editor, row, column) {
cxt.editor.tree.focus = view_id; cxt.editor.focus(view_id);
let view = cxt.editor.tree.get(view_id); let (view, doc) = current!(cxt.editor);
let doc = cxt.editor.documents.get_mut(&view.doc).unwrap();
let line = coords.row + view.offset.row; let line = coords.row + view.offset.row;
if let Ok(pos) = doc.text().try_line_to_char(line) { if let Ok(pos) = doc.text().try_line_to_char(line) {
doc.set_selection(view_id, Selection::point(pos)); doc.set_selection(view_id, Selection::point(pos));
if modifiers == crossterm::event::KeyModifiers::ALT { if modifiers == KeyModifiers::ALT {
commands::MappableCommand::dap_edit_log.execute(cxt); commands::MappableCommand::dap_edit_log.execute(cxt);
} else { } else {
commands::MappableCommand::dap_edit_condition.execute(cxt); commands::MappableCommand::dap_edit_condition.execute(cxt);
@ -1071,7 +1090,7 @@ impl EditorView {
return EventResult::Ignored(None); return EventResult::Ignored(None);
} }
if modifiers == crossterm::event::KeyModifiers::ALT { if modifiers == KeyModifiers::ALT {
commands::MappableCommand::replace_selections_with_primary_clipboard commands::MappableCommand::replace_selections_with_primary_clipboard
.execute(cxt); .execute(cxt);
@ -1081,7 +1100,7 @@ impl EditorView {
if let Some((pos, view_id)) = pos_and_view(editor, row, column) { if let Some((pos, view_id)) = pos_and_view(editor, row, column) {
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap(); let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
doc.set_selection(view_id, Selection::point(pos)); doc.set_selection(view_id, Selection::point(pos));
editor.tree.focus = view_id; cxt.editor.focus(view_id);
commands::MappableCommand::paste_primary_clipboard_before.execute(cxt); commands::MappableCommand::paste_primary_clipboard_before.execute(cxt);
return EventResult::Consumed(None); return EventResult::Consumed(None);
@ -1121,9 +1140,8 @@ impl Component for EditorView {
// Handling it here but not re-rendering will cause flashing // Handling it here but not re-rendering will cause flashing
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(key) => { Event::Key(mut key) => {
cx.editor.reset_idle_timer(); cx.editor.reset_idle_timer();
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key); canonicalize_key(&mut key);
// clear status // clear status
@ -1240,6 +1258,7 @@ impl Component for EditorView {
} }
Event::Mouse(event) => self.handle_mouse_event(event, &mut cx), Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
Event::FocusGained | Event::FocusLost => EventResult::Ignored(None),
} }
} }

@ -1,10 +1,9 @@
use std::{borrow::Cow, path::PathBuf}; use std::{borrow::Cow, path::PathBuf};
use crate::{ use crate::{
compositor::{Callback, Component, Compositor, Context, EventResult}, compositor::{Callback, Component, Compositor, Context, Event, EventResult},
ctrl, key, shift, ctrl, key, shift,
}; };
use crossterm::event::Event;
use tui::{buffer::Buffer as Surface, text::Spans, widgets::Table}; use tui::{buffer::Buffer as Surface, text::Spans, widgets::Table};
pub use tui::widgets::{Cell, Row}; pub use tui::widgets::{Cell, Row};
@ -237,7 +236,7 @@ impl<T: Item + 'static> Component for Menu<T> {
compositor.pop(); compositor.pop();
})); }));
match event.into() { match event {
// esc or ctrl-c aborts the completion and closes the menu // esc or ctrl-c aborts the completion and closes the menu
key!(Esc) | ctrl!('c') => { key!(Esc) | ctrl!('c') => {
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort);

@ -264,6 +264,7 @@ pub mod completers {
names.push("default".into()); names.push("default".into());
names.push("base16_default".into()); names.push("base16_default".into());
names.sort(); names.sort();
names.dedup();
let mut names: Vec<_> = names let mut names: Vec<_> = names
.into_iter() .into_iter()
@ -287,14 +288,28 @@ pub mod completers {
names names
} }
/// Recursive function to get all keys from this value and add them to vec
fn get_keys(value: &serde_json::Value, vec: &mut Vec<String>, scope: Option<&str>) {
if let Some(map) = value.as_object() {
for (key, value) in map.iter() {
let key = match scope {
Some(scope) => format!("{}.{}", scope, key),
None => key.clone(),
};
get_keys(value, vec, Some(&key));
if !value.is_object() {
vec.push(key);
}
}
}
}
pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> { pub fn setting(_editor: &Editor, input: &str) -> Vec<Completion> {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| { static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
serde_json::json!(Config::default()) let mut keys = Vec::new();
.as_object() let json = serde_json::json!(Config::default());
.unwrap() get_keys(&json, &mut keys, None);
.keys() keys
.cloned()
.collect()
}); });
let matcher = Matcher::default(); let matcher = Matcher::default();

@ -1,4 +1,3 @@
use crossterm::event::Event;
use helix_core::Position; use helix_core::Position;
use helix_view::{ use helix_view::{
graphics::{CursorKind, Rect}, graphics::{CursorKind, Rect},
@ -6,7 +5,7 @@ use helix_view::{
}; };
use tui::buffer::Buffer; use tui::buffer::Buffer;
use crate::compositor::{Component, Context, EventResult}; use crate::compositor::{Component, Context, Event, EventResult};
/// Contains a component placed in the center of the parent component /// Contains a component placed in the center of the parent component
pub struct Overlay<T> { pub struct Overlay<T> {

@ -1,9 +1,8 @@
use crate::{ use crate::{
compositor::{Component, Compositor, Context, EventResult}, compositor::{Component, Compositor, Context, Event, EventResult},
ctrl, key, shift, ctrl, key, shift,
ui::{self, EditorView}, ui::{self, EditorView},
}; };
use crossterm::event::Event;
use tui::{ use tui::{
buffer::Buffer as Surface, buffer::Buffer as Surface,
widgets::{Block, BorderType, Borders}, widgets::{Block, BorderType, Borders},
@ -502,7 +501,7 @@ impl<T: Item + 'static> Component for Picker<T> {
compositor.last_picker = compositor.pop(); compositor.last_picker = compositor.pop();
}))); })));
match key_event.into() { match key_event {
shift!(Tab) | key!(Up) | ctrl!('p') => { shift!(Tab) | key!(Up) | ctrl!('p') => {
self.move_by(1, Direction::Backward); self.move_by(1, Direction::Backward);
} }

@ -1,9 +1,8 @@
use crate::{ use crate::{
commands::Open, commands::Open,
compositor::{Callback, Component, Context, EventResult}, compositor::{Callback, Component, Context, Event, EventResult},
ctrl, key, ctrl, key,
}; };
use crossterm::event::Event;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use helix_core::Position; use helix_core::Position;
@ -149,7 +148,7 @@ impl<T: Component> Component for Popup<T> {
_ => return EventResult::Ignored(None), _ => return EventResult::Ignored(None),
}; };
if key!(Esc) == key.into() && self.ignore_escape_key { if key!(Esc) == key && self.ignore_escape_key {
return EventResult::Ignored(None); return EventResult::Ignored(None);
} }
@ -158,7 +157,7 @@ impl<T: Component> Component for Popup<T> {
compositor.remove(self.id.as_ref()); compositor.remove(self.id.as_ref());
}); });
match key.into() { match key {
// esc or ctrl-c aborts the completion and closes the menu // esc or ctrl-c aborts the completion and closes the menu
key!(Esc) | ctrl!('c') => { key!(Esc) | ctrl!('c') => {
let _ = self.contents.handle_event(event, cx); let _ = self.contents.handle_event(event, cx);

@ -1,6 +1,5 @@
use crate::compositor::{Component, Compositor, Context, EventResult}; use crate::compositor::{Component, Compositor, Context, Event, EventResult};
use crate::{alt, ctrl, key, shift, ui}; use crate::{alt, ctrl, key, shift, ui};
use crossterm::event::Event;
use helix_view::input::KeyEvent; use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode; use helix_view::keyboard::KeyCode;
use std::{borrow::Cow, ops::RangeFrom}; use std::{borrow::Cow, ops::RangeFrom};
@ -479,7 +478,7 @@ impl Component for Prompt {
compositor.pop(); compositor.pop();
}))); })));
match event.into() { match event {
ctrl!('c') | key!(Esc) => { ctrl!('c') | key!(Esc) => {
(self.callback_fn)(cx, &self.line, PromptEvent::Abort); (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
return close_fn; return close_fn;
@ -533,16 +532,17 @@ impl Component for Prompt {
.map(|entry| entry.into()) .map(|entry| entry.into())
.unwrap_or_else(|| Cow::from("")) .unwrap_or_else(|| Cow::from(""))
} else { } else {
if let Some(register) = self.history_register {
// store in history
let register = cx.editor.registers.get_mut(register);
register.push(self.line.clone());
}
self.line.as_str().into() self.line.as_str().into()
}; };
(self.callback_fn)(cx, &input, PromptEvent::Validate); (self.callback_fn)(cx, &input, PromptEvent::Validate);
if let Some(register) = self.history_register {
// store in history
let register = cx.editor.registers.get_mut(register);
register.push(self.line.clone());
}
return close_fn; return close_fn;
} }
} }

@ -1,4 +1,4 @@
use helix_core::{coords_at_pos, encoding}; use helix_core::{coords_at_pos, encoding, Position};
use helix_view::{ use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME}, document::{Mode, SCRATCH_BUFFER_NAME},
graphics::Rect, graphics::Rect,
@ -143,6 +143,9 @@ where
helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics, helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics,
helix_view::editor::StatusLineElement::Selections => render_selections, helix_view::editor::StatusLineElement::Selections => render_selections,
helix_view::editor::StatusLineElement::Position => render_position, helix_view::editor::StatusLineElement::Position => render_position,
helix_view::editor::StatusLineElement::PositionPercentage => render_position_percentage,
helix_view::editor::StatusLineElement::Separator => render_separator,
helix_view::editor::StatusLineElement::Spacer => render_spacer,
} }
} }
@ -250,19 +253,22 @@ where
); );
} }
fn render_position<F>(context: &mut RenderContext, write: F) fn get_position(context: &RenderContext) -> Position {
where coords_at_pos(
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let position = coords_at_pos(
context.doc.text().slice(..), context.doc.text().slice(..),
context context
.doc .doc
.selection(context.view.id) .selection(context.view.id)
.primary() .primary()
.cursor(context.doc.text().slice(..)), .cursor(context.doc.text().slice(..)),
); )
}
fn render_position<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let position = get_position(context);
write( write(
context, context,
format!(" {}:{} ", position.row + 1, position.col + 1), format!(" {}:{} ", position.row + 1, position.col + 1),
@ -270,6 +276,19 @@ where
); );
} }
fn render_position_percentage<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let position = get_position(context);
let maxrows = context.doc.text().len_lines();
write(
context,
format!("{}%", (position.row + 1) * 100 / maxrows),
None,
);
}
fn render_file_encoding<F>(context: &mut RenderContext, write: F) fn render_file_encoding<F>(context: &mut RenderContext, write: F)
where where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy, F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
@ -334,3 +353,23 @@ where
write(context, title, None); write(context, title, None);
} }
fn render_separator<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let sep = &context.editor.config().statusline.separator;
write(
context,
sep.to_string(),
Some(context.editor.theme.get("ui.statusline.separator")),
);
}
fn render_spacer<F>(context: &mut RenderContext, write: F)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
write(context, String::from(" "), None);
}

@ -19,7 +19,7 @@ default = ["crossterm"]
bitflags = "1.3" bitflags = "1.3"
cassowary = "0.3" cassowary = "0.3"
unicode-segmentation = "1.9" unicode-segmentation = "1.9"
crossterm = { version = "0.24", optional = true } crossterm = { version = "0.25", optional = true }
serde = { version = "1", "optional" = true, features = ["derive"]} serde = { version = "1", "optional" = true, features = ["derive"]}
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
helix-core = { version = "0.6", path = "../helix-core" } helix-core = { version = "0.6", path = "../helix-core" }

@ -19,13 +19,13 @@ anyhow = "1"
helix-core = { version = "0.6", path = "../helix-core" } helix-core = { version = "0.6", path = "../helix-core" }
helix-lsp = { version = "0.6", path = "../helix-lsp" } helix-lsp = { version = "0.6", path = "../helix-lsp" }
helix-dap = { version = "0.6", path = "../helix-dap" } helix-dap = { version = "0.6", path = "../helix-dap" }
crossterm = { version = "0.24", optional = true } crossterm = { version = "0.25", optional = true }
# Conversion traits # Conversion traits
once_cell = "1.13" once_cell = "1.13"
url = "2" url = "2"
arc-swap = { version = "1.5.0" } arc-swap = { version = "1.5.1" }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
tokio-stream = "0.1" tokio-stream = "0.1"

@ -17,6 +17,10 @@ pub trait ClipboardProvider: std::fmt::Debug {
#[cfg(not(windows))] #[cfg(not(windows))]
macro_rules! command_provider { macro_rules! command_provider {
(paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{ (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
log::info!(
"Using {} to interact with the system clipboard",
if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
);
Box::new(provider::command::Provider { Box::new(provider::command::Provider {
get_cmd: provider::command::Config { get_cmd: provider::command::Config {
prg: $get_prg, prg: $get_prg,
@ -36,6 +40,10 @@ macro_rules! command_provider {
primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ; primary_paste => $pr_get_prg:literal $( , $pr_get_arg:literal )* ;
primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ; primary_copy => $pr_set_prg:literal $( , $pr_set_arg:literal )* ;
) => {{ ) => {{
log::info!(
"Using {} to interact with the system and selection (primary) clipboard",
if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
);
Box::new(provider::command::Provider { Box::new(provider::command::Provider {
get_cmd: provider::command::Config { get_cmd: provider::command::Config {
prg: $get_prg, prg: $get_prg,
@ -131,7 +139,7 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> {
} }
} }
mod provider { pub mod provider {
use super::{ClipboardProvider, ClipboardType}; use super::{ClipboardProvider, ClipboardType};
use anyhow::Result; use anyhow::Result;
use std::borrow::Cow; use std::borrow::Cow;
@ -146,6 +154,9 @@ mod provider {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
impl NopProvider { impl NopProvider {
pub fn new() -> Self { pub fn new() -> Self {
log::warn!(
"No clipboard provider found! Yanking and pasting will be internal to Helix"
);
Self { Self {
buf: String::new(), buf: String::new(),
primary_buf: String::new(), primary_buf: String::new(),
@ -153,6 +164,13 @@ mod provider {
} }
} }
#[cfg(not(target_os = "windows"))]
impl Default for NopProvider {
fn default() -> Self {
Self::new()
}
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
impl ClipboardProvider for NopProvider { impl ClipboardProvider for NopProvider {
fn name(&self) -> Cow<str> { fn name(&self) -> Cow<str> {
@ -184,6 +202,7 @@ mod provider {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
impl ClipboardProvider for WindowsProvider { impl ClipboardProvider for WindowsProvider {
fn name(&self) -> Cow<str> { fn name(&self) -> Cow<str> {
log::info!("Using clipboard-win to interact with the system clipboard");
Cow::Borrowed("clipboard-win") Cow::Borrowed("clipboard-win")
} }
@ -213,12 +232,11 @@ mod provider {
use super::*; use super::*;
use anyhow::{bail, Context as _, Result}; use anyhow::{bail, Context as _, Result};
#[cfg(not(windows))]
pub fn exists(executable_name: &str) -> bool { pub fn exists(executable_name: &str) -> bool {
which::which(executable_name).is_ok() which::which(executable_name).is_ok()
} }
#[cfg(not(any(windows, target_os = "macos")))] #[cfg(not(windows))]
pub fn env_var_is_set(env_var_name: &str) -> bool { pub fn env_var_is_set(env_var_name: &str) -> bool {
std::env::var_os(env_var_name).is_some() std::env::var_os(env_var_name).is_some()
} }

@ -1,4 +1,6 @@
use anyhow::{anyhow, bail, Context, Error}; use anyhow::{anyhow, bail, Context, Error};
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use helix_core::auto_pairs::AutoPairs; use helix_core::auto_pairs::AutoPairs;
use helix_core::Range; use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer}; use serde::de::{self, Deserialize, Deserializer};
@ -20,7 +22,6 @@ use helix_core::{
ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction, ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
DEFAULT_LINE_ENDING, DEFAULT_LINE_ENDING,
}; };
use helix_lsp::util::LspFormatting;
use crate::{DocumentId, Editor, ViewId}; use crate::{DocumentId, Editor, ViewId};
@ -397,7 +398,7 @@ impl Document {
/// The same as [`format`], but only returns formatting changes if auto-formatting /// The same as [`format`], but only returns formatting changes if auto-formatting
/// is configured. /// is configured.
pub fn auto_format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> { pub fn auto_format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
if self.language_config()?.auto_format { if self.language_config()?.auto_format {
self.format() self.format()
} else { } else {
@ -407,7 +408,56 @@ impl Document {
/// If supported, returns the changes that should be applied to this document in order /// If supported, returns the changes that should be applied to this document in order
/// to format it nicely. /// to format it nicely.
pub fn format(&self) -> Option<impl Future<Output = LspFormatting> + 'static> { // We can't use anyhow::Result here since the output of the future has to be
// clonable to be used as shared future. So use a custom error type.
pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
if let Some(formatter) = self.language_config().and_then(|c| c.formatter.clone()) {
use std::process::Stdio;
let text = self.text().clone();
let mut process = tokio::process::Command::new(&formatter.command);
process
.args(&formatter.args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let formatting_future = async move {
let mut process = process
.spawn()
.map_err(|e| FormatterError::SpawningFailed {
command: formatter.command.clone(),
error: e.kind(),
})?;
{
let mut stdin = process.stdin.take().ok_or(FormatterError::BrokenStdin)?;
to_writer(&mut stdin, encoding::UTF_8, &text)
.await
.map_err(|_| FormatterError::BrokenStdin)?;
}
let output = process
.wait_with_output()
.await
.map_err(|_| FormatterError::WaitForOutputFailed)?;
if !output.stderr.is_empty() {
return Err(FormatterError::Stderr(
String::from_utf8_lossy(&output.stderr).to_string(),
));
}
if !output.status.success() {
return Err(FormatterError::NonZeroExitStatus);
}
let str = String::from_utf8(output.stdout)
.map_err(|_| FormatterError::InvalidUtf8Output)?;
Ok(helix_core::diff::compare_ropes(&text, &Rope::from(str)))
};
return Some(formatting_future.boxed());
};
let language_server = self.language_server()?; let language_server = self.language_server()?;
let text = self.text.clone(); let text = self.text.clone();
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
@ -427,13 +477,13 @@ impl Document {
log::warn!("LSP formatting failed: {}", e); log::warn!("LSP formatting failed: {}", e);
Default::default() Default::default()
}); });
LspFormatting { Ok(helix_lsp::util::generate_transaction_from_edits(
doc: text, &text,
edits, edits,
offset_encoding, offset_encoding,
} ))
}; };
Some(fut) Some(fut.boxed())
} }
pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> { pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
@ -442,7 +492,7 @@ impl Document {
pub fn format_and_save( pub fn format_and_save(
&mut self, &mut self,
formatting: Option<impl Future<Output = LspFormatting>>, formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
force: bool, force: bool,
) -> impl Future<Output = anyhow::Result<()>> { ) -> impl Future<Output = anyhow::Result<()>> {
self.save_impl(formatting, force) self.save_impl(formatting, force)
@ -454,7 +504,7 @@ impl Document {
/// at its `path()`. /// at its `path()`.
/// ///
/// If `formatting` is present, it supplies some changes that we apply to the text before saving. /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
fn save_impl<F: Future<Output = LspFormatting>>( fn save_impl<F: Future<Output = Result<Transaction, FormatterError>>>(
&mut self, &mut self,
formatting: Option<F>, formatting: Option<F>,
force: bool, force: bool,
@ -488,7 +538,8 @@ impl Document {
} }
if let Some(fmt) = formatting { if let Some(fmt) = formatting {
let success = Transaction::from(fmt.await).changes().apply(&mut text); let transaction = fmt.await?;
let success = transaction.changes().apply(&mut text);
if !success { if !success {
// This shouldn't happen, because the transaction changes were generated // This shouldn't happen, because the transaction changes were generated
// from the same text we're saving. // from the same text we're saving.
@ -525,9 +576,8 @@ impl Document {
} }
/// Detect the indentation used in the file, or otherwise defaults to the language indentation /// Detect the indentation used in the file, or otherwise defaults to the language indentation
/// configured in `languages.toml`, with a fallback to 4 space indentation if it isn't /// configured in `languages.toml`, with a fallback to tabs if it isn't specified. Line ending
/// specified. Line ending is likewise auto-detected, and will fallback to the default OS /// is likewise auto-detected, and will fallback to the default OS line ending.
/// line ending.
pub fn detect_indent_and_line_ending(&mut self) { pub fn detect_indent_and_line_ending(&mut self) {
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| { self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
self.language_config() self.language_config()
@ -1034,6 +1084,38 @@ impl Default for Document {
} }
} }
#[derive(Clone, Debug)]
pub enum FormatterError {
SpawningFailed {
command: String,
error: std::io::ErrorKind,
},
BrokenStdin,
WaitForOutputFailed,
Stderr(String),
InvalidUtf8Output,
DiskReloadError(String),
NonZeroExitStatus,
}
impl std::error::Error for FormatterError {}
impl Display for FormatterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SpawningFailed { command, error } => {
write!(f, "Failed to spawn formatter {}: {:?}", command, error)
}
Self::BrokenStdin => write!(f, "Could not write to formatter stdin"),
Self::WaitForOutputFailed => write!(f, "Waiting for formatter output failed"),
Self::Stderr(output) => write!(f, "Formatter error: {}", output),
Self::InvalidUtf8Output => write!(f, "Invalid UTF-8 formatter output"),
Self::DiskReloadError(error) => write!(f, "Error reloading file from disk: {}", error),
Self::NonZeroExitStatus => write!(f, "Formatter exited with non zero exit status:"),
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

@ -220,6 +220,7 @@ pub struct Config {
#[serde(default)] #[serde(default)]
pub search: SearchConfig, pub search: SearchConfig,
pub lsp: LspConfig, pub lsp: LspConfig,
pub terminal: Option<TerminalConfig>,
/// Column numbers at which to draw the rulers. Default to `[]`, meaning no rulers. /// Column numbers at which to draw the rulers. Default to `[]`, meaning no rulers.
pub rulers: Vec<u16>, pub rulers: Vec<u16>,
#[serde(default)] #[serde(default)]
@ -232,6 +233,52 @@ pub struct Config {
pub explorer: ExplorerConfig, pub explorer: ExplorerConfig,
} }
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct TerminalConfig {
pub command: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub args: Vec<String>,
}
#[cfg(windows)]
pub fn get_terminal_provider() -> Option<TerminalConfig> {
use crate::clipboard::provider::command::exists;
if exists("wt") {
return Some(TerminalConfig {
command: "wt".to_string(),
args: vec![
"new-tab".to_string(),
"--title".to_string(),
"DEBUG".to_string(),
"cmd".to_string(),
"/C".to_string(),
],
});
}
return Some(TerminalConfig {
command: "conhost".to_string(),
args: vec!["cmd".to_string(), "/C".to_string()],
});
}
#[cfg(not(any(windows, target_os = "wasm32")))]
pub fn get_terminal_provider() -> Option<TerminalConfig> {
use crate::clipboard::provider::command::{env_var_is_set, exists};
if env_var_is_set("TMUX") && exists("tmux") {
return Some(TerminalConfig {
command: "tmux".to_string(),
args: vec!["split-window".to_string()],
});
}
None
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] #[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct LspConfig { pub struct LspConfig {
@ -268,6 +315,7 @@ pub struct StatusLineConfig {
pub left: Vec<StatusLineElement>, pub left: Vec<StatusLineElement>,
pub center: Vec<StatusLineElement>, pub center: Vec<StatusLineElement>,
pub right: Vec<StatusLineElement>, pub right: Vec<StatusLineElement>,
pub separator: String,
} }
impl Default for StatusLineConfig { impl Default for StatusLineConfig {
@ -278,6 +326,7 @@ impl Default for StatusLineConfig {
left: vec![E::Mode, E::Spinner, E::FileName], left: vec![E::Mode, E::Spinner, E::FileName],
center: vec![], center: vec![],
right: vec![E::Diagnostics, E::Selections, E::Position, E::FileEncoding], right: vec![E::Diagnostics, E::Selections, E::Position, E::FileEncoding],
separator: String::from("│"),
} }
} }
} }
@ -311,6 +360,15 @@ pub enum StatusLineElement {
/// The cursor position /// The cursor position
Position, Position,
/// The separator string
Separator,
/// The cursor position as a percent of the total file
PositionPercentage,
/// A single space
Spacer,
} }
// Cursor shape is read and used on every rendered frame and so needs // Cursor shape is read and used on every rendered frame and so needs
@ -398,7 +456,7 @@ pub enum GutterType {
/// Show line numbers /// Show line numbers
LineNumbers, LineNumbers,
/// Show one blank space /// Show one blank space
Padding, Spacer,
} }
impl std::str::FromStr for GutterType { impl std::str::FromStr for GutterType {
@ -492,6 +550,7 @@ pub struct WhitespaceCharacters {
pub space: char, pub space: char,
pub nbsp: char, pub nbsp: char,
pub tab: char, pub tab: char,
pub tabpad: char,
pub newline: char, pub newline: char,
} }
@ -502,6 +561,7 @@ impl Default for WhitespaceCharacters {
nbsp: '', // U+237D nbsp: '', // U+237D
tab: '', // U+2192 tab: '', // U+2192
newline: '', // U+23CE newline: '', // U+23CE
tabpad: ' ',
} }
} }
} }
@ -535,11 +595,7 @@ impl Default for Config {
}, },
line_number: LineNumber::Absolute, line_number: LineNumber::Absolute,
cursorline: false, cursorline: false,
gutters: vec![ gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
GutterType::Diagnostics,
GutterType::LineNumbers,
GutterType::Padding,
],
middle_click_paste: true, middle_click_paste: true,
auto_pairs: AutoPairConfig::default(), auto_pairs: AutoPairConfig::default(),
auto_completion: true, auto_completion: true,
@ -553,6 +609,7 @@ impl Default for Config {
true_color: false, true_color: false,
search: SearchConfig::default(), search: SearchConfig::default(),
lsp: LspConfig::default(), lsp: LspConfig::default(),
terminal: get_terminal_provider(),
rulers: Vec::new(), rulers: Vec::new(),
whitespace: WhitespaceConfig::default(), whitespace: WhitespaceConfig::default(),
indent_guides: IndentGuidesConfig::default(), indent_guides: IndentGuidesConfig::default(),
@ -672,7 +729,6 @@ impl Editor {
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<syntax::Loader>,
config: Box<dyn DynAccess<Config>>, config: Box<dyn DynAccess<Config>>,
) -> Self { ) -> Self {
let language_servers = helix_lsp::Registry::new();
let conf = config.load(); let conf = config.load();
let auto_pairs = (&conf.auto_pairs).into(); let auto_pairs = (&conf.auto_pairs).into();
@ -688,7 +744,7 @@ impl Editor {
macro_recording: None, macro_recording: None,
macro_replaying: Vec::new(), macro_replaying: Vec::new(),
theme: theme_loader.default(), theme: theme_loader.default(),
language_servers, language_servers: helix_lsp::Registry::new(),
diagnostics: BTreeMap::new(), diagnostics: BTreeMap::new(),
debugger: None, debugger: None,
debugger_events: SelectAll::new(), debugger_events: SelectAll::new(),
@ -1118,40 +1174,37 @@ impl Editor {
}; };
} }
pub fn focus_next(&mut self) { pub fn focus(&mut self, view_id: ViewId) {
self.tree.focus_next(); let prev_id = std::mem::replace(&mut self.tree.focus, view_id);
}
pub fn focus_right(&mut self) {
self.tree.focus_direction(tree::Direction::Right);
}
pub fn focus_left(&mut self) {
self.tree.focus_direction(tree::Direction::Left);
}
pub fn focus_up(&mut self) { // if leaving the view: mode should reset
self.tree.focus_direction(tree::Direction::Up); if prev_id != view_id {
} let doc_id = self.tree.get(prev_id).doc;
self.documents.get_mut(&doc_id).unwrap().mode = Mode::Normal;
pub fn focus_down(&mut self) { }
self.tree.focus_direction(tree::Direction::Down);
} }
pub fn swap_right(&mut self) { pub fn focus_next(&mut self) {
self.tree.swap_split_in_direction(tree::Direction::Right); let prev_id = self.tree.focus;
} self.tree.focus_next();
let id = self.tree.focus;
pub fn swap_left(&mut self) { // if leaving the view: mode should reset
self.tree.swap_split_in_direction(tree::Direction::Left); if prev_id != id {
let doc_id = self.tree.get(prev_id).doc;
self.documents.get_mut(&doc_id).unwrap().mode = Mode::Normal;
}
} }
pub fn swap_up(&mut self) { pub fn focus_direction(&mut self, direction: tree::Direction) {
self.tree.swap_split_in_direction(tree::Direction::Up); let current_view = self.tree.focus;
if let Some(id) = self.tree.find_split_in_direction(current_view, direction) {
self.focus(id)
}
} }
pub fn swap_down(&mut self) { pub fn swap_split_in_direction(&mut self, direction: tree::Direction) {
self.tree.swap_split_in_direction(tree::Direction::Down); self.tree.swap_split_in_direction(direction);
} }
pub fn transpose_view(&mut self) { pub fn transpose_view(&mut self) {

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@ use helix_core::Selection;
use helix_dap::{self as dap, Client, Payload, Request, ThreadId}; use helix_dap::{self as dap, Client, Payload, Request, ThreadId};
use helix_lsp::block_on; use helix_lsp::block_on;
use log::warn; use log::warn;
use std::io::ErrorKind; use std::fmt::Write;
use std::path::PathBuf; use std::path::PathBuf;
#[macro_export] #[macro_export]
@ -180,10 +180,10 @@ impl Editor {
let mut status = format!("{} stopped because of {}", scope, reason); let mut status = format!("{} stopped because of {}", scope, reason);
if let Some(desc) = description { if let Some(desc) = description {
status.push_str(&format!(" {}", desc)); write!(status, " {}", desc).unwrap();
} }
if let Some(text) = text { if let Some(text) = text {
status.push_str(&format!(" {}", text)); write!(status, " {}", text).unwrap();
} }
if all_threads_stopped { if all_threads_stopped {
status.push_str(" (all threads stopped)"); status.push_str(" (all threads stopped)");
@ -286,32 +286,32 @@ impl Editor {
serde_json::from_value(request.arguments.unwrap_or_default()).unwrap(); serde_json::from_value(request.arguments.unwrap_or_default()).unwrap();
// TODO: no unwrap // TODO: no unwrap
let process = if cfg!(windows) { let config = match self.config().terminal.clone() {
std::process::Command::new("wt") Some(config) => config,
.arg("new-tab") None => {
.arg("--title") self.set_error("No external terminal defined");
.arg("DEBUG") return true;
.arg("cmd") }
.arg("/C") };
.arg(arguments.args.join(" "))
.spawn() // Re-borrowing debugger to avoid issues when loading config
.unwrap_or_else(|error| match error.kind() { let debugger = match self.debugger.as_mut() {
ErrorKind::NotFound => std::process::Command::new("conhost") Some(debugger) => debugger,
.arg("cmd") None => return false,
.arg("/C") };
.arg(arguments.args.join(" "))
.spawn() let process = match std::process::Command::new(config.command)
.unwrap(), .args(config.args)
// TODO replace the pretty print {:?} with a regular format {} .arg(arguments.args.join(" "))
// when the MSRV is raised to 1.60.0 .spawn()
e => panic!("Error to start debug console: {:?}", e), {
}) Ok(process) => process,
} else { Err(err) => {
std::process::Command::new("tmux") // TODO replace the pretty print {:?} with a regular format {}
.arg("split-window") // when the MSRV is raised to 1.60.0
.arg(arguments.args.join(" ")) self.set_error(format!("Error starting external terminal: {:?}", err));
.spawn() return true;
.unwrap() }
}; };
let _ = debugger let _ = debugger

@ -4,14 +4,62 @@ use helix_core::unicode::{segmentation::UnicodeSegmentation, width::UnicodeWidth
use serde::de::{self, Deserialize, Deserializer}; use serde::de::{self, Deserialize, Deserializer};
use std::fmt; use std::fmt;
use crate::keyboard::{KeyCode, KeyModifiers}; pub use crate::keyboard::{KeyCode, KeyModifiers};
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Event {
FocusGained,
FocusLost,
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u16, u16),
}
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub struct MouseEvent {
/// The kind of mouse event that was caused.
pub kind: MouseEventKind,
/// The column that the event occurred on.
pub column: u16,
/// The row that the event occurred on.
pub row: u16,
/// The key modifiers active when the event occurred.
pub modifiers: KeyModifiers,
}
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MouseEventKind {
/// Pressed mouse button. Contains the button that was pressed.
Down(MouseButton),
/// Released mouse button. Contains the button that was released.
Up(MouseButton),
/// Moved the mouse cursor while pressing the contained mouse button.
Drag(MouseButton),
/// Moved the mouse cursor while not pressing a mouse button.
Moved,
/// Scrolled mouse wheel downwards (towards the user).
ScrollDown,
/// Scrolled mouse wheel upwards (away from the user).
ScrollUp,
}
/// Represents a mouse button.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MouseButton {
/// Left mouse button.
Left,
/// Right mouse button.
Right,
/// Middle mouse button.
Middle,
}
/// Represents a key event. /// Represents a key event.
// We use a newtype here because we want to customize Deserialize and Display. // We use a newtype here because we want to customize Deserialize and Display.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct KeyEvent { pub struct KeyEvent {
pub code: KeyCode, pub code: KeyCode,
pub modifiers: KeyModifiers, pub modifiers: KeyModifiers,
// TODO: crossterm now supports kind & state if terminal supports kitty's extended protocol
} }
impl KeyEvent { impl KeyEvent {
@ -219,9 +267,73 @@ impl<'de> Deserialize<'de> for KeyEvent {
} }
} }
#[cfg(feature = "term")]
impl From<crossterm::event::Event> for Event {
fn from(event: crossterm::event::Event) -> Self {
match event {
crossterm::event::Event::Key(key) => Self::Key(key.into()),
crossterm::event::Event::Mouse(mouse) => Self::Mouse(mouse.into()),
crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
crossterm::event::Event::FocusGained => Self::FocusGained,
crossterm::event::Event::FocusLost => Self::FocusLost,
crossterm::event::Event::Paste(_) => {
unreachable!("crossterm shouldn't emit Paste events without them being enabled")
}
}
}
}
#[cfg(feature = "term")]
impl From<crossterm::event::MouseEvent> for MouseEvent {
fn from(
crossterm::event::MouseEvent {
kind,
column,
row,
modifiers,
}: crossterm::event::MouseEvent,
) -> Self {
Self {
kind: kind.into(),
column,
row,
modifiers: modifiers.into(),
}
}
}
#[cfg(feature = "term")]
impl From<crossterm::event::MouseEventKind> for MouseEventKind {
fn from(kind: crossterm::event::MouseEventKind) -> Self {
match kind {
crossterm::event::MouseEventKind::Down(button) => Self::Down(button.into()),
crossterm::event::MouseEventKind::Up(button) => Self::Down(button.into()),
crossterm::event::MouseEventKind::Drag(button) => Self::Drag(button.into()),
crossterm::event::MouseEventKind::Moved => Self::Moved,
crossterm::event::MouseEventKind::ScrollDown => Self::ScrollDown,
crossterm::event::MouseEventKind::ScrollUp => Self::ScrollUp,
}
}
}
#[cfg(feature = "term")]
impl From<crossterm::event::MouseButton> for MouseButton {
fn from(button: crossterm::event::MouseButton) -> Self {
match button {
crossterm::event::MouseButton::Left => MouseButton::Left,
crossterm::event::MouseButton::Right => MouseButton::Right,
crossterm::event::MouseButton::Middle => MouseButton::Middle,
}
}
}
#[cfg(feature = "term")] #[cfg(feature = "term")]
impl From<crossterm::event::KeyEvent> for KeyEvent { impl From<crossterm::event::KeyEvent> for KeyEvent {
fn from(crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent) -> Self { fn from(
crossterm::event::KeyEvent {
code, modifiers, ..
}: crossterm::event::KeyEvent,
) -> Self {
if code == crossterm::event::KeyCode::BackTab { if code == crossterm::event::KeyCode::BackTab {
// special case for BackTab -> Shift-Tab // special case for BackTab -> Shift-Tab
let mut modifiers: KeyModifiers = modifiers.into(); let mut modifiers: KeyModifiers = modifiers.into();
@ -249,11 +361,15 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
crossterm::event::KeyEvent { crossterm::event::KeyEvent {
code: crossterm::event::KeyCode::BackTab, code: crossterm::event::KeyCode::BackTab,
modifiers: modifiers.into(), modifiers: modifiers.into(),
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
} }
} else { } else {
crossterm::event::KeyEvent { crossterm::event::KeyEvent {
code: code.into(), code: code.into(),
modifiers: modifiers.into(), modifiers: modifiers.into(),
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
} }
} }
} }

@ -1,153 +1,163 @@
use bitflags::bitflags; use bitflags::bitflags;
bitflags! { bitflags! {
/// Represents key modifiers (shift, control, alt). /// Represents key modifiers (shift, control, alt).
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyModifiers: u8 { pub struct KeyModifiers: u8 {
const SHIFT = 0b0000_0001; const SHIFT = 0b0000_0001;
const CONTROL = 0b0000_0010; const CONTROL = 0b0000_0010;
const ALT = 0b0000_0100; const ALT = 0b0000_0100;
const NONE = 0b0000_0000; const NONE = 0b0000_0000;
} }
} }
#[cfg(feature = "term")] #[cfg(feature = "term")]
impl From<KeyModifiers> for crossterm::event::KeyModifiers { impl From<KeyModifiers> for crossterm::event::KeyModifiers {
fn from(key_modifiers: KeyModifiers) -> Self { fn from(key_modifiers: KeyModifiers) -> Self {
use crossterm::event::KeyModifiers as CKeyModifiers; use crossterm::event::KeyModifiers as CKeyModifiers;
let mut result = CKeyModifiers::NONE; let mut result = CKeyModifiers::NONE;
if key_modifiers.contains(KeyModifiers::SHIFT) { if key_modifiers.contains(KeyModifiers::SHIFT) {
result.insert(CKeyModifiers::SHIFT); result.insert(CKeyModifiers::SHIFT);
} }
if key_modifiers.contains(KeyModifiers::CONTROL) { if key_modifiers.contains(KeyModifiers::CONTROL) {
result.insert(CKeyModifiers::CONTROL); result.insert(CKeyModifiers::CONTROL);
} }
if key_modifiers.contains(KeyModifiers::ALT) { if key_modifiers.contains(KeyModifiers::ALT) {
result.insert(CKeyModifiers::ALT); result.insert(CKeyModifiers::ALT);
} }
result result
} }
} }
#[cfg(feature = "term")] #[cfg(feature = "term")]
impl From<crossterm::event::KeyModifiers> for KeyModifiers { impl From<crossterm::event::KeyModifiers> for KeyModifiers {
fn from(val: crossterm::event::KeyModifiers) -> Self { fn from(val: crossterm::event::KeyModifiers) -> Self {
use crossterm::event::KeyModifiers as CKeyModifiers; use crossterm::event::KeyModifiers as CKeyModifiers;
let mut result = KeyModifiers::NONE; let mut result = KeyModifiers::NONE;
if val.contains(CKeyModifiers::SHIFT) { if val.contains(CKeyModifiers::SHIFT) {
result.insert(KeyModifiers::SHIFT); result.insert(KeyModifiers::SHIFT);
} }
if val.contains(CKeyModifiers::CONTROL) { if val.contains(CKeyModifiers::CONTROL) {
result.insert(KeyModifiers::CONTROL); result.insert(KeyModifiers::CONTROL);
} }
if val.contains(CKeyModifiers::ALT) { if val.contains(CKeyModifiers::ALT) {
result.insert(KeyModifiers::ALT); result.insert(KeyModifiers::ALT);
} }
result result
} }
} }
/// Represents a key. /// Represents a key.
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyCode {
pub enum KeyCode { /// Backspace key.
/// Backspace key. Backspace,
Backspace, /// Enter key.
/// Enter key. Enter,
Enter, /// Left arrow key.
/// Left arrow key. Left,
Left, /// Right arrow key.
/// Right arrow key. Right,
Right, /// Up arrow key.
/// Up arrow key. Up,
Up, /// Down arrow key.
/// Down arrow key. Down,
Down, /// Home key.
/// Home key. Home,
Home, /// End key.
/// End key. End,
End, /// Page up key.
/// Page up key. PageUp,
PageUp, /// Page down key.
/// Page down key. PageDown,
PageDown, /// Tab key.
/// Tab key. Tab,
Tab, /// Delete key.
/// Delete key. Delete,
Delete, /// Insert key.
/// Insert key. Insert,
Insert, /// F key.
/// F key. ///
/// /// `KeyCode::F(1)` represents F1 key, etc.
/// `KeyCode::F(1)` represents F1 key, etc. F(u8),
F(u8), /// A character.
/// A character. ///
/// /// `KeyCode::Char('c')` represents `c` character, etc.
/// `KeyCode::Char('c')` represents `c` character, etc. Char(char),
Char(char), /// Null.
/// Null. Null,
Null, /// Escape key.
/// Escape key. Esc,
Esc, }
}
#[cfg(feature = "term")]
#[cfg(feature = "term")] impl From<KeyCode> for crossterm::event::KeyCode {
impl From<KeyCode> for crossterm::event::KeyCode { fn from(key_code: KeyCode) -> Self {
fn from(key_code: KeyCode) -> Self { use crossterm::event::KeyCode as CKeyCode;
use crossterm::event::KeyCode as CKeyCode;
match key_code {
match key_code { KeyCode::Backspace => CKeyCode::Backspace,
KeyCode::Backspace => CKeyCode::Backspace, KeyCode::Enter => CKeyCode::Enter,
KeyCode::Enter => CKeyCode::Enter, KeyCode::Left => CKeyCode::Left,
KeyCode::Left => CKeyCode::Left, KeyCode::Right => CKeyCode::Right,
KeyCode::Right => CKeyCode::Right, KeyCode::Up => CKeyCode::Up,
KeyCode::Up => CKeyCode::Up, KeyCode::Down => CKeyCode::Down,
KeyCode::Down => CKeyCode::Down, KeyCode::Home => CKeyCode::Home,
KeyCode::Home => CKeyCode::Home, KeyCode::End => CKeyCode::End,
KeyCode::End => CKeyCode::End, KeyCode::PageUp => CKeyCode::PageUp,
KeyCode::PageUp => CKeyCode::PageUp, KeyCode::PageDown => CKeyCode::PageDown,
KeyCode::PageDown => CKeyCode::PageDown, KeyCode::Tab => CKeyCode::Tab,
KeyCode::Tab => CKeyCode::Tab, KeyCode::Delete => CKeyCode::Delete,
KeyCode::Delete => CKeyCode::Delete, KeyCode::Insert => CKeyCode::Insert,
KeyCode::Insert => CKeyCode::Insert, KeyCode::F(f_number) => CKeyCode::F(f_number),
KeyCode::F(f_number) => CKeyCode::F(f_number), KeyCode::Char(character) => CKeyCode::Char(character),
KeyCode::Char(character) => CKeyCode::Char(character), KeyCode::Null => CKeyCode::Null,
KeyCode::Null => CKeyCode::Null, KeyCode::Esc => CKeyCode::Esc,
KeyCode::Esc => CKeyCode::Esc, }
} }
} }
}
#[cfg(feature = "term")]
#[cfg(feature = "term")] impl From<crossterm::event::KeyCode> for KeyCode {
impl From<crossterm::event::KeyCode> for KeyCode { fn from(val: crossterm::event::KeyCode) -> Self {
fn from(val: crossterm::event::KeyCode) -> Self { use crossterm::event::KeyCode as CKeyCode;
use crossterm::event::KeyCode as CKeyCode;
match val {
match val { CKeyCode::Backspace => KeyCode::Backspace,
CKeyCode::Backspace => KeyCode::Backspace, CKeyCode::Enter => KeyCode::Enter,
CKeyCode::Enter => KeyCode::Enter, CKeyCode::Left => KeyCode::Left,
CKeyCode::Left => KeyCode::Left, CKeyCode::Right => KeyCode::Right,
CKeyCode::Right => KeyCode::Right, CKeyCode::Up => KeyCode::Up,
CKeyCode::Up => KeyCode::Up, CKeyCode::Down => KeyCode::Down,
CKeyCode::Down => KeyCode::Down, CKeyCode::Home => KeyCode::Home,
CKeyCode::Home => KeyCode::Home, CKeyCode::End => KeyCode::End,
CKeyCode::End => KeyCode::End, CKeyCode::PageUp => KeyCode::PageUp,
CKeyCode::PageUp => KeyCode::PageUp, CKeyCode::PageDown => KeyCode::PageDown,
CKeyCode::PageDown => KeyCode::PageDown, CKeyCode::Tab => KeyCode::Tab,
CKeyCode::Tab => KeyCode::Tab, CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"), CKeyCode::Delete => KeyCode::Delete,
CKeyCode::Delete => KeyCode::Delete, CKeyCode::Insert => KeyCode::Insert,
CKeyCode::Insert => KeyCode::Insert, CKeyCode::F(f_number) => KeyCode::F(f_number),
CKeyCode::F(f_number) => KeyCode::F(f_number), CKeyCode::Char(character) => KeyCode::Char(character),
CKeyCode::Char(character) => KeyCode::Char(character), CKeyCode::Null => KeyCode::Null,
CKeyCode::Null => KeyCode::Null, CKeyCode::Esc => KeyCode::Esc,
CKeyCode::Esc => KeyCode::Esc, CKeyCode::CapsLock
} | CKeyCode::ScrollLock
} | CKeyCode::NumLock
} | CKeyCode::PrintScreen
| CKeyCode::Pause
| CKeyCode::Menu
| CKeyCode::KeypadBegin
| CKeyCode::Media(_)
| CKeyCode::Modifier(_) => unreachable!(
"Shouldn't get this key without enabling DISAMBIGUATE_ESCAPE_CODES in crossterm"
),
}
}
}

@ -504,12 +504,6 @@ impl Tree {
Some(child_id) Some(child_id)
} }
pub fn focus_direction(&mut self, direction: Direction) {
if let Some(id) = self.find_split_in_direction(self.focus, direction) {
self.focus = id;
}
}
pub fn focus_next(&mut self) { pub fn focus_next(&mut self) {
// This function is very dumb, but that's because we don't store any parent links. // This function is very dumb, but that's because we don't store any parent links.
// (we'd be able to go parent.next_sibling() recursively until we find something) // (we'd be able to go parent.next_sibling() recursively until we find something)

@ -106,18 +106,21 @@ impl View {
let width = match gutter_type { let width = match gutter_type {
GutterType::Diagnostics => 1, GutterType::Diagnostics => 1,
GutterType::LineNumbers => 5, GutterType::LineNumbers => 5,
GutterType::Padding => 1, GutterType::Spacer => 1,
}; };
gutter_offset += width; gutter_offset += width;
gutters.push(( gutters.push((
match gutter_type { match gutter_type {
GutterType::Diagnostics => gutter::diagnostics_or_breakpoints, GutterType::Diagnostics => gutter::diagnostics_or_breakpoints,
GutterType::LineNumbers => gutter::line_numbers, GutterType::LineNumbers => gutter::line_numbers,
GutterType::Padding => gutter::padding, GutterType::Spacer => gutter::padding,
}, },
width as usize, width as usize,
)); ));
} }
if !gutter_types.is_empty() {
gutter_offset += 1;
}
Self { Self {
id: ViewId::default(), id: ViewId::default(),
doc, doc,
@ -346,11 +349,7 @@ mod tests {
fn test_text_pos_at_screen_coords() { fn test_text_pos_at_screen_coords() {
let mut view = View::new( let mut view = View::new(
DocumentId::default(), DocumentId::default(),
vec![ vec![GutterType::Diagnostics, GutterType::LineNumbers],
GutterType::Diagnostics,
GutterType::LineNumbers,
GutterType::Padding,
],
); );
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
@ -397,10 +396,7 @@ mod tests {
#[test] #[test]
fn test_text_pos_at_screen_coords_without_line_numbers_gutter() { fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
let mut view = View::new( let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]);
DocumentId::default(),
vec![GutterType::Diagnostics, GutterType::Padding],
);
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
let text = rope.slice(..); let text = rope.slice(..);
@ -426,11 +422,7 @@ mod tests {
fn test_text_pos_at_screen_coords_cjk() { fn test_text_pos_at_screen_coords_cjk() {
let mut view = View::new( let mut view = View::new(
DocumentId::default(), DocumentId::default(),
vec![ vec![GutterType::Diagnostics, GutterType::LineNumbers],
GutterType::Diagnostics,
GutterType::LineNumbers,
GutterType::Padding,
],
); );
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hi! こんにちは皆さん"); let rope = Rope::from_str("Hi! こんにちは皆さん");
@ -470,11 +462,7 @@ mod tests {
fn test_text_pos_at_screen_coords_graphemes() { fn test_text_pos_at_screen_coords_graphemes() {
let mut view = View::new( let mut view = View::new(
DocumentId::default(), DocumentId::default(),
vec![ vec![GutterType::Diagnostics, GutterType::LineNumbers],
GutterType::Diagnostics,
GutterType::LineNumbers,
GutterType::Padding,
],
); );
view.area = Rect::new(40, 40, 40, 40); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hèl̀l̀ò world!"); let rope = Rope::from_str("Hèl̀l̀ò world!");

@ -217,7 +217,7 @@ file-types = ["cs"]
roots = ["sln", "csproj"] roots = ["sln", "csproj"]
comment-token = "//" comment-token = "//"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
language-server = { command = "OmniSharp", args = [ "--languageserver", "--stdio" ] } language-server = { command = "OmniSharp", args = [ "--languageserver" ] }
[[grammar]] [[grammar]]
name = "c-sharp" name = "c-sharp"
@ -285,6 +285,20 @@ indent = { tab-width = 4, unit = "\t" }
name = "gomod" name = "gomod"
source = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "e8f51f8e4363a3d9a427e8f63f4c1bbc5ef5d8d0" } source = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "e8f51f8e4363a3d9a427e8f63f4c1bbc5ef5d8d0" }
[[language]]
name = "gotmpl"
scope = "source.gotmpl"
injection-regex = "gotmpl"
file-types = ["gotmpl"]
roots = []
comment-token = "//"
language-server = { command = "gopls" }
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "gotmpl"
source = { git = "https://github.com/dannylongeuay/tree-sitter-go-template", rev = "395a33e08e69f4155156f0b90138a6c86764c979" }
[[language]] [[language]]
name = "gowork" name = "gowork"
scope = "source.gowork" scope = "source.gowork"
@ -423,7 +437,7 @@ indent = { tab-width = 4, unit = " " }
[[grammar]] [[grammar]]
name = "python" name = "python"
source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "d6210ceab11e8d812d4ab59c07c81458ec6e5184" } source = { git = "https://github.com/tree-sitter/tree-sitter-python", rev = "de221eccf9a221f5b85474a553474a69b4b5784d" }
[[language]] [[language]]
name = "nickel" name = "nickel"
@ -523,7 +537,7 @@ indent = { tab-width = 4, unit = "\t" }
[[grammar]] [[grammar]]
name = "latex" name = "latex"
source = { git = "https://github.com/latex-lsp/tree-sitter-latex", rev = "7f720661de5316c0f8fee956526d4002fa1086d8" } source = { git = "https://github.com/latex-lsp/tree-sitter-latex", rev = "b3b2cf27f33e71438ebe46934900b1153901c6f2" }
[[language]] [[language]]
name = "lean" name = "lean"
@ -585,6 +599,19 @@ indent = { tab-width = 4, unit = " " }
name = "ledger" name = "ledger"
source = { git = "https://github.com/cbarrete/tree-sitter-ledger", rev = "1f864fb2bf6a87fe1b48545cc6adc6d23090adf7" } source = { git = "https://github.com/cbarrete/tree-sitter-ledger", rev = "1f864fb2bf6a87fe1b48545cc6adc6d23090adf7" }
[[language]]
name = "beancount"
scope = "source.beancount"
injection-regex = "beancount"
file-types = ["beancount", "bean"]
roots = []
comment-token = ";"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "beancount"
source = { git = "https://github.com/polarmutex/tree-sitter-beancount", rev = "4cbd1f09cd07c1f1fabf867c2cf354f9da53cc4c" }
[[language]] [[language]]
name = "ocaml" name = "ocaml"
scope = "source.ocaml" scope = "source.ocaml"
@ -692,6 +719,8 @@ auto-format = true
comment-token = "//" comment-token = "//"
language-server = { command = "zls" } language-server = { command = "zls" }
indent = { tab-width = 4, unit = " " } indent = { tab-width = 4, unit = " " }
formatter = { command = "zig" , args = ["fmt", "--stdin"] }
[[grammar]] [[grammar]]
name = "zig" name = "zig"
@ -868,7 +897,19 @@ indent = { tab-width = 2, unit = " " }
[[grammar]] [[grammar]]
name = "markdown" name = "markdown"
source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "ab15701d8f3f68aeb74e30573b7d669a6ef2a7ed" } source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "142a5b4a1b092b64c9f5db8f11558f9dd4009a1b", subpath = "tree-sitter-markdown" }
[[language]]
name = "markdown.inline"
scope = "source.markdown.inline"
injection-regex = "markdown\\.inline"
file-types = []
roots = []
grammar = "markdown_inline"
[[grammar]]
name = "markdown_inline"
source = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "142a5b4a1b092b64c9f5db8f11558f9dd4009a1b", subpath = "tree-sitter-markdown-inline" }
[[language]] [[language]]
name = "dart" name = "dart"
@ -1025,7 +1066,7 @@ indent = { tab-width = 4, unit = " " }
[[grammar]] [[grammar]]
name = "elm" name = "elm"
source = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "bd50ccf66b42c55252ac8efc1086af4ac6bab8cd" } source = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "df4cb639c01b76bc9ac9cc66788709a6da20002c" }
[[language]] [[language]]
name = "iex" name = "iex"
@ -1116,7 +1157,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]] [[grammar]]
name = "org" name = "org"
source = { git = "https://github.com/milisims/tree-sitter-org", rev = "1c3eb533a9cf6800067357b59e03ac3f91fc3a54" } source = { git = "https://github.com/milisims/tree-sitter-org", rev = "698bb1a34331e68f83fc24bdd1b6f97016bb30de" }
[[language]] [[language]]
name = "solidity" name = "solidity"
@ -1193,7 +1234,7 @@ language-server = { command = "sourcekit-lsp" }
[[grammar]] [[grammar]]
name = "swift" name = "swift"
source = { git = "https://github.com/Dispersia/tree-sitter-swift", rev = "e75240f89bb3bfd3396155859ae364e5c58d7377" } source = { git = "https://github.com/alex-pinkus/tree-sitter-swift", rev = "77c6312c8438f4dbaa0350cec92b3d6dd3d74a66" }
[[language]] [[language]]
name = "erb" name = "erb"
@ -1354,7 +1395,8 @@ name = "odin"
auto-format = false auto-format = false
scope = "source.odin" scope = "source.odin"
file-types = ["odin"] file-types = ["odin"]
roots = [] roots = ["ols.json"]
language-server = { command = "ols", args = [] }
comment-token = "//" comment-token = "//"
indent = { tab-width = 4, unit = "\t" } indent = { tab-width = 4, unit = "\t" }
@ -1483,10 +1525,10 @@ source = { git = "https://github.com/victorhqc/tree-sitter-prisma", rev = "17a59
[[language]] [[language]]
name = "clojure" name = "clojure"
scope = "source.clojure" scope = "source.clojure"
injection-regex = "(clojure|clj)" injection-regex = "(clojure|clj|edn|boot)"
file-types = ["clj"] file-types = ["clj", "cljs", "cljc", "clje", "cljr", "cljx", "edn", "boot"]
roots = ["project.clj"] roots = ["project.clj", "build.boot", "deps.edn", "shadow-cljs.edn"]
comment-token = ";;" comment-token = ";"
language-server = { command = "clojure-lsp" } language-server = { command = "clojure-lsp" }
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
@ -1555,3 +1597,59 @@ indent = { tab-width = 2, unit = " " }
[[grammar]] [[grammar]]
name = "ungrammar" name = "ungrammar"
source = { git = "https://github.com/Philipp-M/tree-sitter-ungrammar", rev = "0113de880a58ea14f2a75802e9b99fcc25003d9c" } source = { git = "https://github.com/Philipp-M/tree-sitter-ungrammar", rev = "0113de880a58ea14f2a75802e9b99fcc25003d9c" }
[[language]]
name = "dot"
scope = "source.dot"
injection-regex = "dot"
file-types = ["dot"]
roots = []
comment-token = "//"
indent = { tab-width = 4, unit = " " }
language-server = { command = "dot-language-server", args = ["--stdio"] }
[[grammar]]
name = "dot"
source = { git = "https://github.com/rydesun/tree-sitter-dot", rev = "917230743aa10f45a408fea2ddb54bbbf5fbe7b7" }
[[language]]
name = "cue"
scope = "source.cue"
injection-regex = "cue"
file-types = ["cue"]
roots = ["cue.mod"]
auto-format = true
comment-token = "//"
language-server = { command = "cuelsp" }
indent = { tab-width = 4, unit = "\t" }
[[grammar]]
name = "cue"
source = { git = "https://github.com/eonpatapon/tree-sitter-cue", rev = "61843e3beebf19417e4fede4e8be4df1084317ad" }
[[language]]
name = "slint"
scope = "source.slint"
injection-regex = "slint"
file-types = ["slint"]
roots = []
comment-token = "//"
indent = { tab-width = 4, unit = " " }
language-server = { command = "slint-lsp", args = [] }
[[grammar]]
name = "slint"
source = { git = "https://github.com/jrmoulton/tree-sitter-slint", rev = "0d4dda94f96623302dfc234e06be62a5717f47da" }
[[language]]
name = "task"
scope = "source.task"
injection-regex = "task"
file-types = ["task"]
roots = []
comment-token = "#"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "task"
source = { git = "https://github.com/alexanderbrevig/tree-sitter-task", rev = "f2cb435c5dbf3ee19493e224485d977cb2d36d8b" }

@ -0,0 +1,4 @@
[
(transaction)
(section)
] @fold

@ -0,0 +1,49 @@
(date) @variable.builtin
(txn) @variable.builtin
(account) @type
[
(amount)
(incomplete_amount)
(amount_tolerance)
(number)
] @constant.numeric
[(key_value) (key)] @variable.other.member
(string) @string
[
(currency)
(tag)
(link)
] @constant
(comment) @comment
[
(minus)
(plus)
] @operator
[
(balance) (open) (close) (commodity) (pad)
(event) (price) (note) (document) (query)
(custom) (pushtag) (poptag) (pushmeta)
(popmeta) (option) (include) (plugin)
] @keyword
((headline item: (item) @markup.heading.6) @markup.heading.marker
(#match? @markup.heading.marker "^\\*\\*\\*\\*\\*\\*"))
((headline item: (item) @markup.heading.5) @markup.heading.marker
(#match? @markup.heading.marker "^\\*\\*\\*\\*\\*"))
((headline item: (item) @markup.heading.4) @markup.heading.marker
(#match? @markup.heading.marker "^\\*\\*\\*\\*"))
((headline item: (item) @markup.heading.3) @markup.heading.marker
(#match? @markup.heading.marker "^\\*\\*\\*"))
((headline item: (item) @markup.heading.2) @markup.heading.marker
(#match? @markup.heading.marker "^\\*\\*"))
((headline item: (item) @markup.heading.1) @markup.heading.marker
(#match? @markup.heading.marker "^\\*"))

@ -0,0 +1,110 @@
(package_clause "package" @keyword.control.import)
(package_identifier) @variable
(import_declaration "import" @keyword.control.import)
[
"!"
"*"
"|"
"&"
"||"
"&&"
"=="
"!="
"<"
"<="
">"
">="
"=~"
"!~"
"+"
"-"
"*"
"/"
] @operator
(unary_expression "*" @operator.default)
(unary_expression "=~" @operator.regexp)
(unary_expression "!~" @operator.regexp)
(binary_expression _ "&" @operator.unify _)
(binary_expression _ "|" @operator.disjunct _)
(builtin) @function.builtin
(qualified_identifier) @function.builtin
(let_clause "let" @keyword.storage.type)
(for_clause "for" @keyword.control.repeat)
(for_clause "in" @keyword.control.repeat)
(guard_clause "if" @keyword.control.conditional)
(comment) @comment
[
(string_type)
(simple_string_lit)
(multiline_string_lit)
(bytes_type)
(simple_bytes_lit)
(multiline_bytes_lit)
] @string
[
(number_type)
(int_lit)
(int_type)
(uint_type)
] @constant.numeric.integer
[
(float_lit)
(float_type)
] @constant.numeric.float
[
(bool_type)
(true)
(false)
] @constant.builtin.boolean
(null) @constant.builtin
(ellipsis) @punctuation.bracket
[
","
":"
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(interpolation "\\(" @punctuation.bracket (_) ")" @punctuation.bracket) @variable.other.member
(field (label (identifier) @variable.other.member))
(
(identifier) @keyword.storage.type
(#match? @keyword.storage.type "^#")
)
(field (label alias: (identifier) @label))
(let_clause left: (identifier) @label)
(attribute (identifier) @tag)

@ -0,0 +1,43 @@
(keyword) @keyword
(string_literal) @string
(number_literal) @constant.numeric
[
(edgeop)
(operator)
] @operator
[
","
";"
] @punctuation.delimiter
[
"{"
"}"
"["
"]"
"<"
">"
] @punctuation.bracket
(subgraph
id: (id
(identifier) @namespace)
)
(attribute
name: (id
(identifier) @type)
value: (id
(identifier) @constant)
)
[
(comment)
(preproc)
] @comment
(ERROR) @error
(identifier) @variable

@ -0,0 +1,2 @@
((html_internal) @injection.content
(#set! injection.language "html"))

@ -0,0 +1,221 @@
; Special identifiers
;--------------------
([
(identifier)
(shorthand_property_identifier)
(shorthand_property_identifier_pattern)
] @constant
(#match? @constant "^[A-Z_][A-Z\\d_]+$"))
((identifier) @constructor
(#match? @constructor "^[A-Z]"))
((identifier) @variable.builtin
(#match? @variable.builtin "^(arguments|module|console|window|document)$")
(#is-not? local))
((identifier) @function.builtin
(#eq? @function.builtin "require")
(#is-not? local))
; Function and method definitions
;--------------------------------
(function
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(method_definition
name: (property_identifier) @function.method)
(pair
key: (property_identifier) @function.method
value: [(function) (arrow_function)])
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: [(function) (arrow_function)])
(variable_declarator
name: (identifier) @function
value: [(function) (arrow_function)])
(assignment_expression
left: (identifier) @function
right: [(function) (arrow_function)])
; Function and method calls
;--------------------------
(call_expression
function: (identifier) @function)
(call_expression
function: (member_expression
property: (property_identifier) @function.method))
; Variables
;----------
(identifier) @variable
; Properties
;-----------
(property_identifier) @variable.other.member
(shorthand_property_identifier) @variable.other.member
(shorthand_property_identifier_pattern) @variable.other.member
; Literals
;---------
(this) @variable.builtin
(super) @variable.builtin
[
(true)
(false)
(null)
(undefined)
] @constant.builtin
(comment) @comment
[
(string)
(template_string)
] @string
(regex) @string.regexp
(number) @constant.numeric.integer
; Tokens
;-------
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
[
";"
"?."
"."
","
] @punctuation.delimiter
[
"-"
"--"
"-="
"+"
"++"
"+="
"*"
"*="
"**"
"**="
"/"
"/="
"%"
"%="
"<"
"<="
"<<"
"<<="
"="
"=="
"==="
"!"
"!="
"!=="
"=>"
">"
">="
">>"
">>="
">>>"
">>>="
"~"
"^"
"&"
"|"
"^="
"&="
"|="
"&&"
"||"
"??"
"&&="
"||="
"??="
"..."
] @operator
(ternary_expression ["?" ":"] @operator)
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"as"
"async"
"debugger"
"delete"
"extends"
"from"
"function"
"get"
"in"
"instanceof"
"new"
"of"
"set"
"static"
"target"
"try"
"typeof"
"void"
"with"
] @keyword
[
"class"
"let"
"const"
"var"
] @keyword.storage.type
[
"switch"
"case"
"default"
"if"
"else"
"yield"
"throw"
"finally"
"return"
"catch"
"continue"
"while"
"break"
"for"
"do"
"await"
] @keyword.control
[
"import"
"export"
] @keyword.control.import

@ -0,0 +1,22 @@
[
(array)
(object)
(arguments)
(formal_parameters)
(statement_block)
(object_pattern)
(class_body)
(named_imports)
(binary_expression)
(return_statement)
(template_substitution)
(export_clause)
] @indent
[
"}"
"]"
")"
] @outdent

@ -0,0 +1,36 @@
; Parse the contents of tagged template literals using
; a language inferred from the tag.
(call_expression
function: [
(identifier) @injection.language
(member_expression
property: (property_identifier) @injection.language)
]
arguments: (template_string) @injection.content)
; Parse the contents of gql template literals
((call_expression
function: (identifier) @_template_function_name
arguments: (template_string) @injection.content)
(#eq? @_template_function_name "gql")
(#set! injection.language "graphql"))
; Parse regex syntax within regex literals
((regex_pattern) @injection.content
(#set! injection.language "regex"))
; Parse JSDoc annotations in multiline comments
((comment) @injection.content
(#set! injection.language "jsdoc")
(#match? @injection.content "^/\\*+"))
; Parse general tags in single line comments
((comment) @injection.content
(#set! injection.language "comment")
(#match? @injection.content "^//"))

@ -0,0 +1,29 @@
; Scopes
;-------
[
(statement_block)
(function)
(arrow_function)
(function_declaration)
(method_definition)
] @local.scope
; Definitions
;------------
(pattern/identifier) @local.definition
(pattern/rest_pattern
(identifier) @local.definition)
(arrow_function
parameter: (identifier) @local.definition)
(variable_declarator
name: (identifier) @local.definition)
; References
;------------
(identifier) @local.reference

@ -0,0 +1,36 @@
(function_declaration
body: (_) @function.inside) @function.around
(function
body: (_) @function.inside) @function.around
(arrow_function
body: (_) @function.inside) @function.around
(method_definition
body: (_) @function.inside) @function.around
(generator_function_declaration
body: (_) @function.inside) @function.around
(class_declaration
body: (class_body) @class.inside) @class.around
(class
(class_body) @class.inside) @class.around
(export_statement
declaration: [
(function_declaration) @function.around
(class_declaration) @class.around
])
(formal_parameters
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(arguments
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(comment) @comment.inside
(comment)+ @comment.around

@ -31,3 +31,5 @@
(arguments ((string) . (_)?)) (arguments ((string) . (_)?))
(do_block (_)* @test.inside)?) (do_block (_)* @test.inside)?)
(#match? @_keyword "^(test|describe)$")) @test.around (#match? @_keyword "^(test|describe)$")) @test.around
(comment) @comment.around @comment.inside

@ -0,0 +1,76 @@
; Identifiers
[
(field)
(field_identifier)
] @variable.other.member
(variable) @variable
; Function calls
(function_call
function: (identifier) @function)
(method_call
method: (selector_expression
field: (field_identifier) @function))
; Operators
"|" @operator
":=" @operator
; Builtin functions
((identifier) @function.builtin
(#match? @function.builtin "^(and|call|html|index|slice|js|len|not|or|print|printf|println|urlquery|eq|ne|lt|ge|gt|ge)$"))
; Delimiters
"." @punctuation.delimiter
"," @punctuation.delimiter
"{{" @punctuation.bracket
"}}" @punctuation.bracket
"{{-" @punctuation.bracket
"-}}" @punctuation.bracket
")" @punctuation.bracket
"(" @punctuation.bracket
; Keywords
"else" @keyword
"if" @keyword
"range" @keyword
"with" @keyword
"end" @keyword
"template" @keyword
"define" @keyword
"block" @keyword
; Literals
[
(interpreted_string_literal)
(raw_string_literal)
(rune_literal)
] @string
(escape_sequence) @string.special
[
(int_literal)
(float_literal)
(imaginary_literal)
] @constant.numeric.integer
[
(true)
(false)
] @constant.builtin.boolean
(nil) @constant.builtin
(comment) @comment
(ERROR) @error

@ -0,0 +1,2 @@
((comment) @injection.content
(#set! injection.language "comment"))

@ -0,0 +1 @@
(comment) @comment.around @comment.inside

@ -1,12 +0,0 @@
(formal_parameters
[
(identifier) @variable.parameter
(array_pattern
(identifier) @variable.parameter)
(object_pattern
[
(pair_pattern value: (identifier) @variable.parameter)
(shorthand_property_identifier_pattern) @variable.parameter
])
]
)

@ -1,215 +1,38 @@
; Special identifiers ; Function and method parameters
;-------------------- ;-------------------------------
([ ; (p) => ...
(identifier) (formal_parameters
(shorthand_property_identifier) (identifier) @variable.parameter)
(shorthand_property_identifier_pattern)
] @constant ; (...p) => ...
(#match? @constant "^[A-Z_][A-Z\\d_]+$")) (formal_parameters
(rest_pattern
(identifier) @variable.parameter))
((identifier) @constructor
(#match? @constructor "^[A-Z]")) ; ({ p }) => ...
(formal_parameters
((identifier) @variable.builtin (object_pattern
(#match? @variable.builtin "^(arguments|module|console|window|document)$") (shorthand_property_identifier_pattern) @variable.parameter))
(#is-not? local))
; ({ a: p }) => ...
((identifier) @function.builtin (formal_parameters
(#eq? @function.builtin "require") (object_pattern
(#is-not? local)) (pair_pattern
value: (identifier) @variable.parameter)))
; Function and method definitions
;-------------------------------- ; ([ p ]) => ...
(formal_parameters
(function (array_pattern
name: (identifier) @function) (identifier) @variable.parameter))
(function_declaration
name: (identifier) @function) ; (p = 1) => ...
(method_definition (formal_parameters
name: (property_identifier) @function.method) (assignment_pattern
left: (identifier) @variable.parameter))
(pair
key: (property_identifier) @function.method ; p => ...
value: [(function) (arrow_function)]) (arrow_function
parameter: (identifier) @variable.parameter)
(assignment_expression
left: (member_expression ; inherits: ecma
property: (property_identifier) @function.method)
right: [(function) (arrow_function)])
(variable_declarator
name: (identifier) @function
value: [(function) (arrow_function)])
(assignment_expression
left: (identifier) @function
right: [(function) (arrow_function)])
; Function and method calls
;--------------------------
(call_expression
function: (identifier) @function)
(call_expression
function: (member_expression
property: (property_identifier) @function.method))
; Variables
;----------
(identifier) @variable
; Properties
;-----------
(property_identifier) @variable.other.member
; Literals
;---------
(this) @variable.builtin
(super) @variable.builtin
[
(true)
(false)
(null)
(undefined)
] @constant.builtin
(comment) @comment
[
(string)
(template_string)
] @string
(regex) @string.regexp
(number) @constant.numeric.integer
; Tokens
;-------
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
[
";"
"?."
"."
","
] @punctuation.delimiter
[
"-"
"--"
"-="
"+"
"++"
"+="
"*"
"*="
"**"
"**="
"/"
"/="
"%"
"%="
"<"
"<="
"<<"
"<<="
"="
"=="
"==="
"!"
"!="
"!=="
"=>"
">"
">="
">>"
">>="
">>>"
">>>="
"~"
"^"
"&"
"|"
"^="
"&="
"|="
"&&"
"||"
"??"
"&&="
"||="
"??="
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"as"
"async"
"debugger"
"delete"
"extends"
"from"
"function"
"get"
"in"
"instanceof"
"new"
"of"
"set"
"static"
"target"
"try"
"typeof"
"void"
"with"
] @keyword
[
"class"
"let"
"const"
"var"
] @keyword.storage.type
[
"switch"
"case"
"default"
"if"
"else"
"yield"
"throw"
"finally"
"return"
"catch"
"continue"
"while"
"break"
"for"
"do"
"await"
] @keyword.control
[
"import"
"export"
] @keyword.control.import

@ -1,22 +1 @@
[ ; inherits: ecma
(array)
(object)
(arguments)
(formal_parameters)
(statement_block)
(object_pattern)
(class_body)
(named_imports)
(binary_expression)
(return_statement)
(template_substitution)
(export_clause)
] @indent
[
"}"
"]"
")"
] @outdent

@ -1,36 +1 @@
; Parse the contents of tagged template literals using ; inherits: ecma
; a language inferred from the tag.
(call_expression
function: [
(identifier) @injection.language
(member_expression
property: (property_identifier) @injection.language)
]
arguments: (template_string) @injection.content)
; Parse the contents of gql template literals
((call_expression
function: (identifier) @_template_function_name
arguments: (template_string) @injection.content)
(#eq? @_template_function_name "gql")
(#set! injection.language "graphql"))
; Parse regex syntax within regex literals
((regex_pattern) @injection.content
(#set! injection.language "regex"))
; Parse JSDoc annotations in multiline comments
((comment) @injection.content
(#set! injection.language "jsdoc")
(#match? @injection.content "^/\\*+"))
; Parse general tags in single line comments
((comment) @injection.content
(#set! injection.language "comment")
(#match? @injection.content "^//"))

@ -1,23 +1 @@
; Scopes ; inherits: ecma
;-------
[
(statement_block)
(function)
(arrow_function)
(function_declaration)
(method_definition)
] @local.scope
; Definitions
;------------
(pattern/identifier)@local.definition
(variable_declarator
name: (identifier) @local.definition)
; References
;------------
(identifier) @local.reference

@ -1,4 +1,4 @@
; inherits: javascript ; inherits: ecma
; Highlight component names differently ; Highlight component names differently
(jsx_opening_element ((identifier) @constructor (jsx_opening_element ((identifier) @constructor

@ -1 +1 @@
; inherits: javascript ; inherits: ecma

@ -1 +1 @@
; inherits: javascript ; inherits: ecma

@ -1 +1 @@
; inherits: javascript ; inherits: ecma

@ -1,410 +1,235 @@
;; Math ;; General syntax
[ (ERROR) @error
(displayed_equation)
(inline_formula)
] @text.math
;; This highlights the whole environment like vimtex does
((environment
(begin
name: (word) @_env)) @text.math
(#any-of? @_env
"displaymath" "displaymath*"
"equation" "equation*"
"multline" "multline*"
"eqnarray" "eqnarray*"
"align" "align*"
"array" "array*"
"split" "split*"
"alignat" "alignat*"
"gather" "gather*"
"flalign" "flalign*"))
[ (command_name) @function
(generic_command_name) (caption
"\\newcommand" command: _ @function)
"\\renewcommand"
"\\DeclareRobustCommand"
"\\DeclareMathOperator"
"\\newglossaryentry"
"\\caption"
"\\label"
"\\newlabel"
"\\color"
"\\colorbox"
"\\textcolor"
"\\pagecolor"
"\\definecolor"
"\\definecolorset"
"\\newtheorem"
"\\declaretheorem"
"\\newacronym"
] @function.macro
[ (key_value_pair
"\\ref" key: (_) @variable.parameter
"\\vref" value: (_))
"\\Vref"
"\\autoref"
"\\pageref"
"\\cref"
"\\Cref"
"\\cref*"
"\\Cref*"
"\\namecref"
"\\nameCref"
"\\lcnamecref"
"\\namecrefs"
"\\nameCrefs"
"\\lcnamecrefs"
"\\labelcref"
"\\labelcpageref"
"\\crefrange"
"\\crefrange"
"\\Crefrange"
"\\Crefrange"
"\\crefrange*"
"\\crefrange*"
"\\Crefrange*"
"\\Crefrange*"
] @function.macro
[ [
"\\cite" (comment)
"\\cite*" (line_comment)
"\\Cite" (block_comment)
"\\nocite" (comment_environment)
"\\citet" ] @comment
"\\citep"
"\\citet*"
"\\citep*"
"\\citeauthor"
"\\citeauthor*"
"\\Citeauthor"
"\\Citeauthor*"
"\\citetitle"
"\\citetitle*"
"\\citeyear"
"\\citeyear*"
"\\citedate"
"\\citedate*"
"\\citeurl"
"\\fullcite"
"\\citeyearpar"
"\\citealt"
"\\citealp"
"\\citetext"
"\\parencite"
"\\parencite*"
"\\Parencite"
"\\footcite"
"\\footfullcite"
"\\footcitetext"
"\\textcite"
"\\Textcite"
"\\smartcite"
"\\Smartcite"
"\\supercite"
"\\autocite"
"\\Autocite"
"\\autocite*"
"\\Autocite*"
"\\volcite"
"\\Volcite"
"\\pvolcite"
"\\Pvolcite"
"\\fvolcite"
"\\ftvolcite"
"\\svolcite"
"\\Svolcite"
"\\tvolcite"
"\\Tvolcite"
"\\avolcite"
"\\Avolcite"
"\\notecite"
"\\notecite"
"\\pnotecite"
"\\Pnotecite"
"\\fnotecite"
] @function.macro
[ [
"\\ref" (brack_group)
"\\vref" (brack_group_argc)
"\\Vref" ] @variable.parameter
"\\autoref"
"\\pageref"
"\\cref"
"\\Cref"
"\\cref*"
"\\Cref*"
"\\namecref"
"\\nameCref"
"\\lcnamecref"
"\\namecrefs"
"\\nameCrefs"
"\\lcnamecrefs"
"\\labelcref"
"\\labelcpageref"
] @function.macro
[(operator) "="] @operator
[ "\\item" @punctuation.special
"\\crefrange"
"\\crefrange"
"\\Crefrange"
"\\Crefrange"
"\\crefrange*"
"\\crefrange*"
"\\Crefrange*"
"\\Crefrange*"
] @function.macro
((word) @punctuation.delimiter
(#eq? @punctuation.delimiter "&"))
[ ["[" "]" "{" "}"] @punctuation.bracket ; "(" ")" has no syntactical meaning in LaTeX
"\\gls"
"\\Gls"
"\\GLS"
"\\glspl"
"\\Glspl"
"\\GLSpl"
"\\glsdisp"
"\\glslink"
"\\glstext"
"\\Glstext"
"\\GLStext"
"\\glsfirst"
"\\Glsfirst"
"\\GLSfirst"
"\\glsplural"
"\\Glsplural"
"\\GLSplural"
"\\glsfirstplural"
"\\Glsfirstplural"
"\\GLSfirstplural"
"\\glsname"
"\\Glsname"
"\\GLSname"
"\\glssymbol"
"\\Glssymbol"
"\\glsdesc"
"\\Glsdesc"
"\\GLSdesc"
"\\glsuseri"
"\\Glsuseri"
"\\GLSuseri"
"\\glsuserii"
"\\Glsuserii"
"\\GLSuserii"
"\\glsuseriii"
"\\Glsuseriii"
"\\GLSuseriii"
"\\glsuseriv"
"\\Glsuseriv"
"\\GLSuseriv"
"\\glsuserv"
"\\Glsuserv"
"\\GLSuserv"
"\\glsuservi"
"\\Glsuservi"
"\\GLSuservi"
] @function.macro
;; General environments
(begin
command: _ @function.builtin
name: (curly_group_text (text) @function.macro))
[ (end
"\\acrshort" command: _ @function.builtin
"\\Acrshort" name: (curly_group_text (text) @function.macro))
"\\ACRshort"
"\\acrshortpl" ;; Definitions and references
"\\Acrshortpl" (new_command_definition
"\\ACRshortpl" command: _ @function.macro
"\\acrlong" declaration: (curly_group_command_name (_) @function))
"\\Acrlong" (old_command_definition
"\\ACRlong" command: _ @function.macro
"\\acrlongpl" declaration: (_) @function)
"\\Acrlongpl" (let_command_definition
"\\ACRlongpl" command: _ @function.macro
"\\acrfull" declaration: (_) @function)
"\\Acrfull"
"\\ACRfull" (environment_definition
"\\acrfullpl" command: _ @function.macro
"\\Acrfullpl" name: (curly_group_text (_) @constant))
"\\ACRfullpl"
"\\acs" (theorem_definition
"\\Acs" command: _ @function.macro
"\\acsp" name: (curly_group_text (_) @constant))
"\\Acsp"
"\\acl" (paired_delimiter_definition
"\\Acl" command: _ @function.macro
"\\aclp" declaration: (curly_group_command_name (_) @function))
"\\Aclp"
"\\acf"
"\\Acf"
"\\acfp"
"\\Acfp"
"\\ac"
"\\Ac"
"\\acp"
"\\glsentrylong"
"\\Glsentrylong"
"\\glsentrylongpl"
"\\Glsentrylongpl"
"\\glsentryshort"
"\\Glsentryshort"
"\\glsentryshortpl"
"\\Glsentryshortpl"
"\\glsentryfullpl"
"\\Glsentryfullpl"
] @function.macro
(comment) @comment
(bracket_group) @variable.parameter
[(math_operator) "="] @operator
[ (label_definition
"\\usepackage" command: _ @function.macro
"\\documentclass" name: (curly_group_text (_) @label))
"\\input" (label_reference_range
"\\include" command: _ @function.macro
"\\subfile" from: (curly_group_text (_) @label)
"\\subfileinclude" to: (curly_group_text (_) @label))
"\\subfileinclude" (label_reference
"\\includegraphics" command: _ @function.macro
"\\addbibresource" names: (curly_group_text_list (_) @label))
"\\bibliography" (label_number
"\\includesvg" command: _ @function.macro
"\\includeinkscape" name: (curly_group_text (_) @label)
"\\usepgflibrary" number: (_) @markup.link.label)
"\\usetikzlibrary"
] @keyword.control.import
[ (citation
"\\part" command: _ @function.macro
"\\chapter" keys: (curly_group_text_list) @string)
"\\section"
"\\subsection" (glossary_entry_definition
"\\subsubsection" command: _ @function.macro
"\\paragraph" name: (curly_group_text (_) @string))
"\\subparagraph" (glossary_entry_reference
] @type command: _ @function.macro
name: (curly_group_text (_) @string))
(acronym_definition
command: _ @function.macro
name: (curly_group_text (_) @string))
(acronym_reference
command: _ @function.macro
name: (curly_group_text (_) @string))
(color_definition
command: _ @function.macro
name: (curly_group_text (_) @string))
(color_reference
command: _ @function.macro
name: (curly_group_text (_) @string))
"\\item" @punctuation.special ;; Math
((word) @punctuation.delimiter (displayed_equation) @markup.raw.block
(#eq? @punctuation.delimiter "&")) (inline_formula) @markup.raw.inline
["$" "\\[" "\\]" "\\(" "\\)"] @punctuation.delimiter (math_environment
(begin
command: _ @function.builtin
name: (curly_group_text (text) @markup.raw)))
(label_definition (math_environment
name: (_) @text.reference) (text) @markup.raw)
(label_reference
label: (_) @text.reference)
(equation_label_reference
label: (_) @text.reference)
(label_reference
label: (_) @text.reference)
(label_number
label: (_) @text.reference)
(citation (math_environment
key: (word) @text.reference) (end
command: _ @function.builtin
name: (curly_group_text (text) @markup.raw)))
(key_val_pair ;; Sectioning
key: (_) @variable.parameter (title_declaration
value: (_)) command: _ @namespace
options: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
["[" "]" "{" "}"] @punctuation.bracket ;"(" ")" is has no special meaning in LaTeX (author_declaration
command: _ @namespace
authors: (curly_group_author_list
((author)+ @markup.heading)))
(chapter (chapter
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(part (part
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(section (section
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subsection (subsection
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subsubsection (subsubsection
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(paragraph (paragraph
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subparagraph (subparagraph
text: (brace_group) @markup.heading) command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
((environment ;; Beamer frames
(generic_environment
(begin (begin
name: (word) @_frame) name: (curly_group_text
(brace_group (text) @markup.heading)
child: (text) @markup.heading)) (#any-of? @markup.heading "frame"))
(#eq? @_frame "frame")) .
(curly_group (_) @markup.heading))
((generic_command ((generic_command
name:(generic_command_name) @_name command: (command_name) @_name
arg: (brace_group arg: (curly_group
(text) @markup.heading)) (text) @markup.heading))
(#eq? @_name "\\frametitle")) (#eq? @_name "\\frametitle"))
;; Formatting ;; Formatting
((generic_command ((generic_command
name:(generic_command_name) @_name command: (command_name) @_name
arg: (_) @markup.italic) arg: (curly_group (_) @markup.italic))
(#eq? @_name "\\emph")) (#eq? @_name "\\emph"))
((generic_command ((generic_command
name:(generic_command_name) @_name command: (command_name) @_name
arg: (_) @markup.italic) arg: (curly_group (_) @markup.italic))
(#match? @_name "^(\\\\textit|\\\\mathit)$")) (#match? @_name "^(\\\\textit|\\\\mathit)$"))
((generic_command ((generic_command
name:(generic_command_name) @_name command: (command_name) @_name
arg: (_) @markup.bold) arg: (curly_group (_) @markup.bold))
(#match? @_name "^(\\\\textbf|\\\\mathbf)$")) (#match? @_name "^(\\\\textbf|\\\\mathbf)$"))
((generic_command ((generic_command
name:(generic_command_name) @_name command: (command_name) @_name
. .
arg: (_) @markup.link.url) arg: (curly_group (_) @markup.link.uri))
(#match? @_name "^(\\\\url|\\\\href)$")) (#match? @_name "^(\\\\url|\\\\href)$"))
(ERROR) @error ;; File inclusion commands
(class_include
[ command: _ @keyword.storage.type
"\\begin" path: (curly_group_path) @string)
"\\end"
] @text.environment (package_include
command: _ @keyword.storage.type
(begin paths: (curly_group_path_list) @string)
name: (_) @text.environment.name
(#not-any-of? @text.environment.name (latex_include
"displaymath" "displaymath*" command: _ @keyword.control.import
"equation" "equation*" path: (curly_group_path) @string)
"multline" "multline*" (import_include
"eqnarray" "eqnarray*" command: _ @keyword.control.import
"align" "align*" directory: (curly_group_path) @string
"array" "array*" file: (curly_group_path) @string)
"split" "split*"
"alignat" "alignat*" (bibtex_include
"gather" "gather*" command: _ @keyword.control.import
"flalign" "flalign*")) path: (curly_group_path) @string)
(biblatex_include
(end "\\addbibresource" @include
name: (_) @text.environment.name glob: (curly_group_glob_pattern) @string.regex)
(#not-any-of? @text.environment.name
"displaymath" "displaymath*" (graphics_include
"equation" "equation*" command: _ @keyword.control.import
"multline" "multline*" path: (curly_group_path) @string)
"eqnarray" "eqnarray*" (tikz_library_import
"align" "align*" command: _ @keyword.control.import
"array" "array*" paths: (curly_group_path_list) @string)
"split" "split*"
"alignat" "alignat*"
"gather" "gather*"
"flalign" "flalign*"))

@ -0,0 +1,13 @@
[
(generic_command)
] @function.around
[
(chapter)
(part)
(section)
(subsection)
(subsubsection)
(paragraph)
(subparagraph)
] @class.around

@ -0,0 +1,39 @@
;; From nvim-treesitter/nvim-treesitter
[
(code_span)
(link_title)
] @markup.raw.inline
[
(emphasis_delimiter)
(code_span_delimiter)
] @punctuation.bracket
(emphasis) @markup.italic
(strong_emphasis) @markup.bold
(strikethrough) @markup.strikethrough
[
(link_destination)
(uri_autolink)
] @markup.link.url
[
(link_text)
(image_description)
] @markup.link.text
(link_label) @markup.link.label
[
(backslash_escape)
(hard_line_break)
] @constant.character.escape
(image ["[" "]" "(" ")"] @punctuation.bracket)
(image "!" @punctuation.special)
(inline_link ["[" "]" "(" ")"] @punctuation.bracket)
(shortcut_link ["[" "]"] @punctuation.bracket)

@ -0,0 +1,2 @@
((html_tag) @injection.content (#set! injection.language "html"))

@ -1,54 +1,53 @@
(setext_heading (heading_content) @markup.heading.1 (setext_h1_underline) @markup.heading.marker)
(setext_heading (heading_content) @markup.heading.2 (setext_h2_underline) @markup.heading.marker)
(atx_heading (atx_h1_marker) @markup.heading.marker (heading_content) @markup.heading.1) (setext_heading (paragraph) @markup.heading.1 (setext_h1_underline) @markup.heading.marker)
(atx_heading (atx_h2_marker) @markup.heading.marker (heading_content) @markup.heading.2) (setext_heading (paragraph) @markup.heading.2 (setext_h2_underline) @markup.heading.marker)
(atx_heading (atx_h3_marker) @markup.heading.marker (heading_content) @markup.heading.3)
(atx_heading (atx_h4_marker) @markup.heading.marker (heading_content) @markup.heading.4) (atx_heading (atx_h1_marker) @markup.heading.marker (inline) @markup.heading.1)
(atx_heading (atx_h5_marker) @markup.heading.marker (heading_content) @markup.heading.5) (atx_heading (atx_h2_marker) @markup.heading.marker (inline) @markup.heading.2)
(atx_heading (atx_h6_marker) @markup.heading.marker (heading_content) @markup.heading.6) (atx_heading (atx_h3_marker) @markup.heading.marker (inline) @markup.heading.3)
(atx_heading (atx_h4_marker) @markup.heading.marker (inline) @markup.heading.4)
(atx_heading (atx_h5_marker) @markup.heading.marker (inline) @markup.heading.5)
(atx_heading (atx_h6_marker) @markup.heading.marker (inline) @markup.heading.6)
[ [
(indented_code_block) (indented_code_block)
(code_fence_content) (fenced_code_block)
] @markup.raw.block ] @markup.raw.block
(block_quote) @markup.quote (info_string) @label
(code_span) @markup.raw.inline
(emphasis) @markup.italic
(strong_emphasis) @markup.bold
(link_destination) @markup.link.url [
(link_label) @markup.link.label (fenced_code_block_delimiter)
] @punctuation.bracket
(info_string) @label [
(link_destination)
] @markup.link.url
[ [
(link_text) (link_label)
(image_description) ] @markup.link.label
] @markup.link.text
[ [
(list_marker_plus) (list_marker_plus)
(list_marker_minus) (list_marker_minus)
(list_marker_star) (list_marker_star)
] @markup.list.numbered ] @markup.list.unnumbered
[ [
(list_marker_dot) (list_marker_dot)
(list_marker_parenthesis) (list_marker_parenthesis)
] @markup.list.unnumbered ] @markup.list.numbered
(thematic_break) @punctuation.delimiter
[ [
(backslash_escape) (block_continuation)
(hard_line_break) (block_quote_marker)
] @constant.character.escape ] @punctuation.special
(thematic_break) @punctuation.delimiter [
(backslash_escape)
] @string.escape
(inline_link ["[" "]" "(" ")"] @punctuation.bracket) (block_quote) @markup.quote
(image ["[" "]" "(" ")"] @punctuation.bracket)
(fenced_code_block_delimiter) @punctuation.bracket

@ -1,9 +1,13 @@
; From nvim-treesitter/nvim-treesitter
(fenced_code_block (fenced_code_block
(info_string) @injection.language (info_string
(code_fence_content) @injection.content (language) @injection.language)
(#set! injection.include-children)) (code_fence_content) @injection.content (#set! injection.include-unnamed-children))
((html_block) @injection.content ((html_block) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children))
(#set! injection.language "html"))
((html_tag) @injection.content ((minus_metadata) @injection.content (#set! injection.language "yaml") (#set! injection.include-unnamed-children))
(#set! injection.language "html")) ((plus_metadata) @injection.content (#set! injection.language "toml") (#set! injection.include-unnamed-children))
((inline) @injection.content (#set! injection.language "markdown.inline") (#set! injection.include-unnamed-children))

@ -8,6 +8,14 @@
(named_type (name) @type) @type (named_type (name) @type) @type
(named_type (qualified_name) @type) @type (named_type (qualified_name) @type) @type
(namespace_definition
name: (namespace_name (name) @namespace))
; Superglobals
(subscript_expression
(variable_name(name) @constant.builtin
(#match? @constant.builtin "^_?[A-Z][A-Z\\d_]+$")))
; Functions ; Functions
(array_creation_expression "array" @function.builtin) (array_creation_expression "array" @function.builtin)
@ -17,7 +25,7 @@
name: (name) @function.method) name: (name) @function.method)
(function_call_expression (function_call_expression
function: (qualified_name (name)) @function) function: (_) @function)
(scoped_call_expression (scoped_call_expression
name: (name) @function) name: (name) @function)
@ -28,6 +36,7 @@
(function_definition (function_definition
name: (name) @function) name: (name) @function)
; Member ; Member
(property_element (property_element
@ -67,52 +76,54 @@
; Keywords ; Keywords
"abstract" @keyword [
"as" @keyword "abstract"
"break" @keyword "as"
"case" @keyword "break"
"catch" @keyword "case"
"class" @keyword "catch"
"const" @keyword "class"
"continue" @keyword "const"
"declare" @keyword "continue"
"default" @keyword "declare"
"do" @keyword "default"
"echo" @keyword "do"
"else" @keyword "echo"
"elseif" @keyword "else"
"enddeclare" @keyword "elseif"
"endforeach" @keyword "enddeclare"
"endif" @keyword "endforeach"
"endswitch" @keyword "endif"
"endwhile" @keyword "endswitch"
"enum" @keyword "endwhile"
"extends" @keyword "enum"
"final" @keyword "extends"
"finally" @keyword "final"
"foreach" @keyword "finally"
"fn" @keyword "foreach"
"function" @keyword "fn"
"global" @keyword "function"
"if" @keyword "global"
"implements" @keyword "if"
"include_once" @keyword "implements"
"include" @keyword "include_once"
"insteadof" @keyword "include"
"interface" @keyword "insteadof"
"match" @keyword "interface"
"namespace" @keyword "match"
"new" @keyword "namespace"
"private" @keyword "new"
"protected" @keyword "private"
"public" @keyword "protected"
"require_once" @keyword "public"
"require" @keyword "require_once"
"return" @keyword "require"
"static" @keyword "return"
"switch" @keyword "static"
"throw" @keyword "switch"
"trait" @keyword "throw"
"try" @keyword "trait"
"use" @keyword "try"
"while" @keyword "use"
"while"
] @keyword

@ -1,3 +1,11 @@
; Imports
(dotted_name
(identifier)* @namespace)
(aliased_import
alias: (identifier) @namespace)
; Builtin functions ; Builtin functions
((call ((call
@ -8,6 +16,11 @@
; Function calls ; Function calls
[
"def"
"lambda"
] @keyword.function
(call (call
function: (attribute attribute: (identifier) @constructor) function: (attribute attribute: (identifier) @constructor)
(#match? @constructor "^[A-Z]")) (#match? @constructor "^[A-Z]"))
@ -47,7 +60,16 @@
(parameters (typed_parameter (identifier) @variable.parameter)) (parameters (typed_parameter (identifier) @variable.parameter))
(parameters (default_parameter name: (identifier) @variable.parameter)) (parameters (default_parameter name: (identifier) @variable.parameter))
(parameters (typed_default_parameter name: (identifier) @variable.parameter)) (parameters (typed_default_parameter name: (identifier) @variable.parameter))
(keyword_argument name: (identifier) @variable.parameter)
(parameters
(list_splat_pattern ; *args
(identifier) @variable.parameter))
(parameters
(dictionary_splat_pattern ; **kwargs
(identifier) @variable.parameter))
(lambda_parameters
(identifier) @variable.parameter)
; Types ; Types
@ -81,12 +103,11 @@
(identifier) @variable (identifier) @variable
; Literals ; Literals
(none) @constant.builtin
[ [
(none)
(true) (true)
(false) (false)
] @constant.builtin ] @constant.builtin.boolean
(integer) @constant.numeric.integer (integer) @constant.numeric.integer
(float) @constant.numeric.float (float) @constant.numeric.float
@ -94,9 +115,11 @@
(string) @string (string) @string
(escape_sequence) @constant.character.escape (escape_sequence) @constant.character.escape
["," "." ":" ";" (ellipsis)] @punctuation.delimiter
(interpolation (interpolation
"{" @punctuation.special "{" @punctuation.special
"}" @punctuation.special) @embedded "}" @punctuation.special) @embedded
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
[ [
"-" "-"
@ -135,24 +158,39 @@
"as" "as"
"assert" "assert"
"await" "await"
"break" "from"
"continue" "pass"
"with"
] @keyword.control
[
"if"
"elif" "elif"
"else" "else"
"except" ] @keyword.control.conditional
"finally"
[
"while"
"for" "for"
"from" "break"
"if" "continue"
"import" ] @keyword.control.repeat
"pass"
"raise" [
"return" "return"
"try"
"while"
"with"
"yield" "yield"
] @keyword.control ] @keyword.control.return
(yield "from" @keyword.control.return)
[
"raise"
"try"
"except"
"finally"
] @keyword.control.except
(raise_statement "from" @keyword.control.except)
"import" @keyword.control.import
(for_statement "in" @keyword.control) (for_statement "in" @keyword.control)
(for_in_clause "in" @keyword.control) (for_in_clause "in" @keyword.control)
@ -161,16 +199,22 @@
"and" "and"
"async" "async"
"class" "class"
"def"
"del"
"exec" "exec"
"global" "global"
"in"
"is"
"lambda"
"nonlocal" "nonlocal"
"not"
"or"
"print" "print"
] @keyword ] @keyword
[
"and"
"or"
"in"
"not"
"del"
"is"
] @keyword.operator
((identifier) @type.builtin
(#match? @type.builtin
"^(BaseException|Exception|ArithmeticError|BufferError|LookupError|AssertionError|AttributeError|EOFError|FloatingPointError|GeneratorExit|ImportError|ModuleNotFoundError|IndexError|KeyError|KeyboardInterrupt|MemoryError|NameError|NotImplementedError|OSError|OverflowError|RecursionError|ReferenceError|RuntimeError|StopIteration|StopAsyncIteration|SyntaxError|IndentationError|TabError|SystemError|SystemExit|TypeError|UnboundLocalError|UnicodeError|UnicodeEncodeError|UnicodeDecodeError|UnicodeTranslateError|ValueError|ZeroDivisionError|EnvironmentError|IOError|WindowsError|BlockingIOError|ChildProcessError|ConnectionError|BrokenPipeError|ConnectionAbortedError|ConnectionRefusedError|ConnectionResetError|FileExistsError|FileNotFoundError|InterruptedError|IsADirectoryError|NotADirectoryError|PermissionError|ProcessLookupError|TimeoutError|Warning|UserWarning|DeprecationWarning|PendingDeprecationWarning|SyntaxWarning|RuntimeWarning|FutureWarning|ImportWarning|UnicodeWarning|BytesWarning|ResourceWarning)$"))
(ERROR) @error

@ -0,0 +1,44 @@
;; Scopes
[
(module)
(function_definition)
(lambda)
] @local.scope
;; Definitions
; Parameters
(parameters
(identifier) @local.definition)
(parameters
(typed_parameter
(identifier) @local.definition))
(parameters
(default_parameter
name: (identifier) @local.definition))
(parameters
(typed_default_parameter
name: (identifier) @local.definition))
(parameters
(list_splat_pattern ; *args
(identifier) @local.definition))
(parameters
(dictionary_splat_pattern ; **kwargs
(identifier) @local.definition))
(lambda_parameters
(identifier) @local.definition)
; Imports
(import_statement
name: (dotted_name
(identifier) @local.definition))
(aliased_import
alias: (identifier) @local.definition)
;; References
(identifier) @local.reference

@ -35,7 +35,7 @@
(non_boundary_assertion) (non_boundary_assertion)
] @constant.character.escape ] @constant.character.escape
(group_name) @property (group_name) @label
(count_quantifier (count_quantifier
[ [

@ -78,6 +78,8 @@
"<" "<"
">" ">"
] @punctuation.bracket) ] @punctuation.bracket)
(closure_parameters
"|" @punctuation.bracket)
; --- ; ---
; Variables ; Variables

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save