about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--editors/code/src/ctx.ts67
-rw-r--r--editors/code/src/main.ts45
2 files changed, 73 insertions, 39 deletions
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index d198d4e9383..3e366525ee2 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -4,11 +4,15 @@ import * as ra from "./lsp_ext";
 
 import { Config, substituteVariablesInEnv, substituteVSCodeVariables } from "./config";
 import { createClient } from "./client";
-import { isRustEditor, log, RustEditor } from "./util";
+import { isRustDocument, isRustEditor, log, RustEditor } from "./util";
 import { ServerStatusParams } from "./lsp_ext";
 import { PersistentState } from "./persistent_state";
 import { bootstrap } from "./bootstrap";
 
+// We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
+// only those are in use. We use "Empty" to represent these scenarios
+// (r-a still somewhat works with Live Share, because commands are tunneled to the host)
+
 export type Workspace =
     | { kind: "Empty" }
     | {
@@ -19,6 +23,24 @@ export type Workspace =
           files: vscode.TextDocument[];
       };
 
+export function fetchWorkspace(): Workspace {
+    const folders = (vscode.workspace.workspaceFolders || []).filter(
+        (folder) => folder.uri.scheme === "file"
+    );
+    const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
+        isRustDocument(document)
+    );
+
+    return folders.length === 0
+        ? rustDocuments.length === 0
+            ? { kind: "Empty" }
+            : {
+                  kind: "Detached Files",
+                  files: rustDocuments,
+              }
+        : { kind: "Workspace Folder" };
+}
+
 export type CommandFactory = {
     enabled: (ctx: CtxInit) => Cmd;
     disabled?: (ctx: Ctx) => Cmd;
@@ -75,6 +97,31 @@ export class Ctx {
         this.commandDisposables.forEach((disposable) => disposable.dispose());
     }
 
+    async onWorkspaceFolderChanges() {
+        const workspace = fetchWorkspace();
+        if (workspace.kind === "Detached Files" && this.workspace.kind === "Detached Files") {
+            if (workspace.files !== this.workspace.files) {
+                if (this.client?.isRunning()) {
+                    // Ideally we wouldn't need to tear down the server here, but currently detached files
+                    // are only specified at server start
+                    await this.stopAndDispose();
+                    await this.start();
+                }
+                return;
+            }
+        }
+        if (workspace.kind === "Workspace Folder" && this.workspace.kind === "Workspace Folder") {
+            return;
+        }
+        if (workspace.kind === "Empty") {
+            await this.stopAndDispose();
+            return;
+        }
+        if (this.client?.isRunning()) {
+            await this.restart();
+        }
+    }
+
     private async getOrCreateClient() {
         if (this.workspace.kind === "Empty") {
             return;
@@ -143,8 +190,8 @@ export class Ctx {
         return this._client;
     }
 
-    async activate() {
-        log.info("Activating language client");
+    async start() {
+        log.info("Starting language client");
         const client = await this.getOrCreateClient();
         if (!client) {
             return;
@@ -153,20 +200,26 @@ export class Ctx {
         this.updateCommands();
     }
 
-    async deactivate() {
+    async restart() {
+        // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
+        await this.stopAndDispose();
+        await this.start();
+    }
+
+    async stop() {
         if (!this._client) {
             return;
         }
-        log.info("Deactivating language client");
+        log.info("Stopping language client");
         this.updateCommands("disable");
         await this._client.stop();
     }
 
-    async stop() {
+    async stopAndDispose() {
         if (!this._client) {
             return;
         }
-        log.info("Stopping language client");
+        log.info("Disposing language client");
         this.updateCommands("disable");
         await this.disposeClient();
     }
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 54e0c16e5e0..e76b657c1bf 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -2,8 +2,7 @@ import * as vscode from "vscode";
 import * as lc from "vscode-languageclient/node";
 
 import * as commands from "./commands";
-import { CommandFactory, Ctx, Workspace } from "./ctx";
-import { isRustDocument } from "./util";
+import { CommandFactory, Ctx, fetchWorkspace } from "./ctx";
 import { activateTaskProvider } from "./tasks";
 import { setContextValue } from "./util";
 
@@ -31,28 +30,7 @@ export async function activate(
             .then(() => {}, console.error);
     }
 
-    // We only support local folders, not eg. Live Share (`vlsl:` scheme), so don't activate if
-    // only those are in use.
-    // (r-a still somewhat works with Live Share, because commands are tunneled to the host)
-    const folders = (vscode.workspace.workspaceFolders || []).filter(
-        (folder) => folder.uri.scheme === "file"
-    );
-    const rustDocuments = vscode.workspace.textDocuments.filter((document) =>
-        isRustDocument(document)
-    );
-
-    // FIXME: This can change over time
-    const workspace: Workspace =
-        folders.length === 0
-            ? rustDocuments.length === 0
-                ? { kind: "Empty" }
-                : {
-                      kind: "Detached Files",
-                      files: rustDocuments,
-                  }
-            : { kind: "Workspace Folder" };
-
-    const ctx = new Ctx(context, createCommands(), workspace);
+    const ctx = new Ctx(context, createCommands(), fetchWorkspace());
     // VS Code doesn't show a notification when an extension fails to activate
     // so we do it ourselves.
     const api = await activateServer(ctx).catch((err) => {
@@ -70,6 +48,11 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
         ctx.pushExtCleanup(activateTaskProvider(ctx.config));
     }
 
+    vscode.workspace.onDidChangeWorkspaceFolders(
+        async (_) => ctx.onWorkspaceFolderChanges(),
+        null,
+        ctx.subscriptions
+    );
     vscode.workspace.onDidChangeConfiguration(
         async (_) => {
             await ctx.client?.sendNotification("workspace/didChangeConfiguration", {
@@ -80,7 +63,7 @@ async function activateServer(ctx: Ctx): Promise<RustAnalyzerExtensionApi> {
         ctx.subscriptions
     );
 
-    await ctx.activate();
+    await ctx.start();
     return ctx;
 }
 
@@ -93,27 +76,25 @@ function createCommands(): Record<string, CommandFactory> {
         reload: {
             enabled: (ctx) => async () => {
                 void vscode.window.showInformationMessage("Reloading rust-analyzer...");
-                // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
-                await ctx.stop();
-                await ctx.activate();
+                await ctx.restart();
             },
             disabled: (ctx) => async () => {
                 void vscode.window.showInformationMessage("Reloading rust-analyzer...");
-                await ctx.activate();
+                await ctx.start();
             },
         },
         startServer: {
             enabled: (ctx) => async () => {
-                await ctx.activate();
+                await ctx.start();
             },
             disabled: (ctx) => async () => {
-                await ctx.activate();
+                await ctx.start();
             },
         },
         stopServer: {
             enabled: (ctx) => async () => {
                 // FIXME: We should re-use the client, that is ctx.deactivate() if none of the configs have changed
-                await ctx.stop();
+                await ctx.stopAndDispose();
                 ctx.setServerStatus({
                     health: "stopped",
                 });