mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-08 14:30:05 -04:00
feat!: track parser revision in Lua
Problem: Tracking parser revision in lockfile and allowing override through the parsers module complicates the code. In addition, only revision changes are handled robustly, not changes to other installation info. Solution: Track parser revision in the parsers module directly. Reload parser table on every install or update call. Support modifying parser table in a `User TSUpdate` autocommand.
This commit is contained in:
parent
054080bf59
commit
c17de56890
21 changed files with 1007 additions and 995 deletions
47
lua/nvim-treesitter/_meta/parsers.lua
Normal file
47
lua/nvim-treesitter/_meta/parsers.lua
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---@meta
|
||||
error('Cannot require a meta file')
|
||||
|
||||
---@class InstallInfo
|
||||
---
|
||||
---URL of parser repo (Github/Gitlab)
|
||||
---@field url string
|
||||
---
|
||||
---Commit hash of parser to download (compatible with queries)
|
||||
---@field revision string
|
||||
---
|
||||
---Files to include when compiling (`src/parser.c` and optionally `src/scanner.c')
|
||||
---@field files string[]
|
||||
---
|
||||
---Branch of parser repo to download (if not default branch)
|
||||
---@field branch? string
|
||||
---
|
||||
---Location of `grammar.js` in repo (if not at root, e.g., in a monorepo)
|
||||
---@field location? string
|
||||
---
|
||||
---Repo does not contain a `parser.c`; must be generated from grammar first
|
||||
---@field generate? boolean
|
||||
---
|
||||
---Parser needs to be generated from `grammar.json` (generating from `grammar.js` requires npm)
|
||||
---@field generate_from_json? boolean
|
||||
---
|
||||
---Parser repo is a local directory; overrides `url`, `revision`, and `branch`
|
||||
---@field path? string
|
||||
|
||||
---@class ParserInfo
|
||||
---
|
||||
---Information necessary to build and install the parser (empty for query-only language)
|
||||
---@field install_info? InstallInfo
|
||||
---
|
||||
---List of Github users maintaining the queries for Neovim
|
||||
---@field maintainers? string[]
|
||||
---
|
||||
---List of other languages to install (e.g., if queries inherit from them)
|
||||
---@field requires? string[]
|
||||
---
|
||||
---Language support tier, maps to "core", "stable", "community", "unmaintained"
|
||||
---@field tier integer
|
||||
---
|
||||
---Explanatory footnote text to add in SUPPORTED_LANGUAGES.md
|
||||
---@field readme_note? string
|
||||
|
||||
---@alias nvim-ts.parsers table<string,ParserInfo>
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
local M = {}
|
||||
|
||||
M.tiers = { 'stable', 'core', 'community', 'unsupported' }
|
||||
|
||||
---@class TSConfig
|
||||
---@field auto_install boolean
|
||||
---@field ensure_install string[]
|
||||
|
|
@ -33,7 +35,7 @@ function M.setup(user_data)
|
|||
local ft = vim.bo[buf].filetype
|
||||
local lang = vim.treesitter.language.get_lang(ft) or ft
|
||||
if
|
||||
require('nvim-treesitter.parsers').configs[lang]
|
||||
require('nvim-treesitter.parsers')[lang]
|
||||
and not vim.list_contains(M.installed_parsers(), lang)
|
||||
and not vim.list_contains(config.ignore_install, lang)
|
||||
then
|
||||
|
|
@ -51,7 +53,7 @@ function M.setup(user_data)
|
|||
local to_install = M.norm_languages(config.ensure_install, { ignored = true, installed = true })
|
||||
|
||||
if #to_install > 0 then
|
||||
require('nvim-treesitter.install').install(to_install)
|
||||
require('nvim-treesitter.install').install(to_install, { force = true })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -85,6 +87,52 @@ function M.installed_parsers()
|
|||
return installed
|
||||
end
|
||||
|
||||
-- Get a list of all available parsers
|
||||
---@param tier integer? only get parsers of specified tier
|
||||
---@return string[]
|
||||
function M.get_available(tier)
|
||||
local parsers = require('nvim-treesitter.parsers')
|
||||
--- @type string[]
|
||||
local languages = vim.tbl_keys(parsers)
|
||||
table.sort(languages)
|
||||
if tier then
|
||||
languages = vim.tbl_filter(
|
||||
--- @param p string
|
||||
function(p)
|
||||
return parsers[p].tier == tier
|
||||
end,
|
||||
languages
|
||||
)
|
||||
end
|
||||
if vim.fn.executable('tree-sitter') == 0 then
|
||||
languages = vim.tbl_filter(
|
||||
--- @param p string
|
||||
function(p)
|
||||
return parsers[p].install_info ~= nil and not parsers[p].install_info.generate
|
||||
end,
|
||||
languages
|
||||
)
|
||||
end
|
||||
return languages
|
||||
end
|
||||
|
||||
local function expand_tiers(list)
|
||||
for i, tier in ipairs(M.tiers) do
|
||||
if vim.list_contains(list, tier) then
|
||||
list = vim.tbl_filter(
|
||||
--- @param l string
|
||||
function(l)
|
||||
return l ~= tier
|
||||
end,
|
||||
list
|
||||
)
|
||||
vim.list_extend(list, M.get_available(i))
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
---Normalize languages
|
||||
---@param languages? string[]|string
|
||||
---@param skip? { ignored: boolean, missing: boolean, installed: boolean, dependencies: boolean }
|
||||
|
|
@ -96,32 +144,11 @@ function M.norm_languages(languages, skip)
|
|||
languages = { languages }
|
||||
end
|
||||
|
||||
local parsers = require('nvim-treesitter.parsers')
|
||||
|
||||
if vim.list_contains(languages, 'all') then
|
||||
if skip and skip.missing then
|
||||
return M.installed_parsers()
|
||||
end
|
||||
languages = parsers.get_available()
|
||||
end
|
||||
|
||||
-- keep local to avoid leaking parsers module
|
||||
--- @param list string[]
|
||||
local function expand_tiers(list)
|
||||
for i, tier in ipairs(parsers.tiers) do
|
||||
if vim.list_contains(list, tier) then
|
||||
list = vim.tbl_filter(
|
||||
--- @param l string
|
||||
function(l)
|
||||
return l ~= tier
|
||||
end,
|
||||
list
|
||||
)
|
||||
vim.list_extend(list, parsers.get_available(i))
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
languages = M.get_available()
|
||||
end
|
||||
|
||||
languages = expand_tiers(languages)
|
||||
|
|
@ -159,19 +186,20 @@ function M.norm_languages(languages, skip)
|
|||
)
|
||||
end
|
||||
|
||||
local parsers = require('nvim-treesitter.parsers')
|
||||
languages = vim.tbl_filter(
|
||||
--- @param v string
|
||||
function(v)
|
||||
-- TODO(lewis6991): warn of any unknown parsers?
|
||||
return parsers.configs[v] ~= nil
|
||||
return parsers[v] ~= nil
|
||||
end,
|
||||
languages
|
||||
)
|
||||
|
||||
if not (skip and skip.dependencies) then
|
||||
for _, lang in pairs(languages) do
|
||||
if parsers.configs[lang].requires then
|
||||
vim.list_extend(languages, parsers.configs[lang].requires)
|
||||
if parsers[lang].requires then
|
||||
vim.list_extend(languages, parsers[lang].requires)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ function M.check()
|
|||
health.start('Installed languages' .. string.rep(' ', 5) .. 'H L F I J')
|
||||
local languages = config.installed_parsers()
|
||||
for _, lang in pairs(languages) do
|
||||
local parser = parsers.configs[lang]
|
||||
local parser = parsers[lang]
|
||||
local out = lang .. string.rep(' ', 22 - #lang)
|
||||
if parser.install_info then
|
||||
for _, query_group in pairs(M.bundled_queries) do
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@ local uv_unlink = a.wrap(uv.fs_unlink, 2)
|
|||
|
||||
local M = {}
|
||||
|
||||
---@class LockfileInfo
|
||||
---@field revision string
|
||||
|
||||
---@type table<string, LockfileInfo>
|
||||
local lockfile = {}
|
||||
|
||||
local max_jobs = 10
|
||||
|
||||
local iswin = uv.os_uname().sysname == 'Windows_NT'
|
||||
|
|
@ -63,7 +57,7 @@ end
|
|||
---@param lang string
|
||||
---@return InstallInfo?
|
||||
local function get_parser_install_info(lang)
|
||||
local parser_config = parsers.configs[lang]
|
||||
local parser_config = parsers[lang]
|
||||
|
||||
if not parser_config then
|
||||
log.error('Parser not available for language "' .. lang .. '"')
|
||||
|
|
@ -78,24 +72,6 @@ function M.get_package_path(...)
|
|||
return fs.joinpath(fn.fnamemodify(debug.getinfo(1, 'S').source:sub(2), ':p:h:h:h'), ...)
|
||||
end
|
||||
|
||||
---@param lang string
|
||||
---@return string?
|
||||
local function get_target_revision(lang)
|
||||
local info = get_parser_install_info(lang)
|
||||
if info and info.revision then
|
||||
return info.revision
|
||||
end
|
||||
|
||||
if #lockfile == 0 then
|
||||
local filename = M.get_package_path('lockfile.json')
|
||||
lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string, LockfileInfo>]]
|
||||
end
|
||||
|
||||
if lockfile[lang] then
|
||||
return lockfile[lang].revision
|
||||
end
|
||||
end
|
||||
|
||||
---@param lang string
|
||||
---@return string?
|
||||
local function get_installed_revision(lang)
|
||||
|
|
@ -106,9 +82,9 @@ end
|
|||
---@param lang string
|
||||
---@return boolean
|
||||
local function needs_update(lang)
|
||||
local revision = get_target_revision(lang)
|
||||
if revision then
|
||||
return revision ~= get_installed_revision(lang)
|
||||
local info = get_parser_install_info(lang)
|
||||
if info and info.revision then
|
||||
return info.revision ~= get_installed_revision(lang)
|
||||
end
|
||||
|
||||
-- No revision. Check the queries link to the same place
|
||||
|
|
@ -415,7 +391,7 @@ local function install_lang0(lang, cache_dir, install_dir, generate)
|
|||
|
||||
local project_name = 'tree-sitter-' .. lang
|
||||
|
||||
local revision = get_target_revision(lang)
|
||||
local revision = repo.revision
|
||||
|
||||
local compile_location ---@type string
|
||||
if repo.path then
|
||||
|
|
@ -525,40 +501,33 @@ local function install_lang(lang, cache_dir, install_dir, force, generate)
|
|||
return status
|
||||
end
|
||||
|
||||
--- Reload the parser table and user modifications in case of update
|
||||
local function reload_parsers()
|
||||
package.loaded['nvim-treesitter.parsers'] = nil
|
||||
parsers = require('nvim-treesitter.parsers')
|
||||
vim.api.nvim_exec_autocmds('User', { pattern = 'TSUpdate' })
|
||||
end
|
||||
|
||||
---@class InstallOptions
|
||||
---@field force? boolean
|
||||
---@field generate? boolean
|
||||
---@field skip? table
|
||||
|
||||
--- Install a parser
|
||||
--- @param languages? string[]|string
|
||||
--- @param languages string[]
|
||||
--- @param options? InstallOptions
|
||||
--- @param _callback? fun()
|
||||
local function install(languages, options, _callback)
|
||||
options = options or {}
|
||||
local force = options.force
|
||||
local generate = options.generate
|
||||
local skip = options.skip
|
||||
|
||||
local cache_dir = vim.fs.normalize(fn.stdpath('cache'))
|
||||
local install_dir = config.get_install_dir('parser')
|
||||
|
||||
if not languages or type(languages) == 'string' then
|
||||
languages = { languages }
|
||||
end
|
||||
|
||||
if languages[1] == 'all' then
|
||||
force = true
|
||||
end
|
||||
|
||||
languages = config.norm_languages(languages, skip)
|
||||
|
||||
local tasks = {} --- @type fun()[]
|
||||
local done = 0
|
||||
for _, lang in ipairs(languages) do
|
||||
tasks[#tasks + 1] = a.sync(function()
|
||||
a.main()
|
||||
local status = install_lang(lang, cache_dir, install_dir, force, generate)
|
||||
local status = install_lang(lang, cache_dir, install_dir, options.force, options.generate)
|
||||
if status ~= 'failed' then
|
||||
done = done + 1
|
||||
end
|
||||
|
|
@ -572,7 +541,20 @@ local function install(languages, options, _callback)
|
|||
end
|
||||
end
|
||||
|
||||
M.install = a.sync(install, 2)
|
||||
M.install = a.sync(function(languages, options, _callback)
|
||||
reload_parsers()
|
||||
if not languages or #languages == 0 then
|
||||
languages = 'all'
|
||||
end
|
||||
|
||||
languages = config.norm_languages(languages, options and options.skip)
|
||||
|
||||
if languages[1] == 'all' then
|
||||
options.force = true
|
||||
end
|
||||
|
||||
install(languages, options)
|
||||
end, 2)
|
||||
|
||||
---@class UpdateOptions
|
||||
|
||||
|
|
@ -580,8 +562,7 @@ M.install = a.sync(install, 2)
|
|||
---@param _options? UpdateOptions
|
||||
---@param _callback function
|
||||
M.update = a.sync(function(languages, _options, _callback)
|
||||
M.lockfile = {}
|
||||
|
||||
reload_parsers()
|
||||
if not languages or #languages == 0 then
|
||||
languages = 'all'
|
||||
end
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue