Skip to content

Commit

Permalink
rework initial setup and implement installing specific zls version
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexu committed Sep 28, 2023
1 parent b7245a3 commit 47d0527
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 435 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.5.0
- Rework initial setup and installation management
- Add new zls hint settings (@leecannon)

## 0.4.3
- Fix checking for ZLS updates
- Always check `PATH` when `zigPath` is set to empty string
Expand Down
15 changes: 2 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,11 @@
"default": "${workspaceFolder}/build.zig",
"description": "The path to build.zig. This is only required if zig.buildOptions = build."
},
"zig.zigPath": {
"zig.path": {
"type": "string",
"default": null,
"description": "Set a custom path to the Zig binary. Empty string will lookup zig in PATH."
},
"zig.zigVersion": {
"type": "string",
"default": null,
"description": "Version of Zig to install"
},
"zig.checkForUpdate": {
"scope": "resource",
"type": "boolean",
Expand Down Expand Up @@ -161,12 +156,6 @@
],
"default": "extension"
},
"zig.zls.enabled": {
"scope": "resource",
"type": "boolean",
"description": "Whether to enable zls",
"default": true
},
"zig.trace.server": {
"scope": "window",
"type": "string",
Expand All @@ -181,7 +170,7 @@
"zig.zls.checkForUpdate": {
"scope": "resource",
"type": "boolean",
"description": "Whether to automatically check for new updates",
"description": "Whether to automatically check for new updates for nightly version",
"default": true
},
"zig.zls.path": {
Expand Down
29 changes: 9 additions & 20 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"use strict";
import * as vscode from "vscode";
import ZigCompilerProvider from "./zigCompilerProvider";
import { zigBuild } from "./zigBuild";
import ZigCompilerProvider from "./zigCompilerProvider";
import { ZigFormatProvider, ZigRangeFormatProvider } from "./zigFormat";
import { activate as activateZls, deactivate as deactivateZls } from "./zls";
import { setupZig } from "./zigSetup";
import { activate as activateZls, deactivate as deactivateZls } from "./zls";

const ZIG_MODE: vscode.DocumentFilter = { language: "zig", scheme: "file" };

Expand All @@ -17,10 +17,10 @@ export function activate(context: vscode.ExtensionContext) {
const compiler = new ZigCompilerProvider();
compiler.activate(context.subscriptions);
vscode.languages.registerCodeActionsProvider("zig", compiler);

context.subscriptions.push(logChannel);
if (vscode.workspace.getConfiguration("zig").get<string>("formattingProvider", "extension") === "extension") {

if (vscode.workspace.getConfiguration("zig").get<string>("formattingProvider") === "extension") {
context.subscriptions.push(
vscode.languages.registerDocumentFormattingEditProvider(
ZIG_MODE,
Expand All @@ -34,28 +34,17 @@ export function activate(context: vscode.ExtensionContext) {
),
);
}

buildDiagnosticCollection = vscode.languages.createDiagnosticCollection("zig");
context.subscriptions.push(buildDiagnosticCollection);

// Commands
context.subscriptions.push(vscode.commands.registerCommand("zig.build.workspace", () => zigBuild()));
activateZls(context);

activateZls(context)
});
}

export function deactivate() {
deactivateZls();
}

// Check timestamp `key` to avoid automatically checking for updates
// more than once in an hour.
export function shouldCheckUpdate(context: vscode.ExtensionContext, key: string): boolean {
const HOUR = 60 * 60 * 1000;
const timestamp = new Date().getTime();
const old = context.globalState.get<number>(key);
if (old === undefined || timestamp - old < HOUR) {return false;}
context.globalState.update(key, timestamp);
return true;
}
2 changes: 1 addition & 1 deletion src/zigCompilerProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class ZigCompilerProvider implements vscode.CodeActionProvider {

maybeDoASTGenErrorCheck(change: vscode.TextDocumentChangeEvent) {
if (change.document.languageId !== "zig") {return;}
if (vscode.workspace.getConfiguration("zig").get<string>("astCheckProvider", "zls") !== "extension") {
if (vscode.workspace.getConfiguration("zig").get<string>("astCheckProvider") !== "extension") {
this.astDiagnostics.clear();
return;
}
Expand Down
157 changes: 88 additions & 69 deletions src/zigSetup.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { ExtensionContext, window, workspace } from "vscode";

import axios from "axios";
import * as child_process from "child_process";
import { createHash } from "crypto";
import * as fs from "fs";
import mkdirp from "mkdirp";
import semver from "semver";
import * as vscode from "vscode";
import { shouldCheckUpdate } from "./extension";
import { execCmd, isWindows } from "./zigUtil";
import { execCmd, getHostZigName, isWindows, shouldCheckUpdate } from "./zigUtil";
import { install as installZLS } from "./zls";

const DOWNLOAD_INDEX = "https://ziglang.org/download/index.json";

function getHostZigName(): string {
let os: string = process.platform;
if (os === "darwin") {os = "macos";}
if (os === "win32") {os = "windows";}
let arch: string = process.arch;
if (arch === "ia32") {arch = "x86";}
if (arch === "x64") {arch = "x86_64";}
if (arch === "arm64") {arch = "aarch64";}
if (arch === "ppc") {arch = "powerpc";}
if (arch === "ppc64") {arch = "powerpc64le";}
return `${arch}-${os}`;
}

function getNightlySemVer(url: string): string {
return url.match(/-(\d+\.\d+\.\d+-dev\.\d+\+\w+)\./)[1];
}
Expand All @@ -38,7 +26,7 @@ async function getVersions(): Promise<ZigVersion[]> {
const result: ZigVersion[] = [];
// eslint-disable-next-line prefer-const
for (let [key, value] of Object.entries(indexJson)) {
if (key === "master") {key = "nightly";}
if (key === "master") { key = "nightly"; }
if (value[hostName]) {
result.push({
name: key,
Expand All @@ -53,7 +41,7 @@ async function getVersions(): Promise<ZigVersion[]> {
return result;
}

async function installZig(context: ExtensionContext, version: ZigVersion): Promise<void> {
async function install(context: ExtensionContext, version: ZigVersion) {
await window.withProgress({
title: "Installing Zig...",
location: vscode.ProgressLocation.Notification,
Expand All @@ -68,12 +56,12 @@ async function installZig(context: ExtensionContext, version: ZigVersion): Promi
}

const installDir = vscode.Uri.joinPath(context.globalStorageUri, "zig_install");
if (fs.existsSync(installDir.fsPath)) {fs.rmSync(installDir.fsPath, { recursive: true, force: true });}
if (fs.existsSync(installDir.fsPath)) { fs.rmSync(installDir.fsPath, { recursive: true, force: true }); }
mkdirp.sync(installDir.fsPath);

progress.report({ message: "Decompressing..." });
progress.report({ message: "Extracting..." });
const tar = execCmd("tar", {
cmdArguments: ["-xJf", "-", "-C", `${installDir.fsPath}`, "--strip-components=1"],
cmdArguments: ["-xJf", "-", "-C", installDir.fsPath, "--strip-components=1"],
notFoundText: "Could not find tar",
});
tar.stdin.write(tarball);
Expand All @@ -86,12 +74,11 @@ async function installZig(context: ExtensionContext, version: ZigVersion): Promi
fs.chmodSync(zigPath, 0o755);

const configuration = workspace.getConfiguration("zig");
await configuration.update("zigPath", zigPath, true);
await configuration.update("zigVersion", version.name, true);
await configuration.update("path", zigPath, true);
});
}

async function selectVersionAndInstall(context: ExtensionContext): Promise<void> {
async function selectVersionAndInstall(context: ExtensionContext) {
try {
const available = await getVersions();

Expand All @@ -106,13 +93,10 @@ async function selectVersionAndInstall(context: ExtensionContext): Promise<void>
canPickMany: false,
placeHolder,
});
if (selection === undefined) {return;}
if (selection === undefined) { return; }
for (const option of available) {
if (option.name === selection.label) {
if (option.name === "nightly") {
option.name = `nightly-${getNightlySemVer(option.url)}`;
}
await installZig(context, option);
await install(context, option);
return;
}
}
Expand All @@ -121,14 +105,14 @@ async function selectVersionAndInstall(context: ExtensionContext): Promise<void>
}
}

async function checkUpdate(context: ExtensionContext): Promise<void> {
async function checkUpdate(context: ExtensionContext) {
try {
const update = await getUpdatedVersion(context);
if (!update) {return;}
if (!update) return;

const response = await window.showInformationMessage(`New version of Zig available: ${update.name}`, "Install", "Cancel");
const response = await window.showInformationMessage(`New version of Zig available: ${update.name}`, "Install", "Ignore");
if (response === "Install") {
await installZig(context, update);
await install(context, update);
}
} catch (err) {
window.showErrorMessage(`Unable to update Zig: ${err}`);
Expand All @@ -137,26 +121,25 @@ async function checkUpdate(context: ExtensionContext): Promise<void> {

async function getUpdatedVersion(context: ExtensionContext): Promise<ZigVersion | null> {
const configuration = workspace.getConfiguration("zig");
const zigPath = configuration.get<string | null>("zigPath", null);
const zigPath = configuration.get<string | null>("path");
if (zigPath) {
const zigBinPath = vscode.Uri.joinPath(context.globalStorageUri, "zig_install", "zig").fsPath;
if (!zigPath.startsWith(zigBinPath)) {return null;}
if (!zigPath.startsWith(zigBinPath)) return null;
}

const version = configuration.get<string | null>("zigVersion", null);
if (!version) {return null;}
const buffer = child_process.execFileSync(zigPath, ["version"]);
const curVersion = semver.parse(buffer.toString("utf8"));

const available = await getVersions();
if (version.startsWith("nightly")) {
if (available[0].name === "nightly") {
const curVersion = version.slice("nightly-".length);
if (curVersion.prerelease.length != 0) {
if (available[0].name == "nightly") {
const newVersion = getNightlySemVer(available[0].url);
if (semver.gt(newVersion, curVersion)) {
available[0].name = `nightly-${newVersion}`;
return available[0];
}
}
} else if (available.length > 2 && semver.gt(available[1].name, version)) {
} else if (available.length > 2 && semver.gt(available[1].name, curVersion)) {
return available[1];
}
return null;
Expand All @@ -165,43 +148,79 @@ async function getUpdatedVersion(context: ExtensionContext): Promise<ZigVersion
export async function setupZig(context: ExtensionContext) {
vscode.commands.registerCommand("zig.install", async () => {
await selectVersionAndInstall(context);
await installZLS(context, true);
});

vscode.commands.registerCommand("zig.update", async () => {
await checkUpdate(context);
});

if (context.globalState.get<boolean>("initialSetupDone")) {
await initialSetup(context);
context.globalState.update("initialSetupDone", true);
}

const configuration = workspace.getConfiguration("zig", null);
if (configuration.get<string | null>("zigPath", null) === null) {
const response = await window.showInformationMessage(
"Zig path hasn't been set, do you want to specify the path or install Zig?",
"Install", "Specify path", "Use Zig in PATH"
);
if (configuration.get<string | null>("path") === null) return;
if (!configuration.get<boolean>("checkForUpdate")) return;
if (!shouldCheckUpdate(context, "zigUpdate")) return;
await checkUpdate(context);
}

if (response === "Install") {
await selectVersionAndInstall(context);
const configuration = workspace.getConfiguration("zig", null);
const zigPath = configuration.get<string | null>("zigPath", null);
if (!zigPath) {return;}
window.showInformationMessage(`Zig was installed at '${zigPath}', add it to PATH to use it from the terminal`);
return;
} else if (response === "Specify path") {
const uris = await window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
title: "Select Zig executable",
});
async function initialSetup(context: ExtensionContext) {
const zigConfig = workspace.getConfiguration("zig", null);
const zigResponse = await window.showInformationMessage(
"Zig path hasn't been set, do you want to specify the path or install Zig?",
"Install", "Specify path", "Use Zig in PATH"
);

if (uris) {
await configuration.update("zigPath", uris[0].fsPath, true);
}
} else if (response === "Use Zig in PATH") {
await configuration.update("zigPath", "", true);
} else {throw Error("zigPath not specified");}
}
if (zigResponse === "Install") {
await selectVersionAndInstall(context);
const configuration = workspace.getConfiguration("zig", null);
const path = configuration.get<string | null>("path");
if (!path) return;
window.showInformationMessage(`Zig was installed at '${path}', add it to PATH to use it from the terminal`);
} else if (zigResponse === "Specify path") {
const uris = await window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
title: "Select Zig executable",
});
if (!uris) return;

const buffer = child_process.execFileSync(uris[0].path, ["version"]);
const version = semver.parse(buffer.toString("utf8"));
if (!version) return;

await zigConfig.update("path", uris[0].path, true);
} else if (zigResponse === "Use Zig in PATH") {
await zigConfig.update("path", "", true);
} else return;

const zlsConfig = workspace.getConfiguration("zig.zls", null);
const zlsResponse = await window.showInformationMessage(
"We recommend enabling ZLS (the Zig Language Server) for a better editing experience. Would you like to install it?",
"Install", "Specify path", "Use ZLS in PATH", "Ignore"
);

if (zlsResponse === "Install") {
await installZLS(context, false);
} else if (zlsResponse === "Specify path") {
const uris = await window.showOpenDialog({
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
title: "Select Zig Language Server (ZLS) executable",
});
if (!uris) return;

if (!shouldCheckUpdate(context, "zigUpdate")) {return;}
if (!configuration.get<boolean>("checkForUpdate", true)) {return;}
await checkUpdate(context);
const buffer = child_process.execFileSync(uris[0].path, ["--version"]);
const version = semver.parse(buffer.toString("utf8"));
if (!version) return;

await zlsConfig.update("path", uris[0].path, true);
} else if (zlsResponse === "Use ZLS in PATH") {
await zlsConfig.update("path", "", true);
}
}
Loading

0 comments on commit 47d0527

Please sign in to comment.