From 6a169c649fdac9b2aad5a3b89decf0da2e784561 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:38:21 +0200 Subject: [PATCH] Fix flags_without_count option behavior for empty symbols (#136) The flags_without_count option was not handling empty symbols correctly. Previously, flags with empty symbols were completely hidden regardless of the option value, but according to the intended behavior, they should show counts when flags_without_count=false (default). --- .gitmux.yml | 2 + README.md | 4 +- tmux/formater.go | 51 ++++--- tmux/formater_test.go | 310 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 319 insertions(+), 48 deletions(-) diff --git a/.gitmux.yml b/.gitmux.yml index 51e462a..e2d18f3 100644 --- a/.gitmux.yml +++ b/.gitmux.yml @@ -86,4 +86,6 @@ tmux: # Add a space between behind & ahead upstream counts. divergence_space: false # Show flags symbols without counts. + # When true, shows only symbols (empty symbols show nothing). + # When false (default), shows symbols with counts (empty symbols show counts only). flags_without_count: false diff --git a/README.md b/README.md index 54c24c4..9e3c2be 100644 --- a/README.md +++ b/README.md @@ -296,7 +296,9 @@ This is the list of additional configuration `options`: | `hide_clean` | Hides the clean flag entirely | `false` | | `swap_divergence` | Swaps order of behind & ahead upstream counts | `false` | | `divergence_space` | Add a space between behind & ahead upstream counts | `false` | -| `flags_without_count`| Show flags symbols without counts | `false` | +| `flags_without_count`| Show flags symbols without counts* | `false` | + +*When `flags_without_count` is true, shows only symbols (empty symbols show nothing). When false (default), shows symbols with counts (empty symbols show counts only). ## Troubleshooting diff --git a/tmux/formater.go b/tmux/formater.go index ec246f9..adfd40e 100644 --- a/tmux/formater.go +++ b/tmux/formater.go @@ -295,21 +295,31 @@ func (f *Formater) currentRef() string { return fmt.Sprintf("%s%s%s", f.Styles.Clear, f.Styles.Branch, branch) } -// formatFlag formats a flag with or without count based on the flags_without_count option -func (f *Formater) formatFlag(style, symbol string, count int) string { - if f.Options.FlagsWithoutCount { - return fmt.Sprintf("%s%s", style, symbol) +// appendFlag appends a flag to the flags slice based on configuration options +func (f *Formater) appendFlag(flags []string, style, symbol string, count int) []string { + if count == 0 { + return flags } - return fmt.Sprintf("%s%s%d", style, symbol, count) + + if f.Options.FlagsWithoutCount { + // When flags_without_count is true, show symbol only (empty string if symbol is empty) + if symbol == "" { + return flags + } + return append(flags, fmt.Sprintf("%s%s", style, symbol)) + } + + // When flags_without_count is false, show symbol + count, or just count if symbol is empty + return append(flags, fmt.Sprintf("%s%s%d", style, symbol, count)) } func (f *Formater) flags() string { var flags []string if f.st.IsClean { - if f.st.NumStashed != 0 && f.Symbols.Stashed != "" { - flags = append(flags, f.formatFlag(f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed)) - } + // For stashed in clean state, handle empty symbols properly + flags = f.appendFlag(flags, f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed) + // Clean flag only shows if symbol is not empty and hide_clean is false if !f.Options.HideClean && f.Symbols.Clean != "" { flags = append(flags, fmt.Sprintf("%s%s", f.Styles.Clean, f.Symbols.Clean)) } @@ -319,25 +329,12 @@ func (f *Formater) flags() string { } } - if f.st.NumStaged != 0 && f.Symbols.Staged != "" { - flags = append(flags, f.formatFlag(f.Styles.Staged, f.Symbols.Staged, f.st.NumStaged)) - } - - if f.st.NumConflicts != 0 && f.Symbols.Conflict != "" { - flags = append(flags, f.formatFlag(f.Styles.Conflict, f.Symbols.Conflict, f.st.NumConflicts)) - } - - if f.st.NumModified != 0 && f.Symbols.Modified != "" { - flags = append(flags, f.formatFlag(f.Styles.Modified, f.Symbols.Modified, f.st.NumModified)) - } - - if f.st.NumStashed != 0 && f.Symbols.Stashed != "" { - flags = append(flags, f.formatFlag(f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed)) - } - - if f.st.NumUntracked != 0 && f.Symbols.Untracked != "" { - flags = append(flags, f.formatFlag(f.Styles.Untracked, f.Symbols.Untracked, f.st.NumUntracked)) - } + // For all other flags, handle empty symbols properly + flags = f.appendFlag(flags, f.Styles.Staged, f.Symbols.Staged, f.st.NumStaged) + flags = f.appendFlag(flags, f.Styles.Conflict, f.Symbols.Conflict, f.st.NumConflicts) + flags = f.appendFlag(flags, f.Styles.Modified, f.Symbols.Modified, f.st.NumModified) + flags = f.appendFlag(flags, f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed) + flags = f.appendFlag(flags, f.Styles.Untracked, f.Symbols.Untracked, f.st.NumUntracked) if len(flags) > 0 { return f.Styles.Clear + strings.Join(flags, " ") diff --git a/tmux/formater_test.go b/tmux/formater_test.go index 6890740..0d4488a 100644 --- a/tmux/formater_test.go +++ b/tmux/formater_test.go @@ -104,6 +104,200 @@ func TestFlags(t *testing.T) { } } +func TestFlagsWithoutCountBehavior(t *testing.T) { + tests := []struct { + name string + styles styles + symbols symbols + options options + st *gitstatus.Status + want string + }{ + // Case 1: non-empty symbol, count=1, flags_without_count=false + { + name: "case 1a: non-empty symbol, count=1, flags_without_count=false", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "SymbolStaged", + }, + options: options{ + FlagsWithoutCount: false, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + }, + }, + want: "StyleClearStyleStagedSymbolStaged1", + }, + // Case 1: non-empty symbol, count=1, flags_without_count=true + { + name: "case 1b: non-empty symbol, count=1, flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "SymbolStaged", + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + }, + }, + want: "StyleClearStyleStagedSymbolStaged", + }, + // Case 2: empty symbol, count=1, flags_without_count=false + { + name: "case 2a: empty symbol, count=1, flags_without_count=false", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "", + }, + options: options{ + FlagsWithoutCount: false, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + }, + }, + want: "StyleClearStyleStaged1", + }, + // Case 2: empty symbol, count=1, flags_without_count=true + { + name: "case 2b: empty symbol, count=1, flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "", + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + }, + }, + want: "", + }, + // Case 3: count=0, flags_without_count=false + { + name: "case 3a: count=0, flags_without_count=false", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "SymbolStaged", + }, + options: options{ + FlagsWithoutCount: false, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 0, + }, + }, + want: "", + }, + // Case 3: count=0, flags_without_count=true + { + name: "case 3b: count=0, flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + }, + symbols: symbols{ + Staged: "SymbolStaged", + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + Porcelain: gitstatus.Porcelain{ + NumStaged: 0, + }, + }, + want: "", + }, + // Mixed case: multiple flags with different symbol states + { + name: "mixed flags: some empty symbols, some non-empty, flags_without_count=false", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + Modified: "StyleMod", + Stashed: "StyleStash", + }, + symbols: symbols{ + Staged: "", // empty symbol, should show count only + Modified: "M", // non-empty symbol, should show symbol+count + Stashed: "", // empty symbol, should show count only + }, + options: options{ + FlagsWithoutCount: false, + }, + st: &gitstatus.Status{ + NumStashed: 2, + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + NumModified: 3, + }, + }, + want: "StyleClearStyleStaged1 StyleModM3 StyleStash2", + }, + // Mixed case: multiple flags with different symbol states, flags_without_count=true + { + name: "mixed flags: some empty symbols, some non-empty, flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + Modified: "StyleMod", + Stashed: "StyleStash", + }, + symbols: symbols{ + Staged: "", // empty symbol, should show nothing + Modified: "M", // non-empty symbol, should show symbol only + Stashed: "", // empty symbol, should show nothing + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + NumStashed: 2, + Porcelain: gitstatus.Porcelain{ + NumStaged: 1, + NumModified: 3, + }, + }, + want: "StyleClearStyleModM", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &Formater{ + Config: Config{Styles: tt.styles, Symbols: tt.symbols, Options: tt.options}, + st: tt.st, + } + + compareStrings(t, tt.want, f.flags()) + }) + } +} + func TestFlagsWithoutCount(t *testing.T) { tests := []struct { name string @@ -988,7 +1182,7 @@ func TestFlagsWithEmptySymbols(t *testing.T) { want string }{ { - name: "empty stashed symbol hides stash count", + name: "empty stashed symbol shows stash count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Modified: "StyleMod", @@ -996,7 +1190,7 @@ func TestFlagsWithEmptySymbols(t *testing.T) { }, symbols: symbols{ Modified: "SymbolMod", - Stashed: "", // empty symbol should hide this flag + Stashed: "", // empty symbol should show count with default flags_without_count=false }, st: &gitstatus.Status{ NumStashed: 5, @@ -1004,17 +1198,17 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumModified: 2, }, }, - want: "StyleClear" + "StyleModSymbolMod2", + want: "StyleClear" + "StyleModSymbolMod2 StyleStash5", }, { - name: "empty modified symbol hides modified count", + name: "empty modified symbol shows modified count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Modified: "StyleMod", Stashed: "StyleStash", }, symbols: symbols{ - Modified: "", // empty symbol should hide this flag + Modified: "", // empty symbol should show count with default flags_without_count=false Stashed: "SymbolStash", }, st: &gitstatus.Status{ @@ -1023,17 +1217,17 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumModified: 2, }, }, - want: "StyleClear" + "StyleStashSymbolStash1", + want: "StyleClear" + "StyleMod2 StyleStashSymbolStash1", }, { - name: "empty staged symbol hides staged count", + name: "empty staged symbol shows staged count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Staged: "StyleStaged", Stashed: "StyleStash", }, symbols: symbols{ - Staged: "", // empty symbol should hide this flag + Staged: "", // empty symbol should show count with default flags_without_count=false Stashed: "SymbolStash", }, st: &gitstatus.Status{ @@ -1042,17 +1236,17 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumStaged: 3, }, }, - want: "StyleClear" + "StyleStashSymbolStash1", + want: "StyleClear" + "StyleStaged3 StyleStashSymbolStash1", }, { - name: "empty untracked symbol hides untracked count", + name: "empty untracked symbol shows untracked count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Untracked: "StyleUntracked", Stashed: "StyleStash", }, symbols: symbols{ - Untracked: "", // empty symbol should hide this flag + Untracked: "", // empty symbol should show count with default flags_without_count=false Stashed: "SymbolStash", }, st: &gitstatus.Status{ @@ -1061,17 +1255,17 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumUntracked: 7, }, }, - want: "StyleClear" + "StyleStashSymbolStash1", + want: "StyleClear" + "StyleStashSymbolStash1 StyleUntracked7", }, { - name: "empty conflict symbol hides conflict count", + name: "empty conflict symbol shows conflict count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Conflict: "StyleConflict", Stashed: "StyleStash", }, symbols: symbols{ - Conflict: "", // empty symbol should hide this flag + Conflict: "", // empty symbol should show count with default flags_without_count=false Stashed: "SymbolStash", }, st: &gitstatus.Status{ @@ -1080,7 +1274,7 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumConflicts: 3, }, }, - want: "StyleClear" + "StyleStashSymbolStash1", + want: "StyleClear" + "StyleConflict3 StyleStashSymbolStash1", }, { name: "empty clean symbol hides clean flag", @@ -1100,23 +1294,24 @@ func TestFlagsWithEmptySymbols(t *testing.T) { want: "StyleClear" + "StyleStashSymbolStash1", }, { - name: "empty stashed symbol in clean state hides stash count", + name: "empty stashed symbol in clean state shows stash count (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Clean: "StyleClean", + Stashed: "StyleStash", }, symbols: symbols{ Clean: "SymbolClean", - Stashed: "", // empty symbol should hide this flag + Stashed: "", // empty symbol should show count with default flags_without_count=false }, st: &gitstatus.Status{ IsClean: true, NumStashed: 1, }, - want: "StyleClear" + "StyleCleanSymbolClean", + want: "StyleClear" + "StyleStash1 StyleCleanSymbolClean", }, { - name: "all symbols empty shows nothing", + name: "all symbols empty shows counts (flags_without_count=false default)", styles: styles{ Clear: "StyleClear", Clean: "StyleClean", @@ -1144,7 +1339,7 @@ func TestFlagsWithEmptySymbols(t *testing.T) { NumUntracked: 4, }, }, - want: "", + want: "StyleClear" + "StyleStaged3 StyleConflict1 StyleMod2 StyleStash1 StyleUntracked4", }, } for _, tt := range tests { @@ -1159,6 +1354,81 @@ func TestFlagsWithEmptySymbols(t *testing.T) { } } +func TestFlagsWithEmptySymbolsAndFlagsWithoutCount(t *testing.T) { + tests := []struct { + name string + styles styles + symbols symbols + options options + st *gitstatus.Status + want string + }{ + { + name: "empty symbols hide when flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Modified: "StyleMod", + Stashed: "StyleStash", + }, + symbols: symbols{ + Modified: "", // empty symbol should hide with flags_without_count=true + Stashed: "SymbolStash", + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + NumStashed: 1, + Porcelain: gitstatus.Porcelain{ + NumModified: 2, + }, + }, + want: "StyleClear" + "StyleStashSymbolStash", + }, + { + name: "all empty symbols hide when flags_without_count=true", + styles: styles{ + Clear: "StyleClear", + Staged: "StyleStaged", + Modified: "StyleMod", + Conflict: "StyleConflict", + Untracked: "StyleUntracked", + Stashed: "StyleStash", + }, + symbols: symbols{ + Staged: "", + Modified: "", + Conflict: "", + Untracked: "", + Stashed: "", + }, + options: options{ + FlagsWithoutCount: true, + }, + st: &gitstatus.Status{ + NumStashed: 1, + Porcelain: gitstatus.Porcelain{ + NumStaged: 3, + NumModified: 2, + NumConflicts: 1, + NumUntracked: 4, + }, + }, + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &Formater{ + Config: Config{Styles: tt.styles, Symbols: tt.symbols, Options: tt.options}, + st: tt.st, + } + + compareStrings(t, tt.want, f.flags()) + }) + } +} + func compareStrings(t *testing.T, want, got string) { if got != want { t.Errorf(`