src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/vscode/src/extension.ts (about) 1 import * as path from 'path'; 2 import * as child_process from 'child_process'; 3 import * as vscode from 'vscode'; 4 import { LanguageClient } from 'vscode-languageclient/node'; 5 6 let client: LanguageClient | undefined; 7 8 export function activate(context: vscode.ExtensionContext) { 9 client = new LanguageClient( 10 "elvish", 11 "Elvish Language Server", 12 { command: "elvish", args: ["-lsp"] }, 13 { documentSelector: [{ scheme: "file", language: "elvish" }] } 14 ); 15 client.start(); 16 17 context.subscriptions.push(vscode.commands.registerCommand( 18 'elvish.updateTranscriptOutputForCodeAtCursor', 19 updateTranscriptOutputForCodeAtCursor)); 20 } 21 22 export function deactivate() { 23 return client?.stop(); 24 } 25 26 interface UpdateInstruction { 27 fromLine: number; 28 toLine: number; 29 content: string; 30 } 31 32 async function updateTranscriptOutputForCodeAtCursor() { 33 const editor = vscode.window.activeTextEditor; 34 if (!editor) { 35 return; 36 } 37 const {dir, base} = path.parse(editor.document.uri.fsPath); 38 // VS Code's line number is 0-based, but the ELVISH_TRANSCRIPT_RUN protocol 39 // uses 1-based line numbers. This is also used in the UI, where the user 40 // expects 1-based line numbers. 41 const lineno = editor.selection.active.line + 1; 42 43 await vscode.window.withProgress({ 44 location: vscode.ProgressLocation.Notification, 45 title: `Running ${base}:${lineno}...` 46 }, async (progress, token) => { 47 // Transcript tests uses what's on the disk, so we have to save the 48 // document first. 49 await editor.document.save(); 50 51 // See godoc of pkg/eval/evaltest for the protocol. 52 const {error, stdout} = await exec( 53 "go test -run TestTranscripts", 54 { 55 cwd: dir, 56 env: {...process.env, ELVISH_TRANSCRIPT_RUN: `${base}:${lineno}`}, 57 }); 58 if (error) { 59 const match = stdout.match(/UPDATE (.*)$/m); 60 if (match) { 61 const {fromLine, toLine, content} = JSON.parse(match[1]) as UpdateInstruction; 62 const range = new vscode.Range( 63 new vscode.Position(fromLine-1, 0), new vscode.Position(toLine-1, 0)); 64 editor.edit((editBuilder) => { 65 editBuilder.replace(range, content); 66 }); 67 } else { 68 vscode.window.showWarningMessage(`Unexpected test failure: ${stdout}`) 69 } 70 } else { 71 // TODO: Distinguish two different cases: 72 // 73 // - Output is already up-to-date 74 // - Cursor is in an invalid position. 75 // 76 // This needs to be detected by evaltest first. 77 vscode.window.showInformationMessage('Nothing to do.') 78 } 79 }); 80 } 81 82 // Wraps child_process.exec to return a promise. 83 function exec(cmd: string, options: child_process.ExecOptions): 84 Promise<{error: child_process.ExecException|null, stdout: string, stderr: string}> { 85 return new Promise((resolve) => { 86 child_process.exec(cmd, options, (error, stdout, stderr) => { 87 resolve({error, stdout, stderr}); 88 }); 89 }); 90 }