Skip to content

Commit

Permalink
feat(vscode): Add Language Status Item
Browse files Browse the repository at this point in the history
  • Loading branch information
spotandjake committed Oct 24, 2024
1 parent f3983f9 commit bdaadd1
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 7 deletions.
4 changes: 4 additions & 0 deletions editor-extensions/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
{
"command": "grain.restart",
"title": "Grain: Restart Language Server"
},
{
"command": "grain.openOutput",
"title": "Grain: Show Extension Output"
}
],
"languages": [
Expand Down
55 changes: 55 additions & 0 deletions editor-extensions/vscode/src/GrainErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { LanguageStatusSeverity, type LanguageStatusItem } from "vscode";
import {
ErrorHandler,
ErrorAction,
CloseAction,
type BaseLanguageClient,
type ErrorHandlerResult,
type Message,
type CloseHandlerResult,
} from "vscode-languageclient";

// Derived from: https://github.com/microsoft/vscode-languageserver-node/blob/a561f1342ba94ad7f550cb15446f65432f5e1367/client/src/common/client.ts#L439
export default class GrainErrorHandler implements ErrorHandler {
private readonly restarts: number[];

constructor(
private name: string,
private languageStatusItem: LanguageStatusItem,
private maxRestartCount: number
) {
this.restarts = [];
}

public error(
_error: Error,
_message: Message,
count: number
): ErrorHandlerResult {
if (count && count <= 3) {
return { action: ErrorAction.Continue };
}
return { action: ErrorAction.Shutdown };
}

public closed(): CloseHandlerResult {
this.restarts.push(Date.now());
if (this.restarts.length <= this.maxRestartCount) {
return { action: CloseAction.Restart };
} else {
const diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
if (diff <= 3 * 60 * 1000) {
this.languageStatusItem.severity = LanguageStatusSeverity.Error;
return {
action: CloseAction.DoNotRestart,
message: `The ${this.name} server crashed ${
this.maxRestartCount + 1
} times in the last 3 minutes. The server will not be restarted. See the output for more information.`,
};
} else {
this.restarts.shift();
return { action: CloseAction.Restart };
}
}
}
}
58 changes: 51 additions & 7 deletions editor-extensions/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ConfigurationChangeEvent,
Uri,
window,
type LanguageStatusItem,
} from "vscode";

import {
Expand All @@ -28,18 +29,20 @@ import {
import which from "which";

import { GrainDocCompletionProvider } from "./GrainDocCompletionProvider";
import GrainErrorHandler from "./GrainErrorHandler";

let extensionName = "Grain Language Server";

let languageId = "grain";

let outputChannel = window.createOutputChannel(extensionName, languageId);

let fileClients: Map<string, LanguageClient> = new Map();
let workspaceClients: Map<string, LanguageClient> = new Map();

let activeMutex: Set<string> = new Set();

let grainStatusBarItem: LanguageStatusItem | null = null;
let grainRestartStatusItem: LanguageStatusItem | null = null;

function mutex(key: string, fn: (...args: unknown[]) => Promise<void>) {
return (...args) => {
if (activeMutex.has(key)) return;
Expand Down Expand Up @@ -141,7 +144,7 @@ function getLspCommand(uri: Uri) {
async function startFileClient(uri: Uri) {
let [command, args] = getLspCommand(uri);

let clientOptions = {
let clientOptions: LanguageClientOptions = {
documentSelector: [
{
scheme: uri.scheme,
Expand All @@ -150,6 +153,10 @@ async function startFileClient(uri: Uri) {
},
],
outputChannel,
errorHandler:
grainStatusBarItem != null
? new GrainErrorHandler(extensionName, grainStatusBarItem, 5)
: undefined,
};

let serverOptions: ServerOptions = {
Expand Down Expand Up @@ -261,12 +268,14 @@ async function removeWorkspaceClient(workspaceFolder: WorkspaceFolder) {
}

async function restartAllClients() {
if (grainRestartStatusItem != null) grainRestartStatusItem.busy = true;
for (let client of fileClients.values()) {
await client.restart();
}
for (let client of workspaceClients.values()) {
await client.restart();
}
if (grainRestartStatusItem != null) grainRestartStatusItem.busy = false;
}

async function didOpenTextDocument(
Expand Down Expand Up @@ -352,19 +361,54 @@ async function didChangeWorkspaceFolders(event: WorkspaceFoldersChangeEvent) {
}
}

function createStatusBarEntries(context: ExtensionContext) {
// Language status bar
const statusScope = { language: languageId };

// Main extension entry
grainStatusBarItem = languages.createLanguageStatusItem("grain", statusScope);
grainStatusBarItem.busy = false;
grainStatusBarItem.name = "grain";
grainStatusBarItem.text = "grain";
grainStatusBarItem.command = {
title: "Open Grain Output",
command: "grain.openOutput",
};
context.subscriptions.push(grainStatusBarItem);

// Restart command entry
grainRestartStatusItem = languages.createLanguageStatusItem(
"grain.restart",
statusScope
);
grainStatusBarItem.busy = false;
grainRestartStatusItem.name = "grain.restart";
grainRestartStatusItem.text = "grain";
grainRestartStatusItem.command = {
title: "Restart Extension",
command: "grain.restart",
};
context.subscriptions.push(grainRestartStatusItem);
}

export async function activate(context: ExtensionContext): Promise<void> {
let didOpenTextDocument$ =
const didOpenTextDocument$ =
workspace.onDidOpenTextDocument(didOpenTextDocument);
let didChangeWorkspaceFolders$ = workspace.onDidChangeWorkspaceFolders(
const didChangeWorkspaceFolders$ = workspace.onDidChangeWorkspaceFolders(
didChangeWorkspaceFolders
);
let restart$ = commands.registerCommand("grain.restart", restartAllClients);
const restart$ = commands.registerCommand("grain.restart", restartAllClients);
const output$ = commands.registerCommand("grain.openOutput", () => {
outputChannel.show();
});

context.subscriptions.push(
didOpenTextDocument$,
didChangeWorkspaceFolders$,
restart$
restart$,
output$
);
createStatusBarEntries(context);

for (let doc of workspace.textDocuments) {
const disposable = await didOpenTextDocument(doc);
Expand Down

0 comments on commit bdaadd1

Please sign in to comment.