From 7b958c01a87b9e925be202c7f8e22b057365c8d9 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 3 Aug 2023 14:35:06 -0400 Subject: [PATCH] feat: add option to set a custom lipgloss renderer Lip Gloss defaults to `os.Stdout` to detect the background color and color profile of the terminal. This can be a problem when the application is using a different output stream like `os.Stderr` or a remote session like in a SSH server. When using an SSH server, usually the server provides to the session a `$SSH_TTY` environment variable pointing to the allocated PTY to be used for the application. The value of `$SSH_TTY` points to the terminal TTY FD that _should_ be used to run the application. Starting with Lip Gloss v0.7, you can use custom renderers to specify a different Lip Gloss output to detect the background color and color profile. This PR adds the necessary "glue" to the Bubbles in order to change the renderer being used to detect the color profile and background. --- cursor/cursor.go | 28 ++++++++++++- filepicker/filepicker.go | 74 ++++++++++++++++++++++++++-------- help/help.go | 87 ++++++++++++++++++++++++++++------------ list/list.go | 73 +++++++++++++++++++++------------ list/style.go | 22 ++++++++++ progress/progress.go | 26 +++++++++++- spinner/spinner.go | 16 ++++++++ table/table.go | 25 +++++++++++- textarea/textarea.go | 71 +++++++++++++++++++++++++------- textinput/textinput.go | 43 ++++++++++++++++---- viewport/viewport.go | 26 +++++++++++- 11 files changed, 395 insertions(+), 96 deletions(-) diff --git a/cursor/cursor.go b/cursor/cursor.go index 5abda654..6c157436 100644 --- a/cursor/cursor.go +++ b/cursor/cursor.go @@ -72,11 +72,23 @@ type Model struct { blinkTag int // mode determines the behavior of the cursor mode Mode + + re *lipgloss.Renderer +} + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the cursor. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } } // New creates a new model with default settings. -func New() Model { - return Model{ +func New(opts ...Option) Model { + m := Model{ BlinkSpeed: defaultBlinkSpeed, Blink: true, @@ -86,6 +98,18 @@ func New() Model { ctx: context.Background(), }, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.Style.Renderer(m.re) + + return m } // Update updates the cursor. diff --git a/filepicker/filepicker.go b/filepicker/filepicker.go index 2df4032c..50cbc235 100644 --- a/filepicker/filepicker.go +++ b/filepicker/filepicker.go @@ -27,9 +27,19 @@ func nextID() int { return lastID } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the filepicker. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // New returns a new filepicker model with default styling and key bindings. -func New() Model { - return Model{ +func New(opts ...Option) Model { + m := Model{ id: nextID(), CurrentDirectory: ".", Cursor: ">", @@ -46,8 +56,19 @@ func New() Model { minStack: newStack(), maxStack: newStack(), KeyMap: DefaultKeyMap(), - Styles: DefaultStyles(), } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Styles = DefaultStyles().Renderer(m.re) + + return m } type errorMsg struct { @@ -108,32 +129,51 @@ type Styles struct { EmptyDirectory lipgloss.Style } +// Renderer returns a new Styles copy with the given Lip Gloss renderer. +func (s Styles) Renderer(re *lipgloss.Renderer) Styles { + s.DisabledCursor = s.DisabledCursor.Copy().Renderer(re) + s.Cursor = s.Cursor.Copy().Renderer(re) + s.Symlink = s.Symlink.Copy().Renderer(re) + s.Directory = s.Directory.Copy().Renderer(re) + s.File = s.File.Copy().Renderer(re) + s.DisabledFile = s.DisabledFile.Copy().Renderer(re) + s.Permission = s.Permission.Copy().Renderer(re) + s.Selected = s.Selected.Copy().Renderer(re) + s.DisabledSelected = s.DisabledSelected.Copy().Renderer(re) + s.FileSize = s.FileSize.Copy().Renderer(re) + s.EmptyDirectory = s.EmptyDirectory.Copy().Renderer(re) + return s +} + // DefaultStyles defines the default styling for the file picker. func DefaultStyles() Styles { - return DefaultStylesWithRenderer(lipgloss.DefaultRenderer()) + return Styles{ + DisabledCursor: lipgloss.NewStyle().Foreground(lipgloss.Color("247")), + Cursor: lipgloss.NewStyle().Foreground(lipgloss.Color("212")), + Symlink: lipgloss.NewStyle().Foreground(lipgloss.Color("36")), + Directory: lipgloss.NewStyle().Foreground(lipgloss.Color("99")), + File: lipgloss.NewStyle(), + DisabledFile: lipgloss.NewStyle().Foreground(lipgloss.Color("243")), + DisabledSelected: lipgloss.NewStyle().Foreground(lipgloss.Color("247")), + Permission: lipgloss.NewStyle().Foreground(lipgloss.Color("244")), + Selected: lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Bold(true), + FileSize: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right), + EmptyDirectory: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."), + } } // DefaultStylesWithRenderer defines the default styling for the file picker, // with a given Lip Gloss renderer. +// +// Deprecated: use Styles.Renderer instead. func DefaultStylesWithRenderer(r *lipgloss.Renderer) Styles { - return Styles{ - DisabledCursor: r.NewStyle().Foreground(lipgloss.Color("247")), - Cursor: r.NewStyle().Foreground(lipgloss.Color("212")), - Symlink: r.NewStyle().Foreground(lipgloss.Color("36")), - Directory: r.NewStyle().Foreground(lipgloss.Color("99")), - File: r.NewStyle(), - DisabledFile: r.NewStyle().Foreground(lipgloss.Color("243")), - DisabledSelected: r.NewStyle().Foreground(lipgloss.Color("247")), - Permission: r.NewStyle().Foreground(lipgloss.Color("244")), - Selected: r.NewStyle().Foreground(lipgloss.Color("212")).Bold(true), - FileSize: r.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right), - EmptyDirectory: r.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."), - } + return DefaultStyles().Renderer(r) } // Model represents a file picker. type Model struct { id int + re *lipgloss.Renderer // Path is the path which the user has selected with the file picker. Path string diff --git a/help/help.go b/help/help.go index 8e5f77f1..a10daf1b 100644 --- a/help/help.go +++ b/help/help.go @@ -42,6 +42,43 @@ type Styles struct { FullSeparator lipgloss.Style } +// DefaultStyles returns a set of default styles for the help bubble. +func DefaultStyles() Styles { + keyStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#909090", + Dark: "#626262", + }) + descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#B2B2B2", + Dark: "#4A4A4A", + }) + sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ + Light: "#DDDADA", + Dark: "#3C3C3C", + }) + return Styles{ + ShortKey: keyStyle, + ShortDesc: descStyle, + ShortSeparator: sepStyle, + Ellipsis: sepStyle.Copy(), + FullKey: keyStyle.Copy(), + FullDesc: descStyle.Copy(), + FullSeparator: sepStyle.Copy(), + } +} + +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Ellipsis = s.Ellipsis.Copy().Renderer(r) + s.ShortKey = s.ShortKey.Copy().Renderer(r) + s.ShortDesc = s.ShortDesc.Copy().Renderer(r) + s.ShortSeparator = s.ShortSeparator.Copy().Renderer(r) + s.FullKey = s.FullKey.Copy().Renderer(r) + s.FullDesc = s.FullDesc.Copy().Renderer(r) + s.FullSeparator = s.FullSeparator.Copy().Renderer(r) + return s +} + // Model contains the state of the help view. type Model struct { Width int @@ -55,39 +92,39 @@ type Model struct { Ellipsis string Styles Styles -} -// New creates a new help view with some useful defaults. -func New() Model { - keyStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#909090", - Dark: "#626262", - }) + re *lipgloss.Renderer +} - descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#B2B2B2", - Dark: "#4A4A4A", - }) +// Option is used to set options in New. +type Option func(*Model) - sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{ - Light: "#DDDADA", - Dark: "#3C3C3C", - }) +// WithRenderer sets the Lip Gloss renderer for the help model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} - return Model{ +// New creates a new help view with some useful defaults. +func New(opts ...Option) Model { + m := Model{ ShortSeparator: " • ", FullSeparator: " ", Ellipsis: "…", - Styles: Styles{ - ShortKey: keyStyle, - ShortDesc: descStyle, - ShortSeparator: sepStyle, - Ellipsis: sepStyle.Copy(), - FullKey: keyStyle.Copy(), - FullDesc: descStyle.Copy(), - FullSeparator: sepStyle.Copy(), - }, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Styles = DefaultStyles().Renderer(m.re) + + return m } // NewModel creates a new help view with some useful defaults. diff --git a/list/list.go b/list/list.go index 7b47879f..e7a29ff1 100644 --- a/list/list.go +++ b/list/list.go @@ -134,8 +134,20 @@ func (f FilterState) String() string { }[f] } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the list model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model contains the state of this component. type Model struct { + re *lipgloss.Renderer + showTitle bool showFilter bool showStatusBar bool @@ -195,8 +207,37 @@ 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(items []Item, delegate ItemDelegate, width, height int, opts ...Option) Model { + m := Model{ + showTitle: true, + showFilter: true, + showStatusBar: true, + showPagination: true, + showHelp: true, + itemNameSingular: "item", + itemNamePlural: "items", + filteringEnabled: true, + KeyMap: DefaultKeyMap(), + Filter: DefaultFilter, + Title: "List", + StatusMessageLifetime: time.Second, + + width: width, + height: height, + delegate: delegate, + items: items, + Help: help.New(), + } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + styles := DefaultStyles().Renderer(m.re) sp := spinner.New() sp.Spinner = spinner.Line @@ -214,30 +255,10 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model { p.ActiveDot = styles.ActivePaginationDot.String() p.InactiveDot = styles.InactivePaginationDot.String() - m := Model{ - showTitle: true, - showFilter: true, - showStatusBar: true, - showPagination: true, - showHelp: true, - itemNameSingular: "item", - itemNamePlural: "items", - filteringEnabled: true, - KeyMap: DefaultKeyMap(), - Filter: DefaultFilter, - Styles: styles, - Title: "List", - FilterInput: filterInput, - StatusMessageLifetime: time.Second, - - width: width, - height: height, - delegate: delegate, - items: items, - Paginator: p, - spinner: sp, - Help: help.New(), - } + m.Styles = styles + m.FilterInput = filterInput + m.Paginator = p + m.spinner = sp m.updatePagination() m.updateKeybindings() diff --git a/list/style.go b/list/style.go index e4451f87..500a35b2 100644 --- a/list/style.go +++ b/list/style.go @@ -39,6 +39,28 @@ type Styles struct { DividerDot lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.TitleBar = s.TitleBar.Copy().Renderer(r) + s.Title = s.Title.Copy().Renderer(r) + s.Spinner = s.Spinner.Copy().Renderer(r) + s.FilterPrompt = s.FilterPrompt.Copy().Renderer(r) + s.FilterCursor = s.FilterCursor.Copy().Renderer(r) + s.DefaultFilterCharacterMatch = s.DefaultFilterCharacterMatch.Copy().Renderer(r) + s.StatusBar = s.StatusBar.Copy().Renderer(r) + s.StatusEmpty = s.StatusEmpty.Copy().Renderer(r) + s.StatusBarActiveFilter = s.StatusBarActiveFilter.Copy().Renderer(r) + s.StatusBarFilterCount = s.StatusBarFilterCount.Copy().Renderer(r) + s.NoItems = s.NoItems.Copy().Renderer(r) + s.PaginationStyle = s.PaginationStyle.Copy().Renderer(r) + s.HelpStyle = s.HelpStyle.Copy().Renderer(r) + s.ActivePaginationDot = s.ActivePaginationDot.Copy().Renderer(r) + s.InactivePaginationDot = s.InactivePaginationDot.Copy().Renderer(r) + s.ArabicPagination = s.ArabicPagination.Copy().Renderer(r) + s.DividerDot = s.DividerDot.Copy().Renderer(r) + return s +} + // DefaultStyles returns a set of default style definitions for this list // component. func DefaultStyles() (s Styles) { diff --git a/progress/progress.go b/progress/progress.go index 871d35dc..31bbed45 100644 --- a/progress/progress.go +++ b/progress/progress.go @@ -108,12 +108,22 @@ func WithSpringOptions(frequency, damping float64) Option { } // WithColorProfile sets the color profile to use for the progress bar. +// +// Deprecated: use [WithRenderer] to set a custom Lip Gloss renderer that uses +// a specific color profile. func WithColorProfile(p termenv.Profile) Option { return func(m *Model) { m.colorProfile = p } } +// WithRenderer sets the Lip Gloss renderer for the progress model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // FrameMsg indicates that an animation step should occur. type FrameMsg struct { id int @@ -164,6 +174,9 @@ type Model struct { // Color profile for the progress bar. colorProfile termenv.Profile + + // The Lipgloss renderer for the cursor. + re *lipgloss.Renderer } // New returns a model with default values. @@ -177,7 +190,7 @@ func New(opts ...Option) Model { EmptyColor: "#606060", ShowPercentage: true, PercentFormat: " %3.0f%%", - colorProfile: termenv.ColorProfile(), + colorProfile: -1, } if !m.springCustomized { m.SetSpringOptions(defaultFrequency, defaultDamping) @@ -186,6 +199,17 @@ func New(opts ...Option) Model { for _, opt := range opts { opt(&m) } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + // Set the renderer on the percentage style + m.PercentageStyle = m.PercentageStyle.Copy().Renderer(m.re) + if m.colorProfile < 0 { + m.colorProfile = m.re.ColorProfile() + } + return m } diff --git a/spinner/spinner.go b/spinner/spinner.go index bb53597f..0f786d7c 100644 --- a/spinner/spinner.go +++ b/spinner/spinner.go @@ -105,6 +105,9 @@ type Model struct { frame int id int tag int + + // The renderer for the spinner. If nil, the default renderer will be used. + re *lipgloss.Renderer } // ID returns the spinner's unique ID. @@ -123,6 +126,12 @@ func New(opts ...Option) Model { opt(&m) } + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.Style.Copy().Renderer(m.re) + return m } @@ -228,3 +237,10 @@ func WithStyle(style lipgloss.Style) Option { m.Style = style } } + +// WithRenderer sets the Lip Gloss renderer for the spinner. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} diff --git a/table/table.go b/table/table.go index 36549e3b..2acb2d45 100644 --- a/table/table.go +++ b/table/table.go @@ -23,6 +23,10 @@ type Model struct { viewport viewport.Model start int end int + + // The renderer is used to render the table styles. If nil, the default + // renderer will be used. + re *lipgloss.Renderer } // Row represents one line in the table. @@ -94,6 +98,14 @@ type Styles struct { Selected lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Header = s.Header.Copy().Renderer(r) + s.Cell = s.Cell.Copy().Renderer(r) + s.Selected = s.Selected.Copy().Renderer(r) + return s +} + // DefaultStyles returns a set of default style definitions for this table. func DefaultStyles() Styles { return Styles{ @@ -114,6 +126,13 @@ func (m *Model) SetStyles(s Styles) { // table := New(WithColumns([]Column{{Title: "ID", Width: 10}})) type Option func(*Model) +// WithRenderer sets the Lip Gloss renderer for the table model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // New creates a new model for the table widget. func New(opts ...Option) Model { m := Model{ @@ -121,13 +140,17 @@ func New(opts ...Option) Model { viewport: viewport.New(0, 20), KeyMap: DefaultKeyMap(), - styles: DefaultStyles(), } for _, opt := range opts { opt(&m) } + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.styles = DefaultStyles().Renderer(m.re) m.UpdateViewport() return m diff --git a/textarea/textarea.go b/textarea/textarea.go index b614a900..a50f98e0 100644 --- a/textarea/textarea.go +++ b/textarea/textarea.go @@ -111,14 +111,14 @@ type LineInfo struct { CharOffset int } -// Style that will be applied to the text area. +// Styles that will be applied to the text area. // -// Style can be applied to focused and unfocused states to change the styles +// Styles can be applied to focused and unfocused states to change the styles // depending on the focus state. // // For an introduction to styling with Lip Gloss see: // https://github.com/charmbracelet/lipgloss -type Style struct { +type Styles struct { Base lipgloss.Style CursorLine lipgloss.Style CursorLineNumber lipgloss.Style @@ -129,6 +129,34 @@ type Style struct { Text lipgloss.Style } +// Renderer returns a copy of Styles with the given Lip Gloss renderer set. +func (s Styles) Renderer(r *lipgloss.Renderer) Styles { + s.Base = s.Base.Copy().Renderer(r) + s.CursorLine = s.CursorLine.Copy().Renderer(r) + s.CursorLineNumber = s.CursorLineNumber.Copy().Renderer(r) + s.EndOfBuffer = s.EndOfBuffer.Copy().Renderer(r) + s.LineNumber = s.LineNumber.Copy().Renderer(r) + s.Placeholder = s.Placeholder.Copy().Renderer(r) + s.Prompt = s.Prompt.Copy().Renderer(r) + s.Text = s.Text.Copy().Renderer(r) + return s +} + +// Style is an alias for Styles. +// +// Deprecated: Use Styles instead. +type Style = Styles + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the textarea model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model is the Bubble Tea model for this text area element. type Model struct { Err error @@ -159,13 +187,13 @@ type Model struct { // Styling. FocusedStyle and BlurredStyle are used to style the textarea in // focused and blurred states. - FocusedStyle Style - BlurredStyle Style + FocusedStyle Styles + BlurredStyle Styles // style is the current styling to use. // It is used to abstract the differences in focus state when styling the // model, since we can simply assign the set of styles to this variable // when switching focus states. - style *Style + style *Styles // Cursor is the text area cursor. Cursor cursor.Model @@ -224,24 +252,22 @@ type Model struct { // rune sanitizer for input. rsan runeutil.Sanitizer + + // The Lip Gloss renderer to be used for styling. + re *lipgloss.Renderer } // New creates a new model with default settings. -func New() Model { +func New(opts ...Option) Model { vp := viewport.New(0, 0) vp.KeyMap = viewport.KeyMap{} cur := cursor.New() - focusedStyle, blurredStyle := DefaultStyles() - m := Model{ CharLimit: defaultCharLimit, MaxHeight: defaultMaxHeight, MaxWidth: defaultMaxWidth, Prompt: lipgloss.ThickBorder().Left + " ", - style: &blurredStyle, - FocusedStyle: focusedStyle, - BlurredStyle: blurredStyle, EndOfBufferCharacter: '~', ShowLineNumbers: true, Cursor: cur, @@ -256,6 +282,21 @@ func New() Model { viewport: &vp, } + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + focusedStyle, blurredStyle := DefaultStyles() + focusedStyle = focusedStyle.Renderer(m.re) + blurredStyle = blurredStyle.Renderer(m.re) + m.style = &blurredStyle + m.FocusedStyle = focusedStyle + m.BlurredStyle = blurredStyle + m.SetHeight(defaultHeight) m.SetWidth(defaultWidth) @@ -264,8 +305,8 @@ func New() Model { // DefaultStyles returns the default styles for focused and blurred states for // the textarea. -func DefaultStyles() (Style, Style) { - focused := Style{ +func DefaultStyles() (Styles, Styles) { + focused := Styles{ Base: lipgloss.NewStyle(), CursorLine: lipgloss.NewStyle().Background(lipgloss.AdaptiveColor{Light: "255", Dark: "0"}), CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "240"}), @@ -275,7 +316,7 @@ func DefaultStyles() (Style, Style) { Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")), Text: lipgloss.NewStyle(), } - blurred := Style{ + blurred := Styles{ Base: lipgloss.NewStyle(), CursorLine: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}), CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}), diff --git a/textinput/textinput.go b/textinput/textinput.go index f8990f66..c1410126 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -134,22 +134,49 @@ type Model struct { // rune sanitizer for input. rsan runeutil.Sanitizer + + // The lipgloss renderer used for the styles. + re *lipgloss.Renderer +} + +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the textinput model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } } // New creates a new model with default settings. -func New() Model { - return Model{ - Prompt: "> ", - EchoCharacter: '*', - CharLimit: 0, - PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), - Cursor: cursor.New(), - KeyMap: DefaultKeyMap, +func New(opts ...Option) Model { + m := Model{ + Prompt: "> ", + EchoCharacter: '*', + CharLimit: 0, + Cursor: cursor.New(), + KeyMap: DefaultKeyMap, value: nil, focus: false, pos: 0, } + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.PromptStyle = m.re.NewStyle() + m.TextStyle = m.re.NewStyle() + m.PlaceholderStyle = m.re.NewStyle().Foreground(lipgloss.Color("240")) + m.CursorStyle = m.re.NewStyle() + + return m } // NewModel creates a new model with default settings. diff --git a/viewport/viewport.go b/viewport/viewport.go index b13e33c0..1b749ff4 100644 --- a/viewport/viewport.go +++ b/viewport/viewport.go @@ -11,13 +11,34 @@ 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(width, height int, opts ...Option) (m Model) { m.Width = width m.Height = height + + for _, opt := range opts { + opt(&m) + } + + if m.re == nil { + m.re = lipgloss.DefaultRenderer() + } + + m.Style = m.re.NewStyle() + m.setInitialValues() return m } +// Option is used to set options in New. +type Option func(*Model) + +// WithRenderer sets the Lip Gloss renderer for the viewport model. +func WithRenderer(r *lipgloss.Renderer) Option { + return func(m *Model) { + m.re = r + } +} + // Model is the Bubble Tea model for this viewport element. type Model struct { Width int @@ -54,6 +75,9 @@ type Model struct { initialized bool lines []string + + // The lipgloss renderer used for the styles. + re *lipgloss.Renderer } func (m *Model) setInitialValues() {