Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Adding Locals Queries

locals.scm queries teach Helix about variable scopes and definitions so that local variables can be highlighted distinctly from global ones. When a @local.reference identifier is resolved to a @local.definition, it inherits the highlight class of that definition rather than the class assigned by highlights.scm.

Query files should be placed in runtime/queries/{language}/locals.scm when contributing to Helix.

Captures

@local.scope

Marks a node as a scope boundary. Definitions are only visible to references that appear inside the same scope or a nested one. Typical scope nodes are function bodies and blocks.

[
  (function_definition)
  (block)
] @local.scope

@local.definition.*

Marks a name node as introducing a local symbol. The suffix after @local.definition. becomes the highlight class applied to any reference that resolves to this definition. For example, @local.definition.variable.parameter causes matching references to be highlighted as variable.parameter.

(function_item
  (parameters
    (parameter
      pattern: (identifier) @local.definition.variable.parameter)))

Common suffixes mirror the highlight classes in highlights.scm: variable, variable.parameter, variable.builtin, variable.mutable, function, namespace, type, constant, etc.

@local.reference

Marks an identifier node as a potential reference to a local definition. Helix searches enclosing scopes for a matching definition and, if found, highlights the reference with that definition’s class instead.

(identifier) @local.reference

Discard captures

Any capture in locals.scm that is not @local.scope, @local.reference, or @local.definition.* acts as a discard. It prevents a @local.reference from being resolved at that node without affecting the highlights.scm result. This is useful for identifier nodes that look like references but should not be treated as variable references, for example keyword argument names or struct field names in struct literals.

; Keyword argument names in a call are not variable references.
(keyword_argument
  name: (identifier) @_)

Later patterns in a query file have higher precedence than earlier ones. A discard pattern must appear after the @local.reference pattern it is intended to override. Placing it later ensures it takes precedence and cancels the reference resolution for nodes it matches.

The convention is to use a capture name beginning with an underscore (e.g. @_, @_keyword) to make the discard intent clear, but any non-@local.* name works.

How definitions and references are matched

Helix matches references to definitions by comparing the text of the reference node against the text of definitions visible from the current scope. Definitions in inner scopes shadow those in outer scopes. If no definition is found the reference is left with its highlights.scm highlight.

Relationship with highlights.scm

The locals system runs alongside highlights.scm, not instead of it. highlights.scm always determines the baseline highlight for a node. locals.scm can override that highlight only when a @local.reference successfully resolves to a @local.definition, and only for that specific resolution. Non-@local.* captures in locals.scm (i.e. discards) have no effect on highlights.scm results.