[mlir-vscode] Add better support for multiple workspace folders
authorRiver Riddle <riddleriver@gmail.com>
Thu, 31 Mar 2022 09:26:12 +0000 (02:26 -0700)
committerRiver Riddle <riddleriver@gmail.com>
Tue, 5 Apr 2022 20:41:40 +0000 (13:41 -0700)
We currently only launch one set of language clients when starting the extension,
but this has the unfortunate effect of applying the same settings to all workspace
folders. This commit adds support for multiple workspace folders by launching
a server for each folder in the workspace. This allows for having different servers
for different workspace folders, e.g. when there are multiple MLIR projects in
the same workspace.

Differential Revision: https://reviews.llvm.org/D122793

mlir/utils/vscode/package.json
mlir/utils/vscode/src/config.ts
mlir/utils/vscode/src/configWatcher.ts
mlir/utils/vscode/src/mlirContext.ts

index 9747dd7..74072d9 100644 (file)
@@ -2,7 +2,7 @@
   "name": "vscode-mlir",
   "displayName": "MLIR",
   "description": "MLIR Language Extension",
-  "version": "0.0.4",
+  "version": "0.0.5",
   "publisher": "llvm-vs-code-extensions",
   "homepage": "https://mlir.llvm.org/",
   "icon": "icon.png",
index 58f45cc..d7dd5bf 100644 (file)
@@ -1,10 +1,11 @@
 import * as vscode from 'vscode';
 
 /**
- *  Gets the config value `mlir.<key>`.
+ *  Gets the config value `mlir.<key>`, with an optional workspace folder.
  */
