aboutsummaryrefslogtreecommitdiff
path: root/Scripts/main.js
diff options
context:
space:
mode:
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 @@
1"use strict";
2
3const EXTENSION_ID = "at.dcz.nova-zig";
4const TASK_ASSISTANT_ID = `${EXTENSION_ID}.tasks`;
5const LANGUAGE_CLIENT_ID = `${EXTENSION_ID}.zls`;
6const ISSUE_MATCHER = "zig.compiler";
7
8const CONFIG_KEYS = {
9 zigPath: `${EXTENSION_ID}.toolchain.zig-path`,
10 zlsPath: `${EXTENSION_ID}.toolchain.zls-path`,
11 lldbDapPath: `${EXTENSION_ID}.toolchain.lldb-dap-path`,
12 zlsEnabled: `${EXTENSION_ID}.zls.enabled`,
13 zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`,
14 zlsDebug: `${EXTENSION_ID}.zls.debug`,
15};
16
17let languageServer = null;
18let taskAssistant = null;
19let issueAssistant = null;
20let commandRegistrations = [];
21
22exports.activate = function activate() {
23 registerCommands();
24 taskAssistant = new ZigTaskAssistant();
25 languageServer = new ZigLanguageServer();
26 issueAssistant = new ZigIssueAssistant();
27};
28
29exports.deactivate = function deactivate() {
30 if (languageServer) {
31 languageServer.dispose();
32 languageServer = null;
33 }
34
35 if (taskAssistant) {
36 taskAssistant.dispose();
37 taskAssistant = null;
38 }
39
40 if (issueAssistant) {
41 issueAssistant.dispose();
42 issueAssistant = null;
43 }
44
45 commandRegistrations.forEach((disposable) => {
46 if (disposable && typeof disposable.dispose === "function") {
47 disposable.dispose();
48 }
49 });
50 commandRegistrations = [];
51};
52
53function getConfigValue(key) {
54 const workspaceValue = nova.workspace.config.get(key);
55 if (workspaceValue !== undefined && workspaceValue !== null && workspaceValue !== "") {
56 return workspaceValue;
57 }
58
59 const globalValue = nova.config.get(key);
60 if (globalValue !== undefined && globalValue !== null && globalValue !== "") {
61 return globalValue;
62 }
63
64 return null;
65}
66
67function getBooleanConfigValue(key, fallback) {
68 const workspaceValue = nova.workspace.config.get(key);
69 if (typeof workspaceValue === "boolean") {
70 return workspaceValue;
71 }
72
73 const globalValue = nova.config.get(key);
74 if (typeof globalValue === "boolean") {
75 return globalValue;
76 }
77
78 return fallback;
79}
80
81function normalizeArray(value) {
82 if (!Array.isArray(value)) {
83 return [];
84 }
85
86 return value
87 .map((entry) => (entry === null || entry === undefined ? "" : String(entry).trim()))
88 .filter((entry) => entry.length > 0);
89}
90
91function resolveWorkspaceRelativePath(path) {
92 if (!path) {
93 return null;
94 }
95
96 if (path.startsWith("/")) {
97 return path;
98 }
99
100 if (nova.workspace.path) {
101 return nova.path.join(nova.workspace.path, path);
102 }
103
104 return path;
105}
106
107function resolvePathAgainstBase(path, base) {
108 if (!path) {
109 return null;
110 }
111
112 if (path.startsWith("/")) {
113 return path;
114 }
115
116 if (base) {
117 return nova.path.join(base, path);
118 }
119
120 return resolveWorkspaceRelativePath(path);
121}
122
123function getTaskConfigValue(config, key) {
124 if (!config) {
125 return null;
126 }
127
128 const value = config.get(key);
129 return value === undefined || value === null || value === "" ? null : value;
130}
131
132function getTaskArgs(config, key) {
133 return normalizeArray(getTaskConfigValue(config, key));
134}
135
136function getTaskCwd(config) {
137 const configured = getTaskConfigValue(config, "cwd");
138 if (configured) {
139 return resolveWorkspaceRelativePath(configured);
140 }
141
142 return nova.workspace.path || null;
143}
144
145function activeZigFilePath() {
146 const editor = nova.workspace.activeTextEditor;
147 if (!editor || !editor.document || !editor.document.path) {
148 return null;
149 }
150
151 if (editor.document.syntax !== "zig") {
152 return null;
153 }
154
155 return editor.document.path;
156}
157
158function activeZigFileDirectory() {
159 const filePath = activeZigFilePath();
160 if (!filePath) {
161 return null;
162 }
163
164 return nova.path.dirname(filePath);
165}
166
167function localizedText(key, fallback, variables) {
168 let text = nova.localize(key, fallback);
169
170 if (!variables || typeof variables !== "object") {
171 return text;
172 }
173
174 for (const [name, value] of Object.entries(variables)) {
175 text = text.split(`{${name}}`).join(String(value));
176 }
177
178 return text;
179}
180
181function runProcess(command, options) {
182 return new Promise((resolve) => {
183 const stdout = [];
184 const stderr = [];
185 const process = new Process(command, options || {});
186
187 process.onStdout((line) => stdout.push(line));
188 process.onStderr((line) => stderr.push(line));
189 process.onDidExit((status) => {
190 resolve({
191 status,
192 stdout: stdout.join(""),
193 stderr: stderr.join(""),
194 });
195 });
196
197 process.start();
198 });
199}
200
201async function findExecutableOnPath(commandName) {
202 const result = await runProcess("/usr/bin/env", {
203 args: ["which", commandName],
204 });
205
206 if (result.status !== 0) {
207 return null;
208 }
209
210 const path = result.stdout.trim();
211 return path.length > 0 ? path : null;
212}
213
214async function resolveExecutable(configKey, defaultCommand) {
215 const configuredPath = getConfigValue(configKey);
216 if (configuredPath) {
217 return configuredPath;
218 }
219
220 return findExecutableOnPath(defaultCommand);
221}
222
223async function findExecutableWithXcode(commandName) {
224 const result = await runProcess("/usr/bin/xcrun", {
225 args: ["--find", commandName],
226 });
227
228 if (result.status !== 0) {
229 return null;
230 }
231
232 const path = result.stdout.trim();
233 return path.length > 0 ? path : null;
234}
235
236async function resolveLLDBDAPExecutable() {
237 const configuredPath = getConfigValue(CONFIG_KEYS.lldbDapPath);
238 if (configuredPath) {
239 return configuredPath;
240 }
241
242 const xcodePath = await findExecutableWithXcode("lldb-dap");
243 if (xcodePath) {
244 return xcodePath;
245 }
246
247 return findExecutableOnPath("lldb-dap");
248}
249
250function executableDisplayName(path, fallback) {
251 return path || fallback;
252}
253
254function lldbFrameworkPaths() {
255 return [
256 "/Applications/Xcode-beta.app/Contents/SharedFrameworks/",
257 "/Applications/Xcode.app/Contents/SharedFrameworks/",
258 "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/",
259 ];
260}
261
262function lldbDapProxyPath() {
263 return nova.path.join(nova.extension.path, "Scripts", "lldb-dap-proxy.pl");
264}
265
266function debugAdapterLogPath() {
267 return "/tmp/zig-lldb-dap-proxy.log";
268}
269
270function issueNormalizerScriptPath() {
271 return nova.path.join(nova.extension.path, "Scripts", "normalize-zig-issues.pl");
272}
273
274function quoteShellArgument(value) {
275 const text = value === null || value === undefined ? "" : String(value);
276 return `'${text.replace(/'/g, `'\\''`)}'`;
277}
278
279function escapeAppleScriptString(value) {
280 return String(value)
281 .replace(/\\/g, "\\\\")
282 .replace(/"/g, '\\"');
283}
284
285function buildShellCommand(command, args, cwd) {
286 const segments = [];
287 if (cwd) {
288 segments.push(`cd ${quoteShellArgument(cwd)}`);
289 }
290
291 segments.push([quoteShellArgument(command), ...(args || []).map(quoteShellArgument)].join(" "));
292 return segments.join("; ");
293}
294
295function buildIssueNormalizedCommand(command, args) {
296 const commandLine = [
297 quoteShellArgument(command),
298 ...(args || []).map(quoteShellArgument),
299 ].join(" ");
300 const rewriter = `/usr/bin/perl ${quoteShellArgument(issueNormalizerScriptPath())}`;
301 return `setopt pipefail; ${commandLine} 2>&1 | ${rewriter}`;
302}
303
304function launchInTerminal(commandLine) {
305 return new Promise((resolve) => {
306 const script = `tell application "Terminal"
307activate
308do script "${escapeAppleScriptString(commandLine)}"
309end tell`;
310 const process = new Process("/usr/bin/osascript", {
311 args: ["-e", script],
312 });
313
314 let stderr = "";
315 process.onStderr((line) => {
316 stderr += line;
317 });
318 process.onDidExit((status) => {
319 resolve({ status, stderr: stderr.trim() });
320 });
321 process.start();
322 });
323}
324
325function registerCommands() {
326 commandRegistrations.push(
327 nova.commands.register(`${EXTENSION_ID}.runInTerminal`, async (workspace, payload) => {
328 const command = payload && payload.command;
329 const args = (payload && payload.args) || [];
330 const cwd = (payload && payload.cwd) || workspace.path || null;
331
332 if (!command) {
333 workspace.showWarningMessage(
334 localizedText(
335 "warning.terminal.launch_failed",
336 "Unable to launch the Zig task in Terminal."
337 )
338 );
339 return;
340 }
341
342 const result = await launchInTerminal(buildShellCommand(command, args, cwd));
343 if (result.status !== 0) {
344 const prefix = localizedText(
345 "warning.terminal.open_failed",
346 "Unable to open Terminal for the Zig task."
347 );
348 const suffix = result.stderr ? ` ${result.stderr}` : "";
349 workspace.showWarningMessage(`${prefix}${suffix}`);
350 }
351 })
352 );
353}
354
355function syncWorkspaceZlsConfiguration(settings) {
356 const bridge = {
357 "zls.zig_exe_path": settings.zig_exe_path,
358 "zls.enable_build_on_save": settings.enable_build_on_save,
359 };
360
361 Object.entries(bridge).forEach(([key, value]) => {
362 if (value === undefined || value === null || value === "") {
363 nova.workspace.config.remove(key);
364 } else {
365 nova.workspace.config.set(key, value);
366 }
367 });
368}
369
370class ZigLanguageServer {
371 constructor() {
372 this.client = null;
373 this.clientStopDisposable = null;
374 this.restartGeneration = 0;
375 this.warnedMissing = new Set();
376 this.disposables = [];
377
378 this.observeConfig(CONFIG_KEYS.zigPath, true);
379 this.observeConfig(CONFIG_KEYS.zlsPath, true);
380 this.observeConfig(CONFIG_KEYS.zlsEnabled, true);
381 this.observeConfig(CONFIG_KEYS.zlsBuildOnSave, true);
382 this.observeConfig(CONFIG_KEYS.zlsDebug, true);
383 this.disposables.push(
384 nova.workspace.onDidChangePath(() => {
385 this.start();
386 })
387 );
388
389 this.start();
390 }
391
392 observeConfig(key, restart) {
393 this.disposables.push(
394 nova.config.onDidChange(key, () => {
395 if (restart) {
396 this.start();
397 } else {
398 void this.pushConfiguration();
399 }
400 })
401 );
402 this.disposables.push(
403 nova.workspace.config.onDidChange(key, () => {
404 if (restart) {
405 this.start();
406 } else {
407 void this.pushConfiguration();
408 }
409 })
410 );
411 }
412
413 dispose() {
414 this.stop();
415 this.disposables.forEach((disposable) => {
416 if (disposable && typeof disposable.dispose === "function") {
417 disposable.dispose();
418 }
419 });
420 this.disposables = [];
421 }
422
423 async start() {
424 // Guard against stale async continuations: if a config change triggers
425 // another start() while we are awaiting, the generation check below bails out.
426 const generation = ++this.restartGeneration;
427 this.stop();
428
429 if (!getBooleanConfigValue(CONFIG_KEYS.zlsEnabled, true)) {
430 return;
431 }
432
433 const zlsPath = await resolveExecutable(CONFIG_KEYS.zlsPath, "zls");
434 if (generation !== this.restartGeneration) {
435 return;
436 }
437
438 if (!zlsPath) {
439 this.warnMissingTool(
440 "zls",
441 localizedText(
442 "warning.zls.not_found",
443 "ZLS was not found. Install it or set a ZLS executable path in Zig extension settings."
444 )
445 );
446 return;
447 }
448
449 const { settings, zigPath } = await this.resolveSettings();
450 if (generation !== this.restartGeneration) {
451 return;
452 }
453
454 syncWorkspaceZlsConfiguration(settings);
455
456 const debugLogs = getBooleanConfigValue(CONFIG_KEYS.zlsDebug, false);
457
458 const serverOptions = {
459 path: zlsPath,
460 args: debugLogs ? [] : ["--disable-lsp-logs"],
461 };
462
463 const clientOptions = {
464 syntaxes: [{ syntax: "zig", languageId: "zig" }],
465 debug: debugLogs,
466 initializationOptions: {
467 zls: settings,
468 },
469 };
470
471 const client = new LanguageClient(
472 LANGUAGE_CLIENT_ID,
473 localizedText("name.language_server", "Zig Language Server"),
474 serverOptions,
475 clientOptions
476 );
477
478 this.clientStopDisposable = client.onDidStop((error) => {
479 if (error) {
480 console.error(`[${LANGUAGE_CLIENT_ID}] ${error.message}`);
481 nova.workspace.showWarningMessage(
482 localizedText(
483 "warning.zls.stopped_unexpectedly",
484 "The Zig language server stopped unexpectedly ({executable}).",
485 { executable: executableDisplayName(zlsPath, "zls") }
486 )
487 );
488 }
489 });
490
491 try {
492 client.start();
493 this.client = client;
494 nova.subscriptions.add(client);
495 void this.pushConfiguration();
496 this.warnedMissing.delete("zls");
497 if (zigPath) {
498 this.warnedMissing.delete("zig");
499 }
500 } catch (error) {
501 console.error(`[${LANGUAGE_CLIENT_ID}] Failed to start ZLS`, error);
502 nova.workspace.showWarningMessage(
503 localizedText(
504 "warning.zls.start_failed",
505 "Unable to start the Zig language server at {path}.",
506 { path: zlsPath }
507 )
508 );
509 this.stop();
510 }
511 }
512
513 async resolveSettings() {
514 const settings = {
515 enable_build_on_save: getBooleanConfigValue(CONFIG_KEYS.zlsBuildOnSave, false),
516 };
517 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
518 if (zigPath) {
519 settings.zig_exe_path = zigPath;
520 }
521
522 return { settings, zigPath };
523 }
524
525 async pushConfiguration() {
526 const generation = this.restartGeneration;
527 const { settings } = await this.resolveSettings();
528 if (generation !== this.restartGeneration || !this.client || !this.client.running) {
529 return;
530 }
531
532 syncWorkspaceZlsConfiguration(settings);
533
534 this.client.sendNotification("workspace/didChangeConfiguration", {
535 settings: {
536 zls: settings,
537 },
538 });
539 }
540
541 stop() {
542 if (this.clientStopDisposable && typeof this.clientStopDisposable.dispose === "function") {
543 this.clientStopDisposable.dispose();
544 this.clientStopDisposable = null;
545 }
546
547 if (this.client) {
548 this.client.stop();
549 nova.subscriptions.remove(this.client);
550 this.client = null;
551 }
552 }
553
554 warnMissingTool(tool, message) {
555 if (this.warnedMissing.has(tool)) {
556 return;
557 }
558
559 this.warnedMissing.add(tool);
560 nova.workspace.showWarningMessage(message);
561 }
562}
563
564class ZigTaskAssistant {
565 constructor() {
566 this.disposable = nova.assistants.registerTaskAssistant(this, {
567 identifier: TASK_ASSISTANT_ID,
568 name: localizedText("name.extension", "Zig"),
569 });
570 }
571
572 dispose() {
573 if (this.disposable && typeof this.disposable.dispose === "function") {
574 this.disposable.dispose();
575 this.disposable = null;
576 }
577 }
578
579 provideTasks() {
580 const task = new Task(localizedText("task.current_file.name", "Current Zig File"));
581 task.setAction(Task.Run, new TaskResolvableAction({
582 data: {
583 type: "current-file-run",
584 },
585 }));
586 task.setAction(Task.Clean, new TaskResolvableAction({
587 data: {
588 type: "current-file-clean",
589 },
590 }));
591 return [task];
592 }
593
594 async resolveTaskAction(context) {
595 const type = context.data && context.data.type;
596 const config = context.config;
597 const cwd = getTaskCwd(config);
598
599 if (type === "clean") {
600 return this.resolveCleanAction(cwd);
601 }
602
603 switch (type) {
604 case "build":
605 return this.resolveBuildAction(config, cwd);
606 case "build-debug":
607 return this.resolveDebugBuildAction(config, cwd);
608 case "build-run":
609 return this.resolveBuildRunAction(config, cwd);
610 case "build-run-terminal":
611 return this.resolveBuildRunTerminalAction(config, cwd);
612 case "build-test":
613 return this.resolveBuildTestAction(config, cwd);
614 case "current-file-run":
615 return this.resolveCurrentFileRunAction();
616 case "current-file-clean":
617 return this.resolveCurrentFileCleanAction();
618 case "debug":
619 return this.resolveDebugAction(config, cwd);
620 case "file-test":
621 return this.resolveFileTestAction(config, cwd);
622 default:
623 return null;
624 }
625 }
626
627 createAction(command, args, cwd) {
628 return new TaskProcessAction("/bin/zsh", {
629 args: ["-lc", buildIssueNormalizedCommand(command, args)],
630 cwd,
631 env: {
632 NOVA_ZIG_TASK_CWD: cwd || "",
633 },
634 matchers: [ISSUE_MATCHER],
635 });
636 }
637
638 resolveCleanAction(cwd) {
639 if (!cwd) {
640 nova.workspace.showWarningMessage(
641 localizedText(
642 "warning.clean.missing_cwd",
643 "Choose a workspace or working directory before cleaning Zig build artifacts."
644 )
645 );
646 return null;
647 }
648
649 return new TaskProcessAction("/bin/rm", {
650 args: ["-rf", ".zig-cache", "zig-cache", "zig-out"],
651 cwd,
652 });
653 }
654
655 async resolveBuildAction(config, cwd) {
656 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
657 if (!zigPath) {
658 nova.workspace.showWarningMessage(
659 localizedText(
660 "warning.zig.not_found",
661 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
662 )
663 );
664 return null;
665 }
666
667 return this.createAction(zigPath, ["build", ...getTaskArgs(config, "buildArgs")], cwd);
668 }
669
670 async resolveDebugBuildAction(config, cwd) {
671 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
672 if (!zigPath) {
673 nova.workspace.showWarningMessage(
674 localizedText(
675 "warning.zig.not_found",
676 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
677 )
678 );
679 return null;
680 }
681
682 return this.createAction(zigPath, ["build", "-Doptimize=Debug", ...getTaskArgs(config, "buildArgs")], cwd);
683 }
684
685 async resolveBuildRunAction(config, cwd) {
686 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
687 if (!zigPath) {
688 nova.workspace.showWarningMessage(
689 localizedText(
690 "warning.zig.not_found",
691 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
692 )
693 );
694 return null;
695 }
696
697 const step =
698 getTaskConfigValue(config, "runStep") || getTaskConfigValue(config, "step") || "run";
699 const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
700 const runArgs = getTaskArgs(config, "runArgs");
701
702 if (runArgs.length > 0) {
703 args.push("--", ...runArgs);
704 }
705
706 return this.createAction(zigPath, args, cwd);
707 }
708
709 async resolveBuildRunTerminalAction(config, cwd) {
710 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
711 if (!zigPath) {
712 nova.workspace.showWarningMessage(
713 localizedText(
714 "warning.zig.not_found",
715 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
716 )
717 );
718 return null;
719 }
720
721 const step =
722 getTaskConfigValue(config, "runStep") || getTaskConfigValue(config, "step") || "run";
723 const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
724 const runArgs = getTaskArgs(config, "runArgs");
725
726 if (runArgs.length > 0) {
727 args.push("--", ...runArgs);
728 }
729
730 return new TaskCommandAction(`${EXTENSION_ID}.runInTerminal`, {
731 args: [
732 {
733 command: zigPath,
734 args,
735 cwd,
736 },
737 ],
738 });
739 }
740
741 async resolveBuildTestAction(config, cwd) {
742 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
743 if (!zigPath) {
744 nova.workspace.showWarningMessage(
745 localizedText(
746 "warning.zig.not_found",
747 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
748 )
749 );
750 return null;
751 }
752
753 const step =
754 getTaskConfigValue(config, "testStep") || getTaskConfigValue(config, "step") || "test";
755 const args = ["build", ...getTaskArgs(config, "buildArgs"), step];
756 const testArgs = getTaskArgs(config, "testArgs");
757
758 if (testArgs.length > 0) {
759 args.push("--", ...testArgs);
760 }
761
762 return this.createAction(zigPath, args, cwd);
763 }
764
765 async resolveCurrentFileRunAction() {
766 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
767 if (!zigPath) {
768 nova.workspace.showWarningMessage(
769 localizedText(
770 "warning.zig.not_found",
771 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
772 )
773 );
774 return null;
775 }
776
777 const filePath = activeZigFilePath();
778 const cwd = filePath ? nova.path.dirname(filePath) : null;
779 if (!filePath || !cwd) {
780 nova.workspace.showWarningMessage(
781 localizedText(
782 "warning.current_file.focus_editor_for_run",
783 "Focus a Zig editor before running Current Zig File."
784 )
785 );
786 return null;
787 }
788
789 return this.createAction(zigPath, ["run", filePath], cwd);
790 }
791
792 resolveCurrentFileCleanAction() {
793 const cwd = activeZigFileDirectory();
794 if (!cwd) {
795 nova.workspace.showWarningMessage(
796 localizedText(
797 "warning.current_file.focus_editor_for_clean",
798 "Focus a Zig editor before cleaning Current Zig File artifacts."
799 )
800 );
801 return null;
802 }
803
804 return this.resolveCleanAction(cwd);
805 }
806
807 async resolveDebugAction(config, cwd) {
808 const lldbDapPath = await resolveLLDBDAPExecutable();
809 if (!lldbDapPath) {
810 nova.workspace.showWarningMessage(
811 localizedText(
812 "warning.lldb_dap.not_found",
813 "lldb-dap was not found. Install Xcode Command Line Tools or set an LLDB DAP executable path in Zig extension settings."
814 )
815 );
816 return null;
817 }
818
819 const configuredProgramPath = getTaskConfigValue(config, "programPath");
820 const programPath = resolvePathAgainstBase(configuredProgramPath, cwd);
821 if (!programPath) {
822 nova.workspace.showWarningMessage(
823 localizedText(
824 "warning.debug.choose_program",
825 "Choose a program path before running Zig Debug."
826 )
827 );
828 return null;
829 }
830
831 const consoleMode = getTaskConfigValue(config, "console") || "internalConsole";
832 const stopOnEntry = Boolean(config && config.get("stopOnEntry"));
833
834 const action = new TaskDebugAdapterAction("zig-lldb-dap");
835 action.transport = "stdio";
836 action.command = "/usr/bin/perl";
837 action.args = [lldbDapProxyPath(), lldbDapPath, debugAdapterLogPath()];
838 action.debugRequest = "launch";
839 action.env = {
840 DYLD_FRAMEWORK_PATH: lldbFrameworkPaths().join(":"),
841 NOVA_ZIG_LLDB_DAP_PATH: lldbDapPath,
842 NOVA_ZIG_DEBUG_LOG: debugAdapterLogPath(),
843 };
844 action.debugArgs = {
845 program: programPath,
846 cwd,
847 args: getTaskArgs(config, "runArgs"),
848 stopOnEntry,
849 };
850
851 if (consoleMode !== "internalConsole") {
852 action.debugArgs.console = consoleMode;
853 }
854
855 return action;
856 }
857
858 async resolveFileTestAction(config, cwd) {
859 const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
860 if (!zigPath) {
861 nova.workspace.showWarningMessage(
862 localizedText(
863 "warning.zig.not_found",
864 "Zig was not found. Install it or set a Zig executable path in Zig extension settings."
865 )
866 );
867 return null;
868 }
869
870 const configuredPath = getTaskConfigValue(config, "filePath");
871 const filePath = configuredPath
872 ? resolveWorkspaceRelativePath(configuredPath)
873 : activeZigFilePath();
874
875 if (!filePath) {
876 nova.workspace.showWarningMessage(
877 localizedText(
878 "warning.file_test.choose_file",
879 "Choose a Zig file for this task or focus a Zig editor before running Zig File Test."
880 )
881 );
882 return null;
883 }
884
885 return this.createAction(zigPath, ["test", filePath, ...getTaskArgs(config, "zigArgs")], cwd);
886 }
887}
888
889class ZigIssueAssistant {
890 constructor() {
891 this.disposable = nova.assistants.registerIssueAssistant(
892 [{ syntax: "zig" }, { syntax: "zig-package" }],
893 this,
894 { event: "onChange" }
895 );
896 }
897
898 dispose() {
899 if (this.disposable && typeof this.disposable.dispose === "function") {
900 this.disposable.dispose();
901 this.disposable = null;
902 }
903 }
904
905 provideIssues(editor) {
906 if (!editor || !editor.document) {
907 return [];
908 }
909
910 // Nova's LanguageClient already owns core LSP publishDiagnostics handling.
911 // Registering an issue assistant here tells Nova that Zig supports live
912 // checking, so the Problems UI doesn't show a misleading empty-state banner.
913 return [];
914 }
915}