diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..4b89e775 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a83a1423..b8be1541 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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) diff --git a/.ignore b/.ignore new file mode 100644 index 00000000..865856b4 --- /dev/null +++ b/.ignore @@ -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 diff --git a/Cargo.lock b/Cargo.lock index fb94d1e0..0277cc49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/README.md b/README.md index 48c24de7..1e8a10e6 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/book/src/configuration.md b/book/src/configuration.md index 617071b6..003420c0 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.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"]`
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 diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 21371c93..e9ef56cf 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -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` | diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index 4f04c278..3d180853 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -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.
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. | diff --git a/book/src/install.md b/book/src/install.md index ea46976f..b3109dd9 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -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 diff --git a/book/src/keymap.md b/book/src/keymap.md index c2f99edd..1fd20bed 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -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. diff --git a/book/src/languages.md b/book/src/languages.md index a9d5bea8..841b1377 100644 --- a/book/src/languages.md +++ b/book/src/languages.md @@ -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 diff --git a/book/src/themes.md b/book/src/themes.md index ad8864b2..b37ee852 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -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 | diff --git a/contrib/completion/hx.bash b/contrib/completion/hx.bash index 87c34028..8a2d9777 100644 --- a/contrib/completion/hx.bash +++ b/contrib/completion/hx.bash @@ -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 diff --git a/contrib/completion/hx.elv b/contrib/completion/hx.elv new file mode 100644 index 00000000..d3d227bc --- /dev/null +++ b/contrib/completion/hx.elv @@ -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)" +} \ No newline at end of file diff --git a/contrib/completion/hx.fish b/contrib/completion/hx.fish index df2fb500..65f248d4 100644 --- a/contrib/completion/hx.fish +++ b/contrib/completion/hx.fish @@ -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" diff --git a/contrib/completion/hx.zsh b/contrib/completion/hx.zsh index f9d58d3c..e3375656 100644 --- a/contrib/completion/hx.zsh +++ b/contrib/completion/hx.zsh @@ -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 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 6da50fdd..e7b39b06 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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 diff --git a/flake.nix b/flake.nix index fdeed2aa..7b6f0685 100644 --- a/flake.nix +++ b/flake.nix @@ -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 = { diff --git a/grammars.nix b/grammars.nix index 2f50662e..066fa69d 100644 --- a/grammars.nix +++ b/grammars.nix @@ -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; diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index b7a1f332..0bae7b89 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -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" diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index 210ad639..48a68dc0 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -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, + pub code: Option, } diff --git a/helix-core/src/increment/date_time.rs b/helix-core/src/increment/date_time.rs index 91fa5963..1574bf4d 100644 --- a/helix-core/src/increment/date_time.rs +++ b/helix-core/src/increment/date_time.rs @@ -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..]; } diff --git a/helix-core/src/line_ending.rs b/helix-core/src/line_ending.rs index f0cf3b10..3e8a6cae 100644 --- a/helix-core/src/line_ending.rs +++ b/helix-core/src/line_ending.rs @@ -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); + } } } diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 83bab5e3..59bd736e 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -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 + 'a { + self.ranges.iter().map(move |range| range.slice(text)) + } + #[inline(always)] pub fn iter(&self) -> std::slice::Iter<'_, Range> { self.ranges.iter() diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 9011f835..99922d37 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -79,6 +79,9 @@ pub struct LanguageConfiguration { #[serde(default)] pub auto_format: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub formatter: Option, + #[serde(default)] pub diagnostic_severity: Severity, @@ -126,6 +129,15 @@ pub struct LanguageServerConfiguration { pub language_id: Option, } +#[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, +} + #[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 { 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>, Option>, bool) { +) -> (Option>, Option>, 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 { diff --git a/helix-core/src/textobject.rs b/helix-core/src/textobject.rs index ee06bf47..76c6d103 100644 --- a/helix-core/src/textobject.rs +++ b/helix-core/src/textobject.rs @@ -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(), diff --git a/helix-loader/src/config.rs b/helix-loader/src/config.rs index a8c84361..259b1318 100644 --- a/helix-loader/src/config.rs +++ b/helix-loader/src/config.rs @@ -19,7 +19,23 @@ pub fn user_lang_config() -> Result { .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) diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 7aa9bc83..98a93e56 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -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) -> 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> { Ok(grammars) } -fn run_parallel(grammars: Vec, job: F, action: &'static str) -> Result<()> +fn run_parallel(grammars: Vec, job: F) -> Vec> where - F: Fn(GrammarConfiguration) -> Result<()> + std::marker::Send + 'static + Copy, + F: Fn(GrammarConfiguration) -> Result + 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 { 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 { 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 { 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( diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index ff4414b2..015b39a5 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -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 = - once_cell::sync::Lazy::new(runtime_dir); +pub static RUNTIME_DIR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(runtime_dir); -pub fn runtime_dir() -> std::path::PathBuf { +static CONFIG_FILE: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + +pub fn initialize_config_file(specified_file: Option) { + 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 { +pub fn local_config_dirs() -> Vec { 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 { 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 { +pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec { 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 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() diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index f6cec6aa..9ae8f20e 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -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 { diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs index b04465b3..f3a4f755 100644 --- a/helix-lsp/src/lib.rs +++ b/helix-lsp/src/lib.rs @@ -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, - pub offset_encoding: OffsetEncoding, - } - - impl From for Transaction { - fn from(fmt: LspFormatting) -> Transaction { - generate_transaction_from_edits(&fmt.doc, fmt.edits, fmt.offset_encoding) - } - } } #[derive(Debug, PartialEq, Clone)] diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index b0e22896..f4a9642a 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -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" diff --git a/helix-term/build.rs b/helix-term/build.rs index 974f4b5e..74c35a3a 100644 --- a/helix-term/build.rs +++ b/helix-term/build.rs @@ -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/"); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 3ee5481f..21be7db0 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -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) { + pub fn handle_terminal_events(&mut self, event: Result) { 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); diff --git a/helix-term/src/args.rs b/helix-term/src/args.rs index c3019ea7..d16d7dfd 100644 --- a/helix-term/src/args.rs +++ b/helix-term/src/args.rs @@ -14,6 +14,7 @@ pub struct Args { pub build_grammars: bool, pub split: Option, pub verbosity: u64, + pub config_file: Option, 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) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 73f799ee..a036407c 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -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::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(cx: &mut Context, change_fn: F) where - F: Fn(Cow) -> 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::>() + .join("|"); + + let msg = format!("register '{}' set to '{}'", '/', ®ex); 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 + Send + 'static, + format: impl Future> + Send + 'static, ) -> anyhow::Result { - 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, ) -> 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 diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 9f6f4c15..1c780c1f 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -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| { diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 1785a50c..38507e4d 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -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)) } } }; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index cfd3dfec..4957e8ca 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -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, }, diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 5548e832..bda38c59 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -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), } +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(); diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index f64e121d..4a266e48 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -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| 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)?; diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 8aaac370..71f0f154 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -360,9 +360,7 @@ pub fn default() -> HashMap { "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, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index eb186d78..7f04f201 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -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 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 to continue with default config"); use std::io::Read; - // This waits for an enter press. let _ = std::io::stdin().read(&mut []); Config::default() }), diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index c1816db1..6a743632 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -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}; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index abd21b3c..07b27f59 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -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), } } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 6bb64139..ce51ecbc 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -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 Component for Menu { 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); diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 3ed3f8ae..046ca26d 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -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, 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 { static KEYS: Lazy> = 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(); diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index 9f522e35..1cd60be5 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -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 { diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9707c81e..169aeadd 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -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 Component for Picker { compositor.last_picker = compositor.pop(); }))); - match key_event.into() { + match key_event { shift!(Tab) | key!(Up) | ctrl!('p') => { self.move_by(1, Direction::Backward); } diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 77ab2462..af8e53c5 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -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 Component for Popup { _ => 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 Component for Popup { 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); diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 6e7df907..4cb38fb0 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -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; } } diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 85992c60..75e5dbd7 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -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(context: &mut RenderContext, write: F) -where - F: Fn(&mut RenderContext, String, Option