diff --git a/filepicker/filepicker.go b/filepicker/filepicker.go index 9a1235a..5ca5608 100644 --- a/filepicker/filepicker.go +++ b/filepicker/filepicker.go @@ -29,7 +29,7 @@ func nextID() int { } // New returns a new filepicker model with default styling and key bindings. -func New() Model { +func New(ctx *tea.Context) Model { return Model{ id: nextID(), CurrentDirectory: ".", @@ -49,7 +49,7 @@ func New() Model { minStack: newStack(), maxStack: newStack(), KeyMap: DefaultKeyMap(), - Styles: DefaultStyles(), + Styles: DefaultStyles(ctx.Renderer), } } @@ -112,13 +112,7 @@ type Styles struct { } // DefaultStyles defines the default styling for the file picker. -func DefaultStyles() Styles { - return DefaultStylesWithRenderer(lipgloss.DefaultRenderer()) -} - -// DefaultStylesWithRenderer defines the default styling for the file picker, -// with a given Lip Gloss renderer. -func DefaultStylesWithRenderer(r *lipgloss.Renderer) Styles { +func DefaultStyles(r *lipgloss.Renderer) Styles { return Styles{ DisabledCursor: r.NewStyle().Foreground(lipgloss.Color("247")), Cursor: r.NewStyle().Foreground(lipgloss.Color("212")), diff --git a/help/help.go b/help/help.go index 8e5f77f..1ff24df 100644 --- a/help/help.go +++ b/help/help.go @@ -15,7 +15,6 @@ import ( // Note that if a key is disabled (via key.Binding.SetEnabled) it will not be // rendered in the help view, so in theory generated help should self-manage. type KeyMap interface { - // ShortHelp returns a slice of bindings to be displayed in the short // version of the help. The help bubble will render help in the order in // which the help items are returned here. @@ -58,18 +57,19 @@ type Model struct { } // New creates a new help view with some useful defaults. -func New() Model { - keyStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ +func New(ctx *tea.Context) Model { + r := ctx.Renderer + keyStyle := r.NewStyle().Foreground(lipgloss.AdaptiveColor{ Light: "#909090", Dark: "#626262", }) - descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + descStyle := r.NewStyle().Foreground(lipgloss.AdaptiveColor{ Light: "#B2B2B2", Dark: "#4A4A4A", }) - sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + sepStyle := r.NewStyle().Foreground(lipgloss.AdaptiveColor{ Light: "#DDDADA", Dark: "#3C3C3C", }) @@ -118,7 +118,7 @@ func (m Model) ShortHelpView(bindings []key.Binding) string { var b strings.Builder var totalWidth int - var separator = m.Styles.ShortSeparator.Inline(true).Render(m.ShortSeparator) + separator := m.Styles.ShortSeparator.Inline(true).Render(m.ShortSeparator) for i, kb := range bindings { if !kb.Enabled() { diff --git a/list/list.go b/list/list.go index 7b47879..afb8f5d 100644 --- a/list/list.go +++ b/list/list.go @@ -195,14 +195,14 @@ type Model struct { } // New returns a new model with sensible defaults. -func New(items []Item, delegate ItemDelegate, width, height int) Model { - styles := DefaultStyles() +func New(ctx *tea.Context, items []Item, delegate ItemDelegate, width, height int) Model { + styles := DefaultStyles(ctx.Renderer) sp := spinner.New() sp.Spinner = spinner.Line sp.Style = styles.Spinner - filterInput := textinput.New() + filterInput := textinput.New(ctx) filterInput.Prompt = "Filter: " filterInput.PromptStyle = styles.FilterPrompt filterInput.Cursor.Style = styles.FilterCursor @@ -236,7 +236,7 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { items: items, Paginator: p, spinner: sp, - Help: help.New(), + Help: help.New(ctx), } m.updatePagination() diff --git a/list/list_test.go b/list/list_test.go index 2c925aa..516e6bd 100644 --- a/list/list_test.go +++ b/list/list_test.go @@ -7,6 +7,7 @@ import ( "testing" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) type item string @@ -29,7 +30,8 @@ func (d itemDelegate) Render(w io.Writer, m Model, index int, listItem Item) { } func TestStatusBarItemName(t *testing.T) { - list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + ctx := &tea.Context{Renderer: lipgloss.DefaultRenderer()} + list := New(ctx, []Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) expected := "2 items" if !strings.Contains(list.statusView(), expected) { t.Fatalf("Error: expected view to contain %s", expected) @@ -43,7 +45,8 @@ func TestStatusBarItemName(t *testing.T) { } func TestStatusBarWithoutItems(t *testing.T) { - list := New([]Item{}, itemDelegate{}, 10, 10) + ctx := &tea.Context{Renderer: lipgloss.DefaultRenderer()} + list := New(ctx, []Item{}, itemDelegate{}, 10, 10) expected := "No items" if !strings.Contains(list.statusView(), expected) { @@ -52,7 +55,8 @@ func TestStatusBarWithoutItems(t *testing.T) { } func TestCustomStatusBarItemName(t *testing.T) { - list := New([]Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) + ctx := &tea.Context{Renderer: lipgloss.DefaultRenderer()} + list := New(ctx, []Item{item("foo"), item("bar")}, itemDelegate{}, 10, 10) list.SetStatusBarItemName("connection", "connections") expected := "2 connections" diff --git a/list/style.go b/list/style.go index e4451f8..f97b1a0 100644 --- a/list/style.go +++ b/list/style.go @@ -41,57 +41,57 @@ type Styles struct { // DefaultStyles returns a set of default style definitions for this list // component. -func DefaultStyles() (s Styles) { +func DefaultStyles(r *lipgloss.Renderer) (s Styles) { verySubduedColor := lipgloss.AdaptiveColor{Light: "#DDDADA", Dark: "#3C3C3C"} subduedColor := lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"} - s.TitleBar = lipgloss.NewStyle().Padding(0, 0, 1, 2) + s.TitleBar = r.NewStyle().Padding(0, 0, 1, 2) - s.Title = lipgloss.NewStyle(). + s.Title = r.NewStyle(). Background(lipgloss.Color("62")). Foreground(lipgloss.Color("230")). Padding(0, 1) - s.Spinner = lipgloss.NewStyle(). + s.Spinner = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#8E8E8E", Dark: "#747373"}) - s.FilterPrompt = lipgloss.NewStyle(). + s.FilterPrompt = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"}) - s.FilterCursor = lipgloss.NewStyle(). + s.FilterCursor = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}) - s.DefaultFilterCharacterMatch = lipgloss.NewStyle().Underline(true) + s.DefaultFilterCharacterMatch = r.NewStyle().Underline(true) - s.StatusBar = lipgloss.NewStyle(). + s.StatusBar = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}). Padding(0, 0, 1, 2) - s.StatusEmpty = lipgloss.NewStyle().Foreground(subduedColor) + s.StatusEmpty = r.NewStyle().Foreground(subduedColor) - s.StatusBarActiveFilter = lipgloss.NewStyle(). + s.StatusBarActiveFilter = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"}) - s.StatusBarFilterCount = lipgloss.NewStyle().Foreground(verySubduedColor) + s.StatusBarFilterCount = r.NewStyle().Foreground(verySubduedColor) - s.NoItems = lipgloss.NewStyle(). + s.NoItems = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"}) - s.ArabicPagination = lipgloss.NewStyle().Foreground(subduedColor) + s.ArabicPagination = r.NewStyle().Foreground(subduedColor) - s.PaginationStyle = lipgloss.NewStyle().PaddingLeft(2) //nolint:gomnd + s.PaginationStyle = r.NewStyle().PaddingLeft(2) //nolint:gomnd - s.HelpStyle = lipgloss.NewStyle().Padding(1, 0, 0, 2) + s.HelpStyle = r.NewStyle().Padding(1, 0, 0, 2) - s.ActivePaginationDot = lipgloss.NewStyle(). + s.ActivePaginationDot = r.NewStyle(). Foreground(lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}). SetString(bullet) - s.InactivePaginationDot = lipgloss.NewStyle(). + s.InactivePaginationDot = r.NewStyle(). Foreground(verySubduedColor). SetString(bullet) - s.DividerDot = lipgloss.NewStyle(). + s.DividerDot = r.NewStyle(). Foreground(verySubduedColor). SetString(" " + bullet + " ") diff --git a/table/table.go b/table/table.go index 36549e3..f81a61e 100644 --- a/table/table.go +++ b/table/table.go @@ -115,10 +115,10 @@ func (m *Model) SetStyles(s Styles) { type Option func(*Model) // New creates a new model for the table widget. -func New(opts ...Option) Model { +func New(ctx *tea.Context, opts ...Option) Model { m := Model{ cursor: 0, - viewport: viewport.New(0, 20), + viewport: viewport.New(ctx, 0, 20), KeyMap: DefaultKeyMap(), styles: DefaultStyles(), @@ -380,7 +380,7 @@ func (m *Model) FromValues(value, separator string) { } func (m Model) headersView() string { - var s = make([]string, 0, len(m.cols)) + s := make([]string, 0, len(m.cols)) for _, col := range m.cols { style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true) renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…")) @@ -390,7 +390,7 @@ func (m Model) headersView() string { } func (m *Model) renderRow(rowID int) string { - var s = make([]string, 0, len(m.cols)) + s := make([]string, 0, len(m.cols)) for i, value := range m.rows[rowID] { style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true) renderedCell := m.styles.Cell.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…"))) diff --git a/table/table_test.go b/table/table_test.go index c2929c6..b67c4b8 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -1,10 +1,16 @@ package table -import "testing" +import ( + "testing" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) func TestFromValues(t *testing.T) { input := "foo1,bar1\nfoo2,bar2\nfoo3,bar3" - table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) + ctx := &tea.Context{Renderer: lipgloss.DefaultRenderer()} + table := New(ctx, WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) table.FromValues(input, ",") if len(table.rows) != 3 { @@ -22,8 +28,9 @@ func TestFromValues(t *testing.T) { } func TestFromValuesWithTabSeparator(t *testing.T) { + ctx := &tea.Context{Renderer: lipgloss.DefaultRenderer()} input := "foo1.\tbar1\nfoo,bar,baz\tbar,2" - table := New(WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) + table := New(ctx, WithColumns([]Column{{Title: "Foo"}, {Title: "Bar"}})) table.FromValues(input, "\t") if len(table.rows) != 2 { diff --git a/textarea/textarea.go b/textarea/textarea.go index 28ac39b..f5f115a 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -29,8 +29,10 @@ const ( ) // Internal messages for clipboard operations. -type pasteMsg string -type pasteErrMsg struct{ error } +type ( + pasteMsg string + pasteErrMsg struct{ error } +) // KeyMap is the key bindings for different actions within the textarea. type KeyMap struct { @@ -244,12 +246,12 @@ type Model struct { } // New creates a new model with default settings. -func New(r *tea.Context) Model { - vp := viewport.New(0, 0) +func New(ctx *tea.Context) Model { + vp := viewport.New(ctx, 0, 0) vp.KeyMap = viewport.KeyMap{} cur := cursor.New() - focusedStyle, blurredStyle := DefaultStyles(r.Renderer) + focusedStyle, blurredStyle := DefaultStyles(ctx.Renderer) m := Model{ CharLimit: defaultCharLimit, @@ -605,8 +607,7 @@ func (m *Model) transposeLeft() { if m.col >= len(m.value[m.row]) { m.SetCursor(m.col - 1) } - m.value[m.row][m.col-1], m.value[m.row][m.col] = - m.value[m.row][m.col], m.value[m.row][m.col-1] + m.value[m.row][m.col-1], m.value[m.row][m.col] = m.value[m.row][m.col], m.value[m.row][m.col-1] if m.col < len(m.value[m.row]) { m.SetCursor(m.col + 1) } diff --git a/textarea/textarea_test.go b/textarea/textarea_test.go index 461fc50..ea31a64 100644 --- a/textarea/textarea_test.go +++ b/textarea/textarea_test.go @@ -5,6 +5,7 @@ import ( "testing" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) func TestNew(t *testing.T) { @@ -429,7 +430,9 @@ func TestRendersEndOfLineBuffer(t *testing.T) { } func newTextArea() Model { - textarea := New() + textarea := New(&tea.Context{ + Renderer: lipgloss.DefaultRenderer(), + }) textarea.Prompt = "> " textarea.Placeholder = "Hello, World!" diff --git a/textinput/textinput.go b/textinput/textinput.go index 24e0b6e..8f65895 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -17,8 +17,10 @@ import ( ) // Internal messages for clipboard operations. -type pasteMsg string -type pasteErrMsg struct{ error } +type ( + pasteMsg string + pasteErrMsg struct{ error } +) // EchoMode sets the input behavior of the text input field. type EchoMode int @@ -153,14 +155,15 @@ type Model struct { } // New creates a new model with default settings. -func New() Model { +func New(ctx *tea.Context) Model { + r := ctx.Renderer return Model{ Prompt: "> ", EchoCharacter: '*', CharLimit: 0, - PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + PlaceholderStyle: r.NewStyle().Foreground(lipgloss.Color("240")), ShowSuggestions: false, - CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), + CompletionStyle: r.NewStyle().Foreground(lipgloss.Color("240")), Cursor: cursor.New(), KeyMap: DefaultKeyMap, diff --git a/viewport/viewport.go b/viewport/viewport.go index 0a5a73a..4b95bad 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -11,9 +11,10 @@ import ( // New returns a new model with the given width and height as well as default // key mappings. -func New(width, height int) (m Model) { +func New(ctx *tea.Context, width, height int) (m Model) { m.Width = width m.Height = height + m.renderer = ctx.Renderer m.setInitialValues() return m } @@ -54,6 +55,7 @@ type Model struct { initialized bool lines []string + renderer *lipgloss.Renderer } func (m *Model) setInitialValues() { @@ -372,7 +374,7 @@ func (m Model) View() string { } contentWidth := w - m.Style.GetHorizontalFrameSize() contentHeight := h - m.Style.GetVerticalFrameSize() - contents := lipgloss.NewStyle(). + contents := m.renderer.NewStyle(). Width(contentWidth). // pad to width. Height(contentHeight). // pad to height. MaxHeight(contentHeight). // truncate height if taller.