@@ -1,76 +1,293 @@
1# Adding indent queries
1# Adding indent queries
3−Helix uses tree-sitter to correctly indent new lines. This requires
3+Helix uses tree-sitter to correctly indent new lines. This requires a tree-
4−a tree-sitter grammar and an `indent.scm` query file placed in
4+sitter grammar and an `indent.scm` query file placed in `runtime/queries/
5−`runtime/queries/{language}/indents.scm`. The indentation for a line
5+{language}/indents.scm`. The indentation for a line is calculated by traversing
6−is calculated by traversing the syntax tree from the lowest node at the
6+the syntax tree from the lowest node at the beginning of the new line (see
7−beginning of the new line. Each of these nodes contributes to the total
7+[Indent queries](#indent-queries)). Each of these nodes contributes to the total
8−indent when it is captured by the query (in what way depends on the name
8+indent when it is captured by the query (in what way depends on the name of
9−of the capture).
9+the capture.
11Note that it matters where these added indents begin. For example,
11Note that it matters where these added indents begin. For example,
12multiple indent level increases that start on the same line only increase
12multiple indent level increases that start on the same line only increase
13−the total indent level by 1.
13+the total indent level by 1. See [Capture types](#capture-types).
15−## Scopes
15+## Indent queries
17−Added indents don't always apply to the whole node. For example, in most
17+When Helix is inserting a new line through `o`, `O`, or `<ret>`, to determine
18−cases when a node should be indented, we actually only want everything
18+the indent level for the new line, the query in `indents.scm` is run on the
19−except for its first line to be indented. For this, there are several
19+document. The starting position of the query is the end of the line above where
20−scopes (more scopes may be added in the future if required):
20+a new line will be inserted.
22−- `all`:
22+For `o`, the inserted line is the line below the cursor, so that starting
23−This scope applies to the whole captured node. This is only different from
23+position of the query is the end of the current line.
24−`tail` when the captured node is the first node on its line.
27−This scope applies to everything except for the first line of the
26+fn need_hero(some_hero: Hero, life: Life) -> {
28−captured node.
27+ matches!(some_hero, Hero { // ←─────────────────╮
28+ strong: true,//←╮ ↑ ↑ │
29+ fast: true, // │ │ ╰── query start │
30+ sure: true, // │ ╰───── cursor ├─ traversal
31+ soon: true, // ╰──────── new line inserted │ start node
34+// ╰───────────────────────────────────────────────╯
30−Every capture type has a default scope which should do the right thing
39+For `O`, the newly inserted line is the *current* line, so the starting position
31−in most situations. When a different scope is required, this can be
40+of the query is the end of the line above the cursor.
32−changed by using a `#set!` declaration anywhere in the pattern:
41+
34−(assignment_expression
43+fn need_hero(some_hero: Hero, life: Life) -> { // ←─╮
35− right: (_) @indent
44+ matches!(some_hero, Hero { // ←╮ ↑ │
36− (#set! "scope" "all"))
45+ strong: true,// ↑ ╭───╯ │ │
46+ fast: true, // │ │ query start ─╯ │
47+ sure: true, // ╰───┼ cursor ├─ traversal
48+ soon: true, // ╰ new line inserted │ start node
50+ some_hero > life // │
51+} // ←──────────────────────────────────────────────╯
39−## Capture types
54+From this starting node, the syntax tree is traversed up until the root node.
55+Each indent capture is collected along the way, and then combined according to
56+their [capture types](#capture-types) and [scopes](#scopes) to a final indent
41−- `@indent` (default scope `tail`):
59+### Capture types
42−Increase the indent level by 1. Multiple occurrences in the same line
43−don't stack. If there is at least one `@indent` and one `@outdent`
44−capture on the same line, the indent level isn't changed at all.
61+- `@indent` (default scope `tail`):
62+ Increase the indent level by 1. Multiple occurrences in the same line *do not*
63+ stack. If there is at least one `@indent` and one `@outdent` capture on the
64+ same line, the indent level isn't changed at all.
46- `@outdent` (default scope `all`):
65- `@outdent` (default scope `all`):
47−Decrease the indent level by 1. The same rules as for `@indent` apply.
66+ Decrease the indent level by 1. The same rules as for `@indent` apply.
48−
67+- `@indent.always` (default scope `tail`):
68+ Increase the indent level by 1. Multiple occurrences on the same line *do*
69+ stack. The final indent level is `@indent.always` – `@outdent.always`. If
70+ an `@indent` and an `@indent.always` are on the same line, the `@indent` is
72+- `@outdent.always` (default scope `all`):
73+ Decrease the indent level by 1. The same rules as for `@indent.always` apply.
49- `@extend`:
74- `@extend`:
50−Extend the range of this node to the end of the line and to lines that
75+ Extend the range of this node to the end of the line and to lines that are
51−are indented more than the line that this node starts on. This is useful
76+ indented more than the line that this node starts on. This is useful for
52−for languages like Python, where for the purpose of indentation some nodes
77+ languages like Python, where for the purpose of indentation some nodes (like
53−(like functions or classes) should also contain indented lines that follow them.
78+ functions or classes) should also contain indented lines that follow them.
55- `@extend.prevent-once`:
79- `@extend.prevent-once`:
56−Prevents the first extension of an ancestor of this node. For example, in Python
80+ Prevents the first extension of an ancestor of this node. For example, in Python
57−a return expression always ends the block that it is in. Note that this only stops the
81+ a return expression always ends the block that it is in. Note that this only
58−extension of the next `@extend` capture. If multiple ancestors are captured,
82+ stops the extension of the next `@extend` capture. If multiple ancestors are
59−only the extension of the innermost one is prevented. All other ancestors are unaffected
83+ captured, only the extension of the innermost one is prevented. All other
60−(regardless of whether the innermost ancestor would actually have been extended).
84+ ancestors are unaffected (regardless of whether the innermost ancestor would
85+ actually have been extended).
87+#### `@indent` / `@outdent`
89+Consider this example:
92+fn shout(things: Vec<Thing>) {
94+ // ├───────────────────────╮ indent level
95+ // @indent ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
97+ let it_all = |out| { things.filter(|thing| { // │ 1
99+ // ├───────────────────────┼─────┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄
100+ // @indent @indent │
102+ thing.can_do_with(out) // │
103+ })}; // ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄
105+} //╰┼┴──────────────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┄┄┄
114+Note how on the second line, we have two blocks begin on the same line. In this
115+case, since both captures occur on the same line, they are combined and only
116+result in a net increase of 1. Also note that the closing `}`s are part of the
117+`@indent` captures, but the 3 `@outdent`s also combine into 1 and result in that
118+line losing one indent level.
120+#### `@extend` / `@extend.prevent-once`
122+For an example of where `@extend` can be useful, consider Python, which is
123+whitespace-sensitive.
127+ (parenthesized_expression)
128+ (function_definition)
136+ def __init__(self, strong, fast, sure, soon):# ←─╮
137+ self.is_strong = strong # │
138+ self.is_fast = fast # ╭─── query start │
139+ self.is_sure = sure # │ ╭─ cursor │
140+ self.is_soon = soon # │ │ │
143+ # ╰─────────────────────╯ │
145+ def need_hero(self, life): # │ start node
148+ and self.is_fast # │
149+ and self.is_sure # │
150+ and self.is_soon # │
152+ ) # ←─────────────────────────────────────────╯
155+Without braces to catch the scope of the function, the smallest descendant of
156+the cursor on a line feed ends up being the entire inside of the class. Because
157+of this, it will miss the entire function node and its indent capture, leading
158+to an indent level one too small.
160+To address this case, `@extend` tells helix to "extend" the captured node's span
161+to the line feed and every consecutive line that has a greater indent level than
162+the line of the node.
165+(parenthesized_expression) @indent
168+ (function_definition)
176+ def __init__(self, strong, fast, sure, soon):# ←─╮
177+ self.is_strong = strong # │
178+ self.is_fast = fast # ╭─── query start ├─ traversal
179+ self.is_sure = sure # │ ╭─ cursor │ start node
180+ self.is_soon = soon # │ │ ←───────────────╯
183+ # ╰─────────────────────╯
184+ def need_hero(self, life):
194+Furthermore, there are some cases where extending to everything with a greater
195+indent level may not be desirable. Consider the `need_hero` function above. If
196+our cursor is on the last line of the returned expression.
200+ def __init__(self, strong, fast, sure, soon):
201+ self.is_strong = strong
206+ def need_hero(self, life):
214+ #←────────── where cursor should go on new line
217+In Python, the are a few tokens that will always end a scope, such as a return
218+statement. Since the scope ends, so should the indent level. But because the
219+function span is extended to every line with a greater indent level, a new line
220+would just continue on the same level. And an `@outdent` would not help us here
221+either, since it would cause everything in the parentheses to become outdented
224+To help, we need to signal an end to the extension. We can do this with
225+`@extend.prevent-once`.
228+(parenthesized_expression) @indent
231+ (function_definition)
235+(return_statement) @extend.prevent-once
238+#### `@indent.always` / `@outdent.always`
240+As mentioned before, normally if there is more than one `@indent` or `@outdent`
241+capture on the same line, they are combined.
243+Sometimes, there are cases when you may want to ensure that every indent capture
244+is additive, regardless of how many occur on the same line. Consider this
250+# │ ╰─────────────── start of map
251+# ╰───────────────── start of list element
252+ baz: quux # ←─── cursor
253+ # ←───────────── where the cursor should go on a new line
261+In YAML, you often have lists of maps. In these cases, the syntax is such that
262+the list element and the map both start on the same line. But we really do want
263+to start an indentation for each of these so that subsequent keys in the map
264+hang over the list and align properly. This is where `@indent.always` helps.
267+((block_sequence_item) @item @indent.always @extend
268+ (#not-one-line? @item))
273+ (#not-same-line? @key @val)
274+ ) @indent.always @extend
63## Predicates
278## Predicates
65In some cases, an S-expression cannot express exactly what pattern should be matched.
280In some cases, an S-expression cannot express exactly what pattern should be matched.
66For that, tree-sitter allows for predicates to appear anywhere within a pattern,
281For that, tree-sitter allows for predicates to appear anywhere within a pattern,
67similar to how `#set!` declarations work:
282similar to how `#set!` declarations work:
69(some_kind
285(some_kind
70 (child_kind) @indent
286 (child_kind) @indent
71 (#predicate? arg1 arg2 ...)
287 (#predicate? arg1 arg2 ...)
74The number of arguments depends on the predicate that's used.
291The number of arguments depends on the predicate that's used.
75Each argument is either a capture (`@name`) or a string (`"some string"`).
292Each argument is either a capture (`@name`) or a string (`"some string"`).
76The following predicates are supported by tree-sitter:
293The following predicates are supported by tree-sitter:
@@ -91,3 +308,47 @@ argument (a string).
92- `#same-line?`/`#not-same-line?`:
309- `#same-line?`/`#not-same-line?`:
93The captures given by the 2 arguments must/must not start on the same line.
310The captures given by the 2 arguments must/must not start on the same line.
312+- `#one-line?`/`#not-one-line?`:
313+The captures given by the fist argument must/must span a total of one line.
317+Added indents don't always apply to the whole node. For example, in most
318+cases when a node should be indented, we actually only want everything
319+except for its first line to be indented. For this, there are several
320+scopes (more scopes may be added in the future if required):
323+This scope applies to everything except for the first line of the
326+This scope applies to the whole captured node. This is only different from
327+`tail` when the captured node is the first node on its line.
329+For example, imagine we have the following function
332+fn aha() { // ←─────────────────────────────────────╮
333+ let take = "on me"; // ←──────────────╮ scope: │
334+ let take = "me on"; // ├─ "tail" ├─ (block) @indent
335+ let ill = be_gone_days(1 || 2); // │ │
336+} // ←───────────────────────────────────┴──────────┴─ "}" @outdent
340+We can write the following query with the `#set!` declaration:
344+ (#set! "scope" "tail"))
346+ (#set! "scope" "all"))
349+As we can see, the "tail" scope covers the node, except for the first line.
350+Everything up to and including the closing brace gets an indent level of 1.
351+Then, on the closing brace, we encounter an outdent with a scope of "all", which
352+means the first line is included, and the indent level is cancelled out on this
353+line. (Note these scopes are the defaults for `@indent` and `@outdent`—they are
354+written explicitly for demonstration.)