diff --git a/.gitignore b/.gitignore index 34e2bf2..f7ca397 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so *.dylib sysmon +sysmon-* # Test binary, built with `go test -c` *.test diff --git a/main.go b/main.go index 4c57030..3fc86f4 100644 --- a/main.go +++ b/main.go @@ -157,7 +157,7 @@ func (m model) View() string { // Main stats bars with labels overlaid in a 2x2 grid // Calculate bar width for 2 bars per line with spacing spacingBetweenBars := 2 - availableWidth := m.width - 2 + availableWidth := m.width barWidth := (availableWidth - spacingBetweenBars) / 2 if barWidth < 20 { barWidth = 20 @@ -196,7 +196,7 @@ func (m model) View() string { coresPerLine := 4 spacingBetweenBars = 2 - availableWidth = m.width - 2 + availableWidth = m.width // Each bar needs space for label (5 chars) + percentage (6 chars) + some bar space // Total overhead is just spacing between bars since label/percent are inside coreBarWidth := (availableWidth - (coresPerLine-1)*spacingBetweenBars) / coresPerLine diff --git a/main_test.go b/main_test.go index 208b74a..d3e6772 100644 --- a/main_test.go +++ b/main_test.go @@ -1,6 +1,7 @@ package main import ( + "strings" "testing" ) @@ -76,3 +77,129 @@ func TestTruncateLeft(t *testing.T) { }) } } + +func TestProgressBarWidths(t *testing.T) { + tests := []struct { + name string + terminalWidth int + }{ + { + name: "standard terminal width", + terminalWidth: 80, + }, + { + name: "wide terminal", + terminalWidth: 120, + }, + { + name: "medium terminal", + terminalWidth: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a model with known dimensions and stats + m := model{ + width: tt.terminalWidth, + height: 24, + stats: SystemStats{ + CPUUsage: 50.0, + GPUUsage: 25.0, + MemoryUsage: 60.0, + GPUMemory: 30.0, + CPUCores: []float64{10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0}, + Processes: []ProcessInfo{ + {PID: 1234, CPU: 10.5, Memory: 5.2, Command: "/usr/bin/test"}, + }, + }, + } + + // Render the view + view := m.View() + lines := strings.Split(view, "\n") + + // Find the maximum line length in the rendered output + maxLen := 0 + for _, line := range lines { + // Strip ANSI color codes to get actual character length + stripped := stripAnsiCodes(line) + if len(stripped) > maxLen { + maxLen = len(stripped) + } + } + + // The fix ensures we use m.width instead of m.width-2. + // The view should use close to the full width (allowing for integer division). + // We verify the max line length is within [terminalWidth-1, terminalWidth], + // which confirms we're not using the old width-2 margin. + if maxLen < tt.terminalWidth-1 { + t.Errorf("View does not use enough terminal width: max line length %d, expected %d or %d (terminal width: %d)", + maxLen, tt.terminalWidth-1, tt.terminalWidth, tt.terminalWidth) + } + + // Note: We don't enforce maxLen <= tt.terminalWidth because minimum bar widths + // can cause the view to exceed terminal width in narrow terminals. This is + // existing behavior that ensures bars remain readable. + }) + } +} + +// stripAnsiCodes removes ANSI escape codes to get the actual display length +func stripAnsiCodes(s string) string { + // Simple regex-free approach: skip escape sequences + var result strings.Builder + i := 0 + for i < len(s) { + if s[i] == '\x1b' && i+1 < len(s) && s[i+1] == '[' { + // Skip ANSI escape sequence + i += 2 + for i < len(s) && s[i] != 'm' { + i++ + } + i++ // skip 'm' + } else { + result.WriteByte(s[i]) + i++ + } + } + return result.String() +} + +func TestViewRendersCorrectly(t *testing.T) { + // Create a model with known dimensions and stats + m := model{ + width: 80, + height: 24, + stats: SystemStats{ + CPUUsage: 50.0, + GPUUsage: 25.0, + MemoryUsage: 60.0, + GPUMemory: 30.0, + CPUCores: []float64{10.0, 20.0, 30.0, 40.0}, + Processes: []ProcessInfo{ + {PID: 1234, CPU: 10.5, Memory: 5.2, Command: "/usr/bin/test"}, + }, + }, + } + + // Render the view + view := m.View() + + // Split into lines + lines := strings.Split(view, "\n") + + // Basic sanity checks + if len(lines) < 5 { + t.Errorf("Expected at least 5 lines in output, got %d", len(lines)) + } + + // Verify the view contains expected content + viewContent := strings.ToLower(view) + expectedStrings := []string{"cpu usage", "memory", "gpu usage", "pid", "command"} + for _, expected := range expectedStrings { + if !strings.Contains(viewContent, expected) { + t.Errorf("Expected view to contain %q", expected) + } + } +}