aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md12
-rw-r--r--README.md23
-rw-r--r--Scripts/main.js480
-rw-r--r--de.lproj/strings.json57
-rw-r--r--extension.json304
5 files changed, 736 insertions, 140 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef55853..3786150 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## Unreleased
+
+- Tasks now expose first-class config for `-Doptimize`, `-Dtarget`, and custom `-D<key>=<value>` user options on every template that takes them.
+- Added **Zig Test** template (`zig build test` with `--test-filter` and `--summary`).
+- Added **Zig Watch** template (`zig build --watch`, with `--debounce` and `-fincremental` controls). Note: Nova issue matchers only fire on the first build cycle.
+- **Zig Package** gained a Console setting (Internal / External Terminal). The standalone *Zig Package (macOS Terminal)* template has been removed.
+- Dynamic per-step tasks: each step from `zig build --list-steps` appears as `Zig Build: <step>` in the task list. Cached and refreshed on `build.zig` / `build.zig.zon` changes.
+- **Zig Debug** auto-detects `programPath` from `build.zig.zon`'s `.name` and the `zig-out/bin/<name>` location when the field is left blank.
+- The Run Step default flips from `run` to empty — `zig build` then runs its own default install step. Clear and re-set the field on existing tasks to use the new behavior.
+- Clean now refuses to run when the working directory is `/`, `$HOME`, or outside the workspace, and runs `zig build uninstall` first when the project exposes that step.
+- "Current Zig File" Clean walks up to the nearest `build.zig` instead of cleaning the file's directory.
+- Issue assistant no longer registers for the non-existent `zig-package` syntax.
## 0.1.8 — 2026-05-07
- Added a setting to write an LLDB proxy log file for debugging the debug adapter.
diff --git a/README.md b/README.md
index 0f816bd..118ffb3 100644
--- a/README.md
+++ b/README.md
@@ -28,10 +28,25 @@ This extension is not endorsed by, affiliated with, or associated with the [Zig
### Tasks
-- **Zig Package**: build and run a Zig package in the Nova console
-- **Zig Package (macOS Terminal)**: build and run in the external Terminal app for interactive CLIs
-- **Zig Debug**: build in Debug mode and launch under the debugger, with configurable console and stop-on-entry
-- A clean action on every runnable template that removes `.zig-cache`, `zig-cache`, and `zig-out`
+Four templates expose the `zig build` surface — pick one in **Project Settings > Tasks > New Task**:
+
+- **Zig Package**: build and run a Zig package. Configurable optimize mode, target triple, custom `-D` user options, run step, build/program arguments, and console (Internal Console or External Terminal).
+- **Zig Debug**: build and launch under lldb-dap. Same flag surface as Zig Package, plus program path (auto-detected from `build.zig.zon` when blank), console, and stop-on-entry.
+- **Zig Test**: run `zig build test` with optional `--test-filter`, `--summary`, plus optimize/target/user-options.
+- **Zig Watch**: run `zig build --watch` with configurable step, debounce, and incremental flag.
+
+Every runnable template also exposes a clean action that removes `.zig-cache`, `zig-cache`, and `zig-out`. Clean refuses to run when the working directory resolves to `/`, `$HOME`, or anywhere outside the workspace; when the project's `build.zig` exposes an `uninstall` step, it runs first.
+
+Two ad-hoc tasks appear automatically:
+
+- **Current Zig File**: run via `zig run <file>` and clean the nearest `build.zig` ancestor.
+- **Zig Build: \<step\>**: one task per step discovered from `zig build --list-steps`. The list is cached and refreshed when `build.zig` or `build.zig.zon` changes.
+
+The legacy **Zig Package (macOS Terminal)** template is preserved for backward compatibility. New configurations should use **Zig Package** with Console set to External Terminal.
+
+#### Watch-mode caveat
+
+Nova issue matchers fire only on the first build cycle of a long-running task. With `zig build --watch`, errors from later cycles will not appear inline — re-run the task to refresh the issue overlay.
### Debugging
diff --git a/Scripts/main.js b/Scripts/main.js
index 6f0a54e..70af784 100644
--- a/Scripts/main.js
+++ b/Scripts/main.js
@@ -4,6 +4,8 @@ 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 USER_OPTION_REGEX = /^[A-Za-z_][A-Za-z0-9_-]*(=.*)?$/;
+const STEP_CACHE_TTL_MS = 5 * 60 * 1000;
const CONFIG_KEYS = {
zigPath: `${EXTENSION_ID}.toolchain.zig-path`,
@@ -13,6 +15,7 @@ const CONFIG_KEYS = {
zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`,
zlsDebug: `${EXTENSION_ID}.zls.debug`,
lldbDapDebug: `${EXTENSION_ID}.debug-adapter.debug`,
+ discoverSteps: `${EXTENSION_ID}.tasks.discover-steps`,
};
let languageServer = null;
@@ -143,6 +146,229 @@ function getTaskCwd(config) {
return nova.workspace.path || null;
}
+// Returns the configured step string, or null if the step should be omitted.
+// An explicit empty string means "no step argument" — `zig build` then runs the
+// default install step (Ziglings-style projects rely on this). Missing/null
+// returns the caller-supplied fallback.
+function resolveStep(config, key, fallback) {
+ const raw = config ? config.get(key) : undefined;
+ if (raw === undefined || raw === null) {
+ return fallback === undefined ? null : fallback;
+ }
+ const trimmed = String(raw).trim();
+ return trimmed.length > 0 ? trimmed : null;
+}
+
+// Backwards-compatible wrapper: zigBuildRun's `runStep` field. The legacy
+// `step` key is still honored for users on pre-0.1 task configs.
+function resolveRunStep(config) {
+ const raw = config ? config.get("runStep") : undefined;
+ if (raw === undefined || raw === null) {
+ const legacy = getTaskConfigValue(config, "step");
+ return legacy ? legacy : null;
+ }
+ const trimmed = String(raw).trim();
+ return trimmed.length > 0 ? trimmed : null;
+}
+
+// Builds the `zig build` argv prefix shared by Build/Run/Test/Watch resolvers.
+// Order: ["build", -Doptimize, -Dtarget, -D<userOptions>, ...buildArgs, step?].
+// `runArgs` are the caller's responsibility — they go after `--`.
+function buildZigArgv(config, options) {
+ const opts = options || {};
+ const argv = ["build"];
+
+ const optimize = getTaskConfigValue(config, "optimize") || opts.defaultOptimize || null;
+ if (optimize) argv.push(`-Doptimize=${optimize}`);
+
+ const target = getTaskConfigValue(config, "target");
+ if (target) argv.push(`-Dtarget=${target}`);
+
+ for (const entry of getTaskArgs(config, "userOptions")) {
+ if (USER_OPTION_REGEX.test(entry)) {
+ argv.push(`-D${entry}`);
+ } else {
+ console.warn(`[zig-task] Skipping invalid userOptions entry: ${entry}`);
+ }
+ }
+
+ argv.push(...getTaskArgs(config, "buildArgs"));
+
+ if (opts.step) argv.push(opts.step);
+ return argv;
+}
+
+// Refuses to clean unless `cwd` is non-root, not $HOME, and inside the workspace.
+// Returns the array of cache directories to remove, or null after warning the user.
+function safeCleanPaths(cwd) {
+ const showWarning = () => {
+ nova.workspace.showWarningMessage(
+ localizedText(
+ "warning.clean.unsafe_cwd",
+ "Refusing to clean: the working directory must be inside this workspace."
+ )
+ );
+ };
+
+ if (!cwd || typeof cwd !== "string" || !cwd.startsWith("/")) {
+ showWarning();
+ return null;
+ }
+
+ const normalized = nova.path.normalize(cwd);
+ if (normalized === "/" || normalized === "") {
+ showWarning();
+ return null;
+ }
+
+ const home = nova.environment ? nova.environment.HOME : null;
+ if (home && (normalized === home || normalized === nova.path.normalize(home))) {
+ showWarning();
+ return null;
+ }
+
+ const workspacePath = nova.workspace.path;
+ if (!workspacePath) {
+ showWarning();
+ return null;
+ }
+
+ const workspaceNormalized = nova.path.normalize(workspacePath);
+ if (normalized !== workspaceNormalized && !normalized.startsWith(workspaceNormalized + "/")) {
+ showWarning();
+ return null;
+ }
+
+ return [".zig-cache", "zig-cache", "zig-out"];
+}
+
+// Walks up from `startDir` to the nearest ancestor containing `build.zig`,
+// stopping at the workspace root. Returns the workspace root if no build.zig
+// is found above startDir, or null if no workspace is open.
+function nearestBuildZigDir(startDir) {
+ const workspacePath = nova.workspace.path
+ ? nova.path.normalize(nova.workspace.path)
+ : null;
+
+ if (!startDir) return workspacePath || null;
+
+ let current = nova.path.normalize(startDir);
+ for (let i = 0; i < 64; i++) {
+ if (nova.fs.stat(nova.path.join(current, "build.zig"))) {
+ return current;
+ }
+ if (current === "/" || (workspacePath && current === workspacePath)) {
+ return workspacePath || null;
+ }
+ const parent = nova.path.dirname(current);
+ if (!parent || parent === current) break;
+ current = parent;
+ }
+ return workspacePath || null;
+}
+
+// Best-effort regex extraction of the package name from build.zig.zon. Returns
+// null when the file is missing, unreadable, or doesn't match the expected
+// `\.name = "<name>"` / `\.name = .<ident>` shapes. Used to default
+// `programPath` for the Debug template.
+function parseProjectName(cwd) {
+ if (!cwd) return null;
+ const zonPath = nova.path.join(cwd, "build.zig.zon");
+ if (!nova.fs.stat(zonPath)) return null;
+
+ let content = "";
+ try {
+ const file = nova.fs.open(zonPath, "r");
+ content = file.read() || "";
+ file.close();
+ } catch (_) {
+ return null;
+ }
+ if (typeof content !== "string" || content.length === 0) return null;
+
+ const match = content.match(/\.name\s*=\s*(?:"([^"]+)"|\.([A-Za-z_][\w]*))/);
+ if (!match) return null;
+ return match[1] || match[2] || null;
+}
+
+// Per-cwd cache for `zig build --list-steps`. Invalidated by mtime changes on
+// build.zig / build.zig.zon and a soft 5-minute TTL. `getOrFetch` returns
+// cached steps synchronously and kicks off a background refresh when stale,
+// nudging Nova to reload tasks once the refresh lands.
+const stepCache = {
+ entries: new Map(),
+ pending: new Map(),
+
+ getOrFetch(cwd) {
+ if (!cwd) return null;
+
+ const buildZigPath = nova.path.join(cwd, "build.zig");
+ const buildZigStat = nova.fs.stat(buildZigPath);
+ if (!buildZigStat) return null;
+
+ const buildZonStat = nova.fs.stat(nova.path.join(cwd, "build.zig.zon"));
+ const buildZigMtimeMs = buildZigStat.mtime ? buildZigStat.mtime.getTime() : 0;
+ const buildZonMtimeMs = buildZonStat && buildZonStat.mtime ? buildZonStat.mtime.getTime() : 0;
+
+ const cached = this.entries.get(cwd);
+ const fresh =
+ cached &&
+ cached.buildZigMtimeMs === buildZigMtimeMs &&
+ cached.buildZonMtimeMs === buildZonMtimeMs &&
+ Date.now() - cached.fetchedAt < STEP_CACHE_TTL_MS;
+
+ if (fresh) return cached.steps;
+
+ if (!this.pending.has(cwd)) {
+ const promise = this.fetch(cwd, buildZigMtimeMs, buildZonMtimeMs).finally(() => {
+ this.pending.delete(cwd);
+ if (typeof nova.workspace.reloadTasks === "function") {
+ try {
+ nova.workspace.reloadTasks(TASK_ASSISTANT_ID);
+ } catch (_) {}
+ }
+ });
+ this.pending.set(cwd, promise);
+ }
+
+ return cached ? cached.steps : null;
+ },
+
+ async fetch(cwd, buildZigMtimeMs, buildZonMtimeMs) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) return null;
+
+ const result = await runProcess(zigPath, {
+ args: ["build", "--list-steps"],
+ cwd,
+ });
+ if (result.status !== 0) return null;
+
+ const steps = result.stdout
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+ .map((line) => line.split(/\s+/, 1)[0])
+ .filter((name) => /^[A-Za-z_][\w-]*$/.test(name));
+
+ this.entries.set(cwd, {
+ steps,
+ buildZigMtimeMs,
+ buildZonMtimeMs,
+ fetchedAt: Date.now(),
+ });
+ return steps;
+ },
+
+ invalidate(cwd) {
+ if (cwd) {
+ this.entries.delete(cwd);
+ } else {
+ this.entries.clear();
+ }
+ },
+};
+
function activeZigFilePath() {
const editor = nova.workspace.activeTextEditor;
if (!editor || !editor.document || !editor.document.path) {
@@ -221,6 +447,22 @@ async function resolveExecutable(configKey, defaultCommand) {
return findExecutableOnPath(defaultCommand);
}
+// Resolves the Zig executable, surfacing a single localized warning when it's
+// missing. Returns null when no path is found — callers must short-circuit.
+async function requireZig() {
+ 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 zigPath;
+}
+
async function findExecutableWithXcode(commandName) {
const result = await runProcess("/usr/bin/xcrun", {
args: ["--find", commandName],
@@ -593,18 +835,41 @@ class ZigTaskAssistant {
}
provideTasks() {
- const task = new Task(localizedText("task.current_file.name", "Current Zig File"));
- task.setAction(Task.Run, new TaskResolvableAction({
+ const tasks = [];
+
+ const currentFile = new Task(localizedText("task.current_file.name", "Current Zig File"));
+ currentFile.setAction(Task.Run, new TaskResolvableAction({
data: {
type: "current-file-run",
},
}));
- task.setAction(Task.Clean, new TaskResolvableAction({
+ currentFile.setAction(Task.Clean, new TaskResolvableAction({
data: {
type: "current-file-clean",
},
}));
- return [task];
+ tasks.push(currentFile);
+
+ const workspacePath = nova.workspace.path;
+ if (workspacePath && getBooleanConfigValue(CONFIG_KEYS.discoverSteps, true)) {
+ const steps = stepCache.getOrFetch(workspacePath);
+ if (steps && steps.length > 0) {
+ for (const step of steps) {
+ const task = new Task(
+ localizedText("task.build_step.name", "Zig Build: {step}", { step })
+ );
+ task.setAction(Task.Run, new TaskResolvableAction({
+ data: {
+ type: "build-step",
+ step,
+ },
+ }));
+ tasks.push(task);
+ }
+ }
+ }
+
+ return tasks;
}
async resolveTaskAction(context) {
@@ -623,14 +888,19 @@ class ZigTaskAssistant {
return this.resolveDebugBuildAction(config, cwd);
case "build-run":
return this.resolveBuildRunAction(config, cwd);
- case "build-run-terminal":
- return this.resolveBuildRunTerminalAction(config, cwd);
+ case "build-step":
+ return this.resolveBuildStepAction(context.data && context.data.step);
case "current-file-run":
return this.resolveCurrentFileRunAction();
case "current-file-clean":
return this.resolveCurrentFileCleanAction();
case "debug":
return this.resolveDebugAction(config, cwd);
+ case "test-build":
+ case "test-run":
+ return this.resolveTestAction(config, cwd);
+ case "watch":
+ return this.resolveWatchAction(config, cwd);
default:
return null;
}
@@ -647,7 +917,7 @@ class ZigTaskAssistant {
});
}
- resolveCleanAction(cwd) {
+ async resolveCleanAction(cwd) {
if (!cwd) {
nova.workspace.showWarningMessage(
localizedText(
@@ -658,109 +928,129 @@ class ZigTaskAssistant {
return null;
}
- return new TaskProcessAction("/bin/rm", {
- args: ["-rf", ".zig-cache", "zig-cache", "zig-out"],
- cwd,
- });
+ const paths = safeCleanPaths(cwd);
+ if (!paths) return null;
+
+ // If the project exposes an `uninstall` step, prefer running it before
+ // wiping the cache directories. Use only what's already cached — don't
+ // block clean on a fresh `--list-steps` invocation.
+ const cached = stepCache.entries.get(cwd);
+ const args = ["-rf", ...paths];
+
+ if (cached && Array.isArray(cached.steps) && cached.steps.includes("uninstall")) {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (zigPath) {
+ const command = `${quoteShellArgument(zigPath)} build uninstall; /bin/rm ${args
+ .map(quoteShellArgument)
+ .join(" ")}`;
+ return new TaskProcessAction("/bin/zsh", {
+ args: ["-lc", command],
+ cwd,
+ matchers: [ISSUE_MATCHER],
+ });
+ }
+ }
+
+ return new TaskProcessAction("/bin/rm", { args, 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;
- }
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
- return this.createAction(zigPath, ["build", ...getTaskArgs(config, "buildArgs")], cwd);
+ return this.createAction(zigPath, buildZigArgv(config), 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;
- }
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
- return this.createAction(zigPath, ["build", "-Doptimize=Debug", ...getTaskArgs(config, "buildArgs")], cwd);
+ return this.createAction(
+ zigPath,
+ buildZigArgv(config, { defaultOptimize: "Debug" }),
+ 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;
- }
+ async resolveBuildRunAction(config, cwd, forceConsole) {
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
- const step =
- getTaskConfigValue(config, "runStep") || getTaskConfigValue(config, "step") || "run";
- const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
+ const step = resolveRunStep(config);
+ const argv = buildZigArgv(config, { step });
const runArgs = getTaskArgs(config, "runArgs");
+ if (runArgs.length > 0) argv.push("--", ...runArgs);
+
+ const consoleMode =
+ forceConsole || getTaskConfigValue(config, "console") || "internalConsole";
- if (runArgs.length > 0) {
- args.push("--", ...runArgs);
+ if (consoleMode === "externalTerminal") {
+ return new TaskCommandAction(`${EXTENSION_ID}.runInTerminal`, {
+ args: [
+ {
+ command: zigPath,
+ args: argv,
+ cwd,
+ },
+ ],
+ });
}
- return this.createAction(zigPath, args, cwd);
+ return this.createAction(zigPath, argv, 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;
- }
+ async resolveBuildStepAction(step) {
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
+
+ const cwd = nova.workspace.path || null;
+ if (!cwd || !step || !/^[A-Za-z_][\w-]*$/.test(step)) return null;
+
+ return this.createAction(zigPath, ["build", step], cwd);
+ }
+
+ async resolveTestAction(config, cwd) {
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
+
+ const argv = buildZigArgv(config, { step: "test" });
+
+ const summary = getTaskConfigValue(config, "summary");
+ if (summary) argv.push(`--summary=${summary}`);
+
+ const testFilter = getTaskConfigValue(config, "testFilter");
+ if (testFilter) argv.push("--test-filter", testFilter);
- 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) argv.push("--", ...runArgs);
- if (runArgs.length > 0) {
- args.push("--", ...runArgs);
+ return this.createAction(zigPath, argv, cwd);
+ }
+
+ async resolveWatchAction(config, cwd) {
+ const zigPath = await requireZig();
+ if (!zigPath) return null;
+
+ const step = resolveStep(config, "step", null);
+ const argv = buildZigArgv(config, { step });
+ argv.push("--watch");
+
+ const debounce = getTaskConfigValue(config, "debounceMs");
+ if (debounce !== null && debounce !== undefined && debounce !== "") {
+ const n = Number(debounce);
+ if (Number.isFinite(n) && n >= 0) argv.push("--debounce", String(Math.floor(n)));
}
- return new TaskCommandAction(`${EXTENSION_ID}.runInTerminal`, {
- args: [
- {
- command: zigPath,
- args,
- cwd,
- },
- ],
- });
+ const incremental = getTaskConfigValue(config, "incremental");
+ if (incremental === "on") argv.push("-fincremental");
+ else if (incremental === "off") argv.push("-fno-incremental");
+
+ return this.createAction(zigPath, argv, 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 zigPath = await requireZig();
+ if (!zigPath) return null;
const filePath = activeZigFilePath();
const cwd = filePath ? nova.path.dirname(filePath) : null;
@@ -777,9 +1067,9 @@ class ZigTaskAssistant {
return this.createAction(zigPath, ["run", filePath], cwd);
}
- resolveCurrentFileCleanAction() {
- const cwd = activeZigFileDirectory();
- if (!cwd) {
+ async resolveCurrentFileCleanAction() {
+ const startDir = activeZigFileDirectory();
+ if (!startDir) {
nova.workspace.showWarningMessage(
localizedText(
"warning.current_file.focus_editor_for_clean",
@@ -789,6 +1079,7 @@ class ZigTaskAssistant {
return null;
}
+ const cwd = nearestBuildZigDir(startDir);
return this.resolveCleanAction(cwd);
}
@@ -805,7 +1096,16 @@ class ZigTaskAssistant {
}
const configuredProgramPath = getTaskConfigValue(config, "programPath");
- const programPath = resolvePathAgainstBase(configuredProgramPath, cwd);
+ let programPath = resolvePathAgainstBase(configuredProgramPath, cwd);
+ if (!programPath) {
+ const projectName = parseProjectName(cwd);
+ if (projectName) {
+ const candidate = nova.path.join(cwd, "zig-out", "bin", projectName);
+ if (nova.fs.stat(candidate)) {
+ programPath = candidate;
+ }
+ }
+ }
if (!programPath) {
nova.workspace.showWarningMessage(
localizedText(
@@ -859,7 +1159,7 @@ class ZigTaskAssistant {
class ZigIssueAssistant {
constructor() {
this.disposable = nova.assistants.registerIssueAssistant(
- [{ syntax: "zig" }, { syntax: "zig-package" }],
+ [{ syntax: "zig" }],
this,
{ event: "onChange" }
);
diff --git a/de.lproj/strings.json b/de.lproj/strings.json
index 1834995..217617a 100644
--- a/de.lproj/strings.json
+++ b/de.lproj/strings.json
@@ -29,18 +29,33 @@
"Workspace Root": "Workspace-Wurzel",
"Run Step": "Ausführungsschritt",
"run": "run",
+ "install": "install",
+ "host": "host",
"The `zig build` step to execute for the Run action.": "Der `zig build`-Schritt, der für die Ausführen-Aktion ausgeführt wird.",
+ "The `zig build` step to execute for the Run action. Leave blank to run `zig build` with no step (Zig's default install step).": "Der `zig build`-Schritt für die Ausführen-Aktion. Leer lassen, um `zig build` ohne Schritt auszuführen (Zigs Standardschritt `install`).",
"Build Arguments": "Build-Argumente",
"Additional arguments appended after `zig build` for both Build and Run.": "Zusätzliche Argumente, die nach `zig build` sowohl für Bauen als auch Ausführen angehängt werden.",
+ "Additional arguments appended after `zig build`.": "Zusätzliche Argumente, die nach `zig build` angehängt werden.",
+ "Additional arguments appended after `zig build test`.": "Zusätzliche Argumente, die nach `zig build test` angehängt werden.",
"Program Arguments": "Programmargumente",
"Arguments passed after `--` to the built program.": "Argumente, die nach `--` an das gebaute Programm übergeben werden.",
- "Zig Package (macOS Terminal)": "Zig-Paket (macOS-Terminal)",
- "Build a Zig package and run it in the external macOS Terminal app.": "Ein Zig-Paket bauen und in der externen macOS-Terminal-App ausführen.",
+ "Arguments passed after `--` to the test runner.": "Argumente, die nach `--` an den Test-Runner übergeben werden.",
+ "Optimize": "Optimierung",
+ "Project Default": "Projektstandard",
+ "Passed as `-Doptimize=<mode>`. Leave on Project Default to let `build.zig` decide.": "Wird als `-Doptimize=<mode>` übergeben. Bei „Projektstandard“ entscheidet `build.zig`.",
+ "Passed as `-Doptimize=<mode>`. Debug is the recommended default for stepping through code.": "Wird als `-Doptimize=<mode>` übergeben. „Debug“ ist der empfohlene Standard zum Durchlaufen des Codes.",
+ "Passed as `-Doptimize=<mode>`.": "Wird als `-Doptimize=<mode>` übergeben.",
+ "Target": "Ziel",
+ "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host.": "Cross-Compile-Ziel-Triplet, wird als `-Dtarget=` übergeben. Leer lassen für den Host.",
+ "User Options": "Benutzeroptionen",
+ "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`.": "Eigene `-D`-Flags. Jeder Eintrag ist `key` (boolesches Flag) oder `key=value`.",
+ "Where to run the program. External Terminal launches the macOS Terminal app.": "Wo das Programm ausgeführt werden soll. „Externes Terminal“ startet die macOS-Terminal-App.",
"Zig Debug": "Zig-Debug",
"Build a Zig package in Debug mode and launch it under lldb-dap.": "Ein Zig-Paket im Debug-Modus bauen und unter lldb-dap starten.",
"Program": "Programm",
"zig-out/bin/app": "zig-out/bin/app",
"Path to the executable to debug. Relative paths are resolved against the working directory.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst.",
+ "Path to the executable to debug. Relative paths are resolved against the working directory. Leave blank to auto-detect from `build.zig.zon`.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst. Leer lassen, um aus `build.zig.zon` automatisch zu erkennen.",
"Additional arguments appended after `zig build -Doptimize=Debug`.": "Zusätzliche Argumente, die nach `zig build -Doptimize=Debug` angehängt werden.",
"Arguments passed to the debugged program.": "Argumente, die an das zu debuggende Programm übergeben werden.",
"Console": "Konsole",
@@ -64,5 +79,41 @@
"warning.current_file.focus_editor_for_clean": "Fokussiere einen Zig-Editor, bevor du Artefakte der „Aktuelle Zig-Datei“ bereinigst.",
"warning.lldb_dap.not_found": "lldb-dap wurde nicht gefunden. Installiere die Xcode Command Line Tools oder setze einen LLDB-DAP-Ausführungspfad in den Zig-Erweiterungseinstellungen.",
"warning.debug.choose_program": "Wähle vor dem Ausführen von Zig Debug einen Programmpfad aus.",
- "warning.node.not_found": "Node.js wurde nicht gefunden. Installiere Node.js, damit der Zig-Debug-Adapter-Proxy laufen kann."
+ "warning.node.not_found": "Node.js wurde nicht gefunden. Installiere Node.js, damit der Zig-Debug-Adapter-Proxy laufen kann.",
+ "Debug Adapter": "Debug-Adapter",
+ "Controls for the lldb-dap integration.": "Steuerelemente für die lldb-dap-Integration.",
+ "Enable Proxy Log": "Proxy-Protokoll aktivieren",
+ "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only.": "lldb-dap-Proxy-Datenverkehr in eine Protokolldatei im Speicherverzeichnis der Erweiterung schreiben. Nur für die Erweiterungsentwicklung.",
+ "Zig Test": "Zig-Test",
+ "Run `zig build test` with optional --test-filter and --summary controls.": "Führt `zig build test` mit optionalen --test-filter- und --summary-Steuerungen aus.",
+ "Test Filter": "Testfilter",
+ "substring of test name": "Teil eines Testnamens",
+ "Substring of the test name. Passed as `--test-filter`.": "Teil des Testnamens. Wird als `--test-filter` übergeben.",
+ "Summary": "Zusammenfassung",
+ "Default": "Standard",
+ "All": "Alle",
+ "Failures only": "Nur Fehler",
+ "None": "Keine",
+ "Controls `zig build`'s --summary verbosity.": "Steuert die Ausführlichkeit von `zig build --summary`.",
+ "Zig Watch": "Zig-Watch",
+ "Run `zig build --watch` and rebuild on file changes. Note: inline issues only update on the first build cycle — Nova's matchers do not re-arm for streaming output.": "Führt `zig build --watch` aus und baut bei Dateiänderungen neu. Hinweis: Inline-Probleme werden nur im ersten Build-Zyklus aktualisiert — Novas Matcher werden für Streaming-Ausgaben nicht erneut aktiviert.",
+ "Step": "Schritt",
+ "Build step to watch. Leave blank for `zig build`'s default install step.": "Build-Schritt, der überwacht werden soll. Leer lassen für Zigs Standardschritt `install`.",
+ "Debounce (ms)": "Entprellung (ms)",
+ "Passed as `--debounce <N>`. Leave blank for Zig's default.": "Wird als `--debounce <N>` übergeben. Leer lassen für Zigs Standard.",
+ "Incremental": "Inkrementell",
+ "On": "Ein",
+ "Off": "Aus",
+ "Toggle `-fincremental` / `-fno-incremental`.": "Schaltet `-fincremental` / `-fno-incremental` um.",
+ "task.build_step.name": "Zig-Build: {step}",
+ "warning.clean.unsafe_cwd": "Reinigung verweigert: Das Arbeitsverzeichnis muss innerhalb dieses Workspaces liegen.",
+ "warning.fmt.no_file": "Öffne eine gespeicherte Zig-Datei, bevor du „Aktuelle Zig-Datei formatieren“ ausführst.",
+ "warning.fmt.not_zig": "„Aktuelle Zig-Datei formatieren“ funktioniert nur mit Zig-Dateien.",
+ "warning.fmt.no_workspace": "Öffne einen Workspace, bevor du „Workspace formatieren“ ausführst.",
+ "warning.fmt.failed": "zig fmt ist fehlgeschlagen.",
+ "Tasks": "Aufgaben",
+ "Controls for the task system.": "Steuerelemente für das Aufgabensystem.",
+ "Workspace-specific task settings.": "Workspace-spezifische Aufgabeneinstellungen.",
+ "Discover Build Steps": "Build-Schritte erkennen",
+ "Run `zig build --list-steps` and surface each step as a task in the task list. Disable for projects where running build.zig on activation is undesirable.": "Führt `zig build --list-steps` aus und zeigt jeden Schritt als Aufgabe in der Aufgabenliste an. Für Projekte deaktivieren, bei denen das Ausführen von build.zig beim Aktivieren unerwünscht ist."
}
diff --git a/extension.json b/extension.json
index 8f455cd..83e5934 100644
--- a/extension.json
+++ b/extension.json
@@ -102,6 +102,20 @@
"description": "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only."
}
]
+ },
+ {
+ "type": "section",
+ "title": "Tasks",
+ "description": "Controls for the task system.",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.tasks.discover-steps",
+ "title": "Discover Build Steps",
+ "type": "boolean",
+ "default": true,
+ "description": "Run `zig build --list-steps` and surface each step as a task in the task list. Disable for projects where running build.zig on activation is undesirable."
+ }
+ ]
}
],
"configWorkspace": [
@@ -170,6 +184,19 @@
"description": "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only."
}
]
+ },
+ {
+ "type": "section",
+ "title": "Tasks",
+ "description": "Workspace-specific task settings.",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.tasks.discover-steps",
+ "title": "Discover Build Steps",
+ "type": "boolean",
+ "description": "Run `zig build --list-steps` and surface each step as a task in the task list. Disable for projects where running build.zig on activation is undesirable."
+ }
+ ]
}
],
"issueMatchers": {
@@ -224,9 +251,36 @@
"key": "runStep",
"title": "Run Step",
"type": "string",
- "default": "run",
- "placeholder": "run",
- "description": "The `zig build` step to execute for the Run action."
+ "default": "",
+ "placeholder": "install",
+ "description": "The `zig build` step to execute for the Run action. Leave blank to run `zig build` with no step (Zig's default install step)."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`. Leave on Project Default to let `build.zig` decide."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
},
{
"key": "buildArgs",
@@ -239,23 +293,35 @@
"title": "Program Arguments",
"type": "stringArray",
"description": "Arguments passed after `--` to the built program."
+ },
+ {
+ "key": "console",
+ "title": "Console",
+ "type": "enum",
+ "default": "internalConsole",
+ "values": [
+ ["internalConsole", "Internal Console"],
+ ["externalTerminal", "External Terminal"]
+ ],
+ "description": "Where to run the program. External Terminal launches the macOS Terminal app."
}
]
},
- "zigBuildRunTerminal": {
- "name": "Zig Package (macOS Terminal)",
- "description": "Build a Zig package and run it in the external macOS Terminal app.",
+ "zigDebug": {
+ "name": "Zig Debug",
+ "description": "Build a Zig package in Debug mode and launch it under lldb-dap.",
"tasks": {
"build": {
"resolve": "at.dcz.nova-zig.tasks",
"data": {
- "type": "build"
+ "type": "build-debug"
}
},
"run": {
"resolve": "at.dcz.nova-zig.tasks",
+ "buildBeforeRunning": true,
"data": {
- "type": "build-run-terminal"
+ "type": "debug"
}
},
"clean": {
@@ -276,42 +342,88 @@
"placeholder": "Workspace Root"
},
{
- "key": "runStep",
- "title": "Run Step",
+ "key": "programPath",
+ "title": "Program",
+ "type": "path",
+ "allowFiles": true,
+ "allowFolders": false,
+ "relative": true,
+ "placeholder": "zig-out/bin/app",
+ "description": "Path to the executable to debug. Relative paths are resolved against the working directory. Leave blank to auto-detect from `build.zig.zon`."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "Debug",
+ "values": [
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`. Debug is the recommended default for stepping through code."
+ },
+ {
+ "key": "target",
+ "title": "Target",
"type": "string",
- "default": "run",
- "placeholder": "run",
- "description": "The `zig build` step to execute for the Run action."
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
},
{
"key": "buildArgs",
"title": "Build Arguments",
"type": "stringArray",
- "description": "Additional arguments appended after `zig build` for both Build and Run."
+ "description": "Additional arguments appended after `zig build`."
},
{
"key": "runArgs",
"title": "Program Arguments",
"type": "stringArray",
- "description": "Arguments passed after `--` to the built program."
+ "description": "Arguments passed to the debugged program."
+ },
+ {
+ "key": "console",
+ "title": "Console",
+ "type": "enum",
+ "default": "internalConsole",
+ "values": [
+ ["internalConsole", "Internal Console"],
+ ["integratedTerminal", "Integrated Terminal"],
+ ["externalTerminal", "External Terminal"]
+ ],
+ "description": "Where the debugged program should run."
+ },
+ {
+ "key": "stopOnEntry",
+ "title": "Stop On Entry",
+ "type": "boolean",
+ "default": false,
+ "description": "Pause immediately when the program starts."
}
]
},
- "zigDebug": {
- "name": "Zig Debug",
- "description": "Build a Zig package in Debug mode and launch it under lldb-dap.",
+ "zigTest": {
+ "name": "Zig Test",
+ "description": "Run `zig build test` with optional --test-filter and --summary controls.",
"tasks": {
"build": {
"resolve": "at.dcz.nova-zig.tasks",
"data": {
- "type": "build-debug"
+ "type": "test-build"
}
},
"run": {
"resolve": "at.dcz.nova-zig.tasks",
- "buildBeforeRunning": true,
"data": {
- "type": "debug"
+ "type": "test-run"
}
},
"clean": {
@@ -332,45 +444,151 @@
"placeholder": "Workspace Root"
},
{
- "key": "programPath",
- "title": "Program",
- "type": "path",
- "allowFiles": true,
- "allowFolders": false,
- "relative": true,
- "placeholder": "zig-out/bin/app",
- "description": "Path to the executable to debug. Relative paths are resolved against the working directory."
+ "key": "testFilter",
+ "title": "Test Filter",
+ "type": "string",
+ "placeholder": "substring of test name",
+ "description": "Substring of the test name. Passed as `--test-filter`."
+ },
+ {
+ "key": "summary",
+ "title": "Summary",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Default"],
+ ["all", "All"],
+ ["failures", "Failures only"],
+ ["none", "None"]
+ ],
+ "description": "Controls `zig build`'s --summary verbosity."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
},
{
"key": "buildArgs",
"title": "Build Arguments",
"type": "stringArray",
- "description": "Additional arguments appended after `zig build -Doptimize=Debug`."
+ "description": "Additional arguments appended after `zig build test`."
},
{
"key": "runArgs",
"title": "Program Arguments",
"type": "stringArray",
- "description": "Arguments passed to the debugged program."
+ "description": "Arguments passed after `--` to the test runner."
+ }
+ ]
+ },
+ "zigWatch": {
+ "name": "Zig Watch",
+ "description": "Run `zig build --watch` and rebuild on file changes. Note: inline issues only update on the first build cycle — Nova's matchers do not re-arm for streaming output.",
+ "tasks": {
+ "build": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "watch"
+ }
+ },
+ "clean": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "clean"
+ }
+ }
+ },
+ "config": [
+ {
+ "key": "cwd",
+ "title": "Working Directory",
+ "type": "path",
+ "allowFiles": false,
+ "allowFolders": true,
+ "relative": true,
+ "placeholder": "Workspace Root"
},
{
- "key": "console",
- "title": "Console",
+ "key": "step",
+ "title": "Step",
+ "type": "string",
+ "default": "",
+ "placeholder": "install",
+ "description": "Build step to watch. Leave blank for `zig build`'s default install step."
+ },
+ {
+ "key": "debounceMs",
+ "title": "Debounce (ms)",
+ "type": "number",
+ "description": "Passed as `--debounce <N>`. Leave blank for Zig's default."
+ },
+ {
+ "key": "incremental",
+ "title": "Incremental",
"type": "enum",
- "default": "internalConsole",
+ "default": "",
"values": [
- ["internalConsole", "Internal Console"],
- ["integratedTerminal", "Integrated Terminal"],
- ["externalTerminal", "External Terminal"]
+ ["", "Default"],
+ ["on", "On"],
+ ["off", "Off"]
],
- "description": "Where the debugged program should run."
+ "description": "Toggle `-fincremental` / `-fno-incremental`."
},
{
- "key": "stopOnEntry",
- "title": "Stop On Entry",
- "type": "boolean",
- "default": false,
- "description": "Pause immediately when the program starts."
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
+ },
+ {
+ "key": "buildArgs",
+ "title": "Build Arguments",
+ "type": "stringArray",
+ "description": "Additional arguments appended after `zig build`."
}
]
}