Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
package termenv
import (
"errors"
"os"
"runtime"
"strconv"
"strings"
)
// ColorLevel is the color level supported by a terminal.
type ColorLevel uint8
const (
TermColorNone ColorLevel = iota // not support color
TermColor16 // 16(4bit) ANSI color supported
TermColor256 // 256(8bit) color supported
TermColorTrue // support TRUE(RGB) color
)
// String returns the string name of the color level.
func (l ColorLevel) String() string {
switch l {
case TermColor16:
return "ansiColor"
case TermColor256:
return "256color"
case TermColorTrue:
return "trueColor"
default:
return "none"
}
}
// NoColor returns true if the NO_COLOR environment variable is set.
func NoColor() bool { return noColor }
// TermColorLevel returns the color support level for the current terminal.
func TermColorLevel() ColorLevel { return colorLevel }
// IsSupportColor returns true if the terminal supports color.
func IsSupportColor() bool { return colorLevel > TermColorNone }
// IsSupport256Color returns true if the terminal supports 256 colors.
func IsSupport256Color() bool { return colorLevel >= TermColor256 }
// IsSupportTrueColor returns true if the terminal supports true color.
func IsSupportTrueColor() bool { return colorLevel == TermColorTrue }
//
// ---------------- Force set color support ----------------
//
var backLevel ColorLevel
// SetColorLevel value force.
func SetColorLevel(level ColorLevel) {
// backup old value
backLevel = colorLevel
// force set color level
colorLevel = level
supportColor = level > TermColorNone
noColor = supportColor == false
}
// DisableColor in the current terminal
func DisableColor() {
// backup old value
backLevel = colorLevel
// force disable color
noColor = true
supportColor = false
colorLevel = TermColorNone
}
// ForceEnableColor flags value. TIP: use for unit testing.
//
// Usage:
//
// ccolor.ForceEnableColor()
// defer ccolor.RevertColorSupport()
func ForceEnableColor() {
// backup old value
backLevel = colorLevel
// force enables color
noColor = false
supportColor = true
colorLevel = TermColor256
// return colorLevel
}
// RevertColorSupport flags to init value.
func RevertColorSupport() {
// revert color flags var
colorLevel = backLevel
supportColor = backLevel > TermColorNone
noColor = os.Getenv("NO_COLOR") == ""
}
/*************************************************************
* helper methods for detect color supports
*************************************************************/
// DetectColorLevel for current env
//
// NOTICE: The method will detect terminal info each time.
//
// if only want to get current color level, please direct call IsSupportColor() or TermColorLevel()
func DetectColorLevel() ColorLevel {
level, _ := detectTermColorLevel()
return level
}
// on TERM=screen: not support true-color
const noTrueColorTerm = "screen"
// detect terminal color support level
//
// refer https://github.com/Delta456/box-cli-maker
func detectTermColorLevel() (level ColorLevel, needVTP bool) {
isWin := runtime.GOOS == "windows"
termVal := os.Getenv("TERM")
if termVal != noTrueColorTerm {
// On JetBrains Terminal
// - TERM value not set, but support true-color
// env:
// TERMINAL_EMULATOR=JetBrains-JediTerm
val := os.Getenv("TERMINAL_EMULATOR")
if val == "JetBrains-JediTerm" {
debugf("True Color support on JetBrains-JediTerm, is win: %v", isWin)
return TermColorTrue, false
}
}
level = detectColorLevelFromEnv(termVal, isWin)
// fallback: simple detect by TERM value string.
if level == TermColorNone {
debugf("level=none - fallback check special term color support")
level, needVTP = detectSpecialTermColor(termVal)
debugf("color level by detectSpecialTermColor: %s", level.String())
} else {
debugf("color level by detectColorLevelFromEnv: %s", level.String())
}
return
}
// detectColorFromEnv returns the color level COLORTERM, FORCE_COLOR,
// TERM_PROGRAM, or determined from the TERM environment variable.
//
// refer the github.com/xo/terminfo.ColorLevelFromEnv()
// https://en.wikipedia.org/wiki/Terminfo
func detectColorLevelFromEnv(termVal string, isWin bool) ColorLevel {
if termVal == noTrueColorTerm { // on TERM=screen: not support true-color
return TermColor256
}
// check for overriding environment variables
colorTerm, termProg, forceColor := os.Getenv("COLORTERM"), os.Getenv("TERM_PROGRAM"), os.Getenv("FORCE_COLOR")
switch {
case strings.Contains(colorTerm, "truecolor") || strings.Contains(colorTerm, "24bit"):
return TermColorTrue
case colorTerm != "" || forceColor != "":
return TermColor16
case termProg == "Apple_Terminal":
return TermColor256
case termProg == "Terminus" || termProg == "Hyper":
return TermColorTrue
case termProg == "iTerm.app":
// check iTerm version
termVer := os.Getenv("TERM_PROGRAM_VERSION")
if termVer != "" {
i, err := strconv.Atoi(strings.Split(termVer, ".")[0])
if err != nil {
setLastErr(errors.New("invalid TERM_PROGRAM_VERSION=" + termVer))
return TermColor256 // return TermColorNone
}
if i == 3 {
return TermColorTrue
}
}
return TermColor256
}
// otherwise determine from TERM's max_colors capability
// if !isWin && termVal != "" {
// debugf("TERM=%s - TODO check color level by load terminfo file", termVal)
// return TermColor16
// }
// no TERM env value. default return none level
return TermColorNone
}
+46
View File
@@ -0,0 +1,46 @@
//go:build !windows
package termenv
import (
"strings"
"syscall"
"github.com/gookit/goutil/internal/checkfn"
)
// detect special term color support on macOS, linux, unix
func detectSpecialTermColor(termVal string) (ColorLevel, bool) {
if termVal == "" {
// detect WSL as it has True Color support
// on Windows WSL:
// - runtime.GOOS == "Linux"
// - support true-color
if checkfn.IsWSL() {
debugf("True Color support on WSL environment")
return TermColorTrue, false
}
return TermColorNone, false
}
debugf("terminfo check - fallback detect color by check TERM value")
// on TERM=screen:
// - support 256, not support true-color. test on macOS
if termVal == noTrueColorTerm {
return TermColor256, false
}
if strings.Contains(termVal, "256color") {
return TermColor256, false
}
if strings.Contains(termVal, "xterm") {
return TermColor256, false
}
return TermColor16, false
}
func syscallStdinFd() int { return syscall.Stdin }
func syscallStdoutFd() int { return syscall.Stdout }
+176
View File
@@ -0,0 +1,176 @@
//go:build windows
package termenv
import (
"os"
"syscall"
"golang.org/x/sys/windows"
)
// Get the Windows Version and Build Number
var majorVersion, _, buildNumber = windows.RtlGetNtVersionNumbers()
// refer
//
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57
// https://github.com/gookit/color/issues/25#issuecomment-738727917
//
// detects the color level supported on Windows: CMD, PowerShell
func detectSpecialTermColor(_ string) (tl ColorLevel, needVTP bool) {
if os.Getenv("ConEmuANSI") == "ON" {
debugf("True Color support by ConEmuANSI=ON")
// ConEmuANSI is "ON" for generic ANSI support
// but True Color option is enabled by default
// I am just assuming that people wouldn't have disabled it
// Even if it is not enabled then ConEmu will auto round off accordingly
return TermColorTrue, false
}
// Before Windows 10 Build Number 10586, console never supported ANSI Colors
if buildNumber < 10586 || majorVersion < 10 {
// Detect if using ANSICON on older systems
if os.Getenv("ANSICON") != "" {
conVersion := os.Getenv("ANSICON_VER")
// 8-bit Colors were only supported after v1.81 release
if conVersion >= "181" {
return TermColor256, false
}
return TermColor16, false
}
return TermColorNone, false
}
// True Color is not available before build 14931 so fallback to 8-bit color.
if buildNumber < 14931 {
return TermColor256, true
}
// Windows 10 build 14931 is the first release that supports 16m/TrueColor
debugf("support True Color on windows version is >= build 14931")
return TermColorTrue, true
}
// TryEnableVTP try force enables colors on Windows terminal
func TryEnableVTP(enable bool) bool {
if !enable {
return false
}
// enable colors on Windows terminal
if tryEnableOnCONOUT() {
debugf("True-Color by enable VirtualTerminalProcessing on windows")
return true
}
// initKernel32Proc()
suc := tryEnableOnStdout()
debugf("True-Color by enable VirtualTerminalProcessing on windows")
return suc
}
func tryEnableOnCONOUT() bool {
outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if err != nil {
setLastErr(err)
return false
}
err = EnableVTProcessing(outHandle, true)
if err != nil {
setLastErr(err)
return false
}
return true
}
func tryEnableOnStdout() bool {
// try direct open syscall.Stdout
err := EnableVTProcessing(syscall.Stdout, true)
if err != nil {
setLastErr(err)
return false
}
return true
}
// related docs
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
var (
// isMSys bool
kernel32 *syscall.LazyDLL
procGetConsoleMode *syscall.LazyProc
procSetConsoleMode *syscall.LazyProc
)
func initKernel32Proc() {
if kernel32 != nil {
return
}
// load related Windows dll
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
}
/*************************************************************
* render full color code on Windows(8,16,24bit color)
*************************************************************/
// EnableVTProcessing Enable virtual terminal processing on Windows
//
// ref from github.com/konsorten/go-windows-terminal-sequences
// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
//
// Usage:
//
// err := EnableVTProcessing(syscall.Stdout, true)
// // support print color text
// err = EnableVTProcessing(syscall.Stdout, false)
func EnableVTProcessing(stream syscall.Handle, enable bool) error {
var mode uint32
// Check if it is currently in the terminal
// err := syscall.GetConsoleMode(syscall.Stdout, &mode)
err := syscall.GetConsoleMode(stream, &mode)
if err != nil {
debugf("enable Windows VirtualTerminalProcessing error: %v", err)
return err
}
// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters
if enable {
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
mode &^= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
}
err = windows.SetConsoleMode(windows.Handle(stream), mode)
if err != nil {
return err
}
// ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode))
// if ret == 0 {
// return err
// }
return nil
}
// on Windows, must convert 'syscall.Stdin' to int
func syscallStdinFd() int {
return int(syscall.Stdin)
}
// on Windows, must convert to int
func syscallStdoutFd() int {
return int(syscall.Stdout)
}
+11
View File
@@ -0,0 +1,11 @@
package termenv
func init() {
// terminal supports color OR noColor=true: Don't need to enable virtual process
if colorLevel != TermColorNone || noColor {
return
}
// try force enable colors on Windows terminal
TryEnableVTP(needVTP)
}
+113
View File
@@ -0,0 +1,113 @@
// Package termenv provides detect color support of the current terminal.
// And with some utils for terminal env.
package termenv
import (
"fmt"
"os"
"strconv"
"golang.org/x/term"
)
var (
lastErr error
// debug mode
debugMode bool
// support color of current terminal
supportColor bool
// value of os color render and display
//
// NOTICE:
// if ENV: NO_COLOR is not empty, will disable color render.
noColor = os.Getenv("NO_COLOR") != ""
// the color support level for current terminal
// needVTP - need enable VTP, only for Windows OS
colorLevel, needVTP = detectTermColorLevel()
)
// SetDebugMode sets debug mode.
func SetDebugMode(enable bool) { debugMode = enable }
// LastErr returns the last error.
func LastErr() error {
defer func() {
lastErr = nil // reset on get
}()
return lastErr
}
func debugf(tpl string, v ...any) {
if debugMode {
fmt.Printf("TERMENV: "+tpl+"\n", v...)
}
}
func setLastErr(err error) {
if err != nil {
debugf("TERMENV: last error: %v", err)
lastErr = err
}
}
// exec: `stty -a 2>&1`
// const (
// mac: speed 9600 baud; 97 rows; 362 columns;
// macSttyMsgPattern = `(\d+)\s+rows;\s*(\d+)\s+columns;`
// linux: speed 38400 baud; rows 97; columns 362; line = 0;
// linuxSttyMsgPattern = `rows\s+(\d+);\s*columns\s+(\d+);`
// )
var terminalWidth, terminalHeight int
// GetTermSize for current console terminal. will first try to get from environment variables COLUMNS and LINES.
func GetTermSize(refresh ...bool) (w int, h int) {
if terminalWidth > 0 && (len(refresh) == 0 || !refresh[0]) {
return terminalWidth, terminalHeight
}
// 首先尝试从环境变量获取
if cols := os.Getenv("COLUMNS"); cols != "" {
if width, err := strconv.Atoi(cols); err == nil && width > 0 {
terminalWidth = width
}
}
if rows := os.Getenv("LINES"); rows != "" {
if height, err := strconv.Atoi(rows); err == nil && height > 0 {
terminalHeight = height
}
}
if terminalWidth > 0 && terminalHeight > 0 {
return terminalWidth, terminalHeight
}
var err error
w, h, err = term.GetSize(syscallStdoutFd())
if err != nil {
debugf("get terminal size error: %v", err)
return
}
// cache result
terminalWidth, terminalHeight = w, h
debugf("get terminal size: %d,%d", w, h)
return
}
// ReadPassword from console terminal
func ReadPassword(question ...string) string {
if len(question) > 0 {
print(question[0])
} else {
print("Enter Password: ")
}
bs, err := term.ReadPassword(syscallStdinFd())
if err != nil {
return ""
}
println() // new line
return string(bs)
}
+79
View File
@@ -0,0 +1,79 @@
package termenv
import (
"os"
"os/exec"
"runtime"
"strings"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/internal/comfunc"
"golang.org/x/term"
)
var (
cmdList = []string{"cmd", "cmd.exe"}
pwshList = []string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}
)
// IsTerminal 检查是否为终端设备中
func IsTerminal() bool {
fd := int(os.Stdout.Fd())
return term.IsTerminal(fd)
}
// CurrentShell get current used shell env file.
//
// eg "/bin/zsh" "/bin/bash".
// if onlyName=true, will return "zsh", "bash"
func CurrentShell(onlyName bool, fallbackShell ...string) string {
return comfunc.CurrentShell(onlyName, fallbackShell...)
}
// HasShellEnv has shell env check.
//
// Usage:
//
// HasShellEnv("sh")
// HasShellEnv("bash")
func HasShellEnv(shell string) bool {
// can also use: "echo $0"
out, err := shellExec("echo OK", shell)
if err != nil {
return false
}
return strings.TrimSpace(out) == "OK"
}
// IsShellSpecialVar reports whether the character identifies a special
// shell variable such as $*.
func IsShellSpecialVar(c uint8) bool {
switch c {
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return true
}
return false
}
func shellExec(expr, shell string) (string, error) {
// "-c" for bash,sh,zsh shell
mark := "-c"
if shell == "" {
shell = CurrentShell(true, "sh")
}
// special for Windows shell
if runtime.GOOS == "windows" {
// use cmd.exe, mark is "/c"
if checkfn.StringsContains(cmdList, shell) {
mark = "/c"
} else if checkfn.StringsContains(pwshList, shell) {
// "-Command" for powershell
mark = "-Command"
}
}
cmd := exec.Command(shell, mark, expr)
bs, err := cmd.CombinedOutput()
return string(bs), err
}