Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ produces. The pipeline, orchestrated by `app.Run`:
findings by savings, layouts by `Layout.Total`), and hand the result to
**`ui.Printer`**, which renders (unified / side-by-side / proposed-only diff
via `textdiff`, or annotated layout) to an `io.Writer`. With `-summary` (diff
only) it then prints a one-line `Summary: N structs affected, M bytes saved`.
only) it then prints a one-line `Summary: N structs affected, M bytes saved total`.
The savings metric is the shared `app.savings(common.Finding) int64` helper
(used by sort, threshold, and summary). Because the logic packages return data
and `ui` consumes it, rendering is testable by injecting findings — no
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ the structs shown, and the bytes their reorderings would save):
```
$ structalign -summary ./_example
... (diffs above) ...
Summary: 5 structs affected, 56 bytes saved
Summary: 5 structs affected, 56 bytes saved total
```

### Inspect layout
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (p *Printer) RenderLayouts(layouts []common.Layout, verbose, keepTags bool)
// RenderSummary writes a one-line diff-mode summary to Out. The "Summary:"
// label is bold when color is on; counts are pluralized.
func (p *Printer) RenderSummary(structs int, bytesSaved int64) {
fmt.Fprintf(p.Out, "%s %d %s affected, %d %s saved\n", //nolint:errcheck
fmt.Fprintf(p.Out, "%s %d %s affected, %d %s saved total\n", //nolint:errcheck
paint(p.Color, p.theme().Label, "Summary:"),
structs, plural(int64(structs), "struct", "structs"),
bytesSaved, plural(bytesSaved, "byte", "bytes"))
Expand Down
8 changes: 4 additions & 4 deletions internal/ui/summary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ func TestRenderSummaryPlural(t *testing.T) {
var buf bytes.Buffer
p := &ui.Printer{Out: &buf, Color: false}
p.RenderSummary(3, 40)
assert.Equal(t, "Summary: 3 structs affected, 40 bytes saved\n", buf.String())
assert.Equal(t, "Summary: 3 structs affected, 40 bytes saved total\n", buf.String())
}

func TestRenderSummarySingular(t *testing.T) {
var buf bytes.Buffer
p := &ui.Printer{Out: &buf, Color: false}
p.RenderSummary(1, 1)
assert.Equal(t, "Summary: 1 struct affected, 1 byte saved\n", buf.String())
assert.Equal(t, "Summary: 1 struct affected, 1 byte saved total\n", buf.String())
}

func TestRenderSummaryZero(t *testing.T) {
var buf bytes.Buffer
p := &ui.Printer{Out: &buf, Color: false}
p.RenderSummary(0, 0)
assert.Equal(t, "Summary: 0 structs affected, 0 bytes saved\n", buf.String())
assert.Equal(t, "Summary: 0 structs affected, 0 bytes saved total\n", buf.String())
}

func TestRenderSummaryColorBoldsLabel(t *testing.T) {
Expand All @@ -36,5 +36,5 @@ func TestRenderSummaryColorBoldsLabel(t *testing.T) {
p.RenderSummary(2, 16)
out := buf.String()
assert.Contains(t, out, "\x1b[1m")
assert.Contains(t, out, "2 structs affected, 16 bytes saved")
assert.Contains(t, out, "2 structs affected, 16 bytes saved total")
}
14 changes: 14 additions & 0 deletions internal/ui/theme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ func TestPrinterUsesSetTheme(t *testing.T) {
assert.Contains(t, buf.String(), "\x1b[1m\x1b[97m", "cga Label (bold bright white) should be used")
}

// CGA must be a visibly distinct palette, not a brightened default. It uses the
// iconic mode-4 palette 1 (cyan/magenta/white): the header is magenta (not the
// default's cyan) and removed lines are magenta (not red).
func TestCgaThemeIsDistinctFromDefault(t *testing.T) {
def := ui.DefaultTheme()
cga, ok := ui.ThemeByName("cga")
assert.True(t, ok)
assert.NotEqual(t, def.Header, cga.Header, "cga header must differ from default")
assert.Contains(t, cga.Header, "95", "cga header is magenta")
assert.Contains(t, cga.Added, "96", "cga added is cyan")
assert.Contains(t, cga.Removed, "95", "cga removed is magenta, not red")
assert.NotContains(t, cga.Removed, "31", "cga must not reuse the default red")
}

// The green (P1 phosphor) theme is monochrome: it must never use red (31),
// since add/removed are distinguished by intensity + the +/- prefixes.
func TestGreenThemeIsMonochrome(t *testing.T) {
Expand Down
15 changes: 9 additions & 6 deletions internal/ui/themes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ package ui
// (bold vs dim) plus the +/- diff prefixes to distinguish added from removed.
var builtinThemes = map[string]Theme{
"default": DefaultTheme(),
// The iconic CGA mode-4 palette 1 (high intensity): cyan, magenta, white on
// black. Using magenta for removed/padding instead of the default's red makes
// the palette visibly distinct from the default rather than a mere brightening.
"cga": {
Header: "\x1b[1m\x1b[96m", // bright cyan
Added: "\x1b[92m", // bright green
Removed: "\x1b[91m", // bright red
Meta: "\x1b[90m", // bright black (gray)
Padding: "\x1b[91m", // bright red
Label: "\x1b[1m\x1b[97m", // bright white
Header: "\x1b[1m\x1b[7m\x1b[95m", // bold + reverse-video magenta (a header bar)
Added: "\x1b[96m", // bright cyan
Removed: "\x1b[95m", // bright magenta
Meta: "\x1b[90m", // bright black (gray)
Padding: "\x1b[95m", // bright magenta
Label: "\x1b[1m\x1b[97m", // bright white
},
"green": {
Header: "\x1b[1m\x1b[32m", // bold green
Expand Down
Loading