Skip to content

Commit 656e3fd

Browse files
committed
Add a guide on writing rainbow queries
1 parent 3396f9b commit 656e3fd

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
- [Adding Languages](./guides/adding_languages.md)
1818
- [Adding Textobject Queries](./guides/textobject.md)
1919
- [Adding Indent Queries](./guides/indent.md)
20+
- [Adding Rainbow Bracket Queries](./guides/rainbow_bracket_queries.md)

book/src/guides/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Guides
22

33
This section contains guides for adding new language server configurations,
4-
tree-sitter grammars, textobject queries, etc.
4+
tree-sitter grammars, textobject and rainbow bracket queries, etc.
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Adding Rainbow Bracket Queries
2+
3+
Helix uses `rainbows.scm` tree-sitter query files to provide rainbow bracket
4+
functionality.
5+
6+
Tree-sitter queries are documented in the tree-sitter online documentation.
7+
If you're writing queries for the first time, be sure to check out the section
8+
on [syntax highlighting queries] and on [query syntax].
9+
10+
Rainbow queries have two captures: `@rainbow.scope` and `@rainbow.bracket`.
11+
`@rainbow.scope` should capture any node that increases the nesting level
12+
while `@rainbow.bracket` should capture any bracket nodes. Put another way:
13+
`@rainbow.scope` switches to the next rainbow color for all nodes in the tree
14+
under it while `@rainbow.bracket` paints captured nodes with the current
15+
rainbow color.
16+
17+
For an example, let's add rainbow queries for the tree-sitter query (TSQ)
18+
language itself. These queries will go into a
19+
`runtime/queries/tsq/rainbows.scm` file in the repository root.
20+
21+
First we'll add the `@rainbow.bracket` captures. As a scheme dialect, TSQ
22+
only has parentheses and square brackets:
23+
24+
```tsq
25+
["(" ")" "[" "]"] @rainbow.bracket
26+
```
27+
28+
The ordering of the nodes within the alternation (square brackets) is not
29+
taken into consideration.
30+
31+
> Note: Why are these nodes quoted? Most syntax highlights capture text
32+
> surrounded by parentheses. These are _named nodes_ and correspond to the
33+
> names of rules in the grammar. Brackets are usually written in tree-sitter
34+
> grammars as literal strings, for example:
35+
>
36+
> ```js
37+
> {
38+
> // ...
39+
> arguments: seq("(", repeat($.argument), ")"),
40+
> // ...
41+
> }
42+
> ```
43+
>
44+
> Nodes written as literal strings in tree-sitter grammars may be captured
45+
> in queries with those same literal strings.
46+
47+
Then we need make `@rainbow.scope` captures. The easiest way to do this is to
48+
view the `grammar.js` file in the tree-sitter grammar's repository. For TSQ,
49+
that file is [here][tsq grammar.js]. As we scan down the `grammar.js`, we see
50+
that the `(alternation)`, (L36) `(group)` (L57), `(named_node)` (L59),
51+
`(predicate)` (L87) and `(wildcard_node)` (L97) nodes all contain literal
52+
parentheses or square brackets in their definitions. These nodes are all
53+
direct parents of brackets and happen to also be the nodes we want to change
54+
to the next rainbow color, so we capture them as `@rainbow.scope`.
55+
56+
```tsq
57+
[
58+
(group)
59+
(named_node)
60+
(wildcard_node)
61+
(predicate)
62+
(alternation)
63+
] @rainbow.scope
64+
```
65+
66+
This strategy works as a rule of thumb for most programming and configuration
67+
languages. Markup languages can be trickier and may take additional
68+
experimentation to find the correct nodes to use for scopes and brackets.
69+
70+
The `:tree-sitter-subtree` command shows the syntax tree under the primary
71+
selection in S-expression format and can be a useful tool for determining how
72+
to write a query.
73+
74+
### Properties
75+
76+
The `rainbow.include-children` property may be applied to `@rainbow.scope`
77+
captures. By default, all `@rainbow.bracket` captures must be direct descendant
78+
of a node captured with `@rainbow.scope` in order to be highlighted. This
79+
property disables that check and allows `@rainbow.bracket` captures to be
80+
highlighted if they are direct or indirect descendants of some node captured
81+
with `@rainbow.scope`.
82+
83+
For example, this property is used in the HTML rainbow queries.
84+
85+
For a document like `<a>link</a>`, the syntax tree is:
86+
87+
```tsq
88+
(element ; <a>link</a>
89+
(start_tag ; < >
90+
(tag_name)) ; a
91+
(text) ; link
92+
(end_tag ; </ >
93+
(tag_name))) ; a
94+
```
95+
96+
If we want to highlight the `<`, `>` and `</` nodes with rainbow colors, we
97+
capture them as `@rainbow.bracket`:
98+
99+
```tsq
100+
["<" ">" "</"] @rainbow.bracket
101+
```
102+
103+
And we capture `(element)` as `@rainbow.scope` because `(element)` nodes nest
104+
within each other.
105+
106+
```tsq
107+
(element) @rainbow.scope
108+
```
109+
110+
But this combination of `@rainbow.scope` and `@rainbow.bracket` will not
111+
highlight any nodes: `<`, `>` and `</` are children of the `(start_tag)` and
112+
`(end_tag)` nodes. We can't capture `(start_tag)` and `(end_tag)` as
113+
`@rainbow.scope` because they don't nest other elements. We can fix this case
114+
by removing the requirement that `<`, `>` and `</` are direct descendants of
115+
`element` using the `rainbow.include-children` property.
116+
117+
```tsq
118+
((element) @rainbow.scope
119+
(#set! rainbow.include-children))
120+
```
121+
122+
[syntax highlighting queries]: https://tree-sitter.github.io/tree-sitter/syntax-highlighting#highlights
123+
[query syntax]: https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries
124+
[tsq grammar.js]: https://github.com/the-mikedavis/tree-sitter-tsq/blob/48b5e9f82ae0a4727201626f33a17f69f8e0ff86/grammar.js

0 commit comments

Comments
 (0)