gitstack

grindlemire/go-tui code browser

4.1 KB Go 172 lines 2026-05-29 · 781e1cc raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package tui

import (
	"os"
	"strings"
)

// DetectCapabilities determines terminal capabilities from environment variables
// and returns a Capabilities struct with detected settings.
// Returns conservative defaults when detection fails.
func DetectCapabilities() Capabilities {
	caps := Capabilities{
		Colors:     Color16, // Safe default for most terminals
		Unicode:    true,    // Assume modern terminal
		TrueColor:  false,
		AltScreen:  true,
		Hyperlinks: true, // Most modern terminals support OSC 8; disabled for dumb below
	}

	// First, check for explicit true color indicators that override everything else.
	// These environment variables definitively indicate true color support.

	// Check COLORTERM for explicit true color support
	colorterm := strings.ToLower(os.Getenv("COLORTERM"))
	if colorterm == "truecolor" || colorterm == "24bit" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// Check terminal emulator-specific environment variables
	// These terminals are known to support true color

	// Windows Terminal
	if os.Getenv("WT_SESSION") != "" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// iTerm2
	if os.Getenv("ITERM_SESSION_ID") != "" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// Kitty
	if os.Getenv("KITTY_WINDOW_ID") != "" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// Konsole
	if os.Getenv("KONSOLE_VERSION") != "" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// VTE-based terminals (GNOME Terminal, Tilix, etc.)
	if os.Getenv("VTE_VERSION") != "" {
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	// Dumb terminals cannot handle escape sequences (including OSC 8 hyperlinks)
	// regardless of any color indicator, so check TERM=dumb before the TrueColor
	// early-return below.
	term := strings.ToLower(os.Getenv("TERM"))
	if term == "dumb" {
		caps.Colors = ColorNone
		caps.Unicode = false
		caps.AltScreen = false
		caps.Hyperlinks = false
		return caps
	}

	// If we already detected true color via explicit indicators, we're done
	if caps.TrueColor {
		return caps
	}

	// Now check TERM for terminals without explicit indicators
	switch {
	case strings.Contains(term, "256color"):
		caps.Colors = Color256
	case strings.Contains(term, "truecolor"):
		caps.Colors = ColorTrue
		caps.TrueColor = true
	}

	return caps
}

// SupportsColor returns true if the terminal supports the given color type.
func (c Capabilities) SupportsColor(color Color) bool {
	switch color.Type() {
	case ColorDefault:
		return true
	case ColorANSI:
		// ANSI colors need at least Color16 support
		return c.Colors >= Color16
	case ColorRGB:
		return c.TrueColor
	}
	return false
}

// EffectiveColor returns the color to use given the terminal's capabilities.
// If the terminal supports the color type, returns the original color.
// If not, returns an appropriate fallback (RGB colors are approximated to ANSI).
func (c Capabilities) EffectiveColor(color Color) Color {
	if c.SupportsColor(color) {
		return color
	}

	switch color.Type() {
	case ColorRGB:
		// Fall back to ANSI approximation
		if c.Colors >= Color256 {
			return color.ToANSI()
		}
		// For 16-color terminals, we could add further approximation
		// For now, return the ANSI approximation which uses the 256 palette
		if c.Colors >= Color16 {
			return color.ToANSI()
		}
		// No color support - return default
		return DefaultColor()
	case ColorANSI:
		// If ANSI not supported, return default
		if c.Colors < Color16 {
			return DefaultColor()
		}
		return color
	default:
		return color
	}
}

// String returns a human-readable description of the capabilities.
func (c Capabilities) String() string {
	var parts []string

	switch c.Colors {
	case ColorNone:
		parts = append(parts, "no-color")
	case Color16:
		parts = append(parts, "16-color")
	case Color256:
		parts = append(parts, "256-color")
	case ColorTrue:
		parts = append(parts, "true-color")
	}

	if c.Unicode {
		parts = append(parts, "unicode")
	} else {
		parts = append(parts, "ascii")
	}

	if c.AltScreen {
		parts = append(parts, "altscreen")
	}

	if c.KittyKeyboard {
		parts = append(parts, "kitty-keyboard")
	}

	if c.Hyperlinks {
		parts = append(parts, "hyperlinks")
	}

	return strings.Join(parts, ", ")
}