.cargo | ||
.github/workflows | ||
schemas | ||
src | ||
xtask | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
LICENSE | ||
README.md | ||
rustfmt.toml |
An LSP implementation for tree-sitter query files
Configuration
Configuration can be done via server initialization or via a configuration file
named .tsqueryrc.json
located in the project workspace directory, or in any of
its ancestor directories. Below is an example file:
{
"$schema": "https://raw.githubusercontent.com/ribru17/ts_query_ls/refs/heads/master/schemas/config.json",
"parser_install_directories": ["${HOME}/my/parser", "/installation/paths"],
"parser_aliases": {
"ecma": "javascript"
},
"language_retrieval_patterns": [
"languages/src/([^/]+)/[^/]+\\.scm$"
],
"valid_captures": {
"highlights": {
"variable": "Simple identifiers",
"variable.parameter": "Parameters of a function"
}
},
"valid_predicates": {
"eq": {
"parameters": [
{
"type": "capture",
"arity": "required"
},
{
"type": "any",
"arity": "required"
}
],
"description": "Checks for equality between two nodes, or a node and a string."
},
}
}
}
Configuration options
parser_install_directories
A list of strings representing directories to search for parsers, of the form
<lang>.(so|dll|dylib)
or tree-sitter-<lang>.wasm
.
Supports environment variable expansion of the form ${VAR}
.
parser_aliases
A map of parser aliases. E.g., to point queries/ecma/*.scm
files to the
javascript
parser:
{
"parser_aliases": {
"ecma": "javascript"
}
}
language_retrieval_patterns
A list of patterns to aid the LSP in finding a language, given a file path.
Patterns must have one capture group which represents the language name. Ordered
from highest to lowest precedence. E.g., for zed
support:
{
"language_retrieval_patterns": [
"languages/src/([^/]+)/[^/]+\\.scm$"
]
}
NOTE: The following fallbacks are always provided:
tree-sitter-([^/]+)/queries/[^/]+\.scm$
queries/([^/]+)/[^/]+\.scm$
valid_captures
A map from query file name to valid captures. Valid captures are represented as
a map from capture name (sans @
) to a short (markdown format) description.
Note that captures prefixed with an underscore are always permissible.
{
"valid_captures": {
"highlights": {
"variable": "Simple identifiers",
"variable.parameter": "Parameters of a function"
}
}
}
valid_predicates
A map of predicate names (sans #
and ?
) to parameter specifications.
Parameters can be one or both of two types (a capture or a string), and can be required, optional, or "varargs" (there can be zero-to-many of them). Optional parameters cannot be followed by required parameters, and a varargs parameter may only appear once, as the last parameter.
{
"valid_predicates": {
"any-of": {
"parameters": [
{
"type": "capture",
"arity": "required"
},
{
"type": "string",
"arity": "required"
},
{
"type": "string",
"arity": "varargs"
}
],
"description": "Checks for equality between multiple strings"
}
}
}
valid_directives
Same as valid_predicates
, but for directives (e.g. #foo!
).
Example setup (for Neovim):
-- Disable the (slow) builtin query linter
vim.g.query_lint_on = {}
vim.api.nvim_create_autocmd('FileType', {
pattern = 'query',
callback = function(ev)
if vim.bo[ev.buf].buftype == 'nofile' then
return
end
vim.lsp.start {
name = 'ts_query_ls',
cmd = { '/path/to/ts_query_ls/target/release/ts_query_ls' },
root_dir = vim.fs.root(0, { '.tsqueryrc.json', 'queries' }),
-- OPTIONAL: Override the query omnifunc
on_attach = function(_, buf)
vim.bo[buf].omnifunc = 'v:lua.vim.lsp.omnifunc'
end,
init_options = {
parser_install_directories = {
-- If using nvim-treesitter with lazy.nvim
vim.fs.joinpath(
vim.fn.stdpath('data'),
'/lazy/nvim-treesitter/parser/'
),
},
parser_aliases = {
ecma = 'javascript',
},
language_retrieval_patterns = {
'languages/src/([^/]+)/[^/]+\\.scm$',
},
},
}
end,
})
Standalone tool
Formatter
The language server can be used as a standalone formatter by passing the
format
argument, e.g. ts_query_ls format ./queries --mode write
. The command
can accept multiple directories to format, and must be passed a "mode" of either
write
or check
. The mode determines whether the files will be overwritten or
just checked for proper formatting.
# use this command for the full documentation
ts_query_ls format --help
Linter
The formatter can also be used as standalone linter by passing the check
argument, e.g:
ts_query_ls check ./queries --config \
'{"parser_install_directories": ["/home/jdoe/Documents/parsers/"]}'
The command can accept a list of directories to search for queries, as well as a
flag to pass JSON configuration to the server (needed to detect parser
locations). If no configuration flag is passed, the command will attempt to read
it from the .tsqueryrc.json
configuration file in the current directory. If no
directories are specified to be checked, then the command will search for all
queries in the current directory.
# use this command for the full documentation
ts_query_ls check --help
Checklist
- References for captures
- Renaming captures
- Completions for capture names in a pattern (for predicates)
- Completions for node names
- Fix utility functions, making them robust when it comes to UTF-16 code points
- Go to definition for captures
- Recognition/completion of supertypes (requires
tree-sitter 0.25
) - Completions and diagnostics for a supertype's subtypes
- Completions field names
- Diagnostics for unrecognized nodes
- Diagnostics for referencing undefined capture groups in predicates
- Diagnostics for incorrect syntax
- Diagnostics for impossible patterns
- Currently not possible without a full (sometimes expensive) run of the query file. This should either be implemented as a user command, or core methods should be exposed to gather pattern information more efficiently
- Recognize parsers built for
WASM
- Document formatting compatible with the
nvim-treesitter
formatter - Code cleanup
- Add tests for all* functionality
*All handlers are tested, but core functionality like language loading will be more complicated, and does not yet have test coverage.
Packaging
And others?
References
Many thanks to @lucario387, and the
asm-lsp,
jinja-lsp
,
beancount
-language-server,
and helix-editor projects for the
amazing code that I took inspiration from!