From 71c737fbf1846a14066e2c657501caf111eb5a7c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 31 Oct 2024 13:42:35 -0400 Subject: [PATCH 1/4] feat: windows has colors support We detect windows color support based on build and os version numbers. And/or using ANSICON. --- env.go | 14 +++++++++----- env_other.go | 8 ++++++++ env_test.go | 14 +++++++++++--- env_windows.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 env_other.go create mode 100644 env_windows.go diff --git a/env.go b/env.go index f486cab..fc38790 100644 --- a/env.go +++ b/env.go @@ -2,6 +2,7 @@ package colorprofile import ( "io" + "runtime" "strconv" "strings" @@ -116,7 +117,8 @@ func colorTerm(env map[string]string) bool { // envColorProfile returns infers the color profile from the environment. func envColorProfile(env map[string]string) (p Profile) { - term := strings.ToLower(env["TERM"]) + term, ok := env["TERM"] + term = strings.ToLower(term) switch term { case "", "dumb": p = NoTTY @@ -131,10 +133,12 @@ func envColorProfile(env map[string]string) (p Profile) { p = Ascii // Default to Ascii } - if len(env["WT_SESSION"]) > 0 { - // Windows Terminal supports TrueColor - p = TrueColor - return + if !ok && runtime.GOOS == "windows" { + // Use Windows API to detect color profile + wcp, _ := windowsColorProfile(env) + if wcp < p { + p = wcp + } } if isCloudShell, _ := strconv.ParseBool(env["GOOGLE_CLOUD_SHELL"]); isCloudShell { diff --git a/env_other.go b/env_other.go new file mode 100644 index 0000000..080994b --- /dev/null +++ b/env_other.go @@ -0,0 +1,8 @@ +//go:build !windows +// +build !windows + +package colorprofile + +func windowsColorProfile(map[string]string) (Profile, bool) { + return 0, false +} diff --git a/env_test.go b/env_test.go index 8a2ce5f..333dd05 100644 --- a/env_test.go +++ b/env_test.go @@ -1,6 +1,7 @@ package colorprofile import ( + "runtime" "testing" ) @@ -10,9 +11,16 @@ var cases = []struct { expected Profile }{ { - name: "empty", - environ: []string{}, - expected: NoTTY, + name: "empty", + environ: []string{}, + expected: func() Profile { + if runtime.GOOS == "windows" { + p, _ := windowsColorProfile(map[string]string{}) + return p + } else { + return NoTTY + } + }(), }, { name: "no tty", diff --git a/env_windows.go b/env_windows.go new file mode 100644 index 0000000..3b9c28f --- /dev/null +++ b/env_windows.go @@ -0,0 +1,45 @@ +//go:build windows +// +build windows + +package colorprofile + +import ( + "strconv" + + "golang.org/x/sys/windows" +) + +func windowsColorProfile(env map[string]string) (Profile, bool) { + if env["ConEmuANSI"] == "ON" { + return TrueColor, true + } + + if len(env["WT_SESSION"]) > 0 { + // Windows Terminal supports TrueColor + return TrueColor, true + } + + major, _, build := windows.RtlGetNtVersionNumbers() + if build < 10586 || major < 10 { + // No ANSI support before WindowsNT 10 build 10586 + if len(env["ANSICON"]) > 0 { + ansiconVer := env["ANSICON_VER"] + cv, err := strconv.Atoi(ansiconVer) + if err != nil || cv < 181 { + // No 8 bit color support before ANSICON 1.81 + return ANSI, true + } + + return ANSI256, true + } + + return NoTTY, true + } + + if build < 14931 { + // No true color support before build 14931 + return ANSI256, true + } + + return TrueColor, true +} From 0a6c85d80fbd48e9b964222adf16f8495eb9a5c2 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 31 Oct 2024 13:43:07 -0400 Subject: [PATCH 2/4] chore: go mod tidy --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- go.mod | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 8040ea0..49e44bc 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -7,7 +7,7 @@ replace github.com/charmbracelet/colorprofile => ../ require github.com/charmbracelet/colorprofile v0.0.0-20240913192632-4a4ff4a5f48a require ( - github.com/charmbracelet/x/ansi v0.3.2 // indirect + github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/charmbracelet/x/term v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/examples/go.sum b/examples/go.sum index a6b8b03..325c3d4 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,5 +1,5 @@ -github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= -github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= +github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= diff --git a/go.mod b/go.mod index cbce9ad..e65907b 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,7 @@ require ( github.com/charmbracelet/x/term v0.2.0 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e + golang.org/x/sys v0.24.0 ) -require ( - github.com/rivo/uniseg v0.4.7 // indirect - golang.org/x/sys v0.24.0 // indirect -) +require github.com/rivo/uniseg v0.4.7 // indirect From 5de2f4168ddd49f0049de7e546bc814cae53845e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 31 Oct 2024 13:46:07 -0400 Subject: [PATCH 3/4] fix: windows terminal tests --- env_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/env_test.go b/env_test.go index 333dd05..da3c059 100644 --- a/env_test.go +++ b/env_test.go @@ -150,7 +150,14 @@ var cases = []struct { environ: []string{ "WT_SESSION=1", }, - expected: TrueColor, + expected: func() Profile { + if runtime.GOOS == "windows" { + // Windows Terminal supports TrueColor + return TrueColor + } else { + return NoTTY + } + }(), }, } From 68e66664479fb54e7e49ec438e48a17c77c73347 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 31 Oct 2024 13:54:16 -0400 Subject: [PATCH 4/4] chore: tidy and fix some lint issues --- env.go | 3 ++- profile.go | 12 ++++++------ writer.go | 5 +++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/env.go b/env.go index fc38790..2a4e355 100644 --- a/env.go +++ b/env.go @@ -161,7 +161,8 @@ func envColorProfile(env map[string]string) (p Profile) { p = ANSI } - if ti, err := terminfo.Load(term); err == nil { + ti, err := terminfo.Load(term) + if err == nil { extbools := ti.ExtBoolCapsShort() if _, ok := extbools["RGB"]; ok { p = TrueColor diff --git a/profile.go b/profile.go index 6537784..e6ed78f 100644 --- a/profile.go +++ b/profile.go @@ -12,15 +12,15 @@ import ( type Profile byte const ( - // TrueColor, 24-bit color profile + // TrueColor, 24-bit color profile. TrueColor Profile = iota - // ANSI256, 8-bit color profile + // ANSI256, 8-bit color profile. ANSI256 - // ANSI, 4-bit color profile + // ANSI, 4-bit color profile. ANSI - // Ascii, uncolored profile - Ascii // nolint: revive - // NoTTY, not a terminal profile + // Ascii, uncolored profile. + Ascii //nolint:revive + // NoTTY, not a terminal profile. NoTTY ) diff --git a/writer.go b/writer.go index 6367f06..e58fe0f 100644 --- a/writer.go +++ b/writer.go @@ -40,8 +40,13 @@ func (w *Writer) Write(p []byte) (int, error) { return w.Forward.Write(p) case NoTTY: return io.WriteString(w.Forward, ansi.Strip(string(p))) + default: + return w.downsample(p) } +} +// downsample downgrades the given text to the appropriate color profile. +func (w *Writer) downsample(p []byte) (int, error) { var buf bytes.Buffer var state byte