diff --git a/Cargo.lock b/Cargo.lock
index cabb98a70b0e..3f1f2fe45329 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -30,7 +30,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
- "getrandom 0.2.15",
"once_cell",
"version_check",
"zerocopy",
@@ -1319,14 +1318,13 @@ dependencies = [
name = "helix-core"
version = "25.1.1"
dependencies = [
- "ahash",
"anyhow",
"arc-swap",
"bitflags",
"chrono",
"encoding_rs",
+ "foldhash",
"globset",
- "hashbrown 0.14.5",
"helix-loader",
"helix-parsec",
"helix-stdx",
@@ -1347,7 +1345,7 @@ dependencies = [
"smartstring",
"textwrap",
"toml",
- "tree-sitter",
+ "tree-house",
"unicode-general-category",
"unicode-segmentation",
"unicode-width 0.1.12",
@@ -1391,14 +1389,13 @@ dependencies = [
"cc",
"etcetera",
"helix-stdx",
- "libloading",
"log",
"once_cell",
"serde",
"tempfile",
"threadpool",
"toml",
- "tree-sitter",
+ "tree-house",
]
[[package]]
@@ -2665,13 +2662,31 @@ dependencies = [
]
[[package]]
-name = "tree-sitter"
-version = "0.22.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca"
+name = "tree-house"
+version = "0.1.0-beta.2"
+source = "git+https://github.com/helix-editor/tree-house#1fa65eca36fdbb2837e0655bfda53ed627fc25c0"
dependencies = [
- "cc",
+ "arc-swap",
+ "hashbrown 0.15.2",
+ "kstring",
+ "once_cell",
"regex",
+ "regex-cursor",
+ "ropey",
+ "slab",
+ "tree-house-bindings",
+]
+
+[[package]]
+name = "tree-house-bindings"
+version = "0.1.0-beta.2"
+source = "git+https://github.com/helix-editor/tree-house#1fa65eca36fdbb2837e0655bfda53ed627fc25c0"
+dependencies = [
+ "cc",
+ "libloading",
+ "regex-cursor",
+ "ropey",
+ "thiserror 2.0.12",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 667a83967726..81d445aa27ad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,7 +37,7 @@ package.helix-tui.opt-level = 2
package.helix-term.opt-level = 2
[workspace.dependencies]
-tree-sitter = { version = "0.22" }
+tree-house = { git = "https://github.com/helix-editor/tree-house", default-features = false }
nucleo = "0.5.0"
slotmap = "1.0.7"
thiserror = "2.0"
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index 82715b7efa48..5ea6d1659b34 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -28,3 +28,4 @@
- [Adding textobject queries](./guides/textobject.md)
- [Adding indent queries](./guides/indent.md)
- [Adding injection queries](./guides/injection.md)
+ - [Adding rainbow bracket queries](./guides/rainbow_bracket_queries.md)
diff --git a/book/src/editor.md b/book/src/editor.md
index 1e5c2a507749..42e59c3fc6c9 100644
--- a/book/src/editor.md
+++ b/book/src/editor.md
@@ -61,6 +61,7 @@
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
| `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. |
| `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` |
+| `rainbow-brackets` | Whether to render rainbow colors for matching brackets. Requires tree-sitter `rainbows.scm` queries for the language. | `false` |
### `[editor.clipboard-provider]` Section
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index f6aee3fe3410..3c1796fd4c0a 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -1,260 +1,261 @@
-| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default language servers |
-| --- | --- | --- | --- | --- |
-| ada | ✓ | ✓ | | `ada_language_server` |
-| adl | ✓ | ✓ | ✓ | |
-| agda | ✓ | | | |
-| amber | ✓ | | | |
-| astro | ✓ | | | `astro-ls` |
-| awk | ✓ | ✓ | | `awk-language-server` |
-| bash | ✓ | ✓ | ✓ | `bash-language-server` |
-| bass | ✓ | | | `bass` |
-| beancount | ✓ | | | `beancount-language-server` |
-| bibtex | ✓ | | | `texlab` |
-| bicep | ✓ | | | `bicep-langserver` |
-| bitbake | ✓ | | | `bitbake-language-server` |
-| blade | ✓ | | | |
-| blueprint | ✓ | | | `blueprint-compiler` |
-| c | ✓ | ✓ | ✓ | `clangd` |
-| c-sharp | ✓ | ✓ | | `OmniSharp` |
-| cabal | | | | `haskell-language-server-wrapper` |
-| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
-| capnp | ✓ | | ✓ | |
-| cel | ✓ | | | |
-| circom | ✓ | | | `circom-lsp` |
-| clojure | ✓ | | | `clojure-lsp` |
-| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
-| codeql | ✓ | ✓ | | `codeql` |
-| comment | ✓ | | | |
-| common-lisp | ✓ | | ✓ | `cl-lsp` |
-| cpon | ✓ | | ✓ | |
-| cpp | ✓ | ✓ | ✓ | `clangd` |
-| crystal | ✓ | ✓ | | `crystalline` |
-| css | ✓ | | ✓ | `vscode-css-language-server` |
-| csv | ✓ | | | |
-| cue | ✓ | | | `cuelsp` |
-| cylc | ✓ | ✓ | ✓ | |
-| d | ✓ | ✓ | ✓ | `serve-d` |
-| dart | ✓ | ✓ | ✓ | `dart` |
-| dbml | ✓ | | | |
-| devicetree | ✓ | | | |
-| dhall | ✓ | ✓ | | `dhall-lsp-server` |
-| diff | ✓ | | | |
-| djot | ✓ | | | |
-| docker-compose | ✓ | ✓ | ✓ | `docker-compose-langserver`, `yaml-language-server` |
-| dockerfile | ✓ | ✓ | | `docker-langserver` |
-| dot | ✓ | | | `dot-language-server` |
-| dtd | ✓ | | | |
-| dune | ✓ | | | |
-| earthfile | ✓ | ✓ | ✓ | `earthlyls` |
-| edoc | ✓ | | | |
-| eex | ✓ | | | |
-| ejs | ✓ | | | |
-| elisp | ✓ | | | |
-| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
-| elm | ✓ | ✓ | | `elm-language-server` |
-| elvish | ✓ | | | `elvish` |
-| env | ✓ | ✓ | | |
-| erb | ✓ | | | |
-| erlang | ✓ | ✓ | | `erlang_ls`, `elp` |
-| esdl | ✓ | | | |
-| fga | ✓ | ✓ | ✓ | |
-| fidl | ✓ | | | |
-| fish | ✓ | ✓ | ✓ | `fish-lsp` |
-| forth | ✓ | | | `forth-lsp` |
-| fortran | ✓ | | ✓ | `fortls` |
-| fsharp | ✓ | | | `fsautocomplete` |
-| gas | ✓ | ✓ | | `asm-lsp` |
-| gdscript | ✓ | ✓ | ✓ | |
-| gemini | ✓ | | | |
-| gherkin | ✓ | | | |
-| ghostty | ✓ | | | |
-| git-attributes | ✓ | | | |
-| git-commit | ✓ | ✓ | | |
-| git-config | ✓ | ✓ | | |
-| git-ignore | ✓ | | | |
-| git-rebase | ✓ | | | |
-| gjs | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
-| gleam | ✓ | ✓ | | `gleam` |
-| glimmer | ✓ | | | `ember-language-server` |
-| glsl | ✓ | ✓ | ✓ | `glsl_analyzer` |
-| gn | ✓ | | | |
-| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
-| godot-resource | ✓ | ✓ | | |
-| gomod | ✓ | | | `gopls` |
-| gotmpl | ✓ | | | `gopls` |
-| gowork | ✓ | | | `gopls` |
-| gpr | ✓ | | | `ada_language_server` |
-| graphql | ✓ | ✓ | | `graphql-lsp` |
-| gren | ✓ | ✓ | | |
-| groovy | ✓ | | | |
-| gts | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
-| hare | ✓ | | | |
-| haskell | ✓ | ✓ | | `haskell-language-server-wrapper` |
-| haskell-persistent | ✓ | | | |
-| hcl | ✓ | ✓ | ✓ | `terraform-ls` |
-| heex | ✓ | ✓ | | `elixir-ls` |
-| helm | ✓ | | | `helm_ls` |
-| hocon | ✓ | ✓ | ✓ | |
-| hoon | ✓ | | | |
-| hosts | ✓ | | | |
-| html | ✓ | | | `vscode-html-language-server`, `superhtml` |
-| hurl | ✓ | ✓ | ✓ | |
-| hyprlang | ✓ | | ✓ | `hyprls` |
-| idris | | | | `idris2-lsp` |
-| iex | ✓ | | | |
-| ini | ✓ | | | |
-| ink | ✓ | | | |
-| inko | ✓ | ✓ | ✓ | |
-| janet | ✓ | | | |
-| java | ✓ | ✓ | ✓ | `jdtls` |
-| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
-| jinja | ✓ | | | |
-| jjdescription | ✓ | | | |
-| jq | ✓ | ✓ | | `jq-lsp` |
-| jsdoc | ✓ | | | |
-| json | ✓ | ✓ | ✓ | `vscode-json-language-server` |
-| json5 | ✓ | | | |
-| jsonc | ✓ | | ✓ | `vscode-json-language-server` |
-| jsonnet | ✓ | | | `jsonnet-language-server` |
-| jsx | ✓ | ✓ | ✓ | `typescript-language-server` |
-| julia | ✓ | ✓ | ✓ | `julia` |
-| just | ✓ | ✓ | ✓ | |
-| kdl | ✓ | ✓ | ✓ | |
-| koka | ✓ | | ✓ | `koka` |
-| kotlin | ✓ | ✓ | ✓ | `kotlin-language-server` |
-| koto | ✓ | ✓ | ✓ | `koto-ls` |
-| latex | ✓ | ✓ | | `texlab` |
-| ld | ✓ | | ✓ | |
-| ldif | ✓ | | | |
-| lean | ✓ | | | `lean` |
-| ledger | ✓ | | | |
-| llvm | ✓ | ✓ | ✓ | |
-| llvm-mir | ✓ | ✓ | ✓ | |
-| llvm-mir-yaml | ✓ | | ✓ | |
-| log | ✓ | | | |
-| lpf | ✓ | | | |
-| lua | ✓ | ✓ | ✓ | `lua-language-server` |
-| mail | ✓ | ✓ | | |
-| make | ✓ | | ✓ | |
-| markdoc | ✓ | | | `markdoc-ls` |
-| markdown | ✓ | | | `marksman`, `markdown-oxide` |
-| markdown.inline | ✓ | | | |
-| matlab | ✓ | ✓ | ✓ | |
-| mermaid | ✓ | | | |
-| meson | ✓ | | ✓ | `mesonlsp` |
-| mint | | | | `mint` |
-| mojo | ✓ | ✓ | ✓ | `magic` |
-| move | ✓ | | | |
-| msbuild | ✓ | | ✓ | |
-| nasm | ✓ | ✓ | | `asm-lsp` |
-| nestedtext | ✓ | ✓ | ✓ | |
-| nginx | ✓ | | | |
-| nickel | ✓ | | ✓ | `nls` |
-| nim | ✓ | ✓ | ✓ | `nimlangserver` |
-| nix | ✓ | ✓ | ✓ | `nil`, `nixd` |
-| nu | ✓ | | | `nu` |
-| nunjucks | ✓ | | | |
-| ocaml | ✓ | | ✓ | `ocamllsp` |
-| ocaml-interface | ✓ | | | `ocamllsp` |
-| odin | ✓ | ✓ | ✓ | `ols` |
-| ohm | ✓ | ✓ | ✓ | |
-| opencl | ✓ | ✓ | ✓ | `clangd` |
-| openscad | ✓ | | | `openscad-lsp` |
-| org | ✓ | | | |
-| pascal | ✓ | ✓ | | `pasls` |
-| passwd | ✓ | | | |
-| pem | ✓ | | | |
-| perl | ✓ | ✓ | ✓ | `perlnavigator` |
-| pest | ✓ | ✓ | ✓ | `pest-language-server` |
-| php | ✓ | ✓ | ✓ | `intelephense` |
-| php-only | ✓ | | | |
-| pkgbuild | ✓ | ✓ | ✓ | `termux-language-server`, `bash-language-server` |
-| pkl | ✓ | | ✓ | `pkl-lsp` |
-| po | ✓ | ✓ | | |
-| pod | ✓ | | | |
-| ponylang | ✓ | ✓ | ✓ | |
-| powershell | ✓ | | | |
-| prisma | ✓ | ✓ | | `prisma-language-server` |
-| prolog | | | | `swipl` |
-| protobuf | ✓ | ✓ | ✓ | `buf`, `pb`, `protols` |
-| prql | ✓ | | | |
-| purescript | ✓ | ✓ | | `purescript-language-server` |
-| python | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
-| qml | ✓ | | ✓ | `qmlls` |
-| quint | ✓ | | | `quint-language-server` |
-| r | ✓ | | | `R` |
-| racket | ✓ | | ✓ | `racket` |
-| regex | ✓ | | | |
-| rego | ✓ | | | `regols` |
-| rescript | ✓ | ✓ | | `rescript-language-server` |
-| rmarkdown | ✓ | | ✓ | `R` |
-| robot | ✓ | | | `robotframework_ls` |
-| ron | ✓ | | ✓ | |
-| rst | ✓ | | | |
-| ruby | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
-| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
-| sage | ✓ | ✓ | | |
-| scala | ✓ | ✓ | ✓ | `metals` |
-| scheme | ✓ | | ✓ | |
-| scss | ✓ | | | `vscode-css-language-server` |
-| slint | ✓ | ✓ | ✓ | `slint-lsp` |
-| smali | ✓ | | ✓ | |
-| smithy | ✓ | | | `cs` |
-| sml | ✓ | | | |
-| snakemake | ✓ | | ✓ | `pylsp` |
-| solidity | ✓ | ✓ | | `solc` |
-| sourcepawn | ✓ | ✓ | | `sourcepawn-studio` |
-| spade | ✓ | | ✓ | `spade-language-server` |
-| spicedb | ✓ | | | |
-| sql | ✓ | ✓ | | |
-| sshclientconfig | ✓ | | | |
-| starlark | ✓ | ✓ | ✓ | `starpls` |
-| strace | ✓ | | | |
-| supercollider | ✓ | | | |
-| svelte | ✓ | | ✓ | `svelteserver` |
-| sway | ✓ | ✓ | ✓ | `forc` |
-| swift | ✓ | ✓ | | `sourcekit-lsp` |
-| t32 | ✓ | | | |
-| tablegen | ✓ | ✓ | ✓ | |
-| tact | ✓ | ✓ | ✓ | |
-| task | ✓ | | | |
-| tcl | ✓ | | ✓ | |
-| teal | ✓ | | | `teal-language-server` |
-| templ | ✓ | | | `templ` |
-| tera | ✓ | | | |
-| textproto | ✓ | ✓ | ✓ | |
-| tfvars | ✓ | | ✓ | `terraform-ls` |
-| thrift | ✓ | | | |
-| tlaplus | ✓ | | | |
-| todotxt | ✓ | | | |
-| toml | ✓ | ✓ | | `taplo` |
-| tsq | ✓ | | | `ts_query_ls` |
-| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
-| twig | ✓ | | | |
-| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
-| typespec | ✓ | ✓ | ✓ | `tsp-server` |
-| typst | ✓ | | | `tinymist` |
-| ungrammar | ✓ | | | |
-| unison | ✓ | ✓ | ✓ | |
-| uxntal | ✓ | | | |
-| v | ✓ | ✓ | ✓ | `v-analyzer` |
-| vala | ✓ | ✓ | | `vala-language-server` |
-| vento | ✓ | | | |
-| verilog | ✓ | ✓ | | `svlangserver` |
-| vhdl | ✓ | | | `vhdl_ls` |
-| vhs | ✓ | | | |
-| vue | ✓ | | | `vue-language-server` |
-| wast | ✓ | | | |
-| wat | ✓ | | | `wat_server` |
-| webc | ✓ | | | |
-| werk | ✓ | | | |
-| wgsl | ✓ | | | `wgsl-analyzer` |
-| wit | ✓ | | ✓ | |
-| wren | ✓ | ✓ | ✓ | |
-| xit | ✓ | | | |
-| xml | ✓ | | ✓ | |
-| xtc | ✓ | | | |
-| yaml | ✓ | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
-| yara | ✓ | | | `yls` |
-| yuck | ✓ | | | |
-| zig | ✓ | ✓ | ✓ | `zls` |
+| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Rainbow Brackets | Default language servers |
+| --- | --- | --- | --- | --- | --- |
+| ada | ✓ | ✓ | | | `ada_language_server` |
+| adl | ✓ | ✓ | ✓ | | |
+| agda | ✓ | | | | |
+| amber | ✓ | | | | |
+| astro | ✓ | | | | `astro-ls` |
+| awk | ✓ | ✓ | | | `awk-language-server` |
+| bash | ✓ | ✓ | ✓ | ✓ | `bash-language-server` |
+| bass | ✓ | | | | `bass` |
+| beancount | ✓ | | | | `beancount-language-server` |
+| bibtex | ✓ | | | | `texlab` |
+| bicep | ✓ | | | | `bicep-langserver` |
+| bitbake | ✓ | | | | `bitbake-language-server` |
+| blade | ✓ | | | | |
+| blueprint | ✓ | | | | `blueprint-compiler` |
+| c | ✓ | ✓ | ✓ | ✓ | `clangd` |
+| c-sharp | ✓ | ✓ | | | `OmniSharp` |
+| cabal | | | | | `haskell-language-server-wrapper` |
+| cairo | ✓ | ✓ | ✓ | | `cairo-language-server` |
+| capnp | ✓ | | ✓ | | |
+| cel | ✓ | | | | |
+| circom | ✓ | | | | `circom-lsp` |
+| clojure | ✓ | | | ✓ | `clojure-lsp` |
+| cmake | ✓ | ✓ | ✓ | | `cmake-language-server` |
+| codeql | ✓ | ✓ | | | `codeql` |
+| comment | ✓ | | | | |
+| common-lisp | ✓ | | ✓ | ✓ | `cl-lsp` |
+| cpon | ✓ | | ✓ | | |
+| cpp | ✓ | ✓ | ✓ | ✓ | `clangd` |
+| crystal | ✓ | ✓ | | | `crystalline` |
+| css | ✓ | | ✓ | ✓ | `vscode-css-language-server` |
+| csv | ✓ | | | | |
+| cue | ✓ | | | | `cuelsp` |
+| cylc | ✓ | ✓ | ✓ | | |
+| d | ✓ | ✓ | ✓ | | `serve-d` |
+| dart | ✓ | ✓ | ✓ | | `dart` |
+| dbml | ✓ | | | | |
+| devicetree | ✓ | | | | |
+| dhall | ✓ | ✓ | | | `dhall-lsp-server` |
+| diff | ✓ | | | | |
+| djot | ✓ | | | | |
+| docker-compose | ✓ | ✓ | ✓ | | `docker-compose-langserver`, `yaml-language-server` |
+| dockerfile | ✓ | ✓ | | | `docker-langserver` |
+| dot | ✓ | | | | `dot-language-server` |
+| dtd | ✓ | | | | |
+| dune | ✓ | | | | |
+| earthfile | ✓ | ✓ | ✓ | | `earthlyls` |
+| edoc | ✓ | | | | |
+| eex | ✓ | | | | |
+| ejs | ✓ | | | | |
+| elisp | ✓ | | | | |
+| elixir | ✓ | ✓ | ✓ | ✓ | `elixir-ls` |
+| elm | ✓ | ✓ | | | `elm-language-server` |
+| elvish | ✓ | | | | `elvish` |
+| env | ✓ | ✓ | | | |
+| erb | ✓ | | | | |
+| erlang | ✓ | ✓ | | ✓ | `erlang_ls`, `elp` |
+| esdl | ✓ | | | | |
+| fga | ✓ | ✓ | ✓ | | |
+| fidl | ✓ | | | | |
+| fish | ✓ | ✓ | ✓ | | `fish-lsp` |
+| forth | ✓ | | | | `forth-lsp` |
+| fortran | ✓ | | ✓ | | `fortls` |
+| fsharp | ✓ | | | | `fsautocomplete` |
+| gas | ✓ | ✓ | | | `asm-lsp` |
+| gdscript | ✓ | ✓ | ✓ | | |
+| gemini | ✓ | | | | |
+| gherkin | ✓ | | | | |
+| ghostty | ✓ | | | | |
+| git-attributes | ✓ | | | | |
+| git-commit | ✓ | ✓ | | | |
+| git-config | ✓ | ✓ | | | |
+| git-ignore | ✓ | | | | |
+| git-rebase | ✓ | | | | |
+| gjs | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
+| gleam | ✓ | ✓ | | ✓ | `gleam` |
+| glimmer | ✓ | | | | `ember-language-server` |
+| glsl | ✓ | ✓ | ✓ | | `glsl_analyzer` |
+| gn | ✓ | | | | |
+| go | ✓ | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
+| godot-resource | ✓ | ✓ | | | |
+| gomod | ✓ | | | | `gopls` |
+| gotmpl | ✓ | | | | `gopls` |
+| gowork | ✓ | | | | `gopls` |
+| gpr | ✓ | | | | `ada_language_server` |
+| graphql | ✓ | ✓ | | | `graphql-lsp` |
+| gren | ✓ | ✓ | | | |
+| groovy | ✓ | | | | |
+| gts | ✓ | ✓ | ✓ | | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` |
+| hare | ✓ | | | | |
+| haskell | ✓ | ✓ | | | `haskell-language-server-wrapper` |
+| haskell-persistent | ✓ | | | | |
+| hcl | ✓ | ✓ | ✓ | | `terraform-ls` |
+| heex | ✓ | ✓ | | | `elixir-ls` |
+| helm | ✓ | | | | `helm_ls` |
+| hocon | ✓ | ✓ | ✓ | | |
+| hoon | ✓ | | | | |
+| hosts | ✓ | | | | |
+| html | ✓ | | | ✓ | `vscode-html-language-server`, `superhtml` |
+| hurl | ✓ | ✓ | ✓ | | |
+| hyprlang | ✓ | | ✓ | | `hyprls` |
+| idris | | | | | `idris2-lsp` |
+| iex | ✓ | | | | |
+| ini | ✓ | | | | |
+| ink | ✓ | | | | |
+| inko | ✓ | ✓ | ✓ | | |
+| janet | ✓ | | | | |
+| java | ✓ | ✓ | ✓ | ✓ | `jdtls` |
+| javascript | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| jinja | ✓ | | | | |
+| jjdescription | ✓ | | | | |
+| jq | ✓ | ✓ | | | `jq-lsp` |
+| jsdoc | ✓ | | | | |
+| json | ✓ | ✓ | ✓ | ✓ | `vscode-json-language-server` |
+| json5 | ✓ | | | | |
+| jsonc | ✓ | | ✓ | | `vscode-json-language-server` |
+| jsonnet | ✓ | | | | `jsonnet-language-server` |
+| jsx | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| julia | ✓ | ✓ | ✓ | | `julia` |
+| just | ✓ | ✓ | ✓ | | |
+| kdl | ✓ | ✓ | ✓ | | |
+| koka | ✓ | | ✓ | | `koka` |
+| kotlin | ✓ | ✓ | ✓ | | `kotlin-language-server` |
+| koto | ✓ | ✓ | ✓ | | `koto-ls` |
+| latex | ✓ | ✓ | | | `texlab` |
+| ld | ✓ | | ✓ | | |
+| ldif | ✓ | | | | |
+| lean | ✓ | | | | `lean` |
+| ledger | ✓ | | | | |
+| llvm | ✓ | ✓ | ✓ | | |
+| llvm-mir | ✓ | ✓ | ✓ | | |
+| llvm-mir-yaml | ✓ | | ✓ | | |
+| log | ✓ | | | | |
+| lpf | ✓ | | | | |
+| lua | ✓ | ✓ | ✓ | | `lua-language-server` |
+| mail | ✓ | ✓ | | | |
+| make | ✓ | | ✓ | | |
+| markdoc | ✓ | | | | `markdoc-ls` |
+| markdown | ✓ | | | | `marksman`, `markdown-oxide` |
+| markdown-rustdoc | ✓ | | | | |
+| markdown.inline | ✓ | | | | |
+| matlab | ✓ | ✓ | ✓ | | |
+| mermaid | ✓ | | | | |
+| meson | ✓ | | ✓ | | `mesonlsp` |
+| mint | | | | | `mint` |
+| mojo | ✓ | ✓ | ✓ | | `magic` |
+| move | ✓ | | | | |
+| msbuild | ✓ | | ✓ | | |
+| nasm | ✓ | ✓ | | | `asm-lsp` |
+| nestedtext | ✓ | ✓ | ✓ | | |
+| nginx | ✓ | | | | |
+| nickel | ✓ | | ✓ | | `nls` |
+| nim | ✓ | ✓ | ✓ | | `nimlangserver` |
+| nix | ✓ | ✓ | ✓ | ✓ | `nil`, `nixd` |
+| nu | ✓ | | | | `nu` |
+| nunjucks | ✓ | | | | |
+| ocaml | ✓ | | ✓ | | `ocamllsp` |
+| ocaml-interface | ✓ | | | | `ocamllsp` |
+| odin | ✓ | ✓ | ✓ | | `ols` |
+| ohm | ✓ | ✓ | ✓ | | |
+| opencl | ✓ | ✓ | ✓ | | `clangd` |
+| openscad | ✓ | | | | `openscad-lsp` |
+| org | ✓ | | | | |
+| pascal | ✓ | ✓ | | | `pasls` |
+| passwd | ✓ | | | | |
+| pem | ✓ | | | | |
+| perl | ✓ | ✓ | ✓ | | `perlnavigator` |
+| pest | ✓ | ✓ | ✓ | | `pest-language-server` |
+| php | ✓ | ✓ | ✓ | | `intelephense` |
+| php-only | ✓ | | | | |
+| pkgbuild | ✓ | ✓ | ✓ | | `termux-language-server`, `bash-language-server` |
+| pkl | ✓ | | ✓ | | `pkl-lsp` |
+| po | ✓ | ✓ | | | |
+| pod | ✓ | | | | |
+| ponylang | ✓ | ✓ | ✓ | | |
+| powershell | ✓ | | | | |
+| prisma | ✓ | ✓ | | | `prisma-language-server` |
+| prolog | | | | | `swipl` |
+| protobuf | ✓ | ✓ | ✓ | | `buf`, `pb`, `protols` |
+| prql | ✓ | | | | |
+| purescript | ✓ | ✓ | | | `purescript-language-server` |
+| python | ✓ | ✓ | ✓ | ✓ | `ruff`, `jedi-language-server`, `pylsp` |
+| qml | ✓ | | ✓ | | `qmlls` |
+| quint | ✓ | | | | `quint-language-server` |
+| r | ✓ | | | | `R` |
+| racket | ✓ | | ✓ | ✓ | `racket` |
+| regex | ✓ | | | ✓ | |
+| rego | ✓ | | | | `regols` |
+| rescript | ✓ | ✓ | | | `rescript-language-server` |
+| rmarkdown | ✓ | | ✓ | | `R` |
+| robot | ✓ | | | | `robotframework_ls` |
+| ron | ✓ | | ✓ | | |
+| rst | ✓ | | | | |
+| ruby | ✓ | ✓ | ✓ | ✓ | `ruby-lsp`, `solargraph` |
+| rust | ✓ | ✓ | ✓ | ✓ | `rust-analyzer` |
+| sage | ✓ | ✓ | | | |
+| scala | ✓ | ✓ | ✓ | | `metals` |
+| scheme | ✓ | | ✓ | ✓ | |
+| scss | ✓ | | | ✓ | `vscode-css-language-server` |
+| slint | ✓ | ✓ | ✓ | | `slint-lsp` |
+| smali | ✓ | | ✓ | | |
+| smithy | ✓ | | | | `cs` |
+| sml | ✓ | | | | |
+| snakemake | ✓ | | ✓ | | `pylsp` |
+| solidity | ✓ | ✓ | | | `solc` |
+| sourcepawn | ✓ | ✓ | | | `sourcepawn-studio` |
+| spade | ✓ | | ✓ | | `spade-language-server` |
+| spicedb | ✓ | | | | |
+| sql | ✓ | ✓ | | | |
+| sshclientconfig | ✓ | | | | |
+| starlark | ✓ | ✓ | ✓ | ✓ | `starpls` |
+| strace | ✓ | | | | |
+| supercollider | ✓ | | | | |
+| svelte | ✓ | | ✓ | | `svelteserver` |
+| sway | ✓ | ✓ | ✓ | | `forc` |
+| swift | ✓ | ✓ | | | `sourcekit-lsp` |
+| t32 | ✓ | | | | |
+| tablegen | ✓ | ✓ | ✓ | | |
+| tact | ✓ | ✓ | ✓ | | |
+| task | ✓ | | | | |
+| tcl | ✓ | | ✓ | | |
+| teal | ✓ | | | | `teal-language-server` |
+| templ | ✓ | | | | `templ` |
+| tera | ✓ | | | | |
+| textproto | ✓ | ✓ | ✓ | | |
+| tfvars | ✓ | | ✓ | | `terraform-ls` |
+| thrift | ✓ | | | | |
+| tlaplus | ✓ | | | | |
+| todotxt | ✓ | | | | |
+| toml | ✓ | ✓ | | ✓ | `taplo` |
+| tsq | ✓ | | | | `ts_query_ls` |
+| tsx | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| twig | ✓ | | | | |
+| typescript | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| typespec | ✓ | ✓ | ✓ | | `tsp-server` |
+| typst | ✓ | | | | `tinymist` |
+| ungrammar | ✓ | | | | |
+| unison | ✓ | ✓ | ✓ | | |
+| uxntal | ✓ | | | | |
+| v | ✓ | ✓ | ✓ | | `v-analyzer` |
+| vala | ✓ | ✓ | | | `vala-language-server` |
+| vento | ✓ | | | | |
+| verilog | ✓ | ✓ | | | `svlangserver` |
+| vhdl | ✓ | | | | `vhdl_ls` |
+| vhs | ✓ | | | | |
+| vue | ✓ | | | | `vue-language-server` |
+| wast | ✓ | | | | |
+| wat | ✓ | | | | `wat_server` |
+| webc | ✓ | | | | |
+| werk | ✓ | | | | |
+| wgsl | ✓ | | | | `wgsl-analyzer` |
+| wit | ✓ | | ✓ | | |
+| wren | ✓ | ✓ | ✓ | | |
+| xit | ✓ | | | | |
+| xml | ✓ | | ✓ | ✓ | |
+| xtc | ✓ | | | | |
+| yaml | ✓ | ✓ | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
+| yara | ✓ | | | | `yls` |
+| yuck | ✓ | | | | |
+| zig | ✓ | ✓ | ✓ | | `zls` |
diff --git a/book/src/guides/README.md b/book/src/guides/README.md
index c25768e68961..e53983d60fb1 100644
--- a/book/src/guides/README.md
+++ b/book/src/guides/README.md
@@ -1,4 +1,4 @@
# Guides
This section contains guides for adding new language server configurations,
-tree-sitter grammars, textobject queries, and other similar items.
+tree-sitter grammars, textobject and rainbow bracket queries, and other similar items.
diff --git a/book/src/guides/rainbow_bracket_queries.md b/book/src/guides/rainbow_bracket_queries.md
new file mode 100644
index 000000000000..1cba6a9907d1
--- /dev/null
+++ b/book/src/guides/rainbow_bracket_queries.md
@@ -0,0 +1,132 @@
+# Adding Rainbow Bracket Queries
+
+Helix uses `rainbows.scm` tree-sitter query files to provide rainbow bracket
+functionality.
+
+Tree-sitter queries are documented in the tree-sitter online documentation.
+If you're writing queries for the first time, be sure to check out the section
+on [syntax highlighting queries] and on [query syntax].
+
+Rainbow queries have two captures: `@rainbow.scope` and `@rainbow.bracket`.
+`@rainbow.scope` should capture any node that increases the nesting level
+while `@rainbow.bracket` should capture any bracket nodes. Put another way:
+`@rainbow.scope` switches to the next rainbow color for all nodes in the tree
+under it while `@rainbow.bracket` paints captured nodes with the current
+rainbow color.
+
+For an example, let's add rainbow queries for the tree-sitter query (TSQ)
+language itself. These queries will go into a
+`runtime/queries/tsq/rainbows.scm` file in the repository root.
+
+First we'll add the `@rainbow.bracket` captures. TSQ only has parentheses and
+square brackets:
+
+```tsq
+["(" ")" "[" "]"] @rainbow.bracket
+```
+
+The ordering of the nodes within the alternation (square brackets) is not
+taken into consideration.
+
+> Note: Why are these nodes quoted? Most syntax highlights capture text
+> surrounded by parentheses. These are _named nodes_ and correspond to the
+> names of rules in the grammar. Brackets are usually written in tree-sitter
+> grammars as literal strings, for example:
+>
+> ```js
+> {
+> // ...
+> arguments: seq("(", repeat($.argument), ")"),
+> // ...
+> }
+> ```
+>
+> Nodes written as literal strings in tree-sitter grammars may be captured
+> in queries with those same literal strings.
+
+Then we'll add `@rainbow.scope` captures. The easiest way to do this is to
+view the `grammar.js` file in the tree-sitter grammar's repository. For TSQ,
+that file is [here][tsq grammar.js]. As we scroll down the `grammar.js`, we
+see that the `(alternation)`, (L36) `(group)` (L57), `(named_node)` (L59),
+`(predicate)` (L87) and `(wildcard_node)` (L97) nodes all contain literal
+parentheses or square brackets in their definitions. These nodes are all
+direct parents of brackets and happen to also be the nodes we want to change
+to the next rainbow color, so we capture them as `@rainbow.scope`.
+
+```tsq
+[
+ (group)
+ (named_node)
+ (wildcard_node)
+ (predicate)
+ (alternation)
+] @rainbow.scope
+```
+
+This strategy works as a rule of thumb for most programming and configuration
+languages. Markup languages can be trickier and may take additional
+experimentation to find the correct nodes to use for scopes and brackets.
+
+The `:tree-sitter-subtree` command shows the syntax tree under the primary
+selection in S-expression format and can be a useful tool for determining how
+to write a query.
+
+### Properties
+
+The `rainbow.include-children` property may be applied to `@rainbow.scope`
+captures. By default, all `@rainbow.bracket` captures must be direct descendant
+of a node captured with `@rainbow.scope` in a syntax tree in order to be
+highlighted. The `rainbow.include-children` property disables that check and
+allows `@rainbow.bracket` captures to be highlighted if they are direct or
+indirect descendants of some node captured with `@rainbow.scope`.
+
+For example, this property is used in the HTML rainbow queries.
+
+For a document like `link`, the syntax tree is:
+
+```tsq
+(element ; link
+ (start_tag ;
+ (tag_name)) ; a
+ (text) ; link
+ (end_tag ;
+ (tag_name))) ; a
+```
+
+If we want to highlight the `<`, `>` and `` nodes with rainbow colors, we
+capture them as `@rainbow.bracket`:
+
+```tsq
+["<" ">" ""] @rainbow.bracket
+```
+
+And we capture `(element)` as `@rainbow.scope` because `(element)` nodes nest
+within each other: they increment the nesting level and switch to the next
+color in the rainbow.
+
+```tsq
+(element) @rainbow.scope
+```
+
+But this combination of `@rainbow.scope` and `@rainbow.bracket` will not
+highlight any nodes. `<`, `>` and `` are children of the `(start_tag)` and
+`(end_tag)` nodes. We can't capture `(start_tag)` and `(end_tag)` as
+`@rainbow.scope` because they don't nest other elements. We can fix this case
+by removing the requirement that `<`, `>` and `` are direct descendants of
+`(element)` using the `rainbow.include-children` property.
+
+```tsq
+((element) @rainbow.scope
+ (#set! rainbow.include-children))
+```
+
+With this property set, `<`, `>`, and `` will highlight with rainbow colors
+even though they aren't direct descendents of the `(element)` node.
+
+`rainbow.include-children` is not necessary for the vast majority of programming
+languages. It is only necessary when the node that increments the nesting level
+(changes rainbow color) is not the direct parent of the bracket node.
+
+[syntax highlighting queries]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#highlights
+[query syntax]: https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries
+[tsq grammar.js]: https://github.com/the-mikedavis/tree-sitter-tsq/blob/48b5e9f82ae0a4727201626f33a17f69f8e0ff86/grammar.js
diff --git a/book/src/languages.md b/book/src/languages.md
index 8db121ce1b25..71043b9bb379 100644
--- a/book/src/languages.md
+++ b/book/src/languages.md
@@ -73,6 +73,7 @@ These configuration keys are available:
| `path-completion` | Overrides the `editor.path-completion` config key for the language. |
| `workspace-lsp-roots` | Directories relative to the workspace root that are treated as LSP roots. Should only be set in `.helix/config.toml`. Overwrites the setting of the same name in `config.toml` if set. |
| `persistent-diagnostic-sources` | An array of LSP diagnostic sources assumed unchanged when the language server resends the same set of diagnostics. Helix can track the position for these diagnostics internally instead. Useful for diagnostics that are recomputed on save.
+| `rainbow-brackets` | Overrides the `editor.rainbow-brackets` config key for the language |
### File-type detection and the `file-types` key
diff --git a/book/src/themes.md b/book/src/themes.md
index 412d17efc237..02e40548e1af 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -130,6 +130,17 @@ inherits = "boo_berry"
berry = "#2A2A4D"
```
+### Rainbow
+
+The `rainbow` key is used for rainbow highlight for matching brackets.
+The key is a list of styles.
+
+```toml
+rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
+```
+
+Colors from the palette and modifiers may be used.
+
### Scopes
The following is a list of scopes available to use for styling:
diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml
index 10fb5a52cdee..2d84dcedebec 100644
--- a/helix-core/Cargo.toml
+++ b/helix-core/Cargo.toml
@@ -32,13 +32,12 @@ unicode-segmentation.workspace = true
unicode-width = "=0.1.12"
unicode-general-category = "1.0"
slotmap.workspace = true
-tree-sitter.workspace = true
+tree-house.workspace = true
once_cell = "1.21"
arc-swap = "1"
regex = "1"
bitflags.workspace = true
-ahash = "0.8.11"
-hashbrown = { version = "0.14.5", features = ["raw"] }
+foldhash.workspace = true
url = "2.5.4"
log = "0.4"
diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs
index 6bb1f300c213..5985cac7805a 100644
--- a/helix-core/src/comment.rs
+++ b/helix-core/src/comment.rs
@@ -4,7 +4,8 @@
use smallvec::SmallVec;
use crate::{
- syntax::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril, Transaction,
+ syntax::config::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril,
+ Transaction,
};
use helix_stdx::rope::RopeSliceExt;
use std::borrow::Cow;
diff --git a/helix-core/src/config.rs b/helix-core/src/config.rs
index 27cd4e297e34..559aa2cb8492 100644
--- a/helix-core/src/config.rs
+++ b/helix-core/src/config.rs
@@ -1,4 +1,4 @@
-use crate::syntax::{Configuration, Loader, LoaderError};
+use crate::syntax::{config::Configuration, Loader, LoaderError};
/// Language configuration based on built-in languages.toml.
pub fn default_lang_config() -> Configuration {
diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs
index 04ce9a28dd23..a1e2c86405d8 100644
--- a/helix-core/src/indent.rs
+++ b/helix-core/src/indent.rs
@@ -1,14 +1,17 @@
use std::{borrow::Cow, collections::HashMap, iter};
use helix_stdx::rope::RopeSliceExt;
-use tree_sitter::{Query, QueryCursor, QueryPredicateArg};
use crate::{
chars::{char_is_line_ending, char_is_whitespace},
graphemes::{grapheme_width, tab_width_at},
- syntax::{IndentationHeuristic, LanguageConfiguration, RopeProvider, Syntax},
- tree_sitter::Node,
- Position, Rope, RopeSlice, Tendril,
+ syntax::{self, config::IndentationHeuristic},
+ tree_sitter::{
+ self,
+ query::{InvalidPredicateError, UserPredicate},
+ Capture, Grammar, InactiveQueryCursor, Node, Pattern, Query, QueryMatch, RopeInput,
+ },
+ Position, Rope, RopeSlice, Syntax, Tendril,
};
/// Enum representing indentation style.
@@ -279,18 +282,164 @@ fn add_indent_level(
/// Return true if only whitespace comes before the node on its line.
/// If given, new_line_byte_pos is treated the same way as any existing newline.
-fn is_first_in_line(node: Node, text: RopeSlice, new_line_byte_pos: Option) -> bool {
- let mut line_start_byte_pos = text.line_to_byte(node.start_position().row);
+fn is_first_in_line(node: &Node, text: RopeSlice, new_line_byte_pos: Option) -> bool {
+ let line = text.byte_to_line(node.start_byte() as usize);
+ let mut line_start_byte_pos = text.line_to_byte(line) as u32;
if let Some(pos) = new_line_byte_pos {
if line_start_byte_pos < pos && pos <= node.start_byte() {
line_start_byte_pos = pos;
}
}
- text.byte_slice(line_start_byte_pos..node.start_byte())
+ text.byte_slice(line_start_byte_pos as usize..node.start_byte() as usize)
.chars()
.all(|c| c.is_whitespace())
}
+#[derive(Debug, Default)]
+pub struct IndentQueryPredicates {
+ not_kind_eq: Option<(Capture, Box)>,
+ same_line: Option<(Capture, Capture, bool)>,
+ one_line: Option<(Capture, bool)>,
+}
+
+impl IndentQueryPredicates {
+ fn are_satisfied(
+ &self,
+ match_: &QueryMatch,
+ text: RopeSlice,
+ new_line_byte_pos: Option,
+ ) -> bool {
+ if let Some((capture, not_expected_kind)) = self.not_kind_eq.as_ref() {
+ if !match_
+ .nodes_for_capture(*capture)
+ .next()
+ .is_some_and(|node| node.kind() != not_expected_kind.as_ref())
+ {
+ return false;
+ }
+ }
+
+ if let Some((capture1, capture2, negated)) = self.same_line {
+ let n1 = match_.nodes_for_capture(capture1).next();
+ let n2 = match_.nodes_for_capture(capture2).next();
+ let satisfied = n1.zip(n2).is_some_and(|(n1, n2)| {
+ let n1_line = get_node_start_line(text, n1, new_line_byte_pos);
+ let n2_line = get_node_start_line(text, n2, new_line_byte_pos);
+ let same_line = n1_line == n2_line;
+ same_line != negated
+ });
+
+ if !satisfied {
+ return false;
+ }
+ }
+
+ if let Some((capture, negated)) = self.one_line {
+ let node = match_.nodes_for_capture(capture).next();
+ let satisfied = node.is_some_and(|node| {
+ let start_line = get_node_start_line(text, node, new_line_byte_pos);
+ let end_line = get_node_end_line(text, node, new_line_byte_pos);
+ let one_line = end_line == start_line;
+ one_line != negated
+ });
+
+ if !satisfied {
+ return false;
+ }
+ }
+
+ true
+ }
+}
+
+#[derive(Debug)]
+pub struct IndentQuery {
+ query: Query,
+ properties: HashMap,
+ predicates: HashMap,
+ indent_capture: Option,
+ indent_always_capture: Option,
+ outdent_capture: Option,
+ outdent_always_capture: Option,
+ align_capture: Option,
+ anchor_capture: Option,
+ extend_capture: Option,
+ extend_prevent_once_capture: Option,
+}
+
+impl IndentQuery {
+ pub fn new(grammar: Grammar, source: &str) -> Result {
+ let mut properties = HashMap::new();
+ let mut predicates: HashMap = HashMap::new();
+ let query = Query::new(grammar, source, |pattern, predicate| match predicate {
+ UserPredicate::SetProperty { key: "scope", val } => {
+ let scope = match val {
+ Some("all") => IndentScope::All,
+ Some("tail") => IndentScope::Tail,
+ Some(other) => {
+ return Err(format!("unknown scope (#set! scope \"{other}\")").into())
+ }
+ None => return Err("missing scope value (#set! scope ...)".into()),
+ };
+
+ properties.insert(pattern, scope);
+
+ Ok(())
+ }
+ UserPredicate::Other(predicate) => {
+ let name = predicate.name();
+ match name {
+ "not-kind-eq?" => {
+ predicate.check_arg_count(2)?;
+ let capture = predicate.capture_arg(0)?;
+ let not_expected_kind = predicate.str_arg(1)?;
+
+ predicates.entry(pattern).or_default().not_kind_eq =
+ Some((capture, not_expected_kind.to_string().into_boxed_str()));
+ Ok(())
+ }
+ "same-line?" | "not-same-line?" => {
+ predicate.check_arg_count(2)?;
+ let capture1 = predicate.capture_arg(0)?;
+ let capture2 = predicate.capture_arg(1)?;
+ let negated = name == "not-same-line?";
+
+ predicates.entry(pattern).or_default().same_line =
+ Some((capture1, capture2, negated));
+ Ok(())
+ }
+ "one-line?" | "not-one-line?" => {
+ predicate.check_arg_count(1)?;
+ let capture = predicate.capture_arg(0)?;
+ let negated = name == "not-one-line?";
+
+ predicates.entry(pattern).or_default().one_line = Some((capture, negated));
+ Ok(())
+ }
+ _ => Err(InvalidPredicateError::unknown(UserPredicate::Other(
+ predicate,
+ ))),
+ }
+ }
+ _ => Err(InvalidPredicateError::unknown(predicate)),
+ })?;
+
+ Ok(Self {
+ properties,
+ predicates,
+ indent_capture: query.get_capture("indent"),
+ indent_always_capture: query.get_capture("indent.always"),
+ outdent_capture: query.get_capture("outdent"),
+ outdent_always_capture: query.get_capture("outdent.always"),
+ align_capture: query.get_capture("align"),
+ anchor_capture: query.get_capture("anchor"),
+ extend_capture: query.get_capture("extend"),
+ extend_prevent_once_capture: query.get_capture("extend.prevent-once"),
+ query,
+ })
+ }
+}
+
/// The total indent for some line of code.
/// This is usually constructed in one of 2 ways:
/// - Successively add indent captures to get the (added) indent from a single line
@@ -453,16 +602,16 @@ struct IndentQueryResult<'a> {
extend_captures: HashMap>,
}
-fn get_node_start_line(node: Node, new_line_byte_pos: Option) -> usize {
- let mut node_line = node.start_position().row;
+fn get_node_start_line(text: RopeSlice, node: &Node, new_line_byte_pos: Option) -> usize {
+ let mut node_line = text.byte_to_line(node.start_byte() as usize);
// Adjust for the new line that will be inserted
if new_line_byte_pos.is_some_and(|pos| node.start_byte() >= pos) {
node_line += 1;
}
node_line
}
-fn get_node_end_line(node: Node, new_line_byte_pos: Option) -> usize {
- let mut node_line = node.end_position().row;
+fn get_node_end_line(text: RopeSlice, node: &Node, new_line_byte_pos: Option) -> usize {
+ let mut node_line = text.byte_to_line(node.end_byte() as usize);
// Adjust for the new line that will be inserted (with a strict inequality since end_byte is exclusive)
if new_line_byte_pos.is_some_and(|pos| node.end_byte() > pos) {
node_line += 1;
@@ -471,175 +620,98 @@ fn get_node_end_line(node: Node, new_line_byte_pos: Option) -> usize {
}
fn query_indents<'a>(
- query: &Query,
+ query: &IndentQuery,
syntax: &Syntax,
- cursor: &mut QueryCursor,
text: RopeSlice<'a>,
- range: std::ops::Range,
- new_line_byte_pos: Option,
+ range: std::ops::Range,
+ new_line_byte_pos: Option,
) -> IndentQueryResult<'a> {
let mut indent_captures: HashMap> = HashMap::new();
let mut extend_captures: HashMap> = HashMap::new();
+
+ let mut cursor = InactiveQueryCursor::new();
cursor.set_byte_range(range);
+ let mut cursor = cursor.execute_query(
+ &query.query,
+ &syntax.tree().root_node(),
+ RopeInput::new(text),
+ );
// Iterate over all captures from the query
- for m in cursor.matches(query, syntax.tree().root_node(), RopeProvider(text)) {
+ while let Some(m) = cursor.next_match() {
// Skip matches where not all custom predicates are fulfilled
- if !query.general_predicates(m.pattern_index).iter().all(|pred| {
- match pred.operator.as_ref() {
- "not-kind-eq?" => match (pred.args.first(), pred.args.get(1)) {
- (
- Some(QueryPredicateArg::Capture(capture_idx)),
- Some(QueryPredicateArg::String(kind)),
- ) => {
- let node = m.nodes_for_capture_index(*capture_idx).next();
- match node {
- Some(node) => node.kind()!=kind.as_ref(),
- _ => true,
- }
- }
- _ => {
- panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
- }
- },
- "same-line?" | "not-same-line?" => {
- match (pred.args.first(), pred.args.get(1)) {
- (
- Some(QueryPredicateArg::Capture(capt1)),
- Some(QueryPredicateArg::Capture(capt2))
- ) => {
- let n1 = m.nodes_for_capture_index(*capt1).next();
- let n2 = m.nodes_for_capture_index(*capt2).next();
- match (n1, n2) {
- (Some(n1), Some(n2)) => {
- let n1_line = get_node_start_line(n1, new_line_byte_pos);
- let n2_line = get_node_start_line(n2, new_line_byte_pos);
- let same_line = n1_line == n2_line;
- same_line==(pred.operator.as_ref()=="same-line?")
- }
- _ => true,
- }
- }
- _ => {
- panic!("Invalid indent query: Arguments to \"{}\" must be 2 captures", pred.operator);
- }
- }
- }
- "one-line?" | "not-one-line?" => match pred.args.first() {
- Some(QueryPredicateArg::Capture(capture_idx)) => {
- let node = m.nodes_for_capture_index(*capture_idx).next();
-
- match node {
- Some(node) => {
- let (start_line, end_line) = (get_node_start_line(node,new_line_byte_pos), get_node_end_line(node, new_line_byte_pos));
- let one_line = end_line == start_line;
- one_line != (pred.operator.as_ref() == "not-one-line?")
- },
- _ => true,
- }
- }
- _ => {
- panic!("Invalid indent query: Arguments to \"not-kind-eq?\" must be a capture and a string");
- }
- },
- _ => {
- panic!(
- "Invalid indent query: Unknown predicate (\"{}\")",
- pred.operator
- );
- }
- }
- }) {
+ if query
+ .predicates
+ .get(&m.pattern())
+ .is_some_and(|preds| !preds.are_satisfied(&m, text, new_line_byte_pos))
+ {
continue;
}
// A list of pairs (node_id, indent_capture) that are added by this match.
// They cannot be added to indent_captures immediately since they may depend on other captures (such as an @anchor).
let mut added_indent_captures: Vec<(usize, IndentCapture)> = Vec::new();
// The row/column position of the optional anchor in this query
- let mut anchor: Option = None;
- for capture in m.captures {
- let capture_name = query.capture_names()[capture.index as usize];
- let capture_type = match capture_name {
- "indent" => IndentCaptureType::Indent,
- "indent.always" => IndentCaptureType::IndentAlways,
- "outdent" => IndentCaptureType::Outdent,
- "outdent.always" => IndentCaptureType::OutdentAlways,
- // The alignment will be updated to the correct value at the end, when the anchor is known.
- "align" => IndentCaptureType::Align(RopeSlice::from("")),
- "anchor" => {
- if anchor.is_some() {
- log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
- } else {
- anchor = Some(capture.node);
- }
- continue;
- }
- "extend" => {
- extend_captures
- .entry(capture.node.id())
- .or_insert_with(|| Vec::with_capacity(1))
- .push(ExtendCapture::Extend);
- continue;
- }
- "extend.prevent-once" => {
- extend_captures
- .entry(capture.node.id())
- .or_insert_with(|| Vec::with_capacity(1))
- .push(ExtendCapture::PreventOnce);
- continue;
- }
- _ => {
- // Ignore any unknown captures (these may be needed for predicates such as #match?)
- continue;
+ let mut anchor: Option<&Node> = None;
+ for matched_node in m.matched_nodes() {
+ let node_id = matched_node.node.id();
+ let capture = Some(matched_node.capture);
+ let capture_type = if capture == query.indent_capture {
+ IndentCaptureType::Indent
+ } else if capture == query.indent_always_capture {
+ IndentCaptureType::IndentAlways
+ } else if capture == query.outdent_capture {
+ IndentCaptureType::Outdent
+ } else if capture == query.outdent_always_capture {
+ IndentCaptureType::OutdentAlways
+ } else if capture == query.align_capture {
+ IndentCaptureType::Align(RopeSlice::from(""))
+ } else if capture == query.anchor_capture {
+ if anchor.is_some() {
+ log::error!("Invalid indent query: Encountered more than one @anchor in the same match.")
+ } else {
+ anchor = Some(&matched_node.node);
}
+ continue;
+ } else if capture == query.extend_capture {
+ extend_captures
+ .entry(node_id)
+ .or_insert_with(|| Vec::with_capacity(1))
+ .push(ExtendCapture::Extend);
+ continue;
+ } else if capture == query.extend_prevent_once_capture {
+ extend_captures
+ .entry(node_id)
+ .or_insert_with(|| Vec::with_capacity(1))
+ .push(ExtendCapture::PreventOnce);
+ continue;
+ } else {
+ // Ignore any unknown captures (these may be needed for predicates such as #match?)
+ continue;
};
- let scope = capture_type.default_scope();
- let mut indent_capture = IndentCapture {
+
+ // Apply additional settings for this capture
+ let scope = query
+ .properties
+ .get(&m.pattern())
+ .copied()
+ .unwrap_or_else(|| capture_type.default_scope());
+ let indent_capture = IndentCapture {
capture_type,
scope,
};
- // Apply additional settings for this capture
- for property in query.property_settings(m.pattern_index) {
- match property.key.as_ref() {
- "scope" => {
- indent_capture.scope = match property.value.as_deref() {
- Some("all") => IndentScope::All,
- Some("tail") => IndentScope::Tail,
- Some(s) => {
- panic!("Invalid indent query: Unknown value for \"scope\" property (\"{}\")", s);
- }
- None => {
- panic!(
- "Invalid indent query: Missing value for \"scope\" property"
- );
- }
- }
- }
- _ => {
- panic!(
- "Invalid indent query: Unknown property \"{}\"",
- property.key
- );
- }
- }
- }
- added_indent_captures.push((capture.node.id(), indent_capture))
+ added_indent_captures.push((node_id, indent_capture))
}
for (node_id, mut capture) in added_indent_captures {
// Set the anchor for all align queries.
if let IndentCaptureType::Align(_) = capture.capture_type {
- let anchor = match anchor {
- None => {
- log::error!(
- "Invalid indent query: @align requires an accompanying @anchor."
- );
- continue;
- }
- Some(anchor) => anchor,
+ let Some(anchor) = anchor else {
+ log::error!("Invalid indent query: @align requires an accompanying @anchor.");
+ continue;
};
+ let line = text.byte_to_line(anchor.start_byte() as usize);
+ let line_start = text.line_to_byte(line);
capture.capture_type = IndentCaptureType::Align(
- text.line(anchor.start_position().row)
- .byte_slice(0..anchor.start_position().column),
+ text.byte_slice(line_start..anchor.start_byte() as usize),
);
}
indent_captures
@@ -691,13 +763,15 @@ fn extend_nodes<'a>(
// - the cursor is on the same line as the end of the node OR
// - the line that the cursor is on is more indented than the
// first line of the node
- if deepest_preceding.end_position().row == line {
+ if text.byte_to_line(deepest_preceding.end_byte() as usize) == line {
extend_node = true;
} else {
let cursor_indent =
indent_level_for_line(text.line(line), tab_width, indent_width);
let node_indent = indent_level_for_line(
- text.line(deepest_preceding.start_position().row),
+ text.line(
+ text.byte_to_line(deepest_preceding.start_byte() as usize),
+ ),
tab_width,
indent_width,
);
@@ -714,7 +788,7 @@ fn extend_nodes<'a>(
if node_captured && stop_extend {
stop_extend = false;
} else if extend_node && !stop_extend {
- *node = deepest_preceding;
+ *node = deepest_preceding.clone();
break;
}
// If the tree contains a syntax error, `deepest_preceding` may not
@@ -731,17 +805,17 @@ fn extend_nodes<'a>(
/// - The indent captures for all relevant nodes.
#[allow(clippy::too_many_arguments)]
fn init_indent_query<'a, 'b>(
- query: &Query,
+ query: &IndentQuery,
syntax: &'a Syntax,
text: RopeSlice<'b>,
tab_width: usize,
indent_width: usize,
line: usize,
- byte_pos: usize,
- new_line_byte_pos: Option,
+ byte_pos: u32,
+ new_line_byte_pos: Option,
) -> Option<(Node<'a>, HashMap>>)> {
// The innermost tree-sitter node which is considered for the indent
- // computation. It may change if some predeceding node is extended
+ // computation. It may change if some preceding node is extended
let mut node = syntax
.tree()
.root_node()
@@ -751,37 +825,25 @@ fn init_indent_query<'a, 'b>(
// The query range should intersect with all nodes directly preceding
// the position of the indent query in case one of them is extended.
let mut deepest_preceding = None; // The deepest node preceding the indent query position
- let mut tree_cursor = node.walk();
- for child in node.children(&mut tree_cursor) {
+ for child in node.children() {
if child.byte_range().end <= byte_pos {
- deepest_preceding = Some(child);
+ deepest_preceding = Some(child.clone());
}
}
deepest_preceding = deepest_preceding.map(|mut prec| {
// Get the deepest directly preceding node
while prec.child_count() > 0 {
- prec = prec.child(prec.child_count() - 1).unwrap();
+ prec = prec.child(prec.child_count() - 1).unwrap().clone();
}
prec
});
let query_range = deepest_preceding
+ .as_ref()
.map(|prec| prec.byte_range().end - 1..byte_pos + 1)
.unwrap_or(byte_pos..byte_pos + 1);
- crate::syntax::PARSER.with(|ts_parser| {
- let mut ts_parser = ts_parser.borrow_mut();
- let mut cursor = ts_parser.cursors.pop().unwrap_or_default();
- let query_result = query_indents(
- query,
- syntax,
- &mut cursor,
- text,
- query_range,
- new_line_byte_pos,
- );
- ts_parser.cursors.push(cursor);
- (query_result, deepest_preceding)
- })
+ let query_result = query_indents(query, syntax, text, query_range, new_line_byte_pos);
+ (query_result, deepest_preceding)
};
let extend_captures = query_result.extend_captures;
@@ -839,7 +901,7 @@ fn init_indent_query<'a, 'b>(
/// ```
#[allow(clippy::too_many_arguments)]
pub fn treesitter_indent_for_pos<'a>(
- query: &Query,
+ query: &IndentQuery,
syntax: &Syntax,
tab_width: usize,
indent_width: usize,
@@ -848,7 +910,7 @@ pub fn treesitter_indent_for_pos<'a>(
pos: usize,
new_line: bool,
) -> Option> {
- let byte_pos = text.char_to_byte(pos);
+ let byte_pos = text.char_to_byte(pos) as u32;
let new_line_byte_pos = new_line.then_some(byte_pos);
let (mut node, mut indent_captures) = init_indent_query(
query,
@@ -868,7 +930,7 @@ pub fn treesitter_indent_for_pos<'a>(
let mut indent_for_line_below = Indentation::default();
loop {
- let is_first = is_first_in_line(node, text, new_line_byte_pos);
+ let is_first = is_first_in_line(&node, text, new_line_byte_pos);
// Apply all indent definitions for this node.
// Since we only iterate over each node once, we can remove the
@@ -891,8 +953,8 @@ pub fn treesitter_indent_for_pos<'a>(
}
if let Some(parent) = node.parent() {
- let node_line = get_node_start_line(node, new_line_byte_pos);
- let parent_line = get_node_start_line(parent, new_line_byte_pos);
+ let node_line = get_node_start_line(text, &node, new_line_byte_pos);
+ let parent_line = get_node_start_line(text, &parent, new_line_byte_pos);
if node_line != parent_line {
// Don't add indent for the line below the line of the query
@@ -914,8 +976,9 @@ pub fn treesitter_indent_for_pos<'a>(
} else {
// Only add the indentation for the line below if that line
// is not after the line that the indentation is calculated for.
- if (node.start_position().row < line)
- || (new_line && node.start_position().row == line && node.start_byte() < byte_pos)
+ let node_start_line = text.byte_to_line(node.start_byte() as usize);
+ if node_start_line < line
+ || (new_line && node_start_line == line && node.start_byte() < byte_pos)
{
result.add_line(indent_for_line_below);
}
@@ -930,7 +993,7 @@ pub fn treesitter_indent_for_pos<'a>(
/// This is done either using treesitter, or if that's not available by copying the indentation from the current line
#[allow(clippy::too_many_arguments)]
pub fn indent_for_newline(
- language_config: Option<&LanguageConfiguration>,
+ loader: &syntax::Loader,
syntax: Option<&Syntax>,
indent_heuristic: &IndentationHeuristic,
indent_style: &IndentStyle,
@@ -947,7 +1010,7 @@ pub fn indent_for_newline(
Some(syntax),
) = (
indent_heuristic,
- language_config.and_then(|config| config.indent_query()),
+ syntax.and_then(|syntax| loader.indent_query(syntax.root_language())),
syntax,
) {
if let Some(indent) = treesitter_indent_for_pos(
@@ -1015,10 +1078,10 @@ pub fn indent_for_newline(
indent_style.as_str().repeat(indent_level)
}
-pub fn get_scopes(syntax: Option<&Syntax>, text: RopeSlice, pos: usize) -> Vec<&'static str> {
+pub fn get_scopes<'a>(syntax: Option<&'a Syntax>, text: RopeSlice, pos: usize) -> Vec<&'a str> {
let mut scopes = Vec::new();
if let Some(syntax) = syntax {
- let pos = text.char_to_byte(pos);
+ let pos = text.char_to_byte(pos) as u32;
let mut node = match syntax
.tree()
.root_node()
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 3fcddfcd189a..09865ca40456 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -53,7 +53,7 @@ pub use smartstring::SmartString;
pub type Tendril = SmartString;
#[doc(inline)]
-pub use {regex, tree_sitter};
+pub use {regex, tree_house::tree_sitter};
pub use position::{
char_idx_at_visual_offset, coords_at_pos, pos_at_coords, softwrapped_dimensions,
@@ -73,3 +73,5 @@ pub use line_ending::{LineEnding, NATIVE_LINE_ENDING};
pub use transaction::{Assoc, Change, ChangeSet, Deletion, Operation, Transaction};
pub use uri::Uri;
+
+pub use tree_house::Language;
diff --git a/helix-core/src/match_brackets.rs b/helix-core/src/match_brackets.rs
index 7520d3e4646a..7f2891f334b7 100644
--- a/helix-core/src/match_brackets.rs
+++ b/helix-core/src/match_brackets.rs
@@ -1,7 +1,7 @@
use std::iter;
+use crate::tree_sitter::Node;
use ropey::RopeSlice;
-use tree_sitter::Node;
use crate::movement::Direction::{self, Backward, Forward};
use crate::Syntax;
@@ -75,7 +75,7 @@ fn find_pair(
pos_: usize,
traverse_parents: bool,
) -> Option {
- let pos = doc.char_to_byte(pos_);
+ let pos = doc.char_to_byte(pos_) as u32;
let root = syntax.tree_for_byte_range(pos, pos).root_node();
let mut node = root.descendant_for_byte_range(pos, pos)?;
@@ -128,7 +128,7 @@ fn find_pair(
if find_pair_end(doc, sibling.prev_sibling(), start_char, end_char, Backward)
.is_some()
{
- return doc.try_byte_to_char(sibling.start_byte()).ok();
+ return doc.try_byte_to_char(sibling.start_byte() as usize).ok();
}
}
} else if node.is_named() {
@@ -144,9 +144,9 @@ fn find_pair(
if node.child_count() != 0 {
return None;
}
- let node_start = doc.byte_to_char(node.start_byte());
- find_matching_bracket_plaintext(doc.byte_slice(node.byte_range()), pos_ - node_start)
- .map(|pos| pos + node_start)
+ let node_start = doc.byte_to_char(node.start_byte() as usize);
+ let node_text = doc.byte_slice(node.start_byte() as usize..node.end_byte() as usize);
+ find_matching_bracket_plaintext(node_text, pos_ - node_start).map(|pos| pos + node_start)
}
/// Returns the position of the matching bracket under cursor.
@@ -304,7 +304,7 @@ fn as_char(doc: RopeSlice, node: &Node) -> Option<(usize, char)> {
if node.byte_range().len() != 1 {
return None;
}
- let pos = doc.try_byte_to_char(node.start_byte()).ok()?;
+ let pos = doc.try_byte_to_char(node.start_byte() as usize).ok()?;
Some((pos, doc.char(pos)))
}
diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs
index e446d8cc425d..09a99db2575f 100644
--- a/helix-core/src/movement.rs
+++ b/helix-core/src/movement.rs
@@ -1,7 +1,6 @@
-use std::{cmp::Reverse, iter};
+use std::{borrow::Cow, cmp::Reverse, iter};
use ropey::iter::Chars;
-use tree_sitter::{Node, QueryCursor};
use crate::{
char_idx_at_visual_offset,
@@ -13,9 +12,10 @@ use crate::{
},
line_ending::rope_is_line_ending,
position::char_idx_at_visual_block_offset,
- syntax::LanguageConfiguration,
+ syntax,
text_annotations::TextAnnotations,
textobject::TextObject,
+ tree_sitter::Node,
visual_offset_from_block, Range, RopeSlice, Selection, Syntax,
};
@@ -560,21 +560,23 @@ fn reached_target(target: WordMotionTarget, prev_ch: char, next_ch: char) -> boo
/// Finds the range of the next or previous textobject in the syntax sub-tree of `node`.
/// Returns the range in the forwards direction.
+#[allow(clippy::too_many_arguments)]
pub fn goto_treesitter_object(
slice: RopeSlice,
range: Range,
object_name: &str,
dir: Direction,
- slice_tree: Node,
- lang_config: &LanguageConfiguration,
+ slice_tree: &Node,
+ syntax: &Syntax,
+ loader: &syntax::Loader,
count: usize,
) -> Range {
+ let textobject_query = loader.textobject_query(syntax.root_language());
let get_range = move |range: Range| -> Option {
let byte_pos = slice.char_to_byte(range.cursor(slice));
let cap_name = |t: TextObject| format!("{}.{}", object_name, t);
- let mut cursor = QueryCursor::new();
- let nodes = lang_config.textobject_query()?.capture_nodes_any(
+ let nodes = textobject_query?.capture_nodes_any(
&[
&cap_name(TextObject::Movement),
&cap_name(TextObject::Around),
@@ -582,7 +584,6 @@ pub fn goto_treesitter_object(
],
slice_tree,
slice,
- &mut cursor,
)?;
let node = match dir {
@@ -617,14 +618,15 @@ pub fn goto_treesitter_object(
last_range
}
-fn find_parent_start(mut node: Node) -> Option {
+fn find_parent_start<'tree>(node: &Node<'tree>) -> Option> {
let start = node.start_byte();
+ let mut node = Cow::Borrowed(node);
while node.start_byte() >= start || !node.is_named() {
- node = node.parent()?;
+ node = Cow::Owned(node.parent()?);
}
- Some(node)
+ Some(node.into_owned())
}
pub fn move_parent_node_end(
@@ -635,8 +637,8 @@ pub fn move_parent_node_end(
movement: Movement,
) -> Selection {
selection.transform(|range| {
- let start_from = text.char_to_byte(range.from());
- let start_to = text.char_to_byte(range.to());
+ let start_from = text.char_to_byte(range.from()) as u32;
+ let start_to = text.char_to_byte(range.to()) as u32;
let mut node = match syntax.named_descendant_for_byte_range(start_from, start_to) {
Some(node) => node,
@@ -654,18 +656,18 @@ pub fn move_parent_node_end(
// moving forward, we always want to move one past the end of the
// current node, so use the end byte of the current node, which is an exclusive
// end of the range
- Direction::Forward => text.byte_to_char(node.end_byte()),
+ Direction::Forward => text.byte_to_char(node.end_byte() as usize),
// moving backward, we want the cursor to land on the start char of
// the current node, or if it is already at the start of a node, to traverse up to
// the parent
Direction::Backward => {
- let end_head = text.byte_to_char(node.start_byte());
+ let end_head = text.byte_to_char(node.start_byte() as usize);
// if we're already on the beginning, look up to the parent
if end_head == range.cursor(text) {
- node = find_parent_start(node).unwrap_or(node);
- text.byte_to_char(node.start_byte())
+ node = find_parent_start(&node).unwrap_or(node);
+ text.byte_to_char(node.start_byte() as usize)
} else {
end_head
}
diff --git a/helix-core/src/object.rs b/helix-core/src/object.rs
index 17a393caf277..e0c02d0a905e 100644
--- a/helix-core/src/object.rs
+++ b/helix-core/src/object.rs
@@ -4,8 +4,8 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
let cursor = &mut syntax.walk();
selection.transform(|range| {
- let from = text.char_to_byte(range.from());
- let to = text.char_to_byte(range.to());
+ let from = text.char_to_byte(range.from()) as u32;
+ let to = text.char_to_byte(range.to()) as u32;
let byte_range = from..to;
cursor.reset_to_byte_range(from, to);
@@ -17,8 +17,8 @@ pub fn expand_selection(syntax: &Syntax, text: RopeSlice, selection: Selection)
}
let node = cursor.node();
- let from = text.byte_to_char(node.start_byte());
- let to = text.byte_to_char(node.end_byte());
+ let from = text.byte_to_char(node.start_byte() as usize);
+ let to = text.byte_to_char(node.end_byte() as usize);
Range::new(to, from).with_direction(range.direction())
})
@@ -53,10 +53,10 @@ pub fn select_next_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio
}
pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
- selection.transform_iter(|range| {
- let mut cursor = syntax.walk();
+ let mut cursor = syntax.walk();
+ selection.transform_iter(move |range| {
let (from, to) = range.into_byte_range(text);
- cursor.reset_to_byte_range(from, to);
+ cursor.reset_to_byte_range(from as u32, to as u32);
if !cursor.goto_parent_with(|parent| parent.child_count() > 1) {
return vec![range].into_iter();
@@ -67,21 +67,18 @@ pub fn select_all_siblings(syntax: &Syntax, text: RopeSlice, selection: Selectio
}
pub fn select_all_children(syntax: &Syntax, text: RopeSlice, selection: Selection) -> Selection {
- selection.transform_iter(|range| {
- let mut cursor = syntax.walk();
+ let mut cursor = syntax.walk();
+ selection.transform_iter(move |range| {
let (from, to) = range.into_byte_range(text);
- cursor.reset_to_byte_range(from, to);
+ cursor.reset_to_byte_range(from as u32, to as u32);
select_children(&mut cursor, text, range).into_iter()
})
}
-fn select_children<'n>(
- cursor: &'n mut TreeCursor<'n>,
- text: RopeSlice,
- range: Range,
-) -> Vec {
+fn select_children(cursor: &mut TreeCursor, text: RopeSlice, range: Range) -> Vec {
let children = cursor
- .named_children()
+ .children()
+ .filter(|child| child.is_named())
.map(|child| Range::from_node(child, text, range.direction()))
.collect::>();
@@ -98,7 +95,7 @@ pub fn select_prev_sibling(syntax: &Syntax, text: RopeSlice, selection: Selectio
text,
selection,
|cursor| {
- while !cursor.goto_prev_sibling() {
+ while !cursor.goto_previous_sibling() {
if !cursor.goto_parent() {
break;
}
@@ -121,16 +118,16 @@ where
let cursor = &mut syntax.walk();
selection.transform(|range| {
- let from = text.char_to_byte(range.from());
- let to = text.char_to_byte(range.to());
+ let from = text.char_to_byte(range.from()) as u32;
+ let to = text.char_to_byte(range.to()) as u32;
cursor.reset_to_byte_range(from, to);
motion(cursor);
let node = cursor.node();
- let from = text.byte_to_char(node.start_byte());
- let to = text.byte_to_char(node.end_byte());
+ let from = text.byte_to_char(node.start_byte() as usize);
+ let to = text.byte_to_char(node.end_byte() as usize);
Range::new(from, to).with_direction(direction.unwrap_or_else(|| range.direction()))
})
diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs
index cea0b60714b4..3f888c57a853 100644
--- a/helix-core/src/position.rs
+++ b/helix-core/src/position.rs
@@ -89,11 +89,6 @@ impl From<(usize, usize)> for Position {
}
}
-impl From for tree_sitter::Point {
- fn from(pos: Position) -> Self {
- Self::new(pos.row, pos.col)
- }
-}
/// Convert a character index to (line, column) coordinates.
///
/// column in `char` count which can be used for row:column display in
diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs
index 1db2d619e614..5bde08e31ba7 100644
--- a/helix-core/src/selection.rs
+++ b/helix-core/src/selection.rs
@@ -9,13 +9,13 @@ use crate::{
},
line_ending::get_line_ending,
movement::Direction,
+ tree_sitter::Node,
Assoc, ChangeSet, RopeSlice,
};
use helix_stdx::range::is_subset;
use helix_stdx::rope::{self, RopeSliceExt};
use smallvec::{smallvec, SmallVec};
use std::{borrow::Cow, iter, slice};
-use tree_sitter::Node;
/// A single selection range.
///
@@ -76,8 +76,8 @@ impl Range {
}
pub fn from_node(node: Node, text: RopeSlice, direction: Direction) -> Self {
- let from = text.byte_to_char(node.start_byte());
- let to = text.byte_to_char(node.end_byte());
+ let from = text.byte_to_char(node.start_byte() as usize);
+ let to = text.byte_to_char(node.end_byte() as usize);
Range::new(from, to).with_direction(direction)
}
diff --git a/helix-core/src/snippets/active.rs b/helix-core/src/snippets/active.rs
index 98007ab68caf..1c10b76d20a8 100644
--- a/helix-core/src/snippets/active.rs
+++ b/helix-core/src/snippets/active.rs
@@ -1,6 +1,6 @@
use std::ops::{Index, IndexMut};
-use hashbrown::HashSet;
+use foldhash::HashSet;
use helix_stdx::range::{is_exact_subset, is_subset};
use helix_stdx::Range;
use ropey::Rope;
@@ -35,7 +35,7 @@ impl ActiveSnippet {
let snippet = Self {
ranges: snippet.ranges,
tabstops: snippet.tabstops,
- active_tabstops: HashSet::new(),
+ active_tabstops: HashSet::default(),
current_tabstop: TabstopIdx(0),
};
(snippet.tabstops.len() != 1).then_some(snippet)
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 677cdfa0b673..f3630a29522c 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -1,2709 +1,982 @@
-mod tree_cursor;
-
-use crate::{
- auto_pairs::AutoPairs,
- chars::char_is_line_ending,
- diagnostic::Severity,
- regex::Regex,
- transaction::{ChangeSet, Operation},
- RopeSlice, Tendril,
-};
-
-use ahash::RandomState;
-use arc_swap::{ArcSwap, Guard};
-use bitflags::bitflags;
-use globset::GlobSet;
-use hashbrown::raw::RawTable;
-use helix_stdx::rope::{self, RopeSliceExt};
-use slotmap::{DefaultKey as LayerId, HopSlotMap};
+pub mod config;
use std::{
borrow::Cow,
- cell::RefCell,
- collections::{HashMap, HashSet, VecDeque},
- fmt::{self, Display, Write},
- hash::{Hash, Hasher},
- mem::replace,
- path::{Path, PathBuf},
- str::FromStr,
+ collections::HashMap,
+ fmt, iter,
+ ops::{self, RangeBounds},
+ path::Path,
sync::Arc,
+ time::Duration,
};
-use once_cell::sync::{Lazy, OnceCell};
-use serde::{ser::SerializeSeq, Deserialize, Serialize};
+use anyhow::{Context, Result};
+use arc_swap::{ArcSwap, Guard};
+use config::{Configuration, FileType, LanguageConfiguration, LanguageServerConfiguration};
+use foldhash::HashSet;
+use helix_loader::grammar::get_language;
+use helix_stdx::rope::RopeSliceExt as _;
+use once_cell::sync::OnceCell;
+use ropey::RopeSlice;
+use tree_house::{
+ highlighter,
+ query_iter::{QueryIter, QueryIterEvent},
+ tree_sitter::{
+ query::{InvalidPredicateError, UserPredicate},
+ Capture, Grammar, InactiveQueryCursor, InputEdit, Node, Pattern, Query, RopeInput, Tree,
+ },
+ Error, InjectionLanguageMarker, LanguageConfig as SyntaxConfig, Layer,
+};
-use helix_loader::grammar::{get_language, load_runtime_file};
+use crate::{indent::IndentQuery, tree_sitter, ChangeSet, Language};
-pub use tree_cursor::TreeCursor;
+pub use tree_house::{
+ highlighter::{Highlight, HighlightEvent},
+ Error as HighlighterError, LanguageLoader, TreeCursor, TREE_SITTER_MATCH_LIMIT,
+};
-fn deserialize_regex<'de, D>(deserializer: D) -> Result