Files
Codeman/packages/xterm-zerolag-input/src/prompt-finder.ts
T
arkonandClaude Opus 4.6 53132e3a38 fix: address 7 issues found in xterm-zerolag-input audit
1. Regex global flag safety — strip `g` flag before matching to prevent
   lastIndex mutation and missing .index on match results
2. state.visible false before activate — null overlay no longer reports
   visible: true
3. removeChar() returns 'pending' | 'flushed' | false instead of boolean
   so consumers can distinguish whether to send backspace to PTY
4. removeChar() implements buffer detection cascade (step 3) — detects
   existing prompt text when both pending and flushed are empty
5. RenderKey includes text content, not just length — prevents stale
   renders when setFlushed() called with same count but different text
6. Remove dead `import type { Terminal }` from test file
7. Remove internal `FontStyle` from public exports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-22 10:24:03 +01:00

84 lines
2.8 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 '';
}
}