From 395d66dcc3e57e270af96712c2f3bf253e0c8fdb Mon Sep 17 00:00:00 2001 From: Josh Medeski Date: Wed, 22 Mar 2023 08:08:19 -0500 Subject: [PATCH] feat: separate layout items with spaces (#91) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: separate layout items with spaces * docs: update layout example * fix: add space after stash * feat: add trailing space to stats component * feat: add trailing space to clean section * test: trailing spaces on all components * Remove bytes.Buffer, status functions return strings * Take care of elements spacing in format() * Fix spacing in default config --------- Co-authored-by: Aurélien Rainone --- .gitmux.yml | 2 +- README.md | 2 +- testdata/default.output.txt | 2 +- tmux/formater.go | 136 +++++++++++++++++++----------------- tmux/formater_test.go | 62 ++++++++-------- 5 files changed, 103 insertions(+), 101 deletions(-) diff --git a/.gitmux.yml b/.gitmux.yml index ef02ebe..4d3654d 100644 --- a/.gitmux.yml +++ b/.gitmux.yml @@ -69,7 +69,7 @@ tmux: # - flags: symbols representing the working tree state, for example `✚ 1 ⚑ 1 … 2` # - stats: insertions/deletions (lines), for example`Σ56 Δ21` # - some string `foo`: any other character of string is directly shown, for example `foo` or `|` - layout: [branch, " ", remote-branch, divergence, " - ", flags] + layout: [branch, remote-branch, divergence, " - ", flags] # Additional configuration options. options: diff --git a/README.md b/README.md index 88c257a..dab7a12 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ tmux: clean: '#[fg=green,bold]' insertions: '#[fg=green]' deletions: '#[fg=red]' - layout: [branch, .., remote-branch, divergence, ' - ', flags] + layout: [branch, .., remote-branch, divergence, '- ', flags] options: branch_max_len: 0 branch_trim: right diff --git a/testdata/default.output.txt b/testdata/default.output.txt index 126c0b6..96350cb 100644 --- a/testdata/default.output.txt +++ b/testdata/default.output.txt @@ -51,4 +51,4 @@ with some different content -- empty_file -- -- output.golden -- -#[fg=default]#[fg=default]#[fg=white,bold]⎇ #[fg=default]#[fg=white,bold]main#[fg=default] #[fg=default] - #[fg=default]#[fg=green,bold]● 1 #[fg=red,bold]✚ 1 #[fg=cyan,bold]⚑ 1 #[fg=magenta,bold]… 2 +#[fg=default]#[fg=default]#[fg=white,bold]⎇ #[fg=default]#[fg=white,bold]main#[fg=default] - #[fg=default]#[fg=green,bold]● 1 #[fg=red,bold]✚ 1 #[fg=cyan,bold]⚑ 1 #[fg=magenta,bold]… 2 diff --git a/tmux/formater.go b/tmux/formater.go index b4219fb..8346129 100644 --- a/tmux/formater.go +++ b/tmux/formater.go @@ -1,7 +1,6 @@ package tmux import ( - "bytes" "fmt" "io" "strings" @@ -95,7 +94,6 @@ type options struct { // A Formater formats git status to a tmux style string. type Formater struct { Config - b bytes.Buffer st *gitstatus.Status } @@ -137,119 +135,127 @@ func truncate(s, ellipsis string, max int, dir direction) string { // Format writes st as json into w. func (f *Formater) Format(w io.Writer, st *gitstatus.Status) error { f.st = st - f.clear() - // overall working tree state + // Overall working tree state if f.st.IsInitial { branch := truncate(f.st.LocalBranch, f.Options.Ellipsis, f.Options.BranchMaxLen, f.Options.BranchTrim) - fmt.Fprintf(w, "%s%s [no commits yet]", f.Styles.Branch, branch) - f.flags() - _, err := f.b.WriteTo(w) - + s := fmt.Sprintf("%s%s%s [no commits yet] %s", f.Styles.Clear, f.Styles.Branch, branch, f.flags()) + _, err := io.WriteString(w, s) return err } - f.format() - _, err := f.b.WriteTo(w) - + _, err := fmt.Fprintf(w, "%s%s", f.Styles.Clear, f.format()) return err } -func (f *Formater) format() { +func (f *Formater) format() string { + var comps []string + + // Add spacing between non-empty components. + joinComps := func() string { + i := 0 + for _, s := range comps { + if s != "" { + comps[i] = s + i++ + } + } + return strings.Join(comps[:i], " ") + } + + sb := strings.Builder{} for _, item := range f.Layout { switch item { case "branch": - f.specialState() + comps = append(comps, f.specialState()) case "remote": - f.remoteBranch() - f.divergence() + comps = append(comps, f.remoteBranch()) + comps = append(comps, f.divergence()) case "remote-branch": - f.remoteBranch() + comps = append(comps, f.remoteBranch()) case "divergence": - f.divergence() + comps = append(comps, f.divergence()) case "flags": - f.flags() + comps = append(comps, f.flags()) case "stats": - f.stats() + comps = append(comps, f.stats()) default: - f.clear() - f.b.WriteString(item) + sb.WriteString(joinComps()) + sb.WriteString(f.Styles.Clear) + sb.WriteString(item) + comps = comps[:0] } } + + sb.WriteString(joinComps()) + return sb.String() } -func (f *Formater) specialState() { - f.clear() +func (f *Formater) specialState() string { + s := f.Styles.Clear switch f.st.State { case gitstatus.Rebasing: - fmt.Fprintf(&f.b, "%s[rebase] ", f.Styles.State) + s += fmt.Sprintf("%s[rebase] ", f.Styles.State) case gitstatus.AM: - fmt.Fprintf(&f.b, "%s[am] ", f.Styles.State) + s += fmt.Sprintf("%s[am] ", f.Styles.State) case gitstatus.AMRebase: - fmt.Fprintf(&f.b, "%s[am-rebase] ", f.Styles.State) + s += fmt.Sprintf("%s[am-rebase] ", f.Styles.State) case gitstatus.Merging: - fmt.Fprintf(&f.b, "%s[merge] ", f.Styles.State) + s += fmt.Sprintf("%s[merge] ", f.Styles.State) case gitstatus.CherryPicking: - fmt.Fprintf(&f.b, "%s[cherry-pick] ", f.Styles.State) + s += fmt.Sprintf("%s[cherry-pick] ", f.Styles.State) case gitstatus.Reverting: - fmt.Fprintf(&f.b, "%s[revert] ", f.Styles.State) + s += fmt.Sprintf("%s[revert] ", f.Styles.State) case gitstatus.Bisecting: - fmt.Fprintf(&f.b, "%s[bisect] ", f.Styles.State) + s += fmt.Sprintf("%s[bisect] ", f.Styles.State) case gitstatus.Default: - fmt.Fprintf(&f.b, "%s%s", f.Styles.Branch, f.Symbols.Branch) + s += fmt.Sprintf("%s%s", f.Styles.Branch, f.Symbols.Branch) } - f.currentRef() + s += f.currentRef() + return s } -func (f *Formater) remoteBranch() { +func (f *Formater) remoteBranch() string { if f.st.RemoteBranch == "" { - return + return "" } - f.clear() + s := f.Styles.Clear branch := truncate(f.st.RemoteBranch, f.Options.Ellipsis, f.Options.BranchMaxLen, f.Options.BranchTrim) - fmt.Fprintf(&f.b, "%s%s", f.Styles.Remote, branch) + s += fmt.Sprintf("%s%s", f.Styles.Remote, branch) + return s } -func (f *Formater) divergence() { +func (f *Formater) divergence() string { if f.st.BehindCount == 0 && f.st.AheadCount == 0 { - return + return "" } - f.clear() - f.b.WriteByte(' ') - fmt.Fprintf(&f.b, "%s", f.Styles.Divergence) - + s := f.Styles.Clear + f.Styles.Divergence if f.st.BehindCount != 0 { - fmt.Fprintf(&f.b, "%s%d", f.Symbols.Behind, f.st.BehindCount) + s += fmt.Sprintf("%s%d", f.Symbols.Behind, f.st.BehindCount) } if f.st.AheadCount != 0 { - fmt.Fprintf(&f.b, "%s%d", f.Symbols.Ahead, f.st.AheadCount) + s += fmt.Sprintf("%s%d", f.Symbols.Ahead, f.st.AheadCount) } -} -func (f *Formater) clear() { - // clear global style - f.b.WriteString(f.Styles.Clear) + return s } -func (f *Formater) currentRef() { - f.clear() - +func (f *Formater) currentRef() string { if f.st.IsDetached { - fmt.Fprintf(&f.b, "%s%s%s", f.Styles.Branch, f.Symbols.HashPrefix, f.st.HEAD) - return + return fmt.Sprintf("%s%s%s%s", f.Styles.Clear, f.Styles.Branch, f.Symbols.HashPrefix, f.st.HEAD) } branch := truncate(f.st.LocalBranch, f.Options.Ellipsis, f.Options.BranchMaxLen, f.Options.BranchTrim) - fmt.Fprintf(&f.b, "%s%s", f.Styles.Branch, branch) + return fmt.Sprintf("%s%s%s", f.Styles.Clear, f.Styles.Branch, branch) } -func (f *Formater) flags() { +func (f *Formater) flags() string { var flags []string if f.st.IsClean { if f.st.NumStashed != 0 { @@ -257,13 +263,11 @@ func (f *Formater) flags() { fmt.Sprintf("%s%s%d", f.Styles.Stashed, f.Symbols.Stashed, f.st.NumStashed)) } - if f.Options.HideClean != true { + if !f.Options.HideClean { flags = append(flags, fmt.Sprintf("%s%s", f.Styles.Clean, f.Symbols.Clean)) } - f.clear() - f.b.WriteString(strings.Join(flags, " ")) - return + return f.Styles.Clear + strings.Join(flags, " ") } if f.st.NumStaged != 0 { @@ -292,12 +296,13 @@ func (f *Formater) flags() { } if len(flags) > 0 { - f.clear() - f.b.WriteString(strings.Join(flags, " ")) + return f.Styles.Clear + strings.Join(flags, " ") } + + return "" } -func (f *Formater) stats() { +func (f *Formater) stats() string { stats := make([]string, 0, 2) if f.st.Insertions != 0 { @@ -308,8 +313,9 @@ func (f *Formater) stats() { stats = append(stats, fmt.Sprintf("%s%s%d", f.Styles.Deletions, f.Symbols.Deletions, f.st.Deletions)) } - if len(stats) != 0 { - f.clear() - f.b.WriteString(strings.Join(stats, " ")) + if len(stats) == 0 { + return "" } + + return f.Styles.Clear + strings.Join(stats, " ") } diff --git a/tmux/formater_test.go b/tmux/formater_test.go index dc40f5b..fce353b 100644 --- a/tmux/formater_test.go +++ b/tmux/formater_test.go @@ -25,7 +25,7 @@ func TestFlags(t *testing.T) { symbols: symbols{ Clean: "SymbolClean", }, - layout: []string{"branch", "..", "remote", " - ", "flags"}, + layout: []string{"branch", "..", "remote", "- ", "flags"}, st: &gitstatus.Status{ IsClean: true, }, @@ -62,7 +62,7 @@ func TestFlags(t *testing.T) { Stashed: "SymbolStash", Staged: "SymbolStaged", }, - layout: []string{"branch", "..", "remote", " - ", "flags"}, + layout: []string{"branch", "..", "remote", "- ", "flags"}, st: &gitstatus.Status{ NumStashed: 1, Porcelain: gitstatus.Porcelain{ @@ -98,11 +98,8 @@ func TestFlags(t *testing.T) { Config: Config{Styles: tt.styles, Symbols: tt.symbols, Layout: tt.layout}, st: tt.st, } - f.flags() - if got := f.b.String(); got != tt.want { - t.Errorf("got:\n%s\n\nwant:\n%s\n", got, tt.want) - } + compareStrings(t, tt.want, f.flags()) }) } } @@ -147,7 +144,7 @@ func TestDivergence(t *testing.T) { BehindCount: 0, }, }, - want: "StyleClear" + " ↓·4", + want: "StyleClear" + "↓·4", }, { name: "behind only", @@ -164,7 +161,7 @@ func TestDivergence(t *testing.T) { BehindCount: 12, }, }, - want: "StyleClear" + " ↑·12", + want: "StyleClear" + "↑·12", }, { name: "diverged both ways", @@ -181,7 +178,7 @@ func TestDivergence(t *testing.T) { BehindCount: 128, }, }, - want: "StyleClear" + " ↑·128↓·41", + want: "StyleClear" + "↑·128↓·41", }, } for _, tt := range tests { @@ -190,11 +187,8 @@ func TestDivergence(t *testing.T) { Config: Config{Styles: tt.styles, Symbols: tt.symbols}, st: tt.st, } - f.divergence() - if got := f.b.String(); got != tt.want { - t.Errorf("got:\n%s\n\nwant:\n%s\n", got, tt.want) - } + compareStrings(t, tt.want, f.divergence()) }) } } @@ -360,9 +354,7 @@ func Test_truncate(t *testing.T) { } for _, tt := range tests { t.Run("", func(t *testing.T) { - if got := truncate(tt.s, tt.ellipsis, tt.max, tt.dir); got != tt.want { - t.Errorf("truncate(%q, %d, %s) = %q, want %q", tt.s, tt.max, tt.dir, got, tt.want) - } + compareStrings(t, tt.want, truncate(tt.s, tt.ellipsis, tt.max, tt.dir)) }) } } @@ -391,7 +383,7 @@ func TestFormat(t *testing.T) { Clean: "SymbolClean", Modified: "SymbolMod", }, - layout: []string{"branch", "..", "remote", " - ", "flags"}, + layout: []string{"branch", " .. ", "remote", " - ", "flags"}, st: &gitstatus.Status{ Porcelain: gitstatus.Porcelain{ LocalBranch: "Local", @@ -401,7 +393,7 @@ func TestFormat(t *testing.T) { }, want: "StyleClear" + "StyleBranchSymbolBranch" + "StyleClear" + "StyleBranch" + "Local" + - "StyleClear" + ".." + + "StyleClear" + " .. " + "StyleClear" + "StyleRemoteRemote" + "StyleClear" + " - " + "StyleClear" + "StyleModSymbolMod2", @@ -419,7 +411,7 @@ func TestFormat(t *testing.T) { Ahead: "SymbolAhead", Modified: "SymbolMod", }, - layout: []string{"branch", " ~~ ", "flags"}, + layout: []string{"branch", "~~", "flags"}, st: &gitstatus.Status{ Porcelain: gitstatus.Porcelain{ LocalBranch: "Local", @@ -430,7 +422,7 @@ func TestFormat(t *testing.T) { }, want: "StyleClear" + "StyleBranchSymbolBranch" + "StyleClear" + "StyleBranch" + "Local" + - "StyleClear" + " ~~ " + + "StyleClear" + "~~" + "StyleClear" + "StyleModSymbolMod2", }, { @@ -452,8 +444,8 @@ func TestFormat(t *testing.T) { AheadCount: 1, }, }, - want: "StyleClear" + "StyleRemoteRemote" + - "StyleClear" + " SymbolAhead1", + want: "StyleClear" + "StyleRemoteRemote " + + "StyleClear" + "SymbolAhead1", }, { name: "empty", @@ -512,7 +504,7 @@ func TestFormat(t *testing.T) { symbols: symbols{ Branch: "SymbolBranch", }, - layout: []string{"branch", " ", "remote"}, + layout: []string{"branch", "remote"}, options: options{ BranchMaxLen: 9, BranchTrim: dirLeft, @@ -525,8 +517,7 @@ func TestFormat(t *testing.T) { }, }, want: "StyleClear" + "StyleBranch" + "SymbolBranch" + - "StyleClear" + "StyleBranch" + "...Branch" + - "StyleClear" + " " + + "StyleClear" + "StyleBranch" + "...Branch " + "StyleClear" + "StyleRemote" + "...Branch", }, { @@ -595,10 +586,7 @@ func TestFormat(t *testing.T) { return } - f.format() - if got := f.b.String(); got != tt.want { - t.Errorf("got:\n%s\n\nwant:\n%s\n", got, tt.want) - } + compareStrings(t, tt.want, f.format()) }) } } @@ -651,11 +639,19 @@ func Test_stats(t *testing.T) { Deletions: tt.deletions, }, } - f.stats() - if got := f.b.String(); got != tt.want { - t.Errorf("got:\n%s\n\nwant:\n%s\n", got, tt.want) - } + compareStrings(t, tt.want, f.stats()) }) } } + +func compareStrings(t *testing.T, want, got string) { + if got != want { + t.Errorf(` + got: +%q + + want: +%q`, got, want) + } +}