Skip to content

Commit 5a45687

Browse files
committed
feat: add WithoutCaps() to disable specific terminal caps
Add tea.WithoutCaps() ProgramOption to selectively disable ultraviolet terminal capabilities. This enables workarounds for terminal emulators that mishandle specific escape sequences without disabling all optimizations.
1 parent b427e77 commit 5a45687

4 files changed

Lines changed: 45 additions & 2 deletions

File tree

cursed_renderer.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ type cursedRenderer struct {
3131
hardTabs bool // whether to use hard tabs to optimize cursor movements
3232
backspace bool // whether to use backspace to optimize cursor movements
3333
mapnl bool
34-
syncdUpdates bool // whether to use synchronized output mode for updates
35-
starting bool // indicates whether the renderer is starting after being stopped
34+
syncdUpdates bool // whether to use synchronized output mode for updates
35+
starting bool // indicates whether the renderer is starting after being stopped
36+
disabledCaps []uv.Capability // capabilities to disable on the terminal renderer
3637
}
3738

3839
var _ renderer = &cursedRenderer{}
@@ -597,6 +598,7 @@ func reset(s *cursedRenderer) {
597598
scr.SetBackspace(s.backspace)
598599
scr.SetMapNewline(s.mapnl)
599600
scr.SetScrollOptim(runtime.GOOS != "windows") // disable scroll optimization on Windows due to bugs in some terminals
601+
scr.DisableCaps(s.disabledCaps...)
600602
s.scr = scr
601603
}
602604

@@ -828,3 +830,12 @@ func viewEquals(a, b *View) bool {
828830

829831
return true
830832
}
833+
834+
// setDisabledCaps stores the capabilities to disable and applies them to the
835+
// current terminal renderer. They will also be applied on subsequent reset() calls.
836+
func (s *cursedRenderer) setDisabledCaps(caps []uv.Capability) {
837+
s.mu.Lock()
838+
s.disabledCaps = caps
839+
s.scr.DisableCaps(caps...)
840+
s.mu.Unlock()
841+
}

options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"sync/atomic"
77

88
"github.com/charmbracelet/colorprofile"
9+
uv "github.com/charmbracelet/ultraviolet"
910
)
1011

1112
// ProgramOption is used to set options when initializing a Program. Program can
@@ -166,3 +167,15 @@ func WithWindowSize(width, height int) ProgramOption {
166167
p.height = height
167168
}
168169
}
170+
171+
// WithoutCaps disables specific ultraviolet terminal capabilities. Use this
172+
// when a terminal emulator is known to mishandle certain escape sequences.
173+
// For example, JediTerm (GoLand/IntelliJ) does not correctly implement CBT
174+
// (Cursor Backward Tab), so you can disable it:
175+
//
176+
// p := tea.NewProgram(model, tea.WithoutCaps(uv.CapCBT))
177+
func WithoutCaps(caps ...uv.Capability) ProgramOption {
178+
return func(p *Program) {
179+
p.disabledCaps = caps
180+
}
181+
}

options_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"os"
77
"sync/atomic"
88
"testing"
9+
10+
uv "github.com/charmbracelet/ultraviolet"
911
)
1012

1113
func TestOptions(t *testing.T) {
@@ -94,4 +96,17 @@ func TestOptions(t *testing.T) {
9496
})
9597
})
9698
})
99+
100+
t.Run("without caps", func(t *testing.T) {
101+
p := NewProgram(nil, WithoutCaps(uv.CapCBT, uv.CapREP))
102+
if len(p.disabledCaps) != 2 {
103+
t.Fatalf("expected 2 disabled caps, got %d", len(p.disabledCaps))
104+
}
105+
if p.disabledCaps[0] != uv.CapCBT {
106+
t.Errorf("expected first cap to be CapCBT, got %d", p.disabledCaps[0])
107+
}
108+
if p.disabledCaps[1] != uv.CapREP {
109+
t.Errorf("expected second cap to be CapREP, got %d", p.disabledCaps[1])
110+
}
111+
})
97112
}

tea.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,9 @@ type Program struct {
529529
// whether to use backspace to optimize cursor movements
530530
useBackspace bool
531531

532+
// disabledCaps is a list of ultraviolet terminal capabilities to disable.
533+
disabledCaps []uv.Capability
534+
532535
mu sync.Mutex
533536
}
534537

@@ -1045,6 +1048,7 @@ func (p *Program) Run() (returnModel Model, returnErr error) {
10451048
p.width,
10461049
p.height,
10471050
)
1051+
r.setDisabledCaps(p.disabledCaps)
10481052
r.setLogger(p.logger)
10491053
// XXX: This breaks many things especially when we want the output
10501054
// to be compatible with terminals that are not necessary a TTY.

0 commit comments

Comments
 (0)