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:
Christian Clason 2024-04-14 16:25:28 +02:00
parent 054080bf59
commit c17de56890
21 changed files with 1007 additions and 995 deletions

View file

@ -2,7 +2,7 @@
vim.opt.runtimepath:append('.')
local query_types = require('nvim-treesitter.health').bundled_queries
local configs = require('nvim-treesitter.parsers').configs
local configs = require('nvim-treesitter.parsers')
local parsers = #_G.arg > 0 and { unpack(_G.arg) }
or require('nvim-treesitter.config').installed_parsers()

21
scripts/convert-lockfile.lua Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local filename = require('nvim-treesitter.install').get_package_path('lockfile.json')
local lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string,{revision:string}>]]
for k, p in pairs(parsers) do
if p.install_info then
p.install_info.revision = lockfile[k].revision
end
end
-- write new parser file
local header = '---@type nvim-ts.parsers\nreturn '
local parser_file = header .. vim.inspect(parsers)
if vim.fn.executable('stylua') == 1 then
parser_file = vim.system({ 'stylua', '-' }, { stdin = parser_file }):wait().stdout --[[@as string]]
end
util.write_file('lua/nvim-treesitter/parsers.lua', parser_file)

View file

@ -1,64 +0,0 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers').configs
-- Load previous lockfile
local filename = require('nvim-treesitter.install').get_package_path('lockfile.json')
local old_lockfile = vim.json.decode(util.read_file(filename)) --[[@as table<string,{revision:string}>]]
local jobs = {} ---@type table<string,vim.SystemObj>
local new_lockfile = {} ---@type table<string,{revision:string}>
local updates = {} ---@type string[]
-- check for new revisions
for k, p in pairs(parsers) do
if p.tier == 4 then
new_lockfile[k] = old_lockfile[k]
print('Skipping ' .. k)
elseif p.install_info then
print('Updating ' .. k)
jobs[k] = vim.system({ 'git', 'ls-remote', p.install_info.url })
end
if #vim.tbl_keys(jobs) % 100 == 0 or next(parsers, k) == nil then
for name, job in pairs(jobs) do
local stdout = vim.split(job:wait().stdout, '\n')
jobs[name] = nil
local branch = parsers[name].install_info.branch
local line = 1
if branch then
for j, l in ipairs(stdout) do
if l:find(vim.pesc(branch)) then
line = j
break
end
end
end
local sha = vim.split(stdout[line], '\t')[1]
new_lockfile[name] = { revision = sha }
if new_lockfile[name].revision ~= old_lockfile[name].revision then
updates[#updates + 1] = name
end
end
end
end
assert(#vim.tbl_keys(jobs) == 0)
if #updates > 0 then
-- write new lockfile
local lockfile_json = vim.json.encode(new_lockfile) --[[@as string]]
if vim.fn.executable('jq') == 1 then
lockfile_json =
assert(vim.system({ 'jq', '--sort-keys' }, { stdin = lockfile_json }):wait().stdout)
end
util.write_file(filename, lockfile_json)
print(string.format('\nUpdated parsers: %s', table.concat(updates, ', ')))
else
print('\nAll parsers up to date!')
end

65
scripts/update-parsers.lua Executable file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env -S nvim -l
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local jobs = {} ---@type table<string,vim.SystemObj>
local updates = {} ---@type string[]
-- check for new revisions
for k, p in pairs(parsers) do
if p.tier < 5 and p.install_info then
print('Updating ' .. k)
jobs[k] = vim.system({ 'git', 'ls-remote', p.install_info.url })
end
if #vim.tbl_keys(jobs) % 100 == 0 or next(parsers, k) == nil then
for name, job in pairs(jobs) do
local stdout = vim.split(job:wait().stdout, '\n')
jobs[name] = nil
local info = parsers[name].install_info
assert(info)
local branch = info.branch
local line = 1
if branch then
for j, l in ipairs(stdout) do
if l:find(vim.pesc(branch)) then
line = j
break
end
end
end
local sha = vim.split(stdout[line], '\t')[1]
if info.revision ~= sha then
info.revision = sha
updates[#updates + 1] = name
end
end
end
end
assert(#vim.tbl_keys(jobs) == 0)
if #updates > 0 then
-- write new parser file
local header = '---@type nvim-ts.parsers\nreturn '
local parser_file = header .. vim.inspect(parsers)
if vim.fn.executable('stylua') == 1 then
parser_file = vim.system({ 'stylua', '-' }, { stdin = parser_file }):wait().stdout --[[@as string]]
end
util.write_file('lua/nvim-treesitter/parsers.lua', parser_file)
local update_list = table.concat(updates, ', ')
print(string.format('\nUpdated parsers: %s', update_list))
-- pass list to workflow
if os.getenv('GITHUB_ENV') then
local env = io.open(os.getenv('GITHUB_ENV'), 'a')
env:write(string.format('UPDATED_PARSERS=%s\n', update_list))
env:close()
end
else
print('\nAll parsers up to date!')
end

View file

@ -2,12 +2,13 @@
vim.opt.runtimepath:append('.')
local util = require('nvim-treesitter.util')
local parsers = require('nvim-treesitter.parsers')
local tiers = require('nvim-treesitter.config').tiers
---@class Parser
---@field name string
---@field parser ParserInfo
local sorted_parsers = {}
for k, v in pairs(parsers.configs) do
for k, v in pairs(parsers) do
table.insert(sorted_parsers, { name = k, parser = v })
end
table.sort(sorted_parsers, function(a, b)
@ -45,7 +46,7 @@ for _, v in ipairs(sorted_parsers) do
end
-- tier
generated_text = generated_text .. (p.tier and parsers.tiers[p.tier] or '') .. ' | '
generated_text = generated_text .. (p.tier and tiers[p.tier] or '') .. ' | '
-- queries
generated_text = generated_text