ingest method

void ingest(
  1. String chunk, {
  2. required bool isError,
})

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();
}