brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.4 KiB · e12aa92 Raw
393 lines · typescript
1import * as fs from 'fs';2import * as path from 'path';3import * as vscode from 'vscode';4import * as vscodelc from 'vscode-languageclient/node';5 6import * as config from './config';7import * as configWatcher from './configWatcher';8 9/**10 *  This class represents the context of a specific workspace folder.11 */12class WorkspaceFolderContext implements vscode.Disposable {13  dispose() {14    this.clients.forEach(async client => await client.stop());15    this.clients.clear();16  }17 18  clients: Map<string, vscodelc.LanguageClient> = new Map();19}20 21/**22 *  This class manages all of the MLIR extension state,23 *  including the language client.24 */25export class MLIRContext implements vscode.Disposable {26  subscriptions: vscode.Disposable[] = [];27  workspaceFolders: Map<string, WorkspaceFolderContext> = new Map();28  outputChannel: vscode.OutputChannel;29 30  /**31   *  Activate the MLIR context, and start the language clients.32   */33  async activate(outputChannel: vscode.OutputChannel) {34    this.outputChannel = outputChannel;35 36    // This lambda is used to lazily start language clients for the given37    // document. It removes the need to pro-actively start language clients for38    // every folder within the workspace and every language type we provide.39    const startClientOnOpenDocument = async (document: vscode.TextDocument) => {40      await this.getOrActivateLanguageClient(document.uri, document.languageId);41    };42    // Process any existing documents.43    for (const textDoc of vscode.workspace.textDocuments) {44      await startClientOnOpenDocument(textDoc);45    }46 47    // Watch any new documents to spawn servers when necessary.48    this.subscriptions.push(49        vscode.workspace.onDidOpenTextDocument(startClientOnOpenDocument));50    this.subscriptions.push(51        vscode.workspace.onDidChangeWorkspaceFolders((event) => {52          for (const folder of event.removed) {53            const client = this.workspaceFolders.get(folder.uri.toString());54            if (client) {55              client.dispose();56              this.workspaceFolders.delete(folder.uri.toString());57            }58          }59        }));60  }61 62  /**63   * Open or return a language server for the given uri and language.64   */65  async getOrActivateLanguageClient(uri: vscode.Uri, languageId: string):66      Promise<vscodelc.LanguageClient> {67    let serverSettingName: string;68    if (languageId === 'mlir') {69      serverSettingName = 'server_path';70    } else if (languageId === 'pdll') {71      serverSettingName = 'pdll_server_path';72    } else if (languageId === 'tablegen') {73      serverSettingName = 'tablegen_server_path';74    } else {75      return null;76    }77 78    // Check the scheme of the uri.79    let validSchemes = [ 'file', 'mlir.bytecode-mlir' ];80    if (!validSchemes.includes(uri.scheme)) {81      return null;82    }83 84    // Resolve the workspace folder if this document is in one. We use the85    // workspace folder when determining if a server needs to be started.86    let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);87    let workspaceFolderStr =88        workspaceFolder ? workspaceFolder.uri.toString() : "";89 90    // Get or create a client context for this folder.91    let folderContext = this.workspaceFolders.get(workspaceFolderStr);92    if (!folderContext) {93      folderContext = new WorkspaceFolderContext();94      this.workspaceFolders.set(workspaceFolderStr, folderContext);95    }96    // Start the client for this language if necessary.97    let client = folderContext.clients.get(languageId);98    if (!client) {99      client = await this.activateWorkspaceFolder(100          workspaceFolder, serverSettingName, languageId, this.outputChannel);101      folderContext.clients.set(languageId, client);102    }103    return client;104  }105 106  /**107   *  Prepare a compilation database option for a server.108   */109  async prepareCompilationDatabaseServerOptions(110      languageName: string, workspaceFolder: vscode.WorkspaceFolder,111      configsToWatch: string[], pathsToWatch: string[],112      additionalServerArgs: string[]) {113    // Process the compilation databases attached for the workspace folder.114    let databases = config.get<string[]>(115        `${languageName}_compilation_databases`, workspaceFolder, []);116 117    // If no databases were explicitly specified, default to a database in the118    // 'build' directory within the current workspace.119    if (databases.length === 0) {120      if (workspaceFolder) {121        databases.push(workspaceFolder.uri.fsPath +122                       `/build/${languageName}_compile_commands.yml`);123      }124 125      // Otherwise, try to resolve each of the paths.126    } else {127      for await (let database of databases) {128        database = await this.resolvePath(database, '', workspaceFolder);129      }130    }131 132    configsToWatch.push(`${languageName}_compilation_databases`);133    pathsToWatch.push(...databases);134 135    // Setup the compilation databases as additional arguments to pass to the136    // server.137    databases.filter(database => database !== '');138    additionalServerArgs.push(...databases.map(139        (database) => `--${languageName}-compilation-database=${database}`));140  }141 142  /**143   *  Prepare the server options for a PDLL server, e.g. populating any144   *  accessible compilation databases.145   */146  async preparePDLLServerOptions(workspaceFolder: vscode.WorkspaceFolder,147                                 configsToWatch: string[],148                                 pathsToWatch: string[],149                                 additionalServerArgs: string[]) {150    await this.prepareCompilationDatabaseServerOptions(151        'pdll', workspaceFolder, configsToWatch, pathsToWatch,152        additionalServerArgs);153  }154 155  /**156   *  Prepare the server options for a TableGen server, e.g. populating any157   *  accessible compilation databases.158   */159  async prepareTableGenServerOptions(workspaceFolder: vscode.WorkspaceFolder,160                                     configsToWatch: string[],161                                     pathsToWatch: string[],162                                     additionalServerArgs: string[]) {163    await this.prepareCompilationDatabaseServerOptions(164        'tablegen', workspaceFolder, configsToWatch, pathsToWatch,165        additionalServerArgs);166  }167 168  /**169   *  Activate the language client for the given language in the given workspace170   *  folder.171   */172  async activateWorkspaceFolder(workspaceFolder: vscode.WorkspaceFolder,173                                serverSettingName: string, languageName: string,174                                outputChannel: vscode.OutputChannel):175      Promise<vscodelc.LanguageClient> {176    let configsToWatch: string[] = [];177    let filepathsToWatch: string[] = [];178    let additionalServerArgs: string[] = [];179    additionalServerArgs = config.get<string[]>(languageName + "_additional_server_args", null, []);180 181    // Initialize additional configurations for this server.182    if (languageName === 'pdll') {183      await this.preparePDLLServerOptions(workspaceFolder, configsToWatch,184                                          filepathsToWatch,185                                          additionalServerArgs);186    } else if (languageName == 'tablegen') {187      await this.prepareTableGenServerOptions(workspaceFolder, configsToWatch,188                                              filepathsToWatch,189                                              additionalServerArgs);190    }191 192    // Try to activate the language client.193    const [server, serverPath] = await this.startLanguageClient(194        workspaceFolder, outputChannel, serverSettingName, languageName,195        additionalServerArgs);196    configsToWatch.push(serverSettingName);197    filepathsToWatch.push(serverPath);198 199    // Watch for configuration changes on this folder.200    await configWatcher.activate(this, workspaceFolder, configsToWatch,201                                 filepathsToWatch);202    return server;203  }204 205  /**206   *  Start a new language client for the given language. Returns an array207   *  containing the opened server, or null if the server could not be started,208   *  and the resolved server path.209   */210  async startLanguageClient(workspaceFolder: vscode.WorkspaceFolder,211                            outputChannel: vscode.OutputChannel,212                            serverSettingName: string, languageName: string,213                            additionalServerArgs: string[]):214      Promise<[ vscodelc.LanguageClient, string ]> {215    const clientTitle = languageName.toUpperCase() + ' Language Client';216 217    // Get the path of the lsp-server that is used to provide language218    // functionality.219    var serverPath =220        await this.resolveServerPath(serverSettingName, workspaceFolder);221 222    // If the server path is empty, bail. We don't emit errors if the user223    // hasn't explicitly configured the server.224    if (serverPath === '') {225      return [ null, serverPath ];226    }227 228    // Check that the file actually exists.229    if (!fs.existsSync(serverPath)) {230      vscode.window231          .showErrorMessage(232              `${clientTitle}: Unable to resolve path for '${233                  serverSettingName}', please ensure the path is correct`,234              "Open Setting")235          .then((value) => {236            if (value === "Open Setting") {237              vscode.commands.executeCommand(238                  'workbench.action.openWorkspaceSettings',239                  {openToSide : false, query : `mlir.${serverSettingName}`});240            }241          });242      return [ null, serverPath ];243    }244 245    // Configure the server options.246    const serverOptions: vscodelc.ServerOptions = {247      command : serverPath,248      args : additionalServerArgs249    };250 251    // Configure file patterns relative to the workspace folder.252    let filePattern: vscode.GlobPattern = '**/*.' + languageName;253    let selectorPattern: string = null;254    if (workspaceFolder) {255      filePattern = new vscode.RelativePattern(workspaceFolder, filePattern);256      selectorPattern = `${workspaceFolder.uri.fsPath}/**/*`;257    }258 259    // Configure the middleware of the client. This is sort of abused to allow260    // for defining a "fallback" language server that operates on non-workspace261    // folders. Workspace folder language servers can properly filter out262    // documents not within the folder, but we can't effectively filter for263    // documents outside of the workspace. To support this, and avoid having two264    // servers targeting the same set of files, we use middleware to inject the265    // dynamic logic for checking if a document is in the workspace.266    let middleware = {};267    if (!workspaceFolder) {268      middleware = {269        didOpen : (document, next) : Promise<void> => {270          if (!vscode.workspace.getWorkspaceFolder(document.uri)) {271            return next(document);272          }273          return Promise.resolve();274        }275      };276    }277 278    // Configure the client options.279    const clientOptions: vscodelc.LanguageClientOptions = {280      documentSelector : [281        {language : languageName, pattern : selectorPattern},282      ],283      synchronize : {284        // Notify the server about file changes to language files contained in285        // the workspace.286        fileEvents : vscode.workspace.createFileSystemWatcher(filePattern)287      },288      outputChannel : outputChannel,289      workspaceFolder : workspaceFolder,290      middleware : middleware,291 292      // Don't switch to output window when the server returns output.293      revealOutputChannelOn : vscodelc.RevealOutputChannelOn.Never,294    };295 296    // Create the language client and start the client.297    let languageClient = new vscodelc.LanguageClient(298        languageName + '-lsp', clientTitle, serverOptions, clientOptions);299    languageClient.start();300    return [ languageClient, serverPath ];301  }302 303  /**304   * Given a server setting, return the default server path.305   */306  static getDefaultServerFilename(serverSettingName: string): string {307    if (serverSettingName === 'pdll_server_path') {308      return 'mlir-pdll-lsp-server';309    }310    if (serverSettingName === 'server_path') {311      return 'mlir-lsp-server';312    }313    if (serverSettingName === 'tablegen_server_path') {314      return 'tblgen-lsp-server';315    }316    return '';317  }318 319  /**320   * Try to resolve the given path, or the default path, with an optional321   * workspace folder. If a path could not be resolved, just returns the322   * input filePath.323   */324  async resolvePath(filePath: string, defaultPath: string,325                    workspaceFolder: vscode.WorkspaceFolder): Promise<string> {326    const configPath = filePath;327 328    // If the path is already fully resolved, there is nothing to do.329    if (path.isAbsolute(filePath)) {330      return filePath;331    }332 333    // If a path hasn't been set, try to use the default path.334    if (filePath === '') {335      if (defaultPath === '') {336        return filePath;337      }338      filePath = defaultPath;339 340      // Fallthrough to try resolving the default path.341    }342 343    // Try to resolve the path relative to the workspace.344    let filePattern: vscode.GlobPattern = '**/' + filePath;345    if (workspaceFolder) {346      filePattern = new vscode.RelativePattern(workspaceFolder, filePattern);347    }348    let foundUris = await vscode.workspace.findFiles(filePattern, null, 1);349    if (foundUris.length === 0) {350      // If we couldn't resolve it, just return the original path anyways. The351      // file might not exist yet.352      return configPath;353    }354    // Otherwise, return the resolved path.355    return foundUris[0].fsPath;356  }357 358  /**359   * Try to resolve the path for the given server setting, with an optional360   * workspace folder.361   */362  async resolveServerPath(serverSettingName: string,363                          workspaceFolder: vscode.WorkspaceFolder):364      Promise<string> {365    const serverPath = config.get<string>(serverSettingName, workspaceFolder);366    const defaultPath = MLIRContext.getDefaultServerFilename(serverSettingName);367    return this.resolvePath(serverPath, defaultPath, workspaceFolder);368  }369 370  /**371   * Return the language client for the given language and uri, or null if no372   * client is active.373   */374  getLanguageClient(uri: vscode.Uri,375                    languageName: string): vscodelc.LanguageClient {376    let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);377    let workspaceFolderStr =378        workspaceFolder ? workspaceFolder.uri.toString() : "";379    let folderContext = this.workspaceFolders.get(workspaceFolderStr);380    if (!folderContext) {381      return null;382    }383    return folderContext.clients.get(languageName);384  }385 386  dispose() {387    this.subscriptions.forEach((d) => { d.dispose(); });388    this.subscriptions = [];389    this.workspaceFolders.forEach((d) => { d.dispose(); });390    this.workspaceFolders.clear();391  }392}393