diff --git a/cmd/analyze/format.go b/cmd/analyze/format.go index 7686745..2350879 100644 --- a/cmd/analyze/format.go +++ b/cmd/analyze/format.go @@ -93,15 +93,12 @@ func humanizeBytes(size int64) string { return fmt.Sprintf("%.1f %cB", value, "KMGTPE"[exp]) } -func coloredProgressBar(value, max int64, percent float64) string { - if max <= 0 { +func coloredProgressBar(value, maxValue int64, percent float64) string { + if maxValue <= 0 { return colorGray + strings.Repeat("░", barWidth) + colorReset } - filled := int((value * int64(barWidth)) / max) - if filled > barWidth { - filled = barWidth - } + filled := min(int((value*int64(barWidth))/maxValue), barWidth) var barColor string if percent >= 50 { @@ -114,26 +111,27 @@ func coloredProgressBar(value, max int64, percent float64) string { barColor = colorGreen } - bar := barColor - for i := 0; i < barWidth; i++ { + var bar strings.Builder + bar.WriteString(barColor) + for i := range barWidth { if i < filled { if i < filled-1 { - bar += "█" + bar.WriteString("█") } else { - remainder := (value * int64(barWidth)) % max - if remainder > max/2 { - bar += "█" - } else if remainder > max/4 { - bar += "▓" + remainder := (value * int64(barWidth)) % maxValue + if remainder > maxValue/2 { + bar.WriteString("█") + } else if remainder > maxValue/4 { + bar.WriteString("▓") } else { - bar += "▒" + bar.WriteString("▒") } } } else { - bar += colorGray + "░" + barColor + bar.WriteString(colorGray + "░" + barColor) } } - return bar + colorReset + return bar.String() + colorReset } // runeWidth returns display width for wide characters and emoji. diff --git a/cmd/analyze/heap.go b/cmd/analyze/heap.go index 08bf8a9..0b4a5a5 100644 --- a/cmd/analyze/heap.go +++ b/cmd/analyze/heap.go @@ -7,11 +7,11 @@ func (h entryHeap) Len() int { return len(h) } func (h entryHeap) Less(i, j int) bool { return h[i].Size < h[j].Size } func (h entryHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *entryHeap) Push(x interface{}) { +func (h *entryHeap) Push(x any) { *h = append(*h, x.(dirEntry)) } -func (h *entryHeap) Pop() interface{} { +func (h *entryHeap) Pop() any { old := *h n := len(old) x := old[n-1] @@ -26,11 +26,11 @@ func (h largeFileHeap) Len() int { return len(h) } func (h largeFileHeap) Less(i, j int) bool { return h[i].Size < h[j].Size } func (h largeFileHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h *largeFileHeap) Push(x interface{}) { +func (h *largeFileHeap) Push(x any) { *h = append(*h, x.(fileEntry)) } -func (h *largeFileHeap) Pop() interface{} { +func (h *largeFileHeap) Pop() any { old := *h n := len(old) x := old[n-1] diff --git a/cmd/analyze/main.go b/cmd/analyze/main.go index f81055b..c44476e 100644 --- a/cmd/analyze/main.go +++ b/cmd/analyze/main.go @@ -359,7 +359,7 @@ func (m model) scanCmd(path string) tea.Cmd { return scanResultMsg{result: result, err: nil} } - v, err, _ := scanGroup.Do(path, func() (interface{}, error) { + v, err, _ := scanGroup.Do(path, func() (any, error) { return scanPathConcurrent(path, m.filesScanned, m.dirsScanned, m.bytesScanned, m.currentPath) }) @@ -997,10 +997,7 @@ func (m *model) clampEntrySelection() { m.selected = 0 } viewport := calculateViewport(m.height, false) - maxOffset := len(m.entries) - viewport - if maxOffset < 0 { - maxOffset = 0 - } + maxOffset := max(len(m.entries)-viewport, 0) if m.offset > maxOffset { m.offset = maxOffset } @@ -1025,10 +1022,7 @@ func (m *model) clampLargeSelection() { m.largeSelected = 0 } viewport := calculateViewport(m.height, true) - maxOffset := len(m.largeFiles) - viewport - if maxOffset < 0 { - maxOffset = 0 - } + maxOffset := max(len(m.largeFiles)-viewport, 0) if m.largeOffset > maxOffset { m.largeOffset = maxOffset } diff --git a/cmd/analyze/scanner.go b/cmd/analyze/scanner.go index 9654e59..b6ab09b 100644 --- a/cmd/analyze/scanner.go +++ b/cmd/analyze/scanner.go @@ -39,10 +39,7 @@ func scanPathConcurrent(root string, filesScanned, dirsScanned, bytesScanned *in heap.Init(largeFilesHeap) // Worker pool sized for I/O-bound scanning. - numWorkers := runtime.NumCPU() * cpuMultiplier - if numWorkers < minWorkers { - numWorkers = minWorkers - } + numWorkers := max(runtime.NumCPU()*cpuMultiplier, minWorkers) if numWorkers > maxWorkers { numWorkers = maxWorkers } @@ -289,10 +286,7 @@ func calculateDirSizeFast(root string, filesScanned, dirsScanned, bytesScanned * ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() - concurrency := runtime.NumCPU() * 4 - if concurrency > 64 { - concurrency = 64 - } + concurrency := min(runtime.NumCPU()*4, 64) sem := make(chan struct{}, concurrency) var walk func(string) @@ -363,10 +357,9 @@ func findLargeFilesWithSpotlight(root string, minSize int64) []fileEntry { return nil } - lines := strings.Split(strings.TrimSpace(string(output)), "\n") var files []fileEntry - for _, line := range lines { + for line := range strings.Lines(strings.TrimSpace(string(output))) { if line == "" { continue } @@ -413,8 +406,8 @@ func findLargeFilesWithSpotlight(root string, minSize int64) []fileEntry { // isInFoldedDir checks if a path is inside a folded directory. func isInFoldedDir(path string) bool { - parts := strings.Split(path, string(os.PathSeparator)) - for _, part := range parts { + parts := strings.SplitSeq(path, string(os.PathSeparator)) + for part := range parts { if foldDirs[part] { return true } @@ -432,10 +425,7 @@ func calculateDirSizeConcurrent(root string, largeFileChan chan<- fileEntry, fil var wg sync.WaitGroup // Limit concurrent subdirectory scans. - maxConcurrent := runtime.NumCPU() * 2 - if maxConcurrent > maxDirWorkers { - maxConcurrent = maxDirWorkers - } + maxConcurrent := min(runtime.NumCPU()*2, maxDirWorkers) sem := make(chan struct{}, maxConcurrent) for _, child := range children { diff --git a/cmd/analyze/view.go b/cmd/analyze/view.go index a434c71..6cdf400 100644 --- a/cmd/analyze/view.go +++ b/cmd/analyze/view.go @@ -100,14 +100,8 @@ func (m model) View() string { fmt.Fprintln(&b, " No large files found (>=100MB)") } else { viewport := calculateViewport(m.height, true) - start := m.largeOffset - if start < 0 { - start = 0 - } - end := start + viewport - if end > len(m.largeFiles) { - end = len(m.largeFiles) - } + start := max(m.largeOffset, 0) + end := min(start+viewport, len(m.largeFiles)) maxLargeSize := int64(1) for _, file := range m.largeFiles { if file.Size > maxLargeSize { @@ -163,10 +157,7 @@ func (m model) View() string { for idx, entry := range m.entries { icon := "📁" sizeVal := entry.Size - barValue := sizeVal - if barValue < 0 { - barValue = 0 - } + barValue := max(sizeVal, 0) var percent float64 if totalSize > 0 && sizeVal >= 0 { percent = float64(sizeVal) / float64(totalSize) * 100 @@ -243,14 +234,8 @@ func (m model) View() string { viewport := calculateViewport(m.height, false) nameWidth := calculateNameWidth(m.width) - start := m.offset - if start < 0 { - start = 0 - } - end := start + viewport - if end > len(m.entries) { - end = len(m.entries) - } + start := max(m.offset, 0) + end := min(start+viewport, len(m.entries)) for idx := start; idx < end; idx++ { entry := m.entries[idx] diff --git a/cmd/status/main.go b/cmd/status/main.go index 4e152ee..9af9fc0 100644 --- a/cmd/status/main.go +++ b/cmd/status/main.go @@ -127,10 +127,7 @@ func animTick() tea.Cmd { func animTickWithSpeed(cpuUsage float64) tea.Cmd { // Higher CPU = faster animation. - interval := 300 - int(cpuUsage*2.5) - if interval < 50 { - interval = 50 - } + interval := max(300-int(cpuUsage*2.5), 50) return tea.Tick(time.Duration(interval)*time.Millisecond, func(time.Time) tea.Msg { return animTickMsg{} }) } diff --git a/cmd/status/metrics_battery.go b/cmd/status/metrics_battery.go index ecdb463..ef3515d 100644 --- a/cmd/status/metrics_battery.go +++ b/cmd/status/metrics_battery.go @@ -68,11 +68,10 @@ func collectBatteries() (batts []BatteryStatus, err error) { } func parsePMSet(raw string, health string, cycles int, capacity int) []BatteryStatus { - lines := strings.Split(raw, "\n") var out []BatteryStatus var timeLeft string - for _, line := range lines { + for line := range strings.Lines(raw) { // Time remaining. if strings.Contains(line, "remaining") { parts := strings.Fields(line) @@ -128,8 +127,7 @@ func getCachedPowerData() (health string, cycles int, capacity int) { return "", 0, 0 } - lines := strings.Split(out, "\n") - for _, line := range lines { + for line := range strings.Lines(out) { lower := strings.ToLower(line) if strings.Contains(lower, "cycle count") { if _, after, found := strings.Cut(line, ":"); found { @@ -183,8 +181,7 @@ func collectThermal() ThermalStatus { // Fan info from cached system_profiler. out := getSystemPowerOutput() if out != "" { - lines := strings.Split(out, "\n") - for _, line := range lines { + for line := range strings.Lines(out) { lower := strings.ToLower(line) if strings.Contains(lower, "fan") && strings.Contains(lower, "speed") { if _, after, found := strings.Cut(line, ":"); found { @@ -200,8 +197,7 @@ func collectThermal() ThermalStatus { ctxPower, cancelPower := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancelPower() if out, err := runCmd(ctxPower, "ioreg", "-rn", "AppleSmartBattery"); err == nil { - lines := strings.Split(out, "\n") - for _, line := range lines { + for line := range strings.Lines(out) { line = strings.TrimSpace(line) // Battery temperature ("Temperature" = 3055). diff --git a/cmd/status/metrics_bluetooth.go b/cmd/status/metrics_bluetooth.go index f0c35a7..740c10c 100644 --- a/cmd/status/metrics_bluetooth.go +++ b/cmd/status/metrics_bluetooth.go @@ -68,13 +68,12 @@ func readBluetoothCTLDevices() ([]BluetoothDevice, error) { } func parseSPBluetooth(raw string) []BluetoothDevice { - lines := strings.Split(raw, "\n") var devices []BluetoothDevice var currentName string var connected bool var battery string - for _, line := range lines { + for line := range strings.Lines(raw) { trim := strings.TrimSpace(line) if len(trim) == 0 { continue @@ -112,10 +111,9 @@ func parseSPBluetooth(raw string) []BluetoothDevice { } func parseBluetoothctl(raw string) []BluetoothDevice { - lines := strings.Split(raw, "\n") var devices []BluetoothDevice current := BluetoothDevice{} - for _, line := range lines { + for line := range strings.Lines(raw) { trim := strings.TrimSpace(line) if strings.HasPrefix(trim, "Device ") { if current.Name != "" { @@ -123,8 +121,8 @@ func parseBluetoothctl(raw string) []BluetoothDevice { } current = BluetoothDevice{Name: strings.TrimPrefix(trim, "Device "), Connected: false} } - if strings.HasPrefix(trim, "Name:") { - current.Name = strings.TrimSpace(strings.TrimPrefix(trim, "Name:")) + if after, ok := strings.CutPrefix(trim, "Name:"); ok { + current.Name = strings.TrimSpace(after) } if strings.HasPrefix(trim, "Connected:") { current.Connected = strings.Contains(trim, "yes") diff --git a/cmd/status/metrics_disk.go b/cmd/status/metrics_disk.go index 3863aa0..9586fae 100644 --- a/cmd/status/metrics_disk.go +++ b/cmd/status/metrics_disk.go @@ -156,7 +156,7 @@ func isExternalDisk(device string) (bool, error) { found bool external bool ) - for _, line := range strings.Split(out, "\n") { + for line := range strings.Lines(out) { trim := strings.TrimSpace(line) if strings.HasPrefix(trim, "Internal:") { found = true diff --git a/cmd/status/metrics_gpu.go b/cmd/status/metrics_gpu.go index d4775f2..bb60235 100644 --- a/cmd/status/metrics_gpu.go +++ b/cmd/status/metrics_gpu.go @@ -61,9 +61,8 @@ func (c *Collector) collectGPU(now time.Time) ([]GPUStatus, error) { return nil, err } - lines := strings.Split(strings.TrimSpace(out), "\n") var gpus []GPUStatus - for _, line := range lines { + for line := range strings.Lines(strings.TrimSpace(out)) { fields := strings.Split(line, ",") if len(fields) < 4 { continue diff --git a/cmd/status/metrics_hardware.go b/cmd/status/metrics_hardware.go index 731d6a7..8117e57 100644 --- a/cmd/status/metrics_hardware.go +++ b/cmd/status/metrics_hardware.go @@ -28,8 +28,7 @@ func collectHardware(totalRAM uint64, disks []DiskStatus) HardwareInfo { out, err := runCmd(ctx, "system_profiler", "SPHardwareDataType") if err == nil { - lines := strings.Split(out, "\n") - for _, line := range lines { + for line := range strings.Lines(out) { lower := strings.ToLower(strings.TrimSpace(line)) // Prefer "Model Name" over "Model Identifier". if strings.Contains(lower, "model name:") { @@ -85,10 +84,9 @@ func collectHardware(totalRAM uint64, disks []DiskStatus) HardwareInfo { // parseRefreshRate extracts the highest refresh rate from system_profiler display output. func parseRefreshRate(output string) string { - lines := strings.Split(output, "\n") maxHz := 0 - for _, line := range lines { + for line := range strings.Lines(output) { lower := strings.ToLower(line) // Look for patterns like "@ 60Hz", "@ 60.00Hz", or "Refresh Rate: 120 Hz". if strings.Contains(lower, "hz") { @@ -100,8 +98,7 @@ func parseRefreshRate(output string) string { } continue } - if strings.HasSuffix(field, "hz") { - numStr := strings.TrimSuffix(field, "hz") + if numStr, ok := strings.CutSuffix(field, "hz"); ok { if numStr == "" && i > 0 { numStr = fields[i-1] } diff --git a/cmd/status/view.go b/cmd/status/view.go index a5ef8ea..a3c590d 100644 --- a/cmd/status/view.go +++ b/cmd/status/view.go @@ -66,10 +66,7 @@ func getMoleFrame(animFrame int, termWidth int) string { body := moleBody[bodyIdx] moleWidth := 15 - maxPos := termWidth - moleWidth - if maxPos < 0 { - maxPos = 0 - } + maxPos := max(termWidth-moleWidth, 0) cycleLength := maxPos * 2 if cycleLength == 0 { @@ -197,10 +194,7 @@ func renderCPUCard(cpu CPUStatus) cardData { } sort.Slice(cores, func(i, j int) bool { return cores[i].val > cores[j].val }) - maxCores := 3 - if len(cores) < maxCores { - maxCores = len(cores) - } + maxCores := min(len(cores), 3) for i := 0; i < maxCores; i++ { c := cores[i] lines = append(lines, fmt.Sprintf("Core%-2d %s %5.1f%%", c.idx+1, progressBar(c.val), c.val)) @@ -356,10 +350,7 @@ func formatDiskLine(label string, d DiskStatus) string { } func ioBar(rate float64) string { - filled := int(rate / 10.0) - if filled > 5 { - filled = 5 - } + filled := min(int(rate/10.0), 5) if filled < 0 { filled = 0 } @@ -391,10 +382,7 @@ func renderProcessCard(procs []ProcessInfo) cardData { } func miniBar(percent float64) string { - filled := int(percent / 20) - if filled > 5 { - filled = 5 - } + filled := min(int(percent/20), 5) if filled < 0 { filled = 0 } @@ -437,10 +425,7 @@ func renderNetworkCard(netStats []NetworkStatus, proxy ProxyStatus) cardData { } func netBar(rate float64) string { - filled := int(rate / 2.0) - if filled > 5 { - filled = 5 - } + filled := min(int(rate/2.0), 5) if filled < 0 { filled = 0 } @@ -551,10 +536,7 @@ func renderSensorsCard(sensors []SensorReading) cardData { func renderCard(data cardData, width int, height int) string { titleText := data.icon + " " + data.title - lineLen := width - lipgloss.Width(titleText) - 2 - if lineLen < 4 { - lineLen = 4 - } + lineLen := max(width-lipgloss.Width(titleText)-2, 4) header := titleStyle.Render(titleText) + " " + lineStyle.Render(strings.Repeat("╌", lineLen)) content := header + "\n" + strings.Join(data.lines, "\n") @@ -576,7 +558,7 @@ func progressBar(percent float64) string { filled := int(percent / 100 * float64(total)) var builder strings.Builder - for i := 0; i < total; i++ { + for i := range total { if i < filled { builder.WriteString("█") } else { @@ -597,7 +579,7 @@ func batteryProgressBar(percent float64) string { filled := int(percent / 100 * float64(total)) var builder strings.Builder - for i := 0; i < total; i++ { + for i := range total { if i < filled { builder.WriteString("█") } else {