diff --git a/vscode/package.json b/vscode/package.json index 5826c3eb4..a24f75428 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -43,6 +43,11 @@ ] } ], + "taskDefinitions": [ + { + "type": "ruby_lsp_profile" + } + ], "menus": { "editor/context": [ { diff --git a/vscode/src/profileTaskProvider.ts b/vscode/src/profileTaskProvider.ts new file mode 100644 index 000000000..806b8abbe --- /dev/null +++ b/vscode/src/profileTaskProvider.ts @@ -0,0 +1,106 @@ +import * as vscode from "vscode"; + +import { Workspace } from "./workspace"; + +class ProfileTaskTerminal implements vscode.Pseudoterminal { + readonly writeEmitter = new vscode.EventEmitter(); + onDidWrite: vscode.Event = this.writeEmitter.event; + closeEmitter = new vscode.EventEmitter(); + onDidClose?: vscode.Event = this.closeEmitter.event; + + private readonly workspace: Workspace | undefined; + + constructor(workspace: Workspace | undefined) { + this.workspace = workspace; + } + + async open(_initialDimensions: vscode.TerminalDimensions | undefined) { + if (!this.workspace) { + this.writeEmitter.fire("No workspace found\r\n"); + this.closeEmitter.fire(1); + return; + } + + const currentFile = vscode.window.activeTextEditor?.document.uri.fsPath; + + if (!currentFile) { + this.writeEmitter.fire("No file opened in the editor to profile\r\n"); + this.closeEmitter.fire(1); + return; + } + + this.writeEmitter.fire(`Profiling ${currentFile}...\r\n`); + + const workspaceUri = this.workspace.workspaceFolder.uri; + const profileUri = vscode.Uri.joinPath(workspaceUri, "profile.json"); + const { stderr } = await this.workspace.execute( + `vernier run --output ${profileUri.fsPath} -- ruby ${currentFile}`, + ); + + try { + const profile = await vscode.workspace.fs.readFile(profileUri); + this.writeEmitter.fire( + "Successfully profiled. Generating visualization...", + ); + } catch (error) { + this.writeEmitter.fire( + `An error occurred while profiling (press any key to close):\r\n ${stderr}\r\n`, + ); + } + + this.closeEmitter.fire(0); + } + + close(): void {} + + // Close the task pseudo terminal if the user presses any keys + handleInput(_data: string): void { + this.closeEmitter.fire(0); + } +} + +export class ProfileTaskProvider implements vscode.TaskProvider { + static TaskType = "ruby_lsp_profile"; + + private readonly currentActiveWorkspace: ( + activeEditor?: vscode.TextEditor, + ) => Workspace | undefined; + + constructor( + currentActiveWorkspace: ( + activeEditor?: vscode.TextEditor, + ) => Workspace | undefined, + ) { + this.currentActiveWorkspace = currentActiveWorkspace; + } + + provideTasks( + _token: vscode.CancellationToken, + ): vscode.ProviderResult { + return [ + new vscode.Task( + { type: ProfileTaskProvider.TaskType }, + vscode.TaskScope.Workspace, + "Profile current Ruby file", + "ruby_lsp", + ), + ]; + } + + resolveTask( + task: vscode.Task, + _token: vscode.CancellationToken, + ): vscode.ProviderResult { + const workspace = this.currentActiveWorkspace(); + + return new vscode.Task( + task.definition, + vscode.TaskScope.Workspace, + "Profile current Ruby file", + "ruby_lsp", + new vscode.CustomExecution((): Promise => { + return Promise.resolve(new ProfileTaskTerminal(workspace)); + }), + ); + } +} diff --git a/vscode/src/rubyLsp.ts b/vscode/src/rubyLsp.ts index 0f3829e19..6d70ad120 100644 --- a/vscode/src/rubyLsp.ts +++ b/vscode/src/rubyLsp.ts @@ -18,6 +18,7 @@ import { DependenciesTree } from "./dependenciesTree"; import { Rails } from "./rails"; import { ChatAgent } from "./chatAgent"; import { collectRubyLspInfo } from "./infoCollector"; +import { ProfileTaskProvider } from "./profileTaskProvider"; // The RubyLsp class represents an instance of the entire extension. This should only be instantiated once at the // activation event. One instance of this class controls all of the existing workspaces, telemetry and handles all @@ -109,6 +110,10 @@ export class RubyLsp { }, }), LOG_CHANNEL, + vscode.tasks.registerTaskProvider( + ProfileTaskProvider.TaskType, + new ProfileTaskProvider(this.currentActiveWorkspace.bind(this)), + ), ); }