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

@ -1,6 +1,5 @@
# Helix
[![Build status](https://github.com/helix-editor/helix/actions/workflows/build.yml/badge.svg)](https://github.com/helix-editor/helix/actions)
![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
config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows).
| OS | command |
|-------------------|-----------|
|windows(cmd.exe) |`xcopy runtime %AppData%/helix/runtime` |
|windows(powershell)|`xcopy runtime $Env:AppData\helix\runtime` |
|linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`|
| OS | Command |
| -------------------- | -------------------------------------------- |
| Windows (cmd.exe) | `xcopy /e runtime %AppData%\helix\runtime` |
| Windows (PowerShell) | `xcopy /e runtime $Env:AppData\helix\runtime` |
| Linux/macOS | `ln -s $PWD/runtime ~/.config/helix/runtime` |
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 install helix
```
# Contributing
Contributing guidelines can be found [here](./docs/CONTRIBUTING.md).

@ -25,6 +25,9 @@ select = "underline"
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]` Section
@ -38,7 +41,7 @@ hidden = false
| `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` |
| `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-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` |
@ -63,6 +66,7 @@ Statusline elements can be defined as follows:
left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
```
The following elements can be configured:
@ -78,6 +82,9 @@ The following elements can be configured:
| `diagnostics` | The number of warnings and/or errors |
| `selections` | The number of active selections |
| `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
@ -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`
available, which is not defined by default.
All git related options are only enabled in a git repository.
| Key | Description | Default |
|--|--|---------|
|`hidden` | Enables ignoring hidden files. | true
@ -185,7 +194,7 @@ Options for rendering whitespace with visible characters. Use `:set whitespace.r
| 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"` |
| `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
@ -203,6 +212,7 @@ space = "·"
nbsp = "⍽"
tab = "→"
newline = "⏎"
tabpad = "·" # Tabs will look like "→···" (depending on tab width)
```
### `[editor.indent-guides]` Section

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

@ -1,18 +1,18 @@
| Name | Description |
| --- | --- |
| `: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. |
| `: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-all`, `:bca`, `:bcloseall` | Close all buffers, without quitting. |
| `:buffer-close-all!`, `:bca!`, `:bcloseall!` | Close all buffers forcefully (ignoring unsaved changes), without quitting. |
| `:buffer-next`, `:bn`, `:bnext` | Go to next buffer. |
| `:buffer-previous`, `:bp`, `:bprev` | Go to previous buffer. |
| `: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!` | Force close all buffers ignoring unsaved changes without quitting. |
| `:buffer-next`, `:bn`, `:bnext` | Goto next 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 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. |
| `: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.) |
@ -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 forcefully (ignoring unsaved changes). |
| `: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) 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. |
| `: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. |
@ -42,7 +42,7 @@
| `:show-clipboard-provider` | Show clipboard provider name in status bar. |
| `:change-current-directory`, `:cd` | Change 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. |
| `: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. |
@ -53,7 +53,7 @@
| `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. |
| `:hsplit-new`, `:hnew` | Open a scratch buffer in a horizontal split. |
| `: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-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. |
@ -61,8 +61,8 @@
| `:rsort` | Sort ranges in selection in reverse order. |
| `: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. |
| `:config-reload` | Refreshes helix's config. |
| `:config-open` | Open the helix config.toml file. |
| `:config-reload` | Refresh user config. |
| `:config-open` | Open the user config.toml file. |
| `:log-open` | Open the helix log file. |
| `:insert-output` | Run shell command, inserting 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 |
|-------------------|-----------|
|windows(cmd.exe) |`xcopy runtime %AppData%/helix/runtime` |
|windows(powershell)|`xcopy runtime $Env:AppData\helix\runtime` |
|windows(cmd.exe) |`xcopy /e runtime %AppData%/helix/runtime` |
|windows(powershell)|`xcopy /e runtime $Env:AppData\helix\runtime` |
|linux/macos |`ln -s $PWD/runtime ~/.config/helix/runtime`|
## Finishing up the installation

