Merge remote-tracking branch 'origin/master' into goto_next_reference

pull/6465/head
Anthony Templeton 10 months ago
commit 0190c48d41

296
Cargo.lock generated

@ -145,12 +145,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
version = "1.0.84"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856"
dependencies = [
"libc",
]
checksum = "9b918671670962b48bc23753aef0c51d072dca6f52f01f800854ada6ddb7f7d3"
[[package]]
name = "cfg-if"
@ -171,9 +168,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.32"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
@ -183,9 +180,9 @@ dependencies = [
[[package]]
name = "clipboard-win"
version = "5.0.0"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81"
dependencies = [
"error-code",
]
@ -424,9 +421,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fern"
@ -448,6 +445,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.4.1",
"windows-sys 0.52.0",
]
[[package]]
name = "flate2"
version = "1.0.27"
@ -527,33 +536,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31887c304d9a935f3e5494fb5d6a0106c34e965168ec0db9b457424eedd0c741"
dependencies = [
"gix-actor",
"gix-attributes",
"gix-command",
"gix-commitgraph",
"gix-config",
"gix-date",
"gix-diff",
"gix-discover",
"gix-features",
"gix-filter",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-hashtable",
"gix-ignore",
"gix-index",
"gix-lock",
"gix-macros",
"gix-object",
"gix-odb",
"gix-pack",
"gix-path",
"gix-pathspec",
"gix-ref",
"gix-refspec",
"gix-revision",
"gix-revwalk",
"gix-sec",
"gix-submodule",
"gix-tempfile",
"gix-trace",
"gix-traverse",
"gix-url",
"gix-utils",
"gix-validate",
"gix-worktree",
"once_cell",
"parking_lot",
"smallvec",
@ -571,7 +588,33 @@ dependencies = [
"gix-date",
"itoa",
"thiserror",
"winnow 0.5.28",
"winnow",
]
[[package]]
name = "gix-attributes"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214ee3792e504ee1ce206b36dcafa4f328ca313d1e2ac0b41433d68ef4e14260"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-quote",
"gix-trace",
"kstring",
"smallvec",
"thiserror",
"unicode-bom",
]
[[package]]
name = "gix-bitmap"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b6cd0f246180034ddafac9b00a112f19178135b21eb031b3f79355891f7325"
dependencies = [
"thiserror",
]
[[package]]
@ -583,6 +626,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "gix-command"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1ffc7db3fb50b7dae6ecd937a3527cb725f444614df2ad8988d81806f13f09"
dependencies = [
"bstr",
"gix-path",
"gix-trace",
"shell-words",
]
[[package]]
name = "gix-commitgraph"
version = "0.24.0"
@ -615,7 +670,7 @@ dependencies = [
"smallvec",
"thiserror",
"unicode-bom",
"winnow 0.5.28",
"winnow",
]
[[package]]
@ -690,6 +745,27 @@ dependencies = [
"walkdir",
]
[[package]]
name = "gix-filter"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9240862840fb740d209422937195e129e4ed3da49af212383260134bea8f6c1a"
dependencies = [
"bstr",
"encoding_rs",
"gix-attributes",
"gix-command",
"gix-hash",
"gix-object",
"gix-packetline-blocking",
"gix-path",
"gix-quote",
"gix-trace",
"gix-utils",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-fs"
version = "0.10.0"
@ -733,6 +809,44 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "gix-ignore"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7069aaca4a05784c4cb44e392f0eaf627c6e57e05d3100c0e2386a37a682f0"
dependencies = [
"bstr",
"gix-glob",
"gix-path",
"gix-trace",
"unicode-bom",
]
[[package]]
name = "gix-index"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7152181ba8f0a3addc5075dd612cea31fc3e252b29c8be8c45f4892bf87426"
dependencies = [
"bitflags 2.4.2",
"bstr",
"btoi",
"filetime",
"gix-bitmap",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-traverse",
"itoa",
"libc",
"memmap2",
"rustix",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-lock"
version = "13.0.0"
@ -771,7 +885,7 @@ dependencies = [
"itoa",
"smallvec",
"thiserror",
"winnow 0.5.28",
"winnow",
]
[[package]]
@ -814,6 +928,18 @@ dependencies = [
"thiserror",
]
[[package]]
name = "gix-packetline-blocking"
version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8ef6dd3ea50e26f3bf572e90c034d033c804d340cd1eb386392f184a9ba2f7"
dependencies = [
"bstr",
"faster-hex",
"gix-trace",
"thiserror",
]
[[package]]
name = "gix-path"
version = "0.10.4"
@ -827,6 +953,21 @@ dependencies = [
"thiserror",
]
[[package]]
name = "gix-pathspec"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cbd49750edb26b0a691e5246fc635fa554d344da825cd20fa9ee0da9c1b761f"
dependencies = [
"bitflags 2.4.2",
"bstr",
"gix-attributes",
"gix-config-value",
"gix-glob",
"gix-path",
"thiserror",
]
[[package]]
name = "gix-quote"
version = "0.4.10"
@ -857,7 +998,7 @@ dependencies = [
"gix-validate",
"memmap2",
"thiserror",
"winnow 0.5.28",
"winnow",
]
[[package]]
@ -917,6 +1058,21 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "gix-submodule"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73182f6c1f5ed1ed94ba16581ac62593d5e29cd1c028b2af618f836283b8f8d4"
dependencies = [
"bstr",
"gix-config",
"gix-path",
"gix-pathspec",
"gix-refspec",
"gix-url",
"thiserror",
]
[[package]]
name = "gix-tempfile"
version = "13.0.0"
@ -986,6 +1142,24 @@ dependencies = [
"thiserror",
]
[[package]]
name = "gix-worktree"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca36bb3dc54038c66507dc75c4d8edbee2d6d5cc45227b4eb508ad13dd60a006"
dependencies = [
"bstr",
"gix-attributes",
"gix-features",
"gix-fs",
"gix-glob",
"gix-hash",
"gix-ignore",
"gix-index",
"gix-object",
"gix-path",
]
[[package]]
name = "globset"
version = "0.4.14"
@ -1063,6 +1237,7 @@ dependencies = [
"dunce",
"encoding_rs",
"etcetera",
"globset",
"hashbrown 0.14.3",
"helix-loader",
"helix-stdx",
@ -1141,6 +1316,7 @@ name = "helix-lsp"
version = "23.10.0"
dependencies = [
"anyhow",
"arc-swap",
"futures-executor",
"futures-util",
"globset",
@ -1209,6 +1385,7 @@ dependencies = [
"signal-hook-tokio",
"smallvec",
"tempfile",
"termini",
"tokio",
"tokio-stream",
"toml",
@ -1408,11 +1585,20 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kstring"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3066350882a1cd6d950d055997f379ac37fd39f81cd4d8ed186032eb3c5747"
dependencies = [
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.152"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "libloading"
@ -1653,11 +1839,11 @@ checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
[[package]]
name = "pulldown-cmark"
version = "0.9.3"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998"
checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.4.2",
"memchr",
"unicase",
]
@ -1785,9 +1971,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.30"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.2",
"errno",
@ -1825,18 +2011,18 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.195"
version = "1.0.196"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
@ -1845,9 +2031,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.111"
version = "1.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
dependencies = [
"itoa",
"ryu",
@ -1867,9 +2053,9 @@ dependencies = [
[[package]]
name = "serde_spanned"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
@ -1880,6 +2066,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "shell-words"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
[[package]]
name = "signal-hook"
version = "0.3.17"
@ -2009,13 +2201,12 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.9.0"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.4.1",
"rustix",
"windows-sys 0.52.0",
]
@ -2051,18 +2242,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.56"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "1.0.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
@ -2124,9 +2315,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.35.1"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
@ -2165,9 +2356,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.7.6"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290"
dependencies = [
"serde",
"serde_spanned",
@ -2177,24 +2368,24 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.6.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.12"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.4.6",
"winnow",
]
[[package]]
@ -2256,9 +2447,9 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
@ -2605,15 +2796,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.5.28"

@ -340,7 +340,12 @@ Currently unused
#### `[editor.gutters.diff]` Section
Currently unused
The `diff` gutter option displays colored bars indicating whether a `git` diff represents that a line was added, removed or changed.
These colors are controlled by the theme attributes `diff.plus`, `diff.minus` and `diff.delta`.
Other diff providers will eventually be supported by a future plugin system.
There are currently no options for this section.
#### `[editor.gutters.spacer]` Section

@ -14,6 +14,7 @@
| cabal | | | | `haskell-language-server-wrapper` |
| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
| capnp | ✓ | | ✓ | |
| cel | ✓ | | | |
| clojure | ✓ | | | `clojure-lsp` |
| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
| comment | ✓ | | | |
@ -69,6 +70,7 @@
| hcl | ✓ | | ✓ | `terraform-ls` |
| heex | ✓ | ✓ | | `elixir-ls` |
| hocon | ✓ | | ✓ | |
| hoon | ✓ | | | |
| hosts | ✓ | | | |
| html | ✓ | | | `vscode-html-language-server` |
| hurl | ✓ | | ✓ | |
@ -123,6 +125,7 @@
| pem | ✓ | | | |
| perl | ✓ | ✓ | ✓ | `perlnavigator` |
| php | ✓ | ✓ | ✓ | `intelephense` |
| pkl | ✓ | | ✓ | |
| po | ✓ | ✓ | | |
| pod | ✓ | | | |
| ponylang | ✓ | ✓ | ✓ | |
@ -148,11 +151,12 @@
| scala | ✓ | ✓ | ✓ | `metals` |
| scheme | ✓ | | ✓ | |
| scss | ✓ | | | `vscode-css-language-server` |
| slint | ✓ | | ✓ | `slint-lsp` |
| slint | ✓ | | ✓ | `slint-lsp` |
| smali | ✓ | | ✓ | |
| smithy | ✓ | | | `cs` |
| sml | ✓ | | | |
| solidity | ✓ | | | `solc` |
| spicedb | ✓ | | | |
| sql | ✓ | | | |
| sshclientconfig | ✓ | | | |
| starlark | ✓ | ✓ | | |
@ -162,6 +166,7 @@
| swift | ✓ | | | `sourcekit-lsp` |
| t32 | ✓ | | | |
| tablegen | ✓ | ✓ | ✓ | |
| tact | ✓ | ✓ | ✓ | |
| task | ✓ | | | |
| templ | ✓ | | | `templ` |
| tfvars | ✓ | | ✓ | `terraform-ls` |
@ -173,7 +178,7 @@
| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
| typst | ✓ | | | `typst-lsp` |
| ungrammar | ✓ | | | |
| unison | ✓ | | | |
| unison | ✓ | | | |
| uxntal | ✓ | | | |
| v | ✓ | ✓ | ✓ | `v-analyzer` |
| vala | ✓ | | | `vala-language-server` |

@ -76,6 +76,15 @@ Releases are available in the `extra` repository:
```sh
sudo pacman -S helix
```
> 💡 When installed from the `extra` repository, run Helix with `helix` instead of `hx`.
>
> For example:
> ```sh
> helix --health
> ```
> to check health
Additionally, a [helix-git](https://aur.archlinux.org/packages/helix-git/) package is available
in the AUR, which builds the master branch.

@ -78,24 +78,26 @@ from the above section. `file-types` is a list of strings or tables, for
example:
```toml
file-types = ["Makefile", "toml", { suffix = ".git/config" }]
file-types = ["toml", { glob = "Makefile" }, { glob = ".git/config" }, { glob = ".github/workflows/*.yaml" } ]
```
When determining a language configuration to use, Helix searches the file-types
with the following priorities:
1. Exact match: if the filename of a file is an exact match of a string in a
`file-types` list, that language wins. In the example above, `"Makefile"`
will match against `Makefile` files.
2. Extension: if there are no exact matches, any `file-types` string that
matches the file extension of a given file wins. In the example above, the
`"toml"` matches files like `Cargo.toml` or `languages.toml`.
3. Suffix: if there are still no matches, any values in `suffix` tables
are checked against the full path of the given file. In the example above,
the `{ suffix = ".git/config" }` would match against any `config` files
in `.git` directories. Note: `/` is used as the directory separator but is
replaced at runtime with the appropriate path separator for the operating
system, so this rule would match against `.git\config` files on Windows.
1. Glob: values in `glob` tables are checked against the full path of the given
file. Globs are standard Unix-style path globs (e.g. the kind you use in Shell)
and can be used to match paths for a specific prefix, suffix, directory, etc.
In the above example, the `{ glob = "Makefile" }` config would match files
with the name `Makefile`, the `{ glob = ".git/config" }` config would match
`config` files in `.git` directories, and the `{ glob = ".github/workflows/*.yaml" }`
config would match any `yaml` files in `.github/workflow` directories. Note
that globs should always use the Unix path separator `/` even on Windows systems;
the matcher will automatically take the machine-specific separators into account.
If the glob isn't an absolute path or doesn't already start with a glob prefix,
`*/` will automatically be added to ensure it matches for any subdirectory.
2. Extension: if there are no glob matches, any `file-types` string that matches
the file extension of a given file wins. In the example above, the `"toml"`
config matches files like `Cargo.toml` or `languages.toml`.
## Language Server configuration
@ -127,6 +129,7 @@ These are the available options for a language server.
| `config` | LSP initialization options |
| `timeout` | The maximum time a request to the language server may take, in seconds. Defaults to `20` |
| `environment` | Any environment variables that will be used when starting the language server `{ "KEY1" = "Value1", "KEY2" = "Value2" }` |
| `required-root-patterns` | A list of `glob` patterns to look for in the working directory. The language server is started if at least one of them is found. |
A `format` sub-table within `config` can be used to pass extra formatting options to
[Document Formatting Requests](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting).

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

@ -22,7 +22,7 @@ helix-loader = { path = "../helix-loader" }
ropey = { version = "1.6.1", default-features = false, features = ["simd"] }
smallvec = "1.13"
smartstring = "1.0.1"
unicode-segmentation = "1.10"
unicode-segmentation = "1.11"
unicode-width = "0.1"
unicode-general-category = "0.6"
# slab = "0.4.2"
@ -39,7 +39,7 @@ dunce = "1.0"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.7"
toml = "0.8"
imara-diff = "0.1.0"
@ -52,6 +52,7 @@ textwrap = "0.16.0"
nucleo.workspace = true
parking_lot = "0.12"
globset = "0.4.14"
[dev-dependencies]
quickcheck = { version = "1", default-features = false }

@ -1,10 +1,45 @@
/// Syntax configuration loader based on built-in languages.toml.
pub fn default_syntax_loader() -> crate::syntax::Configuration {
use crate::syntax::{Configuration, Loader, LoaderError};
/// Language configuration based on built-in languages.toml.
pub fn default_lang_config() -> Configuration {
helix_loader::config::default_lang_config()
.try_into()
.expect("Could not serialize built-in languages.toml")
.expect("Could not deserialize built-in languages.toml")
}
/// Syntax configuration loader based on user configured languages.toml.
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
/// Language configuration loader based on built-in languages.toml.
pub fn default_lang_loader() -> Loader {
Loader::new(default_lang_config()).expect("Could not compile loader for default config")
}
#[derive(Debug)]
pub enum LanguageLoaderError {
DeserializeError(toml::de::Error),
LoaderError(LoaderError),
}
impl std::fmt::Display for LanguageLoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DeserializeError(err) => write!(f, "Failed to parse language config: {err}"),
Self::LoaderError(err) => write!(f, "Failed to compile language config: {err}"),
}
}
}
impl std::error::Error for LanguageLoaderError {}
/// Language configuration based on user configured languages.toml.
pub fn user_lang_config() -> Result<Configuration, toml::de::Error> {
helix_loader::config::user_lang_config()?.try_into()
}
/// Language configuration loader based on user configured languages.toml.
pub fn user_lang_loader() -> Result<Loader, LanguageLoaderError> {
let config: Configuration = helix_loader::config::user_lang_config()
.map_err(LanguageLoaderError::DeserializeError)?
.try_into()
.map_err(LanguageLoaderError::DeserializeError)?;
Loader::new(config).map_err(LanguageLoaderError::LoaderError)
}

@ -57,10 +57,10 @@ fn find_pair(
pos_: usize,
traverse_parents: bool,
) -> Option<usize> {
let tree = syntax.tree();
let pos = doc.char_to_byte(pos_);
let mut node = tree.root_node().descendant_for_byte_range(pos, pos + 1)?;
let root = syntax.tree_for_byte_range(pos, pos + 1).root_node();
let mut node = root.descendant_for_byte_range(pos, pos + 1)?;
loop {
if node.is_named() {
@ -118,9 +118,7 @@ fn find_pair(
};
node = parent;
}
let node = tree
.root_node()
.named_descendant_for_byte_range(pos, pos + 1)?;
let node = root.named_descendant_for_byte_range(pos, pos + 1)?;
if node.child_count() != 0 {
return None;
}

@ -573,16 +573,11 @@ pub fn move_parent_node_end(
dir: Direction,
movement: Movement,
) -> Selection {
let tree = syntax.tree();
selection.transform(|range| {
let start_from = text.char_to_byte(range.from());
let start_to = text.char_to_byte(range.to());
let mut node = match tree
.root_node()
.named_descendant_for_byte_range(start_from, start_to)
{
let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
Some(node) => node,
None => {
log::debug!(

@ -10,6 +10,7 @@ use crate::{
use ahash::RandomState;
use arc_swap::{ArcSwap, Guard};
use bitflags::bitflags;
use globset::GlobSet;
use hashbrown::raw::RawTable;
use slotmap::{DefaultKey as LayerId, HopSlotMap};
@ -82,12 +83,6 @@ pub struct Configuration {
pub language_server: HashMap<String, LanguageServerConfiguration>,
}
impl Default for Configuration {
fn default() -> Self {
crate::config::default_syntax_loader()
}
}
// largely based on tree-sitter/cli/src/loader.rs
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
@ -164,9 +159,11 @@ pub enum FileType {
/// The extension of the file, either the `Path::extension` or the full
/// filename if the file does not have an extension.
Extension(String),
/// The suffix of a file. This is compared to a given file's absolute
/// path, so it can be used to detect files based on their directories.
Suffix(String),
/// A Unix-style path glob. This is compared to the file's absolute path, so
/// it can be used to detect files based on their directories. If the glob
/// is not an absolute path and does not already start with a glob pattern,
/// a glob pattern will be prepended to it.
Glob(globset::Glob),
}
impl Serialize for FileType {
@ -178,9 +175,9 @@ impl Serialize for FileType {
match self {
FileType::Extension(extension) => serializer.serialize_str(extension),
FileType::Suffix(suffix) => {
FileType::Glob(glob) => {
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("suffix", &suffix.replace(std::path::MAIN_SEPARATOR, "/"))?;
map.serialize_entry("glob", glob.glob())?;
map.end()
}
}
@ -213,9 +210,20 @@ impl<'de> Deserialize<'de> for FileType {
M: serde::de::MapAccess<'de>,
{
match map.next_entry::<String, String>()? {
Some((key, suffix)) if key == "suffix" => Ok(FileType::Suffix({
suffix.replace('/', std::path::MAIN_SEPARATOR_STR)
})),
Some((key, mut glob)) if key == "glob" => {
// If the glob isn't an absolute path or already starts
// with a glob pattern, add a leading glob so we
// properly match relative paths.
if !glob.starts_with('/') && !glob.starts_with("*/") {
glob.insert_str(0, "*/");
}
globset::Glob::new(glob.as_str())
.map(FileType::Glob)
.map_err(|err| {
serde::de::Error::custom(format!("invalid `glob` pattern: {}", err))
})
}
Some((key, _value)) => Err(serde::de::Error::custom(format!(
"unknown key in `file-types` list: {}",
key
@ -358,6 +366,22 @@ where
serializer.end()
}
fn deserialize_required_root_patterns<'de, D>(deserializer: D) -> Result<Option<GlobSet>, D::Error>
where
D: serde::Deserializer<'de>,
{
let patterns = Vec::<String>::deserialize(deserializer)?;
if patterns.is_empty() {
return Ok(None);
}
let mut builder = globset::GlobSetBuilder::new();
for pattern in patterns {
let glob = globset::Glob::new(&pattern).map_err(serde::de::Error::custom)?;
builder.add(glob);
}
builder.build().map(Some).map_err(serde::de::Error::custom)
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LanguageServerConfiguration {
@ -371,6 +395,12 @@ pub struct LanguageServerConfiguration {
pub config: Option<serde_json::Value>,
#[serde(default = "default_timeout")]
pub timeout: u64,
#[serde(
default,
skip_serializing,
deserialize_with = "deserialize_required_root_patterns"
)]
pub required_root_patterns: Option<GlobSet>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -752,6 +782,47 @@ pub struct SoftWrap {
pub wrap_at_text_width: Option<bool>,
}
#[derive(Debug)]
struct FileTypeGlob {
glob: globset::Glob,
language_id: usize,
}
impl FileTypeGlob {
fn new(glob: globset::Glob, language_id: usize) -> Self {
Self { glob, language_id }
}
}
#[derive(Debug)]
struct FileTypeGlobMatcher {
matcher: globset::GlobSet,
file_types: Vec<FileTypeGlob>,
}
impl FileTypeGlobMatcher {
fn new(file_types: Vec<FileTypeGlob>) -> Result<Self, globset::Error> {
let mut builder = globset::GlobSetBuilder::new();
for file_type in &file_types {
builder.add(file_type.glob.clone());
}
Ok(Self {
matcher: builder.build()?,
file_types,
})
}
fn language_id_for_path(&self, path: &Path) -> Option<&usize> {
self.matcher
.matches(path)
.iter()
.filter_map(|idx| self.file_types.get(*idx))
.max_by_key(|file_type| file_type.glob.glob().len())
.map(|file_type| &file_type.language_id)
}
}
// Expose loader as Lazy<> global since it's always static?
#[derive(Debug)]
@ -759,7 +830,7 @@ pub struct Loader {
// highlight_names ?
language_configs: Vec<Arc<LanguageConfiguration>>,
language_config_ids_by_extension: HashMap<String, usize>, // Vec<usize>
language_config_ids_by_suffix: HashMap<String, usize>,
language_config_ids_glob_matcher: FileTypeGlobMatcher,
language_config_ids_by_shebang: HashMap<String, usize>,
language_server_configs: HashMap<String, LanguageServerConfiguration>,
@ -767,66 +838,57 @@ pub struct Loader {
scopes: ArcSwap<Vec<String>>,
}
pub type LoaderError = globset::Error;
impl Loader {
pub fn new(config: Configuration) -> Self {
let mut loader = Self {
language_configs: Vec::new(),
language_server_configs: config.language_server,
language_config_ids_by_extension: HashMap::new(),
language_config_ids_by_suffix: HashMap::new(),
language_config_ids_by_shebang: HashMap::new(),
scopes: ArcSwap::from_pointee(Vec::new()),
};
pub fn new(config: Configuration) -> Result<Self, LoaderError> {
let mut language_configs = Vec::new();
let mut language_config_ids_by_extension = HashMap::new();
let mut language_config_ids_by_shebang = HashMap::new();
let mut file_type_globs = Vec::new();
for config in config.language {
// get the next id
let language_id = loader.language_configs.len();
let language_id = language_configs.len();
for file_type in &config.file_types {
// entry().or_insert(Vec::new).push(language_id);
match file_type {
FileType::Extension(extension) => loader
.language_config_ids_by_extension
.insert(extension.clone(), language_id),
FileType::Suffix(suffix) => loader
.language_config_ids_by_suffix
.insert(suffix.clone(), language_id),
FileType::Extension(extension) => {
language_config_ids_by_extension.insert(extension.clone(), language_id);
}
FileType::Glob(glob) => {
file_type_globs.push(FileTypeGlob::new(glob.to_owned(), language_id));
}
};
}
for shebang in &config.shebangs {
loader
.language_config_ids_by_shebang
.insert(shebang.clone(), language_id);
language_config_ids_by_shebang.insert(shebang.clone(), language_id);
}
loader.language_configs.push(Arc::new(config));
language_configs.push(Arc::new(config));
}
loader
Ok(Self {
language_configs,
language_config_ids_by_extension,
language_config_ids_glob_matcher: FileTypeGlobMatcher::new(file_type_globs)?,
language_config_ids_by_shebang,
language_server_configs: config.language_server,
scopes: ArcSwap::from_pointee(Vec::new()),
})
}
pub fn language_config_for_file_name(&self, path: &Path) -> Option<Arc<LanguageConfiguration>> {
// Find all the language configurations that match this file name
// or a suffix of the file name.
let configuration_id = path
.file_name()
.and_then(|n| n.to_str())
.and_then(|file_name| self.language_config_ids_by_extension.get(file_name))
let configuration_id = self
.language_config_ids_glob_matcher
.language_id_for_path(path)
.or_else(|| {
path.extension()
.and_then(|extension| extension.to_str())
.and_then(|extension| self.language_config_ids_by_extension.get(extension))
})
.or_else(|| {
self.language_config_ids_by_suffix
.iter()
.find_map(|(file_type, id)| {
if path.to_str()?.ends_with(file_type) {
Some(id)
} else {
None
}
})
});
configuration_id.and_then(|&id| self.language_configs.get(id).cloned())
@ -938,7 +1000,7 @@ thread_local! {
pub struct Syntax {
layers: HopSlotMap<LayerId, LanguageLayer>,
root: LayerId,
loader: Arc<Loader>,
loader: Arc<ArcSwap<Loader>>,
}
fn byte_range_to_str(range: std::ops::Range<usize>, source: RopeSlice) -> Cow<str> {
@ -949,7 +1011,7 @@ impl Syntax {
pub fn new(
source: RopeSlice,
config: Arc<HighlightConfiguration>,
loader: Arc<Loader>,
loader: Arc<ArcSwap<Loader>>,
) -> Option<Self> {
let root_layer = LanguageLayer {
tree: None,
@ -993,9 +1055,10 @@ impl Syntax {
let mut queue = VecDeque::new();
queue.push_back(self.root);
let scopes = self.loader.scopes.load();
let loader = self.loader.load();
let scopes = loader.scopes.load();
let injection_callback = |language: &InjectionLanguageMarker| {
self.loader
loader
.language_configuration_for_injection_string(language)
.and_then(|language_config| language_config.highlight_config(&scopes))
};
@ -1338,7 +1401,7 @@ impl Syntax {
result
}
pub fn descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> {
pub fn tree_for_byte_range(&self, start: usize, end: usize) -> &Tree {
let mut container_id = self.root;
for (layer_id, layer) in self.layers.iter() {
@ -1349,8 +1412,17 @@ impl Syntax {
}
}
self.layers[container_id]
.tree()
self.layers[container_id].tree()
}
pub fn named_descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> {
self.tree_for_byte_range(start, end)
.root_node()
.named_descendant_for_byte_range(start, end)
}
pub fn descendant_for_byte_range(&self, start: usize, end: usize) -> Option<Node<'_>> {
self.tree_for_byte_range(start, end)
.root_node()
.descendant_for_byte_range(start, end)
}
@ -2583,7 +2655,8 @@ mod test {
let loader = Loader::new(Configuration {
language: vec![],
language_server: HashMap::new(),
});
})
.unwrap();
let language = get_language("rust").unwrap();
let query = Query::new(language, query_str).unwrap();
@ -2591,7 +2664,12 @@ mod test {
let mut cursor = QueryCursor::new();
let config = HighlightConfiguration::new(language, "", "", "").unwrap();
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let root = syntax.tree().root_node();
let mut test = |capture, range| {
@ -2645,7 +2723,8 @@ mod test {
let loader = Loader::new(Configuration {
language: vec![],
language_server: HashMap::new(),
});
})
.unwrap();
let language = get_language("rust").unwrap();
let config = HighlightConfiguration::new(
@ -2665,7 +2744,12 @@ mod test {
fn main() {}
",
);
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let tree = syntax.tree();
let root = tree.root_node();
assert_eq!(root.kind(), "source_file");
@ -2751,11 +2835,17 @@ mod test {
let loader = Loader::new(Configuration {
language: vec![],
language_server: HashMap::new(),
});
})
.unwrap();
let language = get_language(language_name).unwrap();
let config = HighlightConfiguration::new(language, "", "", "").unwrap();
let syntax = Syntax::new(source.slice(..), Arc::new(config), Arc::new(loader)).unwrap();
let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let root = syntax
.tree()

@ -1,10 +1,11 @@
use arc_swap::ArcSwap;
use helix_core::{
indent::{indent_level_for_line, treesitter_indent_for_pos, IndentStyle},
syntax::{Configuration, Loader},
Syntax,
};
use ropey::Rope;
use std::{ops::Range, path::PathBuf, process::Command};
use std::{ops::Range, path::PathBuf, process::Command, sync::Arc};
#[test]
fn test_treesitter_indent_rust() {
@ -186,7 +187,7 @@ fn test_treesitter_indent(
lang_scope: &str,
ignored_lines: Vec<std::ops::Range<usize>>,
) {
let loader = Loader::new(indent_tests_config());
let loader = Loader::new(indent_tests_config()).unwrap();
// set runtime path so we can find the queries
let mut runtime = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
@ -197,7 +198,12 @@ fn test_treesitter_indent(
let indent_style = IndentStyle::from_str(&language_config.indent.as_ref().unwrap().unit);
let highlight_config = language_config.highlight_config(&[]).unwrap();
let text = doc.slice(..);
let syntax = Syntax::new(text, highlight_config, std::sync::Arc::new(loader)).unwrap();
let syntax = Syntax::new(
text,
highlight_config,
Arc::new(ArcSwap::from_pointee(loader)),
)
.unwrap();
let indent_query = language_config.indent_query().unwrap();
for i in 0..doc.len_lines() {

@ -19,7 +19,7 @@ helix-stdx = { path = "../helix-stdx" }
anyhow = "1"
serde = { version = "1.0", features = ["derive"] }
toml = "0.7"
toml = "0.8"
etcetera = "0.8"
tree-sitter.workspace = true
once_cell = "1.19"
@ -30,7 +30,7 @@ log = "0.4"
# cloning/compiling tree-sitter grammars
cc = { version = "1" }
threadpool = { version = "1.0" }
tempfile = "3.9.0"
tempfile = "3.10.0"
dunce = "1.0.4"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

@ -27,6 +27,7 @@ lsp-types = { version = "0.95" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
tokio = { version = "1.35", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio = { version = "1.36", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot", "sync"] }
tokio-stream = "0.1.14"
parking_lot = "0.12.1"
arc-swap = "1"

@ -1,4 +1,5 @@
use crate::{
file_operations::FileOperationsInterest,
find_lsp_workspace, jsonrpc,
transport::{Payload, Transport},
Call, Error, OffsetEncoding, Result,
@ -9,20 +10,20 @@ use helix_loader::{self, VERSION_AND_GIT_HASH};
use helix_stdx::path;
use lsp::{
notification::DidChangeWorkspaceFolders, CodeActionCapabilityResolveSupport,
DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, SignatureHelp, WorkspaceFolder,
WorkspaceFoldersChangeEvent,
DidChangeWorkspaceFoldersParams, OneOf, PositionEncodingKind, SignatureHelp, Url,
WorkspaceFolder, WorkspaceFoldersChangeEvent,
};
use lsp_types as lsp;
use parking_lot::Mutex;
use serde::Deserialize;
use serde_json::Value;
use std::future::Future;
use std::process::Stdio;
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
use std::{collections::HashMap, path::PathBuf};
use std::{future::Future, sync::OnceLock};
use std::{path::Path, process::Stdio};
use tokio::{
io::{BufReader, BufWriter},
process::{Child, Command},
@ -51,6 +52,7 @@ pub struct Client {
server_tx: UnboundedSender<Payload>,
request_counter: AtomicU64,
pub(crate) capabilities: OnceCell<lsp::ServerCapabilities>,
pub(crate) file_operation_interest: OnceLock<FileOperationsInterest>,
config: Option<Value>,
root_path: std::path::PathBuf,
root_uri: Option<lsp::Url>,
@ -175,12 +177,11 @@ impl Client {
args: &[String],
config: Option<Value>,
server_environment: HashMap<String, String>,
root_markers: &[String],
manual_roots: &[PathBuf],
root_path: PathBuf,
root_uri: Option<lsp::Url>,
id: usize,
name: String,
req_timeout: u64,
doc_path: Option<&std::path::PathBuf>,
) -> Result<(Self, UnboundedReceiver<(usize, Call)>, Arc<Notify>)> {
// Resolve path to the binary
let cmd = helix_stdx::env::which(cmd)?;
@ -204,22 +205,6 @@ impl Client {
let (server_rx, server_tx, initialize_notify) =
Transport::start(reader, writer, stderr, id, name.clone());
let (workspace, workspace_is_cwd) = find_workspace();
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
root_markers,
manual_roots,
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
let workspace_folders = root_uri
.clone()
@ -233,6 +218,7 @@ impl Client {
server_tx,
request_counter: AtomicU64::new(0),
capabilities: OnceCell::new(),
file_operation_interest: OnceLock::new(),
config,
req_timeout,
root_path,
@ -278,6 +264,11 @@ impl Client {
.expect("language server not yet initialized!")
}
pub(crate) fn file_operations_intests(&self) -> &FileOperationsInterest {
self.file_operation_interest
.get_or_init(|| FileOperationsInterest::new(self.capabilities()))
}
/// Client has to be initialized otherwise this function panics
#[inline]
pub fn supports_feature(&self, feature: LanguageServerFeature) -> bool {
@ -717,27 +708,27 @@ impl Client {
})
}
pub fn prepare_file_rename(
pub fn will_rename(
&self,
old_uri: &lsp::Url,
new_uri: &lsp::Url,
old_path: &Path,
new_path: &Path,
is_dir: bool,
) -> Option<impl Future<Output = Result<lsp::WorkspaceEdit>>> {
let capabilities = self.capabilities.get().unwrap();
// Return early if the server does not support willRename feature
match &capabilities.workspace {
Some(workspace) => match &workspace.file_operations {
Some(op) => {
op.will_rename.as_ref()?;
}
_ => return None,
},
_ => return None,
let capabilities = self.file_operations_intests();
if !capabilities.will_rename.has_interest(old_path, is_dir) {
return None;
}
let url_from_path = |path| {
let url = if is_dir {
Url::from_directory_path(path)
} else {
Url::from_file_path(path)
};
Some(url.ok()?.to_string())
};
let files = vec![lsp::FileRename {
old_uri: old_uri.to_string(),
new_uri: new_uri.to_string(),
old_uri: url_from_path(old_path)?,
new_uri: url_from_path(new_path)?,
}];
let request = self.call_with_timeout::<lsp::request::WillRenameFiles>(
lsp::RenameFilesParams { files },
@ -751,27 +742,28 @@ impl Client {
})
}
pub fn did_file_rename(
pub fn did_rename(
&self,
old_uri: &lsp::Url,
new_uri: &lsp::Url,
old_path: &Path,
new_path: &Path,
is_dir: bool,
) -> Option<impl Future<Output = std::result::Result<(), Error>>> {
let capabilities = self.capabilities.get().unwrap();
// Return early if the server does not support DidRename feature
match &capabilities.workspace {
Some(workspace) => match &workspace.file_operations {
Some(op) => {
op.did_rename.as_ref()?;
}
_ => return None,
},
_ => return None,
let capabilities = self.file_operations_intests();
if !capabilities.did_rename.has_interest(new_path, is_dir) {
return None;
}
let url_from_path = |path| {
let url = if is_dir {
Url::from_directory_path(path)
} else {
Url::from_file_path(path)
};
Some(url.ok()?.to_string())
};
let files = vec![lsp::FileRename {
old_uri: old_uri.to_string(),
new_uri: new_uri.to_string(),
old_uri: url_from_path(old_path)?,
new_uri: url_from_path(new_path)?,
}];
Some(self.notify::<lsp::notification::DidRenameFiles>(lsp::RenameFilesParams { files }))
}

@ -0,0 +1,105 @@
use std::path::Path;
use globset::{GlobBuilder, GlobSet};
use crate::lsp;
#[derive(Default, Debug)]
pub(crate) struct FileOperationFilter {
dir_globs: GlobSet,
file_globs: GlobSet,
}
impl FileOperationFilter {
fn new(capability: Option<&lsp::FileOperationRegistrationOptions>) -> FileOperationFilter {
let Some(cap) = capability else {
return FileOperationFilter::default();
};
let mut dir_globs = GlobSet::builder();
let mut file_globs = GlobSet::builder();
for filter in &cap.filters {
// TODO: support other url schemes
let is_non_file_schema = filter
.scheme
.as_ref()
.is_some_and(|schema| schema != "file");
if is_non_file_schema {
continue;
}
let ignore_case = filter
.pattern
.options
.as_ref()
.and_then(|opts| opts.ignore_case)
.unwrap_or(false);
let mut glob_builder = GlobBuilder::new(&filter.pattern.glob);
glob_builder.case_insensitive(!ignore_case);
let glob = match glob_builder.build() {
Ok(glob) => glob,
Err(err) => {
log::error!("invalid glob send by LS: {err}");
continue;
}
};
match filter.pattern.matches {
Some(lsp::FileOperationPatternKind::File) => {
file_globs.add(glob);
}
Some(lsp::FileOperationPatternKind::Folder) => {
dir_globs.add(glob);
}
None => {
file_globs.add(glob.clone());
dir_globs.add(glob);
}
};
}
let file_globs = file_globs.build().unwrap_or_else(|err| {
log::error!("invalid globs send by LS: {err}");
GlobSet::empty()
});
let dir_globs = dir_globs.build().unwrap_or_else(|err| {
log::error!("invalid globs send by LS: {err}");
GlobSet::empty()
});
FileOperationFilter {
dir_globs,
file_globs,
}
}
pub(crate) fn has_interest(&self, path: &Path, is_dir: bool) -> bool {
if is_dir {
self.dir_globs.is_match(path)
} else {
self.file_globs.is_match(path)
}
}
}
#[derive(Default, Debug)]
pub(crate) struct FileOperationsInterest {
// TODO: support other notifications
// did_create: FileOperationFilter,
// will_create: FileOperationFilter,
pub did_rename: FileOperationFilter,
pub will_rename: FileOperationFilter,
// did_delete: FileOperationFilter,
// will_delete: FileOperationFilter,
}
impl FileOperationsInterest {
pub fn new(capabilities: &lsp::ServerCapabilities) -> FileOperationsInterest {
let capabilities = capabilities
.workspace
.as_ref()
.and_then(|capabilities| capabilities.file_operations.as_ref());
let Some(capabilities) = capabilities else {
return FileOperationsInterest::default();
};
FileOperationsInterest {
did_rename: FileOperationFilter::new(capabilities.did_rename.as_ref()),
will_rename: FileOperationFilter::new(capabilities.will_rename.as_ref()),
}
}
}

@ -1,9 +1,11 @@
mod client;
pub mod file_event;
mod file_operations;
pub mod jsonrpc;
pub mod snippet;
mod transport;
use arc_swap::ArcSwap;
pub use client::Client;
pub use futures_executor::block_on;
pub use jsonrpc::Call;
@ -639,14 +641,14 @@ impl Notification {
#[derive(Debug)]
pub struct Registry {
inner: HashMap<LanguageServerName, Vec<Arc<Client>>>,
syn_loader: Arc<helix_core::syntax::Loader>,
syn_loader: Arc<ArcSwap<helix_core::syntax::Loader>>,
counter: usize,
pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
pub file_event_handler: file_event::Handler,
}
impl Registry {
pub fn new(syn_loader: Arc<helix_core::syntax::Loader>) -> Self {
pub fn new(syn_loader: Arc<ArcSwap<helix_core::syntax::Loader>>) -> Self {
Self {
inner: HashMap::new(),
syn_loader,
@ -679,15 +681,15 @@ impl Registry {
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
) -> Result<Arc<Client>> {
let config = self
.syn_loader
) -> Result<Option<Arc<Client>>> {
let syn_loader = self.syn_loader.load();
let config = syn_loader
.language_server_configs()
.get(&name)
.ok_or_else(|| anyhow::anyhow!("Language server '{name}' not defined"))?;
let id = self.counter;
self.counter += 1;
let NewClient(client, incoming) = start_client(
if let Some(NewClient(client, incoming)) = start_client(
id,
name,
ls_config,
@ -695,9 +697,12 @@ impl Registry {
doc_path,
root_dirs,
enable_snippets,
)?;
)? {
self.incoming.push(UnboundedReceiverStream::new(incoming));
Ok(client)
Ok(Some(client))
} else {
Ok(None)
}
}
/// If this method is called, all documents that have a reference to language servers used by the language config have to refresh their language servers,
@ -722,8 +727,8 @@ impl Registry {
root_dirs,
enable_snippets,
) {
Ok(client) => client,
error => return Some(error),
Ok(client) => client?,
Err(error) => return Some(Err(error)),
};
let old_clients = self
.inner
@ -763,13 +768,13 @@ impl Registry {
root_dirs: &'a [PathBuf],
enable_snippets: bool,
) -> impl Iterator<Item = (LanguageServerName, Result<Arc<Client>>)> + 'a {
language_config.language_servers.iter().map(
language_config.language_servers.iter().filter_map(
move |LanguageServerFeatures { name, .. }| {
if let Some(clients) = self.inner.get(name) {
if let Some((_, client)) = clients.iter().enumerate().find(|(i, client)| {
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
}) {
return (name.to_owned(), Ok(client.clone()));
return Some((name.to_owned(), Ok(client.clone())));
}
}
match self.start_client(
@ -780,13 +785,14 @@ impl Registry {
enable_snippets,
) {
Ok(client) => {
let client = client?;
self.inner
.entry(name.to_owned())
.or_default()
.push(client.clone());
(name.clone(), Ok(client))
Some((name.clone(), Ok(client)))
}
Err(err) => (name.to_owned(), Err(err)),
Err(err) => Some((name.to_owned(), Err(err))),
}
},
)
@ -887,18 +893,45 @@ fn start_client(
doc_path: Option<&std::path::PathBuf>,
root_dirs: &[PathBuf],
enable_snippets: bool,
) -> Result<NewClient> {
) -> Result<Option<NewClient>> {
let (workspace, workspace_is_cwd) = helix_loader::find_workspace();
let workspace = path::normalize(workspace);
let root = find_lsp_workspace(
doc_path
.and_then(|x| x.parent().and_then(|x| x.to_str()))
.unwrap_or("."),
&config.roots,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
&workspace,
workspace_is_cwd,
);
// `root_uri` and `workspace_folder` can be empty in case there is no workspace
// `root_url` can not, use `workspace` as a fallback
let root_path = root.clone().unwrap_or_else(|| workspace.clone());
let root_uri = root.and_then(|root| lsp::Url::from_file_path(root).ok());
if let Some(globset) = &ls_config.required_root_patterns {
if !root_path
.read_dir()?
.flatten()
.map(|entry| entry.file_name())
.any(|entry| globset.is_match(entry))
{
return Ok(None);
}
}
let (client, incoming, initialize_notify) = Client::start(
&ls_config.command,
&ls_config.args,
ls_config.config.clone(),
ls_config.environment.clone(),
&config.roots,
config.workspace_lsp_roots.as_deref().unwrap_or(root_dirs),
root_path,
root_uri,
id,
name,
ls_config.timeout,
doc_path,
)?;
let client = Arc::new(client);
@ -937,7 +970,7 @@ fn start_client(
initialize_notify.notify_one();
});
Ok(NewClient(client, incoming))
Ok(Some(NewClient(client, incoming)))
}
/// Find an LSP workspace of a file using the following mechanism:

@ -18,4 +18,4 @@ ropey = { version = "1.6.1", default-features = false }
which = "6.0"
[dev-dependencies]
tempfile = "3.9"
tempfile = "3.10"

@ -42,6 +42,7 @@ signal-hook = "0.3"
tokio-stream = "0.1"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
arc-swap = { version = "1.6.0" }
termini = "1"
# Logging
fern = "0.6"
@ -52,7 +53,7 @@ log = "0.4"
nucleo.workspace = true
ignore = "0.4"
# markdown doc rendering
pulldown-cmark = { version = "0.9", default-features = false }
pulldown-cmark = { version = "0.10", default-features = false }
# file type detection
content_inspector = "0.2.4"
@ -61,7 +62,7 @@ open = "5.0.1"
url = "2.5.0"
# config
toml = "0.7"
toml = "0.8"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
@ -72,7 +73,7 @@ grep-searcher = "0.1.13"
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
libc = "0.2.152"
libc = "0.2.153"
[target.'cfg(target_os = "macos")'.dependencies]
crossterm = { version = "0.27", features = ["event-stream", "use-dev-tty"] }
@ -83,4 +84,4 @@ helix-loader = { path = "../helix-loader" }
[dev-dependencies]
smallvec = "1.13"
indoc = "2.0.4"
tempfile = "3.9.0"
tempfile = "3.10.0"

@ -6,4 +6,150 @@ fn main() {
build_grammars(Some(std::env::var("TARGET").unwrap()))
.expect("Failed to compile tree-sitter grammars");
}
#[cfg(windows)]
windows_rc::link_icon_in_windows_exe("../contrib/helix-256p.ico");
}
#[cfg(windows)]
mod windows_rc {
use std::io::prelude::Write;
use std::{env, io, path::Path, path::PathBuf, process};
pub(crate) fn link_icon_in_windows_exe(icon_path: &str) {
let rc_exe = find_rc_exe().expect("Windows SDK is to be installed along with MSVC");
let output = env::var("OUT_DIR").expect("Env var OUT_DIR should have been set by compiler");
let output_dir = PathBuf::from(output);
let rc_path = output_dir.join("resource.rc");
write_resource_file(&rc_path, icon_path).unwrap();
let resource_file = PathBuf::from(&output_dir).join("resource.lib");
compile_with_toolkit_msvc(rc_exe, resource_file, rc_path);
println!("cargo:rustc-link-search=native={}", output_dir.display());
println!("cargo:rustc-link-lib=dylib=resource");
}
fn compile_with_toolkit_msvc(rc_exe: PathBuf, output: PathBuf, input: PathBuf) {
let mut command = process::Command::new(rc_exe);
let command = command.arg(format!(
"/I{}",
env::var("CARGO_MANIFEST_DIR")
.expect("CARGO_MANIFEST_DIR should have been set by Cargo")
));
let status = command
.arg(format!("/fo{}", output.display()))
.arg(format!("{}", input.display()))
.output()
.unwrap();
println!(
"RC Output:\n{}\n------",
String::from_utf8_lossy(&status.stdout)
);
println!(
"RC Error:\n{}\n------",
String::from_utf8_lossy(&status.stderr)
);
}
fn find_rc_exe() -> io::Result<PathBuf> {
let find_reg_key = process::Command::new("reg")
.arg("query")
.arg(r"HKLM\SOFTWARE\Microsoft\Windows Kits\Installed Roots")
.arg("/reg:32")
.arg("/v")
.arg("KitsRoot10")
.output();
match find_reg_key {
Err(find_reg_key) => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to run registry query: {}", find_reg_key),
))
}
Ok(find_reg_key) => {
if find_reg_key.status.code().unwrap() != 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
"Can not find Windows SDK",
));
} else {
let lines = String::from_utf8(find_reg_key.stdout)
.expect("Should be able to parse the output");
let mut lines: Vec<&str> = lines.lines().collect();
let mut rc_exe_paths: Vec<PathBuf> = Vec::new();
lines.reverse();
for line in lines {
if line.trim().starts_with("KitsRoot") {
let kit: String = line
.chars()
.skip(line.find("REG_SZ").unwrap() + 6)
.skip_while(|c| c.is_whitespace())
.collect();
let p = PathBuf::from(&kit);
let rc = if cfg!(target_arch = "x86_64") {
p.join(r"bin\x64\rc.exe")
} else {
p.join(r"bin\x86\rc.exe")
};
if rc.exists() {
println!("{:?}", rc);
rc_exe_paths.push(rc.to_owned());
}
if let Ok(bin) = p.join("bin").read_dir() {
for e in bin.filter_map(|e| e.ok()) {
let p = if cfg!(target_arch = "x86_64") {
e.path().join(r"x64\rc.exe")
} else {
e.path().join(r"x86\rc.exe")
};
if p.exists() {
println!("{:?}", p);
rc_exe_paths.push(p.to_owned());
}
}
}
}
}
if rc_exe_paths.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"Can not find Windows SDK",
));
}
println!("{:?}", rc_exe_paths);
let rc_path = rc_exe_paths.pop().unwrap();
let rc_exe = if !rc_path.exists() {
if cfg!(target_arch = "x86_64") {
PathBuf::from(rc_path.parent().unwrap()).join(r"bin\x64\rc.exe")
} else {
PathBuf::from(rc_path.parent().unwrap()).join(r"bin\x86\rc.exe")
}
} else {
rc_path
};
println!("Selected RC path: '{}'", rc_exe.display());
Ok(rc_exe)
}
}
}
}
fn write_resource_file(rc_path: &Path, icon_path: &str) -> io::Result<()> {
let mut f = std::fs::File::create(rc_path)?;
writeln!(f, "{} ICON \"{}\"", 1, icon_path)?;
Ok(())
}
}

@ -21,7 +21,6 @@ use tui::backend::Backend;
use crate::{
args::Args,
commands::apply_workspace_edit,
compositor::{Compositor, Event},
config::Config,
handlers,
@ -67,7 +66,7 @@ pub struct Application {
#[allow(dead_code)]
theme_loader: Arc<theme::Loader>,
#[allow(dead_code)]
syn_loader: Arc<syntax::Loader>,
syn_loader: Arc<ArcSwap<syntax::Loader>>,
signals: Signals,
jobs: Jobs,
@ -97,11 +96,7 @@ fn setup_integration_logging() {
}
impl Application {
pub fn new(
args: Args,
config: Config,
syn_loader_conf: syntax::Configuration,
) -> Result<Self, Error> {
pub fn new(args: Args, config: Config, lang_loader: syntax::Loader) -> Result<Self, Error> {
#[cfg(feature = "integration")]
setup_integration_logging();
@ -127,7 +122,7 @@ impl Application {
})
.unwrap_or_else(|| theme_loader.default_theme(true_color));
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
let syn_loader = Arc::new(ArcSwap::from_pointee(lang_loader));
#[cfg(not(feature = "integration"))]
let backend = CrosstermBackend::new(stdout(), &config.editor);
@ -395,10 +390,9 @@ impl Application {
/// refresh language config after config change
fn refresh_language_config(&mut self) -> Result<(), Error> {
let syntax_config = helix_core::config::user_syntax_loader()
.map_err(|err| anyhow::anyhow!("Failed to load language config: {}", err))?;
let lang_loader = helix_core::config::user_lang_loader()?;
self.syn_loader = std::sync::Arc::new(syntax::Loader::new(syntax_config));
self.syn_loader.store(Arc::new(lang_loader));
self.editor.syn_loader = self.syn_loader.clone();
for document in self.editor.documents.values_mut() {
document.detect_language(self.syn_loader.clone());
@ -573,26 +567,8 @@ impl Application {
let lines = doc_save_event.text.len_lines();
let bytes = doc_save_event.text.len_bytes();
if doc.path() != Some(&doc_save_event.path) {
doc.set_path(Some(&doc_save_event.path));
let loader = self.editor.syn_loader.clone();
// borrowing the same doc again to get around the borrow checker
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
let id = doc.id();
doc.detect_language(loader);
self.editor.refresh_language_servers(id);
// and again a borrow checker workaround...
let doc = doc_mut!(self.editor, &doc_save_event.doc_id);
let diagnostics = Editor::doc_diagnostics(
&self.editor.language_servers,
&self.editor.diagnostics,
doc,
);
doc.replace_diagnostics(diagnostics, &[], None);
}
self.editor
.set_doc_path(doc_save_event.doc_id, &doc_save_event.path);
// TODO: fix being overwritten by lsp
self.editor.set_status(format!(
"'{}' written, {}L {}B",
@ -1011,11 +987,9 @@ impl Application {
let language_server = language_server!();
if language_server.is_initialized() {
let offset_encoding = language_server.offset_encoding();
let res = apply_workspace_edit(
&mut self.editor,
offset_encoding,
&params.edit,
);
let res = self
.editor
.apply_workspace_edit(offset_encoding, &params.edit);
Ok(json!(lsp::ApplyWorkspaceEditResponse {
applied: res.is_ok(),

@ -3653,7 +3653,7 @@ pub mod insert {
(pos, pos, local_offs)
};
let new_range = if doc.restore_cursor {
let new_range = if range.cursor(text) > range.anchor {
// when appending, extend the range by local_offs
Range::new(
range.anchor + global_offs,
@ -4086,6 +4086,7 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
return;
};
let values: Vec<_> = values.map(|value| value.to_string()).collect();
let scrolloff = editor.config().scrolloff;
let (view, doc) = current!(editor);
let repeat = std::iter::repeat(
@ -4108,6 +4109,8 @@ fn replace_with_yanked_impl(editor: &mut Editor, register: char, count: usize) {
});
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
view.ensure_cursor_in_view(doc, scrolloff);
}
fn replace_selections_with_clipboard(cx: &mut Context) {

@ -729,8 +729,7 @@ pub fn code_action(cx: &mut Context) {
resolved_code_action.as_ref().unwrap_or(code_action);
if let Some(ref workspace_edit) = resolved_code_action.edit {
log::debug!("edit: {:?}", workspace_edit);
let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
let _ = editor.apply_workspace_edit(offset_encoding, workspace_edit);
}
// if code action provides both edit and command first the edit
@ -790,63 +789,6 @@ pub fn execute_lsp_command(editor: &mut Editor, language_server_id: usize, cmd:
});
}
pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
use lsp::ResourceOp;
use std::fs;
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if ignore_if_exists && path.exists() {
Ok(())
} else {
// Create directory if it does not exist
if let Some(dir) = path.parent() {
if !dir.is_dir() {
fs::create_dir_all(dir)?;
}
}
fs::write(&path, [])
}
}
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
if path.is_dir() {
let recursive = op
.options
.as_ref()
.and_then(|options| options.recursive)
.unwrap_or(false);
if recursive {
fs::remove_dir_all(&path)
} else {
fs::remove_dir(&path)
}
} else if path.is_file() {
fs::remove_file(&path)
} else {
Ok(())
}
}
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if ignore_if_exists && to.exists() {
Ok(())
} else {
fs::rename(from, &to)
}
}
}
}
#[derive(Debug)]
pub struct ApplyEditError {
pub kind: ApplyEditErrorKind,
@ -874,142 +816,6 @@ impl ToString for ApplyEditErrorKind {
}
}
///TODO make this transactional (and set failureMode to transactional)
pub fn apply_workspace_edit(
editor: &mut Editor,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
) -> Result<(), ApplyEditError> {
let mut apply_edits = |uri: &helix_lsp::Url,
version: Option<i32>,
text_edits: Vec<lsp::TextEdit>|
-> Result<(), ApplyEditErrorKind> {
let path = match uri.to_file_path() {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", uri);
log::error!("{}", err);
editor.set_error(err);
return Err(ApplyEditErrorKind::UnknownURISchema);
}
};
let doc_id = match editor.open(&path, Action::Load) {
Ok(doc_id) => doc_id,
Err(err) => {
let err = format!("failed to open document: {}: {}", uri, err);
log::error!("{}", err);
editor.set_error(err);
return Err(ApplyEditErrorKind::FileNotFound);
}
};
let doc = doc!(editor, &doc_id);
if let Some(version) = version {
if version != doc.version() {
let err = format!("outdated workspace edit for {path:?}");
log::error!("{err}, expected {} but got {version}", doc.version());
editor.set_error(err);
return Err(ApplyEditErrorKind::DocumentChanged);
}
}
// Need to determine a view for apply/append_changes_to_history
let view_id = editor.get_synced_view_id(doc_id);
let doc = doc_mut!(editor, &doc_id);
let transaction = helix_lsp::util::generate_transaction_from_edits(
doc.text(),
text_edits,
offset_encoding,
);
let view = view_mut!(editor, view_id);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
Ok(())
};
if let Some(ref document_changes) = workspace_edit.document_changes {
match document_changes {
lsp::DocumentChanges::Edits(document_edits) => {
for (i, document_edit) in document_edits.iter().enumerate() {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
apply_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - operations: {:?}", operations);
for (i, operation) in operations.iter().enumerate() {
match operation {
lsp::DocumentChangeOperation::Op(op) => {
apply_document_resource_op(op).map_err(|io| ApplyEditError {
kind: ApplyEditErrorKind::IoError(io),
failed_change_idx: i,
})?;
}
lsp::DocumentChangeOperation::Edit(document_edit) => {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
apply_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
}
}
}
return Ok(());
}
if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", changes);
for (i, (uri, text_edits)) in changes.iter().enumerate() {
let text_edits = text_edits.to_vec();
apply_edits(uri, None, text_edits).map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
Ok(())
}
/// Precondition: `locations` should be non-empty.
fn goto_impl(
editor: &mut Editor,
@ -1323,7 +1129,7 @@ pub fn rename_symbol(cx: &mut Context) {
match block_on(future) {
Ok(edits) => {
let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
let _ = cx.editor.apply_workspace_edit(offset_encoding, &edits);
}
Err(err) => cx.editor.set_error(err.to_string()),
}

@ -8,7 +8,6 @@ use super::*;
use helix_core::fuzzy::fuzzy_match;
use helix_core::indent::MAX_INDENT;
use helix_core::{encoding, line_ending, shellwords::Shellwords};
use helix_lsp::{OffsetEncoding, Url};
use helix_view::document::DEFAULT_LANGUAGE_NAME;
use helix_view::editor::{Action, CloseError, ConfigEvent};
use serde_json::Value;
@ -1546,10 +1545,7 @@ fn tree_sitter_highlight_name(
let text = doc.text().slice(..);
let cursor = doc.selection(view.id).primary().cursor(text);
let byte = text.char_to_byte(cursor);
let node = syntax
.tree()
.root_node()
.descendant_for_byte_range(byte, byte)?;
let node = syntax.descendant_for_byte_range(byte, byte)?;
// Query the same range as the one used in syntax highlighting.
let range = {
// Calculate viewport byte ranges:
@ -2407,67 +2403,14 @@ fn move_buffer(
ensure!(args.len() == 1, format!(":move takes one argument"));
let doc = doc!(cx.editor);
let new_path =
helix_stdx::path::canonicalize(&PathBuf::from(args.first().unwrap().to_string()));
let old_path = doc
.path()
.ok_or_else(|| anyhow!("Scratch buffer cannot be moved. Use :write instead"))?
.context("Scratch buffer cannot be moved. Use :write instead")?
.clone();
let old_path_as_url = doc.url().unwrap();
let new_path_as_url = Url::from_file_path(&new_path).unwrap();
let edits: Vec<(
helix_lsp::Result<helix_lsp::lsp::WorkspaceEdit>,
OffsetEncoding,
String,
)> = doc
.language_servers()
.map(|lsp| {
(
lsp.prepare_file_rename(&old_path_as_url, &new_path_as_url),
lsp.offset_encoding(),
lsp.name().to_owned(),
)
})
.filter(|(f, _, _)| f.is_some())
.map(|(f, encoding, name)| (helix_lsp::block_on(f.unwrap()), encoding, name))
.collect();
for (lsp_reply, encoding, name) in edits {
match lsp_reply {
Ok(edit) => {
if let Err(e) = apply_workspace_edit(cx.editor, encoding, &edit) {
log::error!(
":move command failed to apply edits from lsp {}: {:?}",
name,
e
);
};
}
Err(e) => {
log::error!("LSP {} failed to treat willRename request: {:?}", name, e);
}
};
let new_path = args.first().unwrap().to_string();
if let Err(err) = cx.editor.move_path(&old_path, new_path.as_ref()) {
bail!("Could not move file: {err}");
}
let doc = doc_mut!(cx.editor);
doc.set_path(Some(new_path.as_path()));
if let Err(e) = std::fs::rename(&old_path, &new_path) {
doc.set_path(Some(old_path.as_path()));
bail!("Could not move file: {}", e);
};
doc.language_servers().for_each(|lsp| {
lsp.did_file_rename(&old_path_as_url, &new_path_as_url);
});
cx.editor
.language_servers
.file_event_handler
.file_changed(new_path);
Ok(())
}

@ -2,7 +2,7 @@ use crossterm::{
style::{Color, Print, Stylize},
tty::IsTty,
};
use helix_core::config::{default_syntax_loader, user_syntax_loader};
use helix_core::config::{default_lang_config, user_lang_config};
use helix_loader::grammar::load_runtime_file;
use helix_view::clipboard::get_clipboard_provider;
use std::io::Write;
@ -128,7 +128,7 @@ pub fn languages_all() -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut syn_loader_conf = match user_syntax_loader() {
let mut syn_loader_conf = match user_lang_config() {
Ok(conf) => conf,
Err(err) => {
let stderr = std::io::stderr();
@ -141,7 +141,7 @@ pub fn languages_all() -> std::io::Result<()> {
err
)?;
writeln!(stderr, "{}", "Using default language config".yellow())?;
default_syntax_loader()
default_lang_config()
}
};
@ -234,7 +234,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let syn_loader_conf = match user_syntax_loader() {
let syn_loader_conf = match user_lang_config() {
Ok(conf) => conf,
Err(err) => {
let stderr = std::io::stderr();
@ -247,7 +247,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
err
)?;
writeln!(stderr, "{}", "Using default language config".yellow())?;
default_syntax_loader()
default_lang_config()
}
};

@ -22,17 +22,30 @@ use url::Url;
pub use keymap::macros::*;
#[cfg(not(windows))]
fn true_color() -> bool {
std::env::var("COLORTERM")
.map(|v| matches!(v.as_str(), "truecolor" | "24bit"))
.unwrap_or(false)
}
#[cfg(windows)]
fn true_color() -> bool {
true
}
#[cfg(not(windows))]
fn true_color() -> bool {
if matches!(
std::env::var("COLORTERM").map(|v| matches!(v.as_str(), "truecolor" | "24bit")),
Ok(true)
) {
return true;
}
match termini::TermInfo::from_env() {
Ok(t) => {
t.extended_cap("RGB").is_some()
|| t.extended_cap("Tc").is_some()
|| (t.extended_cap("setrgbf").is_some() && t.extended_cap("setrgbb").is_some())
}
Err(_) => false,
}
}
/// Function used for filtering dir entries in the various file pickers.
fn filter_picker_entry(entry: &DirEntry, root: &Path, dedup_symlinks: bool) -> bool {
// We always want to ignore the .git directory, otherwise if

@ -145,18 +145,18 @@ FLAGS:
}
};
let syn_loader_conf = helix_core::config::user_syntax_loader().unwrap_or_else(|err| {
eprintln!("Bad language config: {}", err);
let lang_loader = helix_core::config::user_lang_loader().unwrap_or_else(|err| {
eprintln!("{}", err);
eprintln!("Press <ENTER> to continue with default language config");
use std::io::Read;
// This waits for an enter press.
let _ = std::io::stdin().read(&mut []);
helix_core::config::default_syntax_loader()
helix_core::config::default_lang_loader()
});
// TODO: use the thread local executor to spawn the application task separately from the work pool
let mut app = Application::new(args, config, syn_loader_conf)
.context("unable to create new application")?;
let mut app =
Application::new(args, config, lang_loader).context("unable to create new application")?;
let exit_code = app.run(&mut EventStream::new()).await?;

@ -1,5 +1,6 @@
use std::sync::Arc;
use arc_swap::ArcSwap;
use helix_core::syntax;
use helix_view::graphics::{Margin, Rect, Style};
use tui::buffer::Buffer;
@ -18,13 +19,17 @@ pub struct SignatureHelp {
active_param_range: Option<(usize, usize)>,
language: String,
config_loader: Arc<syntax::Loader>,
config_loader: Arc<ArcSwap<syntax::Loader>>,
}
impl SignatureHelp {
pub const ID: &'static str = "signature-help";
pub fn new(signature: String, language: String, config_loader: Arc<syntax::Loader>) -> Self {
pub fn new(
signature: String,
language: String,
config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
Self {
signature,
signature_doc: None,

@ -1,4 +1,5 @@
use crate::compositor::{Component, Context};
use arc_swap::ArcSwap;
use tui::{
buffer::Buffer as Surface,
text::{Span, Spans, Text},
@ -6,7 +7,7 @@ use tui::{
use std::sync::Arc;
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag};
use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd};
use helix_core::{
syntax::{self, HighlightEvent, InjectionLanguageMarker, Syntax},
@ -31,7 +32,7 @@ pub fn highlighted_code_block<'a>(
text: &str,
language: &str,
theme: Option<&Theme>,
config_loader: Arc<syntax::Loader>,
config_loader: Arc<ArcSwap<syntax::Loader>>,
additional_highlight_spans: Option<Vec<(usize, std::ops::Range<usize>)>>,
) -> Text<'a> {
let mut spans = Vec::new();
@ -48,6 +49,7 @@ pub fn highlighted_code_block<'a>(
let ropeslice = RopeSlice::from(text);
let syntax = config_loader
.load()
.language_configuration_for_injection_string(&InjectionLanguageMarker::Name(
language.into(),
))
@ -121,7 +123,7 @@ pub fn highlighted_code_block<'a>(
pub struct Markdown {
contents: String,
config_loader: Arc<syntax::Loader>,
config_loader: Arc<ArcSwap<syntax::Loader>>,
}
// TODO: pre-render and self reference via Pin
@ -140,7 +142,7 @@ impl Markdown {
];
const INDENT: &'static str = " ";
pub fn new(contents: String, config_loader: Arc<syntax::Loader>) -> Self {
pub fn new(contents: String, config_loader: Arc<ArcSwap<syntax::Loader>>) -> Self {
Self {
contents,
config_loader,
@ -209,7 +211,7 @@ impl Markdown {
list_stack.push(list);
}
Event::End(Tag::List(_)) => {
Event::End(TagEnd::List(_)) => {
list_stack.pop();
// whenever top-level list closes, empty line
@ -249,7 +251,10 @@ impl Markdown {
Event::End(tag) => {
tags.pop();
match tag {
Tag::Heading(_, _, _) | Tag::Paragraph | Tag::CodeBlock(_) | Tag::Item => {
TagEnd::Heading(_)
| TagEnd::Paragraph
| TagEnd::CodeBlock
| TagEnd::Item => {
push_line(&mut spans, &mut lines);
}
_ => (),
@ -257,7 +262,7 @@ impl Markdown {
// whenever heading, code block or paragraph closes, empty line
match tag {
Tag::Heading(_, _, _) | Tag::Paragraph | Tag::CodeBlock(_) => {
TagEnd::Heading(_) | TagEnd::Paragraph | TagEnd::CodeBlock => {
lines.push(Spans::default());
}
_ => (),
@ -279,7 +284,7 @@ impl Markdown {
lines.extend(tui_text.lines.into_iter());
} else {
let style = match tags.last() {
Some(Tag::Heading(level, ..)) => match level {
Some(Tag::Heading { level, .. }) => match level {
HeadingLevel::H1 => heading_styles[0],
HeadingLevel::H2 => heading_styles[1],
HeadingLevel::H3 => heading_styles[2],

@ -427,6 +427,7 @@ impl<T: Item + 'static> Component for Menu<T> {
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
} else if !render_borders {
// Draw scroll track
cell.set_symbol(half_block);
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
}
}

@ -336,8 +336,8 @@ pub mod completers {
pub fn language(editor: &Editor, input: &str) -> Vec<Completion> {
let text: String = "text".into();
let language_ids = editor
.syn_loader
let loader = editor.syn_loader.load();
let language_ids = loader
.language_configs()
.map(|config| &config.language_id)
.chain(std::iter::once(&text));

@ -461,14 +461,17 @@ impl<T: Item + 'static> Picker<T> {
// Then attempt to highlight it if it has no language set
if doc.language_config().is_none() {
if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader) {
if let Some(language_config) = doc.detect_language_config(&cx.editor.syn_loader.load())
{
doc.language = Some(language_config.clone());
let text = doc.text().clone();
let loader = cx.editor.syn_loader.clone();
let job = tokio::task::spawn_blocking(move || {
let syntax = language_config.highlight_config(&loader.scopes()).and_then(
|highlight_config| Syntax::new(text.slice(..), highlight_config, loader),
);
let syntax = language_config
.highlight_config(&loader.load().scopes())
.and_then(|highlight_config| {
Syntax::new(text.slice(..), highlight_config, loader)
});
let callback = move |editor: &mut Editor, compositor: &mut Compositor| {
let Some(syntax) = syntax else {
log::info!("highlighting picker item failed");

@ -303,6 +303,7 @@ impl<T: Component> Component for Popup<T> {
cell.set_fg(scroll_style.fg.unwrap_or(helix_view::theme::Color::Reset));
} else if !render_borders {
// Draw scroll track
cell.set_symbol(half_block);
cell.set_fg(scroll_style.bg.unwrap_or(helix_view::theme::Color::Reset));
}
}

@ -1,5 +1,6 @@
use crate::compositor::{Component, Compositor, Context, Event, EventResult};
use crate::{alt, ctrl, key, shift, ui};
use arc_swap::ArcSwap;
use helix_core::syntax;
use helix_view::input::KeyEvent;
use helix_view::keyboard::KeyCode;
@ -34,7 +35,7 @@ pub struct Prompt {
callback_fn: CallbackFn,
pub doc_fn: DocFn,
next_char_handler: Option<PromptCharHandler>,
language: Option<(&'static str, Arc<syntax::Loader>)>,
language: Option<(&'static str, Arc<ArcSwap<syntax::Loader>>)>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
@ -98,7 +99,11 @@ impl Prompt {
self
}
pub fn with_language(mut self, language: &'static str, loader: Arc<syntax::Loader>) -> Self {
pub fn with_language(
mut self,
language: &'static str,
loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
self.language = Some((language, loader));
self
}
@ -393,7 +398,7 @@ impl Prompt {
height,
);
if !self.completion.is_empty() {
if completion_area.height > 0 && !self.completion.is_empty() {
let area = completion_area;
let background = theme.get("ui.menu");

@ -315,7 +315,7 @@ async fn test_write_auto_format_fails_still_writes() -> anyhow::Result<()> {
let mut app = helpers::AppBuilder::new()
.with_file(file.path(), None)
.with_input_text("#[l|]#et foo = 0;\n")
.with_lang_config(helpers::test_syntax_conf(Some(lang_conf.into())))
.with_lang_loader(helpers::test_syntax_loader(Some(lang_conf.into())))
.build()?;
test_key_sequences(&mut app, vec![(Some(":w<ret>"), None)], false).await?;

@ -139,7 +139,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
let test_case = test_case.into();
let mut app = match app {
Some(app) => app,
None => Application::new(Args::default(), test_config(), test_syntax_conf(None))?,
None => Application::new(Args::default(), test_config(), test_syntax_loader(None))?,
};
let (view, doc) = helix_view::current!(app.editor);
@ -162,9 +162,9 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>(
.await
}
/// Generates language configs that merge in overrides, like a user language
/// Generates language config loader that merge in overrides, like a user language
/// config. The argument string must be a raw TOML document.
pub fn test_syntax_conf(overrides: Option<String>) -> helix_core::syntax::Configuration {
pub fn test_syntax_loader(overrides: Option<String>) -> helix_core::syntax::Loader {
let mut lang = helix_loader::config::default_lang_config();
if let Some(overrides) = overrides {
@ -172,7 +172,7 @@ pub fn test_syntax_conf(overrides: Option<String>) -> helix_core::syntax::Config
lang = helix_loader::merge_toml_values(lang, override_toml, 3);
}
lang.try_into().unwrap()
helix_core::syntax::Loader::new(lang.try_into().unwrap()).unwrap()
}
/// Use this for very simple test cases where there is one input
@ -271,7 +271,7 @@ pub fn new_readonly_tempfile() -> anyhow::Result<NamedTempFile> {
pub struct AppBuilder {
args: Args,
config: Config,
syn_conf: helix_core::syntax::Configuration,
syn_loader: helix_core::syntax::Loader,
input: Option<(String, Selection)>,
}
@ -280,7 +280,7 @@ impl Default for AppBuilder {
Self {
args: Args::default(),
config: test_config(),
syn_conf: test_syntax_conf(None),
syn_loader: test_syntax_loader(None),
input: None,
}
}
@ -314,8 +314,8 @@ impl AppBuilder {
self
}
pub fn with_lang_config(mut self, syn_conf: helix_core::syntax::Configuration) -> Self {
self.syn_conf = syn_conf;
pub fn with_lang_loader(mut self, syn_loader: helix_core::syntax::Loader) -> Self {
self.syn_loader = syn_loader;
self
}
@ -328,7 +328,7 @@ impl AppBuilder {
bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests");
}
let mut app = Application::new(self.args, self.config, self.syn_conf)?;
let mut app = Application::new(self.args, self.config, self.syn_loader)?;
if let Some((text, selection)) = self.input {
let (view, doc) = helix_view::current!(app.editor);

@ -20,7 +20,7 @@ helix-core = { path = "../helix-core" }
bitflags = "2.4"
cassowary = "0.3"
unicode-segmentation = "1.10"
unicode-segmentation = "1.11"
crossterm = { version = "0.27", optional = true }
termini = "1.0"
serde = { version = "1", "optional" = true, features = ["derive"]}

@ -19,7 +19,7 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "time", "sync", "p
parking_lot = "0.12"
arc-swap = { version = "1.6.0" }
gix = { version = "0.58.0", default-features = false , optional = true }
gix = { version = "0.58.0", features = ["attributes"], default-features = false, optional = true }
imara-diff = "0.1.5"
anyhow = "1"
@ -29,4 +29,4 @@ log = "0.4"
git = ["gix"]
[dev-dependencies]
tempfile = "3.9"
tempfile = "3.10"

@ -1,5 +1,7 @@
use anyhow::{bail, Context, Result};
use arc_swap::ArcSwap;
use gix::filter::plumbing::driver::apply::Delay;
use std::io::Read;
use std::path::Path;
use std::sync::Arc;
@ -76,30 +78,22 @@ impl DiffProvider for Git {
let file_oid = find_file_in_commit(&repo, &head, file)?;
let file_object = repo.find_object(file_oid)?;
let mut data = file_object.detach().data;
// convert LF to CRLF if configured to avoid showing every line as changed
if repo
.config_snapshot()
.boolean("core.autocrlf")
.unwrap_or(false)
{
let mut normalized_file = Vec::with_capacity(data.len());
let mut at_cr = false;
for &byte in &data {
if byte == b'\n' {
// if this is a LF instead of a CRLF (last byte was not a CR)
// insert a new CR to generate a CRLF
if !at_cr {
normalized_file.push(b'\r');
}
}
at_cr = byte == b'\r';
normalized_file.push(byte)
}
data = normalized_file
}
let data = file_object.detach().data;
// Get the actual data that git would make out of the git object.
// This will apply the user's git config or attributes like crlf conversions.
if let Some(work_dir) = repo.work_dir() {
let rela_path = file.strip_prefix(work_dir)?;
let rela_path = gix::path::try_into_bstr(rela_path)?;
let (mut pipeline, _) = repo.filter_pipeline(None)?;
let mut worktree_outcome =
pipeline.convert_to_worktree(&data, rela_path.as_ref(), Delay::Forbid)?;
let mut buf = Vec::with_capacity(data.len());
worktree_outcome.read_to_end(&mut buf)?;
Ok(buf)
} else {
Ok(data)
}
}
fn get_current_head_name(&self, file: &Path) -> Result<Arc<ArcSwap<Box<str>>>> {
debug_assert!(!file.exists() || file.is_file());

@ -43,14 +43,14 @@ chardetng = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.7"
toml = "0.8"
log = "~0.4"
parking_lot = "0.12.1"
[target.'cfg(windows)'.dependencies]
clipboard-win = { version = "5.0", features = ["std"] }
clipboard-win = { version = "5.1", features = ["std"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2"

@ -681,7 +681,7 @@ impl Document {
pub fn open(
path: &Path,
encoding: Option<&'static Encoding>,
config_loader: Option<Arc<syntax::Loader>>,
config_loader: Option<Arc<ArcSwap<syntax::Loader>>>,
config: Arc<dyn DynAccess<Config>>,
) -> Result<Self, Error> {
// Open the file if it exists, otherwise assume it is a new file (and thus empty).
@ -922,10 +922,11 @@ impl Document {
}
/// Detect the programming language based on the file type.
pub fn detect_language(&mut self, config_loader: Arc<syntax::Loader>) {
pub fn detect_language(&mut self, config_loader: Arc<ArcSwap<syntax::Loader>>) {
let loader = config_loader.load();
self.set_language(
self.detect_language_config(&config_loader),
Some(config_loader),
self.detect_language_config(&loader),
Some(Arc::clone(&config_loader)),
);
}
@ -1041,6 +1042,9 @@ impl Document {
self.encoding
}
/// sets the document path without sending events to various
/// observers (like LSP), in most cases `Editor::set_doc_path`
/// should be used instead
pub fn set_path(&mut self, path: Option<&Path>) {
let path = path.map(helix_stdx::path::canonicalize);
@ -1056,10 +1060,12 @@ impl Document {
pub fn set_language(
&mut self,
language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
loader: Option<Arc<helix_core::syntax::Loader>>,
loader: Option<Arc<ArcSwap<helix_core::syntax::Loader>>>,
) {
if let (Some(language_config), Some(loader)) = (language_config, loader) {
if let Some(highlight_config) = language_config.highlight_config(&loader.scopes()) {
if let Some(highlight_config) =
language_config.highlight_config(&(*loader).load().scopes())
{
self.syntax = Syntax::new(self.text.slice(..), highlight_config, loader);
}
@ -1075,9 +1081,10 @@ impl Document {
pub fn set_language_by_language_id(
&mut self,
language_id: &str,
config_loader: Arc<syntax::Loader>,
config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> anyhow::Result<()> {
let language_config = config_loader
let language_config = (*config_loader)
.load()
.language_config_for_language_id(language_id)
.ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
self.set_language(Some(language_config), Some(config_loader));

@ -23,7 +23,8 @@ use std::{
borrow::Cow,
cell::Cell,
collections::{BTreeMap, HashMap},
io::stdin,
fs,
io::{self, stdin},
num::NonZeroUsize,
path::{Path, PathBuf},
pin::Pin,
@ -45,10 +46,14 @@ use helix_core::{
};
use helix_dap as dap;
use helix_lsp::lsp;
use helix_stdx::path::canonicalize;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use arc_swap::access::{DynAccess, DynGuard};
use arc_swap::{
access::{DynAccess, DynGuard},
ArcSwap,
};
fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
@ -916,7 +921,7 @@ pub struct Editor {
pub debugger_events: SelectAll<UnboundedReceiverStream<dap::Payload>>,
pub breakpoints: HashMap<PathBuf, Vec<Breakpoint>>,
pub syn_loader: Arc<syntax::Loader>,
pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
pub theme_loader: Arc<theme::Loader>,
/// last_theme is used for theme previews. We store the current theme here,
/// and if previewing is cancelled, we can return to it.
@ -1027,7 +1032,7 @@ impl Editor {
pub fn new(
mut area: Rect,
theme_loader: Arc<theme::Loader>,
syn_loader: Arc<syntax::Loader>,
syn_loader: Arc<ArcSwap<syntax::Loader>>,
config: Arc<dyn DynAccess<Config>>,
handlers: Handlers,
) -> Self {
@ -1188,7 +1193,7 @@ impl Editor {
}
let scopes = theme.scopes();
self.syn_loader.set_scopes(scopes.to_vec());
(*self.syn_loader).load().set_scopes(scopes.to_vec());
match preview {
ThemeAction::Preview => {
@ -1215,6 +1220,90 @@ impl Editor {
self.launch_language_servers(doc_id)
}
/// moves/renames a path, invoking any event handlers (currently only lsp)
/// and calling `set_doc_path` if the file is open in the editor
pub fn move_path(&mut self, old_path: &Path, new_path: &Path) -> io::Result<()> {
let new_path = canonicalize(new_path);
// sanity check
if old_path == new_path {
return Ok(());
}
let is_dir = old_path.is_dir();
let language_servers: Vec<_> = self
.language_servers
.iter_clients()
.filter(|client| client.is_initialized())
.cloned()
.collect();
for language_server in language_servers {
let Some(request) = language_server.will_rename(old_path, &new_path, is_dir) else {
continue;
};
let edit = match helix_lsp::block_on(request) {
Ok(edit) => edit,
Err(err) => {
log::error!("invalid willRename response: {err:?}");
continue;
}
};
if let Err(err) = self.apply_workspace_edit(language_server.offset_encoding(), &edit) {
log::error!("failed to apply workspace edit: {err:?}")
}
}
fs::rename(old_path, &new_path)?;
if let Some(doc) = self.document_by_path(old_path) {
self.set_doc_path(doc.id(), &new_path);
}
let is_dir = new_path.is_dir();
for ls in self.language_servers.iter_clients() {
if let Some(notification) = ls.did_rename(old_path, &new_path, is_dir) {
tokio::spawn(notification);
};
}
self.language_servers
.file_event_handler
.file_changed(old_path.to_owned());
self.language_servers
.file_event_handler
.file_changed(new_path);
Ok(())
}
pub fn set_doc_path(&mut self, doc_id: DocumentId, path: &Path) {
let doc = doc_mut!(self, &doc_id);
let old_path = doc.path();
if let Some(old_path) = old_path {
// sanity check, should not occur but some callers (like an LSP) may
// create bogus calls
if old_path == path {
return;
}
// if we are open in LSPs send did_close notification
for language_server in doc.language_servers() {
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
}
}
// we need to clear the list of language servers here so that
// refresh_doc_language/refresh_language_servers doesn't resend
// text_document_did_close. Since we called `text_document_did_close`
// we have fully unregistered this document from its LS
doc.language_servers.clear();
doc.set_path(Some(path));
self.refresh_doc_language(doc_id)
}
pub fn refresh_doc_language(&mut self, doc_id: DocumentId) {
let loader = self.syn_loader.clone();
let doc = doc_mut!(self, &doc_id);
doc.detect_language(loader);
doc.detect_indent_and_line_ending();
self.refresh_language_servers(doc_id);
let doc = doc_mut!(self, &doc_id);
let diagnostics = Editor::doc_diagnostics(&self.language_servers, &self.diagnostics, doc);
doc.replace_diagnostics(diagnostics, &[], None);
}
/// Launch a language server for a given document
fn launch_language_servers(&mut self, doc_id: DocumentId) {
if !self.config().lsp.enable {
@ -1257,7 +1346,7 @@ impl Editor {
.collect::<HashMap<_, _>>()
});
if language_servers.is_empty() {
if language_servers.is_empty() && doc.language_servers.is_empty() {
return;
}
@ -1904,10 +1993,12 @@ impl Editor {
if doc.restore_cursor {
let text = doc.text().slice(..);
let selection = doc.selection(view.id).clone().transform(|range| {
Range::new(
range.from(),
graphemes::prev_grapheme_boundary(text, range.to()),
)
let mut head = range.to();
if range.head > range.anchor {
head = graphemes::prev_grapheme_boundary(text, head);
}
Range::new(range.from(), head)
});
doc.set_selection(view.id, selection);

@ -1,4 +1,8 @@
use crate::editor::Action;
use crate::Editor;
use crate::{DocumentId, ViewId};
use helix_lsp::util::generate_transaction_from_edits;
use helix_lsp::{lsp, OffsetEncoding};
pub enum CompletionEvent {
/// Auto completion was triggered by typing a word char
@ -39,3 +43,228 @@ pub enum SignatureHelpEvent {
Cancel,
RequestComplete { open: bool },
}
#[derive(Debug)]
pub struct ApplyEditError {
pub kind: ApplyEditErrorKind,
pub failed_change_idx: usize,
}
#[derive(Debug)]
pub enum ApplyEditErrorKind {
DocumentChanged,
FileNotFound,
UnknownURISchema,
IoError(std::io::Error),
// TODO: check edits before applying and propagate failure
// InvalidEdit,
}
impl ToString for ApplyEditErrorKind {
fn to_string(&self) -> String {
match self {
ApplyEditErrorKind::DocumentChanged => "document has changed".to_string(),
ApplyEditErrorKind::FileNotFound => "file not found".to_string(),
ApplyEditErrorKind::UnknownURISchema => "URI schema not supported".to_string(),
ApplyEditErrorKind::IoError(err) => err.to_string(),
}
}
}
impl Editor {
fn apply_text_edits(
&mut self,
uri: &helix_lsp::Url,
version: Option<i32>,
text_edits: Vec<lsp::TextEdit>,
offset_encoding: OffsetEncoding,
) -> Result<(), ApplyEditErrorKind> {
let path = match uri.to_file_path() {
Ok(path) => path,
Err(_) => {
let err = format!("unable to convert URI to filepath: {}", uri);
log::error!("{}", err);
self.set_error(err);
return Err(ApplyEditErrorKind::UnknownURISchema);
}
};
let doc_id = match self.open(&path, Action::Load) {
Ok(doc_id) => doc_id,
Err(err) => {
let err = format!("failed to open document: {}: {}", uri, err);
log::error!("{}", err);
self.set_error(err);
return Err(ApplyEditErrorKind::FileNotFound);
}
};
let doc = doc_mut!(self, &doc_id);
if let Some(version) = version {
if version != doc.version() {
let err = format!("outdated workspace edit for {path:?}");
log::error!("{err}, expected {} but got {version}", doc.version());
self.set_error(err);
return Err(ApplyEditErrorKind::DocumentChanged);
}
}
// Need to determine a view for apply/append_changes_to_history
let view_id = self.get_synced_view_id(doc_id);
let doc = doc_mut!(self, &doc_id);
let transaction = generate_transaction_from_edits(doc.text(), text_edits, offset_encoding);
let view = view_mut!(self, view_id);
doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
Ok(())
}
// TODO make this transactional (and set failureMode to transactional)
pub fn apply_workspace_edit(
&mut self,
offset_encoding: OffsetEncoding,
workspace_edit: &lsp::WorkspaceEdit,
) -> Result<(), ApplyEditError> {
if let Some(ref document_changes) = workspace_edit.document_changes {
match document_changes {
lsp::DocumentChanges::Edits(document_edits) => {
for (i, document_edit) in document_edits.iter().enumerate() {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
self.apply_text_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
offset_encoding,
)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
lsp::DocumentChanges::Operations(operations) => {
log::debug!("document changes - operations: {:?}", operations);
for (i, operation) in operations.iter().enumerate() {
match operation {
lsp::DocumentChangeOperation::Op(op) => {
self.apply_document_resource_op(op).map_err(|io| {
ApplyEditError {
kind: ApplyEditErrorKind::IoError(io),
failed_change_idx: i,
}
})?;
}
lsp::DocumentChangeOperation::Edit(document_edit) => {
let edits = document_edit
.edits
.iter()
.map(|edit| match edit {
lsp::OneOf::Left(text_edit) => text_edit,
lsp::OneOf::Right(annotated_text_edit) => {
&annotated_text_edit.text_edit
}
})
.cloned()
.collect();
self.apply_text_edits(
&document_edit.text_document.uri,
document_edit.text_document.version,
edits,
offset_encoding,
)
.map_err(|kind| {
ApplyEditError {
kind,
failed_change_idx: i,
}
})?;
}
}
}
}
}
return Ok(());
}
if let Some(ref changes) = workspace_edit.changes {
log::debug!("workspace changes: {:?}", changes);
for (i, (uri, text_edits)) in changes.iter().enumerate() {
let text_edits = text_edits.to_vec();
self.apply_text_edits(uri, None, text_edits, offset_encoding)
.map_err(|kind| ApplyEditError {
kind,
failed_change_idx: i,
})?;
}
}
Ok(())
}
fn apply_document_resource_op(&mut self, op: &lsp::ResourceOp) -> std::io::Result<()> {
use lsp::ResourceOp;
use std::fs;
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if !ignore_if_exists || !path.exists() {
// Create directory if it does not exist
if let Some(dir) = path.parent() {
if !dir.is_dir() {
fs::create_dir_all(dir)?;
}
}
fs::write(&path, [])?;
self.language_servers.file_event_handler.file_changed(path);
}
}
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
if path.is_dir() {
let recursive = op
.options
.as_ref()
.and_then(|options| options.recursive)
.unwrap_or(false);
if recursive {
fs::remove_dir_all(&path)?
} else {
fs::remove_dir(&path)?
}
self.language_servers.file_event_handler.file_changed(path);
} else if path.is_file() {
fs::remove_file(&path)?;
}
}
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
});
if !ignore_if_exists || !to.exists() {
self.move_path(&from, &to)?;
}
}
}
Ok(())
}
}

@ -253,7 +253,7 @@ source = { git = "https://github.com/FuelLabs/tree-sitter-sway", rev = "e491a005
name = "toml"
scope = "source.toml"
injection-regex = "toml"
file-types = ["toml", "poetry.lock", "Cargo.lock"]
file-types = ["toml", { glob = "poetry.lock" }, { glob = "Cargo.lock" }]
comment-token = "#"
language-servers = [ "taplo" ]
indent = { tab-width = 2, unit = " " }
@ -292,7 +292,7 @@ source = { git = "https://github.com/yusdacra/tree-sitter-protobuf", rev = "19c2
name = "elixir"
scope = "source.elixir"
injection-regex = "(elixir|ex)"
file-types = ["ex", "exs", "mix.lock"]
file-types = ["ex", "exs", { glob = "mix.lock" }]
shebangs = ["elixir"]
roots = ["mix.exs", "mix.lock"]
comment-token = "#"
@ -311,6 +311,8 @@ file-types = ["fish"]
shebangs = ["fish"]
comment-token = "#"
indent = { tab-width = 4, unit = " " }
auto-format = true
formatter = { command = "fish_indent" }
[[grammar]]
name = "fish"
@ -326,6 +328,29 @@ comment-token = "//"
language-servers = [ "mint" ]
indent = { tab-width = 2, unit = " " }
[[language]]
name = "janet"
scope = "source.janet"
injection-regex = "janet"
file-types = ["cgen", "janet", "jdn"]
shebangs = ["janet"]
roots = ["project.janet"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
formatter = { command = "janet-format" }
grammar = "janet-simple"
[language.auto-pairs]
'"' = '"'
'(' = ')'
'[' = ']'
'{' = '}'
"`" = "`"
[[grammar]]
name = "janet-simple"
source = { git = "https://github.com/sogaiu/tree-sitter-janet-simple", rev = "51271e260346878e1a1aa6c506ce6a797b7c25e2" }
[[language]]
name = "json"
scope = "source.json"
@ -338,20 +363,20 @@ file-types = [
"geojson",
"gltf",
"webmanifest",
"flake.lock",
".babelrc",
".bowerrc",
".jscrc",
{ glob = "flake.lock" },
{ glob = ".babelrc" },
{ glob = ".bowerrc" },
{ glob = ".jscrc" },
"js.map",
"ts.map",
"css.map",
".jslintrc",
{ glob = ".jslintrc" },
"jsonld",
".vuerc",
"composer.lock",
".watchmanconfig",
{ glob = ".vuerc" },
{ glob = "composer.lock" },
{ glob = ".watchmanconfig" },
"avsc",
".prettierrc"
{ glob = ".prettierrc" },
]
language-servers = [ "vscode-json-language-server" ]
auto-format = true
@ -416,7 +441,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-c", rev = "7175a6dd
name = "cpp"
scope = "source.cpp"
injection-regex = "cpp"
file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm", "h++", "ii", "inl", { suffix = ".hpp.in" }, { suffix = ".h.in" }]
file-types = ["cc", "hh", "c++", "cpp", "hpp", "h", "ipp", "tpp", "cxx", "hxx", "ixx", "txx", "ino", "C", "H", "cu", "cuh", "cppm", "h++", "ii", "inl", { glob = ".hpp.in" }, { glob = ".h.in" }]
comment-token = "//"
language-servers = [ "clangd" ]
indent = { tab-width = 2, unit = " " }
@ -491,6 +516,30 @@ args = { processId = "{0}" }
name = "c-sharp"
source = { git = "https://github.com/tree-sitter/tree-sitter-c-sharp", rev = "5b60f99545fea00a33bbfae5be956f684c4c69e2" }
[[language]]
name = "cel"
scope = "source.cel"
injection-regex = "cel"
file-types = ["cel"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "cel"
source = { git = "https://github.com/bufbuild/tree-sitter-cel", rev = "9f2b65da14c216df53933748e489db0f11121464" }
[[language]]
name = "spicedb"
scope = "source.zed"
injection-regex = "spicedb"
file-types = ["zed"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "spicedb"
source = { git = "https://github.com/jzelinskie/tree-sitter-spicedb", rev = "a4e4645651f86d6684c15dfa9931b7841dc52a66" }
[[language]]
name = "go"
scope = "source.go"
@ -548,7 +597,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "64457ea
name = "gomod"
scope = "source.gomod"
injection-regex = "gomod"
file-types = ["go.mod"]
file-types = [{ glob = "go.mod" }]
auto-format = true
comment-token = "//"
language-servers = [ "gopls" ]
@ -575,7 +624,7 @@ source = { git = "https://github.com/dannylongeuay/tree-sitter-go-template", rev
name = "gowork"
scope = "source.gowork"
injection-regex = "gowork"
file-types = ["go.work"]
file-types = [{ glob = "go.work" }]
auto-format = true
comment-token = "//"
language-servers = [ "gopls" ]
@ -590,7 +639,7 @@ name = "javascript"
scope = "source.js"
injection-regex = "(js|javascript)"
language-id = "javascript"
file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", "jakefile"]
file-types = ["js", "mjs", "cjs", "rules", "es6", "pac", { glob = "jakefile" }]
shebangs = ["node"]
comment-token = "//"
language-servers = [ "typescript-language-server" ]
@ -693,7 +742,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-html", rev = "29f53
name = "python"
scope = "source.python"
injection-regex = "python"
file-types = ["py","pyi","py3","pyw","ptl",".pythonstartup",".pythonrc","SConstruct", "rpy", "cpy", "ipy", "pyt", "SConscript"]
file-types = ["py", "pyi", "py3", "pyw", "ptl", "rpy", "cpy", "ipy", "pyt", { glob = ".pythonstartup" }, { glob = ".pythonrc" }, { glob = "SConstruct" }, { glob = "SConscript" }]
shebangs = ["python"]
roots = ["pyproject.toml", "setup.py", "poetry.lock", "pyrightconfig.json"]
comment-token = "#"
@ -746,38 +795,38 @@ injection-regex = "ruby"
file-types = [
"rb",
"rake",
"rakefile",
"irb",
"gemfile",
"gemspec",
"Rakefile",
"Gemfile",
"rabl",
"jbuilder",
"jb",
"Podfile",
"podspec",
"Vagrantfile",
"Brewfile",
"rjs",
"rbi",
"Guardfile",
"Capfile",
"Cheffile",
"Hobofile",
"Appraisals",
"Rantfile",
"Berksfile",
"Berksfile.lock",
"Thorfile",
"Puppetfile",
"Fastfile",
"Appfile",
"Deliverfile",
"Matchfile",
"Scanfile",
"Snapfile",
"Gymfile"
{ glob = "rakefile" },
{ glob = "gemfile" },
{ glob = "Rakefile" },
{ glob = "Gemfile" },
{ glob = "Podfile" },
{ glob = "Vagrantfile" },
{ glob = "Brewfile" },
{ glob = "Guardfile" },
{ glob = "Capfile" },
{ glob = "Cheffile" },
{ glob = "Hobofile" },
{ glob = "Appraisals" },
{ glob = "Rantfile" },
{ glob = "Berksfile" },
{ glob = "Berksfile.lock" },
{ glob = "Thorfile" },
{ glob = "Puppetfile" },
{ glob = "Fastfile" },
{ glob = "Appfile" },
{ glob = "Deliverfile" },
{ glob = "Matchfile" },
{ glob = "Scanfile" },
{ glob = "Snapfile" },
{ glob = "Gymfile" },
]
shebangs = ["ruby"]
comment-token = "#"
@ -796,43 +845,43 @@ file-types = [
"sh",
"bash",
"zsh",
".bash_history",
".bash_login",
".bash_logout",
".bash_profile",
".bashrc",
".profile",
".zshenv",
"zshenv",
".zlogin",
"zlogin",
".zlogout",
"zlogout",
".zprofile",
"zprofile",
".zshrc",
"zshrc",
".zimrc",
"APKBUILD",
"PKGBUILD",
"eclass",
"ebuild",
"bazelrc",
".bash_aliases",
"Renviron",
".Renviron",
".xprofile",
".xsession",
".xsessionrc",
"zsh-theme",
"ksh",
"cshrc",
"tcshrc",
".yashrc",
".yash_profile",
".hushlogin",
"bashrc_Apple_Terminal",
"zshrc_Apple_Terminal"
"zshrc_Apple_Terminal",
{ glob = ".bash_history" },
{ glob = ".bash_login" },
{ glob = ".bash_logout" },
{ glob = ".bash_profile" },
{ glob = ".bashrc" },
{ glob = ".profile" },
{ glob = ".zshenv" },
{ glob = ".zlogin" },
{ glob = ".zlogout" },
{ glob = ".zprofile" },
{ glob = ".zshrc" },
{ glob = ".zimrc" },
{ glob = "APKBUILD" },
{ glob = "PKGBUILD" },
{ glob = ".bash_aliases" },
{ glob = ".Renviron" },
{ glob = ".xprofile" },
{ glob = ".xsession" },
{ glob = ".xsessionrc" },
{ glob = ".yashrc" },
{ glob = ".yash_profile" },
{ glob = ".hushlogin" },
]
shebangs = ["sh", "bash", "dash", "zsh"]
comment-token = "#"
@ -1135,7 +1184,7 @@ source = { git = "https://github.com/postsolar/tree-sitter-purescript", rev = "5
name = "zig"
scope = "source.zig"
injection-regex = "zig"
file-types = ["zig"]
file-types = ["zig", "zon"]
roots = ["build.zig"]
auto-format = true
comment-token = "//"
@ -1193,7 +1242,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-tsq", rev = "48b5
[[language]]
name = "cmake"
scope = "source.cmake"
file-types = ["cmake", "CMakeLists.txt"]
file-types = ["cmake", { glob = "CMakeLists.txt" }]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-servers = [ "cmake-language-server" ]
@ -1206,7 +1255,7 @@ source = { git = "https://github.com/uyha/tree-sitter-cmake", rev = "6e51463ef30
[[language]]
name = "make"
scope = "source.make"
file-types = ["Makefile", "makefile", "make", "mk", "mak", "GNUmakefile", "OCamlMakefile"]
file-types = [{ glob = "Makefile" }, { glob = "makefile" }, "make", "mk", "mak", {glob = "GNUmakefile" }, { glob = "OCamlMakefile" }]
shebangs = ["make", "gmake"]
injection-regex = "(make|makefile|Makefile|mk)"
comment-token = "#"
@ -1349,7 +1398,7 @@ source = { git = "https://github.com/Flakebi/tree-sitter-tablegen", rev = "568dd
name = "markdown"
scope = "source.md"
injection-regex = "md|markdown"
file-types = ["md", "markdown", "PULLREQ_EDITMSG", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook"]
file-types = ["md", "markdown", "mkd", "mdwn", "mdown", "markdn", "mdtxt", "mdtext", "workbook", { glob = "PULLREQ_EDITMSG" }]
roots = [".marksman.toml"]
language-servers = [ "marksman" ]
indent = { tab-width = 2, unit = " " }
@ -1401,7 +1450,7 @@ name = "dockerfile"
scope = "source.dockerfile"
injection-regex = "docker|dockerfile"
roots = ["Dockerfile", "Containerfile"]
file-types = ["Dockerfile", "dockerfile", "Containerfile", "containerfile"]
file-types = [{ glob = "Dockerfile*" }, { glob = "dockerfile*" }, { glob = "Containerfile*" }, { glob = "containerfile*" }]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
language-servers = [ "docker-langserver" ]
@ -1413,7 +1462,7 @@ source = { git = "https://github.com/camdencheek/tree-sitter-dockerfile", rev =
[[language]]
name = "git-commit"
scope = "git.commitmsg"
file-types = ["COMMIT_EDITMSG"]
file-types = [{ glob = "COMMIT_EDITMSG" }]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
rulers = [51, 73]
@ -1438,7 +1487,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-diff", rev = "fd7
[[language]]
name = "git-rebase"
scope = "source.gitrebase"
file-types = ["git-rebase-todo"]
file-types = [{ glob = "git-rebase-todo" }]
injection-regex = "git-rebase"
comment-token = "#"
indent = { tab-width = 2, unit = "y" }
@ -1451,7 +1500,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-git-rebase", rev
name = "regex"
scope = "source.regex"
injection-regex = "regex"
file-types = ["regex", ".Rbuildignore"]
file-types = ["regex", { glob = ".Rbuildignore" }]
[[grammar]]
name = "regex"
@ -1460,7 +1509,7 @@ source = { git = "https://github.com/tree-sitter/tree-sitter-regex", rev = "e1cf
[[language]]
name = "git-config"
scope = "source.gitconfig"
file-types = [".gitmodules", ".gitconfig", { suffix = ".git/config" }, { suffix = ".config/git/config" }]
file-types = [{ glob = ".gitmodules" }, { glob = ".gitconfig" }, { glob = ".git/config" }, { glob = ".config/git/config" }]
injection-regex = "git-config"
comment-token = "#"
indent = { tab-width = 4, unit = "\t" }
@ -1472,7 +1521,7 @@ source = { git = "https://github.com/the-mikedavis/tree-sitter-git-config", rev
[[language]]
name = "git-attributes"
scope = "source.gitattributes"
file-types = [".gitattributes"]
file-types = [{ glob = ".gitattributes" }]
injection-regex = "git-attributes"
comment-token = "#"
grammar = "gitattributes"
@ -1484,7 +1533,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-gitattributes", rev =
[[language]]
name = "git-ignore"
scope = "source.gitignore"
file-types = [".gitignore", ".gitignore_global", ".ignore", ".prettierignore", ".eslintignore", ".npmignore", "CODEOWNERS", { suffix = ".config/helix/ignore" }, { suffix = ".helix/ignore" }]
file-types = [{ glob = ".gitignore" }, { glob = ".gitignore_global" }, { glob = ".ignore" }, { glob = ".prettierignore" }, { glob = ".eslintignore" }, { glob = ".npmignore"}, { glob = "CODEOWNERS" }, { glob = ".config/helix/ignore" }, { glob = ".helix/ignore" }]
injection-regex = "git-ignore"
comment-token = "#"
grammar = "gitignore"
@ -1549,7 +1598,7 @@ source = { git = "https://github.com/jaredramirez/tree-sitter-rescript", rev = "
name = "erlang"
scope = "source.erlang"
injection-regex = "erl(ang)?"
file-types = ["erl", "hrl", "app", "rebar.config", "rebar.lock"]
file-types = ["erl", "hrl", "app", { glob = "rebar.config" }, { glob = "rebar.lock" }]
roots = ["rebar.config"]
shebangs = ["escript"]
comment-token = "%%"
@ -1675,7 +1724,7 @@ source = { git = "https://github.com/Hubro/tree-sitter-robot", rev = "322e4cc657
name = "r"
scope = "source.r"
injection-regex = "(r|R)"
file-types = ["r", "R", ".Rprofile", "Rprofile.site", ".RHistory"]
file-types = ["r", "R", { glob = ".Rprofile" }, { glob = "Rprofile.site" }, { glob = ".RHistory" }]
shebangs = ["r", "R"]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
@ -1707,7 +1756,7 @@ language-servers = [ "sourcekit-lsp" ]
[[grammar]]
name = "swift"
source = { git = "https://github.com/alex-pinkus/tree-sitter-swift", rev = "77c6312c8438f4dbaa0350cec92b3d6dd3d74a66" }
source = { git = "https://github.com/alex-pinkus/tree-sitter-swift", rev = "b1b66955d420d5cf5ff268ae552f0d6e43ff66e1" }
[[language]]
name = "erb"
@ -1809,7 +1858,7 @@ language-servers = [ "nu-lsp" ]
[[grammar]]
name = "nu"
source = { git = "https://github.com/nushell/tree-sitter-nu", rev = "98c11c491e3405c75affa1cf004097692da3dda2" }
source = { git = "https://github.com/nushell/tree-sitter-nu", rev = "358c4f509eb97f0148bbd25ad36acc729819b9c1" }
[[language]]
name = "vala"
@ -1874,13 +1923,14 @@ source = { git = "https://github.com/fvacek/tree-sitter-cpon", rev = "0d01fcdae5
[[language]]
name = "odin"
auto-format = false
auto-format = true
scope = "source.odin"
file-types = ["odin"]
roots = ["ols.json"]
language-servers = [ "ols" ]
comment-token = "//"
indent = { tab-width = 4, unit = "\t" }
formatter = { command = "odinfmt", args = [ "-stdin", "true" ] }
[[grammar]]
name = "odin"
@ -1890,7 +1940,7 @@ source = { git = "https://github.com/ap29600/tree-sitter-odin", rev = "b219207e4
name = "meson"
scope = "source.meson"
injection-regex = "meson"
file-types = ["meson.build", "meson_options.txt"]
file-types = [{ glob = "meson.build" }, { glob = "meson_options.txt" }]
comment-token = "#"
indent = { tab-width = 2, unit = " " }
@ -1901,7 +1951,7 @@ source = { git = "https://github.com/staysail/tree-sitter-meson", rev = "32a83e8
[[language]]
name = "sshclientconfig"
scope = "source.sshclientconfig"
file-types = [{ suffix = ".ssh/config" }, { suffix = "/etc/ssh/ssh_config" }]
file-types = [{ glob = ".ssh/config" }, { glob = "/etc/ssh/ssh_config" }]
comment-token = "#"
[[grammar]]
@ -2022,7 +2072,7 @@ source = { git = "https://github.com/sogaiu/tree-sitter-clojure", rev = "e57c569
name = "starlark"
scope = "source.starlark"
injection-regex = "(starlark|bzl|bazel)"
file-types = ["bzl", "bazel", "BUILD", "star"]
file-types = ["bzl", "bazel", "star", { glob = "BUILD" }, { glob = "BUILD.*" }]
comment-token = "#"
indent = { tab-width = 4, unit = " " }
grammar = "python"
@ -2116,7 +2166,7 @@ language-servers = [ "slint-lsp" ]
[[grammar]]
name = "slint"
source = { git = "https://github.com/jrmoulton/tree-sitter-slint", rev = "00c8a2d3645766f68c0d0460086c0a994e5b0d85" }
source = { git = "https://github.com/slint-ui/tree-sitter-slint", rev = "15618215b79b9db08f824a5c97a12d073dcc1c00" }
[[language]]
name = "task"
@ -2390,7 +2440,7 @@ source = { git = "https://github.com/hh9527/tree-sitter-wit", rev = "c917790ab9a
[[language]]
name = "env"
scope = "source.env"
file-types = [".env", ".env.local", ".env.development", ".env.production", ".env.dist", ".envrc", ".envrc.local", ".envrc.private"]
file-types = [{ glob = ".env" }, { glob = ".env.*" }, { glob = ".envrc" }, { glob = ".envrc.*" }]
injection-regex = "env"
comment-token = "#"
indent = { tab-width = 4, unit = "\t" }
@ -2418,7 +2468,7 @@ file-types = [
"volume",
"kube",
"network",
".editorconfig",
{ glob = ".editorconfig" },
"properties",
"cfg",
"directory"
@ -2546,7 +2596,7 @@ source = { git = "https://github.com/mtoohey31/tree-sitter-pem", rev = "be67a433
[[language]]
name = "passwd"
scope = "source.passwd"
file-types = ["passwd"]
file-types = [{ glob = "passwd" }]
[[grammar]]
name = "passwd"
@ -2555,7 +2605,7 @@ source = { git = "https://github.com/ath3/tree-sitter-passwd", rev = "20239395ea
[[language]]
name = "hosts"
scope = "source.hosts"
file-types = ["hosts"]
file-types = [{ glob = "hosts" }]
comment-token = "#"
[[grammar]]
@ -2763,10 +2813,12 @@ source = { git = "https://github.com/lefp/tree-sitter-opencl", rev = "8e1d24a570
[[language]]
name = "just"
scope = "source.just"
file-types = ["justfile", "Justfile", ".justfile", ".Justfile"]
file-types = [{ glob = "justfile" }, { glob = "Justfile" }, { glob = ".justfile" }, { glob = ".Justfile" }]
injection-regex = "just"
comment-token = "#"
indent = { tab-width = 4, unit = "\t" }
auto-format = true
formatter = { command = "just", args = ["--dump"] }
[[grammar]]
name = "just"
@ -2917,12 +2969,12 @@ indent = { tab-width = 4, unit = " " }
[[grammar]]
name = "unison"
source = { git = "https://github.com/kylegoetz/tree-sitter-unison", rev = "aaec316774c8b50d367ec7cf26523aac5ef0cfc5" }
source = { git = "https://github.com/kylegoetz/tree-sitter-unison", rev = "1f505e2447fa876a87aee47ff3d70b9e141c744f" }
[[language]]
name = "todotxt"
scope = "text.todotxt"
file-types = [{ suffix = ".todo.txt" }, "todotxt"]
file-types = [{ glob = "todo.txt" }, { glob = "*.todo.txt" }, "todotxt"]
formatter = { command = "sort" }
auto-format = true
@ -3009,13 +3061,16 @@ name = "log"
source = { git = "https://github.com/Tudyx/tree-sitter-log", rev = "62cfe307e942af3417171243b599cc7deac5eab9" }
[[language]]
name = "janet"
scope = "source.janet"
injection-regex = "janet"
file-types = ["janet"]
comment-token = "#"
name = "hoon"
scope = "source.hoon"
injection-regex = "hoon"
file-types = ["hoon"]
comment-token = "::"
indent = {tab-width = 2, unit = " "}
grammar = "clojure"
[[grammar]]
name = "hoon"
source = { git = "https://github.com/urbit-pilled/tree-sitter-hoon", rev = "1d5df35af3e0afe592832a67b9fb3feeeba1f7b6" }
[[language]]
name = "hocon"
@ -3028,3 +3083,33 @@ indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "hocon"
source = { git = "https://github.com/antosha417/tree-sitter-hocon", rev = "c390f10519ae69fdb03b3e5764f5592fb6924bcc" }
[[language]]
name = "tact"
scope = "source.tact"
injection-regex = "tact"
file-types = ["tact"]
comment-token = "//"
indent = { tab-width = 4, unit = " " }
[language.auto-pairs]
'"' = '"'
'{' = '}'
'(' = ')'
'<' = '>'
[[grammar]]
name = "tact"
source = { git = "https://github.com/tact-lang/tree-sitter-tact", rev = "ec57ab29c86d632639726631fb2bb178d23e1c91" }
[[language]]
name = "pkl"
scope = "source.pkl"
injection-regex = "pkl"
file-types = ["pkl", "pcf"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "pkl"
source = { git = "https://github.com/apple/tree-sitter-pkl", rev = "c03f04a313b712f8ab00a2d862c10b37318699ae" }

@ -0,0 +1,66 @@
; Operators
[
"-"
"!"
"*"
"/"
"&&"
"%"
"+"
"<"
"<="
"=="
">"
">="
"||"
] @operator
; Keywords
[
"in"
] @keyword
; Function calls
(call_expression
function: (identifier) @function)
(member_call_expression
function: (identifier) @function)
; Identifiers
(select_expression
operand: (identifier) @type)
(select_expression
operand: (select_expression
member: (identifier) @type))
(identifier) @variable.other.member
; Literals
[
(double_quote_string_literal)
(single_quoted_string_literal)
(triple_double_quote_string_literal)
(triple_single_quoted_string_literal)
] @string
[
(int_literal)
(uint_literal)
] @constant.numeric.integer
(float_literal) @constant.numeric.float
[
(true)
(false)
] @constant.builtin.boolean
(null) @constant.builtin
(comment) @comment

@ -13,5 +13,7 @@
(typed_default_parameter)
] @parameter.inside @parameter.around)
(arguments (_expression) @parameter.inside @parameter.around)
(comment) @comment.inside
(comment)+ @comment.around

@ -183,9 +183,12 @@
[
(int_literal)
] @constant.numeric.integer
[
(float_literal)
(imaginary_literal)
] @constant.numeric.integer
] @constant.numeric.float
[
(true)
@ -197,4 +200,31 @@
(iota)
] @constant.builtin
; Comments
(comment) @comment
; Doc Comments
(source_file
.
(comment)+ @comment.block.documentation)
(source_file
(comment)+ @comment.block.documentation
.
(const_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(function_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(type_declaration))
(source_file
(comment)+ @comment.block.documentation
.
(var_declaration))

@ -1,2 +1,14 @@
((comment) @injection.content
(#set! injection.language "comment"))
(call_expression
(selector_expression) @_function
(#any-of? @_function "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" "regexp.MustCompile" "regexp.MustCompilePOSIX")
(argument_list
.
[
(raw_string_literal)
(interpreted_string_literal)
] @injection.content
(#set! injection.language "regex")))

@ -0,0 +1,32 @@
(number) @constant.numeric
(string) @string
[
"("
")"
"["
"]"
] @punctuation.bracket
[
(coreTerminator)
(seriesTerminator)
] @punctuation.delimiter
(rune) @keyword
(term) @constant
(aura) @constant.builtin
(Gap) @comment
(boolean) @constant.builtin
(date) @constant.builtin
(mold) @constant.builtin
(specialIndex) @constant.builtin
(lark) @operator
(fullContext) @constant.builtin

File diff suppressed because one or more lines are too long

@ -2,7 +2,6 @@
;;; keywords
[
"def"
"def-env"
"alias"
"export-env"
"export"
@ -73,7 +72,6 @@
"tb" "tB" "Tb" "TB"
"pb" "pB" "Pb" "PB"
"eb" "eB" "Eb" "EB"
"zb" "zB" "Zb" "ZB"
"kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
"mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
@ -81,7 +79,6 @@
"tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
"pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
"eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
"zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
] @variable.parameter
)
(val_binary

@ -0,0 +1,179 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
; Operators
[
"??"
"@"
"="
"<"
">"
"!"
"=="
"!="
"<="
">="
"&&"
"||"
"+"
"-"
"**"
"*"
"/"
"~/"
"%"
"|>"
] @keyword.operator
[
"?"
"|"
"->"
] @operator.type
[
","
":"
"."
"?."
] @punctuation.delimiter
[
"("
")"
"]"
"{"
"}"
; "[" @punctuation.bracket TODO: FIGURE OUT HOW TO REFER TO CUSTOM TOKENS
] @punctuation.bracket
; Keywords
[
"abstract"
"amends"
"as"
"class"
"extends"
"external"
"function"
"hidden"
"import"
"import*"
"in"
"let"
"local"
"module"
"new"
"open"
"out"
"typealias"
"when"
] @keyword
[
"if"
"is"
"else"
] @keyword.control.conditional
[
"for"
] @keyword.control.repeat
(importExpr "import" @keyword.control.import)
(importGlobExpr "import*" @keyword.control.import)
"read" @function.builtin
"read?" @function.builtin
"read*" @function.builtin
"throw" @function.builtin
"trace" @function.builtin
(moduleExpr "module" @type.builtin)
"nothing" @type.builtin
"unknown" @type.builtin
(outerExpr) @variable.builtin
"super" @variable.builtin
(thisExpr) @variable.builtin
[
(falseLiteral)
(nullLiteral)
(trueLiteral)
] @constant.builtin
; Literals
(stringConstant) @string
(slStringLiteral) @string
(mlStringLiteral) @string
(escapeSequence) @constent.character.escape
(intLiteral) @constant.numeric.integer
(floatLiteral) @constant.numeric.float
(interpolationExpr
"\\(" @punctuation.special
")" @punctuation.special) @embedded
(interpolationExpr
"\\#(" @punctuation.special
")" @punctuation.special) @embedded
(interpolationExpr
"\\##(" @punctuation.special
")" @punctuation.special) @embedded
(lineComment) @comment
(blockComment) @comment
(docComment) @comment
; Identifiers
(classProperty (identifier) @variable.other.member)
(objectProperty (identifier) @variable.other.member)
(parameterList (typedIdentifier (identifier) @variable.parameter))
(objectBodyParameters (typedIdentifier (identifier) @variable.parameter))
(identifier) @variable
; Method definitions
(classMethod (methodHeader (identifier)) @function.method)
(objectMethod (methodHeader (identifier)) @function.method)
; Method calls
(methodCallExpr
(identifier) @function.method)
; Types
(clazz (identifier) @type)
(typeAlias (identifier) @type)
((identifier) @type
(match? @type "^[A-Z]"))
(typeArgumentList
"<" @punctuation.bracket
">" @punctuation.bracket)

@ -0,0 +1,23 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
[
(objectBody)
(classBody)
(ifExpr)
(mlStringLiteral) ; This isn't perfect; newlines are too indented but it's better than if omitted.
] @indent

@ -0,0 +1,30 @@
; Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
; https://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
; this definition is imprecise in that
; * any qualified or unqualified call to a method named "Regex" is considered a regex
; * string delimiters are considered part of the regex
(
((methodCallExpr (identifier) @methodName (argumentList (slStringLiteral) @injection.content))
(#set! injection.language "regex"))
(eq? @methodName "Regex"))
((lineComment) @injection.content
(#set! injection.language "comment"))
((blockComment) @injection.content
(#set! injection.language "comment"))
((docComment) @injection.content
(#set! injection.language "markdown"))

@ -2,11 +2,29 @@
(#set! injection.language "comment"))
((macro_invocation
macro: (identifier) @_html (#eq? @_html "html")
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree) @injection.content)
(#eq? @_macro_name "html")
(#set! injection.language "html")
(#set! injection.include-children))
((macro_invocation
macro:
[
(scoped_identifier
name: (_) @_macro_name)
(identifier) @_macro_name
]
(token_tree) @injection.content)
(#eq? @_macro_name "slint")
(#set! injection.language "slint")
(#set! injection.include-children))
((macro_invocation
(token_tree) @injection.content)
(#set! injection.language "rust")

@ -53,20 +53,13 @@
(var_declaration
name: (identifier) @variable)
; method definition
; function definitions/declarations
(function_declaration
name: (identifier) @function.method)
(class_definition
body: (template_body
(function_definition
name: (identifier) @function.method)))
(object_definition
body: (template_body
(function_definition
name: (identifier) @function.method)))
(trait_definition
body: (template_body
(function_definition
name: (identifier) @function.method)))
name: (identifier) @function.method)
; imports/exports

@ -1,2 +1,16 @@
((comment) @injection.content
([(comment) (block_comment)] @injection.content
(#set! injection.language "comment"))
; TODO for some reason multiline string (triple quotes) interpolation works only if it contains interpolated value
; Matches these SQL interpolators:
; - Doobie: 'sql', 'fr'
; - Quill: 'sql', 'infix'
; - Slick: 'sql', 'sqlu'
(interpolated_string_expression
interpolator:
((identifier) @interpolator
(#any-of? @interpolator "fr" "infix" "sql" "sqlu"))
(interpolated_string) @injection.content
(#set! injection.language "sql"))

@ -1,12 +1,15 @@
; Function queries
(function_definition
body: (_) @function.inside) @function.around
body: (_) @function.inside) @function.around ; Does not include end marker
; Does not match block lambdas or Scala 3 braceless lambdas
(lambda_expression
(_) @function.inside) @function.around
; Scala 3 braceless lambda
(colon_argument
(_) @function.inside) @function.around
; Class queries
@ -32,6 +35,9 @@
(parameters
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(class_parameters
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(parameter_types
((_) @parameter.inside . ","? @parameter.around) @parameter.around)

@ -1,122 +1,109 @@
(comment) @comment
; Different types:
(string_value) @string
(bool_value) @constant.builtin.boolean
; Constants
(escape_sequence) @constant.character.escape
(color_value) @constant
(identifier) @variable
[
(type_identifier)
(units)
]@type
(children_identifier)
(easing_kind_identifier)
] @constant.builtin
(array_literal
(identifier) @type)
[
(int_value)
(physical_length_value)
] @constant.numeric.integer
(function_identifier) @function
[
(image_macro)
(children_macro)
(radial_grad_macro)
(linear_grad_macro)
] @function.macro
(float_value)
(percent_value)
(length_value)
(duration_value)
(angle_value)
(relative_font_size_value)
] @constant.numeric.float
(call_expression
function: (identifier) @function)
(call_expression
function: (field_expression
field: (identifier) @function))
(purity) @keyword.storage.modifier
(vis) @keyword.control.import
(function_visibility) @keyword.storage.modifier
(transition_statement state: (identifier) @variable.other.member)
(state_expression state: (identifier) @variable.other.member)
(struct_block_definition field: (identifier) @variable.other.member)
(assign_property (identifier) @attribute)
(property_visibility) @keyword.storage.modifier
(comment) @comment
(builtin_type_identifier) @type.builtin
(string_literal) @string
(int_literal) @constant.numeric.integer
(float_literal) @constant.numeric.float
(reference_identifier) @variable.builtin
(type
[
"in"
"in-out"
"for"
] @keyword.control.repeat
(type_list)
(user_type_identifier)
(anon_struct_block)
]) @type
[
"import"
"export"
"from"
] @keyword.control.import
(user_type_identifier) @type
[
"if"
"else"
"when"
] @keyword.control.conditional
; Functions and callbacks
(argument) @variable.parameter
[
"struct"
"property"
] @keyword.storage.type
(function_call
name: (_) @function.call)
[
"global"
] @keyword.storage.modifier
; definitions
(callback
name: (_) @function)
(callback_alias
name: (_) @function)
[
"root"
"parent"
"duration"
"easing"
] @variable.builtin
(callback_event
name: (simple_identifier) @function.call)
(enum_definition
name: (_) @type.enum)
[
"callback"
"animate"
"states"
"out"
"transitions"
"component"
"inherits"
] @keyword
[
"black"
"transparent"
"blue"
"ease"
"ease_in"
"ease-in"
"ease_in_out"
"ease-in-out"
"ease_out"
"ease-out"
"end"
"green"
"red"
"start"
"yellow"
"white"
"gray"
] @constant.builtin
(function_definition
name: (_) @function)
(struct_definition
name: (_) @type)
(typed_identifier
type: (_) @type)
; Operators
(binary_expression
op: (_) @operator)
(unary_expression
op: (_) @operator)
[
"true"
"false"
] @constant.builtin.boolean
(comparison_operator)
(mult_prec_operator)
(add_prec_operator)
(unary_prec_operator)
(assignment_prec_operator)
] @operator
"@" @keyword
[
":="
"=>"
"->"
"<=>"
] @operator
; ; Punctuation
[
","
"."
";"
":"
"."
","
] @punctuation.delimiter
; ; Brackets
[
"("
")"
@ -126,46 +113,136 @@
"}"
] @punctuation.bracket
(define_property ["<" ">"] @punctuation.bracket)
(property
[
"<"
">"
] @punctuation.bracket)
; Properties, constants and variables
(component
id: (simple_identifier) @constant)
(property
name: (simple_identifier) @variable)
(binding_alias
name: (simple_identifier) @variable)
(binding
name: (simple_identifier) @variable)
(struct_block
(simple_identifier) @variable.other.member)
(anon_struct_block
(simple_identifier) @variable.other.member)
(property_assignment
property: (simple_identifier) @variable)
(states_definition
name: (simple_identifier) @variable)
(callback
name: (simple_identifier) @variable)
(typed_identifier
name: (_) @variable)
(simple_indexed_identifier
(simple_identifier) @variable)
(expression
(simple_identifier) @variable)
; Attributes
[
"angle"
"bool"
"brush"
"color"
"duration"
"easing"
"float"
"image"
"int"
"length"
"percent"
"physical-length"
"physical_length"
"string"
] @type.builtin
(linear_gradient_identifier)
(radial_gradient_identifier)
(radial_gradient_kind)
] @attribute
(image_call
"@image-url" @attribute)
(tr
"@tr" @attribute)
; Keywords
(animate_option_identifier) @keyword
(export) @keyword.control.import
(if_statement
"if" @keyword.control.conditional)
(if_expr
[
"if"
"else"
] @keyword.control.conditional)
(ternary_expression
[
":="
"<=>"
"!"
"-"
"+"
"*"
"/"
"&&"
"||"
">"
"<"
">="
"<="
"="
":"
"+="
"-="
"*="
"/="
"?"
"=>" ] @operator
":"
] @keyword.control.conditional)
(animate_statement
"animate" @keyword)
(callback
"callback" @keyword.function)
(ternary_expression [":" "?"] @keyword.control.conditional)
(component_definition
[
"component"
"inherits"
] @keyword.storage.type)
(enum_definition
"enum" @keyword.storage.type)
(for_loop
[
"for"
"in"
] @keyword.control.repeat)
(function_definition
"function" @keyword.function)
(global_definition
"global" @keyword.storage.type)
(imperative_block
"return" @keyword.control.return)
(import_statement
[
"import"
"from"
] @keyword.control.import)
(import_type
"as" @keyword.control.import)
(property
"property" @keyword.storage.type)
(states_definition
[
"states"
"when"
] @keyword)
(struct_definition
"struct" @keyword.storage.type)
(transitions_definition
[
"transitions"
"in"
"out"
] @keyword)

@ -1,12 +1,11 @@
[
(comp_body)
(state_statement)
(transition_statement)
(handler_body)
(consequence_body)
(global_single)
(anon_struct_block)
(assignment_block)
(block)
(enum_block)
(global_block)
(imperative_block)
(struct_block)
] @indent
[
"}"
] @outdent
"}" @outdent

@ -1,3 +1,6 @@
; locals.scm
(component_item) @local.scope
[
(component)
(component_definition)
(function_definition)
(imperative_block)
] @local.scope

@ -0,0 +1,35 @@
(function_definition
(imperative_block) @funtion.inside) @function.around
(callback_event
(imperative_block) @function.inside) @function.around
(property
(imperative_block) @function.inside) @function.around
(struct_definition
(struct_block) @class.inside) @class.around
(enum_definition
(enum_block) @class.inside) @class.around
(global_definition
(global_block) @class.inside) @class.around
(component_definition
(block) @class.inside) @class.around
(component_definition
(block) @class.inside) @class.around
(comment) @comment.around
(typed_identifier
name: (_) @parameter.inside) @parameter.around
(callback
arguments: (_) @parameter.inside)
(string_value
"\"" . (_) @text.inside . "\"") @text.around

@ -0,0 +1,47 @@
; highlights.scm
[
"definition"
"caveat"
"permission"
"relation"
"nil"
] @keyword
[
","
":"
] @punctuation.delimiter
[
"("
")"
"{"
"}"
] @punctuation.bracket
[
"|"
"+"
"-"
"&"
"#"
"->"
"="
] @operator
("with") @keyword.operator
[
"nil"
"*"
] @constant.builtin
(comment) @comment
(type_identifier) @type
(cel_type_identifier) @type
(cel_variable_identifier) @variable.parameter
(field_identifier) @variable.other.member
[
(func_identifier)
(method_identifier)
] @function.method

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

@ -0,0 +1,4 @@
(object_definition
name: (type_identifier) @name) @definition.type
(type_identifier) @name @reference.type

@ -1,10 +1,10 @@
; Upstream: https://github.com/alex-pinkus/tree-sitter-swift/blob/8d2fd80e3322df51e3f70952e60d57f5d4077eb8/queries/highlights.scm
; Upstream: https://github.com/alex-pinkus/tree-sitter-swift/blob/1c586339fb00014b23d6933f2cc32b588a226f3b/queries/highlights.scm
(line_string_literal
["\\(" ")"] @punctuation.special)
["." ";" ":" "," ] @punctuation.delimiter
["(" ")" "[" "]" "{" "}"] @punctuation.bracket ; TODO: "\\(" ")" in interpolations should be @punctuation.special
["(" ")" "[" "]" "{" "}"] @punctuation.bracket
; Identifiers
(attribute) @variable
@ -26,6 +26,7 @@
(function_declaration "init" @constructor)
(throws) @keyword
"async" @keyword
"await" @keyword
(where_keyword) @keyword
(parameter external_name: (simple_identifier) @variable.parameter)
(parameter name: (simple_identifier) @variable.parameter)
@ -48,6 +49,7 @@
"convenience"
"required"
"some"
"any"
] @keyword
[

@ -0,0 +1,298 @@
; See: https://docs.helix-editor.com/master/themes.html#syntax-highlighting
; -------------------------------------------------------------------------
; attribute
; ---------
[
"@name"
"@interface"
] @attribute
; comment.line
; ------------
((comment) @comment.line
(#match? @comment.line "^//"))
; comment.block
; -------------
(comment) @comment.block
; function.builtin
; ----------------
((identifier) @function.builtin
(#any-of? @function.builtin
"send" "sender" "require" "now"
"myBalance" "myAddress" "newAddress"
"contractAddress" "contractAddressExt"
"emit" "cell" "ton"
"beginString" "beginComment" "beginTailString" "beginStringFromBuilder" "beginCell" "emptyCell"
"randomInt" "random"
"checkSignature" "checkDataSignature" "sha256"
"min" "max" "abs" "pow"
"throw" "dump" "getConfigParam"
"nativeThrowWhen" "nativeThrowUnless" "nativeReserve"
"nativeRandomize" "nativeRandomizeLt" "nativePrepareRandom" "nativeRandom" "nativeRandomInterval")
(#is-not? local))
; function.method
; ---------------
(method_call_expression
name: (identifier) @function.method)
; function
; --------
(func_identifier) @function
(native_function
name: (identifier) @function)
(static_function
name: (identifier) @function)
(static_call_expression
name: (identifier) @function)
(init_function
"init" @function.method)
(receive_function
"receive" @function.method)
(bounced_function
"bounced" @function.method)
(external_function
"external" @function.method)
(function
name: (identifier) @function.method)
; keyword.control.conditional
; ---------------------------
[
"if" "else"
] @keyword.control.conditional
; keyword.control.repeat
; ----------------------
[
"while" "repeat" "do" "until"
] @keyword.control.repeat
; keyword.control.import
; ----------------------
"import" @keyword.control.import
; keyword.control.return
; ----------------------
"return" @keyword.control.return
; keyword.operator
; ----------------
"initOf" @keyword.operator
; keyword.directive
; -----------------
"primitive" @keyword.directive
; keyword.function
; ----------------
[
"fun"
"native"
] @keyword.function
; keyword.storage.type
; --------------------
[
"contract" "trait" "struct" "message" "with"
"const" "let"
] @keyword.storage.type
; keyword.storage.modifier
; ------------------------
[
"get" "mutates" "extends" "virtual" "override" "inline" "abstract"
] @keyword.storage.modifier
; keyword
; -------
[
"with"
; "public" ; -- not used, but declared in grammar.ohm
; "extend" ; -- not used, but declared in grammar.ohm
] @keyword
; constant.builtin.boolean
; ------------------------
(boolean) @constant.builtin.boolean
; constant.builtin
; ----------------
((identifier) @constant.builtin
(#any-of? @constant.builtin
"SendPayGasSeparately"
"SendIgnoreErrors"
"SendDestroyIfZero"
"SendRemainingValue"
"SendRemainingBalance")
(#is-not? local))
(null) @constant.builtin
; constant.numeric.integer
; ------------------------
(integer) @constant.numeric.integer
; constant
; --------
(constant
name: (identifier) @constant)
; string.special.path
; -------------------
(import_statement
library: (string) @string.special.path)
; string
; ------
(string) @string
; type.builtin
; ------------
(tlb_serialization
"as" @keyword
type: (identifier) @type.builtin
(#any-of? @type.builtin
"int8" "int16" "int32" "int64" "int128" "int256" "int257"
"uint8" "uint16" "uint32" "uint64" "uint128" "uint256"
"coins" "remaining" "bytes32" "bytes64"))
((type_identifier) @type.builtin
(#any-of? @type.builtin
"Address" "Bool" "Builder" "Cell" "Int" "Slice" "String" "StringBuilder"))
(map_type
"map" @type.builtin
"<" @punctuation.bracket
">" @punctuation.bracket)
(bounced_type
"bounced" @type.builtin
"<" @punctuation.bracket
">" @punctuation.bracket)
((identifier) @type.builtin
(#eq? @type.builtin "SendParameters")
(#is-not? local))
; type
; ----
(type_identifier) @type
; constructor
; -----------
(instance_expression
name: (identifier) @constructor)
(initOf
name: (identifier) @constructor)
; operator
; --------
[
"-" "-="
"+" "+="
"*" "*="
"/" "/="
"%" "%="
"=" "=="
"!" "!=" "!!"
"<" "<=" "<<"
">" ">=" ">>"
"&" "|"
"&&" "||"
] @operator
; punctuation.bracket
; -------------------
[
"(" ")"
"{" "}"
] @punctuation.bracket
; punctuation.delimiter
; ---------------------
[
";"
","
"."
":"
"?"
] @punctuation.delimiter
; variable.other.member
; ---------------------
(field
name: (identifier) @variable.other.member)
(contract_body
(constant
name: (identifier) @variable.other.member))
(trait_body
(constant
name: (identifier) @variable.other.member))
(field_access_expression
name: (identifier) @variable.other.member)
(lvalue (_) (_) @variable.other.member)
(instance_argument
name: (identifier) @variable.other.member)
; variable.parameter
; ------------------
(parameter
name: (identifier) @variable.parameter)
; variable.builtin
; ----------------
(self) @variable.builtin
; variable
; --------
(identifier) @variable

@ -0,0 +1,38 @@
; indent
; ------
[
; (..., ...)
(parameter_list)
(argument_list)
; {..., ...}
(instance_argument_list)
; {...; ...}
(message_body)
(struct_body)
(contract_body)
(trait_body)
(function_body)
(block_statement)
; misc.
(binary_expression)
(return_statement)
] @indent
; outdent
; -------
[
"}"
")"
">"
] @outdent
; indent.always
; outdent.always
; align
; extend
; extend.prevent-once

@ -0,0 +1,5 @@
; See: https://docs.helix-editor.com/guides/injection.html
((comment) @injection.content
(#set! injection.language "comment")
(#match? @injection.content "^//"))

@ -0,0 +1,35 @@
; See: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#local-variables
; Scopes @local.scope
; -------------------------
[
(static_function)
(init_function)
(bounced_function)
(receive_function)
(external_function)
(function)
(block_statement)
] @local.scope
; Definitions @local.definition
; ------------------------------
(let_statement
name: (identifier) @local.definition)
(parameter
name: (identifier) @local.definition)
(constant
name: (identifier) @local.definition)
; References @local.reference
; -----------------------------
(self) @local.reference
(value_expression (identifier) @local.reference)
(lvalue (identifier) @local.reference)

@ -0,0 +1,58 @@
; function.inside & around
; ------------------------
(static_function
body: (_) @function.inside) @function.around
(init_function
body: (_) @function.inside) @function.around
(bounced_function
body: (_) @function.inside) @function.around
(receive_function
body: (_) @function.inside) @function.around
(external_function
body: (_) @function.inside) @function.around
(function
body: (_) @function.inside) @function.around
; class.inside & around
; ---------------------
(struct
body: (_) @class.inside) @class.around
(message
body: (_) @class.inside) @class.around
(contract
body: (_) @class.inside) @class.around
; NOTE: Marked as @definition.interface in tags, as it's semantically correct
(trait
body: (_) @class.inside) @class.around
; parameter.inside & around
; -------------------------
(parameter_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(argument_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
(instance_argument_list
((_) @parameter.inside . ","? @parameter.around) @parameter.around)
; comment.inside
; --------------
(comment) @comment.inside
; comment.around
; --------------
(comment)+ @comment.around

@ -9,8 +9,6 @@
;; Keywords
[
(kw_forall)
(unique_kw)
(structural_kw)
(type_kw)
(kw_equals)
(do)
@ -51,7 +49,7 @@
(blank_pattern) @variable.builtin
;; Types
(record_field name: (wordy_id) @variable.other.member type: (wordy_id) @type)
(record_field name: (wordy_id) @variable.other.member type: (_) @type)
(type_constructor (type_name (wordy_id) @constructor))
(ability_declaration type_name: (wordy_id) @type type_arg: (wordy_id) @variable.parameter)
(effect (wordy_id) @special) ;; NOTE: an effect is just like a type, but in signature we special case it

@ -0,0 +1,15 @@
[
(term_definition)
(type_declaration)
(pattern)
(tuple_or_parenthesized)
(literal_list)
(tuple_pattern)
(function_application)
(exp_if)
(constructor)
(delay_block)
(type_signature)
] @indent
[(kw_then) (kw_else) (cases)] @indent.always @extend

@ -0,0 +1,118 @@
attribute_color = "attribute_color"
keyword = "keyword_foreground_color"
"keyword.directive" = "light_blue"
namespace = "light_blue"
punctuation = "punctuation_color"
"punctuation.delimiter" = "punctuation_color"
operator = "operator_color"
special = "label"
"variable.other.member" = "white"
variable = "variable"
"variable.parameter" = { fg = "variable" }
"variable.builtin" = {fg = "built_in", modifiers=["bold","italic"]}
type = "white"
"type.builtin" = "white"
constructor = "light_blue"
function = "white"
"function.macro" = {fg ="light_blue" }
"function.builtin" = "white"
tag = "tag"
comment = { fg = "comment_color", modifiers = ["italic"] }
constant = {fg ="white"}
"constant.builtin" = "white"
string = {fg="string", modifiers=["italic"]}
"constant.numeric" = "constant_numeric_foreground_color"
"constant.character.escape" = "label"
# used for lifetimes
label = "label"
"markup.heading" = "light_blue"
"markup.bold" = { modifiers = ["bold"] }
"markup.italic" = { modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "link_url_foreground_color", modifiers = ["underlined"] }
"markup.link.text" = "markup_link_foreground_color"
"markup.raw" = "markup_raw_foreground_color"
"diff.plus" = "#35bf86"
"diff.minus" = "#f22c86"
"diff.delta" = "info"
"ui.background" = { bg = "black" }
"ui.background.separator" = { fg = "window_color" }
"ui.linenr" = { fg = "window_color" }
"ui.linenr.selected" = { fg = "light_blue" }
"ui.statusline" = { fg = "statusline_foreground_color", bg = "black" }
"ui.statusline.inactive" = { fg = "statusline_inactive_foreground_color", bg = "black" }
"ui.virtual.ruler" = { bg = "dark"}
"ui.popup" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.window" = { fg = "dark"}
"ui.help" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.text" = { fg = "text" }
"ui.text.focus" = { fg = "white" }
"ui.text.inactive" = "comment_color"
"ui.virtual" = { fg = "#008DFF" }
"ui.virtual.indent-guide" = { fg = "window_color" }
"ui.selection" = { bg = "#4f46e5" }
"ui.selection.primary" = { bg = "#4f46e5" }
"ui.cursor.select" = { bg = "cursor_normal_bg_color" }
"ui.cursor.primary.insert" = { bg = "#f43f5e", fg = "white" }
"ui.cursor.match" = { fg = "#212121", bg = "#6C6999" }
"ui.cursorline.primary" = { bg = "dark"}
"ui.highlight" = { bg = "dark" }
"ui.highlight.frameline" = { bg = "#634450" }
"ui.debug" = { fg = "#634450" }
"ui.debug.breakpoint" = { fg = "debug_breakpoint" }
"ui.menu" = { fg = "menu_normal_text_color", bg = "menu_background_color" }
"ui.menu.selected" = { fg = "menu_background_color", bg = "white" }
"ui.menu.scroll" = { fg = "menu_scroll", bg = "window_color" }
"diagnostic.hint" = { underline = { color = "hint", style = "curl" } }
"diagnostic.info" = { underline = { color = "info", style = "curl" } }
"diagnostic.warning" = { underline = { color = "warning", style = "curl" } }
"diagnostic.error" = { underline = { color = "error", style = "curl" } }
warning = "warning"
error = "#f43f5e"
info = "info"
hint = "#38bdf8"
[palette]
label = "#efba5d"
constant_numeric_foreground_color = "#E8DCA0"
tag = "#eccdba"
markup_link_foreground_color = "#eccdba"
markup_raw_foreground_color = "#eccdba"
keyword_foreground_color="#eccdba" # alternative color "#ecc1ba"
comment_color = "#697C81"
link_url_foreground_color="#b8b8b8"
debug_breakpoint = "#f47868"
window_color = "#484a4d"
light_blue = "#bee0ec" #change name
text="#bfdbfe"
black = "#000000"
white = "#ffffff"
dark= "#111111"
punctuation_color = "#a4a0e8"
string="#6ee7b7"
attribute_color="#dbbfef"
operator_color="#bee0ec"
menu_background_color="#1e3a8a"
menu_normal_text_color="#93c5fd"
statusline_active_background_color="#111111"
statusline_inactive_background_color="#0e0e0e"
statusline_inactive_foreground_color="#b8b8b8"
popup_background_color="#1e3a8a"
cursor_normal_bg_color="#6366f1"
warning="#ffcd1c"
error = "#f43f5e"
hint = "#38bdf8"
info = "#6366f1"
variable="#c7d2fe"
menu_scroll="#93c5fd"
built_in="#10b981"
statusline_foreground_color="#6366f1"

@ -95,7 +95,8 @@
"ui.text" = "shade05"
"ui.text.focus" = { fg = "shade07", bg = "light_blue" }
"ui.virtual" = "shade03"
"ui.virtual.ruler" = { bg = "shade04" }
"ui.virtual.ruler" = { bg = "shade01" }
"ui.virtual.inlay-hint" = { fg = "shade03_darker" }
"ui.menu" = { fg = "shade05", bg = "shade01" }
"ui.menu.selected" = { fg = "shade07", bg = "light_blue" }
@ -119,6 +120,9 @@ shade05 = "#434b6c"
shade06 = "#343a54"
shade07 = "#25293c"
shade03_darker = "#9199bb"
shade04_lighter = "#616d9d"
background = "#f2f3f7"
foreground = "#25293c"
@ -133,7 +137,6 @@ blue = "#0073E6"
dark_blue = "#185b93"
darker_blue = "#000080"
purple = "#660E7A"
light_purple = "#ED9CFF"
@ -142,7 +145,6 @@ green = "#00733B"
light_green = "#5DCE87"
green_blue = "#458383"
yellow = "#808000"
dark_yellow = "#7A7A43"

@ -2,134 +2,153 @@
# Author : Chirikumbrah
"annotation" = { fg = "foreground" }
"attribute" = { fg = "green", modifiers = ["italic"] }
"comment" = { fg = "comment" }
"comment.block.documentation" = { fg = "comment" }
"comment.block" = { fg = "comment" }
"comment.block.documentation" = { fg = "comment" }
"comment.line" = { fg = "comment" }
"constant" = { fg = "purple" }
"constant.numeric" = { fg = "purple" }
"constant.builtin" = { fg = "purple" }
"constant.builtin.boolean" = { fg = "purple" }
"constant.character" = { fg = "cyan" }
"constant.character.escape" = { fg = "pink" }
"constant.macro" = { fg = "purple" }
"constant.numeric" = { fg = "purple" }
"constructor" = { fg = "purple" }
"definition" = { underline = { color = "cyan" } }
"diagnostic" = { underline = { color = "orange", style = "curl" } }
"diagnostic.hint" = { underline = { color = "purple", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
"error" = { fg = "red" }
"hint" = { fg = "purple" }
"info" = { fg = "cyan" }
"warning" = { fg = "yellow" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
"diff.plus" = { fg = "green" }
# d
"function" = { fg = "green" }
"function.builtin" = { fg = "green" }
"function.method" = { fg = "green" }
"function.macro" = { fg = "purple" }
"function.call" = { fg = "green" }
"function.macro" = { fg = "purple" }
"function.method" = { fg = "green" }
"keyword" = { fg = "pink" }
"keyword.operator" = { fg = "pink" }
"keyword.function" = { fg = "pink" }
"keyword.return" = { fg = "pink" }
"keyword.control.import" = { fg = "pink" }
"keyword.directive" = { fg = "green" }
"keyword.control.repeat" = { fg = "pink" }
"keyword.control.conditional" = { fg = "pink" }
"keyword.control.exception" = { fg = "purple" }
"keyword.control.import" = { fg = "pink" }
"keyword.control.repeat" = { fg = "pink" }
"keyword.directive" = { fg = "green" }
"keyword.function" = { fg = "pink" }
"keyword.operator" = { fg = "pink" }
"keyword.return" = { fg = "pink" }
"keyword.storage" = { fg = "pink" }
"keyword.storage.type" = { fg = "cyan", modifiers = ["italic"] }
"keyword.storage.modifier" = { fg = "pink" }
"tag" = { fg = "pink" }
"tag.attribute" = { fg = "purple" }
"tag.delimiter" = { fg = "foreground" }
"keyword.storage.type" = { fg = "cyan", modifiers = ["italic"] }
"label" = { fg = "cyan" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.link.text" = { fg = "pink" }
"markup.link.url" = { fg = "cyan" }
"markup.list" = { fg = "cyan" }
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
"markup.raw" = { fg = "foreground" }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"punctuation" = { fg = "foreground" }
"punctuation.bracket" = { fg = "foreground" }
"punctuation.delimiter" = { fg = "foreground" }
"punctuation.special" = { fg = "pink" }
"special" = { fg = "pink" }
"string" = { fg = "yellow" }
"string.regexp" = { fg = "red" }
"string.special" = { fg = "orange" }
"string.symbol" = { fg = "yellow" }
"string.regexp" = { fg = "red" }
"type.builtin" = { fg = "cyan" }
"tag" = { fg = "pink" }
"tag.attribute" = { fg = "purple" }
"tag.delimiter" = { fg = "foreground" }
"type" = { fg = "cyan", modifiers = ["italic"] }
"type.builtin" = { fg = "cyan" }
"type.enum.variant" = { fg = "foreground", modifiers = ["italic"] }
"variable" = { fg = "foreground" }
"variable.builtin" = { fg = "purple", modifiers = ["italic"] }
"variable.parameter" = { fg = "orange", modifiers = ["italic"] }
"variable.other" = { fg = "foreground" }
"variable.other.member" = { fg = "foreground" }
"diff.plus" = { fg = "green" }
"diff.delta" = { fg = "orange" }
"diff.minus" = { fg = "red" }
"ui.background" = { fg = "foreground", bg = "background" }
"ui.cursor.match" = { fg = "foreground", bg = "grey" }
"ui.cursor" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.normal" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.insert" = { fg = "background", bg = "green", modifiers = ["dim"] }
"ui.cursor.select" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.cursor.primary.normal" = { fg = "background", bg = "purple" }
"ui.cursor.match" = { fg = "foreground", bg = "grey" }
"ui.cursor.normal" = { fg = "background", bg = "purple", modifiers = ["dim"] }
"ui.cursor.primary.insert" = { fg = "background", bg = "green" }
"ui.cursor.primary.normal" = { fg = "background", bg = "purple" }
"ui.cursor.primary.select" = { fg = "background", bg = "cyan" }
"ui.cursor.select" = { fg = "background", bg = "cyan", modifiers = ["dim"] }
"ui.cursorline.primary" = { bg = "cursorline" }
"ui.help" = { fg = "foreground", bg = "black" }
"ui.debug" = { fg = "red" }
"ui.help" = { fg = "foreground", bg = "black" }
"ui.highlight.frameline" = { fg = "background", bg = "red" }
"ui.linenr" = { fg = "comment" }
"ui.linenr.selected" = { fg = "foreground" }
"ui.menu" = { fg = "foreground", bg = "current_line" }
"ui.menu.selected" = { fg = "current_line", bg = "purple", modifiers = ["dim"] }
"ui.menu.scroll" = { fg = "foreground", bg = "current_line" }
"ui.menu.selected" = { fg = "current_line", bg = "purple", modifiers = ["dim"] }
"ui.popup" = { fg = "foreground", bg = "black" }
"ui.selection.primary" = { bg = "current_line" }
"ui.selection" = { bg = "selection" }
"ui.selection.primary" = { bg = "current_line" }
"ui.statusline" = { fg = "foreground", bg = "darker" }
"ui.statusline.inactive" = { fg = "comment", bg = "darker" }
"ui.statusline.normal" = { fg = "black", bg = "purple" }
"ui.statusline.insert" = { fg = "black", bg = "green" }
"ui.statusline.select" = { fg = "black", bg = "cyan" }
"ui.statusline.insert" = { fg = "black", bg = "green", modifiers = ["bold"] }
"ui.statusline.normal" = { fg = "black", bg = "purple", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "black", bg = "cyan", modifiers = ["bold"] }
"ui.text" = { fg = "foreground" }
"ui.text.focus" = { fg = "cyan" }
"ui.window" = { fg = "foreground" }
"ui.virtual.whitespace" = { fg = "current_line" }
"ui.virtual.wrap" = { fg = "current_line" }
"ui.virtual.ruler" = { bg = "black" }
"ui.virtual.indent-guide" = { fg = "indent" }
"ui.virtual.inlay-hint" = { fg = "cyan" }
"ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] }
"ui.virtual.inlay-hint.type" = { fg = "cyan", modifiers = ["italic", "dim"] }
"hint" = { fg = "purple" }
"error" = { fg = "red" }
"warning" = { fg = "yellow" }
"info" = { fg = "cyan" }
"markup.heading" = { fg = "purple", modifiers = ["bold"] }
"markup.list" = { fg = "cyan" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.italic" = { fg = "yellow", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "cyan" }
"markup.link.text" = { fg = "pink" }
"markup.quote" = { fg = "yellow", modifiers = ["italic"] }
"markup.raw" = { fg = "foreground" }
"diagnostic" = { underline = { color = "orange", style = "curl" } }
"diagnostic.hint" = { underline = { color = "purple", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"diagnostic.info" = { underline = { color = "cyan", style = "curl" } }
"definition" = { underline = { color = "cyan" } }
"ui.virtual.ruler" = { bg = "black" }
"ui.virtual.whitespace" = { fg = "whitespace" }
"ui.virtual.wrap" = { fg = "current_line" }
"ui.window" = { fg = "foreground" }
"variable" = { fg = "foreground" }
"variable.builtin" = { fg = "purple", modifiers = ["italic"] }
"variable.other" = { fg = "foreground" }
"variable.other.member" = { fg = "foreground" }
"variable.parameter" = { fg = "orange", modifiers = ["italic"] }
[palette]
foreground = "#f8f8f2"
background = "#282A36"
cursorline = "#2d303e"
darker = "#222430"
black = "#191A21"
grey = "#666771"
comment = "#6272A4"
current_line = "#44475a"
cursorline = "#2d303e"
cyan = "#8be9fd"
darker = "#222430"
foreground = "#f8f8f2"
green = "#50fa7b"
grey = "#666771"
indent = "#56596a"
selection = "#363848"
red = "#ff5555"
orange = "#ffb86c"
yellow = "#f1fa8c"
green = "#50fa7b"
purple = "#BD93F9"
cyan = "#8be9fd"
pink = "#ff79c6"
purple = "#BD93F9"
red = "#ff5555"
selection = "#363848"
whitespace = "#586693"
yellow = "#f1fa8c"

@ -60,6 +60,7 @@ label = "scale.red.3"
"ui.text.focus" = { fg = "fg.default" }
"ui.text.inactive" = "fg.subtle"
"ui.virtual" = { fg = "scale.gray.6" }
"ui.virtual.ruler" = { bg = "canvas.subtle" }
"ui.selection" = { bg = "scale.blue.8" }
"ui.selection.primary" = { bg = "scale.blue.7" }

@ -60,6 +60,7 @@ label = "scale.red.5"
"ui.text.focus" = { fg = "fg.default" }
"ui.text.inactive" = "fg.subtle"
"ui.virtual" = { fg = "scale.gray.2" }
"ui.virtual.ruler" = { bg = "canvas.subtle" }
"ui.selection" = { bg = "scale.blue.0" }
"ui.selection.primary" = { bg = "scale.blue.1" }

@ -0,0 +1,7 @@
# Author : Twinkle <saintwinkle@gmail.com>
# The theme uses the gruvbox light palette with hard contrast: github.com/morhetz/gruvbox
inherits = "gruvbox_light"
[palette]
bg0 = "#f9f5d7" # main background

@ -0,0 +1,7 @@
# Author : Twinkle <saintwinkle@gmail.com>
# The theme uses the gruvbox light palette with soft contrast: github.com/morhetz/gruvbox
inherits = "gruvbox_light"
[palette]
bg0 = "#f2e5bc" # main background

@ -11,6 +11,7 @@ keyword = "purple"
function = "blue"
label = "orange"
type = "orange"
constructor = "orange"
namespace = "orange"
# User Interface

@ -73,9 +73,9 @@
"ui.statusline" = { fg = "white", bg = "light-black" }
"ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" }
"ui.statusline.normal" = { fg = "light-black", bg = "blue" }
"ui.statusline.insert" = { fg = "light-black", bg = "green" }
"ui.statusline.select" = { fg = "light-black", bg = "purple" }
"ui.statusline.normal" = { fg = "light-black", bg = "blue", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "light-black", bg = "green", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "light-black", bg = "purple", modifiers = ["bold"] }
"ui.bufferline" = { fg = "light-gray", bg = "light-black" }
"ui.bufferline.active" = { fg = "light-black", bg = "blue", underline = { color = "light-black", style = "line" } }

@ -63,6 +63,9 @@
"ui.cursorline.primary" = { bg = "bg1" }
"ui.statusline" = { fg = "fg", bg = "bg3" }
"ui.statusline.inactive" = { fg = "grey", bg = "bg1" }
"ui.statusline.normal" = { fg = "bg0", bg = "blue", modifiers = ["bold"] }
"ui.statusline.insert" = { fg = "bg0", bg = "green", modifiers = ["bold"] }
"ui.statusline.select" = { fg = "bg0", bg = "purple", modifiers = ["bold"] }
"ui.popup" = { fg = "grey", bg = "bg2" }
"ui.window" = { fg = "grey", bg = "bg0" }
"ui.help" = { fg = "fg", bg = "bg1" }
@ -71,7 +74,7 @@
"ui.menu" = { fg = "fg", bg = "bg2" }
"ui.menu.selected" = { fg = "bg0", bg = "green" }
"ui.virtual.whitespace" = { fg = "grey_dim" }
"ui.virtual.ruler" = { bg = "grey_dim" }
"ui.virtual.ruler" = { bg = "bg3" }
"ui.virtual.inlay-hint" = { fg = "grey_dim" }
info = { fg = 'green', bg = 'bg2' }

@ -0,0 +1,80 @@
# Author: dgkf
"ui.background" = { }
"ui.background.separator" = { fg = "red" }
"ui.cursor" = { fg = "light-gray", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "light-yellow", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "light-gray", modifiers = ["reversed"] }
"ui.cursor.secondary" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "black" }
"ui.gutter" = { }
"ui.gutter.selected" = { bg = "black" }
"ui.help" = { fg = "white", bg = "black" }
"ui.linenr" = { fg = "gray", modifiers = ["bold"] }
"ui.linenr.selected" = { fg = "white", modifiers = ["bold"] }
"ui.menu" = { fg = "light-gray", bg = "gray" }
"ui.menu.selected" = { modifiers = ["reversed"] }
"ui.menu.scroll" = { fg = "light-blue" }
"ui.popup" = { bg = "black" }
"ui.selection" = { bg = "gray" }
"ui.statusline" = { fg = "light-gray", bg = "gray" }
"ui.statusline.inactive" = { bg = "black" }
"ui.virtual" = { bg = "black" }
"ui.virtual.indent-guide" = { fg = "gray" }
"ui.virtual.whitespace" = {}
"ui.virtual.wrap" = { fg = "gray" }
"ui.virtual.inlay-hint" = { fg = "light-gray", modifiers = ["dim", "italic"] }
"ui.virtual.inlay-hint.parameter" = { fg = "yellow", modifiers = ["dim", "italic"] }
"ui.virtual.inlay-hint.type" = { fg = "blue", modifiers = ["dim", "italic"] }
"ui.window" = { fg = "gray", modifiers = ["dim"] }
"comment" = { fg = "light-gray", modifiers = ["italic", "dim"] }
"attribute" = "light-yellow"
"constant" = { fg = "light-yellow", modifiers = ["bold", "dim"] }
"constant.numeric" = "light-yellow"
"constant.character.escape" = "light-cyan"
"constructor" = "light-blue"
"function" = "light-blue"
"function.macro" = "light-red"
"function.builtin" = { fg = "light-blue", modifiers = ["bold"] }
"tag" = { fg = "light-magenta", modifiers = ["dim"] }
"type" = "blue"
"type.builtin" = { fg = "blue", modifiers = ["bold"] }
"type.enum.variant" = { fg = "light-magenta", modifiers = ["dim"] }
"string" = "light-green"
"special" = "light-red"
"variable" = "white"
"variable.parameter" = { fg = "light-yellow", modifiers = ["italic"] }
"variable.other.member" = "light-green"
"keyword" = "light-magenta"
"keyword.control.exception" = "light-red"
"keyword.directive" = { fg = "light-yellow", modifiers = ["bold"] }
"keyword.operator" = { fg = "light-blue", modifiers = ["bold"] }
"label" = "light-green"
"namespace" = { fg = "blue", modifiers = ["dim"] }
"markup.heading" = "light-blue"
"markup.list" = "light-red"
"markup.bold" = { fg = "light-cyan", modifiers = ["bold"] }
"markup.italic" = { fg = "light-blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "magenta", modifiers = ["dim"] }
"markup.link.text" = "light-magenta"
"markup.quote" = "light-cyan"
"markup.raw" = "light-green"
"diff.plus" = "light-green"
"diff.delta" = "light-yellow"
"diff.minus" = "light-red"
"diagnostic.hint" = { underline = { color = "gray", style = "curl" } }
"diagnostic.info" = { underline = { color = "light-cyan", style = "curl" } }
"diagnostic.warning" = { underline = { color = "light-yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "light-red", style = "curl" } }
"info" = "light-cyan"
"hint" = { fg = "light-gray", modifiers = ["dim"] }
"debug" = "white"
"warning" = "yellow"
"error" = "light-red"

@ -0,0 +1,85 @@
# Author: dgkf
# Modified from base16_terminal, Author: NNB <nnbnh@protonmail.com>
inherits = "term16_dark"
"ui.background.separator" = "light-gray"
"ui.cursor" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "yellow", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "black", modifiers = ["reversed"] }
"ui.cursor.secondary" = { fg = "gray", modifiers = ["reversed"] }
"ui.cursorline.primary" = { bg = "white" }
"ui.cursorline.secondary" = { bg = "white" }
"ui.cursorcolumn.primary" = { bg = "white" }
"ui.cursorcolumn.secondary" = { bg = "white" }
"ui.gutter" = { }
"ui.gutter.selected" = { bg = "white" }
"ui.linenr" = { fg = "gray", modifiers = ["dim"] }
"ui.linenr.selected" = { fg = "black", modifiers = ["bold"] }
"ui.menu" = { bg = "light-gray" }
"ui.menu.selected" = { fg = "white", bg = "gray", modifiers = ["bold"] }
"ui.menu.scroll" = { fg = "light-blue" }
"ui.help" = { }
"ui.text" = { }
"ui.text.focus" = { }
"ui.popup" = { bg = "white" }
"ui.selection" = { bg = "light-gray" }
"ui.statusline" = { bg = "white" }
"ui.statusline.inactive" = { fg = "gray", modifiers = ["underlined"] }
"ui.statusline.insert" = { fg = "white", bg = "blue" }
"ui.statusline.select" = { fg = "white", bg = "magenta" }
"ui.virtual" = { fg = "light-gray" }
"ui.virtual.indent-guide" = { fg = "light-gray", modifiers = ["dim"] }
"ui.virtual.ruler" = { bg = "white" }
"ui.virtual.wrap" = { fg = "light-gray" }
"ui.window" = { fg = "gray", modifiers = ["dim"] }
"comment" = { fg = "gray", modifiers = ["italic", "dim"] }
"attribute" = "yellow"
"constant" = { fg = "yellow", modifiers = ["bold"] }
"constant.numeric" = { fg = "yellow", modifiers = ["bold"] }
"constant.character.escape" = "blue"
"constructor" = "blue"
"function" = "blue"
"function.builtin" = { fg = "blue", modifiers = ["bold"] }
"tag" = { fg = "magenta", modifiers = ["dim"] }
"type" = "blue"
"type.builtin" = { fg = "blue", modifiers = ["bold"] }
"type.enum.variant" = { fg = "magenta", modifiers = ["dim"] }
"string" = "green"
"special" = "red"
"variable" = { fg = "black", modifiers = ["dim"] }
"variable.parameter" = { fg = "red", modifiers = ["italic", "dim"] }
"variable.other.member" = "green"
"keyword" = "magenta"
"keyword.control.exception" = "red"
"keyword.directive" = { fg = "yellow", modifiers = ["bold"] }
"keyword.operator" = { fg = "blue", modifiers = ["bold"] }
"label" = "red"
"namespace" = { fg = "blue", modifiers = ["dim"] }
"markup.heading" = { fg = "blue", modifiers = ["bold"] }
"markup.list" = "red"
"markup.bold" = { fg = "cyan", modifiers = ["bold"] }
"markup.italic" = { fg = "blue", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.link.url" = { fg = "magenta", modifiers = ["dim"] }
"markup.link.text" = { fg = "magenta", modifiers = ["bold"] }
"markup.quote" = "cyan"
"markup.raw" = "blue"
"diff.plus" = "green"
"diff.delta" = "yellow"
"diff.minus" = "red"
"diagnostic.hint" = { underline = { color = "cyan", style = "curl" } }
"diagnostic.info" = { underline = { color = "blue", style = "curl" } }
"diagnostic.warning" = { underline = { color = "yellow", style = "curl" } }
"diagnostic.error" = { underline = { color = "red", style = "curl" } }
"hint" = "cyan"
"info" = "blue"
"debug" = "light-yellow"
"warning" = "yellow"
"error" = "red"

@ -4,22 +4,20 @@
"comment" = { fg = "light-gray", modifiers = ["italic"] }
"constant" = { fg = "yellow" }
"constant.numeric" = { fg = "orange" }
"constant.builtin" = { fg = "orange" }
"constant.builtin" = { fg = "yellow" }
"constant.builtin.boolean" = { fg = "yellow" }
"constant.character.escape" = { fg = "orange" }
"constant.character.escape" = { fg = "yellow" }
"constructor" = { fg = "blue" }
"function" = { fg = "blue" }
"function.builtin" = { fg = "blue" }
"function.macro" = { fg = "purple" }
"function.method" = { fg = "blue" }
"function.macro" = { fg = "blue" }
"keyword" = { fg = "purple" }
"keyword.control" = { fg = "purple" }
"keyword.control.import" = { fg = "purple" }
"keyword.directive" = { fg = "purple" }
"label" = { fg = "ui-text" }
"namespace" = { fg = "ui-text" }
"operator" = { fg = "ui-text" }
"keyword.operator" = { fg = "purple" }
"special" = { fg = "blue" }
"puncuation" = { fg = "ui-text" }
"special" = { fg = "ui-text" }
"string" = { fg = "green" }
"type" = { fg = "cyan" }
"variable.builtin" = { fg = "orange" }
@ -28,7 +26,7 @@
"markup.heading" = { fg = "red" }
"markup.raw.inline" = { fg = "green" }
"markup.bold" = { fg = "orange", modifiers = ["bold"] }
"markup.bold" = { fg = "yellow", modifiers = ["bold"] }
"markup.italic" = { fg = "purple", modifiers = ["italic"] }
"markup.strikethrough" = { modifiers = ["crossed_out"] }
"markup.list" = { fg = "red" }
@ -37,7 +35,7 @@
"markup.link.text" = { fg = "purple" }
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.delta" = "yellow"
"diff.minus" = "red"
"diagnostic.info".underline = { color = "blue", style = "curl" }
@ -50,19 +48,21 @@
"error" = { fg = "red", modifiers = ["bold"] }
"ui.background" = { bg = "ui-text-reversed" }
"ui.gutter" = { bg = "gray" }
"ui.virtual" = { fg = "faint-gray" }
"ui.virtual.indent-guide" = { fg = "faint-gray" }
"ui.virtual.whitespace" = { fg = "light-gray" }
"ui.virtual.ruler" = { bg = "gray" }
"ui.virtual.inlay-hint" = { fg = "light-gray" }
"ui.virtual.inlay-hint" = { fg = "blue-gray", modifiers = ["bold"] }
"ui.cursor" = { fg = "white", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] }
"ui.cursor.match" = { fg = "blue", modifiers = ["underlined"] }
"ui.cursor.insert" = { fg = "dark-blue" }
"ui.selection" = { bg = "faint-gray" }
"ui.selection.primary" = { bg = "gray" }
"ui.cursorline.primary" = { bg = "light-black" }
"ui.selection.primary" = { bg = "#293b5bff" }
"ui.cursorline.primary" = { bg = "gray" }
"ui.highlight" = { bg = "gray" }
"ui.highlight.frameline" = { bg = "#97202a" }
@ -70,14 +70,14 @@
"ui.linenr" = { fg = "linenr" }
"ui.linenr.selected" = { fg = "ui-text" }
"ui.statusline" = { fg = "white", bg = "light-black" }
"ui.statusline.inactive" = { fg = "light-gray", bg = "light-black" }
"ui.statusline.normal" = { fg = "light-black", bg = "blue" }
"ui.statusline.insert" = { fg = "light-black", bg = "green" }
"ui.statusline.select" = { fg = "light-black", bg = "purple" }
"ui.statusline" = { fg = "white", bg = "gray" }
"ui.statusline.inactive" = { fg = "light-gray", bg = "black" }
"ui.statusline.normal" = { fg = "black", bg = "blue" }
"ui.statusline.insert" = { fg = "black", bg = "green" }
"ui.statusline.select" = { fg = "black", bg = "purple" }
"ui.text" = { fg = "ui-text" }
"ui.text.focus" = { fg = "white", bg = "light-black", modifiers = ["bold"] }
"ui.text.focus" = { fg = "white", bg = "gray", modifiers = ["bold"] }
"ui.help" = { fg = "white", bg = "gray" }
"ui.popup" = { bg = "gray" }
@ -89,22 +89,21 @@
"ui.debug" = { fg = "red" }
[palette]
yellow = "#dac18c"
blue = "#7ca8dd"
red = "#bd7476"
purple = "#9d74b9"
green = "#a0b783"
orange = "#b4926e"
cyan = "#7eb2be"
light-black = "#2e323a"
gray = "#363f4c"
light-gray = "#5c606b"
yellow = "#dfc184ff"
orange = "#bf956aff"
blue = "#73ade9ff"
blue-gray = "#5a6f89ff"
red = "#d07277ff"
purple = "#b477cfff"
green = "#a1c181ff"
cyan = "#6eb4bfff"
gray = "#2f343ebf"
light-gray = "#5d636fff"
faint-gray = "#3B4048"
linenr = "#4B5263"
linenr = "#5d636fff"
white = "#a8adb7"
black = "#292c33"
white = "#c8ccd4ff"
black = "#282c33ff"
# black and white are used for a lot of the UI text
ui-text = "#a8adb7" #white
ui-text-reversed = "#292c33" #black
ui-text = "#c8ccd4ff" #white
ui-text-reversed = "#282c33ff" #black

@ -16,9 +16,13 @@ inherits = "zed_onedark"
"ui.cursor" = { fg = "dark-blue", modifiers = ["reversed"] }
"ui.cursor.primary" = { fg = "dark-blue", modifiers = ["reversed"] }
"ui.cursor.insert" = { fg = "dark-blue" }
"ui.selection.primary" = { bg = "blue-gray" }
"ui.cursorline.primary" = { bg = "faint-gray" }
"ui.virtual.inlay-hint" = { fg = "violet", modifiers = ["bold"] }
"ui.statusline" = { fg = "black", bg = "gray" }
"ui.statusline.inactive" = { fg = "white", bg = "light-black" }
"ui.statusline.normal" = { fg = "white", bg = "blue" }
@ -32,24 +36,26 @@ inherits = "zed_onedark"
"ui.window" = { fg = "dark-gray" }
[palette]
yellow = "#dac18c"
blue = "#5185b5"
red = "#bd7476"
dark-blue = "#607bdb"
orange = "#ca7667"
purple = "#a160ac"
green = "#739d60"
gold = "#a8763c"
cyan = "#4b80b2"
yellow = "#dabb7e"
red = "#d36151ff"
orange = "#d3604fff"
blue = "#5b79e3ff"
dark-blue = "#4a62db"
purple = "#a449abff"
violet = "#9294beff"
green = "#649f57ff"
gold = "#ad6e25ff"
cyan = "#3882b7ff"
light-black = "#2e323a"
gray = "#dcdcdd"
# gray = "#dcdcdd"
gray = "#eaeaed"
dark-gray = "#ebebec"
light-gray = "#a6a6aa"
light-gray = "#a2a3a7ff"
blue-gray = "#d9dcea"
faint-gray = "#efefef"
linenr = "#4B5263"
linenr = "#b0b1b3"
black = "#404248"
white = "#fafafa"
ui-text = "#404248"
ui-text-reversed = "#fafafa"
black = "#383a41ff"
white = "#fafafaff"
ui-text = "#383a41ff"
ui-text-reversed = "#fafafaff"

@ -16,4 +16,4 @@ helix-term = { path = "../helix-term" }
helix-core = { path = "../helix-core" }
helix-view = { path = "../helix-view" }
helix-loader = { path = "../helix-loader" }
toml = "0.7"
toml = "0.8"

Loading…
Cancel
Save