Skip to content

Commit

Permalink
guard against exceptions when updating configuration
Browse files Browse the repository at this point in the history
When an exception occurs because it is updating a read-only
settings.json to remove the outdated `initialSetupDone` config option,
the extensions fails to initialize properly.

See #392
  • Loading branch information
Techatrix committed Feb 6, 2025
1 parent 6bbd98e commit 30a3420
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 17 deletions.
7 changes: 4 additions & 3 deletions src/zigProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import vscode from "vscode";

import semver from "semver";

import { resolveExePathAndVersion } from "./zigUtil";
import { resolveExePathAndVersion, workspaceConfigUpdateNoThrow } from "./zigUtil";

interface ExeWithVersion {
exe: string;
Expand Down Expand Up @@ -50,13 +50,14 @@ export class ZigProvider implements vscode.Disposable {
* @param zigPath The path to the zig executable. If `null`, the `zig.path` config option will be removed.
*/
public async setAndSave(zigPath: string | null) {
const zigConfig = vscode.workspace.getConfiguration("zig");
if (!zigPath) {
await vscode.workspace.getConfiguration("zig").update("path", undefined, true);
await workspaceConfigUpdateNoThrow(zigConfig, "path", undefined, true);
return;
}
const newValue = this.resolveZigPathConfigOption(zigPath);
if (!newValue) return;
await vscode.workspace.getConfiguration("zig").update("path", newValue.exe, true);
await workspaceConfigUpdateNoThrow(zigConfig, "path", newValue.exe, true);
this.set(newValue);
}

Expand Down
19 changes: 14 additions & 5 deletions src/zigSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@ import semver from "semver";

import * as minisign from "./minisign";
import * as versionManager from "./versionManager";
import { VersionIndex, ZigVersion, asyncDebounce, getHostZigName, resolveExePathAndVersion } from "./zigUtil";
import {
VersionIndex,
ZigVersion,
asyncDebounce,
getHostZigName,
resolveExePathAndVersion,
workspaceConfigUpdateNoThrow,
} from "./zigUtil";
import { ZigProvider } from "./zigProvider";

let statusItem: vscode.StatusBarItem;
Expand Down Expand Up @@ -52,7 +59,8 @@ async function installZig(context: vscode.ExtensionContext, temporaryVersion?: s

try {
const exePath = await versionManager.install(versionManagerConfig, version);
await vscode.workspace.getConfiguration("zig").update("path", undefined, true);
const zigConfig = vscode.workspace.getConfiguration("zig");
await workspaceConfigUpdateNoThrow(zigConfig, "path", undefined, true);
zigProvider.set({ exe: exePath, version: version });
} catch (err) {
zigProvider.set(null);
Expand Down Expand Up @@ -272,7 +280,8 @@ async function selectVersionAndInstall(context: vscode.ExtensionContext) {
await installZig(context);
break;
case "Use Zig in PATH":
await vscode.workspace.getConfiguration("zig").update("path", "zig", true);
const zigConfig = vscode.workspace.getConfiguration("zig");
await workspaceConfigUpdateNoThrow(zigConfig, "path", "zig", true);
break;
case "Manually Specify Path":
const uris = await vscode.window.showOpenDialog({
Expand Down Expand Up @@ -573,10 +582,10 @@ export async function setupZig(context: vscode.ExtensionContext) {
const zigConfig = vscode.workspace.getConfiguration("zig");
const zigPath = zigConfig.get<string>("path", "");
if (zigPath.startsWith(context.globalStorageUri.fsPath)) {
await zigConfig.update("path", undefined, true);
await workspaceConfigUpdateNoThrow(zigConfig, "path", undefined, true);
}

await zigConfig.update("initialSetupDone", undefined, true);
await workspaceConfigUpdateNoThrow(zigConfig, "initialSetupDone", undefined, true);

await context.workspaceState.update("zig-version", undefined);
}
Expand Down
22 changes: 22 additions & 0 deletions src/zigUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,28 @@ export function asyncDebounce<T extends (...args: unknown[]) => Promise<Awaited<
});
}

/**
* Wrapper around `vscode.WorkspaceConfiguration.update` that doesn't throw an exception.
* A common cause of an exception is when the `settings.json` file is read-only.
*/
export async function workspaceConfigUpdateNoThrow(
config: vscode.WorkspaceConfiguration,
section: string,
value: unknown,
configurationTarget?: vscode.ConfigurationTarget | boolean | null,
overrideInLanguage?: boolean,
): Promise<void> {
try {
await config.update(section, value, configurationTarget, overrideInLanguage);
} catch (err) {
if (err instanceof Error) {
void vscode.window.showErrorMessage(err.message);
} else {
void vscode.window.showErrorMessage("failed to update settings.json");
}
}
}

// Check timestamp `key` to avoid automatically checking for updates
// more than once in an hour.
export async function shouldCheckUpdate(context: vscode.ExtensionContext, key: string): Promise<boolean> {
Expand Down
23 changes: 14 additions & 9 deletions src/zls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import semver from "semver";

import * as minisign from "./minisign";
import * as versionManager from "./versionManager";
import { getHostZigName, handleConfigOption, resolveExePathAndVersion } from "./zigUtil";
import { getHostZigName, handleConfigOption, resolveExePathAndVersion, workspaceConfigUpdateNoThrow } from "./zigUtil";
import { zigProvider } from "./zigSetup";

const ZIG_MODE: DocumentSelector = [
Expand Down Expand Up @@ -201,8 +201,13 @@ async function configurationMiddleware(
switch (response) {
case `Use ${optionName} instead`:
const { [optionName]: newValue, ...updatedAdditionalOptions } = additionalOptions;
await configuration.update("additionalOptions", updatedAdditionalOptions, true);
await configuration.update(section, newValue, true);
await workspaceConfigUpdateNoThrow(
configuration,
"additionalOptions",
updatedAdditionalOptions,
true,
);
await workspaceConfigUpdateNoThrow(configuration, section, newValue, true);
break;
case "Show zig.zls.additionalOptions":
await vscode.commands.executeCommand("workbench.action.openSettingsJson", {
Expand Down Expand Up @@ -337,10 +342,10 @@ async function isEnabled(): Promise<boolean> {
);
switch (response) {
case "Yes":
await zlsConfig.update("enabled", "on", true);
await workspaceConfigUpdateNoThrow(zlsConfig, "enabled", "on", true);
return true;
case "No":
await zlsConfig.update("enabled", "off", true);
await workspaceConfigUpdateNoThrow(zlsConfig, "enabled", "off", true);
return false;
case undefined:
return false;
Expand Down Expand Up @@ -391,8 +396,8 @@ export async function activate(context: vscode.ExtensionContext) {
const zlsConfig = vscode.workspace.getConfiguration("zig.zls");
const zlsPath = zlsConfig.get<string>("path", "");
if (zlsPath.startsWith(context.globalStorageUri.fsPath)) {
await zlsConfig.update("enabled", "on", true);
await zlsConfig.update("path", undefined, true);
await workspaceConfigUpdateNoThrow(zlsConfig, "enabled", "on", true);
await workspaceConfigUpdateNoThrow(zlsConfig, "path", undefined, true);
}
}

Expand Down Expand Up @@ -421,14 +426,14 @@ export async function activate(context: vscode.ExtensionContext) {
statusItem,
vscode.commands.registerCommand("zig.zls.enable", async () => {
const zlsConfig = vscode.workspace.getConfiguration("zig.zls");
await zlsConfig.update("enabled", "on", true);
await workspaceConfigUpdateNoThrow(zlsConfig, "enabled", "on", true);
}),
vscode.commands.registerCommand("zig.zls.stop", async () => {
await stopClient();
}),
vscode.commands.registerCommand("zig.zls.startRestart", async () => {
const zlsConfig = vscode.workspace.getConfiguration("zig.zls");
await zlsConfig.update("enabled", "on", true);
await workspaceConfigUpdateNoThrow(zlsConfig, "enabled", "on", true);
await restartClient(context);
}),
vscode.commands.registerCommand("zig.zls.openOutput", () => {
Expand Down

0 comments on commit 30a3420

Please sign in to comment.