Files
futriis/pkg/utils/ansi.go

1095 lines
33 KiB
Go
Raw Normal View History

2026-04-08 21:43:35 +03:00
// Файл: pkg/utils/ansi.go
// Назначение: Кроссплатформенная работа с ANSI-последовательностями для управления
// цветом, стилем текста, позиционированием курсора и очисткой экрана.
// Обеспечивает совместимость с Linux (Debian/Fedora) и Illumos (OpenIndiana/OmniOS),
// автоматически отключая ANSI-коды в неподдерживаемых средах.
package utils
import (
"encoding/json"
"fmt"
"io"
"os"
"runtime"
"strconv"
"strings"
"sync/atomic"
)
// ANSICode представляет ANSI escape код
type ANSICode string
// Базовые ANSI коды форматирования
const (
ANSIReset ANSICode = "\033[0m"
ANSIBold ANSICode = "\033[1m"
ANSIDim ANSICode = "\033[2m"
ANSIItalic ANSICode = "\033[3m"
ANSIUnderline ANSICode = "\033[4m"
ANSIBlink ANSICode = "\033[5m"
ANSIReverse ANSICode = "\033[7m"
ANSIHidden ANSICode = "\033[8m"
ANSIStrike ANSICode = "\033[9m"
)
// ANSI коды цветов текста (foreground)
const (
ANSIFgBlack ANSICode = "\033[30m"
ANSIFgRed ANSICode = "\033[31m"
ANSIFgGreen ANSICode = "\033[32m"
ANSIFgYellow ANSICode = "\033[33m"
ANSIFgBlue ANSICode = "\033[34m"
ANSIFgMagenta ANSICode = "\033[35m"
ANSIFgCyan ANSICode = "\033[36m"
ANSIFgWhite ANSICode = "\033[37m"
ANSIFgBrightBlack ANSICode = "\033[90m"
ANSIFgBrightRed ANSICode = "\033[91m"
ANSIFgBrightGreen ANSICode = "\033[92m"
ANSIFgBrightYellow ANSICode = "\033[93m"
ANSIFgBrightBlue ANSICode = "\033[94m"
ANSIFgBrightMagenta ANSICode = "\033[95m"
ANSIFgBrightCyan ANSICode = "\033[96m"
ANSIFgBrightWhite ANSICode = "\033[97m"
// Специальный цвет #00bfff (Deep Sky Blue)
ANSIFgDeepSkyBlue ANSICode = "\033[38;2;0;191;255m"
)
// ANSI коды цветов фона (background)
const (
ANSIBgBlack ANSICode = "\033[40m"
ANSIBgRed ANSICode = "\033[41m"
ANSIBgGreen ANSICode = "\033[42m"
ANSIBgYellow ANSICode = "\033[43m"
ANSIBgBlue ANSICode = "\033[44m"
ANSIBgMagenta ANSICode = "\033[45m"
ANSIBgCyan ANSICode = "\033[46m"
ANSIBgWhite ANSICode = "\033[47m"
ANSIBgBrightBlack ANSICode = "\033[100m"
ANSIBgBrightRed ANSICode = "\033[101m"
ANSIBgBrightGreen ANSICode = "\033[102m"
ANSIBgBrightYellow ANSICode = "\033[103m"
ANSIBgBrightBlue ANSICode = "\033[104m"
ANSIBgBrightMagenta ANSICode = "\033[105m"
ANSIBgBrightCyan ANSICode = "\033[106m"
ANSIBgBrightWhite ANSICode = "\033[107m"
)
// ANSI коды управления курсором и экраном
const (
ANSICursorHome ANSICode = "\033[H"
ANSICursorUp ANSICode = "\033[A"
ANSICursorDown ANSICode = "\033[B"
ANSICursorForward ANSICode = "\033[C"
ANSICursorBack ANSICode = "\033[D"
ANSIClearScreen ANSICode = "\033[2J"
ANSIClearLine ANSICode = "\033[2K"
ANSISaveCursor ANSICode = "\033[s"
ANSIRestoreCursor ANSICode = "\033[u"
ANSIHideCursor ANSICode = "\033[?25l"
ANSIShowCursor ANSICode = "\033[?25h"
)
// ANSI коды для работы с табуляцией и скроллингом
const (
ANSIScrollUp ANSICode = "\033[D"
ANSIScrollDown ANSICode = "\033[M"
ANSISetTab ANSICode = "\033H"
ANSIClearTab ANSICode = "\033[g"
ANSIClearAllTabs ANSICode = "\033[3g"
)
// ANSI коды для 256-цветной и RGB палитры
const (
ANSI256FgPrefix = "\033[38;5;"
ANSI256BgPrefix = "\033[48;5;"
ANSIRGBFgPrefix = "\033[38;2;"
ANSIRGBBgPrefix = "\033[48;2;"
ANSISuffix = "m"
)
// ANSISupportLevel определяет уровень поддержки ANSI в терминале
type ANSISupportLevel int32
const (
SupportNone ANSISupportLevel = iota // Нет поддержки ANSI
SupportBasic // Только базовые 16 цветов
Support256 // Поддержка 256 цветов
SupportTrueColor // Поддержка True Color (RGB)
)
// ANSIEnableState хранит состояние включения/выключения ANSI
var (
ansiEnabled atomic.Bool
supportLevel atomic.Int32
termEnv string
colorTermEnv string
)
func init() {
// Определяем поддержку ANSI при загрузке пакета
detectANSISupport()
// По умолчанию ANSI включен, если есть поддержка
if getSupportLevel() != SupportNone {
ansiEnabled.Store(true)
} else {
ansiEnabled.Store(false)
}
}
// detectANSISupport определяет уровень поддержки ANSI в текущем окружении
func detectANSISupport() {
// Проверяем операционную систему
switch runtime.GOOS {
case "linux", "illumos", "solaris", "darwin":
// Unix-like системы обычно поддерживают ANSI
default:
supportLevel.Store(int32(SupportNone))
return
}
// Проверяем переменные окружения
termEnv = strings.ToLower(os.Getenv("TERM"))
colorTermEnv = strings.ToLower(os.Getenv("COLORTERM"))
// Проверка на True Color поддержку
if colorTermEnv == "truecolor" || colorTermEnv == "24bit" {
supportLevel.Store(int32(SupportTrueColor))
return
}
// Проверка на 256 цветов
if strings.Contains(termEnv, "256") || strings.Contains(colorTermEnv, "256") {
supportLevel.Store(int32(Support256))
return
}
// Проверка на базовую поддержку цвета
if strings.Contains(termEnv, "color") || strings.Contains(termEnv, "xterm") ||
strings.Contains(termEnv, "screen") || strings.Contains(termEnv, "vt100") {
supportLevel.Store(int32(SupportBasic))
return
}
// Поддержка IllumOS (OpenIndiana, OmniOS)
if runtime.GOOS == "illumos" || runtime.GOOS == "solaris" {
// В IllumOS терминалы обычно поддерживают ANSI
supportLevel.Store(int32(SupportBasic))
return
}
supportLevel.Store(int32(SupportNone))
}
// getSupportLevel возвращает текущий уровень поддержки ANSI
func getSupportLevel() ANSISupportLevel {
return ANSISupportLevel(supportLevel.Load())
}
// EnableANSI включает вывод ANSI кодов
func EnableANSI() {
ansiEnabled.Store(true)
}
// DisableANSI отключает вывод ANSI кодов
func DisableANSI() {
ansiEnabled.Store(false)
}
// IsANSIEnabled возвращает текущий статус ANSI
func IsANSIEnabled() bool {
return ansiEnabled.Load() && getSupportLevel() != SupportNone
}
// ApplyCode применяет ANSI код к строке (если ANSI включен)
func ApplyCode(text string, code ANSICode) string {
if !IsANSIEnabled() {
return text
}
return string(code) + text + string(ANSIReset)
}
// ColorizeText раскрашивает текст указанным цветом с поддержкой стилей
func ColorizeText(text string, color ANSICode, styles ...ANSICode) string {
if !IsANSIEnabled() {
return text
}
var builder strings.Builder
builder.WriteString(string(color))
for _, style := range styles {
builder.WriteString(string(style))
}
builder.WriteString(text)
builder.WriteString(string(ANSIReset))
return builder.String()
}
// SetColorEnabled включает или отключает цветной вывод (глобальная настройка)
func SetColorEnabled(enabled bool) {
if enabled {
EnableANSI()
} else {
DisableANSI()
}
}
// DisableColorMode отключает цветной режим
func DisableColorMode() {
DisableANSI()
}
// Print выводит текст без форматирования
func Print(a ...interface{}) {
fmt.Print(a...)
}
// Println выводит строку с переводом
func Println(a ...interface{}) {
fmt.Println(a...)
}
// PrintInfo выводит информационное сообщение (синим цветом)
func PrintInfo(msg string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgBrightCyan) + msg + string(ANSIReset))
} else {
fmt.Println(msg)
}
}
// PrintSuccess выводит сообщение об успехе (зелёным цветом)
func PrintSuccess(msg string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgBrightGreen) + msg + string(ANSIReset))
} else {
fmt.Println("✓ " + msg)
}
}
// PrintError выводит сообщение об ошибке (красным цветом)
func PrintError(msg string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgBrightRed) + "Error: " + msg + string(ANSIReset))
} else {
fmt.Println("Error: " + msg)
}
}
// PrintWarning выводит предупреждение (жёлтым цветом)
func PrintWarning(msg string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgBrightYellow) + "Warning: " + msg + string(ANSIReset))
} else {
fmt.Println("Warning: " + msg)
}
}
// PrintHeader выводит заголовок (фиолетовым, жирным шрифтом)
func PrintHeader(msg string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgBrightMagenta) + string(ANSIBold) + msg + string(ANSIReset))
} else {
fmt.Println("=== " + msg + " ===")
}
}
// PrintJSON выводит данные в формате JSON с отступами
func PrintJSON(data interface{}) {
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
PrintError("Failed to marshal JSON: " + err.Error())
return
}
fmt.Println(string(jsonData))
}
// FormatBytes форматирует байты в человекочитаемый вид (B, KB, MB, GB, TB)
func FormatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
switch exp {
case 0:
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(div))
case 1:
return fmt.Sprintf("%.1f MB", float64(bytes)/float64(div))
case 2:
return fmt.Sprintf("%.1f GB", float64(bytes)/float64(div))
case 3:
return fmt.Sprintf("%.1f TB", float64(bytes)/float64(div))
default:
return fmt.Sprintf("%d B", bytes)
}
}
// FormatDuration форматирует длительность в человекочитаемый вид
func FormatDuration(milliseconds int64) string {
seconds := milliseconds / 1000
minutes := seconds / 60
hours := minutes / 60
days := hours / 24
if days > 0 {
return fmt.Sprintf("%d days", days)
}
if hours > 0 {
return fmt.Sprintf("%d hours", hours)
}
if minutes > 0 {
return fmt.Sprintf("%d minutes", minutes)
}
if seconds > 0 {
return fmt.Sprintf("%d seconds", seconds)
}
return fmt.Sprintf("%d ms", milliseconds)
}
// TruncateString обрезает строку до указанной длины и добавляет "..."
func TruncateString(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
return s[:maxLen-3] + "..."
}
// Indent добавляет отступ к каждой строке
func Indent(text string, indent string) string {
lines := strings.Split(text, "\n")
for i, line := range lines {
lines[i] = indent + line
}
return strings.Join(lines, "\n")
}
// Confirm запрашивает подтверждение у пользователя
func Confirm(prompt string) bool {
fmt.Print(prompt + " [y/N]: ")
var response string
fmt.Scanln(&response)
response = strings.ToLower(strings.TrimSpace(response))
return response == "y" || response == "yes"
}
// PrintDataTable выводит данные в виде таблицы (обёртка над PrintTable)
func PrintDataTable(headers []string, rows [][]string) {
if len(rows) == 0 {
PrintInfo("No data to display")
return
}
// Вычисляем ширину колонок
colWidths := make([]int, len(headers))
for i, header := range headers {
colWidths[i] = len(header)
}
for _, row := range rows {
for i, cell := range row {
if len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
// Выводим заголовки
separator := "+"
for _, width := range colWidths {
separator += strings.Repeat("-", width+2) + "+"
}
fmt.Println(separator)
headerLine := "|"
for i, header := range headers {
headerLine += fmt.Sprintf(" %-*s |", colWidths[i], header)
}
fmt.Println(headerLine)
fmt.Println(separator)
// Выводим строки
for _, row := range rows {
line := "|"
for i, cell := range row {
line += fmt.Sprintf(" %-*s |", colWidths[i], cell)
}
fmt.Println(line)
}
fmt.Println(separator)
}
// GetEnv возвращает переменную окружения или значение по умолчанию
func GetEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// Contains проверяет, содержит ли слайс указанный элемент
func Contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// Unique возвращает уникальные элементы слайса
func Unique(slice []string) []string {
keys := make(map[string]bool)
result := make([]string, 0, len(slice))
for _, item := range slice {
if !keys[item] {
keys[item] = true
result = append(result, item)
}
}
return result
}
// PrintDeepSkyBlue выводит текст цветом #00bfff без перевода строки
func PrintDeepSkyBlue(text string) {
if IsANSIEnabled() {
fmt.Print(string(ANSIFgDeepSkyBlue) + text)
} else {
fmt.Print(text)
}
}
// PrintlnDeepSkyBlue выводит строку цветом #00bfff с переводом строки
func PrintlnDeepSkyBlue(text string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgDeepSkyBlue) + text)
} else {
fmt.Println(text)
}
}
// PrintDeepSkyBlueReset выводит текст цветом #00bfff и сбрасывает цвет после
func PrintDeepSkyBlueReset(text string) {
if IsANSIEnabled() {
fmt.Print(string(ANSIFgDeepSkyBlue) + text + string(ANSIReset))
} else {
fmt.Print(text)
}
}
// PrintlnDeepSkyBlueReset выводит строку цветом #00bfff с переводом строки и сбрасывает цвет
func PrintlnDeepSkyBlueReset(text string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgDeepSkyBlue) + text + string(ANSIReset))
} else {
fmt.Println(text)
}
}
// ResetColor сбрасывает цвет вывода на стандартный
func ResetColor() {
if IsANSIEnabled() {
fmt.Print(string(ANSIReset))
}
}
// SetGlobalColor устанавливает цвет для всего последующего вывода
func SetGlobalColor(color ANSICode) {
if IsANSIEnabled() {
fmt.Print(string(color))
}
}
// ResetGlobalColor сбрасывает глобальный цвет вывода
func ResetGlobalColor() {
if IsANSIEnabled() {
fmt.Print(string(ANSIReset))
}
}
// Colorize256 раскрашивает текст 256-цветной палитрой
func Colorize256(text string, colorCode int, isBackground bool) string {
if !IsANSIEnabled() || getSupportLevel() < Support256 {
return text
}
if colorCode < 0 || colorCode > 255 {
return text
}
prefix := ANSI256FgPrefix
if isBackground {
prefix = ANSI256BgPrefix
}
code := string(prefix) + strconv.Itoa(colorCode) + ANSISuffix
return code + text + string(ANSIReset)
}
// ColorizeRGB раскрашивает текст RGB цветом
func ColorizeRGB(text string, r, g, b int, isBackground bool) string {
if !IsANSIEnabled() || getSupportLevel() < SupportTrueColor {
return text
}
if r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 {
return text
}
prefix := ANSIRGBFgPrefix
if isBackground {
prefix = ANSIRGBBgPrefix
}
code := string(prefix) + strconv.Itoa(r) + ";" + strconv.Itoa(g) + ";" + strconv.Itoa(b) + ANSISuffix
return code + text + string(ANSIReset)
}
// ClearScreen очищает экран и перемещает курсор в домашнюю позицию
func ClearScreen() {
if IsANSIEnabled() {
fmt.Print(string(ANSIClearScreen) + string(ANSICursorHome))
} else {
// Если ANSI не поддерживается, просто выводим несколько пустых строк
fmt.Print("\n\n\n\n\n\n\n\n\n\n")
}
}
// ClearLine очищает текущую строку
func ClearLine() {
if IsANSIEnabled() {
fmt.Print(string(ANSIClearLine))
}
}
// MoveCursor перемещает курсор в указанную позицию
func MoveCursor(row, col int) {
if IsANSIEnabled() {
fmt.Printf("\033[%d;%dH", row, col)
}
}
// MoveCursorUp перемещает курсор вверх на n позиций
func MoveCursorUp(n int) {
if IsANSIEnabled() && n > 0 {
fmt.Printf("\033[%dA", n)
}
}
// MoveCursorDown перемещает курсор вниз на n позиций
func MoveCursorDown(n int) {
if IsANSIEnabled() && n > 0 {
fmt.Printf("\033[%dB", n)
}
}
// MoveCursorForward перемещает курсор вперёд на n позиций
func MoveCursorForward(n int) {
if IsANSIEnabled() && n > 0 {
fmt.Printf("\033[%dC", n)
}
}
// MoveCursorBack перемещает курсор назад на n позиций
func MoveCursorBack(n int) {
if IsANSIEnabled() && n > 0 {
fmt.Printf("\033[%dD", n)
}
}
// SaveCursorPosition сохраняет текущую позицию курсора
func SaveCursorPosition() {
if IsANSIEnabled() {
fmt.Print(string(ANSISaveCursor))
}
}
// RestoreCursorPosition восстанавливает сохранённую позицию курсора
func RestoreCursorPosition() {
if IsANSIEnabled() {
fmt.Print(string(ANSIRestoreCursor))
}
}
// HideCursor скрывает курсор
func HideCursor() {
if IsANSIEnabled() {
fmt.Print(string(ANSIHideCursor))
}
}
// ShowCursor показывает курсор
func ShowCursor() {
if IsANSIEnabled() {
fmt.Print(string(ANSIShowCursor))
}
}
// SetTitle устанавливает заголовок терминала
func SetTitle(title string) {
if IsANSIEnabled() {
fmt.Printf("\033]0;%s\007", title)
}
}
// GetCursorPosition возвращает текущую позицию курсора
// Возвращает (row, col, error)
func GetCursorPosition() (int, int, error) {
if !IsANSIEnabled() {
return 0, 0, fmt.Errorf("ANSI not supported")
}
fmt.Print("\033[6n")
var row, col int
_, err := fmt.Scanf("\033[%d;%dR", &row, &col)
return row, col, err
}
// PrintProgressBar выводит прогресс-бар с указанным процентом
func PrintProgressBar(percentage float64, width int, color ANSICode) {
if percentage < 0 {
percentage = 0
}
if percentage > 100 {
percentage = 100
}
filled := int(float64(width) * percentage / 100.0)
empty := width - filled
bar := "["
if filled > 0 {
bar += strings.Repeat("=", filled-1)
if percentage < 100 {
bar += ">"
} else {
bar += "="
}
}
if empty > 0 {
bar += strings.Repeat(" ", empty)
}
bar += "]"
coloredBar := ColorizeText(bar, color)
fmt.Printf("\r%s %.1f%%", coloredBar, percentage)
}
// PrintTable выводит данные в виде таблицы с ANSI-форматированием
func PrintTable(headers []string, rows [][]string, borderColor ANSICode) {
if len(rows) == 0 {
return
}
// Вычисляем ширину колонок
colWidths := make([]int, len(headers))
for i, header := range headers {
colWidths[i] = len(header)
}
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) && len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
// Выводим разделитель
printTableBorder(colWidths, borderColor)
// Выводим заголовки
fmt.Print(ColorizeText("|", borderColor))
for i, header := range headers {
padded := fmt.Sprintf(" %-*s ", colWidths[i], header)
fmt.Print(ColorizeText(padded, ANSIFgBrightCyan, ANSIBold))
fmt.Print(ColorizeText("|", borderColor))
}
fmt.Println()
// Выводим разделитель
printTableBorder(colWidths, borderColor)
// Выводим строки данных
for _, row := range rows {
fmt.Print(ColorizeText("|", borderColor))
for i, cell := range row {
if i >= len(colWidths) {
break
}
padded := fmt.Sprintf(" %-*s ", colWidths[i], cell)
fmt.Print(padded)
fmt.Print(ColorizeText("|", borderColor))
}
fmt.Println()
}
// Выводим нижнюю границу
printTableBorder(colWidths, borderColor)
}
// printTableBorder выводит границу таблицы
func printTableBorder(colWidths []int, borderColor ANSICode) {
fmt.Print(ColorizeText("+", borderColor))
for _, width := range colWidths {
fmt.Print(ColorizeText(strings.Repeat("-", width+2), borderColor))
fmt.Print(ColorizeText("+", borderColor))
}
fmt.Println()
}
// FadeText создаёт эффект затухания текста (градиент)
func FadeText(text string, startColor, endColor ANSICode) string {
if !IsANSIEnabled() || len(text) == 0 {
return text
}
runes := []rune(text)
result := strings.Builder{}
for i, r := range runes {
// Рассчитываем прогресс для градиента
progress := float64(i) / float64(len(runes)-1)
_ = progress // Используем progress для будущего расширения функционала
// Для простоты используем начальный цвет на всём протяжении
// В будущем здесь можно реализовать плавный переход между цветами
if i < len(runes)/2 {
result.WriteString(string(startColor))
} else {
result.WriteString(string(endColor))
}
result.WriteRune(r)
}
result.WriteString(string(ANSIReset))
return result.String()
}
// BlinkText создаёт мигающий текст
func BlinkText(text string) string {
return ApplyCode(text, ANSIBlink)
}
// ReverseText инвертирует цвета текста
func ReverseText(text string) string {
return ApplyCode(text, ANSIReverse)
}
// UnderlineText подчёркивает текст
func UnderlineText(text string) string {
return ApplyCode(text, ANSIUnderline)
}
// BoldText делает текст жирным
func BoldText(text string) string {
return ApplyCode(text, ANSIBold)
}
// ItalicText делает текст курсивом
func ItalicText(text string) string {
return ApplyCode(text, ANSIItalic)
}
// StrikeText зачёркивает текст
func StrikeText(text string) string {
return ApplyCode(text, ANSIStrike)
}
// GetANSIColorByName возвращает ANSI код по имени цвета
func GetANSIColorByName(colorName string) ANSICode {
colorMap := map[string]ANSICode{
"black": ANSIFgBlack,
"red": ANSIFgRed,
"green": ANSIFgGreen,
"yellow": ANSIFgYellow,
"blue": ANSIFgBlue,
"magenta": ANSIFgMagenta,
"cyan": ANSIFgCyan,
"white": ANSIFgWhite,
"bright_black": ANSIFgBrightBlack,
"bright_red": ANSIFgBrightRed,
"bright_green": ANSIFgBrightGreen,
"bright_yellow": ANSIFgBrightYellow,
"bright_blue": ANSIFgBrightBlue,
"bright_magenta": ANSIFgBrightMagenta,
"bright_cyan": ANSIFgBrightCyan,
"bright_white": ANSIFgBrightWhite,
"deepskyblue": ANSIFgDeepSkyBlue,
"#00bfff": ANSIFgDeepSkyBlue,
}
if color, ok := colorMap[strings.ToLower(colorName)]; ok {
return color
}
return ANSIFgWhite
}
// GetANSIBgColorByName возвращает ANSI код фона по имени цвета
func GetANSIBgColorByName(colorName string) ANSICode {
colorMap := map[string]ANSICode{
"black": ANSIBgBlack,
"red": ANSIBgRed,
"green": ANSIBgGreen,
"yellow": ANSIBgYellow,
"blue": ANSIBgBlue,
"magenta": ANSIBgMagenta,
"cyan": ANSIBgCyan,
"white": ANSIBgWhite,
"bright_black": ANSIBgBrightBlack,
"bright_red": ANSIBgBrightRed,
"bright_green": ANSIBgBrightGreen,
"bright_yellow": ANSIBgBrightYellow,
"bright_blue": ANSIBgBrightBlue,
"bright_magenta": ANSIBgBrightMagenta,
"bright_cyan": ANSIBgBrightCyan,
"bright_white": ANSIBgBrightWhite,
}
if color, ok := colorMap[strings.ToLower(colorName)]; ok {
return color
}
return ANSIBgBlack
}
// StripANSI удаляет все ANSI коды из строки
func StripANSI(text string) string {
result := strings.Builder{}
inEscape := false
for i := 0; i < len(text); i++ {
if text[i] == '\033' {
inEscape = true
continue
}
if inEscape {
if (text[i] >= 'A' && text[i] <= 'Z') || (text[i] >= 'a' && text[i] <= 'z') {
inEscape = false
}
continue
}
result.WriteByte(text[i])
}
return result.String()
}
// MeasureString возвращает видимую длину строки без ANSI кодов
func MeasureString(text string) int {
return len(StripANSI(text))
}
// CenterText центрирует текст с учётом ANSI кодов
func CenterText(text string, width int) string {
strippedLen := MeasureString(text)
if strippedLen >= width {
return text
}
padding := (width - strippedLen) / 2
return strings.Repeat(" ", padding) + text + strings.Repeat(" ", width-padding-strippedLen)
}
// RightAlign выравнивает текст по правому краю
func RightAlign(text string, width int) string {
strippedLen := MeasureString(text)
if strippedLen >= width {
return text
}
padding := width - strippedLen
return strings.Repeat(" ", padding) + text
}
// LeftAlign выравнивает текст по левому краю
func LeftAlign(text string, width int) string {
strippedLen := MeasureString(text)
if strippedLen >= width {
return text
}
padding := width - strippedLen
return text + strings.Repeat(" ", padding)
}
// PrintColoredBox выводит цветной прямоугольник
func PrintColoredBox(width, height int, borderColor, fillColor ANSICode) {
if !IsANSIEnabled() {
return
}
// Верхняя граница
fmt.Print(ColorizeText("+"+strings.Repeat("-", width-2)+"+", borderColor))
fmt.Println()
// Заполнение
for i := 0; i < height-2; i++ {
fmt.Print(ColorizeText("|", borderColor))
fmt.Print(ColorizeText(strings.Repeat(" ", width-2), fillColor))
fmt.Print(ColorizeText("|", borderColor))
fmt.Println()
}
// Нижняя граница
fmt.Print(ColorizeText("+"+strings.Repeat("-", width-2)+"+", borderColor))
fmt.Println()
}
// GetSupportInfo возвращает информацию о поддержке ANSI
func GetSupportInfo() map[string]interface{} {
info := make(map[string]interface{})
info["enabled"] = IsANSIEnabled()
info["support_level"] = getSupportLevel().String()
info["os"] = runtime.GOOS
info["term"] = termEnv
info["colorterm"] = colorTermEnv
return info
}
// String возвращает строковое представление уровня поддержки ANSI
func (l ANSISupportLevel) String() string {
switch l {
case SupportNone:
return "none"
case SupportBasic:
return "basic"
case Support256:
return "256"
case SupportTrueColor:
return "truecolor"
default:
return "unknown"
}
}
// WrapWithColor оборачивает текст в цвет с возможностью вложенности
type ColorWrapper struct {
codes []ANSICode
}
// NewColorWrapper создаёт новый обёртку для цвета
func NewColorWrapper(codes ...ANSICode) *ColorWrapper {
return &ColorWrapper{codes: codes}
}
// Wrap оборачивает текст в цвет
func (cw *ColorWrapper) Wrap(text string) string {
if !IsANSIEnabled() {
return text
}
var builder strings.Builder
for _, code := range cw.codes {
builder.WriteString(string(code))
}
builder.WriteString(text)
builder.WriteString(string(ANSIReset))
return builder.String()
}
// AddCode добавляет ANSI код в обёртку
func (cw *ColorWrapper) AddCode(code ANSICode) {
cw.codes = append(cw.codes, code)
}
// Clear очищает все ANSI коды
func (cw *ColorWrapper) Clear() {
cw.codes = nil
}
// GradientColor создаёт градиент между двумя цветами
type GradientColor struct {
startColor ANSICode
endColor ANSICode
}
// NewGradient создаёт новый градиент
func NewGradient(start, end ANSICode) *GradientColor {
return &GradientColor{
startColor: start,
endColor: end,
}
}
// Apply применяет градиент к тексту
func (g *GradientColor) Apply(text string) string {
if !IsANSIEnabled() || len(text) == 0 {
return text
}
runes := []rune(text)
result := strings.Builder{}
for i, r := range runes {
// Рассчитываем прогресс для плавного перехода
ratio := float64(i) / float64(len(runes)-1)
// Простая интерполяция - используем разные цвета для разных частей
if ratio < 0.33 {
result.WriteString(string(g.startColor))
} else if ratio < 0.66 {
result.WriteString(string(ANSIReset))
result.WriteString(string(ANSIFgGreen))
} else {
result.WriteString(string(g.endColor))
}
result.WriteRune(r)
}
result.WriteString(string(ANSIReset))
return result.String()
}
// PrintDeepSkyBlueAll выводит текст цветом #00bfff с автоматическим сбросом после каждой строки
func PrintDeepSkyBlueAll(text string) {
if IsANSIEnabled() {
fmt.Print(string(ANSIFgDeepSkyBlue) + text + string(ANSIReset))
} else {
fmt.Print(text)
}
}
// PrintlnDeepSkyBlueAll выводит строку цветом #00bfff с переводом строки и автоматическим сбросом
func PrintlnDeepSkyBlueAll(text string) {
if IsANSIEnabled() {
fmt.Println(string(ANSIFgDeepSkyBlue) + text + string(ANSIReset))
} else {
fmt.Println(text)
}
}
// FprintfDeepSkyBlue форматированный вывод в io.Writer цветом #00bfff
func FprintfDeepSkyBlue(w io.Writer, format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
if IsANSIEnabled() {
fmt.Fprint(w, string(ANSIFgDeepSkyBlue)+msg+string(ANSIReset))
} else {
fmt.Fprint(w, msg)
}
}
// InitDeepSkyBlueMode включает режим постоянного цвета #00bfff
func InitDeepSkyBlueMode() {
if IsANSIEnabled() {
fmt.Print(string(ANSIFgDeepSkyBlue))
}
}