From 63140bb1666bf9eeb6999e9a416474e217f34607 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 23 Jan 2025 18:45:31 +0100 Subject: [PATCH 1/7] offer to update workspace config files when selecting Zig version This removes the invisible `zig-version` workspace state. fixes #247 --- src/zigSetup.ts | 133 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 33 deletions(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 2f03c4c..858c77f 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -16,32 +16,36 @@ let versionManagerConfig: versionManager.Config; export let zigProvider: ZigProvider; /** Removes the `zig.path` config option. */ -async function installZig(context: vscode.ExtensionContext) { - const wantedZig = await getWantedZigVersion( - context, - Object.values(WantedZigVersionSource) as WantedZigVersionSource[], - ); - if (!wantedZig) { - await zigProvider.setAndSave(null); - return; - } +async function installZig(context: vscode.ExtensionContext, temporaryVersion?: semver.SemVer) { + let version = temporaryVersion; + + if (!version) { + const wantedZig = await getWantedZigVersion( + context, + Object.values(WantedZigVersionSource) as WantedZigVersionSource[], + ); + if (!wantedZig) { + await zigProvider.setAndSave(null); + return; + } - if (wantedZig.source === WantedZigVersionSource.workspaceBuildZigZon) { - wantedZig.version = await findClosestSatisfyingZigVersion(context, wantedZig.version); + if (wantedZig.source === WantedZigVersionSource.workspaceBuildZigZon) { + version = await findClosestSatisfyingZigVersion(context, wantedZig.version); + } else { + version = wantedZig.version; + } } try { - const exePath = await versionManager.install(versionManagerConfig, wantedZig.version); + const exePath = await versionManager.install(versionManagerConfig, version); await vscode.workspace.getConfiguration("zig").update("path", undefined, true); - zigProvider.set({ exe: exePath, version: wantedZig.version }); + zigProvider.set({ exe: exePath, version: version }); } catch (err) { zigProvider.set(null); if (err instanceof Error) { - void vscode.window.showErrorMessage( - `Failed to install Zig ${wantedZig.version.toString()}: ${err.message}`, - ); + void vscode.window.showErrorMessage(`Failed to install Zig ${version.toString()}: ${err.message}`); } else { - void vscode.window.showErrorMessage(`Failed to install Zig ${wantedZig.version.toString()}!`); + void vscode.window.showErrorMessage(`Failed to install Zig ${version.toString()}!`); } } } @@ -234,7 +238,6 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) { switch (selection.label) { case "Use Workspace Version": - await context.workspaceState.update("zig-version", undefined); await installZig(context); break; case "Use Zig in PATH": @@ -252,12 +255,78 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) { break; default: const version = new semver.SemVer(selection.detail ?? selection.label); - await context.workspaceState.update("zig-version", version.raw); - await installZig(context); + await installZig(context, version); + void showUpdateWorkspaceVersionDialog(version, workspaceZig?.source); break; } } +async function showUpdateWorkspaceVersionDialog( + version: semver.SemVer, + source?: WantedZigVersionSource, +): Promise { + const workspace = getWorkspaceFolder(); + + switch (source) { + case WantedZigVersionSource.latestTagged: + source = undefined; + // intentional fallthrough + case undefined: + source = workspace + ? WantedZigVersionSource.workspaceZigVersionFile + : WantedZigVersionSource.zigVersionConfigOption; + break; + default: + break; + } + + let sourceName; + switch (source) { + case WantedZigVersionSource.workspaceZigVersionFile: + sourceName = ".ziversion"; + break; + case WantedZigVersionSource.workspaceBuildZigZon: + sourceName = "build.zig.zon"; + break; + case WantedZigVersionSource.zigVersionConfigOption: + sourceName = "workspace settings"; + break; + } + + const response = await vscode.window.showInformationMessage( + "Would you like to save this version for this workspace?", + `update ${sourceName}`, + ); + if (!response) return; + + switch (source) { + case WantedZigVersionSource.workspaceZigVersionFile: { + if (!workspace) throw new Error("failed to resolve workspace folder"); + + const edit = new vscode.WorkspaceEdit(); + edit.createFile(vscode.Uri.joinPath(workspace.uri, ".zigversion"), { + overwrite: true, + contents: new Uint8Array(Buffer.from(version.raw)), + }); + await vscode.workspace.applyEdit(edit); + break; + } + case WantedZigVersionSource.workspaceBuildZigZon: { + const metadata = await parseBuildZigZon(); + if (!metadata) throw new Error("failed to parse build.zig.zon"); + + const edit = new vscode.WorkspaceEdit(); + edit.replace(metadata.document.uri, metadata.minimumZigVersionSourceRange, version.raw); + await vscode.workspace.applyEdit(edit); + break; + } + case WantedZigVersionSource.zigVersionConfigOption: { + await vscode.workspace.getConfiguration("zig").update("version", version.raw, !workspace); + break; + } + } +} + interface BuildZigZonMetadata { /** The `build.zig.zon` document. */ document: vscode.TextDocument; @@ -266,15 +335,19 @@ interface BuildZigZonMetadata { minimumZigVersionSourceRange: vscode.Range; } +function getWorkspaceFolder(): vscode.WorkspaceFolder | null { + // Supporting multiple workspaces is significantly more complex so we just look for the first workspace. + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { + return vscode.workspace.workspaceFolders[0]; + } + return null; +} + /** * Look for a `build.zig.zon` in the current workspace and return the `minimum_zig_version` in it. */ async function parseBuildZigZon(): Promise { - let workspace: vscode.WorkspaceFolder | null = null; - // Supporting multiple workspaces is significantly more complex so we just look for the first workspace. - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { - workspace = vscode.workspace.workspaceFolders[0]; - } + const workspace = getWorkspaceFolder(); if (!workspace) return null; const manifestUri = vscode.Uri.joinPath(workspace.uri, "build.zig.zon"); @@ -301,7 +374,6 @@ async function parseBuildZigZon(): Promise { /** The order of these enums defines the default order in which these sources are executed. */ enum WantedZigVersionSource { - workspaceState = "workspace-state", /** `.zigversion` */ workspaceZigVersionFile = ".zigversion", /** The `minimum_zig_version` in `build.zig.zon` */ @@ -331,12 +403,6 @@ async function getWantedZigVersion( try { switch (source) { - case WantedZigVersionSource.workspaceState: - // `context.workspaceState` appears to behave like `context.globalState` when outside of a workspace - // There is currently no way to remove the specified zig version. - const wantedZigVersion = context.workspaceState.get("zig-version"); - result = wantedZigVersion ? new semver.SemVer(wantedZigVersion) : null; - break; case WantedZigVersionSource.workspaceZigVersionFile: if (workspace) { const zigVersionString = await vscode.workspace.fs.readFile( @@ -465,7 +531,6 @@ async function updateStatus(context: vscode.ExtensionContext): Promise { case undefined: break; case "update Zig": { - await context.workspaceState.update("zig-version", undefined); // This will source the desired Zig version with `getWantedZigVersion` which may not satisfy the minimum Zig version. // This could happen for example when the a `.zigversion` specifies `0.12.0` but `minimum_zig_version` is `0.13.0`. // The extension would install `0.12.0` and then complain again. @@ -502,6 +567,8 @@ export async function setupZig(context: vscode.ExtensionContext) { } await zigConfig.update("initialSetupDone", undefined, true); + + await context.workspaceState.update("zig-version", undefined); } versionManagerConfig = { From 2fca42b946559795821bf8bf500d0e71eef590c2 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 18 Jan 2025 02:56:18 +0100 Subject: [PATCH 2/7] try to looking up zig in $PATH before defaulting to the latest release --- src/zigSetup.ts | 75 +++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 858c77f..9441506 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -24,18 +24,32 @@ async function installZig(context: vscode.ExtensionContext, temporaryVersion?: s context, Object.values(WantedZigVersionSource) as WantedZigVersionSource[], ); - if (!wantedZig) { - await zigProvider.setAndSave(null); - return; + version = wantedZig?.version; + if (wantedZig?.source === WantedZigVersionSource.workspaceBuildZigZon) { + version = await findClosestSatisfyingZigVersion(context, wantedZig.version); } + } - if (wantedZig.source === WantedZigVersionSource.workspaceBuildZigZon) { - version = await findClosestSatisfyingZigVersion(context, wantedZig.version); - } else { - version = wantedZig.version; + if (!version) { + // Lookup zig in $PATH + const result = resolveExePathAndVersion(null, "zig", null, "version"); + if ("exe" in result) { + await vscode.workspace.getConfiguration("zig").update("path", undefined, true); + zigProvider.set(result); + return; } } + if (!version) { + // Default to the latest tagged release + version = (await getLatestTaggedZigVersion(context)) ?? undefined; + } + + if (!version) { + await zigProvider.setAndSave(null); + return; + } + try { const exePath = await versionManager.install(versionManagerConfig, version); await vscode.workspace.getConfiguration("zig").update("path", undefined, true); @@ -69,6 +83,23 @@ async function findClosestSatisfyingZigVersion( } } +async function getLatestTaggedZigVersion(context: vscode.ExtensionContext): Promise { + const cacheKey = "zig-latest-tagged"; + try { + const zigVersion = await getVersions(); + const latestTagged = zigVersion.find((item) => item.version.prerelease.length === 0); + const result = latestTagged?.version ?? null; + await context.globalState.update(cacheKey, latestTagged?.version.raw); + return result; + } catch { + const latestTagged = context.globalState.get(cacheKey, null); + if (latestTagged) { + return new semver.SemVer(latestTagged); + } + return null; + } +} + /** * Returns a sorted list of all versions that are provided by Zig's [index.json](https://ziglang.org/download/index.json) and Mach's [index.json](https://pkg.machengine.org/zig/index.json). * [Nominated Zig versions](https://machengine.org/docs/nominated-zig/#nominated-zig-history) are sorted to the bottom. @@ -267,18 +298,9 @@ async function showUpdateWorkspaceVersionDialog( ): Promise { const workspace = getWorkspaceFolder(); - switch (source) { - case WantedZigVersionSource.latestTagged: - source = undefined; - // intentional fallthrough - case undefined: - source = workspace - ? WantedZigVersionSource.workspaceZigVersionFile - : WantedZigVersionSource.zigVersionConfigOption; - break; - default: - break; - } + source ??= workspace + ? WantedZigVersionSource.workspaceZigVersionFile + : WantedZigVersionSource.zigVersionConfigOption; let sourceName; switch (source) { @@ -380,7 +402,6 @@ enum WantedZigVersionSource { workspaceBuildZigZon = "build.zig.zon", /** `zig.version` */ zigVersionConfigOption = "zig.version", - latestTagged = "latest-tagged", } /** Try to resolve the (workspace-specific) Zig version. */ @@ -428,20 +449,6 @@ async function getWantedZigVersion( } } break; - case WantedZigVersionSource.latestTagged: - const cacheKey = "zig-latest-tagged"; - try { - const zigVersion = await getVersions(); - const latestTagged = zigVersion.find((item) => item.version.prerelease.length === 0); - result = latestTagged?.version ?? null; - await context.globalState.update(cacheKey, latestTagged?.version.raw); - } catch { - const latestTagged = context.globalState.get(cacheKey, null); - if (latestTagged) { - result = new semver.SemVer(latestTagged); - } - } - break; } } catch {} From 86fc0cc46a6ad6173964fc2492de04d97b56fb25 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 18 Jan 2025 03:09:26 +0100 Subject: [PATCH 3/7] ignore existing zig installation when modifying $PATH env variable This means that the version in the status bar is always the same as in the terminal. --- src/zigSetup.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 9441506..afb56b1 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -495,11 +495,9 @@ function updateLanguageStatusItem(item: vscode.LanguageStatusItem, version: semv function updateZigEnvironmentVariableCollection(context: vscode.ExtensionContext, zigExePath: string | null) { if (zigExePath) { - const envValue = path.delimiter + path.dirname(zigExePath); - // Calling `append` means that zig from a user-defined PATH value will take precedence. - // The added value may have already been added by the user but since we - // append, it doesn't have any observable. - context.environmentVariableCollection.append("PATH", envValue); + const envValue = path.dirname(zigExePath) + path.delimiter; + // This will take priority over a user-defined PATH values. + context.environmentVariableCollection.prepend("PATH", envValue); } else { context.environmentVariableCollection.delete("PATH"); } From c4d3d0b7fa64a2fe20037d5a26c6168c3269da2e Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 23 Jan 2025 18:41:30 +0100 Subject: [PATCH 4/7] improve wording of the update workspace version dialog --- src/zigSetup.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index afb56b1..5296db6 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -298,26 +298,28 @@ async function showUpdateWorkspaceVersionDialog( ): Promise { const workspace = getWorkspaceFolder(); - source ??= workspace - ? WantedZigVersionSource.workspaceZigVersionFile - : WantedZigVersionSource.zigVersionConfigOption; - - let sourceName; + let buttonName; switch (source) { case WantedZigVersionSource.workspaceZigVersionFile: - sourceName = ".ziversion"; + buttonName = "update .zigversion"; break; case WantedZigVersionSource.workspaceBuildZigZon: - sourceName = "build.zig.zon"; + buttonName = "update build.zig.zon"; break; case WantedZigVersionSource.zigVersionConfigOption: - sourceName = "workspace settings"; + buttonName = "update workspace settings"; + break; + default: + source = workspace + ? WantedZigVersionSource.workspaceZigVersionFile + : WantedZigVersionSource.zigVersionConfigOption; + buttonName = workspace ? "create .zigversion" : "update settings"; break; } const response = await vscode.window.showInformationMessage( - "Would you like to save this version for this workspace?", - `update ${sourceName}`, + `Would you like to save Zig ${version.toString()} in this workspace?`, + buttonName, ); if (!response) return; From 1719f219806fdd996cf487568ea4dfca43c5ac38 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 25 Jan 2025 18:16:28 +0100 Subject: [PATCH 5/7] show no dialog when changing the zig version outside of a workspace --- src/zigSetup.ts | 47 +++++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 5296db6..230bbdd 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -298,30 +298,33 @@ async function showUpdateWorkspaceVersionDialog( ): Promise { const workspace = getWorkspaceFolder(); - let buttonName; - switch (source) { - case WantedZigVersionSource.workspaceZigVersionFile: - buttonName = "update .zigversion"; - break; - case WantedZigVersionSource.workspaceBuildZigZon: - buttonName = "update build.zig.zon"; - break; - case WantedZigVersionSource.zigVersionConfigOption: - buttonName = "update workspace settings"; - break; - default: - source = workspace - ? WantedZigVersionSource.workspaceZigVersionFile - : WantedZigVersionSource.zigVersionConfigOption; - buttonName = workspace ? "create .zigversion" : "update settings"; - break; + if (workspace !== null) { + let buttonName; + switch (source) { + case WantedZigVersionSource.workspaceZigVersionFile: + buttonName = "update .zigversion"; + break; + case WantedZigVersionSource.workspaceBuildZigZon: + buttonName = "update build.zig.zon"; + break; + case WantedZigVersionSource.zigVersionConfigOption: + buttonName = "update workspace settings"; + break; + case undefined: + buttonName = "create .zigversion"; + break; + } + + const response = await vscode.window.showInformationMessage( + `Would you like to save Zig ${version.toString()} in this workspace?`, + buttonName, + ); + if (!response) return; } - const response = await vscode.window.showInformationMessage( - `Would you like to save Zig ${version.toString()} in this workspace?`, - buttonName, - ); - if (!response) return; + source ??= workspace + ? WantedZigVersionSource.workspaceZigVersionFile + : WantedZigVersionSource.zigVersionConfigOption; switch (source) { case WantedZigVersionSource.workspaceZigVersionFile: { From 539c859d48a640598d4fb3f4c006da5c7bfc00de Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 25 Jan 2025 18:24:23 +0100 Subject: [PATCH 6/7] show the update workspace version dialog before changing zig version When sourcing the zig version from the build.zig.zon and then manually changing the zig to a version below the specified minimum, the following two messages will be shown: - "Your Zig version ... does not satify the minimum Zig version" - "Would you like to save Zig ... in this workspace?" Showing the dialog before changing the zig version will make sure that only the second message will be shown. --- src/zigSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 230bbdd..78fb3bc 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -286,8 +286,8 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) { break; default: const version = new semver.SemVer(selection.detail ?? selection.label); + await showUpdateWorkspaceVersionDialog(version, workspaceZig?.source); await installZig(context, version); - void showUpdateWorkspaceVersionDialog(version, workspaceZig?.source); break; } } From 2afff4fa1aec64affa985d599d1fca4ef4e58e18 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sat, 25 Jan 2025 18:28:36 +0100 Subject: [PATCH 7/7] fix typo 'satify' -> 'satisfy' --- src/zigSetup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zigSetup.ts b/src/zigSetup.ts index 78fb3bc..b6fad59 100644 --- a/src/zigSetup.ts +++ b/src/zigSetup.ts @@ -532,7 +532,7 @@ async function updateStatus(context: vscode.ExtensionContext): Promise { void vscode.window .showWarningMessage( - `Your Zig version '${zigVersion.toString()}' does not satify the minimum Zig version '${buildZigZonMetadata.minimumZigVersion.toString()}' of your project.`, + `Your Zig version '${zigVersion.toString()}' does not satisfy the minimum Zig version '${buildZigZonMetadata.minimumZigVersion.toString()}' of your project.`, "update Zig", "open build.zig.zon", )