Skip to content

Commit 1c4b5ea

Browse files
committed
Support file-based cancellation
1 parent a3e59b9 commit 1c4b5ea

File tree

6 files changed

+138
-90
lines changed

6 files changed

+138
-90
lines changed

client/src/cancellation.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { randomBytes } from "crypto";
2+
import * as fs from "fs";
3+
import * as os from "os";
4+
import { Disposable } from "vscode";
5+
import {
6+
CancellationId,
7+
CancellationReceiverStrategy,
8+
CancellationSenderStrategy,
9+
CancellationStrategy,
10+
MessageConnection,
11+
} from "vscode-languageclient";
12+
import path = require("path");
13+
14+
function getCancellationFolderPath(folderName: string) {
15+
return path.join(os.tmpdir(), "elm-language-server-cancellation", folderName);
16+
}
17+
18+
function getCancellationFilePath(
19+
folderName: string,
20+
id: CancellationId,
21+
): string {
22+
return path.join(
23+
getCancellationFolderPath(folderName),
24+
`cancellation-${String(id)}.tmp`,
25+
);
26+
}
27+
28+
function tryRun(callback: () => void) {
29+
try {
30+
callback();
31+
} catch (e) {
32+
/* empty */
33+
}
34+
}
35+
36+
class FileCancellationSenderStrategy implements CancellationSenderStrategy {
37+
constructor(readonly folderName: string) {
38+
const folder = getCancellationFolderPath(folderName);
39+
tryRun(() => fs.mkdirSync(folder, { recursive: true }));
40+
}
41+
42+
sendCancellation(_: MessageConnection, id: CancellationId): void {
43+
const file = getCancellationFilePath(this.folderName, id);
44+
tryRun(() => fs.writeFileSync(file, "", { flag: "w" }));
45+
}
46+
47+
cleanup(id: CancellationId): void {
48+
tryRun(() => fs.unlinkSync(getCancellationFilePath(this.folderName, id)));
49+
}
50+
51+
dispose(): void {
52+
const folder = getCancellationFolderPath(this.folderName);
53+
tryRun(() => rimraf(folder));
54+
55+
function rimraf(location: string) {
56+
const stat = fs.lstatSync(location);
57+
if (stat) {
58+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
59+
for (const dir of fs.readdirSync(location)) {
60+
rimraf(path.join(location, dir));
61+
}
62+
63+
fs.rmdirSync(location);
64+
} else {
65+
fs.unlinkSync(location);
66+
}
67+
}
68+
}
69+
}
70+
}
71+
72+
export class FileBasedCancellationStrategy
73+
implements CancellationStrategy, Disposable {
74+
private _sender: FileCancellationSenderStrategy;
75+
76+
constructor() {
77+
const folderName = randomBytes(21).toString("hex");
78+
this._sender = new FileCancellationSenderStrategy(folderName);
79+
}
80+
81+
get receiver(): CancellationReceiverStrategy {
82+
return CancellationReceiverStrategy.Message;
83+
}
84+
85+
get sender(): CancellationSenderStrategy {
86+
return this._sender;
87+
}
88+
89+
getCommandLineArguments(): string[] {
90+
return [`--cancellationReceive=file:${this._sender.folderName}`];
91+
}
92+
93+
dispose(): void {
94+
this._sender.dispose();
95+
}
96+
}

client/src/exposeUnexposeAction.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import { LanguageClient } from "vscode-languageclient";
1+
import { LanguageClient } from "vscode-languageclient/node";
22
import { ExtensionContext, commands } from "vscode";
3-
import {
4-
ExposeRequest,
5-
UnexposeRequest,
6-
IExposeUnexposeParams,
7-
} from "./protocol";
3+
import { Protocol } from "@elm-tooling/elm-language-server";
84

