-
-Helix uses tree-sitter to correctly indent new lines. This requires a tree-
-sitter grammar and an indent.scm
query file placed in runtime/queries/ {language}/indents.scm
. The indentation for a line is calculated by traversing
-the syntax tree from the lowest node at the beginning of the new line (see
-Indent queries). Each of these nodes contributes to the total
-indent when it is captured by the query (in what way depends on the name of
-the capture.
-Note that it matters where these added indents begin. For example,
-multiple indent level increases that start on the same line only increase
-the total indent level by 1. See Capture types.
-By default, Helix uses the hybrid
indentation heuristic. This means that
-indent queries are not used to compute the expected absolute indentation of a
-line but rather the expected difference in indentation between the new and an
-already existing line. This difference is then added to the actual indentation
-of the already existing line. Since this makes errors in the indent queries
-harder to find, it is recommended to disable it when testing via
-:set indent-heuristic tree-sitter
. The rest of this guide assumes that
-the tree-sitter
heuristic is used.
-
-When Helix is inserting a new line through o
, O
, or <ret>
, to determine
-the indent level for the new line, the query in indents.scm
is run on the
-document. The starting position of the query is the end of the line above where
-a new line will be inserted.
-For o
, the inserted line is the line below the cursor, so that starting
-position of the query is the end of the current line.
-#![allow(unused)]
-fn main() {
-fn need_hero(some_hero: Hero, life: Life) -> {
- matches!(some_hero, Hero { // โโโโโโโโโโโโโโโโโโโฎ
- strong: true,//โโฎ โ โ โ
- fast: true, // โ โ โฐโโ query start โ
- sure: true, // โ โฐโโโโโ cursor โโ traversal
- soon: true, // โฐโโโโโโโโ new line inserted โ start node
- }) && // โ
-// โ โ
-// โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
- some_hero > life
-}
-}
-For O
, the newly inserted line is the current line, so the starting position
-of the query is the end of the line above the cursor.
-#![allow(unused)]
-fn main() {
-fn need_hero(some_hero: Hero, life: Life) -> { // โโโฎ
- matches!(some_hero, Hero { // โโฎ โ โ
- strong: true,// โ โญโโโโฏ โ โ
- fast: true, // โ โ query start โโฏ โ
- sure: true, // โฐโโโโผ cursor โโ traversal
- soon: true, // โฐ new line inserted โ start node
- }) && // โ
- some_hero > life // โ
-} // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
-}
-From this starting node, the syntax tree is traversed up until the root node.
-Each indent capture is collected along the way, and then combined according to
-their capture types and scopes to a final indent
-level for the line.
-
-
-@indent
(default scope tail
):
-Increase the indent level by 1. Multiple occurrences in the same line do not
-stack. If there is at least one @indent
and one @outdent
capture on the
-same line, the indent level isn't changed at all.
-@outdent
(default scope all
):
-Decrease the indent level by 1. The same rules as for @indent
apply.
-@indent.always
(default scope tail
):
-Increase the indent level by 1. Multiple occurrences on the same line do
-stack. The final indent level is @indent.always
โ @outdent.always
. If
-an @indent
and an @indent.always
are on the same line, the @indent
is
-ignored.
-@outdent.always
(default scope all
):
-Decrease the indent level by 1. The same rules as for @indent.always
apply.
-@align
(default scope all
):
-Align everything inside this node to some anchor. The anchor is given
-by the start of the node captured by @anchor
in the same pattern.
-Every pattern with an @align
should contain exactly one @anchor
.
-Indent (and outdent) for nodes below (in terms of their starting line)
-the @align
node is added to the indentation required for alignment.
-@extend
:
-Extend the range of this node to the end of the line and to lines that are
-indented more than the line that this node starts on. This is useful for
-languages like Python, where for the purpose of indentation some nodes (like
-functions or classes) should also contain indented lines that follow them.
-@extend.prevent-once
:
-Prevents the first extension of an ancestor of this node. For example, in Python
-a return expression always ends the block that it is in. Note that this only
-stops the extension of the next @extend
capture. If multiple ancestors are
-captured, only the extension of the innermost one is prevented. All other
-ancestors are unaffected (regardless of whether the innermost ancestor would
-actually have been extended).
-
-
-Consider this example:
-#![allow(unused)]
-fn main() {
-fn shout(things: Vec<Thing>) {
- // โ
- // โโโโโโโโโโโโโโโโโโโโโโโโโฎ indent level
- // @indent โโโโโโโโโโโโโโโ
- // โ
- let it_all = |out| { things.filter(|thing| { // โ 1
- // โ โ โ
- // โโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโผโโโโโโโโโโโโโโ
- // @indent @indent โ
- // โ 2
- thing.can_do_with(out) // โ
- })}; // โโโโโโโโโโโโโโโ
- //โโโ โ 1
-} //โฐโผโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
-// 3x @outdent
-}
-((block) @indent)
-["}" ")"] @outdent
-
-Note how on the second line, we have two blocks begin on the same line. In this
-case, since both captures occur on the same line, they are combined and only
-result in a net increase of 1. Also note that the closing }
s are part of the
-@indent
captures, but the 3 @outdent
s also combine into 1 and result in that
-line losing one indent level.
-
-For an example of where @extend
can be useful, consider Python, which is
-whitespace-sensitive.
-]
- (parenthesized_expression)
- (function_definition)
- (class_definition)
-] @indent
-
-
-class Hero:
- def __init__(self, strong, fast, sure, soon):# โโโฎ
- self.is_strong = strong # โ
- self.is_fast = fast # โญโโโ query start โ
- self.is_sure = sure # โ โญโ cursor โ
- self.is_soon = soon # โ โ โ
- # โ โ โ โ โ
- # โ โฐโโโโโโโฏ โ โ
- # โฐโโโโโโโโโโโโโโโโโโโโโโฏ โ
- # โโ traversal
- def need_hero(self, life): # โ start node
- return ( # โ
- self.is_strong # โ
- and self.is_fast # โ
- and self.is_sure # โ
- and self.is_soon # โ
- and self > life # โ
- ) # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
-
-Without braces to catch the scope of the function, the smallest descendant of
-the cursor on a line feed ends up being the entire inside of the class. Because
-of this, it will miss the entire function node and its indent capture, leading
-to an indent level one too small.
-To address this case, @extend
tells helix to "extend" the captured node's span
-to the line feed and every consecutive line that has a greater indent level than
-the line of the node.
-(parenthesized_expression) @indent
-
-]
- (function_definition)
- (class_definition)
-] @indent @extend
-
-
-class Hero:
- def __init__(self, strong, fast, sure, soon):# โโโฎ
- self.is_strong = strong # โ
- self.is_fast = fast # โญโโโ query start โโ traversal
- self.is_sure = sure # โ โญโ cursor โ start node
- self.is_soon = soon # โ โ โโโโโโโโโโโโโโโโโฏ
- # โ โ โ โ
- # โ โฐโโโโโโโฏ โ
- # โฐโโโโโโโโโโโโโโโโโโโโโโฏ
- def need_hero(self, life):
- return (
- self.is_strong
- and self.is_fast
- and self.is_sure
- and self.is_soon
- and self > life
- )
-
-Furthermore, there are some cases where extending to everything with a greater
-indent level may not be desirable. Consider the need_hero
function above. If
-our cursor is on the last line of the returned expression.
-class Hero:
- def __init__(self, strong, fast, sure, soon):
- self.is_strong = strong
- self.is_fast = fast
- self.is_sure = sure
- self.is_soon = soon
-
- def need_hero(self, life):
- return (
- self.is_strong
- and self.is_fast
- and self.is_sure
- and self.is_soon
- and self > life
- ) # โโโโ cursor
- #โโโโโโโโโโโ where cursor should go on new line
-
-In Python, the are a few tokens that will always end a scope, such as a return
-statement. Since the scope ends, so should the indent level. But because the
-function span is extended to every line with a greater indent level, a new line
-would just continue on the same level. And an @outdent
would not help us here
-either, since it would cause everything in the parentheses to become outdented
-as well.
-To help, we need to signal an end to the extension. We can do this with
-@extend.prevent-once
.
-(parenthesized_expression) @indent
-
-]
- (function_definition)
- (class_definition)
-] @indent @extend
-
-(return_statement) @extend.prevent-once
-
-
-As mentioned before, normally if there is more than one @indent
or @outdent
-capture on the same line, they are combined.
-Sometimes, there are cases when you may want to ensure that every indent capture
-is additive, regardless of how many occur on the same line. Consider this
-example in YAML.
- - foo: bar
-# โ โ
-# โ โฐโโโโโโโโโโโโโโโ start of map
-# โฐโโโโโโโโโโโโโโโโโ start of list element
- baz: quux # โโโโ cursor
- # โโโโโโโโโโโโโโ where the cursor should go on a new line
- garply: waldo
- - quux:
- bar: baz
- xyzzy: thud
- fred: plugh
-
-In YAML, you often have lists of maps. In these cases, the syntax is such that
-the list element and the map both start on the same line. But we really do want
-to start an indentation for each of these so that subsequent keys in the map
-hang over the list and align properly. This is where @indent.always
helps.
-((block_sequence_item) @item @indent.always @extend
- (#not-one-line? @item))
-
-((block_mapping_pair
- key: (_) @key
- value: (_) @val
- (#not-same-line? @key @val)
- ) @indent.always @extend
-)
-
-
-In some cases, an S-expression cannot express exactly what pattern should be matched.
-For that, tree-sitter allows for predicates to appear anywhere within a pattern,
-similar to how #set!
declarations work:
-(some_kind
- (child_kind) @indent
- (#predicate? arg1 arg2 ...)
-)
-
-The number of arguments depends on the predicate that's used.
-Each argument is either a capture (@name
) or a string ("some string"
).
-The following predicates are supported by tree-sitter:
-
--
-
#eq?
/#not-eq?
:
-The first argument (a capture) must/must not be equal to the second argument
-(a capture or a string).
-
--
-
#match?
/#not-match?
:
-The first argument (a capture) must/must not match the regex given in the
-second argument (a string).
-
--
-
#any-of?
/#not-any-of?
:
-The first argument (a capture) must/must not be one of the other arguments
-(strings).
-
-
-Additionally, we support some custom predicates for indent queries:
-
--
-
#not-kind-eq?
:
-The kind of the first argument (a capture) must not be equal to the second
-argument (a string).
-
--
-
#same-line?
/#not-same-line?
:
-The captures given by the 2 arguments must/must not start on the same line.
-
--
-
#one-line?
/#not-one-line?
:
-The captures given by the fist argument must/must span a total of one line.
-
-
-
-Added indents don't always apply to the whole node. For example, in most
-cases when a node should be indented, we actually only want everything
-except for its first line to be indented. For this, there are several
-scopes (more scopes may be added in the future if required):
-
-tail
:
-This scope applies to everything except for the first line of the
-captured node.
-all
:
-This scope applies to the whole captured node. This is only different from
-tail
when the captured node is the first node on its line.
-
-For example, imagine we have the following function
-#![allow(unused)]
-fn main() {
-fn aha() { // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
- let take = "on me"; // โโโโโโโโโโโโโโโโฎ scope: โ
- let take = "me on"; // โโ "tail" โโ (block) @indent
- let ill = be_gone_days(1 || 2); // โ โ
-} // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโดโ "}" @outdent
- // scope: "all"
-}
-We can write the following query with the #set!
declaration:
-((block) @indent
- (#set! "scope" "tail"))
-("}" @outdent
- (#set! "scope" "all"))
-
-As we can see, the "tail" scope covers the node, except for the first line.
-Everything up to and including the closing brace gets an indent level of 1.
-Then, on the closing brace, we encounter an outdent with a scope of "all", which
-means the first line is included, and the indent level is cancelled out on this
-line. (Note these scopes are the defaults for @indent
and @outdent
โthey are
-written explicitly for demonstration.)
-
-