Skip to content

Commit a42374d

Browse files
committed
term: replace lipgloss/v2/compat with lazy OSC 11 background detection
1 parent 57e784f commit a42374d

11 files changed

Lines changed: 116 additions & 23 deletions

File tree

cmd/hot/command/command_cat.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"strings"
1212

1313
"charm.land/glamour/v2"
14-
"charm.land/lipgloss/v2"
1514
"github.com/alecthomas/chroma/v2"
1615
"github.com/alecthomas/chroma/v2/formatters"
1716
"github.com/alecthomas/chroma/v2/lexers"
@@ -174,7 +173,7 @@ func (c *Cat) markdownOut(w io.Writer, input io.Reader) error {
174173
}
175174
// Detect background color to pick appropriate style
176175
style := "light"
177-
if lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
176+
if term.HasDarkBackground() {
178177
style = "dark"
179178
}
180179
r, err := glamour.NewTermRenderer(
@@ -214,7 +213,7 @@ func (c *Cat) syntaxHighlightOut(w io.Writer, input io.Reader, termLevel term.Le
214213

215214
// Detect background color to pick appropriate style
216215
styleName := "github"
217-
if lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
216+
if term.HasDarkBackground() {
218217
styleName = "dracula"
219218
}
220219

cmd/hot/command/command_scan_refs.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"charm.land/bubbles/v2/table"
1313
tea "charm.land/bubbletea/v2"
1414
"charm.land/lipgloss/v2"
15-
"charm.land/lipgloss/v2/compat"
1615
"github.com/antgroup/hugescm/cmd/hot/pkg/refs"
1716
"github.com/antgroup/hugescm/cmd/hot/pkg/tr"
1817
"github.com/antgroup/hugescm/modules/fnmatch"
@@ -25,8 +24,8 @@ func newModel(references *refs.References) model {
2524
p := paginator.New()
2625
p.Type = paginator.Dots
2726
p.PerPage = 20
28-
p.ActiveDot = lipgloss.NewStyle().Foreground(compat.AdaptiveColor{Light: lipgloss.Color("235"), Dark: lipgloss.Color("252")}).Render("•")
29-
p.InactiveDot = lipgloss.NewStyle().Foreground(compat.AdaptiveColor{Light: lipgloss.Color("250"), Dark: lipgloss.Color("238")}).Render("•")
27+
p.ActiveDot = lipgloss.NewStyle().Foreground(term.AdaptiveColor{Light: lipgloss.Color("235"), Dark: lipgloss.Color("252")}).Render("•")
28+
p.InactiveDot = lipgloss.NewStyle().Foreground(term.AdaptiveColor{Light: lipgloss.Color("250"), Dark: lipgloss.Color("238")}).Render("•")
3029
p.SetTotalPages(len(references.Items))
3130

3231
return model{

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG
3838
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
3939
github.com/charmbracelet/ultraviolet v0.0.0-20260601155805-6cf7526a1b3f h1:vKsPSlO4g4jKfJ9enESgNZ45BkbHngTIq3UxNOzic74=
4040
github.com/charmbracelet/ultraviolet v0.0.0-20260601155805-6cf7526a1b3f/go.mod h1:hFpumms29Smx3LStRfku8vcCTBe1Kq8aCXtHUJa3mjY=
41+
github.com/charmbracelet/ultraviolet v0.0.0-20260608091853-35bcb7319efa h1:rRT2qwk9xbontVloCXEUIsl1ePz0XFcIWkGi2bvmSTY=
42+
github.com/charmbracelet/ultraviolet v0.0.0-20260608091853-35bcb7319efa/go.mod h1:hFpumms29Smx3LStRfku8vcCTBe1Kq8aCXtHUJa3mjY=
4143
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
4244
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
4345
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
@@ -46,12 +48,16 @@ github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9
4648
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
4749
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260607010151-cd19a2bba55f h1:k4kKyMHzsSNnS74mRTuU+QehvrJnWVrFaSovHh8u9vY=
4850
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260607010151-cd19a2bba55f/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY=
51+
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260608090822-c3ad58c6c9e5 h1:Xl3+pllTbd0iZWeTQixTHClROwU/Gs79ANuOGILkA5g=
52+
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260608090822-c3ad58c6c9e5/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY=
4953
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
5054
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
5155
github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
5256
github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
5357
github.com/charmbracelet/x/exp/slice v0.0.0-20260607010151-cd19a2bba55f h1:qfvRQEbGZ0bNU/IlPz4SGpyVfx9hSSPB2h8kGnE6Jpc=
5458
github.com/charmbracelet/x/exp/slice v0.0.0-20260607010151-cd19a2bba55f/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA=
59+
github.com/charmbracelet/x/exp/slice v0.0.0-20260608090822-c3ad58c6c9e5 h1:K+bEhQCdvynpwszgcTF3MqvP/ouj7ocrZi+24viw0zc=
60+
github.com/charmbracelet/x/exp/slice v0.0.0-20260608090822-c3ad58c6c9e5/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA=
5561
github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA=
5662
github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
5763
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
@@ -142,17 +148,27 @@ github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
142148
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
143149
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
144150
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
151+
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
152+
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
145153
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
146154
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
147155
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
148156
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
149157
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
150158
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
159+
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
160+
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
151161
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
152162
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
163+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
164+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
153165
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
154166
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
167+
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
168+
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
155169
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
156170
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
171+
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
172+
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
157173
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
158174
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

modules/patchview/styles.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ package patchview
33
import (
44
"fmt"
55
"image/color"
6-
"os"
76

87
"charm.land/lipgloss/v2"
8+
9+
"github.com/antgroup/hugescm/modules/term"
910
"github.com/charmbracelet/x/exp/charmtone"
1011
)
1112

@@ -62,7 +63,7 @@ type Style struct {
6263

6364
// hasDarkBackground detects terminal background color.
6465
func hasDarkBackground() bool {
65-
return lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
66+
return term.HasDarkBackground()
6667
}
6768

6869
// DefaultStyle returns the default style with auto-detected theme.

modules/term/terminal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,16 @@ func IsNativeTerminal(fd uintptr) bool {
148148
func GetSize(fd int) (width, height int, err error) {
149149
return term.GetSize(fd)
150150
}
151+
152+
// MakeRaw puts the terminal connected to the given file descriptor into raw
153+
// mode and returns the previous state of the terminal so that it can be
154+
// restored.
155+
func MakeRaw(fd int) (*term.State, error) {
156+
return term.MakeRaw(fd)
157+
}
158+
159+
// Restore restores the terminal connected to the given file descriptor to a
160+
// previous state.
161+
func Restore(fd int, oldState *term.State) error {
162+
return term.Restore(fd, oldState)
163+
}

modules/term/theme.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Ant Group. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package term
5+
6+
import (
7+
"image/color"
8+
"os"
9+
"sync"
10+
11+
"charm.land/lipgloss/v2"
12+
)
13+
14+
// supportsOSCQuery returns true if the current terminal is likely to
15+
// support OSC 11 background color queries without leaking escape responses.
16+
//
17+
// The heuristic: if detectColorLevel (environment-based, ignoring FORCE_COLOR)
18+
// reports Level16M, the terminal is modern enough to handle OSC 11.
19+
// Additionally, VTE_VERSION is checked as a supplementary signal for terminals
20+
// that may not advertise truecolor via COLORTERM/TERM.
21+
func supportsOSCQuery() bool {
22+
if detectColorLevel() >= Level16M {
23+
return true
24+
}
25+
// VTE-based terminals (GNOME Terminal, Tilix, Xfce Terminal, etc.)
26+
if _, ok := os.LookupEnv("VTE_VERSION"); ok {
27+
return true
28+
}
29+
return false
30+
}
31+
32+
var (
33+
detectOnce sync.Once
34+
hasDarkBg = true // default to dark when detection is skipped or fails
35+
)
36+
37+
func detectBackground() {
38+
if !supportsOSCQuery() {
39+
return // keep default (dark)
40+
}
41+
if !IsTerminal(os.Stdin.Fd()) || !IsTerminal(os.Stdout.Fd()) {
42+
return // not a terminal, keep default
43+
}
44+
hasDarkBg = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
45+
}
46+
47+
// HasDarkBackground reports whether the terminal has a dark background.
48+
// Detection is lazy (executed at most once) and only queries the terminal
49+
// if it is known to support OSC 11 reliably. Otherwise it defaults to true.
50+
func HasDarkBackground() bool {
51+
detectOnce.Do(detectBackground)
52+
return hasDarkBg
53+
}
54+
55+
// AdaptiveColor holds a pair of colors for light and dark backgrounds.
56+
// It implements [image/color.Color] and selects the appropriate variant
57+
// based on the terminal's background at runtime.
58+
type AdaptiveColor struct {
59+
Light color.Color
60+
Dark color.Color
61+
}
62+
63+
// RGBA implements [image/color.Color].
64+
func (c AdaptiveColor) RGBA() (uint32, uint32, uint32, uint32) {
65+
if HasDarkBackground() {
66+
return c.Dark.RGBA()
67+
}
68+
return c.Light.RGBA()
69+
}

modules/tui/color.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ package tui
22

33
import (
44
"maps"
5-
"os"
65

7-
"charm.land/lipgloss/v2"
86
"github.com/antgroup/hugescm/modules/diferenco"
97
"github.com/antgroup/hugescm/modules/diferenco/color"
108
"github.com/antgroup/hugescm/modules/term"
@@ -135,7 +133,7 @@ func EncoderOptions(level term.Level) []diferenco.EncoderOption {
135133
case term.Level16M:
136134
// Use truecolor with current theme based on background
137135
theme := currentTheme.Dark
138-
if !lipgloss.HasDarkBackground(os.Stdin, os.Stdout) {
136+
if !term.HasDarkBackground() {
139137
theme = currentTheme.Light
140138
}
141139
maps.Copy(cc, theme)

modules/tui/confirm.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import (
55

66
"charm.land/huh/v2"
77
"charm.land/lipgloss/v2"
8-
"charm.land/lipgloss/v2/compat"
8+
"github.com/antgroup/hugescm/modules/term"
99
)
1010

1111
// Color definitions for huh theme
1212
var (
13-
normalFg = compat.AdaptiveColor{Light: lipgloss.Color("235"), Dark: lipgloss.Color("252")}
13+
normalFg = term.AdaptiveColor{Light: lipgloss.Color("235"), Dark: lipgloss.Color("252")}
1414
fuchsia = lipgloss.Color("#F780E2")
15-
green = compat.AdaptiveColor{Light: lipgloss.Color("#02BA84"), Dark: lipgloss.Color("#02BF87")}
15+
green = term.AdaptiveColor{Light: lipgloss.Color("#02BA84"), Dark: lipgloss.Color("#02BF87")}
1616
)
1717

1818
// baseTheme returns a custom theme for huh widgets.
@@ -24,7 +24,7 @@ func baseTheme() huh.Theme {
2424
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red)
2525
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red)
2626
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(green)
27-
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(compat.AdaptiveColor{Light: lipgloss.Color("248"), Dark: lipgloss.Color("238")})
27+
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(term.AdaptiveColor{Light: lipgloss.Color("248"), Dark: lipgloss.Color("238")})
2828
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(fuchsia)
2929
t.Focused.TextInput.Text = t.Focused.TextInput.Text.Foreground(normalFg)
3030

modules/tui/input.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import (
1111
"unicode/utf8"
1212

1313
"charm.land/lipgloss/v2"
14-
"charm.land/lipgloss/v2/compat"
15-
"golang.org/x/term"
14+
"github.com/antgroup/hugescm/modules/term"
1615
)
1716

1817
// ErrInterrupted is returned when user presses Ctrl+C or Ctrl+D.
@@ -22,8 +21,8 @@ const maxAttempts = 3
2221

2322
// Color definitions (package-level to avoid recreation)
2423
var (
25-
blue = compat.AdaptiveColor{Light: lipgloss.Color("#ace0f9"), Dark: lipgloss.Color("#ace0f9")}
26-
red = compat.AdaptiveColor{Light: lipgloss.Color("#FF4672"), Dark: lipgloss.Color("#ED567A")}
24+
blue = term.AdaptiveColor{Light: lipgloss.Color("#ace0f9"), Dark: lipgloss.Color("#ace0f9")}
25+
red = term.AdaptiveColor{Light: lipgloss.Color("#FF4672"), Dark: lipgloss.Color("#ED567A")}
2726
)
2827

2928
var (

pkg/zeta/repository.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"time"
1717

1818
"charm.land/lipgloss/v2"
19-
"charm.land/lipgloss/v2/compat"
2019
"github.com/antgroup/hugescm/modules/plumbing"
2120
"github.com/antgroup/hugescm/modules/strengthen"
2221
"github.com/antgroup/hugescm/modules/term"
@@ -49,7 +48,7 @@ var (
4948
}
5049

5150
// fsNameHighlight is the style for highlighting filesystem names in warnings.
52-
fsNameHighlight = lipgloss.NewStyle().Foreground(compat.AdaptiveColor{
51+
fsNameHighlight = lipgloss.NewStyle().Foreground(term.AdaptiveColor{
5352
Light: lipgloss.Color("#D70000"), Dark: lipgloss.Color("#FF6B6B"),
5453
}).Bold(true)
5554
)

0 commit comments

Comments
 (0)