Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions terminal_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"hash/maphash"
"io"
"os"
"strings"

"github.com/charmbracelet/colorprofile"
Expand Down Expand Up @@ -1584,6 +1585,9 @@ func xtermCaps(termtype string) (v capabilities) {
v &^= capHPA
v &^= capCHT
v &^= capREP
if isJediTerm() {
v &^= capCBT
}
}
case "alacritty":
v = allCaps
Expand All @@ -1599,3 +1603,28 @@ func xtermCaps(termtype string) (v capabilities) {

return v
}

// isJediTerm returns true when running inside JetBrains' JediTerm terminal
// emulator (GoLand, IntelliJ, etc.). JediTerm mishandles CBT escape
// sequences that ultraviolet emits under normal TERM values.
//
// Detection layers (checked in order):
// 1. TERMINAL_EMULATOR == "JetBrains-JediTerm" — all platforms, Terminal tool window
// 2. __CFBundleIdentifier starts with "com.jetbrains." — macOS, "Emulate terminal" Run/Debug
// 3. XPC_SERVICE_NAME contains "com.jetbrains." — macOS, "Emulate terminal" Run/Debug
// 4. TOOLBOX_VERSION is non-empty — all platforms, all contexts (JetBrains Toolbox installs only)
func isJediTerm() bool {
if os.Getenv("TERMINAL_EMULATOR") == "JetBrains-JediTerm" {
return true
}
if strings.HasPrefix(os.Getenv("__CFBundleIdentifier"), "com.jetbrains.") {
return true
}
if strings.Contains(os.Getenv("XPC_SERVICE_NAME"), "com.jetbrains.") {
return true
}
if os.Getenv("TOOLBOX_VERSION") != "" {
return true
}
return false
}
86 changes: 86 additions & 0 deletions terminal_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,92 @@ func TestRendererEnterExitAltScreen(t *testing.T) {
}
}

func TestIsJediTerm(t *testing.T) {
tests := []struct {
name string
env map[string]string
want bool
}{
{
name: "detected via TERMINAL_EMULATOR",
env: map[string]string{"TERMINAL_EMULATOR": "JetBrains-JediTerm"},
want: true,
},
{
name: "detected via __CFBundleIdentifier",
env: map[string]string{"__CFBundleIdentifier": "com.jetbrains.goland"},
want: true,
},
{
name: "detected via XPC_SERVICE_NAME",
env: map[string]string{"XPC_SERVICE_NAME": "application.com.jetbrains.goland.12345"},
want: true,
},
{
name: "not detected when env is empty",
env: map[string]string{},
want: false,
},
{
name: "detected via TOOLBOX_VERSION",
env: map[string]string{"TOOLBOX_VERSION": "2.6.2.40984"},
want: true,
},
{
name: "not detected for other terminals",
env: map[string]string{"TERMINAL_EMULATOR": "iTerm2"},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear relevant env vars (t.Setenv handles save/restore)
keys := []string{"TERMINAL_EMULATOR", "__CFBundleIdentifier", "XPC_SERVICE_NAME", "TOOLBOX_VERSION"}
for _, k := range keys {
t.Setenv(k, "")
}
// Set test env vars
for k, v := range tt.env {
t.Setenv(k, v)
}

if got := isJediTerm(); got != tt.want {
t.Errorf("isJediTerm() = %v, want %v", got, tt.want)
}
})
}
}

func TestXtermCapsJediTermDisablesCBT(t *testing.T) {
// Save and clear env
keys := []string{"TERMINAL_EMULATOR", "__CFBundleIdentifier", "XPC_SERVICE_NAME", "TOOLBOX_VERSION"}
for _, k := range keys {
t.Setenv(k, "")
}

// Without JediTerm, xterm-256color should have CBT
caps := xtermCaps("xterm-256color")
if !caps.Contains(capCBT) {
t.Error("xterm-256color should have capCBT when not in JediTerm")
}

// With JediTerm, xterm-256color should NOT have CBT
t.Setenv("TERMINAL_EMULATOR", "JetBrains-JediTerm")
caps = xtermCaps("xterm-256color")
if caps.Contains(capCBT) {
t.Error("xterm-256color should NOT have capCBT when in JediTerm")
}

// Other caps should still be present (VPA, CHA, etc.)
if !caps.Contains(capVPA) {
t.Error("xterm-256color in JediTerm should still have capVPA")
}
if !caps.Contains(capCHA) {
t.Error("xterm-256color in JediTerm should still have capCHA")
}
}

// Helper type for testing logger
type testLogger struct {
buf *bytes.Buffer
Expand Down
Loading