mirror of
https://github.com/open-goal/opengoal-vscode.git
synced 2024-10-20 12:57:36 -04:00
lsp: support OpenGOAL and improve the lsp local story
This commit is contained in:
parent
1995dfb878
commit
27110517db
183
src/lsp/main.ts
183
src/lsp/main.ts
|
@ -1,65 +1,98 @@
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import {
|
import {
|
||||||
|
BaseLanguageClient,
|
||||||
|
ClientCapabilities,
|
||||||
|
FeatureState,
|
||||||
LanguageClient,
|
LanguageClient,
|
||||||
LanguageClientOptions,
|
LanguageClientOptions,
|
||||||
ServerOptions,
|
ServerOptions,
|
||||||
|
StaticFeature,
|
||||||
TransportKind,
|
TransportKind,
|
||||||
|
WorkDoneProgress,
|
||||||
|
WorkDoneProgressCreateRequest,
|
||||||
} from "vscode-languageclient/node";
|
} from "vscode-languageclient/node";
|
||||||
import { getConfig } from "../config/config";
|
import { getConfig } from "../config/config";
|
||||||
import { downloadLsp } from "./download";
|
import { downloadLsp } from "./download";
|
||||||
import { getLatestVersion, getLspPath, getVersionFromMetaFile } from "./util";
|
import { getLatestVersion, getLspPath, getVersionFromMetaFile } from "./util";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { disposeAll } from "../vendor/vscode-pdfviewer/disposable";
|
||||||
|
|
||||||
let extensionContext: vscode.ExtensionContext;
|
let extensionContext: vscode.ExtensionContext;
|
||||||
let opengoalLspPath: string | undefined;
|
let opengoalLspPath: string | undefined;
|
||||||
let activeClient: LanguageClient | undefined;
|
let activeClient: LanguageClient | undefined;
|
||||||
|
|
||||||
const lspStatusItem = vscode.window.createStatusBarItem(
|
type LspStatus =
|
||||||
vscode.StatusBarAlignment.Left,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
export type LspStatus =
|
|
||||||
| "stopped"
|
| "stopped"
|
||||||
| "starting"
|
| "starting"
|
||||||
| "started"
|
| "started"
|
||||||
| "downloading"
|
| "downloading"
|
||||||
| "error";
|
| "error"
|
||||||
export let lspStatus: LspStatus = "stopped";
|
| "serverProgressBegin"
|
||||||
|
| "serverProgressEnd";
|
||||||
|
|
||||||
function updateStatus(status: LspStatus, extraInfo?: string) {
|
// TODO - rust analyzer's context menu on hover is nice
|
||||||
lspStatus = status;
|
class LSPStatusItem {
|
||||||
switch (status) {
|
private currentStatus: LspStatus = "stopped";
|
||||||
case "stopped":
|
|
||||||
lspStatusItem.text = "$(circle-outline) OpenGOAL LSP Stopped";
|
constructor(private readonly statusItem: vscode.StatusBarItem) {}
|
||||||
lspStatusItem.tooltip = "Launch LSP";
|
|
||||||
lspStatusItem.command = "opengoal.lsp.start";
|
public updateStatus(status: LspStatus, extraInfo?: string) {
|
||||||
break;
|
this.currentStatus = status;
|
||||||
case "starting":
|
switch (this.currentStatus) {
|
||||||
lspStatusItem.text = "$(loading~spin) OpenGOAL LSP Starting";
|
case "stopped":
|
||||||
lspStatusItem.tooltip = "LSP Starting";
|
this.statusItem.text = "$(circle-outline) OpenGOAL LSP Stopped";
|
||||||
lspStatusItem.command = undefined;
|
this.statusItem.tooltip = "Launch LSP";
|
||||||
break;
|
this.statusItem.command = "opengoal.lsp.start";
|
||||||
case "started":
|
break;
|
||||||
lspStatusItem.text = "$(circle-filled) OpenGOAL LSP Ready";
|
case "starting":
|
||||||
lspStatusItem.tooltip = `LSP Active - ${extraInfo}`;
|
this.statusItem.text = "$(loading~spin) OpenGOAL LSP Starting";
|
||||||
lspStatusItem.command = "opengoal.lsp.showLspStartedMenu";
|
this.statusItem.tooltip = "LSP Starting";
|
||||||
break;
|
this.statusItem.command = undefined;
|
||||||
case "downloading":
|
break;
|
||||||
lspStatusItem.text = `$(sync~spin) OpenGOAL LSP Downloading - ${extraInfo}`;
|
case "started":
|
||||||
lspStatusItem.tooltip = `Downloading version - ${extraInfo}`;
|
this.statusItem.text = "$(circle-filled) OpenGOAL LSP Ready";
|
||||||
lspStatusItem.command = undefined;
|
this.statusItem.tooltip = `LSP Active - ${extraInfo}`;
|
||||||
break;
|
this.statusItem.command = "opengoal.lsp.showLspStartedMenu";
|
||||||
case "error":
|
break;
|
||||||
lspStatusItem.text = "$(error) OpenGOAL LSP Error";
|
case "downloading":
|
||||||
lspStatusItem.tooltip = "LSP not running due to an error";
|
this.statusItem.text = `$(sync~spin) OpenGOAL LSP Downloading - ${extraInfo}`;
|
||||||
lspStatusItem.command = undefined;
|
this.statusItem.tooltip = `Downloading version - ${extraInfo}`;
|
||||||
break;
|
this.statusItem.command = undefined;
|
||||||
default:
|
break;
|
||||||
break;
|
case "error":
|
||||||
|
this.statusItem.text = "$(error) OpenGOAL LSP Error";
|
||||||
|
this.statusItem.tooltip = "LSP not running due to an error";
|
||||||
|
this.statusItem.command = undefined;
|
||||||
|
break;
|
||||||
|
case "serverProgressBegin":
|
||||||
|
this.statusItem.text = `$(loading~spin) ${extraInfo}`;
|
||||||
|
this.statusItem.tooltip = extraInfo;
|
||||||
|
this.statusItem.command = "opengoal.lsp.showLspStartedMenu";
|
||||||
|
break;
|
||||||
|
case "serverProgressEnd":
|
||||||
|
this.statusItem.text = `$(circle-filled) ${extraInfo}`;
|
||||||
|
this.statusItem.tooltip = extraInfo;
|
||||||
|
this.statusItem.command = "opengoal.lsp.showLspStartedMenu";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hide() {
|
||||||
|
this.statusItem.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
public show() {
|
||||||
|
this.statusItem.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const statusItem = new LSPStatusItem(
|
||||||
|
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0)
|
||||||
|
);
|
||||||
|
|
||||||
async function ensureServerDownloaded(): Promise<string | undefined> {
|
async function ensureServerDownloaded(): Promise<string | undefined> {
|
||||||
const installedVersion = getVersionFromMetaFile(
|
const installedVersion = getVersionFromMetaFile(
|
||||||
extensionContext.extensionPath
|
extensionContext.extensionPath
|
||||||
|
@ -94,15 +127,15 @@ async function ensureServerDownloaded(): Promise<string | undefined> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the LSP and update the version metadata file
|
// Install the LSP and update the version metadata file
|
||||||
updateStatus("downloading", versionToDownload);
|
statusItem.updateStatus("downloading", versionToDownload);
|
||||||
const newLspPath = await downloadLsp(
|
const newLspPath = await downloadLsp(
|
||||||
extensionContext.extensionPath,
|
extensionContext.extensionPath,
|
||||||
versionToDownload
|
versionToDownload
|
||||||
);
|
);
|
||||||
if (newLspPath === undefined) {
|
if (newLspPath === undefined) {
|
||||||
updateStatus("error");
|
statusItem.updateStatus("error");
|
||||||
} else {
|
} else {
|
||||||
updateStatus("stopped");
|
statusItem.updateStatus("stopped");
|
||||||
}
|
}
|
||||||
return newLspPath;
|
return newLspPath;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +146,13 @@ async function maybeDownloadLspServer(): Promise<void> {
|
||||||
userConfiguredOpengoalLspPath !== "" &&
|
userConfiguredOpengoalLspPath !== "" &&
|
||||||
userConfiguredOpengoalLspPath !== undefined
|
userConfiguredOpengoalLspPath !== undefined
|
||||||
) {
|
) {
|
||||||
opengoalLspPath = userConfiguredOpengoalLspPath;
|
// Copy the binary to the extension directory so it doesn't block future compilations
|
||||||
|
const lspPath = path.join(
|
||||||
|
extensionContext.extensionPath,
|
||||||
|
`opengoal-lsp-local.bin`
|
||||||
|
);
|
||||||
|
fs.copyFileSync(userConfiguredOpengoalLspPath, lspPath);
|
||||||
|
opengoalLspPath = lspPath;
|
||||||
} else {
|
} else {
|
||||||
opengoalLspPath = await ensureServerDownloaded();
|
opengoalLspPath = await ensureServerDownloaded();
|
||||||
}
|
}
|
||||||
|
@ -146,6 +185,7 @@ function createClient(lspPath: string): LanguageClient {
|
||||||
],
|
],
|
||||||
synchronize: {
|
synchronize: {
|
||||||
fileEvents: [
|
fileEvents: [
|
||||||
|
vscode.workspace.createFileSystemWatcher("**/*.gc"),
|
||||||
vscode.workspace.createFileSystemWatcher("**/*_ir2.asm"),
|
vscode.workspace.createFileSystemWatcher("**/*_ir2.asm"),
|
||||||
vscode.workspace.createFileSystemWatcher("**/all-types.gc"),
|
vscode.workspace.createFileSystemWatcher("**/all-types.gc"),
|
||||||
],
|
],
|
||||||
|
@ -167,7 +207,7 @@ function createClient(lspPath: string): LanguageClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopClient() {
|
async function stopClient() {
|
||||||
updateStatus("stopped");
|
statusItem.updateStatus("stopped");
|
||||||
if (activeClient !== undefined) {
|
if (activeClient !== undefined) {
|
||||||
console.log("Stopping opengoal-lsp");
|
console.log("Stopping opengoal-lsp");
|
||||||
return await activeClient
|
return await activeClient
|
||||||
|
@ -181,29 +221,69 @@ async function stopClient() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StatusBarFeature implements StaticFeature {
|
||||||
|
private requestHandlers: vscode.Disposable[] = [];
|
||||||
|
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||||
|
if (!capabilities.window) {
|
||||||
|
capabilities.window = {};
|
||||||
|
}
|
||||||
|
capabilities.window.workDoneProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private readonly client: BaseLanguageClient) {}
|
||||||
|
|
||||||
|
public getState(): FeatureState {
|
||||||
|
return { kind: "static" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
// nothing to dispose here
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialize(): void {
|
||||||
|
this.requestHandlers.push(
|
||||||
|
this.client.onRequest(WorkDoneProgressCreateRequest.type, ({ token }) => {
|
||||||
|
this.client.onProgress(WorkDoneProgress.type, token, (progress) => {
|
||||||
|
if (progress.kind === "begin") {
|
||||||
|
statusItem.updateStatus("serverProgressBegin", progress.title);
|
||||||
|
}
|
||||||
|
if (progress.kind === "report") {
|
||||||
|
// do nothing right now, goalc provides no feedback on it's status
|
||||||
|
}
|
||||||
|
if (progress.kind === "end") {
|
||||||
|
statusItem.updateStatus("serverProgressEnd", progress.message);
|
||||||
|
disposeAll(this.requestHandlers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function startClient(): Promise<void> {
|
async function startClient(): Promise<void> {
|
||||||
if (opengoalLspPath === undefined) {
|
if (opengoalLspPath === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const client = createClient(opengoalLspPath);
|
const client = createClient(opengoalLspPath);
|
||||||
|
client.registerFeature(new StatusBarFeature(client));
|
||||||
console.log("Starting opengoal-lsp at", opengoalLspPath);
|
console.log("Starting opengoal-lsp at", opengoalLspPath);
|
||||||
|
|
||||||
// TODO - some form of startup test would be nice
|
// TODO - some form of startup test would be nice
|
||||||
try {
|
try {
|
||||||
updateStatus("starting");
|
statusItem.updateStatus("starting");
|
||||||
await client.start();
|
await client.start();
|
||||||
activeClient = client;
|
activeClient = client;
|
||||||
updateStatus("started", path.basename(opengoalLspPath));
|
statusItem.updateStatus("started", path.basename(opengoalLspPath));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("opengoal-lsp:", error);
|
console.error("opengoal-lsp:", error);
|
||||||
updateStatus("error");
|
statusItem.updateStatus("error");
|
||||||
lspStatusItem.hide();
|
statusItem.hide();
|
||||||
await stopClient();
|
await stopClient();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startClientCommand() {
|
export async function startClientCommand() {
|
||||||
lspStatusItem.show();
|
statusItem.show();
|
||||||
await maybeDownloadLspServer();
|
await maybeDownloadLspServer();
|
||||||
if (opengoalLspPath !== undefined) {
|
if (opengoalLspPath !== undefined) {
|
||||||
await startClient();
|
await startClient();
|
||||||
|
@ -211,7 +291,10 @@ export async function startClientCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function restartClient() {
|
async function restartClient() {
|
||||||
|
console.log("Stopping opengoal-lsp - restart");
|
||||||
await stopClient();
|
await stopClient();
|
||||||
|
await new Promise((f) => setTimeout(f, 2000));
|
||||||
|
console.log("Starting opengoal-lsp - restart");
|
||||||
await startClientCommand();
|
await startClientCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,8 +359,8 @@ export async function activate(
|
||||||
registerLifeCycleCommands(context);
|
registerLifeCycleCommands(context);
|
||||||
// TODO - add info and open log file options
|
// TODO - add info and open log file options
|
||||||
// registerDiagnosticsCommands(context);
|
// registerDiagnosticsCommands(context);
|
||||||
updateStatus("stopped");
|
statusItem.updateStatus("stopped");
|
||||||
lspStatusItem.show();
|
statusItem.show();
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
if (config.launchLspOnStartup) {
|
if (config.launchLspOnStartup) {
|
||||||
await startClientCommand();
|
await startClientCommand();
|
||||||
|
|
Loading…
Reference in a new issue