feat(install): support custom queries

This commit is contained in:
Christian Clason 2025-05-29 18:23:42 +02:00 committed by Christian Clason
parent fb9b2cfdc3
commit 057e845518
4 changed files with 107 additions and 45 deletions

View file

@ -126,7 +126,7 @@ These queries can be used to look up definitions and references to identifiers i
# Advanced setup # Advanced setup
## Adding parsers ## Adding custom languages
If you have a parser that is not on the list of supported languages (either as a repository on Github or in a local directory), you can add it manually for use by `nvim-treesitter` as follows: If you have a parser that is not on the list of supported languages (either as a repository on Github or in a local directory), you can add it manually for use by `nvim-treesitter` as follows:
@ -144,6 +144,7 @@ callback = function()
location = 'parser', -- only needed if the parser is in subdirectory of a "monorepo" location = 'parser', -- only needed if the parser is in subdirectory of a "monorepo"
generate = true, -- only needed if repo does not contain pre-generated `src/parser.c` generate = true, -- only needed if repo does not contain pre-generated `src/parser.c`
generate_from_json = false, -- only needed if repo does not contain `src/grammar.json` either generate_from_json = false, -- only needed if repo does not contain `src/grammar.json` either
queries = 'queries/neovim', -- also install queries from given directory
}, },
} }
end}) end})
@ -158,6 +159,7 @@ Alternatively, if you have a local checkout, you can instead use
location = 'parser', location = 'parser',
generate = true, generate = true,
generate_from_json = false, generate_from_json = false,
queries = 'queries/neovim', -- symlink queries from given directory
}, },
``` ```
This will always use the state of the directory as-is (i.e., `branch` and `revision` will be ignored). This will always use the state of the directory as-is (i.e., `branch` and `revision` will be ignored).
@ -189,9 +191,3 @@ end})
## Adding queries ## Adding queries
Queries can be placed anywhere in your `runtimepath` under `queries/<language>`, with earlier directories taking precedence unless the queries are marked with `; extends`; see [`:h treesitter-query-modelines`](https://neovim.io/doc/user/treesitter.html#treesitter-query-modeline). Queries can be placed anywhere in your `runtimepath` under `queries/<language>`, with earlier directories taking precedence unless the queries are marked with `; extends`; see [`:h treesitter-query-modelines`](https://neovim.io/doc/user/treesitter.html#treesitter-query-modeline).
E.g., to add queries for `zimbu`, put `highlights.scm` etc. under
```lua
vim.fn.stdpath('data') .. 'site/queries/zimbu'
```

View file

@ -23,6 +23,9 @@ error('Cannot require a meta file')
--- ---
---Parser repo is a local directory; overrides `url`, `revision`, and `branch` ---Parser repo is a local directory; overrides `url`, `revision`, and `branch`
---@field path? string ---@field path? string
---
---Directory with queries to be installed
---@field queries? string
---@class ParserInfo ---@class ParserInfo
--- ---

View file

@ -14,6 +14,9 @@ local uv_copyfile = a.awrap(4, uv.fs_copyfile)
---@type fun(path: string, mode: integer): string? ---@type fun(path: string, mode: integer): string?
local uv_mkdir = a.awrap(3, uv.fs_mkdir) local uv_mkdir = a.awrap(3, uv.fs_mkdir)
---@type fun(path: string): string?
local uv_rmdir = a.awrap(2, uv.fs_rmdir)
---@type fun(path: string, new_path: string): string? ---@type fun(path: string, new_path: string): string?
local uv_rename = a.awrap(3, uv.fs_rename) local uv_rename = a.awrap(3, uv.fs_rename)
@ -23,6 +26,36 @@ local uv_symlink = a.awrap(4, uv.fs_symlink)
---@type fun(path: string): string? ---@type fun(path: string): string?
local uv_unlink = a.awrap(2, uv.fs_unlink) local uv_unlink = a.awrap(2, uv.fs_unlink)
---@async
---@param path string
---@return string? err
local function mkpath(path)
local parent = fs.dirname(path)
if not parent:match('^[./]$') and not uv.fs_stat(parent) then
mkpath(parent)
end
return uv_mkdir(path, 493) -- tonumber('755', 8)
end
---@async
---@param path string
local function rmdir(path)
local stat = uv.fs_lstat(path)
if not stat then
return
end
if stat.type == 'directory' then
for file in fs.dir(path) do
rmdir(fs.joinpath(path, file))
end
return uv_rmdir(path)
else
return uv_unlink(path)
end
end
local MAX_JOBS = 100 local MAX_JOBS = 100
local INSTALL_TIMEOUT = 60000 local INSTALL_TIMEOUT = 60000
@ -95,18 +128,6 @@ local function download_file(url, output)
end end
end end
---@async
---@param path string
---@return string? err
local function mkpath(path)
local parent = fs.dirname(path)
if not parent:match('^[./]$') and not uv.fs_stat(parent) then
mkpath(parent)
end
return uv_mkdir(path, 493) -- tonumber('755', 8)
end
local M = {} local M = {}
--- ---
@ -199,7 +220,8 @@ local function do_download(logger, url, project_name, cache_dir, revision, outpu
local tmp = output_dir .. '-tmp' local tmp = output_dir .. '-tmp'
util.delete(tmp) rmdir(tmp)
a.schedule()
url = url:gsub('.git$', '') url = url:gsub('.git$', '')
local target = is_gitlab local target = is_gitlab
@ -258,7 +280,8 @@ local function do_download(logger, url, project_name, cache_dir, revision, outpu
end end
end end
util.delete(tmp) rmdir(tmp)
a.schedule()
end end
---@async ---@async
@ -300,6 +323,38 @@ local function do_install(logger, compile_location, target_location)
end end
end end
---@async
---@param logger Logger
---@param query_src string
---@param query_dir string
---@return string? err
local function do_link_queries(logger, query_src, query_dir)
uv_unlink(query_dir)
local err = uv_symlink(query_src, query_dir, { dir = true, junction = true })
a.schedule()
if err then
return logger:error(err)
end
end
---@async
---@param logger Logger
---@param query_src string
---@param query_dir string
---@return string? err
local function do_copy_queries(logger, query_src, query_dir)
rmdir(query_dir)
local err = uv_mkdir(query_dir, 493) -- tonumber('755', 8)
for f in fs.dir(query_src) do
err = uv_copyfile(fs.joinpath(query_src, f), fs.joinpath(query_dir, f))
end
a.schedule()
if err then
return logger:error(err)
end
end
---@async ---@async
---@param lang string ---@param lang string
---@param cache_dir string ---@param cache_dir string
@ -310,9 +365,8 @@ local function try_install_lang(lang, cache_dir, install_dir, generate)
local logger = log.new('install/' .. lang) local logger = log.new('install/' .. lang)
local repo = get_parser_install_info(lang) local repo = get_parser_install_info(lang)
local project_name = 'tree-sitter-' .. lang
if repo then if repo then
local project_name = 'tree-sitter-' .. lang
local revision = repo.revision local revision = repo.revision
local compile_location ---@type string local compile_location ---@type string
@ -320,7 +374,7 @@ local function try_install_lang(lang, cache_dir, install_dir, generate)
compile_location = fs.normalize(repo.path) compile_location = fs.normalize(repo.path)
else else
local project_dir = fs.joinpath(cache_dir, project_name) local project_dir = fs.joinpath(cache_dir, project_name)
util.delete(project_dir) rmdir(project_dir)
revision = revision or repo.branch or 'main' revision = revision or repo.branch or 'main'
@ -362,26 +416,37 @@ local function try_install_lang(lang, cache_dir, install_dir, generate)
local revfile = fs.joinpath(config.get_install_dir('parser-info') or '', lang .. '.revision') local revfile = fs.joinpath(config.get_install_dir('parser-info') or '', lang .. '.revision')
util.write_file(revfile, revision or '') util.write_file(revfile, revision or '')
end end
if not repo.path then
util.delete(fs.joinpath(cache_dir, project_name))
end
end end
do -- install queries do -- install queries
local queries_src = M.get_package_path('runtime', 'queries', lang) local query_src = M.get_package_path('runtime', 'queries', lang)
if uv.fs_stat(queries_src) then local query_dir = fs.joinpath(config.get_install_dir('queries'), lang)
local queries = fs.joinpath(config.get_install_dir('queries'), lang) local task ---@type function
uv_unlink(queries) if repo and repo.queries and repo.path then -- link queries from local repo
local err = uv_symlink(queries_src, queries, { dir = true, junction = true }) query_src = fs.joinpath(fs.normalize(repo.path), repo.queries)
a.schedule() task = do_link_queries
elseif repo and repo.queries then -- copy queries from tarball
query_src = fs.joinpath(cache_dir, project_name, repo.queries)
task = do_copy_queries
elseif uv.fs_stat(query_src) then -- link queries from runtime
task = do_link_queries
end
if task then
local err = task(logger, query_src, query_dir)
if err then if err then
return logger:error(err) return err
end end
end end
end end
-- clean up
if repo and not repo.path then
rmdir(fs.joinpath(cache_dir, project_name))
a.schedule()
end
logger:info('Language installed') logger:info('Language installed')
end end
@ -520,17 +585,21 @@ local function uninstall_lang(logger, lang, parser, queries)
logger:debug('Unlinking ' .. parser) logger:debug('Unlinking ' .. parser)
local perr = uv_unlink(parser) local perr = uv_unlink(parser)
a.schedule() a.schedule()
if perr then if perr then
return logger:error(perr) return logger:error(perr)
end end
end end
if fn.isdirectory(queries) == 1 then local stat = uv.fs_lstat(queries)
if stat then
logger:debug('Unlinking ' .. queries) logger:debug('Unlinking ' .. queries)
local qerr = uv_unlink(queries) local qerr ---@type string?
if stat.type == 'link' then
qerr = uv_unlink(queries)
else
qerr = rmdir(queries)
end
a.schedule() a.schedule()
if qerr then if qerr then
return logger:error(qerr) return logger:error(qerr)
end end

View file

@ -17,10 +17,4 @@ function M.write_file(filename, content)
file:close() file:close()
end end
--- Recursively delete a directory
--- @param name string
function M.delete(name)
vim.fs.rm(name, { recursive = true, force = true })
end
return M return M