parent
ba53cd349b
commit
5030d4b474
@ -0,0 +1,150 @@
|
||||
/* ——————————————————————————————————————————————————————————————————————————
|
||||
Proyecto WIAWAIW
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
Fichero Canabilities_detect_windows.go
|
||||
Package ansi2
|
||||
Autor Juan V. Navarro juanvnl@activething.com
|
||||
Creado 29/08/2025
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
LICENSES AND TERMS OF USE
|
||||
-------------------------
|
||||
|
||||
This software is licensed under the Elastic License v2.0 (the "License").
|
||||
For full terms and additional information regarding permitted and prohibited
|
||||
uses, please visit:
|
||||
https://activething.com/wiawiaw/licenses
|
||||
|
||||
You may use, copy, modify, and redistribute this software internally within
|
||||
your organization for any purpose, including research, development, and
|
||||
testing, subject to the terms of this License.
|
||||
|
||||
You may NOT, however, use, provide, distribute, or make this software
|
||||
available to any third party as part of a hosted service, SaaS offering, or
|
||||
commercial product without first obtaining a commercial license from
|
||||
Active Thing.
|
||||
|
||||
You may combine this software with other code, provided that such
|
||||
combination does not circumvent the restrictions of this License.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. In no event shall
|
||||
the authors or copyright holders be liable for any claim, damages, or other
|
||||
liability arising from the use of this software.
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
Web : activething.com | wiawiaw.com
|
||||
git : g.activething.com | github.com/activething
|
||||
Correo : dev@activething.com
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
No deseo caminar sobre el agua", dijo Siddhartha.
|
||||
Que los antiguos chamanes se contenten con tales habilidades.
|
||||
—— Hermann Hesse, Siddhartha
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
Copyright (c) 2025 Active Thing
|
||||
————————————————————————————————————————————————————————————————————————————— */
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ansi2
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
||||
// flags Windows console
|
||||
const (
|
||||
enablePOutput = 0x0001
|
||||
enableVTerminal = 0x0004
|
||||
)
|
||||
|
||||
|
||||
// DetectTerminalCapabilities detecta capacidades ANSI para un descriptor de salida en Windows.
|
||||
// - Intenta habilitar Virtual Terminal Processing para el handle dado (sin lanzar procesos externos).
|
||||
// - Si el handle no es consola o no se puede habilitar, es conservador.
|
||||
func DetectTerminalCapabilities(f *os.File) Capabilities {
|
||||
if f == nil {
|
||||
f = os.Stdout
|
||||
}
|
||||
|
||||
// Respeta NO_COLOR
|
||||
if _, ok := os.LookupEnv("NO_COLOR"); ok {
|
||||
return CanNone
|
||||
}
|
||||
|
||||
// FORCE_COLOR fuerza todas las capacidades
|
||||
if _, ok := os.LookupEnv("FORCE_COLOR"); ok {
|
||||
return CanAll
|
||||
}
|
||||
|
||||
handle := f.Fd()
|
||||
|
||||
// Si no es consola (GetConsoleMode falla), entonces está redirigido -> sin color.
|
||||
if !isConsole(handle) {
|
||||
// Sin consola: pero si WT_SESSION / TERM_PROGRAM indican emuladores remotos, forzamos capacidades
|
||||
if os.Getenv("WT_SESSION") != "" || strings.Contains(strings.ToLower(os.Getenv("TERM_PROGRAM")), "vscode") {
|
||||
return CanAll
|
||||
}
|
||||
return CanNone
|
||||
}
|
||||
|
||||
// Intentar habilitar Virtual Terminal Processing para ese handle.
|
||||
enabled := enableVTProcessing(handle)
|
||||
|
||||
// Si logramos habilitar VT, asumimos soporte de estilos + colores básicos.
|
||||
if enabled {
|
||||
caps := CanBasicColor | CanAllExtended
|
||||
|
||||
colorterm := strings.ToLower(os.Getenv("COLORTERM"))
|
||||
term := strings.ToLower(os.Getenv("TERM"))
|
||||
if strings.Contains(colorterm, "truecolor") || strings.Contains(colorterm, "24bit") {
|
||||
caps |= CanTrueColor | Can256Color
|
||||
} else if strings.Contains(term, "256color") {
|
||||
caps |= Can256Color
|
||||
}
|
||||
// También si estamos en Windows Terminal o VSCode, damos todo por hecho
|
||||
if os.Getenv("WT_SESSION") != "" || strings.Contains(strings.ToLower(os.Getenv("TERM_PROGRAM")), "vscode") {
|
||||
caps |= CanTrueColor | Can256Color
|
||||
}
|
||||
return caps
|
||||
}
|
||||
|
||||
// Si no pudimos habilitar VT, devolvemos lo básico si estamos en consola.
|
||||
// En versiones antiguas de consola sólo colores básicos funcionan.
|
||||
return CanBasicColor
|
||||
}
|
||||
|
||||
func isConsole(handle uintptr) bool {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
getConsoleMode := kernel32.NewProc("GetConsoleMode")
|
||||
var mode uint32
|
||||
r1, _, _ := getConsoleMode.Call(handle, uintptr(unsafe.Pointer(&mode)))
|
||||
return r1 != 0
|
||||
}
|
||||
|
||||
// enableVTProcessing intenta activar VT para el handle dado.
|
||||
// Retorna true si se activó correctamente.
|
||||
func enableVTProcessing(handle uintptr) bool {
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
getConsoleMode := kernel32.NewProc("GetConsoleMode")
|
||||
setConsoleMode := kernel32.NewProc("SetConsoleMode")
|
||||
|
||||
var mode uint32
|
||||
r1, _, _ := getConsoleMode.Call(handle, uintptr(unsafe.Pointer(&mode)))
|
||||
if r1 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
newMode := mode | enableVTerminal | enablePOutput
|
||||
r1, _, _ = setConsoleMode.Call(handle, uintptr(newMode))
|
||||
return r1 != 0
|
||||
}
|
||||
|
||||
@ -0,0 +1,331 @@
|
||||
/* ——————————————————————————————————————————————————————————————————————————
|
||||
Proyecto WIAWAIW
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
Fichero z_ansi_all_test.go
|
||||
Package ansi2
|
||||
Autor Juan V. Navarro juanvnl@activething.com
|
||||
Creado 29/08/2025
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
LICENSES AND TERMS OF USE
|
||||
-------------------------
|
||||
|
||||
This software is licensed under the Elastic License v2.0 (the "License").
|
||||
For full terms and additional information regarding permitted and prohibited
|
||||
uses, please visit:
|
||||
https://activething.com/wiawiaw/licenses
|
||||
|
||||
You may use, copy, modify, and redistribute this software internally within
|
||||
your organization for any purpose, including research, development, and
|
||||
testing, subject to the terms of this License.
|
||||
|
||||
You may NOT, however, use, provide, distribute, or make this software
|
||||
available to any third party as part of a hosted service, SaaS offering, or
|
||||
commercial product without first obtaining a commercial license from
|
||||
Active Thing.
|
||||
|
||||
You may combine this software with other code, provided that such
|
||||
combination does not circumvent the restrictions of this License.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. In no event shall
|
||||
the authors or copyright holders be liable for any claim, damages, or other
|
||||
liability arising from the use of this software.
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
|
||||
Web : activething.com | wiawiaw.com
|
||||
git : g.activething.com | github.com/activething
|
||||
Correo : dev@activething.com
|
||||
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
No deseo caminar sobre el agua", dijo Siddhartha.
|
||||
Que los antiguos chamanes se contenten con tales habilidades.
|
||||
—— Hermann Hesse, Siddhartha
|
||||
—————————————————————————————————————————————————————————————————————————————
|
||||
Copyright (c) 2025 Active Thing
|
||||
————————————————————————————————————————————————————————————————————————————— */
|
||||
|
||||
package ansi2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Helper para comparar valores y reportar errores
|
||||
func assertEqual[T comparable](t *testing.T, got, want T, msg ...string) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
prefix := "Error"
|
||||
if len(msg) > 0 {
|
||||
prefix = msg[0]
|
||||
}
|
||||
t.Errorf("%s: got %v, want %v", prefix, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeStyle(t *testing.T) {
|
||||
t.Run("Create style with multiple options", func(t *testing.T) {
|
||||
s := MakeStyle(Bold(), FgRed(), BgWhite())
|
||||
|
||||
assertEqual(t, s.IsBold(), true, "IsBold")
|
||||
assertEqual(t, s.HasFgColor(), true, "HasFgColor")
|
||||
assertEqual(t, s.HasBgColor(), true, "HasBgColor")
|
||||
assertEqual(t, s.GetFg(), BasicRed(), "GetFg")
|
||||
assertEqual(t, s.GetBg(), BasicWhite(), "GetBg")
|
||||
})
|
||||
|
||||
t.Run("Create an empty style", func(t *testing.T) {
|
||||
s := MakeStyle()
|
||||
assertEqual(t, s.HasFormat(), false, "HasFormat")
|
||||
assertEqual(t, s.HasColor(), false, "HasColor")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStyle_Formatters(t *testing.T) {
|
||||
s := Style(0)
|
||||
|
||||
t.Run("Bold vs Faint", func(t *testing.T) {
|
||||
s1 := s.Bold()
|
||||
assertEqual(t, s1.IsBold(), true, "s1.IsBold")
|
||||
assertEqual(t, s1.IsFaint(), false, "s1.IsFaint")
|
||||
|
||||
s2 := s1.Faint()
|
||||
assertEqual(t, s2.IsBold(), false, "s2.IsBold")
|
||||
assertEqual(t, s2.IsFaint(), true, "s2.IsFaint")
|
||||
})
|
||||
|
||||
t.Run("Blink", func(t *testing.T) {
|
||||
s1 := s.SlowBlink()
|
||||
assertEqual(t, s1.IsSlowBlink(), true, "s1.IsSlowBlink")
|
||||
assertEqual(t, s1.IsRapidBlink(), false, "s1.IsRapidBlink")
|
||||
|
||||
s2 := s1.RapidBlink()
|
||||
assertEqual(t, s2.IsSlowBlink(), false, "s2.IsSlowBlink")
|
||||
assertEqual(t, s2.IsRapidBlink(), true, "s2.IsRapidBlink")
|
||||
})
|
||||
|
||||
t.Run("Remove format", func(t *testing.T) {
|
||||
s1 := MakeStyle(Bold(), Italic())
|
||||
s2 := s1.NoBold()
|
||||
assertEqual(t, s2.IsBold(), false, "s2.IsBold")
|
||||
assertEqual(t, s2.IsItalic(), true, "s2.IsItalic")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStyle_FgBg(t *testing.T) {
|
||||
s := Style(0)
|
||||
|
||||
t.Run("Set and Get TrueColor", func(t *testing.T) {
|
||||
s1 := s.TrueFgColor(10, 20, 30).TrueBgColor(40, 50, 60)
|
||||
fg := s1.GetFg()
|
||||
bg := s1.GetBg()
|
||||
|
||||
assertEqual(t, fg.ColorType(), ColorTypeTrue, "fg.ColorType")
|
||||
assertEqual(t, bg.ColorType(), ColorTypeTrue, "bg.ColorType")
|
||||
|
||||
r, g, b, ok := fg.GetTrueColor()
|
||||
assertEqual(t, ok, true, "fg.GetTrueColor ok")
|
||||
assertEqual(t, r, uint8(10), "fg r")
|
||||
assertEqual(t, g, uint8(20), "fg g")
|
||||
assertEqual(t, b, uint8(30), "fg b")
|
||||
|
||||
r, g, b, ok = bg.GetTrueColor()
|
||||
assertEqual(t, ok, true, "bg.GetTrueColor ok")
|
||||
assertEqual(t, r, uint8(40), "bg r")
|
||||
assertEqual(t, g, uint8(50), "bg g")
|
||||
assertEqual(t, b, uint8(60), "bg b")
|
||||
})
|
||||
|
||||
t.Run("Set and Get IndexColor", func(t *testing.T) {
|
||||
s1 := s.IndexFgColor(123).IndexBgColor(234)
|
||||
fg := s1.GetFg()
|
||||
bg := s1.GetBg()
|
||||
|
||||
assertEqual(t, fg.ColorType(), ColorTypeIndex, "fg.ColorType")
|
||||
assertEqual(t, bg.ColorType(), ColorTypeIndex, "bg.ColorType")
|
||||
|
||||
idx, ok := fg.GetIndexColor()
|
||||
assertEqual(t, ok, true, "fg.GetIndexColor ok")
|
||||
assertEqual(t, idx, uint8(123), "fg index")
|
||||
|
||||
idx, ok = bg.GetIndexColor()
|
||||
assertEqual(t, ok, true, "bg.GetIndexColor ok")
|
||||
assertEqual(t, idx, uint8(234), "bg index")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStyle_Adapt(t *testing.T) {
|
||||
// Estilo original con alta capacidad (TrueColor y Bold)
|
||||
originalStyle := MakeStyle(Bold(), FgTrueColor(100, 150, 200), BgTrueColor(20, 40, 60))
|
||||
|
||||
t.Run("Adapt TrueColor to 256Color", func(t *testing.T) {
|
||||
// Capacidades: soporta 256 colores y negrita
|
||||
caps := Can256Color | CanFormatBold
|
||||
adaptedStyle := originalStyle.Adapt(caps)
|
||||
|
||||
// El formato se debe mantener
|
||||
assertEqual(t, adaptedStyle.IsBold(), true, "Adapted style should be bold")
|
||||
|
||||
// El color de primer plano debe ser de tipo Index
|
||||
fg := adaptedStyle.GetFg()
|
||||
assertEqual(t, fg.ColorType(), ColorTypeIndex, "Adapted Fg should be IndexColor")
|
||||
fgIndex, _ := fg.GetIndexColor()
|
||||
expectedFgIndex := RGBToIndex(100, 150, 200)
|
||||
assertEqual(t, fgIndex, expectedFgIndex, "Adapted Fg index mismatch")
|
||||
|
||||
// El color de fondo debe ser de tipo Index
|
||||
bg := adaptedStyle.GetBg()
|
||||
assertEqual(t, bg.ColorType(), ColorTypeIndex, "Adapted Bg should be IndexColor")
|
||||
bgIndex, _ := bg.GetIndexColor()
|
||||
expectedBgIndex := RGBToIndex(20, 40, 60)
|
||||
assertEqual(t, bgIndex, expectedBgIndex, "Adapted Bg index mismatch")
|
||||
})
|
||||
|
||||
t.Run("Adapt TrueColor to BasicColor", func(t *testing.T) {
|
||||
// Capacidades: solo soporta colores básicos
|
||||
caps := CanBasicColor
|
||||
adaptedStyle := originalStyle.Adapt(caps)
|
||||
|
||||
// El formato se debe descartar
|
||||
assertEqual(t, adaptedStyle.IsBold(), false, "Adapted style should not be bold")
|
||||
|
||||
// El color de primer plano debe ser de tipo Basic
|
||||
fg := adaptedStyle.GetFg()
|
||||
assertEqual(t, fg.ColorType(), ColorTypeBasic, "Adapted Fg should be BasicColor")
|
||||
fgBasic, _ := fg.GetBasicColor()
|
||||
expectedFgBasic := RGBToBasic(100, 150, 200)
|
||||
assertEqual(t, fgBasic, expectedFgBasic, "Adapted Fg basic color mismatch")
|
||||
|
||||
// El color de fondo debe ser de tipo Basic
|
||||
bg := adaptedStyle.GetBg()
|
||||
assertEqual(t, bg.ColorType(), ColorTypeBasic, "Adapted Bg should be BasicColor")
|
||||
bgBasic, _ := bg.GetBasicColor()
|
||||
expectedBgBasic := RGBToBasic(20, 40, 60)
|
||||
assertEqual(t, bgBasic, expectedBgBasic, "Adapted Bg basic color mismatch")
|
||||
})
|
||||
|
||||
t.Run("Adapt 256Color to BasicColor", func(t *testing.T) {
|
||||
// Estilo con color indexado (un tipo de azul)
|
||||
indexedStyle := MakeStyle(Italic(), FgIndexColor(21)) // 21 es un azul
|
||||
caps := CanBasicColor | CanFormatItalic
|
||||
adaptedStyle := indexedStyle.Adapt(caps)
|
||||
|
||||
assertEqual(t, adaptedStyle.IsItalic(), true, "Adapted style should be italic")
|
||||
|
||||
fg := adaptedStyle.GetFg()
|
||||
assertEqual(t, fg.ColorType(), ColorTypeBasic, "Adapted Fg should be BasicColor")
|
||||
fgBasic, _ := fg.GetBasicColor()
|
||||
expectedFgBasic := IndexToBasic(21) // Debería convertirse a azul básico
|
||||
assertEqual(t, fgBasic, expectedFgBasic, fmt.Sprintf("Expected basic blue (%d)", expectedFgBasic))
|
||||
})
|
||||
|
||||
t.Run("Adapt to no color support", func(t *testing.T) {
|
||||
caps := CanFormatBold // Solo soporta negrita, sin colores
|
||||
adaptedStyle := originalStyle.Adapt(caps)
|
||||
|
||||
// El formato se mantiene
|
||||
assertEqual(t, adaptedStyle.IsBold(), true, "Adapted style should be bold")
|
||||
// No debe tener color
|
||||
assertEqual(t, adaptedStyle.HasColor(), false, "Adapted style should have no color")
|
||||
})
|
||||
|
||||
t.Run("Adapt to no capabilities", func(t *testing.T) {
|
||||
caps := CanNone
|
||||
adaptedStyle := originalStyle.Adapt(caps)
|
||||
// El estilo resultante debe ser 0 (sin formato ni color)
|
||||
assertEqual(t, adaptedStyle, Style(0), "Adapted style should be empty")
|
||||
})
|
||||
}
|
||||
|
||||
// En z_ansi_all_test.go (o como se llame tu archivo de pruebas)
|
||||
|
||||
func TestStyle_Build(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
style Style
|
||||
expected AnsiStyle
|
||||
}{
|
||||
{
|
||||
name: "NoStyle",
|
||||
style: Style(0),
|
||||
expected: NoStyle,
|
||||
},
|
||||
{
|
||||
name: "Bold only",
|
||||
style: MakeStyle(Bold()),
|
||||
expected: "\x1b[1m",
|
||||
},
|
||||
{
|
||||
name: "Fg Basic Red",
|
||||
style: MakeStyle(FgRed()),
|
||||
expected: "\x1b[31m",
|
||||
},
|
||||
{
|
||||
name: "Fg Bright Black",
|
||||
style: MakeStyle(FgBrightBlack()),
|
||||
expected: "\x1b[90m",
|
||||
},
|
||||
{
|
||||
name: "Bg Basic Green",
|
||||
style: MakeStyle(BgGreen()),
|
||||
expected: "\x1b[42m",
|
||||
},
|
||||
{
|
||||
name: "Bold and Fg Cyan",
|
||||
style: MakeStyle(Bold(), FgCyan()),
|
||||
expected: "\x1b[36;1m",
|
||||
},
|
||||
{
|
||||
name: "Italic, Fg Yellow, Bg Blue",
|
||||
style: MakeStyle(Italic(), FgYellow(), BgBlue()),
|
||||
expected: "\x1b[33;44;3m",
|
||||
},
|
||||
{
|
||||
name: "Fg Index Color",
|
||||
style: MakeStyle(FgIndexColor(123)),
|
||||
expected: "\x1b[38;5;123m",
|
||||
},
|
||||
{
|
||||
name: "Bg Index Color",
|
||||
style: MakeStyle(BgIndexColor(201)),
|
||||
expected: "\x1b[48;5;201m",
|
||||
},
|
||||
{
|
||||
name: "Fg TrueColor",
|
||||
style: MakeStyle(FgTrueColor(10, 20, 30)),
|
||||
expected: "\x1b[38;2;10;20;30m",
|
||||
},
|
||||
{
|
||||
name: "Bg TrueColor",
|
||||
style: MakeStyle(BgTrueColor(110, 120, 130)),
|
||||
expected: "\x1b[48;2;110;120;130m",
|
||||
},
|
||||
{
|
||||
name: "Complex Style",
|
||||
style: MakeStyle(Underline(), CrossedOut(), FgTrueColor(50, 50, 50), BgIndexColor(250)),
|
||||
expected: "\x1b[38;2;50;50;50;48;5;250;4;9m",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.style.Build()
|
||||
// Esta comparación ahora debería funcionar
|
||||
assertEqual(t, got, tc.expected, "Build output mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnsiStyle_Stylize(t *testing.T) {
|
||||
style := MakeStyle(FgRed()).Build()
|
||||
text := "hello"
|
||||
expected := "\x1b[31mhello\x1b[0m"
|
||||
got := style.Stylize(text)
|
||||
assertEqual(t, got, expected)
|
||||
|
||||
noStyle := NoStyle
|
||||
assertEqual(t, noStyle.Stylize("text"), "text")
|
||||
}
|
||||
Loading…
Reference in new issue