@ -1,7 +1,27 @@
# Keymap
- 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)
- [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
@ -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
you to selectively add search terms to your selections.
# Picker
## Picker
Keys to use within picker. Remapping currently not supported.
| Key | Description |
| ----- | ------------- |
| `Up`, `Ctrl-p` | Previous entry |
| `Tab`, `Up`, `Ctrl-p` | Previous entry |
| `PageUp`, `Ctrl-u` | Page up |
| `Down`, `Ctrl-n` | Next entry |
| `Shift-tab`, `Down`, `Ctrl-n`| Next entry |
| `PageDown`, `Ctrl-d` | Page down |
| `Home` | Go to first entry |
| `End` | Go to last entry |
@ -358,7 +378,7 @@ Keys to use within picker. Remapping currently not supported.
| `Ctrl-t` | Toggle preview |
| `Escape`, `Ctrl-c` | Close picker |
# Prompt
## Prompt
Keys to use within prompt, Remapping currently not supported.

@ -40,6 +40,7 @@ file-types = ["mylang", "myl"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-server = { command = "mylang-lsp", args = ["--stdio"] }
formatter = { command = "mylang-formatter" , args = ["--stdin"] }
```
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. |
| `config` | Language Server configuration |
| `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

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

@ -16,7 +16,7 @@ _hx() {
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
} && 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 -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 -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" \
"--vsplit[Splits all given files vertically 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"
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
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 for helix-term can be run with `cargo integration-test`. Code
contributors are strongly encouraged to write integration tests for their code.
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,
nixCargoIntegration,
...
}:
nixCargoIntegration.lib.makeOutputs {
root = ./.;
renameOutputs = {"helix-term" = "helix";};
# Set default app to hx (binary is from helix-term release build)
# Set default package to helix-term release build
defaultOutputs = {
app = "hx";
package = "helix";
};
overrides = {
cCompiler = common:
with common.pkgs;
if stdenv.isLinux
then gcc
else clang;
crateOverrides = common: _: {
helix-term = prev: let
inherit (common) pkgs;
mkRootPath = rel:
builtins.path {
path = "${common.root}/${rel}";
name = rel;
};
grammars = pkgs.callPackage ./grammars.nix {};
runtimeDir = pkgs.runCommandNoCC "helix-runtime" {} ''
mkdir -p $out
ln -s ${mkRootPath "runtime"}/* $out
rm -r $out/grammars
ln -s ${grammars} $out/grammars
'';
in {
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
# link languages and theme toml files since helix-term expects them (for tests)
preConfigure =
pkgs.lib.concatMapStringsSep
"\n"
(path: "ln -sf ${mkRootPath path} ..")
["languages.toml" "theme.toml" "base16_theme.toml"];
buildInputs = (prev.buildInputs or []) ++ [common.cCompiler.cc.lib];
nativeBuildInputs = [pkgs.makeWrapper];
}: let
outputs = config:
nixCargoIntegration.lib.makeOutputs {
root = ./.;
renameOutputs = {"helix-term" = "helix";};
# Set default app to hx (binary is from helix-term release build)
# Set default package to helix-term release build
defaultOutputs = {
app = "hx";
package = "helix";
};
overrides = {
cCompiler = common:
with common.pkgs;
if stdenv.isLinux
then gcc
else clang;
crateOverrides = common: _: {
helix-term = prev: let
inherit (common) pkgs;
mkRootPath = rel:
builtins.path {
path = "${common.root}/${rel}";
name = rel;
};
grammars = pkgs.callPackage ./grammars.nix config;
runtimeDir = pkgs.runCommandNoCC "helix-runtime" {} ''
mkdir -p $out
ln -s ${mkRootPath "runtime"}/* $out
rm -r $out/grammars
ln -s ${grammars} $out/grammars
'';
overridedAttrs = {
# disable fetching and building of tree-sitter grammars in the helix-term build.rs
HELIX_DISABLE_AUTO_GRAMMAR_BUILD = "1";
# link languages and theme toml files since helix-term expects them (for tests)
preConfigure =
pkgs.lib.concatMapStringsSep
"\n"
(path: "ln -sf ${mkRootPath path} ..")
["languages.toml" "theme.toml" "base16_theme.toml"];
buildInputs = (prev.buildInputs or []) ++ [common.cCompiler.cc.lib];
nativeBuildInputs = [pkgs.makeWrapper];
postFixup = ''
if [ -f "$out/bin/hx" ]; then
wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
fi
'';
postFixup = ''
if [ -f "$out/bin/hx" ]; then
wrapProgram "$out/bin/hx" ''${makeWrapperArgs[@]} --set HELIX_RUNTIME "${runtimeDir}"
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 = {

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

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

@ -23,6 +23,12 @@ pub struct Range {
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)
#[derive(Debug, Clone)]
pub struct Diagnostic {
@ -30,4 +36,5 @@ pub struct Diagnostic {
pub line: usize,
pub message: String,
pub severity: Option<Severity>,
pub code: Option<NumberOrString>,
}

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

@ -305,8 +305,17 @@ mod line_ending_tests {
fn line_end_char_index_rope_slice() {
let r = Rope::from_str("Hello\rworld\nhow\r\nare you?");
let s = &r.slice(..);
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(not(feature = "unicode-lines"))]
{
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
/// 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]
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))
}
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)]
pub fn iter(&self) -> std::slice::Iter<'_, Range> {
self.ranges.iter()

@ -79,6 +79,9 @@ pub struct LanguageConfiguration {
#[serde(default)]
pub auto_format: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub formatter: Option<FormatterConfiguration>,
#[serde(default)]
pub diagnostic_severity: Severity,
@ -126,6 +129,15 @@ pub struct LanguageServerConfiguration {
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)]
#[serde(rename_all = "kebab-case")]
pub struct AdvancedCompletion {
@ -752,7 +764,7 @@ impl Syntax {
);
let mut injections = Vec::new();
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.injections_query,
&mat,
@ -769,7 +781,7 @@ impl Syntax {
{
if let Some(config) = (injection_callback)(&language_name) {
let ranges =
intersect_ranges(&layer.ranges, &[content_node], include_children);
intersect_ranges(&layer.ranges, &[content_node], included_children);
if !ranges.is_empty() {
injections.push((config, ranges));
@ -781,7 +793,10 @@ impl Syntax {
// Process combined injections.
if let Some(combined_injections_query) = &layer.config.combined_injections_query {
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(
combined_injections_query,
layer.tree().root_node(),
@ -789,7 +804,7 @@ impl Syntax {
);
for mat in matches {
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,
combined_injections_query,
&mat,
@ -801,16 +816,16 @@ impl Syntax {
if let Some(content_node) = 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(config) = (injection_callback)(&lang_name) {
let ranges = intersect_ranges(
&layer.ranges,
&content_nodes,
includes_children,
included_children,
);
if !ranges.is_empty() {
injections.push((config, ranges));
@ -1341,8 +1356,8 @@ impl HighlightConfiguration {
/// Tree-sitter syntax-highlighting queries specify highlights in the form of dot-separated
/// highlight names like `punctuation.bracket` and `function.method.builtin`. Consumers of
/// these queries can choose to recognize highlights with different levels of specificity.
/// For example, the string `function.builtin` will match against `function.method.builtin`
/// and `function.builtin.constructor`, but will not match `function.method`.
/// For example, the string `function.builtin` will match against `function.builtin.constructor`
/// but will not match `function.method.builtin` and `function.method`.
///
/// When highlighting, results are returned as `Highlight` values, which contain the index
/// of the matched highlight this list of highlight names.
@ -1362,11 +1377,13 @@ impl HighlightConfiguration {
let recognized_name = recognized_name;
let mut len = 0;
let mut matches = true;
for part in recognized_name.split('.') {
len += 1;
if !capture_parts.contains(&part) {
matches = false;
break;
for (i, part) in recognized_name.split('.').enumerate() {
match capture_parts.get(i) {
Some(capture_part) if *capture_part == part => len += 1,
_ => {
matches = false;
break;
}
}
}
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.
// This takes into account three things:
// * `parent_ranges` - The ranges must all fall within the *current* layer's ranges.
@ -1420,7 +1450,7 @@ impl<'a> HighlightIterLayer<'a> {
fn intersect_ranges(
parent_ranges: &[Range],
nodes: &[Node],
includes_children: bool,
included_children: IncludedChildren,
) -> Vec<Range> {
let mut cursor = nodes[0].walk();
let mut result = Vec::new();
@ -1444,11 +1474,15 @@ fn intersect_ranges(
for excluded_range in node
.children(&mut cursor)
.filter_map(|child| {
if includes_children {
None
} else {
Some(child.range())
.filter_map(|child| match included_children {
IncludedChildren::None => Some(child.range()),
IncludedChildren::All => None,
IncludedChildren::Unnamed => {
if child.is_named() {
Some(child.range())
} else {
None
}
}
})
.chain([following_range].iter().cloned())
@ -1777,7 +1811,7 @@ fn injection_for_match<'a>(
query: &'a Query,
query_match: &QueryMatch<'a, '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 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) {
match prop.key.as_ref() {
// 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
// node itself. This can be changed using a `#set!` predicate that
// 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> {

@ -198,26 +198,26 @@ pub fn textobject_paragraph(
Range::new(anchor, head)
}
pub fn textobject_surround(
pub fn textobject_pair_surround(
slice: RopeSlice,
range: Range,
textobject: TextObject,
ch: char,
count: usize,
) -> 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,
range: Range,
textobject: TextObject,
count: usize,
) -> 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,
range: Range,
textobject: TextObject,
@ -562,7 +562,7 @@ mod test {
let slice = doc.slice(..);
for &case in scenario {
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!(
result,
expected_range.into(),

@ -19,7 +19,23 @@ pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
.into_iter()
.chain([default_lang_config()].into_iter())
.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)

@ -89,11 +89,102 @@ pub fn fetch_grammars() -> Result<()> {
let mut grammars = get_grammar_configs()?;
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<()> {
run_parallel(get_grammar_configs()?, build_grammar, "build")
pub fn build_grammars(target: Option<String>) -> Result<()> {
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.
@ -122,15 +213,17 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
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
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 (tx, rx) = channel();
for grammar in grammars {
let tx = tx.clone();
let job = job.clone();
pool.execute(move || {
// Ignore any SendErrors, if any job in another thread has encountered an
@ -141,14 +234,21 @@ where
drop(tx);
// TODO: print all failures instead of the first one found.
rx.iter()
.find(|result| result.is_err())
.map(|err| err.with_context(|| format!("Failed to {} some grammar(s)", action)))
.unwrap_or(Ok(()))
rx.iter().collect()
}
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 {
remote, revision, ..
} = grammar.source
@ -184,16 +284,18 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> {
)?;
git(&grammar_dir, ["checkout", &revision])?;
println!(
"Grammar '{}' checked out at '{}'.",
grammar.grammar_id, revision
);
Ok(FetchStatus::GitUpdated {
grammar_id: grammar.grammar_id,
revision,
})
} 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
@ -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 {
PathBuf::from(&path)
} else {
@ -273,10 +380,14 @@ fn build_grammar(grammar: GrammarConfiguration) -> Result<()> {
}
.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 parser_path = src_path.join("parser.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")?;
if !recompile {
println!("Grammar '{}' is already built.", grammar.grammar_id);
return Ok(());
return Ok(BuildStatus::AlreadyBuilt);
}
println!("Building grammar '{}'", grammar.grammar_id);
let mut config = cc::Build::new();
config
.cpp(true)
.opt_level(3)
.cargo_metadata(false)
.host(BUILD_TARGET)
.target(BUILD_TARGET);
.target(target.unwrap_or(BUILD_TARGET));
let compiler = config.get_compiler();
let mut command = Command::new(compiler.path());
command.current_dir(src_path);
for (key, value) in compiler.env() {
command.env(key, value);
}
command.args(compiler.args());
if cfg!(windows) {
if cfg!(all(windows, target_env = "msvc")) {
command
.args(&["/nologo", "/LD", "/I"])
.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(

@ -2,11 +2,28 @@ pub mod config;
pub mod grammar;
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
use std::path::PathBuf;
pub static RUNTIME_DIR: once_cell::sync::Lazy<std::path::PathBuf> =
once_cell::sync::Lazy::new(runtime_dir);
pub static RUNTIME_DIR: once_cell::sync::Lazy<PathBuf> = 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") {
return dir.into();
}
@ -31,7 +48,7 @@ pub fn runtime_dir() -> std::path::PathBuf {
.unwrap()
}
pub fn config_dir() -> std::path::PathBuf {
pub fn config_dir() -> PathBuf {
// TODO: allow env var override
let strategy = choose_base_strategy().expect("Unable to find the config directory!");
let mut path = strategy.config_dir();
@ -39,7 +56,7 @@ pub fn config_dir() -> std::path::PathBuf {
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()])
.into_iter()
.map(|path| path.join(".helix"))
@ -48,7 +65,7 @@ pub fn local_config_dirs() -> Vec<std::path::PathBuf> {
directories
}
pub fn cache_dir() -> std::path::PathBuf {
pub fn cache_dir() -> PathBuf {
// TODO: allow env var override
let strategy = choose_base_strategy().expect("Unable to find the config directory!");
let mut path = strategy.cache_dir();
@ -56,19 +73,22 @@ pub fn cache_dir() -> std::path::PathBuf {
path
}
pub fn config_file() -> std::path::PathBuf {
config_dir().join("config.toml")
pub fn config_file() -> PathBuf {
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")
}
pub fn log_file() -> std::path::PathBuf {
pub fn log_file() -> PathBuf {
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 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`,
/// where one usually wants to override or add to the array instead of
/// replacing it altogether.
pub fn merge_toml_values(
left: toml::Value,
right: toml::Value,
merge_toplevel_arrays: bool,
) -> toml::Value {
pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usize) -> toml::Value {
use toml::Value;
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
// `languages.toml` but that nested arrays like Language Server
// arguments are replaced instead of merged.
if merge_toplevel_arrays {
if merge_depth > 0 {
left_items.reserve(right_items.len());
for rvalue in right_items {
let lvalue = get_name(&rvalue)
@ -140,7 +156,7 @@ pub fn merge_toml_values(
})
.map(|lpos| left_items.remove(lpos));
let mvalue = match lvalue {
Some(lvalue) => merge_toml_values(lvalue, rvalue, false),
Some(lvalue) => merge_toml_values(lvalue, rvalue, merge_depth - 1),
None => rvalue,
};
left_items.push(mvalue);
@ -151,18 +167,22 @@ pub fn merge_toml_values(
}
}
(Value::Table(mut left_map), Value::Table(right_map)) => {
for (rname, rvalue) in right_map {
match left_map.remove(&rname) {
Some(lvalue) => {
let merged_value = merge_toml_values(lvalue, rvalue, merge_toplevel_arrays);
left_map.insert(rname, merged_value);
}
None => {
left_map.insert(rname, rvalue);
if merge_depth > 0 {
for (rname, rvalue) in right_map {
match left_map.remove(&rname) {
Some(lvalue) => {
let merged_value = merge_toml_values(lvalue, rvalue, merge_depth - 1);
left_map.insert(rname, merged_value);
}
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
(_, value) => value,
@ -187,7 +207,7 @@ mod merge_toml_tests {
.expect("Couldn't parse built-in languages config");
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 nix = languages
.iter()
@ -220,7 +240,7 @@ mod merge_toml_tests {
.expect("Couldn't parse built-in languages config");
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 ts = languages
.iter()

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

@ -58,7 +58,7 @@ pub enum OffsetEncoding {
pub mod util {
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`].
///
@ -78,11 +78,19 @@ pub mod util {
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
lsp::Diagnostic::new(
range_to_lsp_range(doc, range, offset_encoding),
severity,
None,
code,
None,
diag.message.to_owned(),
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)]

@ -10,6 +10,7 @@ repository = "https://github.com/helix-editor/helix"
homepage = "https://helix-editor.com"
include = ["src/**/*", "README.md"]
default-run = "hx"
rust-version = "1.57"
[package.metadata.nix]
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"] }
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"
tokio-stream = "0.1"
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
fern = "0.6"

@ -19,7 +19,8 @@ fn main() {
if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() {
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/");

@ -2,6 +2,7 @@ use arc_swap::{access::Map, ArcSwap};
use futures_util::Stream;
use helix_core::{
config::{default_syntax_loader, user_syntax_loader},
diagnostic::NumberOrString,
pos_at_coords, syntax, Selection,
};
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
@ -11,7 +12,7 @@ use serde_json::json;
use crate::{
args::Args,
commands::apply_workspace_edit,
compositor::Compositor,
compositor::{Compositor, Event},
config::Config,
job::Jobs,
keymap::Keymaps,
@ -28,7 +29,7 @@ use std::{
use anyhow::{Context, Error};
use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event},
event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent},
execute, terminal,
tty::IsTty,
};
@ -88,9 +89,8 @@ impl Application {
use helix_view::editor::Action;
let config_dir = helix_loader::config_dir();
let theme_loader = std::sync::Arc::new(theme::Loader::new(
&config_dir,
&helix_loader::config_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 {
editor: &mut self.editor,
jobs: &mut self.jobs,
@ -426,13 +426,12 @@ impl Application {
};
// Handle key events
let should_redraw = match event {
Ok(Event::Resize(width, height)) => {
Ok(CrosstermEvent::Resize(width, height)) => {
self.compositor.resize(width, height);
self.compositor
.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),
};
@ -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 {
range: Range { start, end },
line: diagnostic.range.start.line as usize,
message: diagnostic.message.clone(),
severity,
// code
code,
// source
})
})
@ -788,7 +799,7 @@ impl Application {
fn restore_term(&mut self) -> Result<(), Error> {
let mut stdout = stdout();
// 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
// disable without calling enable previously
let _ = execute!(stdout, DisableMouseCapture);

@ -14,6 +14,7 @@ pub struct Args {
pub build_grammars: bool,
pub split: Option<Layout>,
pub verbosity: u64,
pub config_file: Option<PathBuf>,
pub files: Vec<(PathBuf, Position)>,
}
@ -43,6 +44,10 @@ impl Args {
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("--") => {
anyhow::bail!("unexpected double dash argument: {}", arg)
}

@ -28,11 +28,12 @@ use helix_core::{
};
use helix_view::{
clipboard::ClipboardType,
document::{Mode, SCRATCH_BUFFER_NAME},
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
info::Info,
input::KeyEvent,
keyboard::KeyCode,
tree,
view::View,
Document, DocumentId, Editor, ViewId,
};
@ -204,17 +205,17 @@ impl MappableCommand {
extend_line_down, "Extend down",
copy_selection_on_next_line, "Copy selection on next line",
copy_selection_on_prev_line, "Copy selection on previous line",
move_next_word_start, "Move to beginning of next word",
move_prev_word_start, "Move to beginning of previous word",
move_next_word_start, "Move to start of next word",
move_prev_word_start, "Move to start 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_long_word_start, "Move to beginning of next long word",
move_prev_long_word_start, "Move to beginning of previous long word",
move_next_long_word_start, "Move to start of next 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",
extend_next_word_start, "Extend to beginning of next word",
extend_prev_word_start, "Extend to beginning of previous word",
extend_next_long_word_start, "Extend to beginning of next long word",
extend_prev_long_word_start, "Extend to beginning of previous long word",
extend_next_word_start, "Extend to start of next word",
extend_prev_word_start, "Extend to start of previous word",
extend_next_long_word_start, "Extend to start of next 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_word_end, "Extend to end of next word",
find_till_char, "Move till next occurrence of char",
@ -225,7 +226,7 @@ impl MappableCommand {
find_prev_char, "Move to previous occurrence of char",
extend_till_prev_char, "Extend till 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",
switch_case, "Switch (toggle) case",
switch_to_uppercase, "Switch to uppercase",
@ -236,7 +237,7 @@ impl MappableCommand {
half_page_down, "Move half page down",
select_all, "Select whole document",
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",
search, "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_prev, "Add previous search match to selection",
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_above, "Select current line, if already selected, extend to previous line",
extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
shrink_to_line_bounds, "Shrink 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",
delete_selection, "Delete selection",
delete_selection_noyank, "Delete selection, without yanking",
change_selection, "Change selection (delete and enter insert mode)",
change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)",
collapse_selection, "Collapse selection onto a single cursor",
delete_selection_noyank, "Delete selection without yanking",
change_selection, "Change selection",
change_selection_noyank, "Change selection without yanking",
collapse_selection, "Collapse selection into single cursor",
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",
append_mode, "Insert after selection (append)",
append_mode, "Append after selection",
command_mode, "Enter command mode",
file_picker, "Open file picker",
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",
last_picker, "Open last picker",
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_above, "Open new line above selection",
normal_mode, "Enter normal mode",
@ -319,13 +320,13 @@ impl MappableCommand {
delete_char_forward, "Delete next char",
delete_word_backward, "Delete previous word",
delete_word_forward, "Delete next word",
kill_to_line_start, "Delete content till the start of the line",
kill_to_line_end, "Delete content till the end of the line",
kill_to_line_start, "Delete till start of line",
kill_to_line_end, "Delete till end of line",
undo, "Undo change",
redo, "Redo change",
earlier, "Move backward 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_joined_to_clipboard, "Join and yank selections 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",
replace_with_yanked, "Replace with yanked text",
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_before, "Paste before selection",
paste_clipboard_after, "Paste clipboard after selections",
@ -358,19 +359,19 @@ impl MappableCommand {
rotate_selection_contents_backward, "Rotate selections contents backward",
expand_selection, "Expand selection to parent syntax node",
shrink_selection, "Shrink selection to previously expanded syntax node",
select_next_sibling, "Select the next sibling in the syntax tree",
select_prev_sibling, "Select the previous sibling in the syntax tree",
select_next_sibling, "Select next sibling in syntax tree",
select_prev_sibling, "Select previous sibling in syntax tree",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist",
jump_view_right, "Jump to the split to the right",
jump_view_left, "Jump to the split to the left",
jump_view_up, "Jump to the split above",
jump_view_down, "Jump to the split below",
swap_view_right, "Swap with the split to the right",
swap_view_left, "Swap with the split to the left",
swap_view_up, "Swap with the split above",
swap_view_down, "Swap with the split below",
save_selection, "Save current selection to jumplist",
jump_view_right, "Jump to right split",
jump_view_left, "Jump to left split",
jump_view_up, "Jump to split above",
jump_view_down, "Jump to split below",
swap_view_right, "Swap with right split",
swap_view_left, "Swap with left split",
swap_view_up, "Swap with split above",
swap_view_down, "Swap with split below",
transpose_view, "Transpose splits",
rotate_view, "Goto next window",
hsplit, "Horizontal bottom split",
@ -378,7 +379,7 @@ impl MappableCommand {
vsplit, "Vertical right split",
vsplit_new, "Vertical right split scratch buffer",
wclose, "Close window",
wonly, "Current window only",
wonly, "Close windows except current",
select_register, "Select register",
insert_register, "Insert register",
align_view_middle, "Align view middle",
@ -414,21 +415,21 @@ impl MappableCommand {
dap_next, "Step to next",
dap_variables, "List variables",
dap_terminate, "End debug session",
dap_edit_condition, "Edit condition of the breakpoint on the current line",
dap_edit_log, "Edit log message of the breakpoint on the current line",
dap_edit_condition, "Edit breakpoint condition on current line",
dap_edit_log, "Edit breakpoint log message on current line",
dap_switch_thread, "Switch current thread",
dap_switch_stack_frame, "Switch stack frame",
dap_enable_exceptions, "Enable exception breakpoints",
dap_disable_exceptions, "Disable exception breakpoints",
shell_pipe, "Pipe selections through shell command",
shell_pipe_to, "Pipe selections into shell command, ignoring command output",
shell_insert_output, "Insert output of shell command before each selection",
shell_append_output, "Append output of shell command after each selection",
shell_pipe_to, "Pipe selections into shell command ignoring output",
shell_insert_output, "Insert shell command output before selections",
shell_append_output, "Append shell command output after selections",
shell_keep_pipe, "Filter selections with shell predicate",
suspend, "Suspend",
suspend, "Suspend and return to shell",
rename_symbol, "Rename symbol",
increment, "Increment",
decrement, "Decrement",
increment, "Increment item under cursor",
decrement, "Decrement item under cursor",
record_macro, "Record macro",
replay_macro, "Replay macro",
command_palette, "Open command pallete",
@ -769,7 +770,7 @@ fn trim_selections(cx: &mut Context) {
.selection(view.id)
.iter()
.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;
}
let mut start = range.from();
@ -803,13 +804,14 @@ fn align_selections(cx: &mut Context) {
let text = doc.text().slice(..);
let selection = doc.selection(view.id);
let tab_width = doc.tab_width();
let mut column_widths: Vec<Vec<_>> = Vec::new();
let mut last_line = text.len_lines() + 1;
let mut col = 0;
for range in selection {
let coords = coords_at_pos(text, range.head);
let anchor_coords = coords_at_pos(text, range.anchor);
let coords = visual_coords_at_pos(text, range.head, tab_width);
let anchor_coords = visual_coords_at_pos(text, range.anchor, tab_width);
if coords.row != anchor_coords.row {
cx.editor
@ -877,8 +879,8 @@ fn goto_window(cx: &mut Context, align: Align) {
let last_line = view.last_line(doc);
let line = match align {
Align::Top => (view.offset.row + scrolloff + count),
Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)),
Align::Top => view.offset.row + scrolloff + count,
Align::Center => view.offset.row + ((last_line - view.offset.row) / 2),
Align::Bottom => last_line.saturating_sub(scrolloff + count),
}
.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)
where
F: Fn(Cow<str>) -> Tendril,
F: Fn(RopeSlice) -> Tendril,
{
let (view, doc) = current!(cx.editor);
let selection = doc.selection(view.id);
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))
});
@ -1322,11 +1324,15 @@ fn switch_case(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) {
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) {
@ -1751,10 +1757,16 @@ fn extend_search_prev(cx: &mut Context) {
fn search_selection(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
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);
let msg = format!("register '{}' set to '{}'", '/', query);
cx.editor.set_status(msg);
}
@ -2504,14 +2516,14 @@ async fn make_format_callback(
doc_id: DocumentId,
doc_version: i32,
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> {
let format = format.await;
let format = format.await?;
let call: job::Callback = Box::new(move |editor, _compositor| {
let view_id = view!(editor).id;
if let Some(doc) = editor.document_mut(doc_id) {
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.detect_indent_and_line_ending();
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 mut fragments: Vec<_> = selection
.fragments(text)
.map(|fragment| Tendril::from(fragment.as_ref()))
.slices(text)
.map(|fragment| fragment.chunks().collect())
.collect();
let group = count
@ -4109,35 +4121,35 @@ fn rotate_view(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) {
cx.editor.focus_left()
cx.editor.focus_direction(tree::Direction::Left)
}
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) {
cx.editor.focus_down()
cx.editor.focus_direction(tree::Direction::Down)
}
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) {
cx.editor.swap_left()
cx.editor.swap_split_in_direction(tree::Direction::Left)
}
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) {
cx.editor.swap_down()
cx.editor.swap_split_in_direction(tree::Direction::Down)
}
fn transpose_view(cx: &mut Context) {
@ -4361,10 +4373,12 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
'o' => textobject_treesitter("comment", range),
't' => textobject_treesitter("test", range),
'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
ch if !ch.is_ascii_alphanumeric() => {
textobject::textobject_surround(text, range, objtype, ch, count)
textobject::textobject_pair_surround(text, range, objtype, ch, count)
}
_ => range,
}
@ -4390,7 +4404,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
("a", "Argument/parameter (tree-sitter)"),
("o", "Comment (tree-sitter)"),
("t", "Test (tree-sitter)"),
("m", "Matching delimiter under cursor"),
("m", "Closest surrounding pair to cursor"),
(" ", "... or any character acting as a pair"),
];
@ -4543,8 +4557,8 @@ fn shell_keep_pipe(cx: &mut Context) {
let text = doc.text().slice(..);
for (i, range) in selection.ranges().iter().enumerate() {
let fragment = range.fragment(text);
let (_output, success) = match shell_impl(shell, input, Some(fragment.as_bytes())) {
let fragment = range.slice(text);
let (_output, success) = match shell_impl(shell, input, Some(fragment)) {
Ok(result) => result,
Err(err) => {
cx.editor.set_error(err.to_string());
@ -4575,20 +4589,24 @@ fn shell_keep_pipe(cx: &mut Context) {
fn shell_impl(
shell: &[String],
cmd: &str,
input: Option<&[u8]>,
input: Option<RopeSlice>,
) -> anyhow::Result<(Tendril, bool)> {
use std::io::Write;
use std::process::{Command, Stdio};
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..])
.arg(cmd)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
.stderr(Stdio::piped());
if input.is_some() || cfg!(windows) {
process.stdin(Stdio::piped());
}
let mut process = match process.spawn() {
Ok(process) => process,
Err(e) => {
log::error!("Failed to start shell: {}", e);
@ -4597,7 +4615,9 @@ fn shell_impl(
};
if let Some(input) = input {
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()?;
@ -4626,8 +4646,8 @@ fn shell(cx: &mut compositor::Context, cmd: &str, behavior: &ShellBehavior) {
let text = doc.text().slice(..);
for range in selection.ranges() {
let fragment = range.fragment(text);
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment.as_bytes())) {
let fragment = range.slice(text);
let (output, success) = match shell_impl(shell, cmd, pipe.then(|| fragment)) {
Ok(result) => result,
Err(err) => {
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| {
for _ in 0..count {
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

@ -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 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()))
}
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);
doc.set_language_by_language_id(&args[0], cx.editor.syn_loader.clone());
doc.detect_indent_and_line_ending();
let id = doc.id();
cx.editor.refresh_language_server(id);
@ -1291,8 +1292,8 @@ fn sort_impl(
let selection = doc.selection(view.id);
let mut fragments: Vec<_> = selection
.fragments(text)
.map(|fragment| Tendril::from(fragment.as_ref()))
.slices(text)
.map(|fragment| fragment.chunks().collect())
.collect();
fragments.sort_by(match reverse {
@ -1535,7 +1536,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit!",
aliases: &["q!"],
doc: "Close the current view forcefully (ignoring unsaved changes).",
doc: "Force close the current view, ignoring unsaved changes.",
fun: force_quit,
completer: None,
},
@ -1556,7 +1557,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "buffer-close!",
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,
completer: Some(completers::buffer),
},
@ -1570,35 +1571,35 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "buffer-close-others!",
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,
completer: None,
},
TypableCommand {
name: "buffer-close-all",
aliases: &["bca", "bcloseall"],
doc: "Close all buffers, without quitting.",
doc: "Close all buffers without quitting.",
fun: buffer_close_all,
completer: None,
},
TypableCommand {
name: "buffer-close-all!",
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,
completer: None,
},
TypableCommand {
name: "buffer-next",
aliases: &["bn", "bnext"],
doc: "Go to next buffer.",
doc: "Goto next buffer.",
fun: buffer_next,
completer: None,
},
TypableCommand {
name: "buffer-previous",
aliases: &["bp", "bprev"],
doc: "Go to previous buffer.",
doc: "Goto previous buffer.",
fun: buffer_previous,
completer: None,
},
@ -1612,7 +1613,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "write!",
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,
completer: Some(completers::filename),
},
@ -1706,7 +1707,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit-all!",
aliases: &["qa!"],
doc: "Close all views forcefully (ignoring unsaved changes).",
doc: "Force close all views ignoring unsaved changes.",
fun: force_quit_all,
completer: None,
},
@ -1720,7 +1721,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "cquit!",
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,
completer: None,
},
@ -1825,7 +1826,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "encoding",
aliases: &[],
doc: "Set encoding based on `https://encoding.spec.whatwg.org`",
doc: "Set encoding. Based on `https://encoding.spec.whatwg.org`.",
fun: set_encoding,
completer: None,
},
@ -1902,7 +1903,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "goto",
aliases: &["g"],
doc: "Go to line number.",
doc: "Goto line number.",
fun: goto_line_number,
completer: None,
},
@ -1958,14 +1959,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "config-reload",
aliases: &[],
doc: "Refreshes helix's config.",
doc: "Refresh user config.",
fun: refresh_config,
completer: None,
},
TypableCommand {
name: "config-open",
aliases: &[],
doc: "Open the helix config.toml file.",
doc: "Open the user config.toml file.",
fun: open_config,
completer: None,
},

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

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

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

@ -64,6 +64,7 @@ FLAGS:
--health [LANG] Checks for potential errors in editor setup
If given, checks for config errors in language LANG
-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
(default file: {})
-V, --version Prints version information
@ -108,7 +109,7 @@ FLAGS:
}
if args.build_grammars {
helix_loader::grammar::build_grammars()?;
helix_loader::grammar::build_grammars(None)?;
return Ok(0);
}
@ -119,14 +120,15 @@ FLAGS:
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)
.map(helix_term::keymap::merge_keys)
.unwrap_or_else(|err| {
eprintln!("Bad config: {}", err);
eprintln!("Press <ENTER> to continue with default config");
use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []);
Config::default()
}),

@ -1,5 +1,4 @@
use crate::compositor::{Component, Context, EventResult};
use crossterm::event::{Event, KeyCode, KeyEvent};
use crate::compositor::{Component, Context, Event, EventResult};
use helix_view::editor::CompleteAction;
use tui::buffer::Buffer as Surface;
use tui::text::Spans;
@ -7,7 +6,11 @@ use tui::text::Spans;
use std::borrow::Cow;
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::ui::{menu, Markdown, Menu, Popup, PromptEvent};

@ -1,6 +1,6 @@
use crate::{
commands,
compositor::{Component, Context, EventResult},
compositor::{Component, Context, Event, EventResult},
job, key,
keymap::{KeymapResult, Keymaps},
ui::{overlay::Overlay, Completion, Explorer, ProgressSpinners},
@ -19,13 +19,12 @@ use helix_view::{
document::Mode,
editor::{CompleteAction, CursorShapeConfig},
graphics::{Color, CursorKind, Modifier, Rect, Style},
input::KeyEvent,
input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind},
keyboard::{KeyCode, KeyModifiers},
Document, Editor, Theme, View,
};
use std::borrow::Cow;
use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind};
use tui::buffer::Buffer as Surface;
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).
let text = doc.text().slice(..);
let characters = &whitespace.characters;
let mut spans = Vec::new();
let mut visual_x = 0u16;
let mut line = 0u16;
let tab_width = doc.tab_width();
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 {
" ".repeat(tab_width)
};
let space = whitespace.characters.space.to_string();
let nbsp = whitespace.characters.nbsp.to_string();
let space = characters.space.to_string();
let nbsp = characters.nbsp.to_string();
let newline = if whitespace.render.newline() == WhitespaceRenderValue::All {
whitespace.characters.newline.to_string()
characters.newline.to_string()
} else {
" ".to_string()
};
@ -416,7 +419,13 @@ impl EditorView {
let mut is_in_indent_area = true;
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| {
if !config.indent_guides.render {
@ -432,7 +441,7 @@ impl EditorView {
viewport.x + (i * tab_width as u16) - offset.col as u16,
viewport.y + line,
&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
// 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);
}
draw_indent_guides(last_line_indent_level, line, surface);
visual_x = 0;
line += 1;
@ -532,6 +534,8 @@ impl EditorView {
(grapheme.as_ref(), width)
};
let cut_off_start = offset.col.saturating_sub(visual_x as usize);
if !out_of_bounds {
// if we're offscreen just keep going until we hit a new line
surface.set_string(
@ -544,7 +548,24 @@ impl EditorView {
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") {
draw_indent_guides(visual_x, line, surface);
is_in_indent_area = false;
@ -950,23 +971,22 @@ impl EditorView {
if let Some((pos, view_id)) = pos_and_view(editor, row, column) {
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();
doc.set_selection(view_id, selection.push(Range::point(pos)));
} else {
doc.set_selection(view_id, Selection::point(pos));
}
editor.tree.focus = view_id;
editor.focus(view_id);
return EventResult::Consumed(None);
}
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 doc = editor.documents.get_mut(&view.doc).unwrap();
let (view, doc) = current!(cxt.editor);
let path = match doc.path() {
Some(path) => path.clone(),
@ -1013,7 +1033,7 @@ impl EditorView {
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);
cxt.editor.tree.focus = current_view;
@ -1031,8 +1051,8 @@ impl EditorView {
if doc
.selection(view.id)
.primary()
.fragment(doc.text().slice(..))
.width()
.slice(doc.text().slice(..))
.len_chars()
<= 1
{
return EventResult::Ignored(None);
@ -1045,14 +1065,13 @@ impl EditorView {
MouseEventKind::Up(MouseButton::Right) => {
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 doc = cxt.editor.documents.get_mut(&view.doc).unwrap();
let (view, doc) = current!(cxt.editor);
let line = coords.row + view.offset.row;
if let Ok(pos) = doc.text().try_line_to_char(line) {
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);
} else {
commands::MappableCommand::dap_edit_condition.execute(cxt);
@ -1071,7 +1090,7 @@ impl EditorView {
return EventResult::Ignored(None);
}
if modifiers == crossterm::event::KeyModifiers::ALT {
if modifiers == KeyModifiers::ALT {
commands::MappableCommand::replace_selections_with_primary_clipboard
.execute(cxt);
@ -1081,7 +1100,7 @@ impl EditorView {
if let Some((pos, view_id)) = pos_and_view(editor, row, column) {
let doc = editor.document_mut(editor.tree.get(view_id).doc).unwrap();
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);
return EventResult::Consumed(None);
@ -1121,9 +1140,8 @@ impl Component for EditorView {
// Handling it here but not re-rendering will cause flashing
EventResult::Consumed(None)
}
Event::Key(key) => {
Event::Key(mut key) => {
cx.editor.reset_idle_timer();
let mut key = KeyEvent::from(key);
canonicalize_key(&mut key);
// clear status
@ -1240,6 +1258,7 @@ impl Component for EditorView {
}
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 crate::{
compositor::{Callback, Component, Compositor, Context, EventResult},
compositor::{Callback, Component, Compositor, Context, Event, EventResult},
ctrl, key, shift,
};
use crossterm::event::Event;
use tui::{buffer::Buffer as Surface, text::Spans, widgets::Table};
pub use tui::widgets::{Cell, Row};
@ -237,7 +236,7 @@ impl<T: Item + 'static> Component for Menu<T> {
compositor.pop();
}));
match event.into() {
match event {
// esc or ctrl-c aborts the completion and closes the menu
key!(Esc) | ctrl!('c') => {
(self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort);

@ -264,6 +264,7 @@ pub mod completers {
names.push("default".into());
names.push("base16_default".into());
names.sort();
names.dedup();
let mut names: Vec<_> = names
.into_iter()
@ -287,14 +288,28 @@ pub mod completers {
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> {
static KEYS: Lazy<Vec<String>> = Lazy::new(|| {
serde_json::json!(Config::default())
.as_object()
.unwrap()
.keys()
.cloned()
.collect()
let mut keys = Vec::new();
let json = serde_json::json!(Config::default());
get_keys(&json, &mut keys, None);
keys
});
let matcher = Matcher::default();

@ -1,4 +1,3 @@
use crossterm::event::Event;
use helix_core::Position;
use helix_view::{
graphics::{CursorKind, Rect},
@ -6,7 +5,7 @@ use helix_view::{
};
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
pub struct Overlay<T> {

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

@ -1,9 +1,8 @@
use crate::{
commands::Open,
compositor::{Callback, Component, Context, EventResult},
compositor::{Callback, Component, Context, Event, EventResult},
ctrl, key,
};
use crossterm::event::Event;
use tui::buffer::Buffer as Surface;
use helix_core::Position;
@ -149,7 +148,7 @@ impl<T: Component> Component for Popup<T> {
_ => 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);
}
@ -158,7 +157,7 @@ impl<T: Component> Component for Popup<T> {
compositor.remove(self.id.as_ref());
});
match key.into() {
match key {
// esc or ctrl-c aborts the completion and closes the menu
key!(Esc) | ctrl!('c') => {
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 crossterm::event::Event;
use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode;
use std::{borrow::Cow, ops::RangeFrom};
@ -479,7 +478,7 @@ impl Component for Prompt {
compositor.pop();
})));
match event.into() {
match event {
ctrl!('c') | key!(Esc) => {
(self.callback_fn)(cx, &self.line, PromptEvent::Abort);
return close_fn;
@ -533,16 +532,17 @@ impl Component for Prompt {
.map(|entry| entry.into())
.unwrap_or_else(|| Cow::from(""))
} 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.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;
}
}

@ -1,4 +1,4 @@
use helix_core::{coords_at_pos, encoding};
use helix_core::{coords_at_pos, encoding, Position};
use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME},
graphics::Rect,
@ -143,6 +143,9 @@ where
helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics,
helix_view::editor::StatusLineElement::Selections => render_selections,
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)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
{
let position = coords_at_pos(
fn get_position(context: &RenderContext) -> Position {
coords_at_pos(
context.doc.text().slice(..),
context
.doc
.selection(context.view.id)
.primary()
.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(
context,
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)
where
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
@ -334,3 +353,23 @@ where
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"
cassowary = "0.3"
unicode-segmentation = "1.9"
crossterm = { version = "0.24", optional = true }
crossterm = { version = "0.25", optional = true }
serde = { version = "1", "optional" = true, features = ["derive"]}
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
helix-core = { version = "0.6", path = "../helix-core" }

@ -19,13 +19,13 @@ anyhow = "1"
helix-core = { version = "0.6", path = "../helix-core" }
helix-lsp = { version = "0.6", path = "../helix-lsp" }
helix-dap = { version = "0.6", path = "../helix-dap" }
crossterm = { version = "0.24", optional = true }
crossterm = { version = "0.25", optional = true }
# Conversion traits
once_cell = "1.13"
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-stream = "0.1"

@ -17,6 +17,10 @@ pub trait ClipboardProvider: std::fmt::Debug {
#[cfg(not(windows))]
macro_rules! command_provider {
(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 {
get_cmd: provider::command::Config {
prg: $get_prg,
@ -36,6 +40,10 @@ macro_rules! command_provider {
primary_paste => $pr_get_prg:literal $( , $pr_get_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 {
get_cmd: provider::command::Config {
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 anyhow::Result;
use std::borrow::Cow;
@ -146,6 +154,9 @@ mod provider {
#[cfg(not(target_os = "windows"))]
impl NopProvider {
pub fn new() -> Self {
log::warn!(
"No clipboard provider found! Yanking and pasting will be internal to Helix"
);
Self {
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"))]
impl ClipboardProvider for NopProvider {
fn name(&self) -> Cow<str> {
@ -184,6 +202,7 @@ mod provider {
#[cfg(target_os = "windows")]
impl ClipboardProvider for WindowsProvider {
fn name(&self) -> Cow<str> {
log::info!("Using clipboard-win to interact with the system clipboard");
Cow::Borrowed("clipboard-win")
}
@ -213,12 +232,11 @@ mod provider {
use super::*;
use anyhow::{bail, Context as _, Result};
#[cfg(not(windows))]
pub fn exists(executable_name: &str) -> bool {
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 {
std::env::var_os(env_var_name).is_some()
}

@ -1,4 +1,6 @@
use anyhow::{anyhow, bail, Context, Error};
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use helix_core::auto_pairs::AutoPairs;
use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer};
@ -20,7 +22,6 @@ use helix_core::{
ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, State, Syntax, Transaction,
DEFAULT_LINE_ENDING,
};
use helix_lsp::util::LspFormatting;
use crate::{DocumentId, Editor, ViewId};
@ -397,7 +398,7 @@ impl Document {
/// The same as [`format`], but only returns formatting changes if auto-formatting
/// 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 {
self.format()
} else {
@ -407,7 +408,56 @@ impl Document {
/// If supported, returns the changes that should be applied to this document in order
/// 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 text = self.text.clone();
let offset_encoding = language_server.offset_encoding();
@ -427,13 +477,13 @@ impl Document {
log::warn!("LSP formatting failed: {}", e);
Default::default()
});
LspFormatting {
doc: text,
Ok(helix_lsp::util::generate_transaction_from_edits(
&text,
edits,
offset_encoding,
}
))
};
Some(fut)
Some(fut.boxed())
}
pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
@ -442,7 +492,7 @@ impl Document {
pub fn format_and_save(
&mut self,
formatting: Option<impl Future<Output = LspFormatting>>,
formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
force: bool,
) -> impl Future<Output = anyhow::Result<()>> {
self.save_impl(formatting, force)
@ -454,7 +504,7 @@ impl Document {
/// at its `path()`.
///
/// 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,
formatting: Option<F>,
force: bool,
@ -488,7 +538,8 @@ impl Document {
}
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 {
// This shouldn't happen, because the transaction changes were generated
// 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
/// configured in `languages.toml`, with a fallback to 4 space indentation if it isn't
/// specified. Line ending is likewise auto-detected, and will fallback to the default OS
/// line ending.
/// configured in `languages.toml`, with a fallback to tabs if it isn't specified. Line ending
/// is likewise auto-detected, and will fallback to the default OS line ending.
pub fn detect_indent_and_line_ending(&mut self) {
self.indent_style = auto_detect_indent_style(&self.text).unwrap_or_else(|| {
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)]
mod test {
use super::*;

@ -220,6 +220,7 @@ pub struct Config {
#[serde(default)]
pub search: SearchConfig,
pub lsp: LspConfig,
pub terminal: Option<TerminalConfig>,
/// Column numbers at which to draw the rulers. Default to `[]`, meaning no rulers.
pub rulers: Vec<u16>,
#[serde(default)]
@ -232,6 +233,52 @@ pub struct Config {
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)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct LspConfig {
@ -268,6 +315,7 @@ pub struct StatusLineConfig {
pub left: Vec<StatusLineElement>,
pub center: Vec<StatusLineElement>,
pub right: Vec<StatusLineElement>,
pub separator: String,
}
impl Default for StatusLineConfig {
@ -278,6 +326,7 @@ impl Default for StatusLineConfig {
left: vec![E::Mode, E::Spinner, E::FileName],
center: vec![],
right: vec![E::Diagnostics, E::Selections, E::Position, E::FileEncoding],
separator: String::from("│"),
}
}
}
@ -311,6 +360,15 @@ pub enum StatusLineElement {
/// The cursor 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
@ -398,7 +456,7 @@ pub enum GutterType {
/// Show line numbers
LineNumbers,
/// Show one blank space
Padding,
Spacer,
}
impl std::str::FromStr for GutterType {
@ -492,6 +550,7 @@ pub struct WhitespaceCharacters {
pub space: char,
pub nbsp: char,
pub tab: char,
pub tabpad: char,
pub newline: char,
}
@ -502,6 +561,7 @@ impl Default for WhitespaceCharacters {
nbsp: '', // U+237D
tab: '', // U+2192
newline: '', // U+23CE
tabpad: ' ',
}
}
}
@ -535,11 +595,7 @@ impl Default for Config {
},
line_number: LineNumber::Absolute,
cursorline: false,
gutters: vec![
GutterType::Diagnostics,
GutterType::LineNumbers,
GutterType::Padding,
],
gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
middle_click_paste: true,
auto_pairs: AutoPairConfig::default(),
auto_completion: true,
@ -553,6 +609,7 @@ impl Default for Config {
true_color: false,
search: SearchConfig::default(),
lsp: LspConfig::default(),
terminal: get_terminal_provider(),
rulers: Vec::new(),
whitespace: WhitespaceConfig::default(),
indent_guides: IndentGuidesConfig::default(),
@ -672,7 +729,6 @@ impl Editor {
syn_loader: Arc<syntax::Loader>,
config: Box<dyn DynAccess<Config>>,
) -> Self {
let language_servers = helix_lsp::Registry::new();
let conf = config.load();
let auto_pairs = (&conf.auto_pairs).into();
@ -688,7 +744,7 @@ impl Editor {
macro_recording: None,
macro_replaying: Vec::new(),
theme: theme_loader.default(),
language_servers,
language_servers: helix_lsp::Registry::new(),
diagnostics: BTreeMap::new(),
debugger: None,
debugger_events: SelectAll::new(),
@ -1118,40 +1174,37 @@ impl Editor {
};
}
pub fn focus_next(&mut self) {
self.tree.focus_next();
}
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(&mut self, view_id: ViewId) {
let prev_id = std::mem::replace(&mut self.tree.focus, view_id);
pub fn focus_up(&mut self) {
self.tree.focus_direction(tree::Direction::Up);
}
pub fn focus_down(&mut self) {
self.tree.focus_direction(tree::Direction::Down);
// if leaving the view: mode should reset
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 swap_right(&mut self) {
self.tree.swap_split_in_direction(tree::Direction::Right);
}
pub fn focus_next(&mut self) {
let prev_id = self.tree.focus;
self.tree.focus_next();
let id = self.tree.focus;
pub fn swap_left(&mut self) {
self.tree.swap_split_in_direction(tree::Direction::Left);
// if leaving the view: mode should reset
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) {
self.tree.swap_split_in_direction(tree::Direction::Up);
pub fn focus_direction(&mut self, direction: tree::Direction) {
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) {
self.tree.swap_split_in_direction(tree::Direction::Down);
pub fn swap_split_in_direction(&mut self, direction: tree::Direction) {
self.tree.swap_split_in_direction(direction);
}
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_lsp::block_on;
use log::warn;
use std::io::ErrorKind;
use std::fmt::Write;
use std::path::PathBuf;
#[macro_export]
@ -180,10 +180,10 @@ impl Editor {
let mut status = format!("{} stopped because of {}", scope, reason);
if let Some(desc) = description {
status.push_str(&format!(" {}", desc));
write!(status, " {}", desc).unwrap();
}
if let Some(text) = text {
status.push_str(&format!(" {}", text));
write!(status, " {}", text).unwrap();
}
if 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();
// TODO: no unwrap
let process = if cfg!(windows) {
std::process::Command::new("wt")
.arg("new-tab")
.arg("--title")
.arg("DEBUG")
.arg("cmd")
.arg("/C")
.arg(arguments.args.join(" "))
.spawn()
.unwrap_or_else(|error| match error.kind() {
ErrorKind::NotFound => std::process::Command::new("conhost")
.arg("cmd")
.arg("/C")
.arg(arguments.args.join(" "))
.spawn()
.unwrap(),
// TODO replace the pretty print {:?} with a regular format {}
// when the MSRV is raised to 1.60.0
e => panic!("Error to start debug console: {:?}", e),
})
} else {
std::process::Command::new("tmux")
.arg("split-window")
.arg(arguments.args.join(" "))
.spawn()
.unwrap()
let config = match self.config().terminal.clone() {
Some(config) => config,
None => {
self.set_error("No external terminal defined");
return true;
}
};
// Re-borrowing debugger to avoid issues when loading config
let debugger = match self.debugger.as_mut() {
Some(debugger) => debugger,
None => return false,
};
let process = match std::process::Command::new(config.command)
.args(config.args)
.arg(arguments.args.join(" "))
.spawn()
{
Ok(process) => process,
Err(err) => {
// TODO replace the pretty print {:?} with a regular format {}
// when the MSRV is raised to 1.60.0
self.set_error(format!("Error starting external terminal: {:?}", err));
return true;
}
};
let _ = debugger

@ -4,14 +4,62 @@ use helix_core::unicode::{segmentation::UnicodeSegmentation, width::UnicodeWidth
use serde::de::{self, Deserialize, Deserializer};
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.
// We use a newtype here because we want to customize Deserialize and Display.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: KeyModifiers,
// TODO: crossterm now supports kind & state if terminal supports kitty's extended protocol
}
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")]
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 {
// special case for BackTab -> Shift-Tab
let mut modifiers: KeyModifiers = modifiers.into();
@ -249,11 +361,15 @@ impl From<KeyEvent> for crossterm::event::KeyEvent {
crossterm::event::KeyEvent {
code: crossterm::event::KeyCode::BackTab,
modifiers: modifiers.into(),
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
}
} else {
crossterm::event::KeyEvent {
code: code.into(),
modifiers: modifiers.into(),
kind: crossterm::event::KeyEventKind::Press,
state: crossterm::event::KeyEventState::NONE,
}
}
}

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

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

@ -217,7 +217,7 @@ file-types = ["cs"]
roots = ["sln", "csproj"]
comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
language-server = { command = "OmniSharp", args = [ "--languageserver", "--stdio" ] }
language-server = { command = "OmniSharp", args = [ "--languageserver" ] }
[[grammar]]
name = "c-sharp"
@ -285,6 +285,20 @@ indent = { tab-width = 4, unit = "\t" }
name = "gomod"
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]]
name = "gowork"
scope = "source.gowork"
@ -423,7 +437,7 @@ indent = { tab-width = 4, unit = " " }
[[grammar]]
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]]
name = "nickel"
@ -523,7 +537,7 @@ indent = { tab-width = 4, unit = "\t" }
[[grammar]]
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]]
name = "lean"
@ -585,6 +599,19 @@ indent = { tab-width = 4, unit = " " }
name = "ledger"
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]]
name = "ocaml"
scope = "source.ocaml"
@ -692,6 +719,8 @@ auto-format = true
comment-token = "//"
language-server = { command = "zls" }
indent = { tab-width = 4, unit = " " }
formatter = { command = "zig" , args = ["fmt", "--stdin"] }
[[grammar]]
name = "zig"
@ -868,7 +897,19 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
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]]
name = "dart"
@ -1025,7 +1066,7 @@ indent = { tab-width = 4, unit = " " }
[[grammar]]
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]]
name = "iex"
@ -1116,7 +1157,7 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
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]]
name = "solidity"
@ -1193,7 +1234,7 @@ language-server = { command = "sourcekit-lsp" }
[[grammar]]
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]]
name = "erb"
@ -1354,7 +1395,8 @@ name = "odin"
auto-format = false
scope = "source.odin"
file-types = ["odin"]
roots = []
roots = ["ols.json"]
language-server = { command = "ols", args = [] }
comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
@ -1483,10 +1525,10 @@ source = { git = "https://github.com/victorhqc/tree-sitter-prisma", rev = "17a59
[[language]]
name = "clojure"
scope = "source.clojure"
injection-regex = "(clojure|clj)"
file-types = ["clj"]
roots = ["project.clj"]
comment-token = ";;"
injection-regex = "(clojure|clj|edn|boot)"
file-types = ["clj", "cljs", "cljc", "clje", "cljr", "cljx", "edn", "boot"]
roots = ["project.clj", "build.boot", "deps.edn", "shadow-cljs.edn"]
comment-token = ";"
language-server = { command = "clojure-lsp" }
indent = { tab-width = 2, unit = " " }
@ -1555,3 +1597,59 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "ungrammar"
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) . (_)?))
(do_block (_)* @test.inside)?)
(#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
;--------------------
([
(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
; 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
; Function and method parameters
;-------------------------------
; (p) => ...
(formal_parameters
(identifier) @variable.parameter)
; (...p) => ...
(formal_parameters
(rest_pattern
(identifier) @variable.parameter))
; ({ p }) => ...
(formal_parameters
(object_pattern
(shorthand_property_identifier_pattern) @variable.parameter))
; ({ a: p }) => ...
(formal_parameters
(object_pattern
(pair_pattern
value: (identifier) @variable.parameter)))
; ([ p ]) => ...
(formal_parameters
(array_pattern
(identifier) @variable.parameter))
; (p = 1) => ...
(formal_parameters
(assignment_pattern
left: (identifier) @variable.parameter))
; p => ...
(arrow_function
parameter: (identifier) @variable.parameter)
; inherits: ecma

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

@ -1,36 +1 @@
; 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 "^//"))
; inherits: ecma

@ -1,23 +1 @@
; Scopes
;-------
[
(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
; inherits: ecma

@ -1,4 +1,4 @@
; inherits: javascript
; inherits: ecma
; Highlight component names differently
(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
[
(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*"))
;; General syntax
(ERROR) @error
[
(generic_command_name)
"\\newcommand"
"\\renewcommand"
"\\DeclareRobustCommand"
"\\DeclareMathOperator"
"\\newglossaryentry"
"\\caption"
"\\label"
"\\newlabel"
"\\color"
"\\colorbox"
"\\textcolor"
"\\pagecolor"
"\\definecolor"
"\\definecolorset"
"\\newtheorem"
"\\declaretheorem"
"\\newacronym"
] @function.macro
(command_name) @function
(caption
command: _ @function)
[
"\\ref"
"\\vref"
"\\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
(key_value_pair
key: (_) @variable.parameter
value: (_))
[
"\\cite"
"\\cite*"
"\\Cite"
"\\nocite"
"\\citet"
"\\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
(comment)
(line_comment)
(block_comment)
(comment_environment)
] @comment
[
"\\ref"
"\\vref"
"\\Vref"
"\\autoref"
"\\pageref"
"\\cref"
"\\Cref"
"\\cref*"
"\\Cref*"
"\\namecref"
"\\nameCref"
"\\lcnamecref"
"\\namecrefs"
"\\nameCrefs"
"\\lcnamecrefs"
"\\labelcref"
"\\labelcpageref"
] @function.macro
(brack_group)
(brack_group_argc)
] @variable.parameter
[(operator) "="] @operator
[
"\\crefrange"
"\\crefrange"
"\\Crefrange"
"\\Crefrange"
"\\crefrange*"
"\\crefrange*"
"\\Crefrange*"
"\\Crefrange*"
] @function.macro
"\\item" @punctuation.special
((word) @punctuation.delimiter
(#eq? @punctuation.delimiter "&"))
[
"\\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
["[" "]" "{" "}"] @punctuation.bracket ; "(" ")" has no syntactical meaning in LaTeX
;; General environments
(begin
command: _ @function.builtin
name: (curly_group_text (text) @function.macro))
[
"\\acrshort"
"\\Acrshort"
"\\ACRshort"
"\\acrshortpl"
"\\Acrshortpl"
"\\ACRshortpl"
"\\acrlong"
"\\Acrlong"
"\\ACRlong"
"\\acrlongpl"
"\\Acrlongpl"
"\\ACRlongpl"
"\\acrfull"
"\\Acrfull"
"\\ACRfull"
"\\acrfullpl"
"\\Acrfullpl"
"\\ACRfullpl"
"\\acs"
"\\Acs"
"\\acsp"
"\\Acsp"
"\\acl"
"\\Acl"
"\\aclp"
"\\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
(end
command: _ @function.builtin
name: (curly_group_text (text) @function.macro))
;; Definitions and references
(new_command_definition
command: _ @function.macro
declaration: (curly_group_command_name (_) @function))
(old_command_definition
command: _ @function.macro
declaration: (_) @function)
(let_command_definition
command: _ @function.macro
declaration: (_) @function)
(environment_definition
command: _ @function.macro
name: (curly_group_text (_) @constant))
(theorem_definition
command: _ @function.macro
name: (curly_group_text (_) @constant))
(paired_delimiter_definition
command: _ @function.macro
declaration: (curly_group_command_name (_) @function))
[
"\\usepackage"
"\\documentclass"
"\\input"
"\\include"
"\\subfile"
"\\subfileinclude"
"\\subfileinclude"
"\\includegraphics"
"\\addbibresource"
"\\bibliography"
"\\includesvg"
"\\includeinkscape"
"\\usepgflibrary"
"\\usetikzlibrary"
] @keyword.control.import
(label_definition
command: _ @function.macro
name: (curly_group_text (_) @label))
(label_reference_range
command: _ @function.macro
from: (curly_group_text (_) @label)
to: (curly_group_text (_) @label))
(label_reference
command: _ @function.macro
names: (curly_group_text_list (_) @label))
(label_number
command: _ @function.macro
name: (curly_group_text (_) @label)
number: (_) @markup.link.label)
[
"\\part"
"\\chapter"
"\\section"
"\\subsection"
"\\subsubsection"
"\\paragraph"
"\\subparagraph"
] @type
(citation
command: _ @function.macro
keys: (curly_group_text_list) @string)
(glossary_entry_definition
command: _ @function.macro
name: (curly_group_text (_) @string))
(glossary_entry_reference
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
(#eq? @punctuation.delimiter "&"))
(displayed_equation) @markup.raw.block
(inline_formula) @markup.raw.inline
["$" "\\[" "\\]" "\\(" "\\)"] @punctuation.delimiter
(math_environment
(begin
command: _ @function.builtin
name: (curly_group_text (text) @markup.raw)))
(label_definition
name: (_) @text.reference)
(label_reference
label: (_) @text.reference)
(equation_label_reference
label: (_) @text.reference)
(label_reference
label: (_) @text.reference)
(label_number
label: (_) @text.reference)
(math_environment
(text) @markup.raw)
(citation
key: (word) @text.reference)
(math_environment
(end
command: _ @function.builtin
name: (curly_group_text (text) @markup.raw)))
(key_val_pair
key: (_) @variable.parameter
value: (_))
;; Sectioning
(title_declaration
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
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(part
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(section
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subsection
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subsubsection
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(paragraph
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
(subparagraph
text: (brace_group) @markup.heading)
command: _ @namespace
toc: (brack_group (_) @markup.heading)?
text: (curly_group (_) @markup.heading))
((environment
;; Beamer frames
(generic_environment
(begin
name: (word) @_frame)
(brace_group
child: (text) @markup.heading))
(#eq? @_frame "frame"))
name: (curly_group_text
(text) @markup.heading)
(#any-of? @markup.heading "frame"))
.
(curly_group (_) @markup.heading))
((generic_command
name:(generic_command_name) @_name
arg: (brace_group
command: (command_name) @_name
arg: (curly_group
(text) @markup.heading))
(#eq? @_name "\\frametitle"))
(#eq? @_name "\\frametitle"))
;; Formatting
((generic_command
name:(generic_command_name) @_name
arg: (_) @markup.italic)
(#eq? @_name "\\emph"))
command: (command_name) @_name
arg: (curly_group (_) @markup.italic))
(#eq? @_name "\\emph"))
((generic_command
name:(generic_command_name) @_name
arg: (_) @markup.italic)
(#match? @_name "^(\\\\textit|\\\\mathit)$"))
command: (command_name) @_name
arg: (curly_group (_) @markup.italic))
(#match? @_name "^(\\\\textit|\\\\mathit)$"))
((generic_command
name:(generic_command_name) @_name
arg: (_) @markup.bold)
(#match? @_name "^(\\\\textbf|\\\\mathbf)$"))
command: (command_name) @_name
arg: (curly_group (_) @markup.bold))
(#match? @_name "^(\\\\textbf|\\\\mathbf)$"))
((generic_command
name:(generic_command_name) @_name
command: (command_name) @_name
.
arg: (_) @markup.link.url)
(#match? @_name "^(\\\\url|\\\\href)$"))
(ERROR) @error
[
"\\begin"
"\\end"
] @text.environment
(begin
name: (_) @text.environment.name
(#not-any-of? @text.environment.name
"displaymath" "displaymath*"
"equation" "equation*"
"multline" "multline*"
"eqnarray" "eqnarray*"
"align" "align*"
"array" "array*"
"split" "split*"
"alignat" "alignat*"
"gather" "gather*"
"flalign" "flalign*"))
(end
name: (_) @text.environment.name
(#not-any-of? @text.environment.name
"displaymath" "displaymath*"
"equation" "equation*"
"multline" "multline*"
"eqnarray" "eqnarray*"
"align" "align*"
"array" "array*"
"split" "split*"
"alignat" "alignat*"
"gather" "gather*"
"flalign" "flalign*"))
arg: (curly_group (_) @markup.link.uri))
(#match? @_name "^(\\\\url|\\\\href)$"))
;; File inclusion commands
(class_include
command: _ @keyword.storage.type
path: (curly_group_path) @string)
(package_include
command: _ @keyword.storage.type
paths: (curly_group_path_list) @string)
(latex_include
command: _ @keyword.control.import
path: (curly_group_path) @string)
(import_include
command: _ @keyword.control.import
directory: (curly_group_path) @string
file: (curly_group_path) @string)
(bibtex_include
command: _ @keyword.control.import
path: (curly_group_path) @string)
(biblatex_include
"\\addbibresource" @include
glob: (curly_group_glob_pattern) @string.regex)
(graphics_include
command: _ @keyword.control.import
path: (curly_group_path) @string)
(tikz_library_import
command: _ @keyword.control.import
paths: (curly_group_path_list) @string)

@ -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)
(atx_heading (atx_h2_marker) @markup.heading.marker (heading_content) @markup.heading.2)
(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_h5_marker) @markup.heading.marker (heading_content) @markup.heading.5)
(atx_heading (atx_h6_marker) @markup.heading.marker (heading_content) @markup.heading.6)
(setext_heading (paragraph) @markup.heading.1 (setext_h1_underline) @markup.heading.marker)
(setext_heading (paragraph) @markup.heading.2 (setext_h2_underline) @markup.heading.marker)
(atx_heading (atx_h1_marker) @markup.heading.marker (inline) @markup.heading.1)
(atx_heading (atx_h2_marker) @markup.heading.marker (inline) @markup.heading.2)
(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)
(code_fence_content)
(fenced_code_block)
] @markup.raw.block
(block_quote) @markup.quote
(code_span) @markup.raw.inline
(emphasis) @markup.italic
(strong_emphasis) @markup.bold
(info_string) @label
(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)
(image_description)
] @markup.link.text
(link_label)
] @markup.link.label
[
(list_marker_plus)
(list_marker_minus)
(list_marker_star)
] @markup.list.numbered
] @markup.list.unnumbered
[
(list_marker_dot)
(list_marker_parenthesis)
] @markup.list.unnumbered
] @markup.list.numbered
(thematic_break) @punctuation.delimiter
[
(backslash_escape)
(hard_line_break)
] @constant.character.escape
(block_continuation)
(block_quote_marker)
] @punctuation.special
(thematic_break) @punctuation.delimiter
[
(backslash_escape)
] @string.escape
(inline_link ["[" "]" "(" ")"] @punctuation.bracket)
(image ["[" "]" "(" ")"] @punctuation.bracket)
(fenced_code_block_delimiter) @punctuation.bracket
(block_quote) @markup.quote

@ -1,9 +1,13 @@
; From nvim-treesitter/nvim-treesitter
(fenced_code_block
(info_string) @injection.language
(code_fence_content) @injection.content
(#set! injection.include-children))
((html_block) @injection.content
(#set! injection.language "html"))
((html_tag) @injection.content
(#set! injection.language "html"))
(info_string
(language) @injection.language)
(code_fence_content) @injection.content (#set! injection.include-unnamed-children))
((html_block) @injection.content (#set! injection.language "html") (#set! injection.include-unnamed-children))
((minus_metadata) @injection.content (#set! injection.language "yaml") (#set! injection.include-unnamed-children))
((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 (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
(array_creation_expression "array" @function.builtin)
@ -17,7 +25,7 @@
name: (name) @function.method)
(function_call_expression
function: (qualified_name (name)) @function)
function: (_) @function)
(scoped_call_expression
name: (name) @function)
@ -28,6 +36,7 @@
(function_definition
name: (name) @function)
; Member
(property_element
@ -67,52 +76,54 @@
; Keywords
"abstract" @keyword
"as" @keyword
"break" @keyword
"case" @keyword
"catch" @keyword
"class" @keyword
"const" @keyword
"continue" @keyword
"declare" @keyword
"default" @keyword
"do" @keyword
"echo" @keyword
"else" @keyword
"elseif" @keyword
"enddeclare" @keyword
"endforeach" @keyword
"endif" @keyword
"endswitch" @keyword
"endwhile" @keyword
"enum" @keyword
"extends" @keyword
"final" @keyword
"finally" @keyword
"foreach" @keyword
"fn" @keyword
"function" @keyword
"global" @keyword
"if" @keyword
"implements" @keyword
"include_once" @keyword
"include" @keyword
"insteadof" @keyword
"interface" @keyword
"match" @keyword
"namespace" @keyword
"new" @keyword
"private" @keyword
"protected" @keyword
"public" @keyword
"require_once" @keyword
"require" @keyword
"return" @keyword
"static" @keyword
"switch" @keyword
"throw" @keyword
"trait" @keyword
"try" @keyword
"use" @keyword
"while" @keyword
[
"abstract"
"as"
"break"
"case"
"catch"
"class"
"const"
"continue"
"declare"
"default"
"do"
"echo"
"else"
"elseif"
"enddeclare"
"endforeach"
"endif"
"endswitch"
"endwhile"
"enum"
"extends"
"final"
"finally"
"foreach"
"fn"
"function"
"global"
"if"
"implements"
"include_once"
"include"
"insteadof"
"interface"
"match"
"namespace"
"new"
"private"
"protected"
"public"
"require_once"
"require"
"return"
"static"
"switch"
"throw"
"trait"
"try"
"use"
"while"
] @keyword

@ -1,3 +1,11 @@
; Imports
(dotted_name
(identifier)* @namespace)
(aliased_import
alias: (identifier) @namespace)
; Builtin functions
((call
@ -8,6 +16,11 @@
; Function calls
[
"def"
"lambda"
] @keyword.function
(call
function: (attribute attribute: (identifier) @constructor)
(#match? @constructor "^[A-Z]"))
@ -47,7 +60,16 @@
(parameters (typed_parameter (identifier) @variable.parameter))
(parameters (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
@ -81,12 +103,11 @@
(identifier) @variable
; Literals
(none) @constant.builtin
[
(none)
(true)
(false)
] @constant.builtin
] @constant.builtin.boolean
(integer) @constant.numeric.integer
(float) @constant.numeric.float
@ -94,9 +115,11 @@
(string) @string
(escape_sequence) @constant.character.escape
["," "." ":" ";" (ellipsis)] @punctuation.delimiter
(interpolation
"{" @punctuation.special
"}" @punctuation.special) @embedded
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
[
"-"
@ -135,24 +158,39 @@
"as"
"assert"
"await"
"break"
"continue"
"from"
"pass"
"with"
] @keyword.control
[
"if"
"elif"
"else"
"except"
"finally"
] @keyword.control.conditional
[
"while"
"for"
"from"
"if"
"import"
"pass"
"raise"
"break"
"continue"
] @keyword.control.repeat
[
"return"
"try"
"while"
"with"
"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_in_clause "in" @keyword.control)
@ -161,16 +199,22 @@
"and"
"async"
"class"
"def"
"del"
"exec"
"global"
"in"
"is"
"lambda"
"nonlocal"
"not"
"or"
"print"
] @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)
] @constant.character.escape
(group_name) @property
(group_name) @label
(count_quantifier
[

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

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

Loading…
Cancel
Save