diff --git a/cursed_renderer.go b/cursed_renderer.go index c954873033..475f677385 100644 --- a/cursed_renderer.go +++ b/cursed_renderer.go @@ -33,6 +33,7 @@ type cursedRenderer struct { mapnl bool syncdUpdates bool // whether to use synchronized output mode for updates starting bool // indicates whether the renderer is starting after being stopped + disabledCaps []uv.Capability // capabilities to disable on the terminal renderer } var _ renderer = &cursedRenderer{} @@ -597,6 +598,7 @@ func reset(s *cursedRenderer) { scr.SetBackspace(s.backspace) scr.SetMapNewline(s.mapnl) scr.SetScrollOptim(runtime.GOOS != "windows") // disable scroll optimization on Windows due to bugs in some terminals + scr.DisableCaps(s.disabledCaps...) s.scr = scr } @@ -828,3 +830,12 @@ func viewEquals(a, b *View) bool { return true } + +// setDisabledCaps stores the capabilities to disable and applies them to the +// current terminal renderer. They will also be applied on subsequent reset() calls. +func (s *cursedRenderer) setDisabledCaps(caps []uv.Capability) { + s.mu.Lock() + s.disabledCaps = caps + s.scr.DisableCaps(caps...) + s.mu.Unlock() +} diff --git a/options.go b/options.go index dfd8cda4f8..ff40b3b2e4 100644 --- a/options.go +++ b/options.go @@ -6,6 +6,7 @@ import ( "sync/atomic" "github.com/charmbracelet/colorprofile" + uv "github.com/charmbracelet/ultraviolet" ) // ProgramOption is used to set options when initializing a Program. Program can @@ -166,3 +167,15 @@ func WithWindowSize(width, height int) ProgramOption { p.height = height } } + +// WithoutCaps disables specific ultraviolet terminal capabilities. Use this +// when a terminal emulator is known to mishandle certain escape sequences. +// For example, JediTerm (GoLand/IntelliJ) does not correctly implement CBT +// (Cursor Backward Tab), so you can disable it: +// +// p := tea.NewProgram(model, tea.WithoutCaps(uv.CapCBT)) +func WithoutCaps(caps ...uv.Capability) ProgramOption { + return func(p *Program) { + p.disabledCaps = caps + } +} diff --git a/options_test.go b/options_test.go index 170de1ba2e..393d681838 100644 --- a/options_test.go +++ b/options_test.go @@ -6,6 +6,8 @@ import ( "os" "sync/atomic" "testing" + + uv "github.com/charmbracelet/ultraviolet" ) func TestOptions(t *testing.T) { @@ -94,4 +96,17 @@ func TestOptions(t *testing.T) { }) }) }) + + t.Run("without caps", func(t *testing.T) { + p := NewProgram(nil, WithoutCaps(uv.CapCBT, uv.CapREP)) + if len(p.disabledCaps) != 2 { + t.Fatalf("expected 2 disabled caps, got %d", len(p.disabledCaps)) + } + if p.disabledCaps[0] != uv.CapCBT { + t.Errorf("expected first cap to be CapCBT, got %d", p.disabledCaps[0]) + } + if p.disabledCaps[1] != uv.CapREP { + t.Errorf("expected second cap to be CapREP, got %d", p.disabledCaps[1]) + } + }) } diff --git a/tea.go b/tea.go index e1dfc97d9f..f03c36c01f 100644 --- a/tea.go +++ b/tea.go @@ -529,6 +529,9 @@ type Program struct { // whether to use backspace to optimize cursor movements useBackspace bool + // disabledCaps is a list of ultraviolet terminal capabilities to disable. + disabledCaps []uv.Capability + mu sync.Mutex } @@ -1045,6 +1048,7 @@ func (p *Program) Run() (returnModel Model, returnErr error) { p.width, p.height, ) + r.setDisabledCaps(p.disabledCaps) r.setLogger(p.logger) // XXX: This breaks many things especially when we want the output // to be compatible with terminals that are not necessary a TTY.