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).
This commit is contained in:
Copilot 2025-08-10 17:38:21 +02:00 committed by GitHub
parent 70a31330cd
commit 6a169c649f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 319 additions and 48 deletions

View file

@ -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

View file

@ -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

View file

@ -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, " ")

View file

@ -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(`