Guest User

treesitter diagnostic hack v2

a guest
Feb 17th, 2025
383
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 3.15 KB | Source Code | 0 0
  1. --- language-independent query for syntax errors and missing elements
  2. local error_query = vim.treesitter.query.parse('query', '[(ERROR)(MISSING)] @a')
  3. local namespace = vim.api.nvim_create_namespace('treesitter.diagnostics')
  4.  
  5. --- @param args vim.api.keyset.create_autocmd.callback_args
  6. local function diagnose(args)
  7.   if not vim.diagnostic.is_enabled({bufnr = args.buf}) then
  8.     return
  9.   end
  10.   -- don't diagnose strange stuff
  11.   if vim.bo[args.buf].buftype ~= '' then
  12.     return
  13.   end
  14.  
  15.   local diagnostics = {}
  16.   local parser = vim.treesitter.get_parser(args.buf, nil, { error = false })
  17.   if parser then
  18.     parser:parse(false, function(_, trees)
  19.       if not trees then
  20.         return
  21.       end
  22.       parser:for_each_tree(function(tree, ltree)
  23.         -- only process trees containing errors
  24.         if tree:root():has_error() then
  25.           for _, node in error_query:iter_captures(tree:root(), args.buf) do
  26.             local lnum, col, end_lnum, end_col = node:range()
  27.  
  28.             -- collapse nested syntax errors that occur at the exact same position
  29.             local parent = node:parent()
  30.             if parent and parent:type() == 'ERROR' and parent:range() == node:range() then
  31.               goto continue
  32.             end
  33.  
  34.             -- clamp large syntax error ranges to just the line to reduce noise
  35.             if end_lnum > lnum then
  36.               end_lnum = lnum + 1
  37.               end_col = 0
  38.             end
  39.  
  40.             --- @type vim.Diagnostic
  41.             local diagnostic = {
  42.               source = 'treesitter',
  43.               lnum = lnum,
  44.               end_lnum = end_lnum,
  45.               col = col,
  46.               end_col = end_col,
  47.               message = '',
  48.               code = string.format('%s-syntax', ltree:lang()),
  49.               bufnr = args.buf,
  50.               namespace = namespace,
  51.               severity = vim.diagnostic.severity.ERROR
  52.             }
  53.             if node:missing() then
  54.               diagnostic.message = string.format('missing `%s`', node:type())
  55.             else
  56.               diagnostic.message = 'error'
  57.             end
  58.  
  59.             -- add context to the error using sibling and parent nodes
  60.             local previous = node:prev_sibling()
  61.             if previous and previous:type() ~= 'ERROR' then
  62.               local previous_type = previous:named() and previous:type() or string.format('`%s`', previous:type())
  63.               diagnostic.message = diagnostic.message .. ' after ' .. previous_type
  64.             end
  65.  
  66.             if parent and parent:type() ~= 'ERROR' and (previous == nil or previous:type() ~= parent:type()) then
  67.               diagnostic.message = diagnostic.message .. ' in ' .. parent:type()
  68.             end
  69.  
  70.             table.insert(diagnostics, diagnostic)
  71.             ::continue::
  72.           end
  73.         end
  74.       end)
  75.     end)
  76.     vim.diagnostic.set(namespace, args.buf, diagnostics)
  77.   end
  78. end
  79.  
  80.  
  81. local autocmd_group = vim.api.nvim_create_augroup('editor.treesitter', { clear = true })
  82.  
  83. vim.api.nvim_create_autocmd({ 'FileType', 'TextChanged', 'InsertLeave' }, {
  84.   desc = 'treesitter diagnostics',
  85.   group = autocmd_group,
  86.   callback = vim.schedule_wrap(diagnose),
  87. })
Advertisement
Add Comment
Please, Sign In to add comment