local log = require("log") local M = {} ---@class ow.TS.ParserSpec ---@field lang string ---@field location? string ---@field generate? boolean ---@field from_json? boolean ---@alias ow.TS.ParserField string | ow.TS.ParserSpec | ow.TS.ParserSpec[] ---@param plugin ow.Pack.Plugin ---@param lang string ---@return string local function parser_path(plugin, lang) return vim.fs.joinpath(plugin.path, "parser", lang .. ".so") 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) return end for _, win in ipairs(vim.fn.win_findbuf(buf)) do vim.wo[win].foldmethod = "expr" vim.wo[win].foldexpr = "v:lua.vim.treesitter.foldexpr()" end end ---@param lang string local function activate_open_buffers(lang) local fts = vim.treesitter.language.get_filetypes(lang) for _, buf in ipairs(vim.api.nvim_list_bufs()) do if vim.api.nvim_buf_is_loaded(buf) and vim.list_contains(fts, vim.bo[buf].filetype) then start_treesitter(buf) end end end ---@param plugin ow.Pack.Plugin ---@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) 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, r.stderr or "" ) return end vim.treesitter.language.add(spec.lang, { path = out }) activate_open_buffers(spec.lang) end local function do_build() vim.system( { "tree-sitter", "build", "-o", out }, { cwd = cwd }, vim.schedule_wrap(on_build) ) end if spec.generate then local cmd = { "tree-sitter", "generate", "--abi", tostring(vim.treesitter.language_version), } if spec.from_json ~= false then table.insert(cmd, "src/grammar.json") end vim.system( cmd, { cwd = cwd, env = { TREE_SITTER_JS_RUNTIME = "native" }, }, vim.schedule_wrap(function(r) if r.code ~= 0 then log.error( "Failed to generate parser for %s: %s", spec.lang, r.stderr or "" ) return end do_build() end) ) else do_build() end end ---@param field ow.TS.ParserField ---@return ow.TS.ParserSpec[] function M.normalize(field) if type(field) == "string" then return { { lang = field } } 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 end error("invalid ts_parser value: " .. vim.inspect(field)) end ---@param languages string[] local function collect_filetypes(languages) local filetypes = {} 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 end end return filetypes end ---@param opts { 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 end end end vim.api.nvim_create_autocmd("FileType", { pattern = collect_filetypes(opts.languages), callback = function(ev) start_treesitter(ev.buf) end, }) end return M