From ebf5a0e8ebc24217ee955f1fba5edcfb15233a94 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 26 Sep 2026 23:08:55 +0200 Subject: [PATCH] cunstom diagnositcs --- nvim/.config/nvim/README.md | 2 +- nvim/.config/nvim/lua/romanzy/commands.lua | 1 + .../romanzy/custom/project_diagnostics.lua | 184 ++++++++++++++++++ nvim/.config/nvim/lua/romanzy/keymaps.lua | 12 +- 4 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 nvim/.config/nvim/lua/romanzy/custom/project_diagnostics.lua diff --git a/nvim/.config/nvim/README.md b/nvim/.config/nvim/README.md index cd66c18..e180abb 100644 --- a/nvim/.config/nvim/README.md +++ b/nvim/.config/nvim/README.md @@ -101,7 +101,7 @@ Diagnostics: - `]g`, `[g` jump to warning or error - `ti` toggle inline diagnostics - `tv` toggle virtual diagnostics -- `xx` toggle project warnings and errors +- `xx`, `:ProjectDiagnostics` check the current Dart or TypeScript package and show warnings and errors in Trouble. Save modified package files first. Other languages show available LSP diagnostics; project coverage depends on the language server. ## Tree-sitter diff --git a/nvim/.config/nvim/lua/romanzy/commands.lua b/nvim/.config/nvim/lua/romanzy/commands.lua index 7295847..d1c1bf5 100644 --- a/nvim/.config/nvim/lua/romanzy/commands.lua +++ b/nvim/.config/nvim/lua/romanzy/commands.lua @@ -3,6 +3,7 @@ local ac = vim.api.nvim_create_autocmd local aug = vim.api.nvim_create_augroup uc("W", "w|e", { desc = "write file then refresh buffer" }) +uc("ProjectDiagnostics", require("romanzy.custom.project_diagnostics").run, { desc = "check project warnings and errors" }) ac("TextYankPost", { desc = "Highlight when yanking text", diff --git a/nvim/.config/nvim/lua/romanzy/custom/project_diagnostics.lua b/nvim/.config/nvim/lua/romanzy/custom/project_diagnostics.lua new file mode 100644 index 0000000..5cf31bf --- /dev/null +++ b/nvim/.config/nvim/lua/romanzy/custom/project_diagnostics.lua @@ -0,0 +1,184 @@ +local M = {} + +local run_id = 0 +local severities = { error = "E", warning = "W" } +local extensions = { + dart = "dart", + ts = "typescript", + tsx = "typescript", + mts = "typescript", + cts = "typescript", + js = "typescript", + jsx = "typescript", + mjs = "typescript", + cjs = "typescript", +} + +local function absolute_path(root, path) + if path:sub(1, 1) == "/" then + return vim.fs.normalize(path) + end + return vim.fs.normalize(vim.fs.joinpath(root, path)) +end + +local function parse_output(kind, output, root) + local items = {} + for line in output:gmatch("[^\r\n]+") do + local path, row, col, level, message, code + if kind == "dart" then + level, path, row, col, message, code = line:match("^%s*(%a+)%s+%-%s+(.+):(%d+):(%d+)%s+%-%s+(.+)%s+%-%s+([%w_]+)%s*$") + else + path, row, col, level, code, message = line:match("^(.+)%((%d+),(%d+)%)%:%s*(%a+)%s+TS(%d+):%s*(.+)$") + if not path then + level, code, message = line:match("^(%a+)%s+TS(%d+):%s*(.+)$") + if level then + path, row, col = "tsconfig.json", 1, 1 + end + end + end + if path and severities[level] then + table.insert(items, { + filename = absolute_path(root, path), + lnum = tonumber(row), + col = tonumber(col), + text = message, + type = severities[level], + nr = code and tonumber(code) or nil, + }) + end + end + return items +end + +local function add_lsp_diagnostics(items, root) + local seen = {} + for _, item in ipairs(items) do + seen[table.concat({ item.filename, item.lnum, item.col, item.type, item.text }, "\0")] = true + end + local prefix = root .. "/" + for _, diagnostic in ipairs(vim.diagnostic.get()) do + local path = vim.api.nvim_buf_get_name(diagnostic.bufnr) + local kind = diagnostic.severity == vim.diagnostic.severity.ERROR and "E" + or diagnostic.severity == vim.diagnostic.severity.WARN and "W" + if kind and path:sub(1, #prefix) == prefix then + local item = { + filename = path, + lnum = diagnostic.lnum + 1, + col = diagnostic.col + 1, + text = diagnostic.message, + type = kind, + } + local key = table.concat({ item.filename, item.lnum, item.col, item.type, item.text }, "\0") + if not seen[key] then + table.insert(items, item) + seen[key] = true + end + end + end +end + +local function show_lsp_diagnostics(bufnr) + local clients = vim.lsp.get_clients({ bufnr = bufnr }) + local supported = false + for _, client in ipairs(clients) do + if client:supports_method("workspace/diagnostic") then + supported = true + break + end + end + if supported then + vim.lsp.buf.workspace_diagnostics() + elseif #clients == 0 then + vim.notify("Project diagnostics: no language server is attached; showing available diagnostics", vim.log.levels.WARN) + else + vim.notify("Project diagnostics: this language server may only report opened files", vim.log.levels.WARN) + end + require("trouble").close({ mode = "qflist" }) + require("trouble").open({ + mode = "diagnostics", + filter = { severity = { vim.diagnostic.severity.ERROR, vim.diagnostic.severity.WARN } }, + }) +end + +local function has_modified_buffers(root) + local prefix = root .. "/" + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + local path = vim.api.nvim_buf_get_name(bufnr) + if vim.bo[bufnr].modified and path:sub(1, #prefix) == prefix then + return true + end + end + return false +end + +local function checker_for(path) + local extension = path:match("%.([^./]+)$") + local kind = extensions[extension] + if not kind then + return nil + end + local marker = kind == "dart" and "pubspec.yaml" or "tsconfig.json" + local found = vim.fs.find(marker, { path = vim.fs.dirname(path), upward = true })[1] + if not found then + return nil + end + local root = vim.fs.dirname(found) + if kind == "dart" then + local has_fvm = vim.fs.find({ ".fvmrc", ".fvm" }, { path = root, upward = true })[1] + local executable = has_fvm and "fvm" or "dart" + local command = has_fvm and { "fvm", "dart", "analyze" } or { "dart", "analyze" } + return kind, root, executable, command + end + local config = table.concat(vim.fn.readfile(found), "\n") + if config:find('"references"%s*:') then + return kind, root, "pnpm", { "pnpm", "exec", "tsc", "--build", "tsconfig.json", "--noEmit", "--pretty", "false" } + end + return kind, root, "pnpm", { "pnpm", "exec", "tsc", "--noEmit", "--pretty", "false", "--project", "tsconfig.json" } +end + +function M.run() + run_id = run_id + 1 + local current_run = run_id + local bufnr = vim.api.nvim_get_current_buf() + local path = vim.api.nvim_buf_get_name(bufnr) + local kind, root, executable, command = checker_for(path) + if not kind then + show_lsp_diagnostics(bufnr) + return + end + require("trouble").close({ mode = "qflist" }) + if has_modified_buffers(root) then + vim.notify("Project diagnostics: save modified package files before checking", vim.log.levels.WARN) + return + end + if vim.fn.executable(executable) ~= 1 then + vim.notify("Project diagnostics: " .. executable .. " is unavailable", vim.log.levels.ERROR) + return + end + + vim.notify("Checking " .. root .. "…", vim.log.levels.INFO) + vim.system(command, { cwd = root, text = true }, function(result) + vim.schedule(function() + if current_run ~= run_id then + return + end + local output = (result.stdout or "") .. "\n" .. (result.stderr or "") + local items = parse_output(kind, output, root) + if result.code ~= 0 and #items == 0 then + vim.notify("Project diagnostics failed:\n" .. vim.trim(output), vim.log.levels.ERROR) + return + end + add_lsp_diagnostics(items, root) + vim.fn.setqflist({}, "r", { title = "Project diagnostics: " .. root, items = items }) + if #items == 0 then + require("trouble").close({ mode = "qflist" }) + vim.notify("Project diagnostics: no warnings or errors", vim.log.levels.INFO) + else + require("trouble").close({ mode = "diagnostics" }) + require("trouble").open({ mode = "qflist" }) + end + end) + end) +end + +return M diff --git a/nvim/.config/nvim/lua/romanzy/keymaps.lua b/nvim/.config/nvim/lua/romanzy/keymaps.lua index a28afac..d5057a2 100644 --- a/nvim/.config/nvim/lua/romanzy/keymaps.lua +++ b/nvim/.config/nvim/lua/romanzy/keymaps.lua @@ -138,17 +138,7 @@ vim.api.nvim_create_autocmd("LspAttach", { }) -- diagnostics -map("n", "xx", function() - require("trouble").toggle({ - mode = "diagnostics", - filter = { - severity = { - vim.diagnostic.severity.ERROR, - vim.diagnostic.severity.WARN, - }, - }, - }) -end, { desc = "toggle project warnings and errors" }) +map("n", "xx", require("romanzy.custom.project_diagnostics").run, { desc = "check project warnings and errors" }) map("n", "g.", vim.diagnostic.open_float, { desc = "line diagnostics" }) map("n", "gl", vim.diagnostic.setloclist, { desc = "diagnostic list" })