From 79c05e00cac716cd27a562e21e5c40fb73b00767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Mon, 21 Oct 2024 15:36:09 +0200 Subject: [PATCH] Add code lens for running `main` (#28) --- src/effektManager.ts | 3 +- src/extension.ts | 78 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/effektManager.ts b/src/effektManager.ts index 231ce6e..812030c 100644 --- a/src/effektManager.ts +++ b/src/effektManager.ts @@ -505,7 +505,7 @@ export class EffektManager { } /** - * Gets the command arguments for starting the Effekt server. + * Gets the command arguments for starting Effekt. * @returns An array of command arguments. */ public getEffektArgs(): string[] { @@ -519,7 +519,6 @@ export class EffektManager { const folders = vscode.workspace.workspaceFolders || []; folders.forEach(folder => args.push("--includes", folder.uri.fsPath)); - args.push("--server"); return args; } } diff --git a/src/extension.ts b/src/extension.ts index d83e476..5ae1fd5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import * as net from 'net'; let client: LanguageClient; let effektManager: EffektManager; +let effektRunnerTerminal: vscode.Terminal | null = null; function registerCommands(context: vscode.ExtensionContext) { context.subscriptions.push( @@ -18,10 +19,61 @@ function registerCommands(context: vscode.ExtensionContext) { vscode.commands.registerCommand('effekt.restartServer', async () => { await client?.stop(); client?.start(); - }) + }), + vscode.commands.registerCommand('effekt.runFile', runEffektFile), ); } +async function getEffektTerminal() { + if (effektRunnerTerminal === null || effektRunnerTerminal.exitStatus !== undefined) { + effektRunnerTerminal = vscode.window.createTerminal({ + name: 'Effekt Runner', + isTransient: true, // Don't persist across VSCode restarts + }); + effektRunnerTerminal.hide(); + } + return effektRunnerTerminal; +} + +async function runEffektFile(uri: vscode.Uri) { + // Save the document if it has unsaved changes + const document = await vscode.workspace.openTextDocument(uri); + if (document.isDirty) { + await document.save(); + } + + const terminal = await getEffektTerminal(); + + const effektExecutable = await effektManager.locateEffektExecutable(); + const args = [ uri.fsPath, ...effektManager.getEffektArgs() ]; + + terminal.sendText("clear"); + terminal.sendText(`"${effektExecutable.path}" ${args.join(' ')}`); + terminal.show(); +} + +class EffektRunCodeLensProvider implements vscode.CodeLensProvider { + public provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] { + const codeLenses: vscode.CodeLens[] = []; + const text = document.getText(); + const mainFunctionRegex = /^def\s+main\s*\(\s*\)/gm; + let match: RegExpExecArray | null; + + while ((match = mainFunctionRegex.exec(text)) !== null) { + const line = document.lineAt(document.positionAt(match.index).line); + const range = new vscode.Range(line.range.start, line.range.end); + + codeLenses.push(new vscode.CodeLens(range, { + title: '$(play) Run', + command: 'effekt.runFile', + arguments: [document.uri] + })); + } + + return codeLenses; + } +} + export async function activate(context: vscode.ExtensionContext) { effektManager = new EffektManager(); @@ -32,6 +84,27 @@ export async function activate(context: vscode.ExtensionContext) { registerCommands(context); + // Register the CodeLens provider + context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + { language: 'effekt', scheme: 'file' }, + new EffektRunCodeLensProvider() + ), + vscode.languages.registerCodeLensProvider( + { language: 'literate effekt', scheme: 'file' }, + new EffektRunCodeLensProvider() + ) + ); + + // Clean up REPL when closed + context.subscriptions.push( + vscode.window.onDidCloseTerminal(terminal => { + if (terminal === effektRunnerTerminal) { + effektRunnerTerminal = null; + } + }) + ); + const config = vscode.workspace.getConfiguration("effekt"); let serverOptions: ServerOptions; @@ -48,7 +121,7 @@ export async function activate(context: vscode.ExtensionContext) { }; } else { const effektExecutable = await effektManager.locateEffektExecutable(); - const args = effektManager.getEffektArgs(); + const args = ["--server", ...effektManager.getEffektArgs()]; /* > Node.js will now error with EINVAL if a .bat or .cmd file is passed to child_process.spawn and child_process.spawnSync without the shell option set. * > If the input to spawn/spawnSync is sanitized, users can now pass { shell: true } as an option to prevent the occurrence of EINVALs errors. @@ -204,6 +277,7 @@ export async function activate(context: vscode.ExtensionContext) { } export function deactivate(): Thenable | undefined { + if (effektRunnerTerminal) effektRunnerTerminal.dispose() if (!client) { return undefined; }