ingest method
Feeds one write from the child's stdout or stderr.
Chunks arrive one write at a time and split wherever the pipe felt like splitting them, so a line can straddle any number of them. The tail of a chunk is held until a newline terminates it, per stream — splicing a half-written stdout line onto the next stderr chunk would corrupt both.
Unlike prefixLines, which writes into a shared append-only stream and
so has to drop a frame that has not resolved, a pane owns its region and
can redraw in place. An unresolved frame is kept as the transient last
line and the next frame replaces it: one ⠋ Retrieving… animating,
rather than twenty stacked.
Implementation
void ingest(String chunk, {required bool isError}) {
if (chunk.isEmpty) return;
final stream = isError ? _stderr : _stdout;
var text = stream.pending + chunk;
// A rule, not a truncation. See [kRedrawDivider] for why obeying this
// literally is wrong in a pane with scrollback, and [clear] for where a
// real clear comes from instead.
//
// Turned into a line of its own so it settles through the ordinary path
// below: the divider is a thing the pane shows, and the one place that
// decides what the pane shows is the loop that follows.
if (text.contains(kClearScreen)) {
text = text.replaceAll(kClearScreen, '\n$kRedrawDivider\n');
}
final segments = text.split('\n');
// The last segment has no newline after it yet, so the child may still be
// drawing it.
stream.pending = collapseRedraws(segments.removeLast());
for (final segment in segments) {
final frame = lastFrame(segment);
if (isBlank(frame)) continue;
if (frame == kRedrawDivider) {
_divide();
continue;
}
if (isUnfinished(frame)) {
// A spinner frame that ended in a newline anyway. Still unresolved,
// so it stays replaceable.
stream.transient = ServiceLogLine(frame, isError: isError);
} else {
stream.transient = null;
_settle(ServiceLogLine(frame, isError: isError));
}
_note(frame);
}
if (lastFrame(stream.pending) case final tail when !isBlank(tail)) {
stream.transient = ServiceLogLine(tail, isError: isError);
_note(tail);
}
notifyListeners();
}