aboutsummaryrefslogtreecommitdiff
path: root/Scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Scripts')
-rw-r--r--Scripts/main.js157
1 files changed, 114 insertions, 43 deletions
diff --git a/Scripts/main.js b/Scripts/main.js
index 3ff3fa2..aa11e9e 100644
--- a/Scripts/main.js
+++ b/Scripts/main.js
@@ -25,11 +25,39 @@ const CONFIG_KEYS = {
let languageServer = null;
let taskAssistant = null;
let commandRegistrations = [];
+// Tracks when the last ZLS client was stopped. Used by start() to wait for
+// the old ZLS process to fully exit before spawning a new one — survives
+// across instances when Nova auto-restarts the extension.
+let lastZlsStopAt = 0;
+const ZLS_RESTART_GRACE_MS = 500;
exports.activate = function activate() {
+ // 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.
+ if (languageServer) {
+ languageServer.dispose();
+ languageServer = null;
+ }
+ if (taskAssistant) {
+ taskAssistant.dispose();
+ taskAssistant = null;
+ }
+ commandRegistrations.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ try {
+ disposable.dispose();
+ } catch (error) {
+ console.error(`[${EXTENSION_ID}] Failed to dispose registration`, error);
+ }
+ }
+ });
+ commandRegistrations = [];
+
registerCommands();
taskAssistant = new ZigTaskAssistant();
languageServer = new ZigLanguageServer();
+ console.log(`[${EXTENSION_ID}] activated`);
};
exports.deactivate = function deactivate() {
@@ -53,6 +81,8 @@ exports.deactivate = function deactivate() {
}
});
commandRegistrations = [];
+
+ console.log(`[${EXTENSION_ID}] deactivated`);
};
// --- CONFIG HELPERS ----------------------------------------------------------
@@ -448,6 +478,7 @@ const stepCache = {
},
async fetch(cwd, buildZigMtimeMs, buildZonMtimeMs) {
+ console.log(`[${TASK_ASSISTANT_ID}] stepCache.fetch: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -588,16 +619,36 @@ function runProcess(command, options) {
* @returns {Promise<string|null>} - Path to the executable or null if not found
*/
async function findOnPath(executableName) {
+ // Pass Nova's own PATH explicitly — child processes do not inherit the parent
+ // environment automatically, so without this the subprocess sees a stripped PATH
+ // that misses entries like /opt/homebrew/bin.
+ const novaPath = (nova.environment && nova.environment.PATH) || "";
+
+ // Augment Nova's PATH with well-known prefixes that Homebrew and common
+ // package managers use but that may be absent when Nova is launched from the
+ // Dock (where launchd provides a narrower system PATH than a login shell).
+ const fallbackPrefixes = [
+ "/opt/homebrew/bin", // Homebrew – Apple Silicon
+ "/usr/local/bin", // Homebrew – Intel / manual installs
+ `${nova.environment && nova.environment.HOME}/.local/bin`, // mise, cargo, etc.
+ ];
+ const augmentedPath = [...new Set([
+ ...novaPath.split(":").filter(Boolean),
+ ...fallbackPrefixes,
+ ])].join(":");
+
const result = await runProcess("/usr/bin/env", {
args: ["which", executableName],
+ env: { PATH: augmentedPath },
});
+ const found = result.stdout.trim();
+ console.log(`[${EXTENSION_ID}] findOnPath: ${executableName} → ${result.status === 0 ? found : "not found"}`);
if (result.status !== 0) {
return null;
}
- const path = result.stdout.trim();
- return path.length > 0 ? path : null;
+ return found.length > 0 ? found : null;
}
/**
@@ -610,6 +661,7 @@ async function findOnPath(executableName) {
async function resolveExecutable(configKey, defaultCommand) {
const configuredPath = getConfigValue(configKey);
if (configuredPath) {
+ console.log(`[${EXTENSION_ID}] findOnPath: ${defaultCommand} → config: ${configuredPath}`);
return configuredPath;
}
@@ -825,6 +877,7 @@ function registerCommands() {
const command = payload && payload.command;
const args = (payload && payload.args) || [];
const cwd = (payload && payload.cwd) || workspace.path || null;
+ console.log(`[${EXTENSION_ID}] runInTerminal: command=${command} cwd=${cwd}`);
if (!command) {
workspace.showWarningMessage(
@@ -892,38 +945,28 @@ class ZigLanguageServer {
}
observeConfig(key, restart) {
- this.disposables.push(
- nova.config.onDidChange(key, () => {
- if (restart) {
- this.start();
- } else {
- this.pushConfiguration().catch((error) => {
- console.error(
- `[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
- error,
- );
- });
- }
- }),
- );
- this.disposables.push(
- nova.workspace.config.onDidChange(key, () => {
- if (restart) {
- this.start();
- } else {
- this.pushConfiguration().catch((error) => {
- console.error(
- `[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
- error,
- );
- });
- }
- }),
- );
+ const onChange = () => {
+ if (restart) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] config changed (${key}) → restart`);
+ this.start();
+ } else {
+ console.log(`[${LANGUAGE_CLIENT_ID}] config changed (${key}) → push configuration`);
+ this.pushConfiguration().catch((error) => {
+ console.error(
+ `[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
+ error,
+ );
+ });
+ }
+ };
+ this.disposables.push(nova.config.onDidChange(key, onChange));
+ this.disposables.push(nova.workspace.config.onDidChange(key, onChange));
}
dispose() {
this.stop();
+ // Remove observers first so the workspace config cleanup below does not
+ // accidentally fire any remaining callbacks.
this.disposables.forEach((disposable) => {
if (disposable && typeof disposable.dispose === "function") {
try {
@@ -943,9 +986,26 @@ class ZigLanguageServer {
// 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;
+ console.log(`[${LANGUAGE_CLIENT_ID}] start (generation ${generation})`);
this.stop();
+ // If a ZLS process was recently stopped (either on this instance or on a
+ // previous instance after Nova auto-restarted the extension), wait for it
+ // to fully exit before spawning a new one. Without this pause the two
+ // processes overlap, Nova sees two clients with the same ID, and kills
+ // the new one before the LSP handshake completes.
+ const sinceLastStop = Date.now() - lastZlsStopAt;
+ if (lastZlsStopAt > 0 && sinceLastStop < ZLS_RESTART_GRACE_MS) {
+ const wait = ZLS_RESTART_GRACE_MS - sinceLastStop;
+ console.log(`[${LANGUAGE_CLIENT_ID}] waiting ${wait}ms for previous ZLS to exit`);
+ await new Promise((resolve) => setTimeout(resolve, wait));
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+ }
+
if (!getBooleanConfigValue(CONFIG_KEYS.zlsEnabled, true)) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] start: ZLS disabled, skipping`);
return;
}
@@ -970,8 +1030,6 @@ class ZigLanguageServer {
return;
}
- syncWorkspaceZlsConfiguration(settings);
-
const debugLogs = getBooleanConfigValue(CONFIG_KEYS.zlsDebug, false);
const serverOptions = {
@@ -982,9 +1040,7 @@ class ZigLanguageServer {
const clientOptions = {
syntaxes: [{ syntax: "zig", languageId: "zig" }],
debug: debugLogs,
- initializationOptions: {
- zls: settings,
- },
+ initializationOptions: settings,
};
const client = new LanguageClient(
@@ -1022,11 +1078,12 @@ class ZigLanguageServer {
}
});
+ console.log(`[${LANGUAGE_CLIENT_ID}] starting client: zls=${zlsPath} zig=${zigPath || "not found"}`);
try {
client.start();
this.client = client;
nova.subscriptions.add(client);
- this.pushConfiguration().catch((error) => {
+ this.pushConfiguration(settings).catch((error) => {
console.error(
`[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
error,
@@ -1064,9 +1121,12 @@ class ZigLanguageServer {
return { settings, zigPath };
}
- async pushConfiguration() {
+ async pushConfiguration(preResolvedSettings) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] pushConfiguration`);
const generation = this.restartGeneration;
- const { settings } = await this.resolveSettings();
+ const { settings } = preResolvedSettings
+ ? { settings: preResolvedSettings }
+ : await this.resolveSettings();
if (
generation !== this.restartGeneration ||
!this.client ||
@@ -1075,16 +1135,15 @@ class ZigLanguageServer {
return;
}
- syncWorkspaceZlsConfiguration(settings);
-
this.client.sendNotification("workspace/didChangeConfiguration", {
- settings: {
- zls: settings,
- },
+ settings,
});
}
stop() {
+ if (this.client) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] stop`);
+ }
if (
this.clientStopDisposable &&
typeof this.clientStopDisposable.dispose === "function"
@@ -1097,6 +1156,7 @@ class ZigLanguageServer {
this.client.stop();
nova.subscriptions.remove(this.client);
this.client = null;
+ lastZlsStopAt = Date.now();
}
}
@@ -1185,6 +1245,7 @@ class ZigTaskAssistant {
const type = context.data && context.data.type;
const config = context.config;
const cwd = getTaskCwd(config);
+ console.log(`[${TASK_ASSISTANT_ID}] resolveTaskAction: type=${type} cwd=${cwd}`);
if (type === "clean") {
return this.resolveCleanAction(cwd);
@@ -1227,6 +1288,7 @@ class ZigTaskAssistant {
}
async resolveCleanAction(cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCleanAction: cwd=${cwd}`);
if (!cwd) {
nova.workspace.showWarningMessage(
localizeText(
@@ -1268,6 +1330,7 @@ class ZigTaskAssistant {
}
async resolveBuildAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildAction: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1275,6 +1338,7 @@ class ZigTaskAssistant {
}
async resolveDebugBuildAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveDebugBuildAction: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1286,6 +1350,7 @@ class ZigTaskAssistant {
}
async resolveBuildRunAction(config, cwd, forceConsole) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildRunAction: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1315,6 +1380,7 @@ class ZigTaskAssistant {
}
async resolveBuildStepAction(step) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildStepAction: step=${step}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1325,6 +1391,7 @@ class ZigTaskAssistant {
}
async resolveTestAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveTestAction: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1343,6 +1410,7 @@ class ZigTaskAssistant {
}
async resolveWatchAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveWatchAction: cwd=${cwd}`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1365,6 +1433,7 @@ class ZigTaskAssistant {
}
async resolveCurrentFileRunAction() {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCurrentFileRunAction`);
const zigPath = await resolveZigExecutable();
if (!zigPath) return null;
@@ -1384,6 +1453,7 @@ class ZigTaskAssistant {
}
async resolveCurrentFileCleanAction() {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCurrentFileCleanAction`);
const startDir = activeZigFileDirectory();
if (!startDir) {
nova.workspace.showWarningMessage(
@@ -1400,6 +1470,7 @@ class ZigTaskAssistant {
}
async resolveDebugAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveDebugAction: cwd=${cwd}`);
const lldbDapPath = await resolveLldbDapExecutable();
if (!lldbDapPath) {
nova.workspace.showWarningMessage(