diff --git a/CMakeLists.txt b/CMakeLists.txt index b0c6727..399c446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(go-libghostty LANGUAGES C) include(FetchContent) FetchContent_Declare(ghostty GIT_REPOSITORY https://github.com/ghostty-org/ghostty.git - GIT_TAG 3790fb78fecb3577dee30c40efe1ced3e3f0d9a1 + GIT_TAG 4816afc74201c4a8170223fb43e7e6fdbaa34a0a ) FetchContent_MakeAvailable(ghostty) diff --git a/formatter.go b/formatter.go index be4ed55..77da9f5 100644 --- a/formatter.go +++ b/formatter.go @@ -290,20 +290,19 @@ func (f *Formatter) FormatString() (string, error) { return string(b), nil } -// WriteTo implements io.WriterTo. It formats the current terminal -// state and writes the entire output to w. +// WriteTo implements io.WriterTo. It formats the current terminal state and +// streams the output directly to w without first allocating the complete +// formatted result. The writer is called synchronously and must not call +// methods on f or its terminal. The returned count includes bytes accepted +// before an error. +// C: ghostty_formatter_format func (f *Formatter) WriteTo(w io.Writer) (int64, error) { - b, err := f.Format() + bridge, writer, err := newGhosttyWriter(w) if err != nil { return 0, err } - n, err := w.Write(b) - // Writers are required to report an error when they accept fewer bytes - // than requested. Guard the io.WriterTo contract even when a faulty writer - // violates that requirement so truncated formatter output is not reported - // as a successful write. - if n != len(b) && err == nil { - err = io.ErrShortWrite - } - return int64(n), err + defer bridge.close() + + result := C.ghostty_formatter_format(f.ptr, writer) + return bridge.written, resultErrorWithCallback(result, bridge.err) } diff --git a/formatter_test.go b/formatter_test.go index ef0ad84..cd6dcfd 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -11,8 +11,8 @@ import ( // Verify interface satisfaction at compile time. var _ io.WriterTo = (*Formatter)(nil) -// shortFormatterWriter simulates a broken io.Writer that accepts only one -// byte without reporting the required error. +// shortFormatterWriter accepts one byte per call to verify that the shared +// GhosttyWriter bridge retries short writes until it consumes each callback. type shortFormatterWriter struct { bytes.Buffer } @@ -265,15 +265,41 @@ func TestFormatterWriteToShortWrite(t *testing.T) { var output shortFormatterWriter n, err := f.WriteTo(&output) - if n != 1 { - t.Fatalf("expected one accepted byte, got %d", n) + if err != nil { + t.Fatal(err) + } + if n != 4 { + t.Fatalf("expected four accepted bytes, got %d", n) + } + if output.String() != "abcd" { + t.Fatalf("expected accepted output %q, got %q", "abcd", output.String()) + } +} + +func TestFormatterWriteToError(t *testing.T) { + term, err := NewTerminal(WithSize(80, 24)) + if err != nil { + t.Fatal(err) } - if !errors.Is(err, io.ErrShortWrite) { - t.Fatalf("expected io.ErrShortWrite, got %v", err) + defer term.Close() + term.VTWrite([]byte("formatter writer error")) + + f, err := NewFormatter(term, WithFormatterFormat(FormatterFormatPlain)) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + wantErr := errors.New("formatter sink failed") + w := &failingWriter{limit: 3, err: wantErr} + written, err := f.WriteTo(w) + if written != 3 { + t.Fatalf("expected three accepted bytes, got %d", written) } - if output.String() != "a" { - t.Fatalf("expected accepted output %q, got %q", "a", output.String()) + if !errors.Is(err, wantErr) { + t.Fatalf("expected original writer error, got %v", err) } + assertResultError(t, err, ResultIOError) } func TestFormatterFormatBuf(t *testing.T) {