From 21f566112e1a5843393780d8ba07065cc795982a Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Mon, 20 Apr 2026 22:50:16 +0200 Subject: [PATCH] refactor(ts): own parser installation, split from pack --- init.lua | 148 +++++++++++++++++---------------------------------- lua/pack.lua | 119 +++++++++++++++-------------------------- lua/ts.lua | 146 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 201 insertions(+), 212 deletions(-) diff --git a/init.lua b/init.lua index 8dede5b..ccf3439 100644 --- a/init.lua +++ b/init.lua @@ -22,7 +22,6 @@ for _, file in ipairs(files) do end end -local ts = require("ts") require("pack").setup({ "https://github.com/navarasu/onedark.nvim", "https://github.com/ibhagwan/fzf-lua", @@ -42,105 +41,58 @@ require("pack").setup({ "https://github.com/saghen/blink.cmp", version = vim.version.range("^1"), }, - { - "https://github.com/tree-sitter/tree-sitter-bash", - ts_parser = "bash", - }, - -- required by cpp - { - "https://github.com/tree-sitter/tree-sitter-c", - ts_parser = "c", - }, - { - "https://github.com/stsewd/tree-sitter-comment", - ts_parser = "comment", - }, - { - "https://github.com/tree-sitter/tree-sitter-cpp", - ts_parser = "cpp", - }, - -- required by scss - { - "https://github.com/tree-sitter/tree-sitter-css", - ts_parser = "css", - }, - { - "https://github.com/gbprod/tree-sitter-gitcommit", - ts_parser = "gitcommit", - }, - { - "https://github.com/tree-sitter/tree-sitter-go", - ts_parser = "go", - }, - { - "https://github.com/ngalaiko/tree-sitter-go-template", - ts_parser = "gotmpl", - }, - { - "https://github.com/tree-sitter/tree-sitter-html", - ts_parser = "html", - }, - { - "https://github.com/tree-sitter/tree-sitter-json", - ts_parser = "json", - }, - { - "https://github.com/tree-sitter-grammars/tree-sitter-luadoc", - ts_parser = "luadoc", - }, - { - "https://github.com/tree-sitter/tree-sitter-php", - ts_parser = { - { lang = "php", location = "php" }, - { lang = "php_only", location = "php_only" }, - }, - }, - { - "https://github.com/tree-sitter/tree-sitter-python", - ts_parser = "python", - }, - { - "https://github.com/tree-sitter/tree-sitter-rust", - ts_parser = "rust", - }, - { - "https://github.com/serenadeai/tree-sitter-scss", - ts_parser = "scss", - }, - { - "https://github.com/derekstride/tree-sitter-sql", - version = "gh-pages", - ts_parser = "sql", - }, - { - "https://github.com/tree-sitter-grammars/tree-sitter-svelte", - ts_parser = "svelte", - }, - { - "https://github.com/tree-sitter/tree-sitter-typescript", - ts_parser = { - { lang = "typescript", location = "typescript" }, - { lang = "tsx", location = "tsx" }, - }, - }, - { - "https://github.com/tree-sitter-grammars/tree-sitter-xml", - ts_parser = { - { lang = "xml", location = "xml" }, - { lang = "dtd", location = "dtd" }, - }, - }, - { - "https://github.com/tree-sitter-grammars/tree-sitter-yaml", - ts_parser = "yaml", - }, - { - "https://github.com/georgeharker/tree-sitter-zsh", - ts_parser = "zsh", - }, }) -ts.setup({ +require("ts").setup({ + parsers = { + "https://github.com/tree-sitter/tree-sitter-bash", + -- required by cpp + "https://github.com/tree-sitter/tree-sitter-c", + "https://github.com/stsewd/tree-sitter-comment", + "https://github.com/tree-sitter/tree-sitter-cpp", + -- required by scss + "https://github.com/tree-sitter/tree-sitter-css", + "https://github.com/gbprod/tree-sitter-gitcommit", + "https://github.com/tree-sitter/tree-sitter-go", + { + "https://github.com/ngalaiko/tree-sitter-go-template", + lang = "gotmpl", + }, + "https://github.com/tree-sitter/tree-sitter-html", + "https://github.com/tree-sitter/tree-sitter-json", + "https://github.com/tree-sitter-grammars/tree-sitter-luadoc", + { + "https://github.com/tree-sitter/tree-sitter-php", + specs = { + { lang = "php", location = "php" }, + { lang = "php_only", location = "php_only" }, + }, + }, + "https://github.com/tree-sitter/tree-sitter-python", + "https://github.com/tree-sitter/tree-sitter-rust", + "https://github.com/serenadeai/tree-sitter-scss", + { + "https://github.com/derekstride/tree-sitter-sql", + version = "gh-pages", + }, + "https://github.com/tree-sitter-grammars/tree-sitter-svelte", + { + "https://github.com/tree-sitter/tree-sitter-typescript", + specs = { + { lang = "typescript", location = "typescript" }, + { lang = "tsx", location = "tsx" }, + }, + }, + { + "https://github.com/tree-sitter-grammars/tree-sitter-xml", + specs = { + { lang = "xml", location = "xml" }, + { lang = "dtd", location = "dtd" }, + }, + }, + "https://github.com/tree-sitter-grammars/tree-sitter-yaml", + "https://github.com/georgeharker/tree-sitter-zsh", + }, languages = { "bash", "c", -- builtin diff --git a/lua/pack.lua b/lua/pack.lua index 86e67c8..cd70aea 100644 --- a/lua/pack.lua +++ b/lua/pack.lua @@ -29,7 +29,6 @@ end ---@field name? string ---@field version? string | vim.VersionRange ---@field build? string[] | fun(self: ow.Pack.Plugin) ----@field ts_parser? ow.TS.ParserField ---@class ow.Pack.Plugin : ow.Pack.PluginSpec ---@field name string @@ -89,7 +88,6 @@ local function to_pack_spec(spec) version = spec.version, data = { build = spec.build, - ts_parser = spec.ts_parser, }, } end @@ -112,14 +110,6 @@ local function run_build(plugin) log.error("Invalid build parameter for %s", plugin.name) 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 ---@field active boolean ---@field kind "install" | "update" | "delete" @@ -129,28 +119,6 @@ end ---@class ow.Pack.Event : vim.api.keyset.create_autocmd.callback_args ---@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? local watcher = nil ---@type ow.Util.KeyedDebounceHandle? @@ -237,56 +205,55 @@ function M.unwatch() end end ----@param specs (string | ow.Pack.PluginSpec)[] -function M.setup(specs) - ---@type table - local events = {} - local group = vim.api.nvim_create_augroup("ow.Pack", { clear = true }) - local id = vim.api.nvim_create_autocmd( - { "PackChangedPre", "PackChanged" }, - { - group = group, - ---@param ev ow.Pack.Event - callback = function(ev) - local name = ev.data.spec.name - if not name then - return - end - if not events[name] then - events[name] = {} - end - table.insert(events[name], ev) - end, - } - ) - vim.pack.add(vim.tbl_map(to_pack_spec, specs), { - load = function(data) - if not data.spec.name then - log.error("Missing name for plugin: %s", data.spec.src) - return - end - local d = data.spec.data or {} - ---@type ow.Pack.Plugin - local plugin = { - [1] = data.spec.src, - name = data.spec.name, - version = data.spec.version, - build = d.build, - ts_parser = d.ts_parser, - path = data.path, - } - table.insert(M.plugins, plugin) - if not d.ts_parser then - vim.cmd.packadd(plugin.name) +---@param specs vim.pack.Spec[] +---@param on_load fun(data: { spec: vim.pack.Spec, path: string }) +---@return table changed names installed or updated during this call +function M.install(specs, on_load) + ---@type table + local changed = {} + local group = + vim.api.nvim_create_augroup("ow.Pack.install", { clear = true }) + local id = vim.api.nvim_create_autocmd("PackChanged", { + group = group, + ---@param ev ow.Pack.Event + callback = function(ev) + local name = ev.data.spec.name + if + name + and (ev.data.kind == "install" or ev.data.kind == "update") + then + changed[name] = true end end, }) + vim.pack.add(specs, { load = on_load }) vim.api.nvim_del_autocmd(id) + 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 + log.error("Missing name for plugin: %s", data.spec.src) + return + end + local d = data.spec.data or {} + ---@type ow.Pack.Plugin + local plugin = { + [1] = data.spec.src, + name = data.spec.name, + version = data.spec.version, + build = d.build, + path = data.path, + } + table.insert(M.plugins, plugin) + vim.cmd.packadd(plugin.name) + end) for _, plugin in ipairs(M.plugins) do - local ev = events[plugin.name] - if ev then - process_events(plugin, ev) + if plugin.build and changed[plugin.name] then + run_build(plugin) end load(plugin.name, false) end diff --git a/lua/ts.lua b/lua/ts.lua index fa37b7d..19b440c 100644 --- a/lua/ts.lua +++ b/lua/ts.lua @@ -8,13 +8,33 @@ local M = {} ---@field generate? 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 ---@return string -local function parser_path(plugin, lang) - return vim.fs.joinpath(plugin.path, "parser", lang .. ".so") +local function parser_so(path, lang) + return vim.fs.joinpath(path, "parser", lang .. ".so") end ---@param buf integer @@ -43,12 +63,12 @@ local function activate_open_buffers(lang) end end ----@param plugin ow.Pack.Plugin +---@param parser ow.TS.Parser ---@param spec ow.TS.ParserSpec -function M.build(plugin, spec) - local cwd = spec.location and vim.fs.joinpath(plugin.path, spec.location) - or plugin.path - local out = parser_path(plugin, spec.lang) +function M.build(parser, spec) + local cwd = spec.location and vim.fs.joinpath(parser.path, spec.location) + or parser.path + local out = parser_so(parser.path, spec.lang) vim.fn.mkdir(vim.fs.dirname(out), "p") local function on_build(r) @@ -105,49 +125,99 @@ function M.build(plugin, spec) 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[] -function M.normalize(field) - if type(field) == "string" then - return { { lang = field } } +local function entry_specs(entry, pack_name) + if type(entry) == "string" then + return { { lang = lang_from_name(pack_name) } } end - - if type(field) == "table" then - if type(field.lang) == "string" then - ---@cast field ow.TS.ParserSpec - return { field } - end - if type(field[1]) == "table" then - ---@cast field ow.TS.ParserSpec[] - return field - end + if entry.specs then + return entry.specs end - - error("invalid ts_parser value: " .. vim.inspect(field)) + ---@cast entry ow.TS.SingleParser + return { + { + lang = entry.lang or lang_from_name(pack_name), + location = entry.location, + generate = entry.generate, + from_json = entry.from_json, + }, + } end ---@param languages string[] +---@return string[] local function collect_filetypes(languages) - local filetypes = {} + local seen = {} for _, lang in ipairs(languages) do for _, ft in ipairs(vim.treesitter.language.get_filetypes(lang)) do - if not vim.list_contains(filetypes, ft) then - table.insert(filetypes, ft) - end + seen[ft] = true end end - return filetypes + return vim.tbl_keys(seen) end ----@param opts { languages: string[] } +---@param opts { parsers: ow.TS.ParserEntry[], languages: string[] } function M.setup(opts) - for _, plugin in ipairs(require("pack").plugins) do - if plugin.ts_parser then - for _, p in ipairs(M.normalize(plugin.ts_parser)) do - local path = parser_path(plugin, p.lang) - if vim.uv.fs_stat(path) then - vim.treesitter.language.add(p.lang, { path = path }) - end + ---@type vim.pack.Spec[] + local pack_specs = {} + ---@type table + local entries_by_src = {} + for _, entry in ipairs(opts.parsers) do + local pack_spec = entry_pack_spec(entry) + table.insert(pack_specs, pack_spec) + entries_by_src[pack_spec.src] = entry + 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