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