LSP implementation for Tree-sitter's query files
Find a file
2025-04-20 16:18:26 -07:00
.cargo feat: generate schema for config file 2025-04-19 22:52:00 -07:00
.github/workflows feat: partial support for capture name validation 2025-04-20 15:33:23 -07:00
schemas feat: partial support for capture name validation 2025-04-20 15:33:23 -07:00
src test: add diagnostics tests 2025-04-20 16:18:22 -07:00
xtask feat: generate schema for config file 2025-04-19 22:52:00 -07:00
.gitignore feat: minimal LSP implementation 2024-10-15 09:54:32 -07:00
Cargo.lock chore: bump to 1.11.0 2025-04-20 09:38:15 -07:00
Cargo.toml chore: bump to 1.11.0 2025-04-20 09:38:15 -07:00
LICENSE feat: free license 2024-10-18 14:53:49 -07:00
README.md feat: partial support for capture name validation 2025-04-20 15:33:23 -07:00
rustfmt.toml chore: add rustfmt.toml 2025-04-20 16:18:26 -07:00

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 at the project workspace root. Below is an example file:

{
  "parser_install_directories": ["a/list/of", "parser/installation/paths"],
  "parser_aliases": {
    "ecma": "javascript"
  },
  "language_retrieval_patterns": [
    "languages/src/([^/]+)/[^/]+\\.scm$"
  ],
  "allowable_captures": {
    "highlights": [
      {
        "name": "variable",
        "description": "Simple identifiers"
      },
      {
        "name": "variable.parameter",
        "description": "Parameters of a function"
      }
    ]
  }
}

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.

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$

allowable_captures

A map from query file names to allowable captures. Captures are represented as an object containing their name (sans @) and an optional description. Note that captures prefixed with an underscore are always permissible. Example:

{
  "allowable_captures": {
    "highlights": [
      {
        "name": "variable",
        "description": "Simple identifiers"
      },
      {
        "name": "variable.parameter",
        "description": "Parameters of a function"
      }
    ]
  }
}

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!