From 1f323a0cf2b8123238c571a2d488c357bf805de0 Mon Sep 17 00:00:00 2001 From: Florian Rey Date: Wed, 29 May 2024 12:15:53 +0200 Subject: [PATCH] feat: synchronized output --- nil_renderer.go | 1 + renderer.go | 3 +++ standard_renderer.go | 21 +++++++++++++++++++++ tea.go | 16 ++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/nil_renderer.go b/nil_renderer.go index f4a83b6bc4..268cb90064 100644 --- a/nil_renderer.go +++ b/nil_renderer.go @@ -23,3 +23,4 @@ func (n nilRenderer) enableMouseSGRMode() {} func (n nilRenderer) disableMouseSGRMode() {} func (n nilRenderer) bracketedPasteActive() bool { return false } func (n nilRenderer) setWindowTitle(_ string) {} +func (n nilRenderer) enableSynchronizedOutput() {} diff --git a/renderer.go b/renderer.go index de3936e73b..04b14b9071 100644 --- a/renderer.go +++ b/renderer.go @@ -70,6 +70,9 @@ type renderer interface { // setWindowTitle sets the terminal window title. setWindowTitle(string) + + // enableSynchronizedOutput enables synchronized output. + enableSynchronizedOutput() } // repaintMsg forces a full repaint. diff --git a/standard_renderer.go b/standard_renderer.go index 1b7f4b9bdc..0e8e45c51a 100644 --- a/standard_renderer.go +++ b/standard_renderer.go @@ -47,6 +47,9 @@ type standardRenderer struct { // whether or not we're currently using bracketed paste bpActive bool + // whether or not we're currently using synchronized output + soActive bool + // renderer dimensions; usually the size of the window width int height int @@ -274,7 +277,18 @@ func (r *standardRenderer) flush() { buf.WriteString(ansi.CursorLeft(r.width)) } + // Enable synchronized output + if r.soActive { + _, _ = io.WriteString(r.out, ansi.EnableSyncdOutput) + } + _, _ = r.out.Write(buf.Bytes()) + + // Disable synchronized output + if r.soActive { + _, _ = io.WriteString(r.out, ansi.DisableSyncdOutput) + } + r.lastRender = r.buf.String() r.buf.Reset() } @@ -459,6 +473,13 @@ func (r *standardRenderer) setWindowTitle(title string) { r.execute(ansi.SetWindowTitle(title)) } +func (r *standardRenderer) enableSynchronizedOutput() { + r.mtx.Lock() + defer r.mtx.Unlock() + + r.bpActive = true +} + // setIgnoredLines specifies lines not to be touched by the standard Bubble Tea // renderer. func (r *standardRenderer) setIgnoredLines(from int, to int) { diff --git a/tea.go b/tea.go index 0915d1adbd..839f2af469 100644 --- a/tea.go +++ b/tea.go @@ -13,6 +13,8 @@ import ( "context" "errors" "fmt" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/input" "io" "os" "os/signal" @@ -20,6 +22,7 @@ import ( "sync" "sync/atomic" "syscall" + "time" "github.com/charmbracelet/x/term" "github.com/muesli/cancelreader" @@ -489,6 +492,19 @@ func (p *Program) Run() (Model, error) { return p.initialModel, err } + // See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036 + _ = term.QueryTerminal(p.input, p.output, time.Millisecond*100, func(events []input.Event) bool { + for _, e := range events { + switch event := e.(type) { + case input.ReportModeEvent: + if event.Mode == 2026 && (event.Value == 2 || event.Value == 4) { + p.renderer.enableSynchronizedOutput() + } + } + } + return true + }, ansi.RequestSyncdOutput) + // Honor program startup options. if p.startupOptions&withAltScreen != 0 { p.renderer.enterAltScreen()