add Terminal.writeBytes with a persistent UTF-8 decoder (T-373)
The terminal's only ingestion API was write(String), so both byte consumers decoded per chunk — a multi-byte rune split across PTY reads (or a tail window starting mid-character, which FileTailFollower does by construction) rendered as U+FFFD garbage. writeBytes feeds a per-instance chunked Utf8Decoder that carries partial-rune state across calls; the terminal pane and the Bash live-tail follower now use it, and write(String) stays for tests and programmatic writes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -463,7 +463,9 @@ class _BashLiveTailState extends State<_BashLiveTail> {
|
||||
if (source == null) return; // no file-backed source → muted note in build
|
||||
final term = Terminal(maxLines: 1000);
|
||||
_terminal = term;
|
||||
_follower = FileTailFollower(source, onData: (bytes) => term.write(utf8.decode(bytes, allowMalformed: true)));
|
||||
// writeBytes: the follower's chunk boundaries are arbitrary (it can even
|
||||
// start mid-rune by construction) — keep decode state across reads (T-373).
|
||||
_follower = FileTailFollower(source, onData: term.writeBytes);
|
||||
unawaited(_follower!.start());
|
||||
}
|
||||
|
||||
|
||||
@@ -114,8 +114,9 @@ class _TerminalPaneState extends State<TerminalPane> {
|
||||
case 'pane.output':
|
||||
final b64 = event.data['bytes_b64'];
|
||||
if (b64 is String) {
|
||||
final bytes = base64Decode(b64);
|
||||
_terminal.write(utf8.decode(bytes, allowMalformed: true));
|
||||
// writeBytes keeps UTF-8 decode state across chunks — a rune
|
||||
// split across PTY reads must not become U+FFFD (T-373).
|
||||
_terminal.writeBytes(base64Decode(b64));
|
||||
}
|
||||
case 'pane.exit':
|
||||
setState(() => _error = 'Shell exited.');
|
||||
|
||||
Reference in New Issue
Block a user