From bf0eacb506bc8125b0ab8d438913e54feba63391 Mon Sep 17 00:00:00 2001 From: David Czihak Date: Mon, 11 May 2026 17:45:21 +0200 Subject: Fix: Cache compiler errors Cache compiler errors instead of passing them directly to Nova. This removes the possibility for outdated old errors not going away. --- Zig.novaextension/Scripts/main.js | 136 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 6 deletions(-) (limited to 'Zig.novaextension/Scripts/main.js') diff --git a/Zig.novaextension/Scripts/main.js b/Zig.novaextension/Scripts/main.js index 6ef2241..0e66188 100644 --- a/Zig.novaextension/Scripts/main.js +++ b/Zig.novaextension/Scripts/main.js @@ -32,7 +32,112 @@ let commandRegistrations = []; let lastZlsStopAt = 0; const ZLS_RESTART_GRACE_MS = 500; +// Extension-managed issue collection for compiler diagnostics. Using an +// IssueCollection instead of task matchers lets us explicitly clear stale +// issues before each build, regardless of which task type the user runs. +let zigBuildIssues = null; +let issueFileWatcher = null; + +// --- ISSUE COLLECTION -------------------------------------------------------- + +function zigIssueFilePath() { + return nova.path.join(nova.extension.workspaceStoragePath, "build-issues.json"); +} + +function loadIssuesFromFile() { + if (!zigBuildIssues) return; + const filePath = zigIssueFilePath(); + let content = ""; + try { + const file = nova.fs.open(filePath, "r"); + if (!file) return; + content = file.read() || ""; + file.close(); + } catch (e) { + return; + } + + const trimmed = content.trim(); + if (!trimmed || trimmed === "[]") return; + + let data; + try { + data = JSON.parse(trimmed); + } catch (e) { + console.warn(`[${EXTENSION_ID}] Could not parse build-issues.json: ${e}`); + return; + } + if (!Array.isArray(data)) return; + + const byFile = new Map(); + for (const entry of data) { + if (!entry.file || !entry.severity) continue; + if (!byFile.has(entry.file)) byFile.set(entry.file, []); + const issue = new Issue(); + issue.message = entry.message || ""; + issue.severity = + entry.severity === "error" ? IssueSeverity.Error : + entry.severity === "warning" ? IssueSeverity.Warning : + IssueSeverity.Hint; + issue.line = entry.line || 1; + issue.column = entry.column || 1; + byFile.get(entry.file).push(issue); + } + + zigBuildIssues.clear(); + for (const [file, issues] of byFile) { + zigBuildIssues.set("file://" + file, issues); + } +} + +function setupIssueCollection() { + if (issueFileWatcher) { + issueFileWatcher.dispose(); + issueFileWatcher = null; + } + if (zigBuildIssues) { + zigBuildIssues.dispose(); + zigBuildIssues = null; + } + + zigBuildIssues = new IssueCollection(); + nova.subscriptions.add(zigBuildIssues); + + const filePath = zigIssueFilePath(); + try { + const dir = nova.path.dirname(filePath); + if (!nova.fs.stat(dir)) nova.fs.mkdir(dir); + if (!nova.fs.stat(filePath)) { + const f = nova.fs.open(filePath, "w"); + f.write("[]"); + f.close(); + } + issueFileWatcher = nova.fs.watch(filePath, loadIssuesFromFile); + } catch (e) { + console.warn(`[${EXTENSION_ID}] Could not set up issue file watcher: ${e}`); + } +} + +// Clear the issue collection and reset the JSON file before a build starts. +// Called from every build-type task resolver so stale issues from a previous +// run of ANY task type are always removed before the new output arrives. +function resetBuildIssues() { + if (zigBuildIssues) zigBuildIssues.clear(); + const filePath = zigIssueFilePath(); + try { + const dir = nova.path.dirname(filePath); + if (!nova.fs.stat(dir)) nova.fs.mkdir(dir); + const f = nova.fs.open(filePath, "w"); + f.write("[]"); + f.close(); + } catch (e) { + console.warn(`[${EXTENSION_ID}] Could not reset issue file: ${e}`); + } +} + exports.activate = function activate() { + setupIssueCollection(); + // Nova may call activate() again without first calling deactivate() when a // language server crashes and the extension is auto-restarted. Dispose any // existing instances so their config observers and ZLS processes don't leak. @@ -89,6 +194,15 @@ exports.deactivate = function deactivate() { }); commandRegistrations = []; + if (issueFileWatcher) { + issueFileWatcher.dispose(); + issueFileWatcher = null; + } + if (zigBuildIssues) { + zigBuildIssues.dispose(); + zigBuildIssues = null; + } + console.log(`[${EXTENSION_ID}] deactivated`); }; @@ -1340,14 +1454,16 @@ class ZigTaskAssistant { } } - createAction(command, args, cwd) { + // useMatchers: true keeps Nova's built-in matcher (needed for watch mode + // where the process never exits and the normalizer never writes the JSON). + createAction(command, args, cwd, { useMatchers = false } = {}) { + const env = { NOVA_ZIG_TASK_CWD: cwd || "" }; + if (!useMatchers) env.NOVA_ZIG_ISSUE_FILE = zigIssueFilePath(); return new TaskProcessAction("/bin/zsh", { args: ["-lc", buildIssueNormalizedCommand(command, args)], cwd, - env: { - NOVA_ZIG_TASK_CWD: cwd || "", - }, - matchers: [ISSUE_MATCHER], + env, + ...(useMatchers ? { matchers: [ISSUE_MATCHER] } : {}), }); } @@ -1392,6 +1508,7 @@ class ZigTaskAssistant { async resolveBuildAction(config, cwd) { console.log(`[${TASK_ASSISTANT_ID}] resolveBuildAction: cwd=${cwd}`); + resetBuildIssues(); const zigPath = await resolveZigExecutable(); if (!zigPath) return null; @@ -1400,6 +1517,7 @@ class ZigTaskAssistant { async resolveDebugBuildAction(config, cwd) { console.log(`[${TASK_ASSISTANT_ID}] resolveDebugBuildAction: cwd=${cwd}`); + resetBuildIssues(); const zigPath = await resolveZigExecutable(); if (!zigPath) return null; @@ -1412,6 +1530,7 @@ class ZigTaskAssistant { async resolveBuildRunAction(config, cwd, forceConsole) { console.log(`[${TASK_ASSISTANT_ID}] resolveBuildRunAction: cwd=${cwd}`); + resetBuildIssues(); const zigPath = await resolveZigExecutable(); if (!zigPath) return null; @@ -1442,6 +1561,7 @@ class ZigTaskAssistant { async resolveBuildStepAction(step) { console.log(`[${TASK_ASSISTANT_ID}] resolveBuildStepAction: step=${step}`); + resetBuildIssues(); if (!step || !/^[A-Za-z_][\w-]*$/.test(step)) return null; const zigPath = await resolveZigExecutable(); @@ -1455,6 +1575,7 @@ class ZigTaskAssistant { async resolveTestAction(config, cwd) { console.log(`[${TASK_ASSISTANT_ID}] resolveTestAction: cwd=${cwd}`); + resetBuildIssues(); const zigPath = await resolveZigExecutable(); if (!zigPath) return null; @@ -1492,11 +1613,14 @@ class ZigTaskAssistant { if (incremental === "on") argv.push("-fincremental"); else if (incremental === "off") argv.push("-fno-incremental"); - return this.createAction(zigPath, argv, cwd); + // Watch mode never exits, so the normalizer never writes the JSON file. + // Keep the matcher-based approach for continuous issue updates. + return this.createAction(zigPath, argv, cwd, { useMatchers: true }); } async resolveCurrentFileRunAction() { console.log(`[${TASK_ASSISTANT_ID}] resolveCurrentFileRunAction`); + resetBuildIssues(); const zigPath = await resolveZigExecutable(); if (!zigPath) return null; -- cgit v1.3