blob: 6463e6625132ba6919d20e9e17519d36d515b4bb [file] [edit]
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:math' as math;
/// A simple fake terminal that interprets a subset of ANSI escape sequences
/// to maintain a virtual screen buffer.
///
/// Supported sequences:
/// - `\x1b[nA`: Move cursor up `n` lines.
/// - `\x1b[2K`: Clear the current line.
///
/// Ignored sequences (stripped from output):
/// - `\x1b[1m`: Bold.
/// - `\x1b[0m`: Reset formatting.
/// - `\x1b[?25l`: Hide cursor.
/// - `\x1b[?25h`: Show cursor.
class FakeTerminal {
final List<String> _lines = [''];
int _cursorRow = 0;
int _cursorCol = 0;
/// Writes text to the terminal, interpreting supported escape sequences.
void write(String text) {
// Regex for the specific escape sequences used in select_dialog.dart
final seqRegex = RegExp(
r'(\x1b\[\d*A|\x1b\[2K|\x1b\[1m|\x1b\[0m|\x1b\[\?25[lh])',
);
var lastEnd = 0;
for (final match in seqRegex.allMatches(text)) {
if (match.start > lastEnd) {
_writeText(text.substring(lastEnd, match.start));
}
final seq = match.group(0)!;
if (seq.endsWith('A')) {
final n = int.tryParse(seq.substring(2, seq.length - 1)) ?? 1;
_cursorRow = math.max(0, _cursorRow - n);
} else if (seq == '\x1b[2K') {
_lines[_cursorRow] = '';
} else {
// Ignore style and cursor visibility sequences
}
lastEnd = match.end;
}
if (lastEnd < text.length) {
_writeText(text.substring(lastEnd));
}
}
void _writeText(String text) {
final textLines = text.split('\n');
for (var i = 0; i < textLines.length; i++) {
if (i > 0) {
_cursorRow++;
_cursorCol = 0;
if (_cursorRow >= _lines.length) {
_lines.add('');
}
}
final currentLine = _lines[_cursorRow];
final textToAppend = textLines[i];
if (_cursorCol < currentLine.length) {
final end = _cursorCol + textToAppend.length;
if (end <= currentLine.length) {
_lines[_cursorRow] =
currentLine.substring(0, _cursorCol) +
textToAppend +
currentLine.substring(end);
} else {
_lines[_cursorRow] =
currentLine.substring(0, _cursorCol) + textToAppend;
}
} else {
_lines[_cursorRow] = currentLine.padRight(_cursorCol) + textToAppend;
}
_cursorCol += textToAppend.length;
}
}
/// The currently displayed lines of the terminal.
List<String> get lines => List.unmodifiable(_lines);
/// The full displayed content as a single string.
String get content => _lines.join('\n');
/// The current cursor row (0-indexed).
int get cursorRow => _cursorRow;
}