diff --git a/init.lua b/init.lua index cec7386..8b4bb27 100644 --- a/init.lua +++ b/init.lua @@ -44,85 +44,52 @@ require("pack").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/uyha/tree-sitter-cmake", - "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", + "https://github.com/tree-sitter/tree-sitter-bash", + -- required by cpp + "https://github.com/tree-sitter/tree-sitter-c", + "https://github.com/uyha/tree-sitter-cmake", + "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", }, - languages = { - "bash", - "c", -- builtin - "cmake", - "comment", - "cpp", - "css", - "gitcommit", - "go", - "gotmpl", - "html", - "json", - "lua", -- builtin - "luadoc", - "markdown", -- builtin - "markdown_inline", -- builtin - "php", - "python", - "query", -- builtin - "rust", - "scss", - "sql", - "svelte", - "tsx", - "typescript", - "vim", -- builtin - "vimdoc", -- builtin - "xml", - "yaml", - "zsh", + "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", + parsers = { + { 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", + parsers = { + { lang = "typescript", location = "typescript" }, + { lang = "tsx", location = "tsx" }, + }, + }, + { + "https://github.com/tree-sitter-grammars/tree-sitter-xml", + parsers = { + { lang = "xml", location = "xml" }, + { lang = "dtd", location = "dtd" }, + }, + }, + "https://github.com/tree-sitter-grammars/tree-sitter-yaml", + "https://github.com/georgeharker/tree-sitter-zsh", }) diff --git a/lua/ts.lua b/lua/ts.lua index 19b440c..7812378 100644 --- a/lua/ts.lua +++ b/lua/ts.lua @@ -2,33 +2,27 @@ local log = require("log") local M = {} ----@class ow.TS.ParserSpec +---@class ow.TS.Parser ---@field lang string ---@field location? string ---@field generate? boolean ---@field from_json? boolean ----@class ow.TS.SingleParser +---@class ow.TS.RepoBase ---@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 ----@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 +---@class ow.TS.Repo : ow.TS.RepoBase ---@field name string ----@field path string ----@field specs ow.TS.ParserSpec[] +---@field parsers ow.TS.Parser[] +---@field path? string + +---@class ow.TS.ParserOpts : ow.TS.Parser +---@field lang? string + +---@class ow.TS.RepoOpts : ow.TS.RepoBase, ow.TS.ParserOpts +---@field name? string +---@field parsers? ow.TS.Parser[] ---@param path string ---@param lang string @@ -39,9 +33,7 @@ end ---@param buf integer local function start_treesitter(buf) - local ok, err = pcall(vim.treesitter.start, buf) - if not ok then - log.error("Failed to enable treesitter for buffer %d: %s", buf, err) + if not pcall(vim.treesitter.start, buf) then return end for _, win in ipairs(vim.fn.win_findbuf(buf)) do @@ -63,25 +55,26 @@ local function activate_open_buffers(lang) end end +---@param repo ow.TS.Repo ---@param parser ow.TS.Parser ----@param spec ow.TS.ParserSpec -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) +function M.build(repo, parser) + local path = assert(repo.path, "repo not installed: " .. repo.name) + local cwd = parser.location and vim.fs.joinpath(path, parser.location) + or path + local out = parser_so(path, parser.lang) vim.fn.mkdir(vim.fs.dirname(out), "p") local function on_build(r) if r.code ~= 0 then log.error( "Failed to build parser for %s: %s", - spec.lang, + parser.lang, r.stderr or "" ) return end - vim.treesitter.language.add(spec.lang, { path = out }) - activate_open_buffers(spec.lang) + vim.treesitter.language.add(parser.lang, { path = out }) + activate_open_buffers(parser.lang) end local function do_build() @@ -92,14 +85,14 @@ function M.build(parser, spec) ) end - if spec.generate then + if parser.generate then local cmd = { "tree-sitter", "generate", "--abi", tostring(vim.treesitter.language_version), } - if spec.from_json ~= false then + if parser.from_json ~= false then table.insert(cmd, "src/grammar.json") end vim.system( @@ -112,7 +105,7 @@ function M.build(parser, spec) if r.code ~= 0 then log.error( "Failed to generate parser for %s: %s", - spec.lang, + parser.lang, r.stderr or "" ) return @@ -135,95 +128,102 @@ local function lang_from_name(pack_name) return lang end ----@param entry ow.TS.ParserEntry ----@return vim.pack.Spec -local function entry_pack_spec(entry) +---@param url string +---@return string +local function name_from_url(url) + local name = url:match("([^/]+)$") or url + return (name:gsub("%.git$", "")) +end + +---@param entry string | ow.TS.RepoOpts +---@return ow.TS.Repo +local function normalize(entry) + ---@type ow.TS.RepoOpts + local opts 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[] -local function entry_specs(entry, pack_name) - if type(entry) == "string" then - return { { lang = lang_from_name(pack_name) } } - end - if entry.specs then - return entry.specs - end - ---@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 seen = {} - for _, lang in ipairs(languages) do - for _, ft in ipairs(vim.treesitter.language.get_filetypes(lang)) do - seen[ft] = true - end - end - return vim.tbl_keys(seen) -end - ----@param opts { parsers: ow.TS.ParserEntry[], languages: string[] } -function M.setup(opts) - ---@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 + opts = { entry } --[[@as ow.TS.RepoOpts]] + else + opts = entry end + local name = opts.name or name_from_url(opts[1]) ---@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 + local input_parsers = opts.parsers + if input_parsers and vim.islist(input_parsers) then + for _, s in ipairs(input_parsers) do + table.insert(parsers, { + lang = s.lang, + location = s.location, + generate = s.generate ~= nil and s.generate or opts.generate, + from_json = s.from_json ~= nil and s.from_json + or opts.from_json, + }) end + else table.insert(parsers, { - name = data.spec.name, - path = data.path, - specs = entry_specs(entry, data.spec.name), + lang = opts.lang or lang_from_name(name), + location = opts.location, + generate = opts.generate, + from_json = opts.from_json, }) + end + + return { + [1] = opts[1], + name = name, + version = opts.version, + parsers = parsers, + } +end + +---@param opts (string | ow.TS.RepoOpts)[] +function M.setup(opts) + ---@type ow.TS.Repo[] + local repos = vim.tbl_map(normalize, opts) + + ---@type vim.pack.Spec[] + local pack_specs = vim.tbl_map(function(p) + return { src = p[1], name = p.name, version = p.version } + end, repos) + + ---@type table + local by_src = {} + for _, p in ipairs(repos) do + by_src[p[1]] = p + end + + local pack = require("pack") + local changed = pack.install(pack_specs, function(data) + local repo = by_src[data.spec.src] + if repo then + repo.path = data.path + end 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) + for _, repo in ipairs(repos) do + pack.register_hook(repo.name, function(ev) + repo.path = ev.path + for _, parser in ipairs(repo.parsers) do + M.build(repo, parser) + end + end) + if not repo.path then + log.error("Repo not installed: %s", repo.name) + goto continue + end + for _, parser in ipairs(repo.parsers) do + local so = parser_so(repo.path, parser.lang) + if changed[repo.name] or not vim.uv.fs_stat(so) then + M.build(repo, parser) else - vim.treesitter.language.add(spec.lang, { path = so }) + vim.treesitter.language.add(parser.lang, { path = so }) end end + ::continue:: end vim.api.nvim_create_autocmd("FileType", { - pattern = collect_filetypes(opts.languages), callback = function(ev) start_treesitter(ev.buf) end,