Queries are patterns that match tree-sitter nodes to enable features like highlighting, folding, and indentation. You can customize existing queries or create new ones.
Query Files Overview
nvim-treesitter uses several types of query files:
highlights.scm : Syntax highlighting
injections.scm : Multi-language injection (e.g., JS in HTML)
folds.scm : Code folding regions
indents.scm : Indentation rules
locals.scm : Definitions and references (backward compatibility)
For a language to support a feature, both the parser and the corresponding query file must exist.
Where to Place Custom Queries
Custom queries go in your Neovim config under queries/<language>/:
~/.config/nvim/
└── queries/
├── lua/
│ ├── highlights.scm
│ └── indents.scm
├── python/
│ └── highlights.scm
└── rust/
└── folds.scm
Queries in your config directory replace default queries unless you use the ; extends modeline.
Extending vs Replacing Queries
Extending Default Queries
To add to existing queries without replacing them:
~/.config/nvim/queries/lua/highlights.scm
; extends
; Add custom highlight for a specific pattern
(function_call
name: (identifier) @custom.function
(#eq? @custom.function "mySpecialFunction"))
The ; extends modeline tells Neovim to merge your queries with the defaults.
Replacing Default Queries
To completely replace default queries, omit ; extends:
~/.config/nvim/queries/python/highlights.scm
; This file replaces the default highlights
(function_definition
name: (identifier) @function)
(string) @string
Most of the time you want to extend , not replace.
Writing Highlight Queries
Highlight queries assign tree-sitter nodes to capture groups that map to highlight groups.
Basic Syntax
; Match a specific node type
(function_definition) @function
; Match specific text
"return" @keyword.return
; Match multiple alternatives
[
"if"
"else"
"elif"
] @keyword.conditional
Using Fields
; Capture specific fields of a node
(function_definition
name: (identifier) @function.name
parameters: (parameters) @function.parameters)
Common Capture Groups
Here are the most commonly used captures:
Keywords
Literals
Functions & Variables
Types & Comments
@keyword ; General keywords
@keyword.function ; def, function
@keyword.return ; return, yield
@keyword.conditional ; if, else, elif
@keyword.repeat ; for, while
@keyword.import ; import, require
Using Predicates
Predicates filter matches based on conditions:
; Match exact text
(identifier) @variable.builtin
(#eq? @variable.builtin "self")
; Match one of several values
(identifier) @constant.builtin
(#any-of? @constant.builtin "None" "True" "False")
; Match a Lua pattern
(identifier) @constant
(#lua-match? @constant "^[A-Z_]+$")
; Match a Vim regex
(identifier) @constant
(#match? @constant "^[A-Z_]+$")
For performance, prefer #eq? over #any-of? over #lua-match? over #match?.
Setting Priority
Control highlight precedence with priority directives:
; Default priority is 100
(comment) @comment
; Higher priority takes precedence
(comment
content: (tag) @comment.todo
(#eq? @comment.todo "TODO"))
(#set! priority 110)
Only set priorities between 90 and 120 to avoid conflicts with other highlight sources.
Writing Fold Queries
Fold queries mark regions that can be folded:
~/.config/nvim/queries/lua/folds.scm
; extends
; Fold function bodies
(function_definition) @fold
; Fold table constructors
(table_constructor) @fold
; Fold multi-line comments
(comment) @fold
(#lua-match? @fold "^%-%-%-")
Good Fold Candidates
Function/method definitions
Class/struct definitions
Blocks (if, while, for)
Array/object literals
Multi-line comments
Import statement groups
Bad Fold Candidates
Single-line expressions
Variable assignments
Chain expressions (e.g., obj.prop.prop)
Writing Indent Queries
Indentation queries are experimental and may change.
Indent queries control automatic indentation:
~/.config/nvim/queries/python/indents.scm
; extends
; Indent inside function bodies
(function_definition
body: (block) @indent.begin)
; Dedent at closing keywords
["end" "else" "elif"] @indent.branch
; Maintain indentation for comments
(comment) @indent.auto
Indent Captures
Begin & End
Control
Alignment
@indent.begin ; Start indented region
@indent.end ; End indented region (next line dedents)
@indent.branch ; Dedent at this node (e.g., else, elif)
Example: Function Indentation
; Indent function bodies
(function_definition) @indent.begin
; Dedent at "end" keyword
"end" @indent.end
; Align function arguments
(parameters) @indent.align
(#set! indent.open_delimiter "(")
(#set! indent.close_delimiter ")")
Writing Injection Queries
Injection queries enable parsing of embedded languages:
~/.config/nvim/queries/html/injections.scm
; extends
; JavaScript in <script> tags
(script_element
(raw_text) @injection.content
(#set! injection.language "javascript"))
; CSS in <style> tags
(style_element
(raw_text) @injection.content
(#set! injection.language "css"))
Dynamic Language Detection
; Detect language from attribute
(script_element
(start_tag
(attribute
(attribute_name) @_attr
(quoted_attribute_value (attribute_value) @injection.language)
(#eq? @_attr "type")))
(raw_text) @injection.content)
This allows <script type="module"> to be highlighted as JavaScript.
Filename-based Injection
; Inject based on filepath
(import_statement
path: (string_content) @injection.filename
content: (_) @injection.content)
Testing Your Queries
Use :InspectTree
Open a file and run :InspectTree to see the parse tree. Hover over nodes to see corresponding text.
Use :EditQuery
Run :EditQuery to open an interactive query editor. Write patterns and see matches in real-time.
Use :Inspect
Position cursor and run :Inspect to see what highlight groups are applied at that location.
tree-sitter CLI
# Parse a file and show the tree
tree-sitter parse file.lua
# Test a query
tree-sitter query queries/lua/highlights.scm file.lua
ts_query_ls
ts_query_ls is a language server for query files:
Validation
Autocompletion
Formatting
Error diagnostics
Install and configure it in your Neovim LSP config.
Advanced Techniques
Inheriting from Other Languages
TypeScript extends JavaScript:
~/.config/nvim/queries/typescript/highlights.scm
; inherits: javascript
; Add TypeScript-specific patterns
(type_annotation) @type
(interface_declaration) @type.definition
Optional Inheritance
Use parentheses for optional inheritance:
; inherits: javascript,(jsx)
This inherits JavaScript, and optionally JSX if available.
; format-ignore
(complex_pattern
(nested
(very_long_pattern) @capture
(#some-predicate? @capture)))
The ; format-ignore directive prevents auto-formatting of the following pattern.
Concealing Text
; Conceal lambda keyword
"lambda" @conceal
(#set! conceal "λ")
; Conceal part of a capture
(string
"\"" @string.delimiter
(#offset! @string.delimiter 0 1 0 0)
(#set! conceal ""))
Spell Checking
; Enable spell checking in comments
(comment) @spell
; Disable in code strings
(string) @nospell
Real-World Examples
Example 1: Custom Python Highlights
~/.config/nvim/queries/python/highlights.scm
; extends
; Highlight self parameter specially
(parameters
(identifier) @variable.builtin
(#eq? @variable.builtin "self"))
; Highlight type hints
(type_annotation
(type) @type)
; Highlight decorators
(decorator) @attribute
; Highlight f-string expressions
(interpolation) @string.escape
Example 2: Lua Function Folds
~/.config/nvim/queries/lua/folds.scm
; extends
; Fold function definitions
[
(function_definition)
(function_declaration)
] @fold
; Fold table constructors with multiple lines
(table_constructor) @fold
; Fold do...end blocks
(do_statement) @fold
; Fold if statements
(if_statement) @fold
Example 3: Markdown Injections
~/.config/nvim/queries/markdown/injections.scm
; extends
; Inject language based on fence info string
(fenced_code_block
(info_string
(language) @injection.language)
(code_fence_content) @injection.content)
; Handle inline code as plain text
(inline
(code_span) @none)
Troubleshooting
Query Not Loading
Check file location: ~/.config/nvim/queries/<language>/
Verify filename: highlights.scm, folds.scm, etc.
Check for syntax errors: :messages
Ensure parser is installed: :TSInstallInfo
Invalid Pattern Errors
Invalid pattern at line 5
Use :InspectTree to see valid node names
Check field names in the parser grammar
Test with :EditQuery
Highlights Not Applying
Check priority with :Inspect
Verify capture group names are valid
Test predicates match correctly
Ensure colorscheme defines the highlight group
Complex queries with many predicates can slow down highlighting.
Use #eq? instead of #match? when possible
Limit the scope of patterns
Avoid overlapping captures
Profile with :profile start profile.log and :profile func *treesitter*
Query Syntax Reference
For complete query syntax documentation, see:
Next Steps