local puk = require 'std.puk' local auto = require 'std.auto' local ignored = require 'std.ignored' local dbg = require 'std.dbg' local add_dependency = require 'bugabinga.health'.add_dependency add_dependency { name_of_executable = 'zig' } add_dependency { name = 'TreeSitter CLI', name_of_executable = 'tree-sitter' } add_dependency { name_of_executable = 'tar' } add_dependency { name_of_executable = 'curl' } local function is_ignored( match ) for _, v in ipairs( ignored.filetypes ) do if v == match or match:match( v ) then return true end end return false end local treesitter = puk 'nvim-treesitter' treesitter.setup {} treesitter.install { 'bash', 'nu', 'ini' } -- TODO: in 0.12 check if this behaviour is builtin on vim.treesitter.start() -- if so, remove my stuff auto 'look_for_treesitter_parser' { description = 'Try loading a treesitter parser, if available.', events = 'FileType', pattern = '*', command = function ( data ) local match = data.match if match == 'text' then return end if is_ignored( match ) then dbg( 'Ignore %s for treesitter.', match ) return end local ok, _ = pcall( vim.treesitter.start, data.buf ) if not ok then -- resolve filetype to its treesitter language (sh -> bash, quadlet -> ini, -- cucumber -> gherkin) before consulting the registry local lang = vim.treesitter.language.get_lang( match ) or match local available = require 'nvim-treesitter.parsers' local potential_parser = available[lang] if potential_parser then vim.notify( 'Found potential treesitter parser for current filetype. Trying to install it. ' .. lang ) local task = require 'nvim-treesitter.install'.install( lang, { force = false } ) task:await( function ( err, success ) if not success then vim.notify( 'Failed to install treesitter parser: ' .. lang, vim.log.levels.ERROR ) return end vim.schedule( function () if pcall( vim.treesitter.start, data.buf ) then vim.opt_local.foldmethod = 'expr' vim.opt_local.foldexpr = 'v:lua.vim.treesitter.foldexpr()' else -- revision marker lied (e.g. .so wiped out of band): force rebuild once local repair = require 'nvim-treesitter.install'.install( { lang }, { force = true } ) repair:await( function ( _, repaired ) if repaired then vim.schedule( function() pcall( vim.treesitter.start, data.buf ) vim.opt_local.foldmethod = 'expr' vim.opt_local.foldexpr = 'v:lua.vim.treesitter.foldexpr()' end ) end end ) end end ) end ) end return end vim.opt_local.foldmethod = 'expr' vim.opt_local.foldexpr = 'v:lua.vim.treesitter.foldexpr()' end, } -- add d2 parser; the .so and queries are installed out of band by puk (puk.parsers.nuon) vim.treesitter.language.register( 'd2', 'd2' ) -- systemd units and podman quadlets are INI-dialect files vim.treesitter.language.register( 'ini', 'systemd' ) -- podman quadlet units get their own semantic filetype, same INI grammar vim.treesitter.language.register( 'ini', 'quadlet' ) -- sh files are POSIX-ish, the bash grammar covers them vim.treesitter.language.register( 'bash', 'sh' ) -- opentofu files are HCL vim.treesitter.language.register( 'hcl', { 'opentofu', 'opentofu-vars' } ) -- nvim calls the gherkin filetype 'cucumber' vim.treesitter.language.register( 'gherkin', 'cucumber' ) -- surface treesitter parse errors as ERROR diagnostics in every language; -- refreshed on change/insert-leave/save, so typing stays flicker-free local ts_errors = vim.api.nvim_create_namespace 'treesitter-errors' local function collect_errors( node, out ) if node:type() == 'ERROR' or node:missing() then local lnum, col = node:range() out[#out + 1] = { lnum = lnum, col = col, end_lnum = lnum, end_col = col, message = 'syntax error', severity = vim.diagnostic.severity.ERROR, source = 'treesitter' } return end for child in node:iter_children() do collect_errors( child, out ) end end local function show_ts_errors( args ) local ok, parser = pcall( vim.treesitter.get_parser, args.buf ) if not ok or not parser then return end local trees = parser:parse() if not trees then return end local tree = trees[1] if not tree then return end local out = {} local root = tree:root() if not root then return end collect_errors( root, out ) vim.diagnostic.set( ts_errors, args.buf, out ) end auto 'treesitter_error_diagnostics' { description = 'Report treesitter parse errors as ERROR diagnostics.', events = { 'FileType', 'TextChanged', 'InsertLeave', 'BufWritePost' }, pattern = '*', command = function( args ) vim.schedule( function() show_ts_errors( args ) end ) end, } -- this module loads deferred, so the startup file's FileType event has already -- passed: catch up on every loaded buffer once vim.schedule( function() for _, buf in ipairs( vim.api.nvim_list_bufs() ) do if vim.api.nvim_buf_is_loaded( buf ) then show_ts_errors( { buf = buf } ) end end end ) -- runs out of band via puk post-install (headless nvim, editor closed): -- updates nvim-treesitter-managed parsers to the revisions pinned upstream. local function post_install() require 'nvim-treesitter.install'.update():wait() end _G.BugabingaTreesitter = { is_ignored = is_ignored, post_install = post_install, } return BugabingaTreesitter