[NFC][AArch64] Explicitly define undefined bits for instructions (#122129)
[llvm-project.git] / mlir / utils / vscode / src / mlirContext.ts
blobe12aa92522d0838f2f992fa1076fcf1bfc9f0c2d
1 import * as fs from 'fs';
2 import * as path from 'path';
3 import * as vscode from 'vscode';
4 import * as vscodelc from 'vscode-languageclient/node';
6 import * as config from './config';
7 import * as configWatcher from './configWatcher';
9 /**
10  *  This class represents the context of a specific workspace folder.
11  */
12 class WorkspaceFolderContext implements vscode.Disposable {
13   dispose() {
14     this.clients.forEach(async client => await client.stop());
15     this.clients.clear();
16   }
18   clients: Map<string, vscodelc.LanguageClient> = new Map();
21 /**
22  *  This class manages all of the MLIR extension state,
23  *  including the language client.
24  */
25 export class MLIRContext implements vscode.Disposable {
26   subscriptions: vscode.Disposable[] = [];
27   workspaceFolders: Map<string, WorkspaceFolderContext> = new Map();
28   outputChannel: vscode.OutputChannel;
30   /**
31    *  Activate the MLIR context, and start the language clients.
32    */
33   async activate(outputChannel: vscode.OutputChannel) {
34     this.outputChannel = outputChannel;
36     // This lambda is used to lazily start language clients for the given
37     // document. It removes the need to pro-actively start language clients for
38     // 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     }
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   }
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     }
78     // Check the scheme of the uri.
79     let validSchemes = [ 'file', 'mlir.bytecode-mlir' ];
80     if (!validSchemes.includes(uri.scheme)) {
81       return null;
82     }
84     // Resolve the workspace folder if this document is in one. We use the
85     // 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() : "";
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   }
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, []);
117     // If no databases were explicitly specified, default to a database in the
118     // '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       }
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     }
132     configsToWatch.push(`${languageName}_compilation_databases`);
133     pathsToWatch.push(...databases);
135     // Setup the compilation databases as additional arguments to pass to the
136     // server.
137     databases.filter(database => database !== '');
138     additionalServerArgs.push(...databases.map(
139         (database) => `--${languageName}-compilation-database=${database}`));
140   }
142   /**
143    *  Prepare the server options for a PDLL server, e.g. populating any
144    *  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   }
155   /**
156    *  Prepare the server options for a TableGen server, e.g. populating any
157    *  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   }
168   /**
169    *  Activate the language client for the given language in the given workspace
170    *  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, []);
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     }
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);
199     // Watch for configuration changes on this folder.
200     await configWatcher.activate(this, workspaceFolder, configsToWatch,
201                                  filepathsToWatch);
202     return server;
203   }
205   /**
206    *  Start a new language client for the given language. Returns an array
207    *  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';
217     // Get the path of the lsp-server that is used to provide language
218     // functionality.
219     var serverPath =
220         await this.resolveServerPath(serverSettingName, workspaceFolder);
222     // If the server path is empty, bail. We don't emit errors if the user
223     // hasn't explicitly configured the server.
224     if (serverPath === '') {
225       return [ null, serverPath ];
226     }
228     // Check that the file actually exists.
229     if (!fs.existsSync(serverPath)) {
230       vscode.window
231           .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     }
245     // Configure the server options.
246     const serverOptions: vscodelc.ServerOptions = {
247       command : serverPath,
248       args : additionalServerArgs
249     };
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     }
259     // Configure the middleware of the client. This is sort of abused to allow
260     // for defining a "fallback" language server that operates on non-workspace
261     // folders. Workspace folder language servers can properly filter out
262     // documents not within the folder, but we can't effectively filter for
263     // documents outside of the workspace. To support this, and avoid having two
264     // servers targeting the same set of files, we use middleware to inject the
265     // 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     }
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 in
285         // the workspace.
286         fileEvents : vscode.workspace.createFileSystemWatcher(filePattern)
287       },
288       outputChannel : outputChannel,
289       workspaceFolder : workspaceFolder,
290       middleware : middleware,
292       // Don't switch to output window when the server returns output.
293       revealOutputChannelOn : vscodelc.RevealOutputChannelOn.Never,
294     };
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   }
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   }
319   /**
320    * Try to resolve the given path, or the default path, with an optional
321    * workspace folder. If a path could not be resolved, just returns the
322    * input filePath.
323    */
324   async resolvePath(filePath: string, defaultPath: string,
325                     workspaceFolder: vscode.WorkspaceFolder): Promise<string> {
326     const configPath = filePath;
328     // If the path is already fully resolved, there is nothing to do.
329     if (path.isAbsolute(filePath)) {
330       return filePath;
331     }
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;
340       // Fallthrough to try resolving the default path.
341     }
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. The
351       // file might not exist yet.
352       return configPath;
353     }
354     // Otherwise, return the resolved path.
355     return foundUris[0].fsPath;
356   }
358   /**
359    * Try to resolve the path for the given server setting, with an optional
360    * 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   }
370   /**
371    * Return the language client for the given language and uri, or null if no
372    * 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   }
386   dispose() {
387     this.subscriptions.forEach((d) => { d.dispose(); });
388     this.subscriptions = [];
389     this.workspaceFolders.forEach((d) => { d.dispose(); });
390     this.workspaceFolders.clear();
391   }