1095 lines
33 KiB
Go
1095 lines
33 KiB
Go
// Файл: 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))
|
||
}
|
||
}
|