Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework initial setup and implement installing specific ZLS version #138

Merged
merged 5 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
20 changes: 7 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
"type": "object",
"title": "Zig",
"properties": {
"zig.initialSetupDone": {
"type": "boolean",
"default": false,
"description": "Has the initial setup been done yet?"
},
"zig.buildOnSave": {
"type": "boolean",
"default": false,
Expand Down Expand Up @@ -98,16 +103,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 +161,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 +175,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
160 changes: 92 additions & 68 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>("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,84 @@ 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);
});

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"
const configuration = workspace.getConfiguration("zig");
if (!configuration.get<boolean>("initialSetupDone")) {
await configuration.update("initialSetupDone",
await initialSetup(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",
});
if (!configuration.get<string>("path")) return;
if (!configuration.get<boolean>("checkForUpdate")) return;
if (!shouldCheckUpdate(context, "zigUpdate")) return;
await checkUpdate(context);
}

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");}
async function initialSetup(context: ExtensionContext): Promise<boolean> {
const zigConfig = workspace.getConfiguration("zig");
const zigResponse = await window.showInformationMessage(
"Zig path hasn't been set, do you want to specify the path or install Zig?",
{ modal: true },
"Install", "Specify path", "Use Zig in PATH"
);

if (zigResponse === "Install") {
await selectVersionAndInstall(context);
const configuration = workspace.getConfiguration("zig");
const path = configuration.get<string>("path");
if (!path) return false;
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 false;

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

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

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

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 true;

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

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

if (!shouldCheckUpdate(context, "zigUpdate")) {return;}
if (!configuration.get<boolean>("checkForUpdate", true)) {return;}
await checkUpdate(context);
return true;
}
Loading