Unnamed repository; edit this file 'description' to name the repository.
1
2
((comment) @injection.content
  (#set! injection.language "comment"))
k, .markdown-body h6:hover a.anchor .mini-icon-link { display: inline-block; } div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink { color: #CCCAC2; } .markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { font-size: inherit; } .markdown-body h1 { font-size: 28px; color: #CCCAC2; } .markdown-body h2 { font-size: 24px; border-bottom: 1px solid #8A9199; color: #CCCAC2; } .markdown-body h3 { font-size: 18px; color: #CCCAC2; } .markdown-body h4 { font-size: 16px; color: #CCCAC2; } .markdown-body h5 { font-size: 14px; color: #CCCAC2; } .markdown-body h6 { color: #CCCAC2; font-size: 14px; } .markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { margin: 15px 0; } .markdown-body hr { border: 2px solid #8A9199; } .markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child { margin-top: 0; padding-top: 0; } .markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 { margin-top: 0; padding-top: 0; } .markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p { margin-top: 0; } .markdown-body li p.first { display: inline-block; } .markdown-body ul, .markdown-body ol { padding-left: 30px; } .markdown-body ul.no-list, .markdown-body ol.no-list { list-style-type: none; padding: 0; } .markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type { margin-top: 0px; } .markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type { margin-bottom: 0; } .markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-bottom: 0; } .markdown-body dl { padding: 0; } .markdown-body dl dt { font-size: 14px; font-weight: bold; font-style: italic; padding: 0; margin: 15px 0 5px; } .markdown-body dl dt:first-child { padding: 0; } .markdown-body dl dt>:first-child { margin-top: 0px; } .markdown-body dl dt>:last-child { margin-bottom: 0px; } .markdown-body dl dd { margin: 0 0 15px; padding: 0 15px; } .markdown-body dl dd>:first-child { margin-top: 0px; } .markdown-body dl dd>:last-child { margin-bottom: 0px; } .markdown-body blockquote { border-left: 4px solid #FFAD66; padding: 0 15px; color: #B8CFE6; } .markdown-body blockquote>:first-child { margin-top: 0px; } .markdown-body blockquote>:last-child { margin-bottom: 0px; } .markdown-body table th { font-weight: bold; } .markdown-body table th, .markdown-body table td { border: 1px solid #8A9199; padding: 6px 13px; } .markdown-body table tr { border-top: 1px solid #8A9199; background-color: #242936; } .markdown-body table tr:nth-child(2n) { background-color: #1A1F29; } .markdown-body img { max-width: 100%; -moz-box-sizing: border-box; box-sizing: border-box; } .markdown-body span.frame { display: block; overflow: hidden; } .markdown-body span.frame>span { border: 1px solid #8A9199; display: block; float: left; overflow: hidden; margin: 13px 0 0; padding: 7px; width: auto; } .markdown-body span.frame span img { display: block; float: left; } .markdown-body span.frame span span { clear: both; color: #CCCAC2; display: block; padding: 5px 0 0; } .markdown-body span.align-center { display: block; overflow: hidden; clear: both; } .markdown-body span.align-center>span { display: block; overflow: hidden; margin: 13px auto 0; text-align: center; } .markdown-body span.align-center span img { margin: 0 auto; text-align: center; } .markdown-body span.align-right { display: block; overflow: hidden; clear: both; } .markdown-body span.align-right>span { display: block; overflow: hidden; margin: 13px 0 0; text-align: right; } .markdown-body span.align-right span img { margin: 0; text-align: right; } .markdown-body span.float-left { display: block; margin-right: 13px; overflow: hidden; float: left; } .markdown-body span.float-left span { margin: 13px 0 0; } .markdown-body span.float-right { display: block; margin-left: 13px; overflow: hidden; float: right; } .markdown-body span.float-right>span { display: block; overflow: hidden; margin: 13px auto 0; text-align: right; } .markdown-body code, .markdown-body tt { margin: 0 2px; padding: 0px 5px; border: 1px solid #8A9199; background-color: #282E3B; border-radius: 3px; color: #CCCAC2; } .markdown-body code { white-space: nowrap; } .markdown-body pre>code { margin: 0; padding: 0; white-space: pre; border: none; background: transparent; } .markdown-body .highlight pre, .markdown-body pre { background-color: #282E3B; border: 1px solid #8A9199; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px; border-radius: 3px; } .markdown-body pre code, .markdown-body pre tt { margin: 0; padding: 0; background-color: transparent; border: none; } pre { line-height: 125%; } td.linenos .normal { color: #8a919966; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: #8a919966; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #363C48 } .highlight .c { color: #6E7C8E } /* Comment */ .highlight .err { color: #FF6666 } /* Error */ .highlight .g { color: #CCCAC2 } /* Generic */ .highlight .k { color: #FFAD66 } /* Keyword */ .highlight .o { color: #F29E74 } /* Operator */ .highlight .ch { color: #6E7C8E } /* Comment.Hashbang */ .highlight .cm { color: #6E7C8E; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #FFAD66 } /* Comment.Preproc */ .highlight .cpf { color: #D5FF80 } /* Comment.PreprocFile */ .highlight .c1 { color: #6E7C8E; font-style: italic } /* Comment.Single */ .highlight .cs { color: #6E7C8E } /* Comment.Special */ .highlight .gd { color: #F27983 } /* Generic.Deleted */ .highlight .ge { color: #FFD173; font-style: italic } /* Generic.Emph */ .highlight .gr { color: #FF6666 } /* Generic.Error */ .highlight .gh { color: #FFD173; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #87D96C } /* Generic.Inserted */ .highlight .go { color: #D5FF80 } /* Generic.Output */ .highlight .gp { color: #F29E74 } /* Generic.Prompt */ .highlight .gs { color: #FFD173; font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #CCCAC2 } /* Generic.Subheading */ .highlight .gt { color: #FF6666 } /* Generic.Traceback */ .highlight .kc { color: #DFBFFF } /* Keyword.Constant */ .highlight .kd { color: #FFCC66 } /* Keyword.Declaration */ .highlight .kn { color: #FFAD66 } /* Keyword.Namespace */ .highlight .kp { color: #FFAD66 } /* Keyword.Pseudo */ .highlight .kr { color: #FFAD66 } /* Keyword.Reserved */ .highlight .kt { color: #73D0FF } /* Keyword.Type */ .highlight .m { color: #DFBFFF } /* Literal.Number */ .highlight .s { color: #D5FF80 } /* Literal.String */ .highlight .na { color: #FFD173 } /* Name.Attribute */ .highlight .nb { color: #F28779 } /* Name.Builtin */ .highlight .nc { color: #5CCFE6 } /* Name.Class */ .highlight .no { color: #DFBFFF } /* Name.Constant */ .highlight .nd { color: #FFDFB3 } /* Name.Decorator */ .highlight .nf { color: #FFD173 } /* Name.Function */ .highlight .nt { color: #5CCFE6 } /* Name.Tag */ .highlight .ow { color: #F29E74 } /* Operator.Word */ .highlight .w { color: #CCCAC2 } /* Text.Whitespace */ .highlight .mb { color: #DFBFFF } /* Literal.Number.Bin */ .highlight .mf { color: #DFBFFF } /* Literal.Number.Float */ .highlight .mh { color: #DFBFFF } /* Literal.Number.Hex */ .highlight .mi { color: #DFBFFF } /* Literal.Number.Integer */ .highlight .mo { color: #DFBFFF } /* Literal.Number.Oct */ .highlight .sa { color: #D5FF80 } /* Literal.String.Affix */ .highlight .sb { color: #F29E74 } /* Literal.String.Backtick */ .highlight .sc { color: #D5FF80 } /* Literal.String.Char */ .highlight .dl { color: #D5FF80 } /* Literal.String.Delimiter */ .highlight .sd { color: #95E6CB; font-style: italic } /* Literal.String.Doc */ .highlight .s2 { color: #D5FF80 } /* Literal.String.Double */ .highlight .se { color: #D5FF80 } /* Literal.String.Escape */ .highlight .sh { color: #D5FF80 } /* Literal.String.Heredoc */ .highlight .si { color: #D5FF80 } /* Literal.String.Interpol */ .highlight .sx { color: #D5FF80 } /* Literal.String.Other */ .highlight .sr { color: #95E6CB } /* Literal.String.Regex */ .highlight .s1 { color: #D5FF80 } /* Literal.String.Single */ .highlight .ss { color: #D5FF80 } /* Literal.String.Symbol */ .highlight .bp { color: #F28779 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #FFD173 } /* Name.Function.Magic */ .highlight .vi { color: #FFCC66 } /* Name.Variable.Instance */ .highlight .vm { color: #5CCFE6; font-style: italic } /* Name.Variable.Magic */ .highlight .il { color: #DFBFFF } /* Literal.Number.Integer.Long */

Adding indent queries

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.

Indent queries

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.

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.

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.

Capture types

@indent / @outdent

Consider this example:

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 @outdents also combine into 1 and result in that line losing one indent level.

@extend / @extend.prevent-once

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

@indent.always / @outdent.always

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
)

Predicates

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:

Additionally, we support some custom predicates for indent queries:

Scopes

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

For example, imagine we have the following function

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:

scm ((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.)