zsvo/pkg/ui/pacman.go
itexpert228 ebf2b6e53e
bump version and pacman ui fixes
Made-with: Cursor
2026-03-15 17:30:06 +03:00

379 lines
8.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ui
import (
"fmt"
"os"
"strings"
"time"
)
// Colors for terminal output
const (
Reset = "\033[0m"
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Magenta = "\033[35m"
Cyan = "\033[36m"
White = "\033[37m"
Bold = "\033[1m"
)
// PacmanUI provides pacman-style interface
type PacmanUI struct {
quiet bool
noColor bool
termWidth int
}
// NewPacmanUI creates new UI instance
func NewPacmanUI(quiet bool) *PacmanUI {
return &PacmanUI{
quiet: quiet,
termWidth: getTerminalWidth(),
}
}
// DisableColor disables colored output
func (p *PacmanUI) DisableColor() {
p.noColor = true
}
// color returns color code or empty string if disabled
func (p *PacmanUI) color(c string) string {
if p.noColor || p.quiet {
return ""
}
return c
}
// PrintOperation prints operation header like pacman
func (p *PacmanUI) PrintOperation(op, target string) {
if p.quiet {
return
}
opColor := p.color(Cyan + Bold)
targetColor := p.color(Reset)
switch op {
case "resolving":
opColor = p.color(Yellow + Bold)
case "downloading":
opColor = p.color(Cyan + Bold)
case "building":
opColor = p.color(Yellow + Bold)
case "installing":
opColor = p.color(Green + Bold)
case "removing":
opColor = p.color(Red + Bold)
}
fmt.Printf("%s%s%s %s%s%s\n",
opColor, op, p.color(Reset),
targetColor, target, p.color(Reset))
}
// PrintProgress prints pacman-style progress line
func (p *PacmanUI) PrintProgress(current, total int, pkgName, action string) {
if p.quiet {
return
}
percent := float64(current) / float64(total) * 100
// Build progress bar
barWidth := 30
filled := int(float64(barWidth) * percent / 100)
empty := barWidth - filled
bar := strings.Repeat("#", filled) + strings.Repeat("-", empty)
color := p.color(Green)
if percent < 30 {
color = p.color(Red)
} else if percent < 70 {
color = p.color(Yellow)
}
reset := p.color(Reset)
// Clear line and print
fmt.Printf("\r\033[K[%s%s%s] %s%d/%d%s (%s%.0f%%%s) %s %s",
color, bar, reset,
p.color(White+Bold), current, total, reset,
p.color(Cyan), percent, reset,
pkgName, action)
if current == total {
fmt.Println() // New line on completion
}
}
// PrintDownloadProgress prints download progress with speed and ETA
func (p *PacmanUI) PrintDownloadProgress(pkgName string, downloaded, total int64, speed float64) {
if p.quiet {
return
}
percent := float64(downloaded) / float64(total) * 100
// Format sizes
downloadedStr := formatSize(downloaded)
totalStr := formatSize(total)
speedStr := formatSpeed(speed)
// Calculate ETA
eta := ""
if speed > 0 {
remaining := float64(total-downloaded) / speed
eta = formatDuration(time.Duration(remaining) * time.Second)
}
barWidth := 25
filled := int(float64(barWidth) * percent / 100)
empty := barWidth - filled
bar := strings.Repeat("#", filled) + strings.Repeat("-", empty)
color := p.color(Cyan)
reset := p.color(Reset)
fmt.Printf("\r\033[K %s %s[%s%s%s] %s%s/%s%s %s%s/s ETA: %s",
pkgName,
color, bar, reset,
p.color(White), downloadedStr, totalStr, reset,
p.color(Yellow), speedStr, reset,
eta)
if downloaded >= total {
fmt.Println()
}
}
// PrintSuccess prints success message
func (p *PacmanUI) PrintSuccess(msg string) {
if p.quiet {
return
}
fmt.Printf("%s✓%s %s\n", p.color(Green+Bold), p.color(Reset), msg)
}
// PrintError prints error message
func (p *PacmanUI) PrintError(msg string) {
fmt.Fprintf(os.Stderr, "%s✗%s %s\n", p.color(Red+Bold), p.color(Reset), msg)
}
// PrintWarning prints warning message
func (p *PacmanUI) PrintWarning(msg string) {
if p.quiet {
return
}
fmt.Printf("%s⚠%s %s\n", p.color(Yellow+Bold), p.color(Reset), msg)
}
// PrintInfo prints info message
func (p *PacmanUI) PrintInfo(msg string) {
if p.quiet {
return
}
fmt.Printf("%s%s %s\n", p.color(Blue), p.color(Reset), msg)
}
// PrintPackageList prints package list like pacman -Q
func (p *PacmanUI) PrintPackageList(packages []PackageInfo) {
if p.quiet {
return
}
maxNameLen := 0
for _, pkg := range packages {
if len(pkg.Name) > maxNameLen {
maxNameLen = len(pkg.Name)
}
}
for _, pkg := range packages {
fmt.Printf("%s%s%s %s%s%s\n",
p.color(Green+Bold), padRight(pkg.Name, maxNameLen+2), p.color(Reset),
p.color(Cyan), pkg.Version, p.color(Reset))
}
}
// PrintTransactionSummary prints transaction summary
func (p *PacmanUI) PrintTransactionSummary(toInstall, toRemove, toUpgrade []string) {
if p.quiet {
return
}
fmt.Println()
fmt.Printf("%s%sTransaction Summary:%s\n", p.color(Bold), p.color(White), p.color(Reset))
if len(toInstall) > 0 {
fmt.Printf("%sInstall:%s %d packages\n", p.color(Green), p.color(Reset), len(toInstall))
for _, pkg := range toInstall {
fmt.Printf(" %s+%s %s\n", p.color(Green), p.color(Reset), pkg)
}
}
if len(toRemove) > 0 {
fmt.Printf("%sRemove:%s %d packages\n", p.color(Red), p.color(Reset), len(toRemove))
for _, pkg := range toRemove {
fmt.Printf(" %s-%s %s\n", p.color(Red), p.color(Reset), pkg)
}
}
if len(toUpgrade) > 0 {
fmt.Printf("%sUpgrade:%s %d packages\n", p.color(Yellow), p.color(Reset), len(toUpgrade))
for _, pkg := range toUpgrade {
fmt.Printf(" %s*%s %s\n", p.color(Yellow), p.color(Reset), pkg)
}
}
fmt.Println()
}
// MultiProgress tracks multiple parallel operations
type MultiProgress struct {
ui *PacmanUI
items []ProgressItem
mu chan struct{} // semaphore for thread-safe updates
}
// ProgressItem represents single progress item
type ProgressItem struct {
Name string
Progress float64
Status string
Speed string
Completed bool
}
// NewMultiProgress creates multi-item progress tracker
func (p *PacmanUI) NewMultiProgress() *MultiProgress {
return &MultiProgress{
ui: p,
mu: make(chan struct{}, 1),
items: []ProgressItem{},
}
}
// AddItem adds progress item
func (mp *MultiProgress) AddItem(name string) int {
mp.mu <- struct{}{}
defer func() { <-mp.mu }()
idx := len(mp.items)
mp.items = append(mp.items, ProgressItem{Name: name})
return idx
}
// Update updates progress item
func (mp *MultiProgress) Update(idx int, progress float64, status, speed string) {
if idx < 0 || idx >= len(mp.items) {
return
}
mp.mu <- struct{}{}
defer func() { <-mp.mu }()
mp.items[idx].Progress = progress
mp.items[idx].Status = status
mp.items[idx].Speed = speed
mp.items[idx].Completed = progress >= 100
mp.redraw()
}
// redraw refreshes display
func (mp *MultiProgress) redraw() {
if mp.ui.quiet {
return
}
// Clear previous lines
for i := 0; i < len(mp.items)+2; i++ {
fmt.Printf("\033[A\033[K")
}
// Print header
fmt.Printf("%s%sProgress:%s\n", mp.ui.color(Bold), mp.ui.color(White), mp.ui.color(Reset))
// Print items
for _, item := range mp.items {
bar := mp.renderBar(item.Progress, 20)
statusColor := mp.ui.color(Yellow)
if item.Completed {
statusColor = mp.ui.color(Green)
} else if item.Progress < 20 {
statusColor = mp.ui.color(Red)
}
speedStr := ""
if item.Speed != "" {
speedStr = fmt.Sprintf(" %s%s%s", mp.ui.color(Cyan), item.Speed, mp.ui.color(Reset))
}
fmt.Printf(" %s %s%s%s%s%s\n",
bar,
padRight(item.Name, 25),
statusColor, item.Status, mp.ui.color(Reset),
speedStr)
}
}
// renderBar renders progress bar
func (mp *MultiProgress) renderBar(percent float64, width int) string {
filled := int(float64(width) * percent / 100)
empty := width - filled
bar := strings.Repeat("█", filled) + strings.Repeat("░", empty)
color := mp.ui.color(Green)
if percent < 30 {
color = mp.ui.color(Red)
} else if percent < 70 {
color = mp.ui.color(Yellow)
}
return fmt.Sprintf("%s[%s]%s %3.0f%%", color, bar, mp.ui.color(Reset), percent)
}
// PackageInfo for list display
type PackageInfo struct {
Name string
Version string
Desc string
}
// Helper functions
func formatSize(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++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
func formatSpeed(bytesPerSec float64) string {
return formatSize(int64(bytesPerSec))
}
func padRight(s string, length int) string {
if len(s) >= length {
return s
}
return s + strings.Repeat(" ", length-len(s))
}
func getTerminalWidth() int {
// Default to 80 if can't determine
return 80
}