refactor(ts): own parser installation, split from pack

This commit is contained in:
2026-04-20 22:50:16 +02:00
parent c7dd083083
commit 21f566112e
3 changed files with 201 additions and 212 deletions
+8 -56
View File
@@ -22,7 +22,6 @@ for _, file in ipairs(files) do
end end
end end
local ts = require("ts")
require("pack").setup({ require("pack").setup({
"https://github.com/navarasu/onedark.nvim", "https://github.com/navarasu/onedark.nvim",
"https://github.com/ibhagwan/fzf-lua", "https://github.com/ibhagwan/fzf-lua",
@@ -42,105 +41,58 @@ require("pack").setup({
"https://github.com/saghen/blink.cmp", "https://github.com/saghen/blink.cmp",
version = vim.version.range("^1"), version = vim.version.range("^1"),
}, },
{ })
require("ts").setup({
parsers = {
"https://github.com/tree-sitter/tree-sitter-bash", "https://github.com/tree-sitter/tree-sitter-bash",
ts_parser = "bash",
},
-- required by cpp -- required by cpp
{
"https://github.com/tree-sitter/tree-sitter-c", "https://github.com/tree-sitter/tree-sitter-c",
ts_parser = "c",
},
{
"https://github.com/stsewd/tree-sitter-comment", "https://github.com/stsewd/tree-sitter-comment",
ts_parser = "comment",
},
{
"https://github.com/tree-sitter/tree-sitter-cpp", "https://github.com/tree-sitter/tree-sitter-cpp",
ts_parser = "cpp",
},
-- required by scss -- required by scss
{
"https://github.com/tree-sitter/tree-sitter-css", "https://github.com/tree-sitter/tree-sitter-css",
ts_parser = "css",
},
{
"https://github.com/gbprod/tree-sitter-gitcommit", "https://github.com/gbprod/tree-sitter-gitcommit",
ts_parser = "gitcommit",
},
{
"https://github.com/tree-sitter/tree-sitter-go", "https://github.com/tree-sitter/tree-sitter-go",
ts_parser = "go",
},
{ {
"https://github.com/ngalaiko/tree-sitter-go-template", "https://github.com/ngalaiko/tree-sitter-go-template",
ts_parser = "gotmpl", lang = "gotmpl",
}, },
{
"https://github.com/tree-sitter/tree-sitter-html", "https://github.com/tree-sitter/tree-sitter-html",
ts_parser = "html",
},
{
"https://github.com/tree-sitter/tree-sitter-json", "https://github.com/tree-sitter/tree-sitter-json",
ts_parser = "json",
},
{
"https://github.com/tree-sitter-grammars/tree-sitter-luadoc", "https://github.com/tree-sitter-grammars/tree-sitter-luadoc",
ts_parser = "luadoc",
},
{ {
"https://github.com/tree-sitter/tree-sitter-php", "https://github.com/tree-sitter/tree-sitter-php",
ts_parser = { specs = {
{ lang = "php", location = "php" }, { lang = "php", location = "php" },
{ lang = "php_only", location = "php_only" }, { lang = "php_only", location = "php_only" },
}, },
}, },
{
"https://github.com/tree-sitter/tree-sitter-python", "https://github.com/tree-sitter/tree-sitter-python",
ts_parser = "python",
},
{
"https://github.com/tree-sitter/tree-sitter-rust", "https://github.com/tree-sitter/tree-sitter-rust",
ts_parser = "rust",
},
{
"https://github.com/serenadeai/tree-sitter-scss", "https://github.com/serenadeai/tree-sitter-scss",
ts_parser = "scss",
},
{ {
"https://github.com/derekstride/tree-sitter-sql", "https://github.com/derekstride/tree-sitter-sql",
version = "gh-pages", version = "gh-pages",
ts_parser = "sql",
}, },
{
"https://github.com/tree-sitter-grammars/tree-sitter-svelte", "https://github.com/tree-sitter-grammars/tree-sitter-svelte",
ts_parser = "svelte",
},
{ {
"https://github.com/tree-sitter/tree-sitter-typescript", "https://github.com/tree-sitter/tree-sitter-typescript",
ts_parser = { specs = {
{ lang = "typescript", location = "typescript" }, { lang = "typescript", location = "typescript" },
{ lang = "tsx", location = "tsx" }, { lang = "tsx", location = "tsx" },
}, },
}, },
{ {
"https://github.com/tree-sitter-grammars/tree-sitter-xml", "https://github.com/tree-sitter-grammars/tree-sitter-xml",
ts_parser = { specs = {
{ lang = "xml", location = "xml" }, { lang = "xml", location = "xml" },
{ lang = "dtd", location = "dtd" }, { lang = "dtd", location = "dtd" },
}, },
}, },
{
"https://github.com/tree-sitter-grammars/tree-sitter-yaml", "https://github.com/tree-sitter-grammars/tree-sitter-yaml",
ts_parser = "yaml",
},
{
"https://github.com/georgeharker/tree-sitter-zsh", "https://github.com/georgeharker/tree-sitter-zsh",
ts_parser = "zsh",
}, },
})
ts.setup({
languages = { languages = {
"bash", "bash",
"c", -- builtin "c", -- builtin
+26 -59
View File
@@ -29,7 +29,6 @@ end
---@field name? string ---@field name? string
---@field version? string | vim.VersionRange ---@field version? string | vim.VersionRange
---@field build? string[] | fun(self: ow.Pack.Plugin) ---@field build? string[] | fun(self: ow.Pack.Plugin)
---@field ts_parser? ow.TS.ParserField
---@class ow.Pack.Plugin : ow.Pack.PluginSpec ---@class ow.Pack.Plugin : ow.Pack.PluginSpec
---@field name string ---@field name string
@@ -89,7 +88,6 @@ local function to_pack_spec(spec)
version = spec.version, version = spec.version,
data = { data = {
build = spec.build, build = spec.build,
ts_parser = spec.ts_parser,
}, },
} }
end end
@@ -112,14 +110,6 @@ local function run_build(plugin)
log.error("Invalid build parameter for %s", plugin.name) log.error("Invalid build parameter for %s", plugin.name)
end end
---@param plugin ow.Pack.Plugin
local function run_ts_build(plugin)
local ts = require("ts")
for _, p in ipairs(ts.normalize(assert(plugin.ts_parser))) do
ts.build(plugin, p)
end
end
---@class ow.Pack.Event.Data ---@class ow.Pack.Event.Data
---@field active boolean ---@field active boolean
---@field kind "install" | "update" | "delete" ---@field kind "install" | "update" | "delete"
@@ -129,28 +119,6 @@ end
---@class ow.Pack.Event : vim.api.keyset.create_autocmd.callback_args ---@class ow.Pack.Event : vim.api.keyset.create_autocmd.callback_args
---@field data ow.Pack.Event.Data ---@field data ow.Pack.Event.Data
---@param plugin ow.Pack.Plugin
---@param events ow.Pack.Event[]
local function process_events(plugin, events)
if not plugin.build and not plugin.ts_parser then
return
end
for _, ev in ipairs(events) do
if
ev.data.spec.name == plugin.name
and ev.event == "PackChanged"
and (ev.data.kind == "install" or ev.data.kind == "update")
then
if plugin.ts_parser then
run_ts_build(plugin)
else
run_build(plugin)
end
end
end
end
---@type uv.uv_fs_event_t? ---@type uv.uv_fs_event_t?
local watcher = nil local watcher = nil
---@type ow.Util.KeyedDebounceHandle<string>? ---@type ow.Util.KeyedDebounceHandle<string>?
@@ -237,30 +205,35 @@ function M.unwatch()
end end
end end
---@param specs (string | ow.Pack.PluginSpec)[] ---@param specs vim.pack.Spec[]
function M.setup(specs) ---@param on_load fun(data: { spec: vim.pack.Spec, path: string })
---@type table<string, ow.Pack.Event[]?> ---@return table<string, true> changed names installed or updated during this call
local events = {} function M.install(specs, on_load)
local group = vim.api.nvim_create_augroup("ow.Pack", { clear = true }) ---@type table<string, true>
local id = vim.api.nvim_create_autocmd( local changed = {}
{ "PackChangedPre", "PackChanged" }, local group =
{ vim.api.nvim_create_augroup("ow.Pack.install", { clear = true })
local id = vim.api.nvim_create_autocmd("PackChanged", {
group = group, group = group,
---@param ev ow.Pack.Event ---@param ev ow.Pack.Event
callback = function(ev) callback = function(ev)
local name = ev.data.spec.name local name = ev.data.spec.name
if not name then if
return name
and (ev.data.kind == "install" or ev.data.kind == "update")
then
changed[name] = true
end end
if not events[name] then
events[name] = {}
end
table.insert(events[name], ev)
end, end,
} })
) vim.pack.add(specs, { load = on_load })
vim.pack.add(vim.tbl_map(to_pack_spec, specs), { vim.api.nvim_del_autocmd(id)
load = function(data) return changed
end
---@param specs (string | ow.Pack.PluginSpec)[]
function M.setup(specs)
local changed = M.install(vim.tbl_map(to_pack_spec, specs), function(data)
if not data.spec.name then if not data.spec.name then
log.error("Missing name for plugin: %s", data.spec.src) log.error("Missing name for plugin: %s", data.spec.src)
return return
@@ -272,21 +245,15 @@ function M.setup(specs)
name = data.spec.name, name = data.spec.name,
version = data.spec.version, version = data.spec.version,
build = d.build, build = d.build,
ts_parser = d.ts_parser,
path = data.path, path = data.path,
} }
table.insert(M.plugins, plugin) table.insert(M.plugins, plugin)
if not d.ts_parser then
vim.cmd.packadd(plugin.name) vim.cmd.packadd(plugin.name)
end end)
end,
})
vim.api.nvim_del_autocmd(id)
for _, plugin in ipairs(M.plugins) do for _, plugin in ipairs(M.plugins) do
local ev = events[plugin.name] if plugin.build and changed[plugin.name] then
if ev then run_build(plugin)
process_events(plugin, ev)
end end
load(plugin.name, false) load(plugin.name, false)
end end
+107 -37
View File
@@ -8,13 +8,33 @@ local M = {}
---@field generate? boolean ---@field generate? boolean
---@field from_json? boolean ---@field from_json? boolean
---@alias ow.TS.ParserField string | ow.TS.ParserSpec | ow.TS.ParserSpec[] ---@class ow.TS.SingleParser
---@field [1] string
---@field name? string
---@field version? string | vim.VersionRange
---@field lang? string inferred from URL (strip "tree-sitter-" prefix) if omitted
---@field location? string
---@field generate? boolean
---@field from_json? boolean
---@param plugin ow.Pack.Plugin ---@class ow.TS.MultiParser
---@field [1] string
---@field name? string
---@field version? string | vim.VersionRange
---@field specs ow.TS.ParserSpec[]
---@alias ow.TS.ParserEntry string | ow.TS.SingleParser | ow.TS.MultiParser
---@class ow.TS.Parser
---@field name string
---@field path string
---@field specs ow.TS.ParserSpec[]
---@param path string
---@param lang string ---@param lang string
---@return string ---@return string
local function parser_path(plugin, lang) local function parser_so(path, lang)
return vim.fs.joinpath(plugin.path, "parser", lang .. ".so") return vim.fs.joinpath(path, "parser", lang .. ".so")
end end
---@param buf integer ---@param buf integer
@@ -43,12 +63,12 @@ local function activate_open_buffers(lang)
end end
end end
---@param plugin ow.Pack.Plugin ---@param parser ow.TS.Parser
---@param spec ow.TS.ParserSpec ---@param spec ow.TS.ParserSpec
function M.build(plugin, spec) function M.build(parser, spec)
local cwd = spec.location and vim.fs.joinpath(plugin.path, spec.location) local cwd = spec.location and vim.fs.joinpath(parser.path, spec.location)
or plugin.path or parser.path
local out = parser_path(plugin, spec.lang) local out = parser_so(parser.path, spec.lang)
vim.fn.mkdir(vim.fs.dirname(out), "p") vim.fn.mkdir(vim.fs.dirname(out), "p")
local function on_build(r) local function on_build(r)
@@ -105,49 +125,99 @@ function M.build(plugin, spec)
end end
end end
---@param field ow.TS.ParserField ---@param pack_name string
---@return string
local function lang_from_name(pack_name)
local lang = pack_name:match("^tree%-sitter%-(.+)$")
if not lang then
error("cannot derive lang from pack name: " .. pack_name)
end
return lang
end
---@param entry ow.TS.ParserEntry
---@return vim.pack.Spec
local function entry_pack_spec(entry)
if type(entry) == "string" then
return { src = entry }
end
return {
src = entry[1],
name = entry.name,
version = entry.version,
}
end
---@param entry ow.TS.ParserEntry
---@param pack_name string fallback for lang when the entry doesn't set it
---@return ow.TS.ParserSpec[] ---@return ow.TS.ParserSpec[]
function M.normalize(field) local function entry_specs(entry, pack_name)
if type(field) == "string" then if type(entry) == "string" then
return { { lang = field } } return { { lang = lang_from_name(pack_name) } }
end end
if entry.specs then
if type(field) == "table" then return entry.specs
if type(field.lang) == "string" then
---@cast field ow.TS.ParserSpec
return { field }
end end
if type(field[1]) == "table" then ---@cast entry ow.TS.SingleParser
---@cast field ow.TS.ParserSpec[] return {
return field {
end lang = entry.lang or lang_from_name(pack_name),
end location = entry.location,
generate = entry.generate,
error("invalid ts_parser value: " .. vim.inspect(field)) from_json = entry.from_json,
},
}
end end
---@param languages string[] ---@param languages string[]
---@return string[]
local function collect_filetypes(languages) local function collect_filetypes(languages)
local filetypes = {} local seen = {}
for _, lang in ipairs(languages) do for _, lang in ipairs(languages) do
for _, ft in ipairs(vim.treesitter.language.get_filetypes(lang)) do for _, ft in ipairs(vim.treesitter.language.get_filetypes(lang)) do
if not vim.list_contains(filetypes, ft) then seen[ft] = true
table.insert(filetypes, ft)
end end
end end
end return vim.tbl_keys(seen)
return filetypes
end end
---@param opts { languages: string[] } ---@param opts { parsers: ow.TS.ParserEntry[], languages: string[] }
function M.setup(opts) function M.setup(opts)
for _, plugin in ipairs(require("pack").plugins) do ---@type vim.pack.Spec[]
if plugin.ts_parser then local pack_specs = {}
for _, p in ipairs(M.normalize(plugin.ts_parser)) do ---@type table<string, ow.TS.ParserEntry>
local path = parser_path(plugin, p.lang) local entries_by_src = {}
if vim.uv.fs_stat(path) then for _, entry in ipairs(opts.parsers) do
vim.treesitter.language.add(p.lang, { path = path }) local pack_spec = entry_pack_spec(entry)
table.insert(pack_specs, pack_spec)
entries_by_src[pack_spec.src] = entry
end end
---@type ow.TS.Parser[]
local parsers = {}
local changed = require("pack").install(pack_specs, function(data)
if not data.spec.name then
log.error("Missing name for parser: %s", data.spec.src)
return
end
local entry = entries_by_src[data.spec.src]
if not entry then
return
end
table.insert(parsers, {
name = data.spec.name,
path = data.path,
specs = entry_specs(entry, data.spec.name),
})
end)
for _, parser in ipairs(parsers) do
for _, spec in ipairs(parser.specs) do
local so = parser_so(parser.path, spec.lang)
if changed[parser.name] or not vim.uv.fs_stat(so) then
M.build(parser, spec)
else
vim.treesitter.language.add(spec.lang, { path = so })
end end
end end
end end