All files stable-shell-execution.ts

100% Statements 53/53
92.3% Branches 12/13
100% Functions 3/3
100% Lines 53/53

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 731x 1x                     1x 4x 4x 4x 4x 4x 1x 1x   3x   3x 3x     3x 1x 1x   2x 2x 2x 2x   2x 2x 2x 2x 2x 2x   2x 3x 3x 3x   2x 2x 3x 3x 3x 3x 2x 2x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 3x   3x 3x 3x  
import * as vscode from "vscode";
import { ShellIntegrationAccumulator } from "./extract-output";
import type { TerminalOutputRegisterDeps } from "./types";
 
type WindowWithShellExec = typeof vscode.window & {
  onDidStartTerminalShellExecution?: vscode.Event<vscode.TerminalShellExecutionStartEvent>;
};
 
/**
 * Stable API: per-command terminal output via TerminalShellExecution.read().
 * One ShellIntegrationAccumulator per execution (isolated state vs global proposed stream).
 */
export function registerStableShellExecutionTerminalOutput(
  context: vscode.ExtensionContext,
  deps: TerminalOutputRegisterDeps,
): boolean {
  const w = vscode.window as WindowWithShellExec;
  if (typeof w.onDidStartTerminalShellExecution !== "function") {
    return false;
  }
 
  const { maxChars, debounceMs, emitOutput } = deps;
 
  const subscription = w.onDidStartTerminalShellExecution!((e) => {
    const exec = e.execution as vscode.TerminalShellExecution & {
      read?: () => AsyncIterable<string>;
    };
    if (typeof exec.read !== "function") {
      return;
    }
 
    void (async () => {
      const acc = new ShellIntegrationAccumulator();
      let rolling = "";
      let timer: ReturnType<typeof setTimeout> | null = null;
 
      const flush = (): void => {
        timer = null;
        const trimmed = rolling.trim();
        if (!trimmed) return;
        emitOutput(trimmed.slice(-maxChars));
      };
 
      const scheduleFlush = (): void => {
        if (timer) clearTimeout(timer);
        timer = setTimeout(flush, debounceMs);
      };
 
      try {
        for await (const chunk of exec.read()) {
          const piece = acc.process(chunk);
          if (piece) {
            rolling += piece;
            if (rolling.length > maxChars * 4) {
              rolling = rolling.slice(-maxChars * 2);
            }
            scheduleFlush();
          }
        }
      } finally {
        if (timer) {
          clearTimeout(timer);
          timer = null;
        }
        flush();
      }
    })();
  });
 
  context.subscriptions.push(subscription);
  return true;
}