aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Davis2024-05-01 22:52:02 +0000
committerJJ2024-05-01 23:54:41 +0000
commit9a3f23b0661f7a37a0dab885fe5eb844b615a22b (patch)
tree48a816a487a38bf3145263a61d96c4a06d28765f
parent214f7ba218223cd292fb1788ad203e5168657975 (diff)
Add rainbow tree-sitter highlights
ref: https://github.com/helix-editor/helix/issues/695 ref: https://github.com/helix-editor/helix/pull/2857
-rw-r--r--book/src/SUMMARY.md1
-rw-r--r--book/src/configuration.md1
-rw-r--r--book/src/generated/lang-support.md430
-rw-r--r--book/src/guides/README.md2
-rw-r--r--book/src/guides/rainbow_bracket_queries.md132
-rw-r--r--book/src/languages.md2
-rw-r--r--book/src/themes.md11
-rw-r--r--helix-core/src/syntax.rs419
-rw-r--r--helix-term/src/health.rs11
-rw-r--r--helix-term/src/ui/editor.rs55
-rw-r--r--helix-view/src/editor.rs8
-rw-r--r--helix-view/src/theme.rs113
-rw-r--r--runtime/queries/bash/rainbows.scm21
-rw-r--r--runtime/queries/c/rainbows.scm29
-rw-r--r--runtime/queries/clojure/rainbows.scm13
-rw-r--r--runtime/queries/common-lisp/rainbows.scm1
-rw-r--r--runtime/queries/cpp/rainbows.scm49
-rw-r--r--runtime/queries/css/rainbows.scm15
-rw-r--r--runtime/queries/ecma/rainbows.scm28
-rw-r--r--runtime/queries/elixir/rainbows.scm24
-rw-r--r--runtime/queries/erlang/rainbows.scm24
-rw-r--r--runtime/queries/gleam/rainbows.scm32
-rw-r--r--runtime/queries/go/rainbows.scm33
-rw-r--r--runtime/queries/html/rainbows.scm13
-rw-r--r--runtime/queries/java/rainbows.scm35
-rw-r--r--runtime/queries/javascript/rainbows.scm1
-rw-r--r--runtime/queries/json/rainbows.scm9
-rw-r--r--runtime/queries/jsx/rainbows.scm9
-rw-r--r--runtime/queries/nix/rainbows.scm17
-rw-r--r--runtime/queries/python/rainbows.scm28
-rw-r--r--runtime/queries/racket/rainbows.scm1
-rw-r--r--runtime/queries/regex/rainbows.scm17
-rw-r--r--runtime/queries/ruby/rainbows.scm28
-rw-r--r--runtime/queries/rust/rainbows.scm60
-rw-r--r--runtime/queries/scheme/rainbows.scm12
-rw-r--r--runtime/queries/scss/rainbows.scm3
-rw-r--r--runtime/queries/starlark/rainbows.scm1
-rw-r--r--runtime/queries/toml/rainbows.scm12
-rw-r--r--runtime/queries/tsq/rainbows.scm12
-rw-r--r--runtime/queries/tsx/rainbows.scm2
-rw-r--r--runtime/queries/typescript/rainbows.scm19
-rw-r--r--runtime/queries/xml/rainbows.scm29
-rw-r--r--runtime/queries/yaml/rainbows.scm9
-rw-r--r--runtime/queries/zig/rainbows.scm42
-rw-r--r--xtask/src/querycheck.rs1
45 files changed, 1517 insertions, 297 deletions
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
index ba330cf7..dcd128de 100644
--- a/book/src/SUMMARY.md
+++ b/book/src/SUMMARY.md
@@ -17,3 +17,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/configuration.md b/book/src/configuration.md
index 63c20334..22531868 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -70,6 +70,7 @@ Its settings will be merged with the configuration directory `config.toml` and t
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
| `initial-mode` | The initial mode for newly opened editors. | `"normal"` |
+| `rainbow-brackets` | Whether to render rainbow colors for matching brackets. Requires tree-sitter `rainbows.scm` queries for the language. | `false` |
### `[editor.statusline]` Section
diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md
index 9acab0de..05bdbd59 100644
--- a/book/src/generated/lang-support.md
+++ b/book/src/generated/lang-support.md
@@ -1,215 +1,215 @@
-| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Default LSP |
-| --- | --- | --- | --- | --- |
-| ada | ✓ | ✓ | | `ada_language_server`, `ada_language_server` |
-| agda | ✓ | | | |
-| astro | ✓ | | | |
-| awk | ✓ | ✓ | | `awk-language-server` |
-| bash | ✓ | ✓ | ✓ | `bash-language-server` |
-| bass | ✓ | | | `bass` |
-| beancount | ✓ | | | |
-| bibtex | ✓ | | | `texlab` |
-| bicep | ✓ | | | `bicep-langserver` |
-| blade | ✓ | | | |
-| blueprint | ✓ | | | `blueprint-compiler` |
-| c | ✓ | ✓ | ✓ | `clangd` |
-| c-sharp | ✓ | ✓ | | `OmniSharp` |
-| cabal | | | | `haskell-language-server-wrapper` |
-| cairo | ✓ | ✓ | ✓ | `cairo-language-server` |
-| capnp | ✓ | | ✓ | |
-| cel | ✓ | | | |
-| clojure | ✓ | | | `clojure-lsp` |
-| cmake | ✓ | ✓ | ✓ | `cmake-language-server` |
-| comment | ✓ | | | |
-| common-lisp | ✓ | | ✓ | `cl-lsp` |
-| cpon | ✓ | | ✓ | |
-| cpp | ✓ | ✓ | ✓ | `clangd` |
-| crystal | ✓ | ✓ | | `crystalline` |
-| css | ✓ | | ✓ | `vscode-css-language-server` |
-| cue | ✓ | | | `cuelsp` |
-| d | ✓ | ✓ | ✓ | `serve-d` |
-| dart | ✓ | ✓ | ✓ | `dart` |
-| dbml | ✓ | | | |
-| devicetree | ✓ | | | |
-| dhall | ✓ | ✓ | | `dhall-lsp-server` |
-| diff | ✓ | | | |
-| docker-compose | ✓ | | ✓ | `docker-compose-langserver`, `yaml-language-server` |
-| dockerfile | ✓ | | | `docker-langserver` |
-| dot | ✓ | | | `dot-language-server` |
-| dtd | ✓ | | | |
-| edoc | ✓ | | | |
-| eex | ✓ | | | |
-| ejs | ✓ | | | |
-| elixir | ✓ | ✓ | ✓ | `elixir-ls` |
-| elm | ✓ | ✓ | | `elm-language-server` |
-| elvish | ✓ | | | `elvish` |
-| env | ✓ | | | |
-| erb | ✓ | | | |
-| erlang | ✓ | ✓ | | `erlang_ls` |
-| esdl | ✓ | | | |
-| fidl | ✓ | | | |
-| fish | ✓ | ✓ | ✓ | |
-| forth | ✓ | | | `forth-lsp` |
-| fortran | ✓ | | ✓ | `fortls` |
-| fsharp | ✓ | | | `fsautocomplete` |
-| gas | ✓ | ✓ | | |
-| gdscript | ✓ | ✓ | ✓ | |
-| gemini | ✓ | | | |
-| git-attributes | ✓ | | | |
-| git-commit | ✓ | ✓ | | |
-| git-config | ✓ | | | |
-| git-ignore | ✓ | | | |
-| git-rebase | ✓ | | | |
-| gleam | ✓ | ✓ | | `gleam` |
-| glimmer | ✓ | | | `ember-language-server` |
-| glsl | ✓ | ✓ | ✓ | |
-| gn | ✓ | | | |
-| go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
-| godot-resource | ✓ | | | |
-| gomod | ✓ | | | `gopls` |
-| gotmpl | ✓ | | | `gopls` |
-| gowork | ✓ | | | `gopls` |
-| graphql | ✓ | | | `graphql-lsp` |
-| groovy | ✓ | | | |
-| 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` |
-| hurl | ✓ | | ✓ | |
-| hyprlang | ✓ | | ✓ | |
-| idris | | | | `idris2-lsp` |
-| iex | ✓ | | | |
-| ini | ✓ | | | |
-| janet | ✓ | | | |
-| java | ✓ | ✓ | ✓ | `jdtls` |
-| javascript | ✓ | ✓ | ✓ | `typescript-language-server` |
-| jinja | ✓ | | | |
-| 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` |
-| latex | ✓ | ✓ | | `texlab` |
-| ld | ✓ | | ✓ | |
-| lean | ✓ | | | `lean` |
-| ledger | ✓ | | | |
-| llvm | ✓ | ✓ | ✓ | |
-| llvm-mir | ✓ | ✓ | ✓ | |
-| llvm-mir-yaml | ✓ | | ✓ | |
-| log | ✓ | | | |
-| lpf | ✓ | | | |
-| lua | ✓ | ✓ | ✓ | `lua-language-server` |
-| make | ✓ | | ✓ | |
-| markdoc | ✓ | | | `markdoc-ls` |
-| markdown | ✓ | | | `marksman`, `markdown-oxide` |
-| markdown.inline | ✓ | | | |
-| matlab | ✓ | ✓ | ✓ | |
-| mermaid | ✓ | | | |
-| meson | ✓ | | ✓ | |
-| mint | | | | `mint` |
-| msbuild | ✓ | | ✓ | |
-| nasm | ✓ | ✓ | | |
-| nickel | ✓ | | ✓ | `nls` |
-| nim | ✓ | ✓ | ✓ | `nimlangserver` |
-| nix | ✓ | ✓ | | `nil` |
-| nu | ✓ | | | `nu` |
-| nunjucks | ✓ | | | |
-| ocaml | ✓ | | ✓ | `ocamllsp` |
-| ocaml-interface | ✓ | | | `ocamllsp` |
-| odin | ✓ | | ✓ | `ols` |
-| ohm | ✓ | ✓ | ✓ | |
-| opencl | ✓ | ✓ | ✓ | `clangd` |
-| openscad | ✓ | | | `openscad-lsp` |
-| org | ✓ | | | |
-| pascal | ✓ | ✓ | | `pasls` |
-| passwd | ✓ | | | |
-| pem | ✓ | | | |
-| perl | ✓ | ✓ | ✓ | `perlnavigator` |
-| php | ✓ | ✓ | ✓ | `intelephense` |
-| php-only | ✓ | | | |
-| pkgbuild | ✓ | ✓ | ✓ | `pkgbuild-language-server`, `bash-language-server` |
-| pkl | ✓ | | ✓ | |
-| po | ✓ | ✓ | | |
-| pod | ✓ | | | |
-| ponylang | ✓ | ✓ | ✓ | |
-| powershell | ✓ | | | |
-| prisma | ✓ | | | `prisma-language-server` |
-| prolog | | | | `swipl` |
-| protobuf | ✓ | ✓ | ✓ | `bufls`, `pb` |
-| prql | ✓ | | | |
-| purescript | ✓ | ✓ | | `purescript-language-server` |
-| python | ✓ | ✓ | ✓ | `pylsp` |
-| qml | ✓ | | ✓ | `qmlls` |
-| r | ✓ | | | `R` |
-| racket | ✓ | | ✓ | `racket` |
-| regex | ✓ | | | |
-| rego | ✓ | | | `regols` |
-| rescript | ✓ | ✓ | | `rescript-language-server` |
-| rmarkdown | ✓ | | ✓ | `R` |
-| robot | ✓ | | | `robotframework_ls` |
-| ron | ✓ | | ✓ | |
-| rst | ✓ | | | |
-| ruby | ✓ | ✓ | ✓ | `solargraph` |
-| rust | ✓ | ✓ | ✓ | `rust-analyzer` |
-| sage | ✓ | ✓ | | |
-| scala | ✓ | ✓ | ✓ | `metals` |
-| scheme | ✓ | | ✓ | |
-| scss | ✓ | | | `vscode-css-language-server` |
-| slint | ✓ | ✓ | ✓ | `slint-lsp` |
-| smali | ✓ | | ✓ | |
-| smithy | ✓ | | | `cs` |
-| sml | ✓ | | | |
-| solidity | ✓ | | | `solc` |
-| spicedb | ✓ | | | |
-| sql | ✓ | | | |
-| sshclientconfig | ✓ | | | |
-| starlark | ✓ | ✓ | | |
-| strace | ✓ | | | |
-| supercollider | ✓ | | | |
-| svelte | ✓ | | ✓ | `svelteserver` |
-| sway | ✓ | ✓ | ✓ | `forc` |
-| swift | ✓ | | | `sourcekit-lsp` |
-| t32 | ✓ | | | |
-| tablegen | ✓ | ✓ | ✓ | |
-| tact | ✓ | ✓ | ✓ | |
-| task | ✓ | | | |
-| templ | ✓ | | | `templ` |
-| tfvars | ✓ | | ✓ | `terraform-ls` |
-| todotxt | ✓ | | | |
-| toml | ✓ | | | `taplo` |
-| tsq | ✓ | | | |
-| tsx | ✓ | ✓ | ✓ | `typescript-language-server` |
-| twig | ✓ | | | |
-| typescript | ✓ | ✓ | ✓ | `typescript-language-server` |
-| typst | ✓ | | | `typst-lsp` |
-| ungrammar | ✓ | | | |
-| unison | ✓ | | ✓ | |
-| uxntal | ✓ | | | |
-| v | ✓ | ✓ | ✓ | `v-analyzer` |
-| vala | ✓ | ✓ | | `vala-language-server` |
-| verilog | ✓ | ✓ | | `svlangserver` |
-| vhdl | ✓ | | | `vhdl_ls` |
-| vhs | ✓ | | | |
-| vue | ✓ | | | `vue-language-server` |
-| wast | ✓ | | | |
-| wat | ✓ | | | |
-| webc | ✓ | | | |
-| wgsl | ✓ | | | `wgsl_analyzer` |
-| wit | ✓ | | ✓ | |
-| wren | ✓ | ✓ | ✓ | |
-| xit | ✓ | | | |
-| xml | ✓ | | ✓ | |
-| yaml | ✓ | | ✓ | `yaml-language-server`, `ansible-language-server` |
-| yuck | ✓ | | | |
-| zig | ✓ | ✓ | ✓ | `zls` |
+| Language | Syntax Highlighting | Treesitter Textobjects | Auto Indent | Rainbow Brackets | Default LSP |
+| --- | --- | --- | --- | --- | --- |
+| ada | ✓ | ✓ | | | `ada_language_server`, `ada_language_server` |
+| agda | ✓ | | | | |
+| astro | ✓ | | | | |
+| awk | ✓ | ✓ | | | `awk-language-server` |
+| bash | ✓ | ✓ | ✓ | ✓ | `bash-language-server` |
+| bass | ✓ | | | | `bass` |
+| beancount | ✓ | | | | |
+| bibtex | ✓ | | | | `texlab` |
+| bicep | ✓ | | | | `bicep-langserver` |
+| blade | ✓ | | | | |
+| blueprint | ✓ | | | | `blueprint-compiler` |
+| c | ✓ | ✓ | ✓ | ✓ | `clangd` |
+| c-sharp | ✓ | ✓ | | | `OmniSharp` |
+| cabal | | | | | `haskell-language-server-wrapper` |
+| cairo | ✓ | ✓ | ✓ | | `cairo-language-server` |
+| capnp | ✓ | | ✓ | | |
+| cel | ✓ | | | | |
+| clojure | ✓ | | | ✓ | `clojure-lsp` |
+| cmake | ✓ | ✓ | ✓ | | `cmake-language-server` |
+| comment | ✓ | | | | |
+| common-lisp | ✓ | | ✓ | ✓ | `cl-lsp` |
+| cpon | ✓ | | ✓ | | |
+| cpp | ✓ | ✓ | ✓ | ✓ | `clangd` |
+| crystal | ✓ | ✓ | | | `crystalline` |
+| css | ✓ | | ✓ | ✓ | `vscode-css-language-server` |
+| cue | ✓ | | | | `cuelsp` |
+| d | ✓ | ✓ | ✓ | | `serve-d` |
+| dart | ✓ | ✓ | ✓ | | `dart` |
+| dbml | ✓ | | | | |
+| devicetree | ✓ | | | | |
+| dhall | ✓ | ✓ | | | `dhall-lsp-server` |
+| diff | ✓ | | | | |
+| docker-compose | ✓ | | ✓ | | `docker-compose-langserver`, `yaml-language-server` |
+| dockerfile | ✓ | | | | `docker-langserver` |
+| dot | ✓ | | | | `dot-language-server` |
+| dtd | ✓ | | | | |
+| edoc | ✓ | | | | |
+| eex | ✓ | | | | |
+| ejs | ✓ | | | | |
+| elixir | ✓ | ✓ | ✓ | ✓ | `elixir-ls` |
+| elm | ✓ | ✓ | | | `elm-language-server` |
+| elvish | ✓ | | | | `elvish` |
+| env | ✓ | | | | |
+| erb | ✓ | | | | |
+| erlang | ✓ | ✓ | | ✓ | `erlang_ls` |
+| esdl | ✓ | | | | |
+| fidl | ✓ | | | | |
+| fish | ✓ | ✓ | ✓ | | |
+| forth | ✓ | | | | `forth-lsp` |
+| fortran | ✓ | | ✓ | | `fortls` |
+| fsharp | ✓ | | | | `fsautocomplete` |
+| gas | ✓ | ✓ | | | |
+| gdscript | ✓ | ✓ | ✓ | | |
+| gemini | ✓ | | | | |
+| git-attributes | ✓ | | | | |
+| git-commit | ✓ | ✓ | | | |
+| git-config | ✓ | | | | |
+| git-ignore | ✓ | | | | |
+| git-rebase | ✓ | | | | |
+| gleam | ✓ | ✓ | | ✓ | `gleam` |
+| glimmer | ✓ | | | | `ember-language-server` |
+| glsl | ✓ | ✓ | ✓ | | |
+| gn | ✓ | | | | |
+| go | ✓ | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` |
+| godot-resource | ✓ | | | | |
+| gomod | ✓ | | | | `gopls` |
+| gotmpl | ✓ | | | | `gopls` |
+| gowork | ✓ | | | | `gopls` |
+| graphql | ✓ | | | | `graphql-lsp` |
+| groovy | ✓ | | | | |
+| 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` |
+| hurl | ✓ | | ✓ | | |
+| hyprlang | ✓ | | ✓ | | |
+| idris | | | | | `idris2-lsp` |
+| iex | ✓ | | | | |
+| ini | ✓ | | | | |
+| janet | ✓ | | | | |
+| java | ✓ | ✓ | ✓ | ✓ | `jdtls` |
+| javascript | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| jinja | ✓ | | | | |
+| 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` |
+| latex | ✓ | ✓ | | | `texlab` |
+| ld | ✓ | | ✓ | | |
+| lean | ✓ | | | | `lean` |
+| ledger | ✓ | | | | |
+| llvm | ✓ | ✓ | ✓ | | |
+| llvm-mir | ✓ | ✓ | ✓ | | |
+| llvm-mir-yaml | ✓ | | ✓ | | |
+| log | ✓ | | | | |
+| lpf | ✓ | | | | |
+| lua | ✓ | ✓ | ✓ | | `lua-language-server` |
+| make | ✓ | | ✓ | | |
+| markdoc | ✓ | | | | `markdoc-ls` |
+| markdown | ✓ | | | | `marksman`, `markdown-oxide` |
+| markdown.inline | ✓ | | | | |
+| matlab | ✓ | ✓ | ✓ | | |
+| mermaid | ✓ | | | | |
+| meson | ✓ | | ✓ | | |
+| mint | | | | | `mint` |
+| msbuild | ✓ | | ✓ | | |
+| nasm | ✓ | ✓ | | | |
+| nickel | ✓ | | ✓ | | `nls` |
+| nim | ✓ | ✓ | ✓ | | `nimlangserver` |
+| nix | ✓ | ✓ | | ✓ | `nil` |
+| nu | ✓ | | | | `nu` |
+| nunjucks | ✓ | | | | |
+| ocaml | ✓ | | ✓ | | `ocamllsp` |
+| ocaml-interface | ✓ | | | | `ocamllsp` |
+| odin | ✓ | | ✓ | | `ols` |
+| ohm | ✓ | ✓ | ✓ | | |
+| opencl | ✓ | ✓ | ✓ | | `clangd` |
+| openscad | ✓ | | | | `openscad-lsp` |
+| org | ✓ | | | | |
+| pascal | ✓ | ✓ | | | `pasls` |
+| passwd | ✓ | | | | |
+| pem | ✓ | | | | |
+| perl | ✓ | ✓ | ✓ | | `perlnavigator` |
+| php | ✓ | ✓ | ✓ | | `intelephense` |
+| php-only | ✓ | | | | |
+| pkgbuild | ✓ | ✓ | ✓ | | `pkgbuild-language-server`, `bash-language-server` |
+| pkl | ✓ | | ✓ | | |
+| po | ✓ | ✓ | | | |
+| pod | ✓ | | | | |
+| ponylang | ✓ | ✓ | ✓ | | |
+| powershell | ✓ | | | | |
+| prisma | ✓ | | | | `prisma-language-server` |
+| prolog | | | | | `swipl` |
+| protobuf | ✓ | ✓ | ✓ | | `bufls`, `pb` |
+| prql | ✓ | | | | |
+| purescript | ✓ | ✓ | | | `purescript-language-server` |
+| python | ✓ | ✓ | ✓ | ✓ | `pylsp` |
+| qml | ✓ | | ✓ | | `qmlls` |
+| r | ✓ | | | | `R` |
+| racket | ✓ | | ✓ | ✓ | `racket` |
+| regex | ✓ | | | ✓ | |
+| rego | ✓ | | | | `regols` |
+| rescript | ✓ | ✓ | | | `rescript-language-server` |
+| rmarkdown | ✓ | | ✓ | | `R` |
+| robot | ✓ | | | | `robotframework_ls` |
+| ron | ✓ | | ✓ | | |
+| rst | ✓ | | | | |
+| ruby | ✓ | ✓ | ✓ | ✓ | `solargraph` |
+| rust | ✓ | ✓ | ✓ | ✓ | `rust-analyzer` |
+| sage | ✓ | ✓ | | | |
+| scala | ✓ | ✓ | ✓ | | `metals` |
+| scheme | ✓ | | ✓ | ✓ | |
+| scss | ✓ | | | ✓ | `vscode-css-language-server` |
+| slint | ✓ | ✓ | ✓ | | `slint-lsp` |
+| smali | ✓ | | ✓ | | |
+| smithy | ✓ | | | | `cs` |
+| sml | ✓ | | | | |
+| solidity | ✓ | | | | `solc` |
+| spicedb | ✓ | | | | |
+| sql | ✓ | | | | |
+| sshclientconfig | ✓ | | | | |
+| starlark | ✓ | ✓ | | ✓ | |
+| strace | ✓ | | | | |
+| supercollider | ✓ | | | | |
+| svelte | ✓ | | ✓ | | `svelteserver` |
+| sway | ✓ | ✓ | ✓ | | `forc` |
+| swift | ✓ | | | | `sourcekit-lsp` |
+| t32 | ✓ | | | | |
+| tablegen | ✓ | ✓ | ✓ | | |
+| tact | ✓ | ✓ | ✓ | | |
+| task | ✓ | | | | |
+| templ | ✓ | | | | `templ` |
+| tfvars | ✓ | | ✓ | | `terraform-ls` |
+| todotxt | ✓ | | | | |
+| toml | ✓ | | | ✓ | `taplo` |
+| tsq | ✓ | | | ✓ | |
+| tsx | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| twig | ✓ | | | | |
+| typescript | ✓ | ✓ | ✓ | ✓ | `typescript-language-server` |
+| typst | ✓ | | | | `typst-lsp` |
+| ungrammar | ✓ | | | | |
+| unison | ✓ | | ✓ | | |
+| uxntal | ✓ | | | | |
+| v | ✓ | ✓ | ✓ | | `v-analyzer` |
+| vala | ✓ | ✓ | | | `vala-language-server` |
+| verilog | ✓ | ✓ | | | `svlangserver` |
+| vhdl | ✓ | | | | `vhdl_ls` |
+| vhs | ✓ | | | | |
+| vue | ✓ | | | | `vue-language-server` |
+| wast | ✓ | | | | |
+| wat | ✓ | | | | |
+| webc | ✓ | | | | |
+| wgsl | ✓ | | | | `wgsl_analyzer` |
+| wit | ✓ | | ✓ | | |
+| wren | ✓ | ✓ | ✓ | | |
+| xit | ✓ | | | | |
+| xml | ✓ | | ✓ | ✓ | |
+| yaml | ✓ | | ✓ | ✓ | `yaml-language-server`, `ansible-language-server` |
+| yuck | ✓ | | | | |
+| zig | ✓ | ✓ | ✓ | ✓ | `zls` |
diff --git a/book/src/guides/README.md b/book/src/guides/README.md
index c25768e6..e53983d6 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 00000000..1cba6a99
--- /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 `<a>link</a>`, the syntax tree is:
+
+```tsq
+(element ; <a>link</a>
+ (start_tag ; <a>
+ (tag_name)) ; a
+ (text) ; link
+ (end_tag ; </a>
+ (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 dd93fec5..a0fe2299 100644
--- a/book/src/languages.md
+++ b/book/src/languages.md
@@ -71,6 +71,8 @@ These configuration keys are available:
| `text-width` | Maximum line length. Used for the `:reflow` command and soft-wrapping if `soft-wrap.wrap-at-text-width` is set, defaults to `editor.text-width` |
| `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.
+| `rulers` | Overrides the `editor.rulers` config key for the language. |
+| `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 29a8c4ba..4a0f6f10 100644
--- a/book/src/themes.md
+++ b/book/src/themes.md
@@ -137,6 +137,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/src/syntax.rs b/helix-core/src/syntax.rs
index 78abc0b0..7de29e2b 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -169,6 +169,9 @@ pub struct LanguageConfiguration {
pub workspace_lsp_roots: Option<Vec<PathBuf>>,
#[serde(default)]
pub persistent_diagnostic_sources: Vec<String>,
+
+ /// If set, overrides rainbow brackets for a language.
+ pub rainbow_brackets: Option<bool>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
@@ -740,6 +743,7 @@ impl LanguageConfiguration {
// always highlight syntax errors
// highlights_query += "\n(ERROR) @error";
+ let rainbows_query = read_query(&self.language_id, "rainbows.scm");
let injections_query = read_query(&self.language_id, "injections.scm");
let locals_query = read_query(&self.language_id, "locals.scm");
@@ -758,6 +762,7 @@ impl LanguageConfiguration {
let config = HighlightConfiguration::new(
language,
&highlights_query,
+ &rainbows_query,
&injections_query,
&locals_query,
)
@@ -1066,6 +1071,39 @@ thread_local! {
})
}
+/// Creates an iterator over the captures in a query within the given range,
+/// re-using a cursor from the pool if available.
+/// SAFETY: The `QueryCaptures` must be droped before the `QueryCursor` is dropped.
+unsafe fn query_captures<'a, 'tree>(
+ query: &'a Query,
+ root: Node<'tree>,
+ source: RopeSlice<'a>,
+ range: Option<std::ops::Range<usize>>,
+) -> (
+ QueryCursor,
+ QueryCaptures<'a, 'tree, RopeProvider<'a>, &'a [u8]>,
+) {
+ // Reuse a cursor from the pool if available.
+ let mut cursor = PARSER.with(|ts_parser| {
+ let highlighter = &mut ts_parser.borrow_mut();
+ highlighter.cursors.pop().unwrap_or_else(QueryCursor::new)
+ });
+
+ // This is the unsafe line:
+ // The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
+ // prevents them from being moved. But both of these values are really just
+ // pointers, so it's actually ok to move them.
+ let cursor_ref = mem::transmute::<_, &'static mut QueryCursor>(&mut cursor);
+
+ // if reusing cursors & no range this resets to whole range
+ cursor_ref.set_byte_range(range.unwrap_or(0..usize::MAX));
+ cursor_ref.set_match_limit(TREE_SITTER_MATCH_LIMIT);
+
+ let captures = cursor_ref.captures(query, root, RopeProvider(source));
+
+ (cursor, captures)
+}
+
#[derive(Debug)]
pub struct Syntax {
layers: HopSlotMap<LayerId, LanguageLayer>,
@@ -1402,6 +1440,46 @@ impl Syntax {
self.layers[self.root].tree()
}
+ /// Iterate over all captures for a query across injection layers.
+ fn query_iter<'a, F>(
+ &'a self,
+ query_fn: F,
+ source: RopeSlice<'a>,
+ range: Option<std::ops::Range<usize>>,
+ ) -> impl Iterator<Item = (&'a LanguageLayer, QueryMatch<'a, 'a>, usize)>
+ where
+ F: Fn(&'a HighlightConfiguration) -> &'a Query,
+ {
+ let mut layers: Vec<_> = self
+ .layers
+ .iter()
+ .filter_map(|(_, layer)| {
+ let (cursor, captures) = unsafe {
+ query_captures(
+ query_fn(&layer.config),
+ layer.tree().root_node(),
+ source,
+ range.clone(),
+ )
+ };
+ let mut captures = captures.peekable();
+
+ // If there aren't any captures for this layer, skip the layer.
+ captures.peek()?;
+
+ Some(QueryIterLayer {
+ cursor,
+ captures: RefCell::new(captures),
+ layer,
+ })
+ })
+ .collect();
+
+ layers.sort_unstable_by_key(|layer| layer.sort_key());
+
+ QueryIter { layers }
+ }
+
/// Iterate over the highlighted regions for a given slice of source code.
pub fn highlight_iter<'a>(
&'a self,
@@ -1409,37 +1487,23 @@ impl Syntax {
range: Option<std::ops::Range<usize>>,
cancellation_flag: Option<&'a AtomicUsize>,
) -> impl Iterator<Item = Result<HighlightEvent, Error>> + 'a {
- let mut layers = self
+ let mut layers: Vec<_> = self
.layers
.iter()
.filter_map(|(_, layer)| {
// TODO: if range doesn't overlap layer range, skip it
- // Reuse a cursor from the pool if available.
- let mut cursor = PARSER.with(|ts_parser| {
- let highlighter = &mut ts_parser.borrow_mut();
- highlighter.cursors.pop().unwrap_or_else(QueryCursor::new)
- });
-
- // The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
- // prevents them from being moved. But both of these values are really just
- // pointers, so it's actually ok to move them.
- let cursor_ref =
- unsafe { mem::transmute::<_, &'static mut QueryCursor>(&mut cursor) };
-
- // if reusing cursors & no range this resets to whole range
- cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
- cursor_ref.set_match_limit(TREE_SITTER_MATCH_LIMIT);
-
- let mut captures = cursor_ref
- .captures(
+ let (cursor, captures) = unsafe {
+ query_captures(
&layer.config.query,
layer.tree().root_node(),
- RopeProvider(source),
+ source,
+ range.clone(),
)
- .peekable();
+ };
+ let mut captures = captures.peekable();
- // If there's no captures, skip the layer
+ // If there are no captures, skip the layer
captures.peek()?;
Some(HighlightIterLayer {
@@ -1456,11 +1520,13 @@ impl Syntax {
depth: layer.depth, // TODO: just reuse `layer`
})
})
- .collect::<Vec<_>>();
+ .collect();
layers.sort_unstable_by_key(|layer| layer.sort_key());
- let mut result = HighlightIter {
+ sort_layers(&mut layers);
+
+ HighlightIter {
source,
byte_offset: range.map_or(0, |r| r.start),
cancellation_flag,
@@ -1468,9 +1534,95 @@ impl Syntax {
layers,
next_event: None,
last_highlight_range: None,
- };
- result.sort_layers();
- result
+ }
+ }
+
+ /// Queries for rainbow highlights in the given range.
+ pub fn rainbow_spans<'a>(
+ &'a self,
+ source: RopeSlice<'a>,
+ query_range: Option<std::ops::Range<usize>>,
+ rainbow_length: usize,
+ ) -> Vec<(usize, std::ops::Range<usize>)> {
+ struct RainbowScope {
+ end: usize,
+ node_id: Option<usize>,
+ highlight: usize,
+ }
+
+ let mut spans = Vec::new();
+ let mut scope_stack: Vec<RainbowScope> = Vec::new();
+
+ // Calculating rainbow highlights is similar to determining local highlights
+ // in the highlight iterator. We iterate over the query captures for
+ // `@rainbow.scope` and `@rainbow.bracket`:
+ //
+ // * `@rainbow.scope`: pushes a new `RainbowScope` onto the `scope_stack`
+ // stack. The number of `RainbowScope`s is the level of nesting within
+ // brackets and determines which color of the rainbow should be used as
+ // a highlight: `scope_stack.len() % rainbow_length`.
+ //
+ // * `@rainbow.bracket`: adds a new highlight span to the `spans` Vec.
+ // A `@rainbow.bracket` capture only creates a new highlight if that node
+ // is a child node of the latest node captured with `@rainbow.scope`,
+ // or if the last `RainbowScope` on the `scope_stack` was captured with
+ // the `(set! rainbow.include-children)` property.
+ //
+ // The iterator over the query captures returns captures across injection
+ // layers sorted by the earliest captures in the document first, so
+ // highlight colors are calculated correctly across injection layers.
+
+ // Iterate over all of the captures for rainbow queries across injections.
+ for (layer, match_, capture_index) in
+ self.query_iter(|config| &config.rainbow_query, source, query_range)
+ {
+ let capture = match_.captures[capture_index];
+ let range = capture.node.byte_range();
+
+ // If any scope in the stack ends before this new capture begins,
+ // pop the scope out of the scope stack.
+ while let Some(scope) = scope_stack.last() {
+ if range.start >= scope.end {
+ scope_stack.pop();
+ } else {
+ break;
+ }
+ }
+
+ if Some(capture.index) == layer.config.rainbow_scope_capture_index {
+ // If the capture is a "rainbow.scope", push it onto the scope stack.
+ let mut scope = RainbowScope {
+ end: range.end,
+ node_id: Some(capture.node.id()),
+ highlight: scope_stack.len() % rainbow_length,
+ };
+ for prop in layer
+ .config
+ .rainbow_query
+ .property_settings(match_.pattern_index)
+ {
+ if prop.key.as_ref() == "rainbow.include-children" {
+ scope.node_id = None;
+ }
+ }
+ scope_stack.push(scope);
+ } else if Some(capture.index) == layer.config.rainbow_bracket_capture_index {
+ // If the capture is a "rainbow.bracket", check that the top of the scope stack
+ // is a valid scope for the bracket. The scope is valid if:
+ // * The scope's node is the direct parent of the captured node.
+ // * The scope has the "rainbow.include-children" property set. This allows the
+ // scope to match all descendant nodes in its range.
+ if let Some(scope) = scope_stack.last() {
+ if scope.node_id.is_none()
+ || capture.node.parent().map(|p| p.id()) == scope.node_id
+ {
+ spans.push((scope.highlight, range));
+ }
+ }
+ }
+ }
+
+ spans
}
pub fn tree_for_byte_range(&self, start: usize, end: usize) -> &Tree {
@@ -1517,6 +1669,18 @@ impl Syntax {
// TODO: Folding
}
+/// Finds the child of `node` which contains the given byte range `range`.
+pub fn child_for_byte_range(node: Node, range: std::ops::Range<usize>) -> Option<Node> {
+ for child in node.children(&mut node.walk()) {
+ let child_range = child.byte_range();
+ if range.start >= child_range.start && range.end <= child_range.end {
+ return Some(child);
+ }
+ }
+
+ None
+}
+
bitflags! {
/// Flags that track the status of a layer
/// in the `Sytaxn::update` function
@@ -1765,7 +1929,8 @@ pub enum HighlightEvent {
#[derive(Debug)]
pub struct HighlightConfiguration {
pub language: Grammar,
- pub query: Query,
+ query: Query,
+ rainbow_query: Query,
injections_query: Query,
combined_injections_patterns: Vec<usize>,
highlights_pattern_index: usize,
@@ -1779,6 +1944,8 @@ pub struct HighlightConfiguration {
local_def_capture_index: Option<u32>,
local_def_value_capture_index: Option<u32>,
local_ref_capture_index: Option<u32>,
+ rainbow_scope_capture_index: Option<u32>,
+ rainbow_bracket_capture_index: Option<u32>,
}
#[derive(Debug)]
@@ -1863,6 +2030,7 @@ impl HighlightConfiguration {
pub fn new(
language: Grammar,
highlights_query: &str,
+ rainbow_query: &str,
injection_query: &str,
locals_query: &str,
) -> Result<Self, QueryError> {
@@ -1882,6 +2050,7 @@ impl HighlightConfiguration {
highlights_pattern_index += 1;
}
}
+ let rainbow_query = Query::new(&language, rainbow_query)?;
let injections_query = Query::new(&language, injection_query)?;
let combined_injections_patterns = (0..injections_query.pattern_count())
@@ -1913,6 +2082,8 @@ impl HighlightConfiguration {
let mut local_def_value_capture_index = None;
let mut local_ref_capture_index = None;
let mut local_scope_capture_index = None;
+ let mut rainbow_scope_capture_index = None;
+ let mut rainbow_bracket_capture_index = None;
for (i, name) in query.capture_names().iter().enumerate() {
let i = Some(i as u32);
match *name {
@@ -1924,6 +2095,15 @@ impl HighlightConfiguration {
}
}
+ for (i, name) in rainbow_query.capture_names().iter().enumerate() {
+ let i = Some(i as u32);
+ match *name {
+ "rainbow.scope" => rainbow_scope_capture_index = i,
+ "rainbow.bracket" => rainbow_bracket_capture_index = i,
+ _ => {}
+ }
+ }
+
for (i, name) in injections_query.capture_names().iter().enumerate() {
let i = Some(i as u32);
match *name {
@@ -1939,6 +2119,7 @@ impl HighlightConfiguration {
Ok(Self {
language,
query,
+ rainbow_query,
injections_query,
combined_injections_patterns,
highlights_pattern_index,
@@ -1952,6 +2133,8 @@ impl HighlightConfiguration {
local_def_capture_index,
local_def_value_capture_index,
local_ref_capture_index,
+ rainbow_scope_capture_index,
+ rainbow_bracket_capture_index,
})
}
@@ -2096,11 +2279,21 @@ impl HighlightConfiguration {
}
}
-impl<'a> HighlightIterLayer<'a> {
- // First, sort scope boundaries by their byte offset in the document. At a
- // given position, emit scope endings before scope beginnings. Finally, emit
- // scope boundaries from deeper layers first.
- fn sort_key(&self) -> Option<(usize, bool, isize)> {
+trait IterLayer {
+ type SortKey: PartialOrd;
+
+ fn sort_key(&self) -> Option<Self::SortKey>;
+
+ fn cursor(self) -> QueryCursor;
+}
+
+impl<'a> IterLayer for HighlightIterLayer<'a> {
+ type SortKey = (usize, bool, isize);
+
+ fn sort_key(&self) -> Option<Self::SortKey> {
+ // First, sort scope boundaries by their byte offset in the document. At a
+ // given position, emit scope endings before scope beginnings. Finally, emit
+ // scope boundaries from deeper layers first.
let depth = -(self.depth as isize);
let next_start = self
.captures
@@ -2121,6 +2314,82 @@ impl<'a> HighlightIterLayer<'a> {
_ => None,
}
}
+
+ fn cursor(self) -> QueryCursor {
+ self.cursor
+ }
+}
+
+impl<'a> IterLayer for QueryIterLayer<'a> {
+ type SortKey = (usize, isize);
+
+ fn sort_key(&self) -> Option<Self::SortKey> {
+ // Sort the layers so that the first layer in the Vec has the next
+ // capture ordered by start byte and depth (descending).
+ let depth = -(self.layer.depth as isize);
+ let mut captures = self.captures.borrow_mut();
+ let (match_, capture_index) = captures.peek()?;
+ let start = match_.captures[*capture_index].node.start_byte();
+
+ Some((start, depth))
+ }
+
+ fn cursor(self) -> QueryCursor {
+ self.cursor
+ }
+}
+
+/// Re-sort the given layers so that the next capture for the `layers[0]` is
+/// the earliest capture in the document for all layers.
+///
+/// This function assumes that `layers` is already sorted except for the
+/// first layer in the `Vec`. This function shifts the first layer later in
+/// the `Vec` after any layers with earlier captures.
+///
+/// This is quicker than a regular full sort: it can only take as many
+/// iterations as the number of layers and usually takes many fewer than
+/// the full number of layers. The case when `layers[0]` is already the
+/// layer with the earliest capture and the sort is a no-op is a fast-lane
+/// which only takes one comparison operation.
+///
+/// This function also removes any layers which have no more query captures
+/// to emit.
+fn sort_layers<L: IterLayer>(layers: &mut Vec<L>) {
+ while !layers.is_empty() {
+ // If `Layer::sort_key` returns `None`, the layer has no more captures
+ // to emit and can be removed.
+ if let Some(sort_key) = layers[0].sort_key() {
+ let mut i = 0;
+ while i + 1 < layers.len() {
+ if let Some(next_offset) = layers[i + 1].sort_key() {
+ // Compare `0`'s sort key to `i + 1`'s. If `i + 1` comes
+ // before `0`, shift the `0` layer so it comes after the
+ // `i + 1` layers.
+ if next_offset < sort_key {
+ i += 1;
+ continue;
+ }
+ } else {
+ let layer = layers.remove(i + 1);
+ PARSER.with(|ts_parser| {
+ let highlighter = &mut ts_parser.borrow_mut();
+ highlighter.cursors.push(layer.cursor());
+ });
+ }
+ break;
+ }
+ if i > 0 {
+ layers[0..(i + 1)].rotate_left(1);
+ }
+ break;
+ } else {
+ let layer = layers.remove(0);
+ PARSER.with(|ts_parser| {
+ let highlighter = &mut ts_parser.borrow_mut();
+ highlighter.cursors.push(layer.cursor());
+ });
+ }
+ }
}
#[derive(Clone)]
@@ -2251,42 +2520,9 @@ impl<'a> HighlightIter<'a> {
} else {
result = event.map(Ok);
}
- self.sort_layers();
+ sort_layers(&mut self.layers);
result
}
-
- fn sort_layers(&mut self) {
- while !self.layers.is_empty() {
- if let Some(sort_key) = self.layers[0].sort_key() {
- let mut i = 0;
- while i + 1 < self.layers.len() {
- if let Some(next_offset) = self.layers[i + 1].sort_key() {
- if next_offset < sort_key {
- i += 1;
- continue;
- }
- } else {
- let layer = self.layers.remove(i + 1);
- PARSER.with(|ts_parser| {
- let highlighter = &mut ts_parser.borrow_mut();
- highlighter.cursors.push(layer.cursor);
- });
- }
- break;
- }
- if i > 0 {
- self.layers[0..(i + 1)].rotate_left(1);
- }
- break;
- } else {
- let layer = self.layers.remove(0);
- PARSER.with(|ts_parser| {
- let highlighter = &mut ts_parser.borrow_mut();
- highlighter.cursors.push(layer.cursor);
- });
- }
- }
- }
}
impl<'a> Iterator for HighlightIter<'a> {
@@ -2438,7 +2674,7 @@ impl<'a> Iterator for HighlightIter<'a> {
}
}
- self.sort_layers();
+ sort_layers(&mut self.layers);
continue 'main;
}
@@ -2447,7 +2683,7 @@ impl<'a> Iterator for HighlightIter<'a> {
// a different layer, then skip over this one.
if let Some((last_start, last_end, last_depth)) = self.last_highlight_range {
if range.start == last_start && range.end == last_end && layer.depth < last_depth {
- self.sort_layers();
+ sort_layers(&mut self.layers);
continue 'main;
}
}
@@ -2466,7 +2702,7 @@ impl<'a> Iterator for HighlightIter<'a> {
}
}
- self.sort_layers();
+ sort_layers(&mut self.layers);
continue 'main;
}
}
@@ -2501,7 +2737,7 @@ impl<'a> Iterator for HighlightIter<'a> {
.emit_event(range.start, Some(HighlightEvent::HighlightStart(highlight)));
}
- self.sort_layers();
+ sort_layers(&mut self.layers);
}
}
}
@@ -2711,6 +2947,42 @@ fn pretty_print_tree_impl<W: fmt::Write>(
Ok(())
}
+struct QueryIterLayer<'a> {
+ cursor: QueryCursor,
+ captures: RefCell<iter::Peekable<QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>>>,
+ layer: &'a LanguageLayer,
+}
+
+impl<'a> fmt::Debug for QueryIterLayer<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("QueryIterLayer").finish()
+ }
+}
+
+#[derive(Debug)]
+pub struct QueryIter<'a> {
+ layers: Vec<QueryIterLayer<'a>>,
+}
+
+impl<'a> Iterator for QueryIter<'a> {
+ type Item = (&'a LanguageLayer, QueryMatch<'a, 'a>, usize);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ // Sort the layers so that the first layer contains the next capture.
+ sort_layers(&mut self.layers);
+
+ // Emit the next capture from the lowest layer. If there are no more
+ // layers, terminate.
+ let layer = self.layers.get_mut(0)?;
+ let inner = layer.layer;
+ layer
+ .captures
+ .borrow_mut()
+ .next()
+ .map(|(match_, index)| (inner, match_, index))
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -2741,7 +3013,7 @@ mod test {
let textobject = TextObjectQuery { query };
let mut cursor = QueryCursor::new();
- let config = HighlightConfiguration::new(language, "", "", "").unwrap();
+ let config = HighlightConfiguration::new(language, "", "", "", "").unwrap();
let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
@@ -2809,6 +3081,7 @@ mod test {
language,
&std::fs::read_to_string("../runtime/grammars/sources/rust/queries/highlights.scm")
.unwrap(),
+ "", // rainbows.scm
&std::fs::read_to_string("../runtime/grammars/sources/rust/queries/injections.scm")
.unwrap(),
"", // locals.scm
@@ -2917,7 +3190,7 @@ mod test {
.unwrap();
let language = get_language(language_name).unwrap();
- let config = HighlightConfiguration::new(language, "", "", "").unwrap();
+ let config = HighlightConfiguration::new(language, "", "", "", "").unwrap();
let syntax = Syntax::new(
source.slice(..),
Arc::new(config),
diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs
index 0bbb5735..a6eaa39b 100644
--- a/helix-term/src/health.rs
+++ b/helix-term/src/health.rs
@@ -12,11 +12,17 @@ pub enum TsFeature {
Highlight,
TextObject,
AutoIndent,
+ RainbowBrackets,
}
impl TsFeature {
pub fn all() -> &'static [Self] {
- &[Self::Highlight, Self::TextObject, Self::AutoIndent]
+ &[
+ Self::Highlight,
+ Self::TextObject,
+ Self::AutoIndent,
+ Self::RainbowBrackets,
+ ]
}
pub fn runtime_filename(&self) -> &'static str {
@@ -24,6 +30,7 @@ impl TsFeature {
Self::Highlight => "highlights.scm",
Self::TextObject => "textobjects.scm",
Self::AutoIndent => "indents.scm",
+ Self::RainbowBrackets => "rainbows.scm",
}
}
@@ -32,6 +39,7 @@ impl TsFeature {
Self::Highlight => "Syntax Highlighting",
Self::TextObject => "Treesitter Textobjects",
Self::AutoIndent => "Auto Indent",
+ Self::RainbowBrackets => "Rainbow Brackets",
}
}
@@ -40,6 +48,7 @@ impl TsFeature {
Self::Highlight => "Highlight",
Self::TextObject => "Textobject",
Self::AutoIndent => "Indent",
+ Self::RainbowBrackets => "Rainbow",
}
}
}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index f761a546..294433b5 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -97,6 +97,11 @@ impl EditorView {
let theme = &editor.theme;
let config = editor.config();
+ let should_render_rainbow_brackets = doc
+ .language_config()
+ .and_then(|lang_config| lang_config.rainbow_brackets)
+ .unwrap_or(config.rainbow_brackets);
+
let text_annotations = view.text_annotations(doc, Some(theme));
let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
@@ -126,8 +131,14 @@ impl EditorView {
line_decorations.push(Box::new(line_decoration));
}
- let syntax_highlights =
+ let mut syntax_highlights =
Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
+ if should_render_rainbow_brackets {
+ syntax_highlights = Box::new(syntax::merge(
+ syntax_highlights,
+ Self::doc_rainbow_highlights(doc, view.offset.anchor, inner.height, theme),
+ ));
+ }
let mut overlay_highlights =
Self::empty_highlight_iter(doc, view.offset.anchor, inner.height);
@@ -357,6 +368,48 @@ impl EditorView {
text_annotations.collect_overlay_highlights(range)
}
+ pub fn doc_rainbow_highlights(
+ doc: &Document,
+ anchor: usize,
+ height: u16,
+ theme: &Theme,
+ ) -> Vec<(usize, std::ops::Range<usize>)> {
+ let syntax = match doc.syntax() {
+ Some(syntax) => syntax,
+ None => return Vec::new(),
+ };
+
+ let text = doc.text().slice(..);
+ let row = text.char_to_line(anchor.min(text.len_chars()));
+
+ // calculate viewport byte ranges
+ let last_line = doc.text().len_lines().saturating_sub(1);
+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
+ let visible_start = text.line_to_byte(row.min(last_line));
+ let visible_end = text.line_to_byte(last_visible_line + 1);
+
+ // The calculation for the current nesting level for rainbow highlights
+ // depends on where we start the iterator from. For accuracy, we start
+ // the iterator further back than the viewport: at the start of the containing
+ // non-root syntax-tree node. Any spans that are off-screen are truncated when
+ // the spans are merged via [syntax::merge].
+ let syntax_node_start =
+ syntax::child_for_byte_range(syntax.tree().root_node(), visible_start..visible_start)
+ .map_or(visible_start, |node| node.byte_range().start);
+ let syntax_node_range = syntax_node_start..visible_end;
+
+ let mut spans = syntax.rainbow_spans(text, Some(syntax_node_range), theme.rainbow_length());
+
+ for (_highlight, range) in spans.iter_mut() {
+ let start = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, range.start));
+ let end = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, range.end));
+
+ *range = start..end;
+ }
+
+ spans
+ }
+
/// Get highlight spans for document diagnostics
pub fn doc_diagnostics_highlights(
doc: &Document,
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 8013043f..4987c073 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -356,6 +356,8 @@ pub struct Config {
pub initial_mode: Mode,
/// User supplied digraphs for use with the `insert_diagraphs` command
pub digraphs: DigraphStore,
+ /// Whether to render rainbow highlights. Defaults to `true`.
+ pub rainbow_brackets: bool,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
@@ -936,6 +938,7 @@ impl Default for Config {
explorer: ExplorerConfig::default(),
initial_mode: Mode::Normal,
digraphs: Default::default(),
+ rainbow_brackets: true,
}
}
}
@@ -1276,8 +1279,9 @@ impl Editor {
return;
}
- let scopes = theme.scopes();
- (*self.syn_loader).load().set_scopes(scopes.to_vec());
+ self.syn_loader
+ .load_full()
+ .set_scopes(theme.scopes().to_vec());
match preview {
ThemeAction::Preview => {
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index 4acc5664..16da247f 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -216,17 +216,19 @@ pub struct Theme {
// tree-sitter highlight styles are stored in a Vec to optimize lookups
scopes: Vec<String>,
highlights: Vec<Style>,
+ rainbow_length: usize,
}
impl From<Value> for Theme {
fn from(value: Value) -> Self {
if let Value::Table(table) = value {
- let (styles, scopes, highlights) = build_theme_values(table);
+ let (styles, scopes, highlights, rainbow_length) = build_theme_values(table);
Self {
styles,
scopes,
highlights,
+ rainbow_length,
..Default::default()
}
} else {
@@ -243,12 +245,13 @@ impl<'de> Deserialize<'de> for Theme {
{
let values = Map::<String, Value>::deserialize(deserializer)?;
- let (styles, scopes, highlights) = build_theme_values(values);
+ let (styles, scopes, highlights, rainbow_length) = build_theme_values(values);
Ok(Self {
styles,
scopes,
highlights,
+ rainbow_length,
..Default::default()
})
}
@@ -256,10 +259,11 @@ impl<'de> Deserialize<'de> for Theme {
fn build_theme_values(
mut values: Map<String, Value>,
-) -> (HashMap<String, Style>, Vec<String>, Vec<Style>) {
+) -> (HashMap<String, Style>, Vec<String>, Vec<Style>, usize) {
let mut styles = HashMap::new();
let mut scopes = Vec::new();
let mut highlights = Vec::new();
+ let mut rainbow_length = 0;
// TODO: alert user of parsing failures in editor
let palette = values
@@ -276,6 +280,27 @@ fn build_theme_values(
styles.reserve(values.len());
scopes.reserve(values.len());
highlights.reserve(values.len());
+
+ for (i, style) in values
+ .remove("rainbow")
+ .and_then(|value| match palette.parse_style_array(value) {
+ Ok(styles) => Some(styles),
+ Err(err) => {
+ warn!("{}", err);
+ None
+ }
+ })
+ .unwrap_or_else(default_rainbow)
+ .iter()
+ .enumerate()
+ {
+ let name = format!("rainbow.{}", i);
+ styles.insert(name.clone(), *style);
+ scopes.push(name);
+ highlights.push(*style);
+ rainbow_length += 1;
+ }
+
for (name, style_value) in values {
let mut style = Style::default();
if let Err(err) = palette.parse_style(&mut style, style_value) {
@@ -288,7 +313,7 @@ fn build_theme_values(
highlights.push(style);
}
- (styles, scopes, highlights)
+ (styles, scopes, highlights, rainbow_length)
}
impl Theme {
@@ -354,6 +379,21 @@ impl Theme {
.all(|color| !matches!(color, Some(Color::Rgb(..))))
})
}
+
+ pub fn rainbow_length(&self) -> usize {
+ self.rainbow_length
+ }
+}
+
+fn default_rainbow() -> Vec<Style> {
+ vec![
+ Style::default().fg(Color::Red),
+ Style::default().fg(Color::Yellow),
+ Style::default().fg(Color::Green),
+ Style::default().fg(Color::Blue),
+ Style::default().fg(Color::Cyan),
+ Style::default().fg(Color::Magenta),
+ ]
}
struct ThemePalette {
@@ -500,6 +540,24 @@ impl ThemePalette {
}
Ok(())
}
+
+ /// Parses a TOML array into a [`Vec`] of [`Style`]. If the value cannot be
+ /// parsed as an array or if any style in the array cannot be parsed then an
+ /// error is returned.
+ pub fn parse_style_array(&self, value: Value) -> Result<Vec<Style>, String> {
+ let mut styles = Vec::new();
+
+ for v in value
+ .as_array()
+ .ok_or_else(|| format!("Theme: could not parse value as an array: '{}'", value))?
+ {
+ let mut style = Style::default();
+ self.parse_style(&mut style, v.clone())?;
+ styles.push(style);
+ }
+
+ Ok(styles)
+ }
}
impl TryFrom<Value> for ThemePalette {
@@ -574,4 +632,51 @@ mod tests {
.add_modifier(Modifier::BOLD)
);
}
+
+ #[test]
+ fn test_parse_valid_style_array() {
+ let theme = toml::toml! {
+ rainbow = ["#ff0000", "#ffa500", "#fff000", { fg = "#00ff00", modifiers = ["bold"] }]
+ };
+
+ let palette = ThemePalette::default();
+
+ let rainbow = theme.get("rainbow").unwrap();
+ let parse_result = palette.parse_style_array(rainbow.clone());
+
+ assert_eq!(
+ Ok(vec![
+ Style::default().fg(Color::Rgb(255, 0, 0)),
+ Style::default().fg(Color::Rgb(255, 165, 0)),
+ Style::default().fg(Color::Rgb(255, 240, 0)),
+ Style::default()
+ .fg(Color::Rgb(0, 255, 0))
+ .add_modifier(Modifier::BOLD),
+ ]),
+ parse_result
+ )
+ }
+
+ #[test]
+ fn test_parse_invalid_style_array() {
+ let palette = ThemePalette::default();
+
+ let theme = toml::toml! { invalid_hex_code = ["#f00"] };
+ let invalid_hex_code = theme.get("invalid_hex_code").unwrap();
+ let parse_result = palette.parse_style_array(invalid_hex_code.clone());
+
+ assert_eq!(
+ Err("Theme: malformed hexcode: #f00".to_string()),
+ parse_result
+ );
+
+ let theme = toml::toml! { not_an_array = { red = "#ff0000" } };
+ let not_an_array = theme.get("not_an_array").unwrap();
+ let parse_result = palette.parse_style_array(not_an_array.clone());
+
+ assert_eq!(
+ Err("Theme: could not parse value as an array: '{ red = \"#ff0000\" }'".to_string()),
+ parse_result
+ )
+ }
}
diff --git a/runtime/queries/bash/rainbows.scm b/runtime/queries/bash/rainbows.scm
new file mode 100644
index 00000000..fd2a9d3a
--- /dev/null
+++ b/runtime/queries/bash/rainbows.scm
@@ -0,0 +1,21 @@
+[
+ (function_definition)
+ (compound_statement)
+ (subshell)
+ (test_command)
+ (subscript)
+ (parenthesized_expression)
+ (array)
+ (expansion_flags)
+ (expansion)
+ (command_substitution)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "((" "))"
+ "${" "$("
+ "{" "}"
+ "[" "]"
+ "[[" "]]"
+] @rainbow.bracket
diff --git a/runtime/queries/c/rainbows.scm b/runtime/queries/c/rainbows.scm
new file mode 100644
index 00000000..1f80868a
--- /dev/null
+++ b/runtime/queries/c/rainbows.scm
@@ -0,0 +1,29 @@
+[
+ (preproc_params)
+ (preproc_defined)
+ (argument_list)
+ (attribute_specifier)
+ (ms_declspec_modifier)
+ (declaration_list)
+ (parenthesized_declarator)
+ (parenthesized_expression)
+ (abstract_parenthesized_declarator)
+ (array_declarator)
+ (compound_statement)
+ (initializer_list)
+ (compound_literal_expression)
+ (enumerator_list)
+ (field_declaration_list)
+ (parameter_list)
+ (for_statement)
+ (macro_type_specifier)
+ (subscript_expression)
+ (subscript_designator)
+ (cast_expression)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/clojure/rainbows.scm b/runtime/queries/clojure/rainbows.scm
new file mode 100644
index 00000000..99dc8bc3
--- /dev/null
+++ b/runtime/queries/clojure/rainbows.scm
@@ -0,0 +1,13 @@
+[
+ (list_lit)
+ (map_lit)
+ (vec_lit)
+ (anon_fn_lit)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "#"
+ "{" "}"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/common-lisp/rainbows.scm b/runtime/queries/common-lisp/rainbows.scm
new file mode 100644
index 00000000..e11eb788
--- /dev/null
+++ b/runtime/queries/common-lisp/rainbows.scm
@@ -0,0 +1 @@
+; inherits: scheme
diff --git a/runtime/queries/cpp/rainbows.scm b/runtime/queries/cpp/rainbows.scm
new file mode 100644
index 00000000..ff4882c2
--- /dev/null
+++ b/runtime/queries/cpp/rainbows.scm
@@ -0,0 +1,49 @@
+[
+ ; c
+ (preproc_params)
+ (preproc_defined)
+ (argument_list)
+ (attribute_specifier)
+ (ms_declspec_modifier)
+ (declaration_list)
+ (parenthesized_declarator)
+ (parenthesized_expression)
+ (abstract_parenthesized_declarator)
+ (array_declarator)
+ (compound_statement)
+ (initializer_list)
+ (compound_literal_expression)
+ (enumerator_list)
+ (field_declaration_list)
+ (parameter_list)
+ (for_statement)
+ ; (macro_type_specifier) - not part of cpp
+ (subscript_expression)
+ (subscript_designator)
+ (cast_expression)
+
+ ; cpp
+ (decltype)
+ (explicit_function_specifier)
+ (template_parameter_list)
+ (template_argument_list)
+ (parameter_list)
+ (argument_list)
+ (structured_binding_declarator)
+ (noexcept)
+ (throw_specifier)
+ (static_assert_declaration)
+ (condition_clause)
+ (for_range_loop)
+ (new_declarator)
+ (delete_expression "[" "]")
+ (lambda_capture_specifier)
+ (sizeof_expression)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+ "<" ">"
+] @rainbow.bracket
diff --git a/runtime/queries/css/rainbows.scm b/runtime/queries/css/rainbows.scm
new file mode 100644
index 00000000..66b60d51
--- /dev/null
+++ b/runtime/queries/css/rainbows.scm
@@ -0,0 +1,15 @@
+[
+ (keyframe_block_list)
+ (block)
+ (attribute_selector)
+ (feature_query)
+ (parenthesized_query)
+ (selector_query)
+ (parenthesized_value)
+ (arguments)
+] @rainbow.scope
+
+[
+ "{" "}"
+ "(" ")"
+] @rainbow.bracket
diff --git a/runtime/queries/ecma/rainbows.scm b/runtime/queries/ecma/rainbows.scm
new file mode 100644
index 00000000..50f9f813
--- /dev/null
+++ b/runtime/queries/ecma/rainbows.scm
@@ -0,0 +1,28 @@
+[
+ (export_clause)
+ (named_imports)
+ (statement_block)
+ (for_statement)
+ (for_in_statement)
+ (switch_body)
+ (catch_clause "(" ")")
+ (parenthesized_expression)
+ (object)
+ (object_pattern)
+ (array)
+ (array_pattern)
+ (subscript_expression)
+ (template_substitution)
+ (arguments)
+ (class_body)
+ (formal_parameters)
+ (computed_property_name)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "${" "{" "}"
+ "[" "]"
+] @rainbow.bracket
+
+(regex "/" @rainbow.bracket) @rainbow.scope
diff --git a/runtime/queries/elixir/rainbows.scm b/runtime/queries/elixir/rainbows.scm
new file mode 100644
index 00000000..01d3da7a
--- /dev/null
+++ b/runtime/queries/elixir/rainbows.scm
@@ -0,0 +1,24 @@
+[
+ (block)
+ (interpolation)
+ (list)
+ (tuple)
+ (bitstring)
+ (map)
+ ; short-hand function captures like &(&1 + &2)
+ (unary_operator
+ operator: "&")
+ (arguments "(" ")")
+ (access_call)
+ (sigil)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "%"
+ "{" "}"
+ "[" "]"
+ "<<" ">>"
+ "#{"
+ "|"
+] @rainbow.bracket
diff --git a/runtime/queries/erlang/rainbows.scm b/runtime/queries/erlang/rainbows.scm
new file mode 100644
index 00000000..5092c998
--- /dev/null
+++ b/runtime/queries/erlang/rainbows.scm
@@ -0,0 +1,24 @@
+[
+ ; ()
+ (arguments "(" ")")
+ (parenthesized_expression)
+ (function_type)
+ ; #{}
+ (record)
+ (map)
+ ; {}
+ (map_update)
+ (tuple)
+ ; <<>>
+ (bitstring)
+ ; []
+ (list)
+] @rainbow.scope
+
+[
+ "#"
+ "{" "}"
+ "(" ")"
+ "[" "]"
+ "<<" ">>"
+] @rainbow.bracket
diff --git a/runtime/queries/gleam/rainbows.scm b/runtime/queries/gleam/rainbows.scm
new file mode 100644
index 00000000..a28312c1
--- /dev/null
+++ b/runtime/queries/gleam/rainbows.scm
@@ -0,0 +1,32 @@
+[
+ (target_group)
+ (unqualified_imports)
+ (tuple)
+ (list)
+ (function)
+ (function_parameters)
+ (todo)
+ (tuple)
+ (list)
+ (anonymous_function)
+ (block)
+ (case)
+ (record_update)
+ (arguments)
+ (record_pattern_arguments)
+ (tuple_pattern)
+ (list_pattern)
+ (type_definition)
+ (data_constructor_arguments)
+ (tuple_type)
+ (function_parameter_types)
+ (type_arguments)
+ (type_parameters)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "[" "]"
+ "{" "}"
+ "#"
+] @rainbow.bracket
diff --git a/runtime/queries/go/rainbows.scm b/runtime/queries/go/rainbows.scm
new file mode 100644
index 00000000..81004bf8
--- /dev/null
+++ b/runtime/queries/go/rainbows.scm
@@ -0,0 +1,33 @@
+[
+ (import_spec_list)
+ (const_declaration)
+ (var_declaration)
+ (type_parameter_list)
+ (parameter_list)
+ (type_declaration)
+ (parenthesized_type)
+ (type_arguments)
+ (array_type)
+ (implicit_length_array_type)
+ (slice_type)
+ (field_declaration_list)
+ (interface_type)
+ (map_type)
+ (block)
+ (expression_switch_statement)
+ (type_switch_statement)
+ (select_statement)
+ (parenthesized_expression)
+ (argument_list)
+ (index_expression)
+ (slice_expression)
+ (type_assertion_expression)
+ (type_conversion_expression)
+ (literal_value)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/html/rainbows.scm b/runtime/queries/html/rainbows.scm
new file mode 100644
index 00000000..66e62e95
--- /dev/null
+++ b/runtime/queries/html/rainbows.scm
@@ -0,0 +1,13 @@
+[
+ (doctype)
+ (erroneous_end_tag)
+] @rainbow.scope
+
+([
+ (element)
+ (script_element)
+ (style_element)
+ ] @rainbow.scope
+ (#set! rainbow.include-children))
+
+["<" ">" "<!" "</" "/>"] @rainbow.bracket
diff --git a/runtime/queries/java/rainbows.scm b/runtime/queries/java/rainbows.scm
new file mode 100644
index 00000000..699b899c
--- /dev/null
+++ b/runtime/queries/java/rainbows.scm
@@ -0,0 +1,35 @@
+[
+ (cast_expression)
+ (inferred_parameters)
+ (dimensions_expr)
+ (parenthesized_expression)
+ (array_access)
+ (argument_list)
+ (type_arguments)
+ (dimensions)
+ (block)
+ (switch_block)
+ (catch_clause)
+ (resource_specification)
+ (for_statement)
+ (enhanced_for_statement)
+ (annotation_argument_list)
+ (element_value_array_initializer)
+ (module_body)
+ (enum_body)
+ (type_parameters)
+ (class_body)
+ (constructor_body)
+ (annotation_type_body)
+ (annotation_type_element_declaration)
+ (interface_body)
+ (array_initializer)
+ (formal_parameters)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+ "<" ">"
+] @rainbow.bracket
diff --git a/runtime/queries/javascript/rainbows.scm b/runtime/queries/javascript/rainbows.scm
new file mode 100644
index 00000000..04328f09
--- /dev/null
+++ b/runtime/queries/javascript/rainbows.scm
@@ -0,0 +1 @@
+; inherits: ecma
diff --git a/runtime/queries/json/rainbows.scm b/runtime/queries/json/rainbows.scm
new file mode 100644
index 00000000..5c21bdcc
--- /dev/null
+++ b/runtime/queries/json/rainbows.scm
@@ -0,0 +1,9 @@
+[
+ (object)
+ (array)
+] @rainbow.scope
+
+[
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/jsx/rainbows.scm b/runtime/queries/jsx/rainbows.scm
new file mode 100644
index 00000000..38d8f242
--- /dev/null
+++ b/runtime/queries/jsx/rainbows.scm
@@ -0,0 +1,9 @@
+; inherits: ecma
+
+[
+ (jsx_expression)
+] @rainbow.scope
+
+(jsx_opening_element ["<" ">"] @rainbow.bracket) @rainbow.scope
+(jsx_closing_element ["</" ">"] @rainbow.bracket) @rainbow.scope
+(jsx_self_closing_element ["<" "/>"] @rainbow.bracket) @rainbow.scope
diff --git a/runtime/queries/nix/rainbows.scm b/runtime/queries/nix/rainbows.scm
new file mode 100644
index 00000000..2df51393
--- /dev/null
+++ b/runtime/queries/nix/rainbows.scm
@@ -0,0 +1,17 @@
+[
+ (formals)
+ (parenthesized_expression)
+ (attrset_expression)
+ (let_attrset_expression)
+ (rec_attrset_expression)
+ (inherit_from)
+ (interpolation)
+ (list_expression)
+] @rainbow.scope
+
+[
+ "${"
+ "{" "}"
+ "(" ")"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/python/rainbows.scm b/runtime/queries/python/rainbows.scm
new file mode 100644
index 00000000..15e5db29
--- /dev/null
+++ b/runtime/queries/python/rainbows.scm
@@ -0,0 +1,28 @@
+[
+ (future_import_statement)
+ (import_from_statement)
+ (with_clause)
+ (parameters)
+ (parenthesized_list_splat)
+ (argument_list)
+ (tuple_pattern)
+ (list_pattern)
+ (subscript)
+ (list)
+ (set)
+ (tuple)
+ (dictionary)
+ (dictionary_comprehension)
+ (set_comprehension)
+ (list_comprehension)
+ (generator_expression)
+ (parenthesized_expression)
+ (interpolation)
+ (format_expression)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/racket/rainbows.scm b/runtime/queries/racket/rainbows.scm
new file mode 100644
index 00000000..e11eb788
--- /dev/null
+++ b/runtime/queries/racket/rainbows.scm
@@ -0,0 +1 @@
+; inherits: scheme
diff --git a/runtime/queries/regex/rainbows.scm b/runtime/queries/regex/rainbows.scm
new file mode 100644
index 00000000..a9eb1cff
--- /dev/null
+++ b/runtime/queries/regex/rainbows.scm
@@ -0,0 +1,17 @@
+[
+ (lookahead_assertion)
+ (character_class)
+ (anonymous_capturing_group)
+ (named_capturing_group)
+ (non_capturing_group)
+ (count_quantifier)
+ (character_class_escape)
+] @rainbow.scope
+
+[
+ "(?" "(?:"
+ "(?<" ">"
+ "(" ")"
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/ruby/rainbows.scm b/runtime/queries/ruby/rainbows.scm
new file mode 100644
index 00000000..e67edfb8
--- /dev/null
+++ b/runtime/queries/ruby/rainbows.scm
@@ -0,0 +1,28 @@
+[
+ (begin_block)
+ (end_block)
+ (singleton_method)
+ (block_parameters)
+ (parenthesized_statements)
+ (element_reference)
+ (argument_list "(" ")")
+ (block)
+ (destructured_left_assignment)
+ (interpolation)
+ (string_array)
+ (symbol_array)
+ (regex)
+ (array)
+ (hash)
+ (method_parameters)
+] @rainbow.scope
+
+[
+ "#{"
+ "{" "}"
+ "(" ")"
+ "%w(" "%i("
+ "[" "]"
+ "|"
+ "/"
+] @rainbow.bracket
diff --git a/runtime/queries/rust/rainbows.scm b/runtime/queries/rust/rainbows.scm
new file mode 100644
index 00000000..0656047b
--- /dev/null
+++ b/runtime/queries/rust/rainbows.scm
@@ -0,0 +1,60 @@
+[
+ ; {/}
+ (declaration_list)
+ (field_declaration_list)
+ (field_initializer_list)
+ (enum_variant_list)
+ (block)
+ (match_block)
+ (use_list)
+ (struct_pattern)
+
+ ; (/)
+ (ordered_field_declaration_list)
+ (arguments)
+ (parameters)
+ (tuple_type)
+ (tuple_expression)
+ (tuple_pattern)
+ (tuple_struct_pattern)
+ (unit_type)
+ (unit_expression)
+ (visibility_modifier)
+ (parenthesized_expression)
+ (token_repetition_pattern)
+
+ ; </>
+ (type_parameters)
+ (type_arguments)
+ (bracketed_type)
+ (for_lifetimes)
+
+ ; [/]
+ (array_type)
+ (array_expression)
+ (index_expression)
+ (slice_pattern)
+
+ ; attributes #[]
+ (attribute_item)
+ (inner_attribute_item)
+
+ ; macros
+ (token_tree_pattern)
+ (macro_definition)
+
+ ; closures
+ (closure_parameters)
+] @rainbow.scope
+
+; attributes like `#[serde(rename_all = "kebab-case")]`
+(attribute arguments: (token_tree) @rainbow.scope)
+
+[
+ "#"
+ "[" "]"
+ "(" ")"
+ "{" "}"
+ "<" ">"
+ "|"
+] @rainbow.bracket
diff --git a/runtime/queries/scheme/rainbows.scm b/runtime/queries/scheme/rainbows.scm
new file mode 100644
index 00000000..f948772c
--- /dev/null
+++ b/runtime/queries/scheme/rainbows.scm
@@ -0,0 +1,12 @@
+[
+ (list)
+ (vector)
+ (byte_vector)
+] @rainbow.scope
+
+[
+ "#(" "#vu8("
+ "(" ")"
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/scss/rainbows.scm b/runtime/queries/scss/rainbows.scm
new file mode 100644
index 00000000..f0c648f3
--- /dev/null
+++ b/runtime/queries/scss/rainbows.scm
@@ -0,0 +1,3 @@
+; inherits: css
+
+(parameters) @rainbow.scope
diff --git a/runtime/queries/starlark/rainbows.scm b/runtime/queries/starlark/rainbows.scm
new file mode 100644
index 00000000..0b920cbf
--- /dev/null
+++ b/runtime/queries/starlark/rainbows.scm
@@ -0,0 +1 @@
+; inherits: python
diff --git a/runtime/queries/toml/rainbows.scm b/runtime/queries/toml/rainbows.scm
new file mode 100644
index 00000000..1f61c8ac
--- /dev/null
+++ b/runtime/queries/toml/rainbows.scm
@@ -0,0 +1,12 @@
+[
+ (table_array_element)
+ (table)
+ (array)
+ (inline_table)
+] @rainbow.scope
+
+[
+ "[[" "]]"
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/tsq/rainbows.scm b/runtime/queries/tsq/rainbows.scm
new file mode 100644
index 00000000..b1785fa8
--- /dev/null
+++ b/runtime/queries/tsq/rainbows.scm
@@ -0,0 +1,12 @@
+[
+ (group)
+ (named_node)
+ (wildcard_node)
+ (predicate)
+ (alternation)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/tsx/rainbows.scm b/runtime/queries/tsx/rainbows.scm
new file mode 100644
index 00000000..64c2fe36
--- /dev/null
+++ b/runtime/queries/tsx/rainbows.scm
@@ -0,0 +1,2 @@
+; inherits: typescript
+; inherits: jsx
diff --git a/runtime/queries/typescript/rainbows.scm b/runtime/queries/typescript/rainbows.scm
new file mode 100644
index 00000000..919061aa
--- /dev/null
+++ b/runtime/queries/typescript/rainbows.scm
@@ -0,0 +1,19 @@
+; inherits: ecma
+
+[
+ (import_require_clause)
+ (enum_body)
+ (lookup_type)
+ (parenthesized_type)
+ (object_type)
+ (type_parameters)
+ (index_signature)
+ (array_type)
+ (tuple_type)
+] @rainbow.scope
+
+(type_arguments ["<" ">"] @rainbow.bracket) @rainbow.scope
+
+[
+ "{|" "|}"
+] @rainbow.bracket
diff --git a/runtime/queries/xml/rainbows.scm b/runtime/queries/xml/rainbows.scm
new file mode 100644
index 00000000..0ff9c7fa
--- /dev/null
+++ b/runtime/queries/xml/rainbows.scm
@@ -0,0 +1,29 @@
+[
+ (processing_instructions)
+ (cdata_sect)
+ (xml_decl)
+ (doctype_decl)
+ (element_decl)
+ (element_choice)
+ (element_seq)
+ (mixed)
+ (attlist_decl)
+ (notation_type)
+ (enumeration)
+ (ge_decl)
+ (pe_decl)
+ (notation_decl)
+] @rainbow.scope
+
+((element) @rainbow.scope
+ (#set! rainbow.include-children))
+
+[
+ "<?" "?>"
+ "<" ">"
+ "</" "/>"
+ "<!"
+ "(" ")"
+ ")*"
+ "[" "]"
+] @rainbow.bracket
diff --git a/runtime/queries/yaml/rainbows.scm b/runtime/queries/yaml/rainbows.scm
new file mode 100644
index 00000000..d810accc
--- /dev/null
+++ b/runtime/queries/yaml/rainbows.scm
@@ -0,0 +1,9 @@
+[
+ (flow_sequence)
+ (flow_mapping)
+] @rainbow.scope
+
+[
+ "[" "]"
+ "{" "}"
+] @rainbow.bracket
diff --git a/runtime/queries/zig/rainbows.scm b/runtime/queries/zig/rainbows.scm
new file mode 100644
index 00000000..af823e6d
--- /dev/null
+++ b/runtime/queries/zig/rainbows.scm
@@ -0,0 +1,42 @@
+[
+ ; zig
+ (ArrayTypeStart)
+ ; using ()
+ (AsmExpr)
+ (AsmOutputItem)
+ (ByteAlign)
+ (CallConv)
+ (ContainerDeclType)
+ (ErrorSetDecl)
+ (FnCallArguments)
+ (ForPrefix)
+ (GroupedExpr)
+ (IfPrefix)
+ (ParamDeclList)
+ (SwitchExpr)
+ (WhileContinueExpr)
+ (WhilePrefix)
+ ; for align expressions
+ (PtrTypeStart)
+
+ ; using {}
+ (Block)
+ (BlockExpr)
+ (FormatSequence)
+ (InitList)
+
+ ; using []
+ (SliceTypeStart)
+ (SuffixOp)
+
+ ; zig uses || for captures
+ (Payload "|" @rainbow.bracket)
+ (PtrPayload "|" @rainbow.bracket)
+ (PtrIndexPayload "|" @rainbow.bracket)
+] @rainbow.scope
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+] @rainbow.bracket
diff --git a/xtask/src/querycheck.rs b/xtask/src/querycheck.rs
index a27f85e6..eca0af9a 100644
--- a/xtask/src/querycheck.rs
+++ b/xtask/src/querycheck.rs
@@ -11,6 +11,7 @@ pub fn query_check() -> Result<(), DynError> {
"injections.scm",
"textobjects.scm",
"indents.scm",
+ "rainbows.scm",
];
for language in lang_config().language {