feed method
Feeds a chunk of remote stdout, returning clean output and any cwd found.
Implementation
CwdScan feed(Uint8List chunk) {
_buffer.addAll(chunk);
final output = BytesBuilder(copy: false);
String? cwd;
String? branch;
String? gitStatus;
String? privilege;
int? exitCode;
var completed = false;
final tokenBytes = utf8.encode(token);
int? pendingStart; // start of a found-but-unterminated token, if any.
while (true) {
final start = _indexOf(_buffer, tokenBytes);
if (start < 0) break;
final newline = _buffer.indexOf(0x0a, start + tokenBytes.length);
if (newline < 0) {
// A complete token is present but its terminating newline has not
// arrived yet (e.g. winpty scraped the console mid-line, splitting the
// marker from its `\n`). Retain from the token start so it is not
// leaked; a later feed() completes it. Without this the token body is
// flushed to the user and the completion signal is lost.
pendingStart = start;
break;
}
output.add(Uint8List.fromList(_buffer.sublist(0, start)));
// On a real PTY the kernel's ONLCR translates the marker's trailing `\n`
// into `\r\n`; drop that `\r` so it is not captured as part of the last
// field (otherwise an empty privilege field becomes "\r", whose carriage
// return corrupts the rendered prompt).
final fieldsStart = start + tokenBytes.length;
final fieldsEnd = (newline > fieldsStart && _buffer[newline - 1] == 0x0d)
? newline - 1
: newline;
completed = true;
// A real Windows PTY (winpty) renders the marker line through a console
// scraper that injects VT escape sequences around the fields and before
// the `\r\n` — erase-line (`\x1b[K`) and cursor show/hide (`\x1b[?25h/l`).
// Strip every escape from the field region so they never pollute the cwd
// (a polluted cwd later fails TAB-completion's `chdir`) or other fields.
// The fields themselves never legitimately contain an ESC.
final fieldText = _stripAnsi(
utf8.decode(
_buffer.sublist(fieldsStart, fieldsEnd),
allowMalformed: true,
),
);
// A ping marker carries no fields (just the token, possibly wrapped in the
// PTY's escapes); after stripping it is empty, so it only signals
// completion and leaves the cached cwd/git state untouched.
if (fieldText.isNotEmpty) {
final fields = fieldText.split('\t');
cwd = fields[0];
branch = _field(fields, 1);
gitStatus = _field(fields, 2);
privilege = _field(fields, 3);
final ec = _field(fields, 4);
if (ec != null) exitCode = int.tryParse(ec);
}
_buffer.removeRange(0, newline + 1);
}
if (pendingStart != null) {
// Flush everything before the unterminated token; keep the token (and any
// partial fields) until its newline arrives.
if (pendingStart > 0) {
output.add(Uint8List.fromList(_buffer.sublist(0, pendingStart)));
_buffer.removeRange(0, pendingStart);
}
} else {
// Retain only the longest buffer suffix that could start a split token, so
// partial tokens are never leaked but normal output is not held back.
final keep = _tokenPrefixSuffix(_buffer, tokenBytes);
if (_buffer.length > keep) {
final flush = _buffer.length - keep;
output.add(Uint8List.fromList(_buffer.sublist(0, flush)));
_buffer.removeRange(0, flush);
}
}
return CwdScan(
output.takeBytes(),
cwd,
branch,
gitStatus,
privilege,
completed,
exitCode,
);
}