| import * as base64 from 'base64-js' |
| import * as vscode from 'vscode' |
| |
| import {MLIRContext} from '../mlirContext'; |
| |
| /** |
| * The parameters to the mlir/convert(To|From)Bytecode commands. These |
| * parameters are: |
| * - `uri`: The URI of the file to convert. |
| */ |
| type ConvertBytecodeParams = Partial<{uri : string}>; |
| |
| /** |
| * The output of the mlir/convert(To|From)Bytecode commands: |
| * - `output`: The output buffer of the command, e.g. a .mlir or bytecode |
| * buffer. |
| */ |
| type ConvertBytecodeResult = Partial<{output : string}>; |
| |
| /** |
| * A custom filesystem that is used to convert MLIR bytecode files to text for |
| * use in the editor, but still use bytecode on disk. |
| */ |
| class BytecodeFS implements vscode.FileSystemProvider { |
| mlirContext: MLIRContext; |
| |
| constructor(mlirContext: MLIRContext) { this.mlirContext = mlirContext; } |
| |
| /* |
| * Forward to the default filesystem for the various methods that don't need |
| * to understand the bytecode <-> text translation. |
| */ |
| readDirectory(uri: vscode.Uri): Thenable<[ string, vscode.FileType ][]> { |
| return vscode.workspace.fs.readDirectory(uri); |
| } |
| delete(uri: vscode.Uri): void { |
| vscode.workspace.fs.delete(uri.with({scheme : "file"})); |
| } |
| stat(uri: vscode.Uri): Thenable<vscode.FileStat> { |
| return vscode.workspace.fs.stat(uri.with({scheme : "file"})); |
| } |
| rename(oldUri: vscode.Uri, newUri: vscode.Uri, |
| options: {overwrite: boolean}): void { |
| vscode.workspace.fs.rename(oldUri.with({scheme : "file"}), |
| newUri.with({scheme : "file"}), options); |
| } |
| createDirectory(uri: vscode.Uri): void { |
| vscode.workspace.fs.createDirectory(uri.with({scheme : "file"})); |
| } |
| watch(_uri: vscode.Uri, _options: { |
| readonly recursive: boolean; readonly excludes : readonly string[] |
| }): vscode.Disposable { |
| return new vscode.Disposable(() => {}); |
| } |
| |
| private _emitter = new vscode.EventEmitter<vscode.FileChangeEvent[]>(); |
| readonly onDidChangeFile: vscode.Event<vscode.FileChangeEvent[]> = |
| this._emitter.event; |
| |
| /* |
| * Read in a bytecode file, converting it to text before returning it to the |
| * caller. |
| */ |
| async readFile(uri: vscode.Uri): Promise<Uint8Array> { |
| // Try to start a language client for this file so that we can parse |
| // it. |
| const client = |
| await this.mlirContext.getOrActivateLanguageClient(uri, 'mlir'); |
| if (!client) { |
| throw new Error( |
| 'Failed to activate mlir language server to read bytecode'); |
| } |
| |
| // Ask the client to do the conversion. |
| let result: ConvertBytecodeResult; |
| try { |
| let params: ConvertBytecodeParams = {uri : uri.toString()}; |
| result = await client.sendRequest('mlir/convertFromBytecode', params); |
| } catch (e) { |
| vscode.window.showErrorMessage(e.message); |
| throw new Error(`Failed to read bytecode file: ${e}`); |
| } |
| let resultBuffer = new TextEncoder().encode(result.output); |
| |
| // NOTE: VSCode does not allow for extensions to manage files above 50mb. |
| // Detect that here and if our result is too large for us to manage, alert |
| // the user and open it as a new temporary .mlir file. |
| if (resultBuffer.length > (50 * 1024 * 1024)) { |
| const openAsTempInstead: vscode.MessageItem = { |
| title : 'Open as temporary .mlir instead', |
| }; |
| const message: string = `Failed to open bytecode file "${ |
| uri.toString()}". Cannot edit converted bytecode files larger than 50MB.`; |
| const errorResult: vscode.MessageItem|undefined = |
| await vscode.window.showErrorMessage(message, openAsTempInstead); |
| if (errorResult === openAsTempInstead) { |
| let tempFile = await vscode.workspace.openTextDocument({ |
| language : 'mlir', |
| content : result.output, |
| }); |
| await vscode.window.showTextDocument(tempFile); |
| } |
| throw new Error(message); |
| } |
| |
| return resultBuffer; |
| } |
| |
| /* |
| * Save the provided content, which contains MLIR text, as bytecode. |
| */ |
| async writeFile(uri: vscode.Uri, content: Uint8Array, |
| _options: {create: boolean, overwrite: boolean}) { |
| // Get the language client managing this file. |
| let client = this.mlirContext.getLanguageClient(uri, 'mlir'); |
| if (!client) { |
| throw new Error( |
| 'Failed to activate mlir language server to write bytecode'); |
| } |
| |
| // Ask the client to do the conversion. |
| let convertParams: ConvertBytecodeParams = { |
| uri : uri.toString(), |
| }; |
| const result: ConvertBytecodeResult = |
| await client.sendRequest('mlir/convertToBytecode', convertParams); |
| await vscode.workspace.fs.writeFile(uri.with({scheme : "file"}), |
| base64.toByteArray(result.output)); |
| } |
| } |
| |
| /** |
| * A custom bytecode document for use by the custom editor provider below. |
| */ |
| class BytecodeDocument implements vscode.CustomDocument { |
| readonly uri: vscode.Uri; |
| |
| constructor(uri: vscode.Uri) { this.uri = uri; } |
| dispose(): void {} |
| } |
| |
| /** |
| * A custom editor provider for MLIR bytecode that allows for non-binary |
| * interpretation. |
| */ |
| class BytecodeEditorProvider implements |
| vscode.CustomReadonlyEditorProvider<BytecodeDocument> { |
| public async openCustomDocument(uri: vscode.Uri, _openContext: any, |
| _token: vscode.CancellationToken): |
| Promise<BytecodeDocument> { |
| return new BytecodeDocument(uri); |
| } |
| |
| public async resolveCustomEditor(document: BytecodeDocument, |
| _webviewPanel: vscode.WebviewPanel, |
| _token: vscode.CancellationToken): |
| Promise<void> { |
| // Ask the user for the desired view type. |
| const editType = await vscode.window.showQuickPick( |
| [ {label : '.mlir', description : "Edit as a .mlir text file"} ], |
| {title : 'Select an editor for the bytecode.'}, |
| ); |
| |
| // If we don't have a valid view type, just bail. |
| if (!editType) { |
| await vscode.commands.executeCommand( |
| 'workbench.action.closeActiveEditor'); |
| return; |
| } |
| |
| // TODO: We should also provide a non-`.mlir` way of viewing the |
| // bytecode, which should also ideally have some support for invalid |
| // bytecode files. |
| |
| // Close the active editor given that we aren't using it. |
| await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); |
| |
| // Display the file using a .mlir format. |
| await vscode.window.showTextDocument( |
| document.uri.with({scheme : "mlir.bytecode-mlir"}), |
| {preview : true, preserveFocus : false}); |
| } |
| } |
| |
| /** |
| * Register the necessary providers for supporting MLIR bytecode. |
| */ |
| export function registerMLIRBytecodeExtensions(context: vscode.ExtensionContext, |
| mlirContext: MLIRContext) { |
| vscode.workspace.registerFileSystemProvider("mlir.bytecode-mlir", |
| new BytecodeFS(mlirContext)); |
| vscode.window.registerCustomEditorProvider('mlir.bytecode', |
| new BytecodeEditorProvider()); |
| } |