diff --git a/lsp/README.md b/lsp/README.md index 5cf0e129..3d3955fb 100644 --- a/lsp/README.md +++ b/lsp/README.md @@ -22,12 +22,10 @@ Language Server for [SimplicityHL language](https://simplicity-lang.org/). ## Installation -Clone this repository and install using Cargo: +Install Language Server using `cargo`: ```bash -https://github.com/distributed-lab/simplicityhl-lsp -cd simplicityhl-lsp -cargo install --path . +cargo install simplicityhl-lsp ``` ## Integration with editors diff --git a/vscode/package-lock.json b/vscode/package-lock.json index e908eed6..f5681bb3 100644 --- a/vscode/package-lock.json +++ b/vscode/package-lock.json @@ -1,12 +1,12 @@ { "name": "simplicityhl", - "version": "0.2.0", + "version": "0.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simplicityhl", - "version": "0.2.0", + "version": "0.2.2", "license": "MIT", "dependencies": { "vscode-languageclient": "^9.0.1" diff --git a/vscode/package.json b/vscode/package.json index 130f4a3b..d27de9d3 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,7 @@ "name": "simplicityhl", "displayName": "SimplicityHL Language Support", "description": "Syntax highlighting and autocompletion for SimplicityHL (Simfony) language", - "version": "0.2.1", + "version": "0.2.2", "publisher": "Blockstream", "repository": { "type": "git", @@ -67,6 +67,11 @@ "type": "boolean", "default": false, "description": "Do not show missing LSP executable warning." + }, + "simplicityhl.disableAutoupdate": { + "type": "boolean", + "default": false, + "description": "Do not autoupdate LSP server." } } }, diff --git a/vscode/src/client.ts b/vscode/src/client.ts index 975c75b3..96471262 100644 --- a/vscode/src/client.ts +++ b/vscode/src/client.ts @@ -53,11 +53,12 @@ export class LspClient { } } - public stop(): Thenable | undefined { + public async stop(): Promise { if (!this.client) { - return undefined; + return; } - return this.client.stop(); + await this.client.stop(); + this.client = undefined; } public async restart(): Promise { @@ -67,8 +68,8 @@ export class LspClient { } try { - await this.client.stop(); - await this.client.start(); + await this.stop(); + await this.start(); window.showInformationMessage("SimplicityHL Language Server restarted successfully!"); } catch (e) { window.showErrorMessage(`Failed to restart LSP: ${e}`); diff --git a/vscode/src/find_server.ts b/vscode/src/find_server.ts index c57fdccb..cc1af06f 100644 --- a/vscode/src/find_server.ts +++ b/vscode/src/find_server.ts @@ -1,9 +1,11 @@ -import * as cp from "child_process"; +import * as os from "os"; import * as fs from "fs"; import * as path from "path"; -import * as os from "os"; -import { env, Uri, window, workspace } from "vscode"; + import process from "node:process"; +import * as cp from "child_process"; + +import { env, ProgressLocation, Uri, window, workspace } from "vscode"; function findExecutable(command: string): string | null { try { @@ -51,27 +53,81 @@ function findExecutable(command: string): string | null { return null; } +async function installServer(command: string) { + const cargoPath = findExecutable("cargo"); + if (!cargoPath) { + throw new Error("Unable to find 'cargo'. Please ensure Rust is installed and in your PATH."); + } + + const action = findExecutable(command) ? "Updating" : "Installing"; + + return window.withProgress({ + location: ProgressLocation.Notification, + title: `${action} ${command}`, + cancellable: true + }, async (progress, token) => { + return new Promise((resolve, reject) => { + const installProcess = cp.spawn(cargoPath!, ["install", "--color", "never", command]); + + token.onCancellationRequested(() => { + installProcess.kill("SIGTERM"); + reject(new Error("Installation canceled")); + }); + + const reportProgress = (data: Buffer) => { + const lines = data.toString() + .split(/\r?\n/) + .map(l => l.trim()) + + for (const line of lines) { + if (line.startsWith("Compiling") && line !== "Compiling") { + progress.report({ message: line }); + } + } + }; + + installProcess.stderr?.on('data', reportProgress); + + installProcess.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Installation failed with exit code ${code}`)); + } + }); + + installProcess.on('error', (err) => { + reject(new Error(`Failed to start cargo process: ${err.message}`)); + }); + }); + }); +} + export async function ensureExecutable( command: string, ): Promise { - const exePath = findExecutable(command); + const cargoPath = findExecutable("cargo"); const config = workspace.getConfiguration("simplicityhl"); - const suppressWarning = config.get( - "suppressMissingLspWarning", - false, - ); + let serverPath = findExecutable(command); + + if (!cargoPath && !serverPath) { + const suppressWarning = config.get( + "suppressMissingLspWarning", + false, + ); + if (suppressWarning) { + return null; + } - if (!exePath && !suppressWarning) { const choice = await window.showWarningMessage( - `LSP server "${command}" was not found in PATH or common locations. To use language server feautures, please install server to PATH`, + `To use SimplicityHL language server, please install cargo`, "Learn more", "Don't show again", - "Close", ); if (choice === "Learn more") { - const url = "https://github.com/distributed-lab/simplicityhl-lsp"; + const url = "https://rust-lang.org/tools/install"; await env.openExternal(Uri.parse(url)); } else if (choice === "Don't show again") { const config = workspace.getConfiguration("simplicityhl"); @@ -80,5 +136,25 @@ export async function ensureExecutable( return null; } - return exePath; + + if (!cargoPath) { + return serverPath; + } + + const disableAutoupdate = config.get("disableAutoupdate", false); + + if (serverPath && disableAutoupdate) { + return serverPath; + } + + try { + await installServer(command); + + serverPath = findExecutable(command); + } catch (err) { + window.showErrorMessage(err); + return null; + } + + return serverPath; }