diff options
Diffstat (limited to 'Scripts')
| -rw-r--r-- | Scripts/main.js | 157 |
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( |