-export function get<T>(key: string): T {
-  return vscode.workspace.getConfiguration('mlir').get<T>(key);
+export function get<T>(key: string,
+                       workspaceFolder: vscode.WorkspaceFolder = null): T {
+  return vscode.workspace.getConfiguration('mlir', workspaceFolder).get<T>(key);
 }
 
 /**
index a89391d..180a814 100644 (file)
@@ -38,10 +38,11 @@ async function promptRestart(settingName: string, promptMessage: string) {
 }
 
 /**
- *  Activate the watchers that track configuration changes which decide when to
- *  restart the server.
+ *  Activate watchers that track configuration changes for the given workspace
+ *  folder, or null if the workspace is top-level.
  */
 export async function activate(mlirContext: MLIRContext,
+                               workspaceFolder: vscode.WorkspaceFolder,
                                serverPathsToWatch: string[]) {
   // When a configuration change happens, check to see if we should restart the
   // server.
@@ -49,7 +50,7 @@ export async function activate(mlirContext: MLIRContext,
     const settings: string[] = [ 'server_path', 'pdll_server_path' ];
     for (const setting of settings) {
       const expandedSetting = `mlir.${setting}`;
-      if (event.affectsConfiguration(expandedSetting)) {
+      if (event.affectsConfiguration(expandedSetting, workspaceFolder)) {
         promptRestart(
             'onSettingsChanged',
             `setting '${
index ea1d0a5..521fdf0 100644 (file)
@@ -7,29 +7,65 @@ import * as config from './config';
 import * as configWatcher from './configWatcher';
 
 /**
+ *  This class represents the context of a specific workspace folder.
+ */
+class WorkspaceFolderContext {
+  constructor(mlirServer: vscodelc.LanguageClient,
+              pdllServer: vscodelc.LanguageClient) {
+    this.mlirServer = mlirServer;
+    this.pdllServer = pdllServer;
+  }
+  mlirServer!: vscodelc.LanguageClient;
+  pdllServer!: vscodelc.LanguageClient;
+}
+
+/**
  *  This class manages all of the MLIR extension state,
  *  including the language client.
  */
 export class MLIRContext implements vscode.Disposable {
   subscriptions: vscode.Disposable[] = [];
-  client!: vscodelc.LanguageClient;
-  pdllClient!: vscodelc.LanguageClient;
+  workspaceFolders: WorkspaceFolderContext[] = [];
 
   /**
    *  Activate the MLIR context, and start the language clients.
    */
   async activate(outputChannel: vscode.OutputChannel,
                  warnOnEmptyServerPath: boolean) {
-    // Create the language clients for mlir and pdll.
-    let mlirServerPath: string, pdllServerPath: string;
-    [this.client, mlirServerPath] = await this.startLanguageClient(
-        outputChannel, warnOnEmptyServerPath, 'server_path', 'mlir');
-    [this.pdllClient, pdllServerPath] = await this.startLanguageClient(
-        outputChannel, warnOnEmptyServerPath, 'pdll_server_path', 'pdll');
+    // Start clients for each workspace folder.
+    if (vscode.workspace.workspaceFolders &&
+        vscode.workspace.workspaceFolders.length > 0) {
+      for (const workspaceFolder of vscode.workspace.workspaceFolders) {
+        this.workspaceFolders.push(await this.activateWorkspaceFolder(
+            workspaceFolder, outputChannel, warnOnEmptyServerPath));
+      }
+    } else {
+      this.workspaceFolders.push(await this.activateWorkspaceFolder(
+          null, outputChannel, warnOnEmptyServerPath));
+    }
+  }
 
-    // Watch for configuration changes.
+  /**
+   *  Activate the context for the given workspace folder, and start the
+   *  language clients.
+   */
+  async activateWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder,
+                                outputChannel: vscode.OutputChannel,
+                                warnOnEmptyServerPath: boolean):
+      Promise<WorkspaceFolderContext> {
+    // Create the language clients for mlir and pdll.
+    const [mlirServer, mlirServerPath] = await this.startLanguageClient(
+        workspaceFolder, outputChannel, warnOnEmptyServerPath, 'server_path',
+        'mlir');
+    const [pdllServer, pdllServerPath] = await this.startLanguageClient(
+        workspaceFolder, outputChannel, warnOnEmptyServerPath,
+        'pdll_server_path', 'pdll');
+
+    // Watch for configuration changes on this folder.
     const serverPathsToWatch = [ mlirServerPath, pdllServerPath ];
-    await configWatcher.activate(this, serverPathsToWatch);
+    await configWatcher.activate(this, workspaceFolder, serverPathsToWatch);
+
+    return new WorkspaceFolderContext(mlirServer, pdllServer);
   }
 
   /**
@@ -37,7 +73,8 @@ export class MLIRContext implements vscode.Disposable {
    *  containing the opened server, or null if the server could not be started,
    *  and the resolved server path.
    */
-  async startLanguageClient(outputChannel: vscode.OutputChannel,
+  async startLanguageClient(workspaceFolder: vscode.WorkspaceFolder,
+                            outputChannel: vscode.OutputChannel,
                             warnOnEmptyServerPath: boolean,
                             serverSettingName: string, languageName: string):
       Promise<[ vscodelc.LanguageClient, string ]> {
@@ -45,7 +82,8 @@ export class MLIRContext implements vscode.Disposable {
 
     // Get the path of the lsp-server that is used to provide language
     // functionality.
-    var serverPath = await this.resolveServerPath(serverSettingName);
+    var serverPath =
+        await this.resolveServerPath(serverSettingName, workspaceFolder);
 
     // If we aren't emitting warnings on an empty server path, and the server
     // path is empty, bail.
@@ -84,16 +122,26 @@ export class MLIRContext implements vscode.Disposable {
       }
     };
 
+    // Configure file patterns relative to the workspace folder.
+    let filePattern: vscode.GlobPattern = '**/*.' + languageName;
+    let selectorPattern: string = null;
+    if (workspaceFolder) {
+      filePattern = new vscode.RelativePattern(workspaceFolder, filePattern);
+      selectorPattern = `${workspaceFolder.uri.fsPath}/**/*`;
+    }
+
     // Configure the client options.
     const clientOptions: vscodelc.LanguageClientOptions = {
-      documentSelector : [ {scheme : 'file', language : languageName} ],
+      documentSelector : [
+        {scheme : 'file', language : languageName, pattern : selectorPattern}
+      ],
       synchronize : {
         // Notify the server about file changes to language files contained in
         // the workspace.
-        fileEvents :
-            vscode.workspace.createFileSystemWatcher('**/*.' + languageName)
+        fileEvents : vscode.workspace.createFileSystemWatcher(filePattern)
       },
       outputChannel : outputChannel,
+      workspaceFolder : workspaceFolder
     };
 
     // Create the language client and start the client.
@@ -117,10 +165,14 @@ export class MLIRContext implements vscode.Disposable {
   }
 
   /**
-   * Try to resolve the path for the given server setting.
+   * Try to resolve the path for the given server setting, with an optional
+   * workspace folder.
    */
-  async resolveServerPath(serverSettingName: string): Promise<string> {
-    let configServerPath = config.get<string>(serverSettingName);
+  async resolveServerPath(serverSettingName: string,
+                          workspaceFolder: vscode.WorkspaceFolder):
+      Promise<string> {
+    const configServerPath =
+        config.get<string>(serverSettingName, workspaceFolder);
     let serverPath = configServerPath;
 
     // If the path is already fully resolved, there is nothing to do.
@@ -138,8 +190,11 @@ export class MLIRContext implements vscode.Disposable {
     }
 
     // Try to resolve the path relative to the workspace.
-    const foundUris: vscode.Uri[] =
-        await vscode.workspace.findFiles('**/' + serverPath, null, 1);
+    let filePattern: vscode.GlobPattern = '**/' + serverPath;
+    if (workspaceFolder) {
+      filePattern = new vscode.RelativePattern(workspaceFolder, filePattern);
+    }
+    let foundUris = await vscode.workspace.findFiles(filePattern, null, 1);
     if (foundUris.length === 0) {
       // If we couldn't resolve it, just return the current configuration path
       // anyways. The file might not exist yet.
@@ -152,5 +207,6 @@ export class MLIRContext implements vscode.Disposable {
   dispose() {
     this.subscriptions.forEach((d) => { d.dispose(); });
     this.subscriptions = [];
+    this.workspaceFolders = [];
   }
 }