mirror of
https://github.com/yuaotian/go-cursor-help.git
synced 2026-04-28 13:20:50 +00:00
refactor: streamline configuration management and enhance UI interactions
- Updated go.mod and go.sum to include necessary dependencies. - Refactored README.md for clearer installation instructions and improved formatting. - Enhanced main.go with better error handling and user feedback during execution. - Improved configuration management in config.go, ensuring atomic writes and better error handling. - Updated language support in lang.go for clearer user messages. - Enhanced process management in manager.go to ensure more reliable process termination. - Improved UI display methods for better user experience. - Removed outdated test file generator_test.go to clean up the codebase. - Updated install.ps1 script for better output formatting and error handling.
This commit is contained in:
@@ -32,10 +32,7 @@ func NewManager(username string) (*Manager, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get config path: %w", err)
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
configPath: configPath,
|
||||
}, nil
|
||||
return &Manager{configPath: configPath}, nil
|
||||
}
|
||||
|
||||
// ReadConfig reads the existing configuration
|
||||
@@ -69,22 +66,23 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
// Set file permissions
|
||||
if err := os.Chmod(m.configPath, 0666); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to set file permissions: %w", err)
|
||||
// Prepare updated configuration
|
||||
updatedConfig := m.prepareUpdatedConfig(config)
|
||||
|
||||
// Write configuration
|
||||
if err := m.writeConfigFile(updatedConfig, readOnly); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read existing config to preserve other fields
|
||||
var originalFile map[string]interface{}
|
||||
originalFileContent, err := os.ReadFile(m.configPath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to read original file: %w", err)
|
||||
} else if err == nil {
|
||||
if err := json.Unmarshal(originalFileContent, &originalFile); err != nil {
|
||||
return fmt.Errorf("failed to parse original file: %w", err)
|
||||
}
|
||||
} else {
|
||||
originalFile = make(map[string]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareUpdatedConfig merges existing config with updates
|
||||
func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} {
|
||||
// Read existing config
|
||||
originalFile := make(map[string]interface{})
|
||||
if data, err := os.ReadFile(m.configPath); err == nil {
|
||||
json.Unmarshal(data, &originalFile)
|
||||
}
|
||||
|
||||
// Update fields
|
||||
@@ -95,15 +93,20 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
|
||||
originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339)
|
||||
originalFile["version"] = "1.0.1"
|
||||
|
||||
return originalFile
|
||||
}
|
||||
|
||||
// writeConfigFile handles the atomic write of the config file
|
||||
func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error {
|
||||
// Marshal with indentation
|
||||
newFileContent, err := json.MarshalIndent(originalFile, "", " ")
|
||||
content, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal config: %w", err)
|
||||
}
|
||||
|
||||
// Write to temporary file
|
||||
tmpPath := m.configPath + ".tmp"
|
||||
if err := os.WriteFile(tmpPath, newFileContent, 0666); err != nil {
|
||||
if err := os.WriteFile(tmpPath, content, 0666); err != nil {
|
||||
return fmt.Errorf("failed to write temporary file: %w", err)
|
||||
}
|
||||
|
||||
@@ -126,8 +129,8 @@ func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error {
|
||||
|
||||
// Sync directory
|
||||
if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil {
|
||||
defer dir.Close()
|
||||
dir.Sync()
|
||||
dir.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -7,34 +7,43 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Language represents a supported language
|
||||
// Language represents a supported language code
|
||||
type Language string
|
||||
|
||||
const (
|
||||
// CN represents Chinese language
|
||||
CN Language = "cn"
|
||||
// EN represents English language
|
||||
// EN represents English language
|
||||
EN Language = "en"
|
||||
)
|
||||
|
||||
// TextResource contains all translatable text resources
|
||||
type TextResource struct {
|
||||
SuccessMessage string
|
||||
RestartMessage string
|
||||
ReadingConfig string
|
||||
GeneratingIds string
|
||||
PressEnterToExit string
|
||||
ErrorPrefix string
|
||||
PrivilegeError string
|
||||
// Success messages
|
||||
SuccessMessage string
|
||||
RestartMessage string
|
||||
|
||||
// Progress messages
|
||||
ReadingConfig string
|
||||
GeneratingIds string
|
||||
CheckingProcesses string
|
||||
ClosingProcesses string
|
||||
ProcessesClosed string
|
||||
PleaseWait string
|
||||
|
||||
// Error messages
|
||||
ErrorPrefix string
|
||||
PrivilegeError string
|
||||
|
||||
// Instructions
|
||||
RunAsAdmin string
|
||||
RunWithSudo string
|
||||
SudoExample string
|
||||
ConfigLocation string
|
||||
CheckingProcesses string
|
||||
ClosingProcesses string
|
||||
ProcessesClosed string
|
||||
PleaseWait string
|
||||
PressEnterToExit string
|
||||
SetReadOnlyMessage string
|
||||
|
||||
// Info messages
|
||||
ConfigLocation string
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -68,28 +77,32 @@ func GetText() TextResource {
|
||||
|
||||
// detectLanguage detects the system language
|
||||
func detectLanguage() Language {
|
||||
// Check environment variables
|
||||
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
|
||||
if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") {
|
||||
return CN
|
||||
}
|
||||
// Check environment variables first
|
||||
if isChineseEnvVar() {
|
||||
return CN
|
||||
}
|
||||
|
||||
// Check Windows language settings
|
||||
// Then check OS-specific locale
|
||||
if isWindows() {
|
||||
if isWindowsChineseLocale() {
|
||||
return CN
|
||||
}
|
||||
} else {
|
||||
// Check Unix locale
|
||||
if isUnixChineseLocale() {
|
||||
return CN
|
||||
}
|
||||
} else if isUnixChineseLocale() {
|
||||
return CN
|
||||
}
|
||||
|
||||
return EN
|
||||
}
|
||||
|
||||
func isChineseEnvVar() bool {
|
||||
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} {
|
||||
if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isWindows() bool {
|
||||
return os.Getenv("OS") == "Windows_NT"
|
||||
}
|
||||
@@ -118,39 +131,57 @@ func isUnixChineseLocale() bool {
|
||||
// texts contains all translations
|
||||
var texts = map[Language]TextResource{
|
||||
CN: {
|
||||
SuccessMessage: "[√] 配置文件已成功更新!",
|
||||
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
|
||||
ReadingConfig: "正在读取配置文件...",
|
||||
GeneratingIds: "正在生成新的标识符...",
|
||||
PressEnterToExit: "按回车键退出程序...",
|
||||
ErrorPrefix: "程序发生严重错误: %v",
|
||||
PrivilegeError: "\n[!] 错误:需要管理员权限",
|
||||
// Success messages
|
||||
SuccessMessage: "[√] 配置文件已成功更新!",
|
||||
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效",
|
||||
|
||||
// Progress messages
|
||||
ReadingConfig: "正在读取配置文件...",
|
||||
GeneratingIds: "正在生成新的标识符...",
|
||||
CheckingProcesses: "正在检查运行中的 Cursor 实例...",
|
||||
ClosingProcesses: "正在关闭 Cursor 实例...",
|
||||
ProcessesClosed: "所有 Cursor 实例已关闭",
|
||||
PleaseWait: "请稍候...",
|
||||
|
||||
// Error messages
|
||||
ErrorPrefix: "程序发生严重错误: %v",
|
||||
PrivilegeError: "\n[!] 错误:需要管理员权限",
|
||||
|
||||
// Instructions
|
||||
RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」",
|
||||
RunWithSudo: "请使用 sudo 命令运行此程序",
|
||||
SudoExample: "示例: sudo %s",
|
||||
ConfigLocation: "配置文件位置:",
|
||||
CheckingProcesses: "正在检查运行中的 Cursor 实例...",
|
||||
ClosingProcesses: "正在关闭 Cursor 实例...",
|
||||
ProcessesClosed: "所有 Cursor 实例已关闭",
|
||||
PleaseWait: "请稍候...",
|
||||
PressEnterToExit: "按回车键退出程序...",
|
||||
SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题",
|
||||
|
||||
// Info messages
|
||||
ConfigLocation: "配置文件位置:",
|
||||
},
|
||||
EN: {
|
||||
SuccessMessage: "[√] Configuration file updated successfully!",
|
||||
RestartMessage: "[!] Please restart Cursor manually for changes to take effect",
|
||||
ReadingConfig: "Reading configuration file...",
|
||||
GeneratingIds: "Generating new identifiers...",
|
||||
PressEnterToExit: "Press Enter to exit...",
|
||||
ErrorPrefix: "Program encountered a serious error: %v",
|
||||
PrivilegeError: "\n[!] Error: Administrator privileges required",
|
||||
// Success messages
|
||||
SuccessMessage: "[√] Configuration file updated successfully!",
|
||||
RestartMessage: "[!] Please restart Cursor manually for changes to take effect",
|
||||
|
||||
// Progress messages
|
||||
ReadingConfig: "Reading configuration file...",
|
||||
GeneratingIds: "Generating new identifiers...",
|
||||
CheckingProcesses: "Checking for running Cursor instances...",
|
||||
ClosingProcesses: "Closing Cursor instances...",
|
||||
ProcessesClosed: "All Cursor instances have been closed",
|
||||
PleaseWait: "Please wait...",
|
||||
|
||||
// Error messages
|
||||
ErrorPrefix: "Program encountered a serious error: %v",
|
||||
PrivilegeError: "\n[!] Error: Administrator privileges required",
|
||||
|
||||
// Instructions
|
||||
RunAsAdmin: "Please right-click and select 'Run as Administrator'",
|
||||
RunWithSudo: "Please run this program with sudo",
|
||||
SudoExample: "Example: sudo %s",
|
||||
ConfigLocation: "Config file location:",
|
||||
CheckingProcesses: "Checking for running Cursor instances...",
|
||||
ClosingProcesses: "Closing Cursor instances...",
|
||||
ProcessesClosed: "All Cursor instances have been closed",
|
||||
PleaseWait: "Please wait...",
|
||||
PressEnterToExit: "Press Enter to exit...",
|
||||
SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records",
|
||||
|
||||
// Info messages
|
||||
ConfigLocation: "Config file location:",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -12,22 +12,24 @@ import (
|
||||
|
||||
// Config holds process manager configuration
|
||||
type Config struct {
|
||||
MaxAttempts int
|
||||
RetryDelay time.Duration
|
||||
ProcessPatterns []string
|
||||
MaxAttempts int // Maximum number of attempts to kill processes
|
||||
RetryDelay time.Duration // Delay between retry attempts
|
||||
ProcessPatterns []string // Process names to look for
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configuration
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
MaxAttempts: 3,
|
||||
RetryDelay: time.Second,
|
||||
RetryDelay: 2 * time.Second,
|
||||
ProcessPatterns: []string{
|
||||
"Cursor.exe", // Windows
|
||||
"Cursor", // Linux/macOS binary
|
||||
"cursor", // Linux/macOS process
|
||||
"cursor-helper", // Helper process
|
||||
"cursor-id-modifier", // Our tool
|
||||
"Cursor.exe", // Windows executable
|
||||
"Cursor ", // Linux/macOS executable with space
|
||||
"cursor ", // Linux/macOS executable lowercase with space
|
||||
"cursor", // Linux/macOS executable lowercase
|
||||
"Cursor", // Linux/macOS executable
|
||||
"*cursor*", // Any process containing cursor
|
||||
"*Cursor*", // Any process containing Cursor
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -38,7 +40,7 @@ type Manager struct {
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// NewManager creates a new process manager
|
||||
// NewManager creates a new process manager with optional config and logger
|
||||
func NewManager(config *Config, log *logrus.Logger) *Manager {
|
||||
if config == nil {
|
||||
config = DefaultConfig()
|
||||
@@ -52,7 +54,7 @@ func NewManager(config *Config, log *logrus.Logger) *Manager {
|
||||
}
|
||||
}
|
||||
|
||||
// IsCursorRunning checks if any Cursor process is running
|
||||
// IsCursorRunning checks if any Cursor process is currently running
|
||||
func (m *Manager) IsCursorRunning() bool {
|
||||
processes, err := m.getCursorProcesses()
|
||||
if err != nil {
|
||||
@@ -62,7 +64,7 @@ func (m *Manager) IsCursorRunning() bool {
|
||||
return len(processes) > 0
|
||||
}
|
||||
|
||||
// KillCursorProcesses attempts to kill all Cursor processes
|
||||
// KillCursorProcesses attempts to kill all running Cursor processes
|
||||
func (m *Manager) KillCursorProcesses() error {
|
||||
for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ {
|
||||
processes, err := m.getCursorProcesses()
|
||||
@@ -74,34 +76,34 @@ func (m *Manager) KillCursorProcesses() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, proc := range processes {
|
||||
if err := m.killProcess(proc); err != nil {
|
||||
m.log.Warnf("Failed to kill process %s: %v", proc, err)
|
||||
// Try graceful shutdown first on Windows
|
||||
if runtime.GOOS == "windows" {
|
||||
for _, pid := range processes {
|
||||
exec.Command("taskkill", "/PID", pid).Run()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(m.config.RetryDelay)
|
||||
}
|
||||
// Force kill remaining processes
|
||||
remainingProcesses, _ := m.getCursorProcesses()
|
||||
for _, pid := range remainingProcesses {
|
||||
m.killProcess(pid)
|
||||
}
|
||||
|
||||
if m.IsCursorRunning() {
|
||||
return fmt.Errorf("failed to kill all Cursor processes after %d attempts", m.config.MaxAttempts)
|
||||
time.Sleep(m.config.RetryDelay)
|
||||
|
||||
if processes, _ := m.getCursorProcesses(); len(processes) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCursorProcesses returns PIDs of running Cursor processes
|
||||
func (m *Manager) getCursorProcesses() ([]string, error) {
|
||||
var cmd *exec.Cmd
|
||||
var processes []string
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("tasklist", "/FO", "CSV", "/NH")
|
||||
case "darwin":
|
||||
cmd = exec.Command("ps", "-ax")
|
||||
case "linux":
|
||||
cmd = exec.Command("ps", "-A")
|
||||
default:
|
||||
cmd := m.getProcessListCommand()
|
||||
if cmd == nil {
|
||||
return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
@@ -110,32 +112,80 @@ func (m *Manager) getCursorProcesses() ([]string, error) {
|
||||
return nil, fmt.Errorf("failed to execute command: %w", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
for _, pattern := range m.config.ProcessPatterns {
|
||||
if strings.Contains(strings.ToLower(line), strings.ToLower(pattern)) {
|
||||
// Extract PID based on OS
|
||||
pid := m.extractPID(line)
|
||||
if pid != "" {
|
||||
processes = append(processes, pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return processes, nil
|
||||
return m.parseProcessList(string(output)), nil
|
||||
}
|
||||
|
||||
// getProcessListCommand returns the appropriate command to list processes based on OS
|
||||
func (m *Manager) getProcessListCommand() *exec.Cmd {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return exec.Command("tasklist", "/FO", "CSV", "/NH")
|
||||
case "darwin":
|
||||
return exec.Command("ps", "-ax")
|
||||
case "linux":
|
||||
return exec.Command("ps", "-A")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseProcessList extracts Cursor process PIDs from process list output
|
||||
func (m *Manager) parseProcessList(output string) []string {
|
||||
var processes []string
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
lowerLine := strings.ToLower(line)
|
||||
|
||||
if m.isOwnProcess(lowerLine) {
|
||||
continue
|
||||
}
|
||||
|
||||
if pid := m.findCursorProcess(line, lowerLine); pid != "" {
|
||||
processes = append(processes, pid)
|
||||
}
|
||||
}
|
||||
return processes
|
||||
}
|
||||
|
||||
// isOwnProcess checks if the process belongs to this application
|
||||
func (m *Manager) isOwnProcess(line string) bool {
|
||||
return strings.Contains(line, "cursor-id-modifier") ||
|
||||
strings.Contains(line, "cursor-helper")
|
||||
}
|
||||
|
||||
// findCursorProcess checks if a process line matches Cursor patterns and returns its PID
|
||||
func (m *Manager) findCursorProcess(line, lowerLine string) string {
|
||||
for _, pattern := range m.config.ProcessPatterns {
|
||||
if m.matchPattern(lowerLine, strings.ToLower(pattern)) {
|
||||
return m.extractPID(line)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// matchPattern checks if a line matches a pattern, supporting wildcards
|
||||
func (m *Manager) matchPattern(line, pattern string) bool {
|
||||
switch {
|
||||
case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"):
|
||||
search := pattern[1 : len(pattern)-1]
|
||||
return strings.Contains(line, search)
|
||||
case strings.HasPrefix(pattern, "*"):
|
||||
return strings.HasSuffix(line, pattern[1:])
|
||||
case strings.HasSuffix(pattern, "*"):
|
||||
return strings.HasPrefix(line, pattern[:len(pattern)-1])
|
||||
default:
|
||||
return line == pattern
|
||||
}
|
||||
}
|
||||
|
||||
// extractPID extracts process ID from a process list line based on OS format
|
||||
func (m *Manager) extractPID(line string) string {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// Windows CSV format: "ImageName","PID",...
|
||||
parts := strings.Split(line, ",")
|
||||
if len(parts) >= 2 {
|
||||
return strings.Trim(parts[1], "\"")
|
||||
}
|
||||
case "darwin", "linux":
|
||||
// Unix format: PID TTY TIME CMD
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 1 {
|
||||
return parts[0]
|
||||
@@ -144,17 +194,23 @@ func (m *Manager) extractPID(line string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// killProcess forcefully terminates a process by PID
|
||||
func (m *Manager) killProcess(pid string) error {
|
||||
var cmd *exec.Cmd
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("taskkill", "/F", "/PID", pid)
|
||||
case "darwin", "linux":
|
||||
cmd = exec.Command("kill", "-9", pid)
|
||||
default:
|
||||
cmd := m.getKillCommand(pid)
|
||||
if cmd == nil {
|
||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// getKillCommand returns the appropriate command to kill a process based on OS
|
||||
func (m *Manager) getKillCommand(pid string) *exec.Cmd {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return exec.Command("taskkill", "/F", "/PID", pid)
|
||||
case "darwin", "linux":
|
||||
return exec.Command("kill", "-9", pid)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,37 @@ import (
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// Display handles UI display operations
|
||||
// Display handles UI operations for terminal output
|
||||
type Display struct {
|
||||
spinner *Spinner
|
||||
}
|
||||
|
||||
// NewDisplay creates a new display handler
|
||||
// NewDisplay creates a new display instance with an optional spinner
|
||||
func NewDisplay(spinner *Spinner) *Display {
|
||||
if spinner == nil {
|
||||
spinner = NewSpinner(nil)
|
||||
}
|
||||
return &Display{
|
||||
spinner: spinner,
|
||||
}
|
||||
return &Display{spinner: spinner}
|
||||
}
|
||||
|
||||
// ShowProgress shows a progress message with spinner
|
||||
// Terminal Operations
|
||||
|
||||
// ClearScreen clears the terminal screen based on OS
|
||||
func (d *Display) ClearScreen() error {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", "cls")
|
||||
default:
|
||||
cmd = exec.Command("clear")
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Progress Indicator
|
||||
|
||||
// ShowProgress displays a progress message with a spinner
|
||||
func (d *Display) ShowProgress(message string) {
|
||||
d.spinner.SetMessage(message)
|
||||
d.spinner.Start()
|
||||
@@ -36,66 +51,44 @@ func (d *Display) StopProgress() {
|
||||
d.spinner.Stop()
|
||||
}
|
||||
|
||||
// ClearScreen clears the terminal screen
|
||||
func (d *Display) ClearScreen() error {
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.Command("cmd", "/c", "cls")
|
||||
} else {
|
||||
cmd = exec.Command("clear")
|
||||
// Message Display
|
||||
|
||||
// ShowSuccess displays success messages in green
|
||||
func (d *Display) ShowSuccess(messages ...string) {
|
||||
green := color.New(color.FgGreen)
|
||||
for _, msg := range messages {
|
||||
green.Println(msg)
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// ShowProcessStatus shows the current process status
|
||||
func (d *Display) ShowProcessStatus(message string) {
|
||||
fmt.Printf("\r%s", strings.Repeat(" ", 80)) // Clear line
|
||||
fmt.Printf("\r%s", color.CyanString("⚡ "+message))
|
||||
// ShowInfo displays an info message in cyan
|
||||
func (d *Display) ShowInfo(message string) {
|
||||
cyan := color.New(color.FgCyan)
|
||||
cyan.Println(message)
|
||||
}
|
||||
|
||||
// ShowPrivilegeError shows the privilege error message
|
||||
func (d *Display) ShowPrivilegeError(errorMsg, adminMsg, sudoMsg, sudoExample string) {
|
||||
// ShowError displays an error message in red
|
||||
func (d *Display) ShowError(message string) {
|
||||
red := color.New(color.FgRed)
|
||||
red.Println(message)
|
||||
}
|
||||
|
||||
// ShowPrivilegeError displays privilege error messages with instructions
|
||||
func (d *Display) ShowPrivilegeError(messages ...string) {
|
||||
red := color.New(color.FgRed, color.Bold)
|
||||
yellow := color.New(color.FgYellow)
|
||||
|
||||
red.Println(errorMsg)
|
||||
if runtime.GOOS == "windows" {
|
||||
yellow.Println(adminMsg)
|
||||
} else {
|
||||
yellow.Printf("%s\n%s\n", sudoMsg, fmt.Sprintf(sudoExample, os.Args[0]))
|
||||
// Main error message
|
||||
red.Println(messages[0])
|
||||
fmt.Println()
|
||||
|
||||
// Additional instructions
|
||||
for _, msg := range messages[1:] {
|
||||
if strings.Contains(msg, "%s") {
|
||||
exe, _ := os.Executable()
|
||||
yellow.Printf(msg+"\n", exe)
|
||||
} else {
|
||||
yellow.Println(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ShowSuccess shows a success message
|
||||
func (d *Display) ShowSuccess(successMsg, restartMsg string) {
|
||||
green := color.New(color.FgGreen, color.Bold)
|
||||
yellow := color.New(color.FgYellow, color.Bold)
|
||||
|
||||
green.Printf("\n%s\n", successMsg)
|
||||
yellow.Printf("%s\n", restartMsg)
|
||||
}
|
||||
|
||||
// ShowError shows an error message
|
||||
func (d *Display) ShowError(message string) {
|
||||
red := color.New(color.FgRed, color.Bold)
|
||||
red.Printf("\n%s\n", message)
|
||||
}
|
||||
|
||||
// ShowWarning shows a warning message
|
||||
func (d *Display) ShowWarning(message string) {
|
||||
yellow := color.New(color.FgYellow, color.Bold)
|
||||
yellow.Printf("\n%s\n", message)
|
||||
}
|
||||
|
||||
// ShowInfo shows an info message
|
||||
func (d *Display) ShowInfo(message string) {
|
||||
cyan := color.New(color.FgCyan)
|
||||
cyan.Printf("\n%s\n", message)
|
||||
}
|
||||
|
||||
// ShowPrompt shows a prompt message and waits for user input
|
||||
func (d *Display) ShowPrompt(message string) {
|
||||
fmt.Print(message)
|
||||
os.Stdout.Sync()
|
||||
}
|
||||
|
||||
@@ -5,15 +5,15 @@ import (
|
||||
)
|
||||
|
||||
const cyberpunkLogo = `
|
||||
______ ______ ______
|
||||
/ ____/_ __________ ___ _____/ __/ // / / /
|
||||
/ / / / / / ___/ _ \/ __ \/ ___/ /_/ // /_/ /
|
||||
/ /___/ /_/ / / / __/ /_/ (__ ) __/__ __/ /
|
||||
\____/\__,_/_/ \___/\____/____/_/ /_/ /_/
|
||||
|
||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
`
|
||||
|
||||
// ShowLogo displays the cyberpunk-style logo
|
||||
// ShowLogo displays the application logo
|
||||
func (d *Display) ShowLogo() {
|
||||
cyan := color.New(color.FgCyan, color.Bold)
|
||||
cyan.Println(cyberpunkLogo)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
// SpinnerConfig defines spinner configuration
|
||||
type SpinnerConfig struct {
|
||||
Frames []string
|
||||
Delay time.Duration
|
||||
Frames []string // Animation frames for the spinner
|
||||
Delay time.Duration // Delay between frame updates
|
||||
}
|
||||
|
||||
// DefaultSpinnerConfig returns the default spinner configuration
|
||||
@@ -43,6 +43,8 @@ func NewSpinner(config *SpinnerConfig) *Spinner {
|
||||
}
|
||||
}
|
||||
|
||||
// State management
|
||||
|
||||
// SetMessage sets the spinner message
|
||||
func (s *Spinner) SetMessage(message string) {
|
||||
s.mu.Lock()
|
||||
@@ -50,7 +52,16 @@ func (s *Spinner) SetMessage(message string) {
|
||||
s.message = message
|
||||
}
|
||||
|
||||
// Start starts the spinner animation
|
||||
// IsActive returns whether the spinner is currently active
|
||||
func (s *Spinner) IsActive() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.active
|
||||
}
|
||||
|
||||
// Control methods
|
||||
|
||||
// Start begins the spinner animation
|
||||
func (s *Spinner) Start() {
|
||||
s.mu.Lock()
|
||||
if s.active {
|
||||
@@ -63,7 +74,7 @@ func (s *Spinner) Start() {
|
||||
go s.run()
|
||||
}
|
||||
|
||||
// Stop stops the spinner animation
|
||||
// Stop halts the spinner animation
|
||||
func (s *Spinner) Stop() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -75,20 +86,21 @@ func (s *Spinner) Stop() {
|
||||
s.active = false
|
||||
close(s.stopCh)
|
||||
s.stopCh = make(chan struct{})
|
||||
fmt.Println()
|
||||
fmt.Print("\r") // Clear the spinner line
|
||||
}
|
||||
|
||||
// IsActive returns whether the spinner is currently active
|
||||
func (s *Spinner) IsActive() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.active
|
||||
}
|
||||
// Internal methods
|
||||
|
||||
func (s *Spinner) run() {
|
||||
ticker := time.NewTicker(s.config.Delay)
|
||||
defer ticker.Stop()
|
||||
|
||||
cyan := color.New(color.FgCyan, color.Bold)
|
||||
message := s.message
|
||||
|
||||
// Print initial state
|
||||
fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.stopCh:
|
||||
@@ -100,11 +112,11 @@ func (s *Spinner) run() {
|
||||
return
|
||||
}
|
||||
frame := s.config.Frames[s.current%len(s.config.Frames)]
|
||||
message := s.message
|
||||
s.current++
|
||||
s.mu.RUnlock()
|
||||
|
||||
fmt.Printf("\r%s %s", color.CyanString(frame), message)
|
||||
fmt.Printf("\r %s", cyan.Sprint(frame))
|
||||
fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user