Skip to content

Commit ebce5e9

Browse files
committedOct 28, 2022
Auto merge of rust-lang#13513 - Veykril:vscode-workspace-changes, r=Veykril
Properly handle vscode workspace changes
2 parents 7610ee9 + fccf8eb commit ebce5e9

File tree

2 files changed

+73
-39
lines changed

2 files changed

+73
-39
lines changed
 

‎editors/code/src/ctx.ts

+60-7
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@ import * as ra from "./lsp_ext";
44

55
import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
66
import { createClient } from "./client";
7-
import { isRustEditor, log, RustEditor } from "./util";
7+
import { isRustDocument, isRustEditor, log, RustEditor } from "./util";
88
import { ServerStatusParams } from "./lsp_ext";
99
import { PersistentState } from "./persistent_state";
1010
import { bootstrap } from "./bootstrap";
1111

12+
// We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
13+
// only those are in use. We use "Empty" to represent these scenarios
14+
// (r-a still somewhat works with Live Share, because commands are tunneled to the host)
15+
1216
export type Workspace =
1317
| { kind: "Empty" }
1418
| {
@@ -19,6 +23,24 @@ export type Workspace =
1923
files: vscode.TextDocument[];
2024
};
2125

26+
export function fetchWorkspace(): Workspace {
27+
const folders = (vscode.workspace.workspaceFolders || []).filter(
28+
(folder) => folder.uri.scheme === "file"
29+
);
30+
const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
31+
isRustDocument(document)
32+
);
33+
34+
return folders.length === 0
35+
? rustDocuments.length === 0
36+
? { kind: "Empty" }
37+
: {
38+
kind: "Detached Files",
39+
files: rustDocuments,
40+
}
41+
: { kind: "Workspace Folder" };
42+
}
43+
2244
export type CommandFactory = {
2345
enabled: (ctx: CtxInit) => Cmd;
2446
disabled?: (ctx: Ctx) => Cmd;
@@ -75,6 +97,31 @@ export class Ctx {
7597
this.commandDisposables.forEach((disposable) => disposable.dispose());
7698
}
7799

100+
async onWorkspaceFolderChanges() {
101+
const workspace = fetchWorkspace();
102+
if (workspace.kind === "Detached Files" && this.workspace.kind === "Detached Files") {
103+
if (workspace.files !== this.workspace.files) {
104+
if (this.client?.isRunning()) {
105+
// Ideally we wouldn't need to tear down the server here, but currently detached files
106+
// are only specified at server start
107+
await this.stopAndDispose();
108+
await this.start();
109+
}
110+
return;
111+
}
112+
}
113+
if (workspace.kind === "Workspace Folder" && this.workspace.kind === "Workspace Folder") {
114+
return;
115+
}
116+
if (workspace.kind === "Empty") {
117+
await this.stopAndDispose();
118+
return;
119+
}
120+
if (this.client?.isRunning()) {
121+
await this.restart();
122+
}
123+
}
124+
78125
private async getOrCreateClient() {
79126
if (this.workspace.kind === "Empty") {
80127
return;
@@ -143,8 +190,8 @@ export class Ctx {
143190
return this._client;
144191
}
145192

146-
async activate() {
147-
log.info("Activating language client");
193+
async start() {
194+
log.info("Starting language client");
148195
const client = await this.getOrCreateClient();
149196
if (!client) {
150197
return;
@@ -153,20 +200,26 @@ export class Ctx {
153200
this.updateCommands();
154201
}
155202

156-
async deactivate() {
203+
async restart() {
204+
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
205+
await this.stopAndDispose();
206+
await this.start();
207+
}
208+
209+
async stop() {
157210
if (!this._client) {
158211
return;
159212
}
160-
log.info("Deactivating language client");
213+
log.info("Stopping language client");
161214
this.updateCommands("disable");
162215
await this._client.stop();
163216
}
164217

165-
async stop() {
218+
async stopAndDispose() {
166219
if (!this._client) {
167220
return;
168221
}
169-
log.info("Stopping language client");
222+
log.info("Disposing language client");
170223
this.updateCommands("disable");
171224
await this.disposeClient();
172225
}

‎editors/code/src/main.ts

+13-32
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import * as vscode from "vscode";
22
import * as lc from "vscode-languageclient/node";
33

44
import * as commands from "./commands";
5-
import { CommandFactory, Ctx, Workspace } from "./ctx";
6-
import { isRustDocument } from "./util";
5+
import { CommandFactory, Ctx, fetchWorkspace } from "./ctx";
76
import { activateTaskProvider } from "./tasks";
87
import { setContextValue } from "./util";
98

@@ -31,28 +30,7 @@ export async function activate(
3130
.then(() => {}, console.error);
3231
}
3332

34-
// We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
35-
// only those are in use.
36-
// (r-a still somewhat works with Live Share, because commands are tunneled to the host)
37-
const folders = (vscode.workspace.workspaceFolders || []).filter(
38-
(folder) => folder.uri.scheme === "file"
39-
);
40-
const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
41-
isRustDocument(document)
42-
);
43-
44-
// FIXME: This can change over time
45-
const workspace: Workspace =
46-
folders.length === 0
47-
? rustDocuments.length === 0
48-
? { kind: "Empty" }
49-
: {
50-
kind: "Detached Files",
51-
files: rustDocuments,
52-
}
53-
: { kind: "Workspace Folder" };
54-
55-
const ctx = new Ctx(context, createCommands(), workspace);
33+
const ctx = new Ctx(context, createCommands(), fetchWorkspace());
5634
// VS Code doesn't show a notification when an extension fails to activate
5735
// so we do it ourselves.
5836
const api = await activateServer(ctx).catch((err) => {
@@ -70,6 +48,11 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
7048
ctx.pushExtCleanup(activateTaskProvider(ctx.config));
7149
}
7250

51+
vscode.workspace.onDidChangeWorkspaceFolders(
52+
async (_) => ctx.onWorkspaceFolderChanges(),
53+
null,
54+
ctx.subscriptions
55+
);
7356
vscode.workspace.onDidChangeConfiguration(
7457
async (_) => {
7558
await ctx.client?.sendNotification("workspace/didChangeConfiguration", {
@@ -80,7 +63,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
8063
ctx.subscriptions
8164
);
8265

83-
await ctx.activate();
66+
await ctx.start();
8467
return ctx;
8568
}
8669

@@ -93,27 +76,25 @@ function createCommands(): Record<string, CommandFactory> {
9376
reload: {
9477
enabled: (ctx) => async () => {
9578
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
96-
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
97-
await ctx.stop();
98-
await ctx.activate();
79+
await ctx.restart();
9980
},
10081
disabled: (ctx) => async () => {
10182
void vscode.window.showInformationMessage("Reloading rust-analyzer...");
102-
await ctx.activate();
83+
await ctx.start();
10384
},
10485
},
10586
startServer: {
10687
enabled: (ctx) => async () => {
107-
await ctx.activate();
88+
await ctx.start();
10889
},
10990
disabled: (ctx) => async () => {
110-
await ctx.activate();
91+
await ctx.start();
11192
},
11293
},
11394
stopServer: {
11495
enabled: (ctx) => async () => {
11596
// FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
116-
await ctx.stop();
97+
await ctx.stopAndDispose();
11798
ctx.setServerStatus({
11899
health: "stopped",
119100
});

0 commit comments

Comments
 (0)
Please sign in to comment.