95
export function registerCommands(
106
languageClient: LanguageClient,
@@ -13,7 +9,7 @@ export function registerCommands(
139
context.subscriptions.push(
1410
commands.registerCommand(
1511
"elm.expose",
16-
async (params: IExposeUnexposeParams) => {
12+
async (params: Protocol.IExposeUnexposeParams) => {
1713
await expose(languageClient, params);
1814
},
1915
),
@@ -22,7 +18,7 @@ export function registerCommands(
2218
context.subscriptions.push(
2319
commands.registerCommand(
2420
"elm.unexpose",
25-
async (params: IExposeUnexposeParams) => {
21+
async (params: Protocol.IExposeUnexposeParams) => {
2622
await unexpose(languageClient, params);
2723
},
2824
),
@@ -31,14 +27,14 @@ export function registerCommands(
3127

3228
async function expose(
3329
languageClient: LanguageClient,
34-
params: IExposeUnexposeParams,
30+
params: Protocol.IExposeUnexposeParams,
3531
) {
36-
await languageClient.sendRequest(ExposeRequest, params);
32+
await languageClient.sendRequest(Protocol.ExposeRequest, params);
3733
}
3834

3935
async function unexpose(
4036
languageClient: LanguageClient,
41-
params: IExposeUnexposeParams,
37+
params: Protocol.IExposeUnexposeParams,
4238
) {
43-
await languageClient.sendRequest(UnexposeRequest, params);
39+
await languageClient.sendRequest(Protocol.UnexposeRequest, params);
4440
}

client/src/extension.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,25 @@ import {
1515
WorkspaceFolder,
1616
} from "vscode";
1717
import {
18-
LanguageClient,
1918
LanguageClientOptions,
2019
Middleware,
2120
ResolveCodeLensSignature,
2221
RevealOutputChannelOn,
23-
TransportKind,
2422
ProvideCodeLensesSignature,
2523
DidChangeConfigurationNotification,
24+
Disposable,
2625
} from "vscode-languageclient";
26+
import {
27+
LanguageClient,
28+
ServerOptions,
29+
TransportKind,
30+
} from "vscode-languageclient/node";
2731
import * as Package from "./elmPackage";
2832
import * as RefactorAction from "./refactorAction";
2933
import * as ExposeUnexposeAction from "./exposeUnexposeAction";
3034
import * as Restart from "./restart";
31-
import { OnDidCreateFilesRequest, OnDidRenameFilesRequest } from "./protocol";
35+
import { Protocol } from "@elm-tooling/elm-language-server";
36+
import { FileBasedCancellationStrategy } from "./cancellation";
3237

3338
export type ElmAnalyseTrigger = "change" | "save" | "never";
3439

@@ -83,6 +88,8 @@ function getOuterMostWorkspaceFolder(
8388
return folder;
8489
}
8590

91+
const disposables: Disposable[] = [];
92+
8693
export function activate(context: ExtensionContext): void {
8794
const module = context.asAbsolutePath(path.join("server", "out", "index.js"));
8895

@@ -114,16 +121,24 @@ export function activate(context: ExtensionContext): void {
114121
relativeWorkspace.length > 0 ? `Elm (${relativeWorkspace})` : "Elm",
115122
);
116123

124+
const cancellationStrategy = new FileBasedCancellationStrategy();
125+
disposables.push(cancellationStrategy);
126+
117127
const debugOptions = {
118128
execArgv: ["--nolazy", `--inspect=${6010 + clients.size}`],
119129
};
120-
const serverOptions = {
130+
const serverOptions: ServerOptions = {
121131
debug: {
122132
module,
123133
options: debugOptions,
124134
transport: TransportKind.ipc,
135+
args: cancellationStrategy.getCommandLineArguments(),
136+
},
137+
run: {
138+
module,
139+
transport: TransportKind.ipc,
140+
args: cancellationStrategy.getCommandLineArguments(),
125141
},
126-
run: { module, transport: TransportKind.ipc },
127142
};
128143
const clientOptions: LanguageClientOptions = {
129144
diagnosticCollectionName: "Elm",
@@ -143,6 +158,9 @@ export function activate(context: ExtensionContext): void {
143158
progressOnInitialization: true,
144159
revealOutputChannelOn: RevealOutputChannelOn.Never,
145160
workspaceFolder: folder,
161+
connectionOptions: {
162+
cancellationStrategy,
163+
},
146164
};
147165
const client = new LanguageClient(
148166
"elmLS",
@@ -173,15 +191,17 @@ export function activate(context: ExtensionContext): void {
173191
Workspace.onDidCreateFiles((e) => {
174192
if (e.files.some((file) => file.toString().endsWith(".elm"))) {
175193
clients.forEach(
176-
(client) => void client.sendRequest(OnDidCreateFilesRequest, e),
194+
(client) =>
195+
void client.sendRequest(Protocol.OnDidCreateFilesRequest, e),
177196
);
178197
}
179198
});
180199

181200
Workspace.onDidRenameFiles((e) => {
182201
if (e.files.some(({ newUri }) => newUri.toString().endsWith(".elm"))) {
183202
clients.forEach(
184-
(client) => void client.sendRequest(OnDidRenameFilesRequest, e),
203+
(client) =>
204+
void client.sendRequest(Protocol.OnDidRenameFilesRequest, e),
185205
);
186206
}
187207
});
@@ -223,6 +243,7 @@ export function activate(context: ExtensionContext): void {
223243
}
224244

225245
export function deactivate(): Thenable<void> | undefined {
246+
disposables.forEach((d) => d.dispose());
226247
const promises: Thenable<void>[] = [];
227248
for (const client of clients.values()) {
228249
promises.push(client.stop());

client/src/protocol.ts

Lines changed: 0 additions & 66 deletions
This file was deleted.

client/src/refactorAction.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { commands, ExtensionContext, window } from "vscode";
2-
import { CodeActionParams, LanguageClient } from "vscode-languageclient";
3-
import { GetMoveDestinationRequest, MoveRequest } from "./protocol";
2+
import { CodeActionParams } from "vscode-languageclient";
3+
import { LanguageClient } from "vscode-languageclient/node";
4+
import { Protocol } from "@elm-tooling/elm-language-server";
45

56
export function registerCommands(
67
languageClient: LanguageClient,
@@ -28,7 +29,7 @@ async function moveFunction(
2829
commandInfo: string,
2930
) {
3031
const moveDestinations = await languageClient.sendRequest(
31-
GetMoveDestinationRequest,
32+
Protocol.GetMoveDestinationRequest,
3233
{
3334
sourceUri: params.textDocument.uri,
3435
params,
@@ -65,7 +66,7 @@ async function moveFunction(
6566
return;
6667
}
6768

68-
await languageClient.sendRequest(MoveRequest, {
69+
await languageClient.sendRequest(Protocol.MoveRequest, {
6970
sourceUri: params.textDocument.uri,
7071
params,
7172
destination: selected.destination,

client/src/restart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
import * as vscode from "vscode";
3-
import { LanguageClient } from "vscode-languageclient";
3+
import { LanguageClient } from "vscode-languageclient/node";
44

55
export function registerCommand(
66
langClients: Map<string, LanguageClient>,

0 commit comments

Comments
 (0)