Skip to content

Commit

Permalink
Add a guide on writing rainbow queries
Browse files Browse the repository at this point in the history
  • Loading branch information
the-mikedavis committed Aug 21, 2022
1 parent 3396f9b commit 656e3fd
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 1 deletion.
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
- [Adding Languages](./guides/adding_languages.md)
- [Adding Textobject Queries](./guides/textobject.md)
- [Adding Indent Queries](./guides/indent.md)
- [Adding Rainbow Bracket Queries](./guides/rainbow_bracket_queries.md)
2 changes: 1 addition & 1 deletion book/src/guides/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Guides

This section contains guides for adding new language server configurations,
tree-sitter grammars, textobject queries, etc.
tree-sitter grammars, textobject and rainbow bracket queries, etc.
124 changes: 124 additions & 0 deletions book/src/guides/rainbow_bracket_queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 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. As a scheme dialect, 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 need make `@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 scan 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 order to be highlighted. This
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 ; < >
(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.

```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))
```

[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

0 comments on commit 656e3fd

Please sign in to comment.