mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 07:59:42 +02:00
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>
75 lines
2.5 KiB
TypeScript
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 '';
|
|
}
|
|
}
|