writeBytes method

void writeBytes(
  1. Uint8List data
)

Writes raw bytes from the underlying program to the terminal. This is the byte-level counterpart of write: UTF-8 is decoded incrementally, so a multibyte sequence split across two calls is reassembled instead of being replaced by U+FFFD on both sides (which is what per-chunk utf8.decode does). Malformed bytes decode to U+FFFD exactly like utf8.decode(bytes, allowMalformed: true).

A chunk without multibyte content is never decoded at all: bytes below 0x80 are their own code points, so one classifying scan decides between the fast path (bytes go straight to the buffer — no String, no code point list) and the parser. Only chunks that actually contain multibyte UTF-8 pay for decoding, with the control-character scan fused into it.

Note: data may be retained by the parser (when a chunk ends mid sequence), so do not mutate or reuse the buffer after the call — pass a fresh buffer per chunk, like socket and file reads do.

Do not interleave writeBytes with write on the same terminal: the two entry points track split sequences separately and interleaving them can reorder output.

Implementation

void writeBytes(Uint8List data) {
  if (!_utf8Decoder.hasPendingBytes) {
    // Classify the chunk in a single pass. Stops early at the first
    // multibyte lead byte — such chunks go through the decoder, which
    // re-detects control characters while decoding.
    var asciiOnly = true;
    var hasControlChars = false;
    for (var i = 0; i < data.length; i++) {
      final byte = data[i];
      if (byte >= 0x80) {
        asciiOnly = false;
        break;
      }
      if (byte < 0x20 || byte == 0x7F) {
        hasControlChars = true;
      }
    }

    if (asciiOnly) {
      if (!hasControlChars && !_parser.hasPendingInput) {
        // Fast path: plain text. The bytes are already code points
        // (Uint8List is a List<int>), so the buffer consumes them
        // zero-copy.
        if (data.isNotEmpty) {
          _precedingCodepoint = data.last;
        }
        _buffer.writeCodepoints(data);
        _notifyOutput();
        return;
      }
      // Pure ASCII with escapes or controls: the parser consumes the bytes
      // as code points directly, again without a decoding pass.
      _parser.writeCodepoints(data);
      _notifyOutput();
      return;
    }
  }

  // Multibyte content (or a sequence still pending from the previous
  // chunk): decode incrementally into the scratch buffer.
  final codepoints = _decodeScratch;
  _utf8Decoder.decodeInto(data, codepoints);

  if (!_parser.hasPendingInput && !_utf8Decoder.lastChunkHasControlChars) {
    // Fast path: decoded text with nothing the parser would dispatch on.
    if (codepoints.isNotEmpty) {
      _precedingCodepoint = codepoints.last;
    }
    _buffer.writeCodepoints(codepoints);
    codepoints.clear();
    _notifyOutput();
    return;
  }

  // The parser's queue stores the list, so hand over ownership and start a
  // fresh scratch buffer for the next chunk.
  _decodeScratch = <int>[];
  _parser.writeCodepoints(codepoints);
  _notifyOutput();
}