From ee5636d39a32b20186fdcd8936d6ea4aa7ecc287 Mon Sep 17 00:00:00 2001 From: Roland Rodriguez Date: Fri, 6 Dec 2024 12:51:43 -0600 Subject: [PATCH 1/2] feat(help): add styled whitespace with configurable background Added WhitespaceStyle fields to both short and full help Styles struct, llowing customization of whitespace appearance between keys and descriptions. Previously whitespace was unstyled, now it matches the aesthetic of other elements and can be customized (e.g. with background colors for testing). - Added ShortWhitespace and FullWhitespace to Styles struct - Modified ShortHelpView to use styled whitespace between key and desc - Modified FullHelpView to use styled whitespace between key and desc - Added tests to verify whitespace styling behavior Fixes #571 --- help/help.go | 35 +++--- help/help_whitespace_test.go | 102 ++++++++++++++++++ .../custom_style_full.golden | 3 + .../custom_style_short.golden | 1 + .../disabled_item_full.golden | 1 + .../disabled_item_short.golden | 1 + .../full_help_width_20.golden | 1 + .../full_help_width_30.golden | 2 + .../full_help_width_40.golden | 3 + .../short_help_width_20.golden | 1 + .../short_help_width_30.golden | 1 + .../short_help_width_40.golden | 1 + 12 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 help/help_whitespace_test.go create mode 100644 help/testdata/TestWhitespaceStyle/custom_style_full.golden create mode 100644 help/testdata/TestWhitespaceStyle/custom_style_short.golden create mode 100644 help/testdata/TestWhitespaceStyle/disabled_item_full.golden create mode 100644 help/testdata/TestWhitespaceStyle/disabled_item_short.golden create mode 100644 help/testdata/TestWhitespaceStyle/full_help_width_20.golden create mode 100644 help/testdata/TestWhitespaceStyle/full_help_width_30.golden create mode 100644 help/testdata/TestWhitespaceStyle/full_help_width_40.golden create mode 100644 help/testdata/TestWhitespaceStyle/short_help_width_20.golden create mode 100644 help/testdata/TestWhitespaceStyle/short_help_width_30.golden create mode 100644 help/testdata/TestWhitespaceStyle/short_help_width_40.golden diff --git a/help/help.go b/help/help.go index 85afbee13..807c5ec47 100644 --- a/help/help.go +++ b/help/help.go @@ -31,14 +31,16 @@ type Styles struct { Ellipsis lipgloss.Style // Styling for the short help - ShortKey lipgloss.Style - ShortDesc lipgloss.Style - ShortSeparator lipgloss.Style + ShortKey lipgloss.Style + ShortDesc lipgloss.Style + ShortSeparator lipgloss.Style + ShortWhitespace lipgloss.Style // Styling for the full help - FullKey lipgloss.Style - FullDesc lipgloss.Style - FullSeparator lipgloss.Style + FullKey lipgloss.Style + FullDesc lipgloss.Style + FullSeparator lipgloss.Style + FullWhitespace lipgloss.Style } // Model contains the state of the help view. @@ -78,13 +80,15 @@ func New() Model { FullSeparator: " ", Ellipsis: "…", Styles: Styles{ - ShortKey: keyStyle, - ShortDesc: descStyle, - ShortSeparator: sepStyle, - Ellipsis: sepStyle, - FullKey: keyStyle, - FullDesc: descStyle, - FullSeparator: sepStyle, + ShortKey: keyStyle, + ShortDesc: descStyle, + ShortSeparator: sepStyle, + ShortWhitespace: sepStyle, + Ellipsis: sepStyle, + FullKey: keyStyle, + FullDesc: descStyle, + FullSeparator: sepStyle, + FullWhitespace: sepStyle, }, } } @@ -132,7 +136,8 @@ func (m Model) ShortHelpView(bindings []key.Binding) string { // Item str := sep + - m.Styles.ShortKey.Inline(true).Render(kb.Help().Key) + " " + + m.Styles.ShortKey.Inline(true).Render(kb.Help().Key) + + m.Styles.ShortWhitespace.Inline(true).Render(" ") + m.Styles.ShortDesc.Inline(true).Render(kb.Help().Desc) w := lipgloss.Width(str) @@ -197,7 +202,7 @@ func (m Model) FullHelpView(groups [][]key.Binding) string { col := lipgloss.JoinHorizontal(lipgloss.Top, sep, m.Styles.FullKey.Render(strings.Join(keys, "\n")), - " ", + m.Styles.FullWhitespace.Render(" "), m.Styles.FullDesc.Render(strings.Join(descriptions, "\n")), ) w := lipgloss.Width(col) diff --git a/help/help_whitespace_test.go b/help/help_whitespace_test.go new file mode 100644 index 000000000..8f8b8c258 --- /dev/null +++ b/help/help_whitespace_test.go @@ -0,0 +1,102 @@ +package help + +import ( + "fmt" + "testing" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/exp/golden" +) + +func TestWhitespaceStyle(t *testing.T) { + m := New() + m.FullSeparator = " | " + + // Set a distinctive background color for whitespace to make it visible in tests + whitespaceBg := lipgloss.Color("#FF0000") + m.Styles.ShortWhitespace = m.Styles.ShortWhitespace.Background(whitespaceBg) + m.Styles.FullWhitespace = m.Styles.FullWhitespace.Background(whitespaceBg) + + // Standard keys setup + k := key.WithKeys("x") + kb := [][]key.Binding{ + { + key.NewBinding(k, key.WithHelp("enter", "continue")), + }, + { + key.NewBinding(k, key.WithHelp("esc", "back")), + key.NewBinding(k, key.WithHelp("?", "help")), + }, + { + key.NewBinding(k, key.WithHelp("H", "home")), + key.NewBinding(k, key.WithHelp("ctrl+c", "quit")), + key.NewBinding(k, key.WithHelp("ctrl+l", "log")), + }, + } + + // Test both views at different widths + for _, w := range []int{20, 30, 40} { + t.Run(fmt.Sprintf("full_help_width_%d", w), func(t *testing.T) { + m.Width = w + s := m.FullHelpView(kb) + golden.RequireEqual(t, []byte(s)) + }) + + t.Run(fmt.Sprintf("short_help_width_%d", w), func(t *testing.T) { + m.Width = w + // Flatten the bindings for short help + var shortBindings []key.Binding + for _, group := range kb { + shortBindings = append(shortBindings, group...) + } + s := m.ShortHelpView(shortBindings) + golden.RequireEqual(t, []byte(s)) + }) + } + +// Test with a disabled item and custom style +for _, tc := range []struct { + name string + setupFn func() + bindings [][]key.Binding +}{ + { + name: "disabled_item", + setupFn: func() { + m.Width = 40 + }, + bindings: [][]key.Binding{{ + key.NewBinding(k, key.WithHelp("enter", "continue")), + key.NewBinding(k, key.WithHelp("ctrl+c", "quit"), key.WithDisabled()), + }}, + }, + { + name: "custom_style", + setupFn: func() { + m.Width = 40 + customBg := lipgloss.Color("#00FF00") + m.Styles.FullWhitespace = m.Styles.FullWhitespace.Background(customBg) + m.Styles.ShortWhitespace = m.Styles.ShortWhitespace.Background(customBg) + }, + bindings: kb, + }, +} { + t.Run(tc.name+"_full", func(t *testing.T) { + tc.setupFn() + s := m.FullHelpView(tc.bindings) + golden.RequireEqual(t, []byte(s)) + }) + + t.Run(tc.name+"_short", func(t *testing.T) { + tc.setupFn() + // Flatten the bindings for short help + var shortBindings []key.Binding + for _, group := range tc.bindings { + shortBindings = append(shortBindings, group...) + } + s := m.ShortHelpView(shortBindings) + golden.RequireEqual(t, []byte(s)) + }) +} +} diff --git a/help/testdata/TestWhitespaceStyle/custom_style_full.golden b/help/testdata/TestWhitespaceStyle/custom_style_full.golden new file mode 100644 index 000000000..e0227d09c --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/custom_style_full.golden @@ -0,0 +1,3 @@ +enter continue | esc back | H home + ? help ctrl+c quit + ctrl+l log \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/custom_style_short.golden b/help/testdata/TestWhitespaceStyle/custom_style_short.golden new file mode 100644 index 000000000..878907574 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/custom_style_short.golden @@ -0,0 +1 @@ +enter continue • esc back • ? help … \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/disabled_item_full.golden b/help/testdata/TestWhitespaceStyle/disabled_item_full.golden new file mode 100644 index 000000000..b80d3cde9 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/disabled_item_full.golden @@ -0,0 +1 @@ +enter continue \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/disabled_item_short.golden b/help/testdata/TestWhitespaceStyle/disabled_item_short.golden new file mode 100644 index 000000000..b80d3cde9 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/disabled_item_short.golden @@ -0,0 +1 @@ +enter continue \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/full_help_width_20.golden b/help/testdata/TestWhitespaceStyle/full_help_width_20.golden new file mode 100644 index 000000000..e8c569b13 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/full_help_width_20.golden @@ -0,0 +1 @@ +enter continue … \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/full_help_width_30.golden b/help/testdata/TestWhitespaceStyle/full_help_width_30.golden new file mode 100644 index 000000000..1183a10ec --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/full_help_width_30.golden @@ -0,0 +1,2 @@ +enter continue | esc back … + ? help \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/full_help_width_40.golden b/help/testdata/TestWhitespaceStyle/full_help_width_40.golden new file mode 100644 index 000000000..e0227d09c --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/full_help_width_40.golden @@ -0,0 +1,3 @@ +enter continue | esc back | H home + ? help ctrl+c quit + ctrl+l log \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/short_help_width_20.golden b/help/testdata/TestWhitespaceStyle/short_help_width_20.golden new file mode 100644 index 000000000..e8c569b13 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/short_help_width_20.golden @@ -0,0 +1 @@ +enter continue … \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/short_help_width_30.golden b/help/testdata/TestWhitespaceStyle/short_help_width_30.golden new file mode 100644 index 000000000..cda71348d --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/short_help_width_30.golden @@ -0,0 +1 @@ +enter continue • esc back … \ No newline at end of file diff --git a/help/testdata/TestWhitespaceStyle/short_help_width_40.golden b/help/testdata/TestWhitespaceStyle/short_help_width_40.golden new file mode 100644 index 000000000..878907574 --- /dev/null +++ b/help/testdata/TestWhitespaceStyle/short_help_width_40.golden @@ -0,0 +1 @@ +enter continue • esc back • ? help … \ No newline at end of file From a2a300a0065e3f212a59db1bb77b493ee2854aef Mon Sep 17 00:00:00 2001 From: Roland Rodriguez Date: Fri, 6 Dec 2024 13:00:00 -0600 Subject: [PATCH 2/2] fix(fmt): format test file --- help/help_whitespace_test.go | 86 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/help/help_whitespace_test.go b/help/help_whitespace_test.go index 8f8b8c258..b3c4efc44 100644 --- a/help/help_whitespace_test.go +++ b/help/help_whitespace_test.go @@ -55,48 +55,48 @@ func TestWhitespaceStyle(t *testing.T) { }) } -// Test with a disabled item and custom style -for _, tc := range []struct { - name string - setupFn func() - bindings [][]key.Binding -}{ - { - name: "disabled_item", - setupFn: func() { - m.Width = 40 - }, - bindings: [][]key.Binding{{ - key.NewBinding(k, key.WithHelp("enter", "continue")), - key.NewBinding(k, key.WithHelp("ctrl+c", "quit"), key.WithDisabled()), - }}, - }, - { - name: "custom_style", - setupFn: func() { - m.Width = 40 - customBg := lipgloss.Color("#00FF00") - m.Styles.FullWhitespace = m.Styles.FullWhitespace.Background(customBg) - m.Styles.ShortWhitespace = m.Styles.ShortWhitespace.Background(customBg) - }, - bindings: kb, - }, -} { - t.Run(tc.name+"_full", func(t *testing.T) { - tc.setupFn() - s := m.FullHelpView(tc.bindings) - golden.RequireEqual(t, []byte(s)) - }) + // Test with a disabled item and custom style + for _, tc := range []struct { + name string + setupFn func() + bindings [][]key.Binding + }{ + { + name: "disabled_item", + setupFn: func() { + m.Width = 40 + }, + bindings: [][]key.Binding{{ + key.NewBinding(k, key.WithHelp("enter", "continue")), + key.NewBinding(k, key.WithHelp("ctrl+c", "quit"), key.WithDisabled()), + }}, + }, + { + name: "custom_style", + setupFn: func() { + m.Width = 40 + customBg := lipgloss.Color("#00FF00") + m.Styles.FullWhitespace = m.Styles.FullWhitespace.Background(customBg) + m.Styles.ShortWhitespace = m.Styles.ShortWhitespace.Background(customBg) + }, + bindings: kb, + }, + } { + t.Run(tc.name+"_full", func(t *testing.T) { + tc.setupFn() + s := m.FullHelpView(tc.bindings) + golden.RequireEqual(t, []byte(s)) + }) - t.Run(tc.name+"_short", func(t *testing.T) { - tc.setupFn() - // Flatten the bindings for short help - var shortBindings []key.Binding - for _, group := range tc.bindings { - shortBindings = append(shortBindings, group...) - } - s := m.ShortHelpView(shortBindings) - golden.RequireEqual(t, []byte(s)) - }) -} + t.Run(tc.name+"_short", func(t *testing.T) { + tc.setupFn() + // Flatten the bindings for short help + var shortBindings []key.Binding + for _, group := range tc.bindings { + shortBindings = append(shortBindings, group...) + } + s := m.ShortHelpView(shortBindings) + golden.RequireEqual(t, []byte(s)) + }) + } }