aboutsummaryrefslogtreecommitdiff
path: root/Scripts/main.js
diff options
context:
space:
mode:
authorDavid Czihak <git@dcz.at>2026-05-07 14:33:19 +0200
committerDavid Czihak <git@dcz.at>2026-05-07 14:33:19 +0200
commitddf2de739068b5ff0866ccb1d067f3cb53a4fc55 (patch)
tree1a77efe9d73a6172be3c37d29b321eadd4efe379 /Scripts/main.js
Initial commitv0.1.7
Diffstat (limited to 'Scripts/main.js')
-rw-r--r--Scripts/main.js915
1 files changed, 915 insertions, 0 deletions
diff --git a/Scripts/main.js b/Scripts/main.js
new file mode 100644
index 0000000..76bd816
--- /dev/null
+++ b/Scripts/main.js
@@ -0,0 +1,915 @@
+"use strict";
+
+const EXTENSION_ID = "at.dcz.nova-zig";
+const TASK_ASSISTANT_ID = `${EXTENSION_ID}.tasks`;
+const LANGUAGE_CLIENT_ID = `${EXTENSION_ID}.zls`;
+const ISSUE_MATCHER = "zig.compiler";
+
+const CONFIG_KEYS = {
+ zigPath: `${EXTENSION_ID}.toolchain.zig-path`,
+ zlsPath: `${EXTENSION_ID}.toolchain.zls-path`,
+ lldbDapPath: `${EXTENSION_ID}.toolchain.lldb-dap-path`,
+ zlsEnabled: `${EXTENSION_ID}.zls.enabled`,
+ zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`,
+ zlsDebug: `${EXTENSION_ID}.zls.debug`,
+};
+
+let languageServer = null;
+let taskAssistant = null;
+let issueAssistant = null;
+let commandRegistrations = [];
+
+exports.activate = function activate() {
+ registerCommands();
+ taskAssistant = new ZigTaskAssistant();
+ languageServer = new ZigLanguageServer();
+ issueAssistant = new ZigIssueAssistant();
+};
+
+exports.deactivate = function deactivate() {
+ if (languageServer) {
+ languageServer.dispose();
+ languageServer = null;
+ }
+
+ if (taskAssistant) {
+ taskAssistant.dispose();
+ taskAssistant = null;
+ }
+
+ if (issueAssistant) {
+ issueAssistant.dispose();
+ issueAssistant = null;
+ }
+
+ commandRegistrations.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ disposable.dispose();
+ }
+ });
+ commandRegistrations = [];
+};
+
+function getConfigValue(key) {
+ const workspaceValue = nova.workspace.config.get(key);
+ if (workspaceValue !== undefined && workspaceValue !== null && workspaceValue !== "") {
+ return workspaceValue;
+ }
+
+ const globalValue = nova.config.get(key);
+ if (globalValue !== undefined && globalValue !== null && globalValue !== "") {
+ return globalValue;
+ }
+
+ return null;
+}
+
+function getBooleanConfigValue(key, fallback) {
+ const workspaceValue = nova.workspace.config.get(key);
+ if (typeof workspaceValue === "boolean") {
+ return workspaceValue;
+ }
+
+ const globalValue = nova.config.get(key);
+ if (typeof globalValue === "boolean") {
+ return globalValue;
+ }
+
+ return fallback;
+}
+
+function normalizeArray(value) {
+ if (!Array.isArray(value)) {
+ return [];
+ }
+
+ return value
+ .map((entry) => (entry === null || entry === undefined ? "" : String(entry).trim()))
+ .filter((entry) => entry.length > 0);
+}
+
+function resolveWorkspaceRelativePath(path) {
+ if (!path) {
+ return null;
+ }
+
+ if (path.startsWith("/")) {
+ return path;
+ }
+
+ if (nova.workspace.path) {
+ return nova.path.join(nova.workspace.path, path);
+ }
+
+ return path;
+}
+
+function resolvePathAgainstBase(path, base) {
+ if (!path) {
+ return null;
+ }
+
+ if (path.startsWith("/")) {
+ return path;
+ }
+
+ if (base) {
+ return nova.path.join(base, path);
+ }
+
+ return resolveWorkspaceRelativePath(path);
+}
+
+function getTaskConfigValue(config, key) {
+ if (!config) {
+ return null;
+ }
+
+ const value = config.get(key);
+ return value === undefined || value === null || value === "" ? null : value;
+}
+
+function getTaskArgs(config, key) {
+ return normalizeArray(getTaskConfigValue(config, key));
+}
+
+function getTaskCwd(config) {
+ const configured = getTaskConfigValue(config, "cwd");
+ if (configured) {
+ return resolveWorkspaceRelativePath(configured);
+ }
+
+ return nova.workspace.path || null;
+}
+
+function activeZigFilePath() {
+ const editor = nova.workspace.activeTextEditor;
+ if (!editor || !editor.document || !editor.document.path) {
+ return null;
+ }
+
+ if (editor.document.syntax !== "zig") {
+ return null;
+ }
+
+ return editor.document.path;
+}
+
+function activeZigFileDirectory() {
+ const filePath = activeZigFilePath();
+ if (!filePath) {
+ return null;
+ }
+
+ return nova.path.dirname(filePath);
+}
+
+function localizedText(key, fallback, variables) {
+ let text = nova.localize(key, fallback);
+
+ if (!variables || typeof variables !== "object") {
+ return text;
+ }
+
+ for (const [name, value] of Object.entries(variables)) {
+ text = text.split(`{${name}}`).join(String(value));
+ }
+
+ return text;
+}
+
+function runProcess(command, options) {
+ return new Promise((resolve) => {
+ const stdout = [];
+ const stderr = [];
+ const process = new Process(command, options || {});
+
+ process.onStdout((line) => stdout.push(line));
+ process.onStderr((line) => stderr.push(line));
+ process.onDidExit((status) => {
+ resolve({
+ status,
+ stdout: stdout.join(""),
+ stderr: stderr.join(""),
+ });
+ });
+
+ process.start();
+ });
+}
+
+async function findExecutableOnPath(commandName) {
+ const result = await runProcess("/usr/bin/env", {
+ args: ["which", commandName],
+ });
+
+ if (result.status !== 0) {
+ return null;
+ }
+
+ const path = result.stdout.trim();
+ return path.length > 0 ? path : null;
+}
+
+async function resolveExecutable(configKey, defaultCommand) {
+ const configuredPath = getConfigValue(configKey);
+ if (configuredPath) {
+ return configuredPath;
+ }
+
+ return findExecutableOnPath(defaultCommand);
+}
+
+async function findExecutableWithXcode(commandName) {
+ const result = await runProcess("/usr/bin/xcrun", {
+ args: ["--find", commandName],
+ });
+
+ if (result.status !== 0) {
+ return null;
+ }
+
+ const path = result.stdout.trim();
+ return path.length > 0 ? path : null;
+}
+
+async function resolveLLDBDAPExecutable() {
+ const configuredPath = getConfigValue(CONFIG_KEYS.lldbDapPath);
+ if (configuredPath) {
+ return configuredPath;
+ }
+
+ const xcodePath = await findExecutableWithXcode("lldb-dap");
+ if (xcodePath) {
+ return xcodePath;
+ }
+
+ return findExecutableOnPath("lldb-dap");
+}
+
+function executableDisplayName(path, fallback) {
+ return path || fallback;
+}
+
+function lldbFrameworkPaths() {
+ return [
+ "/Applications/Xcode-beta.app/Contents/SharedFrameworks/",
+ "/Applications/Xcode.app/Contents/SharedFrameworks/",
+ "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/",
+ ];
+}
+
+function lldbDapProxyPath() {
+ return nova.path.join(nova.extension.path, "Scripts", "lldb-dap-proxy.pl");
+}
+
+function debugAdapterLogPath() {
+ return "/tmp/zig-lldb-dap-proxy.log";
+}
+
+function issueNormalizerScriptPath() {
+ return nova.path.join(nova.extension.path, "Scripts", "normalize-zig-issues.pl");
+}
+
+function quoteShellArgument(value) {
+ const text = value === null || value === undefined ? "" : String(value);
+ return `'${text.replace(/'/g, `'\\''`)}'`;
+}
+
+function escapeAppleScriptString(value) {
+ return String(value)
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"');
+}
+
+function buildShellCommand(command, args, cwd) {
+ const segments = [];
+ if (cwd) {
+ segments.push(`cd ${quoteShellArgument(cwd)}`);
+ }
+
+ segments.push([quoteShellArgument(command), ...(args || []).map(quoteShellArgument)].join(" "));
+ return segments.join("; ");
+}
+
+function buildIssueNormalizedCommand(command, args) {
+ const commandLine = [
+ quoteShellArgument(command),
+ ...(args || []).map(quoteShellArgument),
+ ].join(" ");
+ const rewriter = `/usr/bin/perl ${quoteShellArgument(issueNormalizerScriptPath())}`;
+ return `setopt pipefail; ${commandLine} 2>&1 | ${rewriter}`;
+}
+
+function launchInTerminal(commandLine) {
+ return new Promise((resolve) => {
+ const script = `tell application "Terminal"
+activate
+do script "${escapeAppleScriptString(commandLine)}"
+end tell`;
+ const process = new Process("/usr/bin/osascript", {
+ args: ["-e", script],
+ });
+
+ let stderr = "";
+ process.onStderr((line) => {
+ stderr += line;
+ });
+ process.onDidExit((status) => {
+ resolve({ status, stderr: stderr.trim() });
+ });
+ process.start();
+ });
+}
+
+function registerCommands() {
+ commandRegistrations.push(
+ nova.commands.register(`${EXTENSION_ID}.runInTerminal`, async (workspace, payload) => {
+ const command = payload && payload.command;
+ const args = (payload && payload.args) || [];
+ const cwd = (payload && payload.cwd) || workspace.path || null;
+
+ if (!command) {
+ workspace.showWarningMessage(
+ localizedText(
+ "warning.terminal.launch_failed",
+ "Unable to launch the Zig task in Terminal."
+ )
+ );
+ return;
+ }
+
+ const result = await launchInTerminal(buildShellCommand(command, args, cwd));
+ if (result.status !== 0) {
+ const prefix = localizedText(
+ "warning.terminal.open_failed",
+ "Unable to open Terminal for the Zig task."
+ );
+ const suffix = result.stderr ? ` ${result.stderr}` : "";
+ workspace.showWarningMessage(`${prefix}${suffix}`);
+ }
+ })
+ );
+}
+
+function syncWorkspaceZlsConfiguration(settings) {
+ const bridge = {
+ "zls.zig_exe_path": settings.zig_exe_path,
+ "zls.enable_build_on_save": settings.enable_build_on_save,
+ };
+
+ Object.entries(bridge).forEach(([key, value]) => {
+ if (value === undefined || value === null || value === "") {
+ nova.workspace.config.remove(key);
+ } else {
+ nova.workspace.config.set(key, value);
+ }
+ });
+}
+
+class ZigLanguageServer {
+ constructor() {
+ this.client = null;
+ this.clientStopDisposable = null;
+ this.restartGeneration = 0;
+ this.warnedMissing = new Set();
+ this.disposables = [];
+
+ this.observeConfig(CONFIG_KEYS.zigPath, true);
+ this.observeConfig(CONFIG_KEYS.zlsPath, true);
+ this.observeConfig(CONFIG_KEYS.zlsEnabled, true);
+ this.observeConfig(CONFIG_KEYS.zlsBuildOnSave, true);
+ this.observeConfig(CONFIG_KEYS.zlsDebug, true);
+ this.disposables.push(
+ nova.workspace.onDidChangePath(() => {
+ this.start();
+ })
+ );
+
+ this.start();
+ }
+
+ observeConfig(key, restart) {
+ this.disposables.push(
+ nova.config.onDidChange(key, () => {
+ if (restart) {
+ this.start();
+ } else {
+ void this.pushConfiguration();
+ }
+ })
+ );
+ this.disposables.push(
+ nova.workspace.config.onDidChange(key, () => {
+ if (restart) {
+ this.start();
+ } else {
+ void this.pushConfiguration();
+ }
+ })
+ );
+ }
+
+ dispose() {
+ this.stop();
+ this.disposables.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ disposable.dispose();
+ }
+ });
+ this.disposables = [];
+ }
+
+ async start() {
+ // Guard against stale async continuations: if a config change triggers
+ // another start() while we are awaiting, the generation check below bails out.
+ const generation = ++this.restartGeneration;
+ this.stop();
+
+ if (!getBooleanConfigValue(CONFIG_KEYS.zlsEnabled, true)) {
+ return;
+ }
+
+ const zlsPath = await resolveExecutable(CONFIG_KEYS.zlsPath, "zls");
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+
+ if (!zlsPath) {
+ this.warnMissingTool(
+ "zls",
+ localizedText(
+ "warning.zls.not_found",
+ "ZLS was not found. Install it or set a ZLS executable path in Zig extension settings."
+ )
+ );
+ return;
+ }
+
+ const { settings, zigPath } = await this.resolveSettings();
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+
+ syncWorkspaceZlsConfiguration(settings);
+
+ const debugLogs = getBooleanConfigValue(CONFIG_KEYS.zlsDebug, false);
+
+ const serverOptions = {
+ path: zlsPath,
+ args: debugLogs ? [] : ["--disable-lsp-logs"],
+ };
+
+ const clientOptions = {
+ syntaxes: [{ syntax: "zig", languageId: "zig" }],
+ debug: debugLogs,
+ initializationOptions: {
+ zls: settings,
+ },
+ };
+
+ const client = new LanguageClient(
+ LANGUAGE_CLIENT_ID,
+ localizedText("name.language_server", "Zig Language Server"),
+ serverOptions,
+ clientOptions
+ );
+
+ this.clientStopDisposable = client.onDidStop((error) => {
+ if (error) {
+ console.error(`[${LANGUAGE_CLIENT_ID}] ${error.message}`);
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zls.stopped_unexpectedly",
+ "The Zig language server stopped unexpectedly ({executable}).",
+ { executable: executableDisplayName(zlsPath, "zls") }
+ )
+ );
+ }
+ });
+
+ try {
+ client.start();
+ this.client = client;
+ nova.subscriptions.add(client);
+ void this.pushConfiguration();
+ this.warnedMissing.delete("zls");
+ if (zigPath) {
+ this.warnedMissing.delete("zig");
+ }
+ } catch (error) {
+ console.error(`[${LANGUAGE_CLIENT_ID}] Failed to start ZLS`, error);
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zls.start_failed",
+ "Unable to start the Zig language server at {path}.",
+ { path: zlsPath }
+ )
+ );
+ this.stop();
+ }
+ }
+
+ async resolveSettings() {
+ const settings = {
+ enable_build_on_save: getBooleanConfigValue(CONFIG_KEYS.zlsBuildOnSave, false),
+ };
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (zigPath) {
+ settings.zig_exe_path = zigPath;
+ }
+
+ return { settings, zigPath };
+ }
+
+ async pushConfiguration() {
+ const generation = this.restartGeneration;
+ const { settings } = await this.resolveSettings();
+ if (generation !== this.restartGeneration || !this.client || !this.client.running) {
+ return;
+ }
+
+ syncWorkspaceZlsConfiguration(settings);
+
+ this.client.sendNotification("workspace/didChangeConfiguration", {
+ settings: {
+ zls: settings,
+ },
+ });
+ }
+
+ stop() {
+ if (this.clientStopDisposable && typeof this.clientStopDisposable.dispose === "function") {
+ this.clientStopDisposable.dispose();
+ this.clientStopDisposable = null;
+ }
+
+ if (this.client) {
+ this.client.stop();
+ nova.subscriptions.remove(this.client);
+ this.client = null;
+ }
+ }
+
+ warnMissingTool(tool, message) {
+ if (this.warnedMissing.has(tool)) {
+ return;
+ }
+
+ this.warnedMissing.add(tool);
+ nova.workspace.showWarningMessage(message);
+ }
+}
+
+class ZigTaskAssistant {
+ constructor() {
+ this.disposable = nova.assistants.registerTaskAssistant(this, {
+ identifier: TASK_ASSISTANT_ID,
+ name: localizedText("name.extension", "Zig"),
+ });
+ }
+
+ dispose() {
+ if (this.disposable && typeof this.disposable.dispose === "function") {
+ this.disposable.dispose();
+ this.disposable = null;
+ }
+ }
+
+ provideTasks() {
+ const task = new Task(localizedText("task.current_file.name", "Current Zig File"));
+ task.setAction(Task.Run, new TaskResolvableAction({
+ data: {
+ type: "current-file-run",
+ },
+ }));
+ task.setAction(Task.Clean, new TaskResolvableAction({
+ data: {
+ type: "current-file-clean",
+ },
+ }));
+ return [task];
+ }
+
+ async resolveTaskAction(context) {
+ const type = context.data && context.data.type;
+ const config = context.config;
+ const cwd = getTaskCwd(config);
+
+ if (type === "clean") {
+ return this.resolveCleanAction(cwd);
+ }
+
+ switch (type) {
+ case "build":
+ return this.resolveBuildAction(config, cwd);
+ case "build-debug":
+ return this.resolveDebugBuildAction(config, cwd);
+ case "build-run":
+ return this.resolveBuildRunAction(config, cwd);
+ case "build-run-terminal":
+ return this.resolveBuildRunTerminalAction(config, cwd);
+ case "build-test":
+ return this.resolveBuildTestAction(config, cwd);
+ case "current-file-run":
+ return this.resolveCurrentFileRunAction();
+ case "current-file-clean":
+ return this.resolveCurrentFileCleanAction();
+ case "debug":
+ return this.resolveDebugAction(config, cwd);
+ case "file-test":
+ return this.resolveFileTestAction(config, cwd);
+ default:
+ return null;
+ }
+ }
+
+ createAction(command, args, cwd) {
+ return new TaskProcessAction("/bin/zsh", {
+ args: ["-lc", buildIssueNormalizedCommand(command, args)],
+ cwd,
+ env: {
+ NOVA_ZIG_TASK_CWD: cwd || "",
+ },
+ matchers: [ISSUE_MATCHER],
+ });
+ }
+
+ resolveCleanAction(cwd) {
+ if (!cwd) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.clean.missing_cwd",
+ "Choose a workspace or working directory before cleaning Zig build artifacts."
+ )
+ );
+ return null;
+ }
+
+ return new TaskProcessAction("/bin/rm", {
+ args: ["-rf", ".zig-cache", "zig-cache", "zig-out"],
+ cwd,
+ });
+ }
+
+ async resolveBuildAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ return this.createAction(zigPath, ["build", ...getTaskArgs(config, "buildArgs")], cwd);
+ }
+
+ async resolveDebugBuildAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ return this.createAction(zigPath, ["build", "-Doptimize=Debug", ...getTaskArgs(config, "buildArgs")], cwd);
+ }
+
+ async resolveBuildRunAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const step =
+ getTaskConfigValue(config, "runStep") || getTaskConfigValue(config, "step") || "run";
+ const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
+ const runArgs = getTaskArgs(config, "runArgs");
+
+ if (runArgs.length > 0) {
+ args.push("--", ...runArgs);
+ }
+
+ return this.createAction(zigPath, args, cwd);
+ }
+
+ async resolveBuildRunTerminalAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const step =
+ getTaskConfigValue(config, "runStep") || getTaskConfigValue(config, "step") || "run";
+ const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
+ const runArgs = getTaskArgs(config, "runArgs");
+
+ if (runArgs.length > 0) {
+ args.push("--", ...runArgs);
+ }
+
+ return new TaskCommandAction(`${EXTENSION_ID}.runInTerminal`, {
+ args: [
+ {
+ command: zigPath,
+ args,
+ cwd,
+ },
+ ],
+ });
+ }
+
+ async resolveBuildTestAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const step =
+ getTaskConfigValue(config, "testStep") || getTaskConfigValue(config, "step") || "test";
+ const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
+ const testArgs = getTaskArgs(config, "testArgs");
+
+ if (testArgs.length > 0) {
+ args.push("--", ...testArgs);
+ }
+
+ return this.createAction(zigPath, args, cwd);
+ }
+
+ async resolveCurrentFileRunAction() {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const filePath = activeZigFilePath();
+ const cwd = filePath ? nova.path.dirname(filePath) : null;
+ if (!filePath || !cwd) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.current_file.focus_editor_for_run",
+ "Focus a Zig editor before running Current Zig File."
+ )
+ );
+ return null;
+ }
+
+ return this.createAction(zigPath, ["run", filePath], cwd);
+ }
+
+ resolveCurrentFileCleanAction() {
+ const cwd = activeZigFileDirectory();
+ if (!cwd) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.current_file.focus_editor_for_clean",
+ "Focus a Zig editor before cleaning Current Zig File artifacts."
+ )
+ );
+ return null;
+ }
+
+ return this.resolveCleanAction(cwd);
+ }
+
+ async resolveDebugAction(config, cwd) {
+ const lldbDapPath = await resolveLLDBDAPExecutable();
+ if (!lldbDapPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.lldb_dap.not_found",
+ "lldb-dap was not found. Install Xcode Command Line Tools or set an LLDB DAP executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const configuredProgramPath = getTaskConfigValue(config, "programPath");
+ const programPath = resolvePathAgainstBase(configuredProgramPath, cwd);
+ if (!programPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.debug.choose_program",
+ "Choose a program path before running Zig Debug."
+ )
+ );
+ return null;
+ }
+
+ const consoleMode = getTaskConfigValue(config, "console") || "internalConsole";
+ const stopOnEntry = Boolean(config && config.get("stopOnEntry"));
+
+ const action = new TaskDebugAdapterAction("zig-lldb-dap");
+ action.transport = "stdio";
+ action.command = "/usr/bin/perl";
+ action.args = [lldbDapProxyPath(), lldbDapPath, debugAdapterLogPath()];
+ action.debugRequest = "launch";
+ action.env = {
+ DYLD_FRAMEWORK_PATH: lldbFrameworkPaths().join(":"),
+ NOVA_ZIG_LLDB_DAP_PATH: lldbDapPath,
+ NOVA_ZIG_DEBUG_LOG: debugAdapterLogPath(),
+ };
+ action.debugArgs = {
+ program: programPath,
+ cwd,
+ args: getTaskArgs(config, "runArgs"),
+ stopOnEntry,
+ };
+
+ if (consoleMode !== "internalConsole") {
+ action.debugArgs.console = consoleMode;
+ }
+
+ return action;
+ }
+
+ async resolveFileTestAction(config, cwd) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.zig.not_found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
+ )
+ );
+ return null;
+ }
+
+ const configuredPath = getTaskConfigValue(config, "filePath");
+ const filePath = configuredPath
+ ? resolveWorkspaceRelativePath(configuredPath)
+ : activeZigFilePath();
+
+ if (!filePath) {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.file_test.choose_file",
+ "Choose a Zig file for this task or focus a Zig editor before running Zig File Test."
+ )
+ );
+ return null;
+ }
+
+ return this.createAction(zigPath, ["test", filePath, ...getTaskArgs(config, "zigArgs")], cwd);
+ }
+}
+
+class ZigIssueAssistant {
+ constructor() {
+ this.disposable = nova.assistants.registerIssueAssistant(
+ [{ syntax: "zig" }, { syntax: "zig-package" }],
+ this,
+ { event: "onChange" }
+ );
+ }
+
+ dispose() {
+ if (this.disposable && typeof this.disposable.dispose === "function") {
+ this.disposable.dispose();
+ this.disposable = null;
+ }
+ }
+
+ provideIssues(editor) {
+ if (!editor || !editor.document) {
+ return [];
+ }
+
+ // Nova's LanguageClient already owns core LSP publishDiagnostics handling.
+ // Registering an issue assistant here tells Nova that Zig supports live
+ // checking, so the Problems UI doesn't show a misleading empty-state banner.
+ return [];
+ }
+}