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  }