Files
Codeman/packages/xterm-zerolag-input/src/prompt-finder.ts
T
Codeman maintainer a30524060a feat(predictive-echo): PredictiveEchoAddon + Layers 1-3 test suites (0.2.0)
Mosh-style write-through prediction: the consumer sends every keystroke
unchanged; the addon paints predicted glyphs and reconciles against the
parsed buffer. Confirm = cell match + cursor advance (placeholder-safe,
repaint-safe); two-pass mismatch cascade with neutral blanks (measured:
codex clears its placeholder on first echo); TTL bound; baseY-based line
reads; scroll/resize/off-row clears. Zero edits to zerolag-input-addon.ts.

Tests: 30 addon-law specs + renderer geometry (fake performance clock for
TTL/grace), 6 replay suites running the real algorithm through a real
@xterm/headless parser fed by the recorded codex fixtures, and a
500-iteration seeded fuzz with per-op span/record + grid invariants.
227 total, the pre-existing 175 untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 04:25:24 +02:00

75 lines
2.5 KiB
TypeScript

import type { XtermTerminal, PromptFinder, PromptPosition } from './types.js';
/**
* Find the prompt in the terminal buffer using the configured strategy.
* Scans bottom-up through the viewport to find the most recent prompt.
*
* @returns The prompt position (viewport-relative), or `null` if not found.
*/
export function findPrompt(terminal: XtermTerminal, finder: PromptFinder): PromptPosition | null {
try {
const buffer = terminal.buffer.active;
const viewportTop = buffer.viewportY;
switch (finder.type) {
case 'character': {
for (let row = terminal.rows - 1; row >= 0; row--) {
const line = buffer.getLine(viewportTop + row);
if (!line) continue;
const text = line.translateToString(true);
const idx = text.lastIndexOf(finder.char);
if (idx >= 0) return { row, col: idx };
}
return null;
}
case 'regex': {
// Create a fresh non-global regex to avoid lastIndex mutation
// and ensure .match() returns a single result with .index
const pattern = finder.pattern;
const safePattern = pattern.global ? new RegExp(pattern.source, pattern.flags.replace('g', '')) : pattern;
for (let row = terminal.rows - 1; row >= 0; row--) {
const line = buffer.getLine(viewportTop + row);
if (!line) continue;
const text = line.translateToString(true);
const match = text.match(safePattern);
if (match) {
const col = match.index ?? 0;
return { row, col };
}
}
return null;
}
case 'custom':
return finder.find(terminal);
default:
return null;
}
} catch {
return null;
}
}
/**
* Read text after the prompt position on the same line.
*
* @param terminal - The xterm.js terminal instance
* @param prompt - The prompt position in the viewport
* @param offset - Characters to skip after the prompt marker (e.g., 2 for "> ")
* @returns The text after the prompt, trimmed. Empty string if nothing found.
*/
export function readTextAfterPrompt(terminal: XtermTerminal, prompt: PromptPosition, offset: number): string {
try {
const buffer = terminal.buffer.active;
const absRow = buffer.viewportY + prompt.row;
const line = buffer.getLine(absRow);
if (!line) return '';
const lineText = line.translateToString(true);
return lineText.slice(prompt.col + offset).trimEnd();
} catch {
return '';
}
}