chore: conform to the proposed bubble tea API

This commit is contained in:
Ayman Bagabas
2024-05-13 14:39:34 -04:00
parent f4f674440a
commit d115a6d4d6
17 changed files with 183 additions and 194 deletions
+7 -4
View File
@@ -75,21 +75,24 @@ type Model struct {
}
// New creates a new model with default settings.
func New() Model {
func New(ctx tea.Context) Model {
return Model{
Style: ctx.NewStyle(),
TextStyle: ctx.NewStyle(),
BlinkSpeed: defaultBlinkSpeed,
Blink: true,
mode: CursorBlink,
blinkCtx: &blinkCtx{
ctx: context.Background(),
ctx: ctx,
},
}
}
// Update updates the cursor.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case initialBlinkMsg:
// We accept all initialBlinkMsgs generated by the Blink command.
@@ -203,7 +206,7 @@ func (m *Model) SetChar(char string) {
}
// View displays the cursor.
func (m Model) View() string {
func (m Model) View(tea.Context) string {
if m.Blink {
return m.TextStyle.Inline(true).Render(m.char)
}
+17 -23
View File
@@ -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),
}
}
@@ -112,25 +112,19 @@ 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(ctx tea.Context) 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."),
DisabledCursor: ctx.NewStyle().Foreground(lipgloss.Color("247")),
Cursor: ctx.NewStyle().Foreground(lipgloss.Color("212")),
Symlink: ctx.NewStyle().Foreground(lipgloss.Color("36")),
Directory: ctx.NewStyle().Foreground(lipgloss.Color("99")),
File: ctx.NewStyle(),
DisabledFile: ctx.NewStyle().Foreground(lipgloss.Color("243")),
DisabledSelected: ctx.NewStyle().Foreground(lipgloss.Color("247")),
Permission: ctx.NewStyle().Foreground(lipgloss.Color("244")),
Selected: ctx.NewStyle().Foreground(lipgloss.Color("212")).Bold(true),
FileSize: ctx.NewStyle().Foreground(lipgloss.Color("240")).Width(fileSizeWidth).Align(lipgloss.Right),
EmptyDirectory: ctx.NewStyle().Foreground(lipgloss.Color("240")).PaddingLeft(paddingLeft).SetString("Bummer. No Files Found."),
}
}
@@ -236,12 +230,12 @@ func (m Model) readDir(path string, showHidden bool) tea.Cmd {
}
// Init initializes the file picker model.
func (m Model) Init() tea.Cmd {
func (m Model) Init(tea.Context) tea.Cmd {
return m.readDir(m.CurrentDirectory, m.ShowHidden)
}
// Update handles user interactions within the file picker model.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(_ tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case readDirMsg:
if msg.id != m.id {
@@ -363,7 +357,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View returns the view of the file picker.
func (m Model) View() string {
func (m Model) View(tea.Context) string {
if len(m.files) == 0 {
return m.Styles.EmptyDirectory.Height(m.Height).MaxHeight(m.Height).String()
}
+1 -5
View File
@@ -2,13 +2,11 @@ module github.com/charmbracelet/bubbles
go 1.18
replace github.com/charmbracelet/bubbletea => ../bubbletea
require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbletea v0.25.1-0.20240306212323-3df8b37dba50
github.com/charmbracelet/bubbletea v0.26.3-0.20240510203954-a434f25ff7d3
github.com/charmbracelet/harmonica v0.2.0
github.com/charmbracelet/lipgloss v0.10.1-0.20240510203806-a6dc2ab509a8
github.com/dustin/go-humanize v1.0.1
@@ -35,5 +33,3 @@ require (
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)
replace github.com/charmbracelet/x/exp/term => ../x/exp/term
+2
View File
@@ -6,6 +6,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbletea v0.26.3-0.20240510203954-a434f25ff7d3 h1:Bg0eRBAgsWh84rpAq5X+e52TMAfloxoWeSJkYp9Pyw0=
github.com/charmbracelet/bubbletea v0.26.3-0.20240510203954-a434f25ff7d3/go.mod h1:jxN9TKmttdNsUKWSjjQQonHIY4nCuTBNdkDn3/Bpf7A=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.10.1-0.20240510203806-a6dc2ab509a8 h1:aOfL0f8fhgqKe+iOIQYAIazSwTO4J0axB5ztPxw5JHY=
+5 -6
View File
@@ -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,18 @@ 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 {
keyStyle := ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{
Light: "#909090",
Dark: "#626262",
})
descStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{
descStyle := ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{
Light: "#B2B2B2",
Dark: "#4A4A4A",
})
sepStyle := lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{
sepStyle := ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{
Light: "#DDDADA",
Dark: "#3C3C3C",
})
@@ -118,7 +117,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() {
+7 -7
View File
@@ -32,15 +32,15 @@ type DefaultItemStyles struct {
// NewDefaultItemStyles returns style definitions for a default item. See
// DefaultItemView for when these come into play.
func NewDefaultItemStyles() (s DefaultItemStyles) {
s.NormalTitle = lipgloss.NewStyle().
func NewDefaultItemStyles(ctx tea.Context) (s DefaultItemStyles) {
s.NormalTitle = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"}).
Padding(0, 0, 0, 2)
s.NormalDesc = s.NormalTitle.Copy().
Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"})
s.SelectedTitle = lipgloss.NewStyle().
s.SelectedTitle = ctx.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(lipgloss.AdaptiveColor{Light: "#F793FF", Dark: "#AD58B4"}).
Foreground(lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"}).
@@ -49,14 +49,14 @@ func NewDefaultItemStyles() (s DefaultItemStyles) {
s.SelectedDesc = s.SelectedTitle.Copy().
Foreground(lipgloss.AdaptiveColor{Light: "#F793FF", Dark: "#AD58B4"})
s.DimmedTitle = lipgloss.NewStyle().
s.DimmedTitle = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}).
Padding(0, 0, 0, 2)
s.DimmedDesc = s.DimmedTitle.Copy().
Foreground(lipgloss.AdaptiveColor{Light: "#C2B8C2", Dark: "#4D4D4D"})
s.FilterMatch = lipgloss.NewStyle().Underline(true)
s.FilterMatch = ctx.NewStyle().Underline(true)
return s
}
@@ -92,10 +92,10 @@ type DefaultDelegate struct {
}
// NewDefaultDelegate creates a new delegate with default styles.
func NewDefaultDelegate() DefaultDelegate {
func NewDefaultDelegate(ctx tea.Context) DefaultDelegate {
return DefaultDelegate{
ShowDescription: true,
Styles: NewDefaultItemStyles(),
Styles: NewDefaultItemStyles(ctx),
height: 2,
spacing: 1,
}
+20 -17
View File
@@ -49,7 +49,7 @@ type ItemDelegate interface {
// loop will pass through here except when the user is setting a filter.
// Use this method to perform item-level updates appropriate to this
// delegate.
Update(msg tea.Msg, m *Model) tea.Cmd
Update(ctx tea.Context, msg tea.Msg, m *Model) tea.Cmd
}
type filteredItem struct {
@@ -192,24 +192,26 @@ type Model struct {
filteredItems filteredItems
delegate ItemDelegate
ctx tea.Context
}
// 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)
sp := spinner.New()
sp := spinner.New(ctx)
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
filterInput.CharLimit = 64
filterInput.Focus()
p := paginator.New()
p := paginator.New(ctx)
p.Type = paginator.Dots
p.ActiveDot = styles.ActivePaginationDot.String()
p.InactiveDot = styles.InactivePaginationDot.String()
@@ -236,7 +238,8 @@ func New(items []Item, delegate ItemDelegate, width, height int) Model {
items: items,
Paginator: p,
spinner: sp,
Help: help.New(),
Help: help.New(ctx),
ctx: ctx,
}
m.updatePagination()
@@ -768,7 +771,7 @@ func (m *Model) hideStatusMessage() {
}
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
@@ -782,7 +785,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
case spinner.TickMsg:
newSpinnerModel, cmd := m.spinner.Update(msg)
newSpinnerModel, cmd := m.spinner.Update(ctx, msg)
m.spinner = newSpinnerModel
if m.showSpinner {
cmds = append(cmds, cmd)
@@ -859,7 +862,7 @@ func (m *Model) handleBrowsing(msg tea.Msg) tea.Cmd {
}
}
cmd := m.delegate.Update(msg, m)
cmd := m.delegate.Update(m.ctx, msg, m)
cmds = append(cmds, cmd)
// Keep the index in bounds when paginating
@@ -909,7 +912,7 @@ func (m *Model) handleFiltering(msg tea.Msg) tea.Cmd {
}
// Update the filter text input component
newFilterInputModel, inputCmd := m.FilterInput.Update(msg)
newFilterInputModel, inputCmd := m.FilterInput.Update(m.ctx, msg)
filterChanged := m.FilterInput.Value() != newFilterInputModel.Value()
m.FilterInput = newFilterInputModel
cmds = append(cmds, inputCmd)
@@ -1003,7 +1006,7 @@ func (m Model) FullHelp() [][]key.Binding {
}
// View renders the component.
func (m Model) View() string {
func (m Model) View(ctx tea.Context) string {
var (
sections []string
availHeight = m.height
@@ -1033,7 +1036,7 @@ func (m Model) View() string {
availHeight -= lipgloss.Height(help)
}
content := lipgloss.NewStyle().Height(availHeight).Render(m.populatedView())
content := ctx.NewStyle().Height(availHeight).Render(m.populatedView())
sections = append(sections, content)
if m.showPagination {
@@ -1062,7 +1065,7 @@ func (m Model) titleView() string {
// If the filter's showing, draw that. Otherwise draw the title.
if m.showFilter && m.filterState == Filtering {
view += m.FilterInput.View()
view += m.FilterInput.View(m.ctx)
} else if m.showTitle {
if m.showSpinner && spinnerOnLeft {
view += spinnerView + spinnerLeftGap
@@ -1147,13 +1150,13 @@ func (m Model) paginationView() string {
return ""
}
s := m.Paginator.View()
s := m.Paginator.View(m.ctx)
// If the dot pagination is wider than the width of the window
// use the arabic paginator.
if ansi.PrintableRuneWidth(s) > m.width {
m.Paginator.Type = paginator.Arabic
s = m.Styles.ArabicPagination.Render(m.Paginator.View())
s = m.Styles.ArabicPagination.Render(m.Paginator.View(m.ctx))
}
style := m.Styles.PaginationStyle
@@ -1209,7 +1212,7 @@ func (m Model) helpView() string {
}
func (m Model) spinnerView() string {
return m.spinner.View()
return m.spinner.View(m.ctx)
}
func filterItems(m Model) tea.Cmd {
+19 -18
View File
@@ -1,6 +1,7 @@
package list
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
@@ -41,57 +42,57 @@ type Styles struct {
// DefaultStyles returns a set of default style definitions for this list
// component.
func DefaultStyles() (s Styles) {
func DefaultStyles(ctx tea.Context) (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 = ctx.NewStyle().Padding(0, 0, 1, 2)
s.Title = lipgloss.NewStyle().
s.Title = ctx.NewStyle().
Background(lipgloss.Color("62")).
Foreground(lipgloss.Color("230")).
Padding(0, 1)
s.Spinner = lipgloss.NewStyle().
s.Spinner = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#8E8E8E", Dark: "#747373"})
s.FilterPrompt = lipgloss.NewStyle().
s.FilterPrompt = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"})
s.FilterCursor = lipgloss.NewStyle().
s.FilterCursor = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"})
s.DefaultFilterCharacterMatch = lipgloss.NewStyle().Underline(true)
s.DefaultFilterCharacterMatch = ctx.NewStyle().Underline(true)
s.StatusBar = lipgloss.NewStyle().
s.StatusBar = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#A49FA5", Dark: "#777777"}).
Padding(0, 0, 1, 2)
s.StatusEmpty = lipgloss.NewStyle().Foreground(subduedColor)
s.StatusEmpty = ctx.NewStyle().Foreground(subduedColor)
s.StatusBarActiveFilter = lipgloss.NewStyle().
s.StatusBarActiveFilter = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#1a1a1a", Dark: "#dddddd"})
s.StatusBarFilterCount = lipgloss.NewStyle().Foreground(verySubduedColor)
s.StatusBarFilterCount = ctx.NewStyle().Foreground(verySubduedColor)
s.NoItems = lipgloss.NewStyle().
s.NoItems = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#909090", Dark: "#626262"})
s.ArabicPagination = lipgloss.NewStyle().Foreground(subduedColor)
s.ArabicPagination = ctx.NewStyle().Foreground(subduedColor)
s.PaginationStyle = lipgloss.NewStyle().PaddingLeft(2) //nolint:gomnd
s.PaginationStyle = ctx.NewStyle().PaddingLeft(2) //nolint:gomnd
s.HelpStyle = lipgloss.NewStyle().Padding(1, 0, 0, 2)
s.HelpStyle = ctx.NewStyle().Padding(1, 0, 0, 2)
s.ActivePaginationDot = lipgloss.NewStyle().
s.ActivePaginationDot = ctx.NewStyle().
Foreground(lipgloss.AdaptiveColor{Light: "#847A85", Dark: "#979797"}).
SetString(bullet)
s.InactivePaginationDot = lipgloss.NewStyle().
s.InactivePaginationDot = ctx.NewStyle().
Foreground(verySubduedColor).
SetString(bullet)
s.DividerDot = lipgloss.NewStyle().
s.DividerDot = ctx.NewStyle().
Foreground(verySubduedColor).
SetString(" " + bullet + " ")
+3 -3
View File
@@ -131,7 +131,7 @@ func (m Model) OnFirstPage() bool {
}
// New creates a new model with defaults.
func New() Model {
func New(ctx tea.Context) Model {
return Model{
Type: Arabic,
Page: 0,
@@ -150,7 +150,7 @@ func New() Model {
var NewModel = New
// Update is the Tea update function which binds keystrokes to pagination.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
@@ -165,7 +165,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View renders the pagination to a string.
func (m Model) View() string {
func (m Model) View(tea.Context) string {
switch m.Type {
case Dots:
return m.dotsView()
+14 -31
View File
@@ -12,7 +12,6 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/lucasb-eyer/go-colorful"
"github.com/muesli/reflow/ansi"
"github.com/muesli/termenv"
)
// Internal ID management. Used during animating to assure that frame messages
@@ -115,13 +114,6 @@ func WithSpringOptions(frequency, damping float64) Option {
}
}
// WithColorProfile sets the color profile to use for the progress bar.
func WithColorProfile(p termenv.Profile) Option {
return func(m *Model) {
m.colorProfile = p
}
}
// FrameMsg indicates that an animation step should occur.
type FrameMsg struct {
id int
@@ -169,13 +161,10 @@ type Model struct {
// of the progress bar. When false, the width of the gradient will be set
// to the full width of the progress bar.
scaleRamp bool
// Color profile for the progress bar.
colorProfile termenv.Profile
}
// New returns a model with default values.
func New(opts ...Option) Model {
func New(ctx tea.Context, opts ...Option) Model {
m := Model{
id: nextID(),
Width: defaultWidth,
@@ -185,7 +174,6 @@ func New(opts ...Option) Model {
EmptyColor: "#606060",
ShowPercentage: true,
PercentFormat: " %3.0f%%",
colorProfile: termenv.ColorProfile(),
}
if !m.springCustomized {
m.SetSpringOptions(defaultFrequency, defaultDamping)
@@ -203,15 +191,15 @@ func New(opts ...Option) Model {
var NewModel = New
// Init exists to satisfy the tea.Model interface.
func (m Model) Init() tea.Cmd {
return nil
func (m Model) Init(tea.Context) (tea.Model, tea.Cmd) {
return m, nil
}
// Update is used to animate the progress bar during transitions. Use
// SetPercent to create the command you'll need to trigger the animation.
//
// If you're rendering with ViewAs you won't need this.
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case FrameMsg:
if msg.id != m.id || msg.tag != m.tag {
@@ -275,15 +263,15 @@ func (m *Model) DecrPercent(v float64) tea.Cmd {
// View renders an animated progress bar in its current state. To render
// a static progress bar based on your own calculations use ViewAs instead.
func (m Model) View() string {
return m.ViewAs(m.percentShown)
func (m Model) View(ctx tea.Context) string {
return m.ViewAs(ctx, m.percentShown)
}
// ViewAs renders the progress bar with a given percentage.
func (m Model) ViewAs(percent float64) string {
func (m Model) ViewAs(ctx tea.Context, percent float64) string {
b := strings.Builder{}
percentView := m.percentageView(percent)
m.barView(&b, percent, ansi.PrintableRuneWidth(percentView))
m.barView(ctx, &b, percent, ansi.PrintableRuneWidth(percentView))
b.WriteString(percentView)
return b.String()
}
@@ -294,7 +282,7 @@ func (m *Model) nextFrame() tea.Cmd {
})
}
func (m Model) barView(b *strings.Builder, percent float64, textWidth int) {
func (m Model) barView(ctx tea.Context, b *strings.Builder, percent float64, textWidth int) {
var (
tw = max(0, m.Width-textWidth) // total width
fw = int(math.Round((float64(tw) * percent))) // filled width
@@ -317,20 +305,19 @@ func (m Model) barView(b *strings.Builder, percent float64, textWidth int) {
p = float64(i) / float64(tw-1)
}
c := m.rampColorA.BlendLuv(m.rampColorB, p).Hex()
b.WriteString(termenv.
String(string(m.Full)).
Foreground(m.color(c)).
String(),
b.WriteString(ctx.NewStyle().
Foreground(lipgloss.Color(c)).
Render(string(m.Full)),
)
}
} else {
// Solid fill
s := termenv.String(string(m.Full)).Foreground(m.color(m.FullColor)).String()
s := ctx.NewStyle().Foreground(lipgloss.Color(m.FullColor)).Render(string(m.Full))
b.WriteString(strings.Repeat(s, fw))
}
// Empty fill
e := termenv.String(string(m.Empty)).Foreground(m.color(m.EmptyColor)).String()
e := ctx.NewStyle().Foreground(lipgloss.Color(m.EmptyColor)).Render(string(m.Empty))
n := max(0, tw-fw)
b.WriteString(strings.Repeat(e, n))
}
@@ -358,10 +345,6 @@ func (m *Model) setRamp(colorA, colorB string, scaled bool) {
m.rampColorB = b
}
func (m Model) color(c string) termenv.Color {
return m.colorProfile.Color(c)
}
func max(a, b int) int {
if a > b {
return a
+4 -3
View File
@@ -113,9 +113,10 @@ func (m Model) ID() int {
}
// New returns a model with default values.
func New(opts ...Option) Model {
func New(ctx tea.Context, opts ...Option) Model {
m := Model{
Spinner: Line,
Style: ctx.NewStyle(),
id: nextID(),
}
@@ -139,7 +140,7 @@ type TickMsg struct {
}
// Update is the Tea update function.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case TickMsg:
// If an ID is set, and the ID doesn't belong to this spinner, reject
@@ -168,7 +169,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View renders the model's view.
func (m Model) View() string {
func (m Model) View(ctx tea.Context) string {
if m.frame >= len(m.Spinner.Frames) {
return "(error)"
}
+6 -6
View File
@@ -55,7 +55,7 @@ type Model struct {
// NewWithInterval creates a new stopwatch with the given timeout and tick
// interval.
func NewWithInterval(interval time.Duration) Model {
func NewWithInterval(_ tea.Context, interval time.Duration) Model {
return Model{
Interval: interval,
id: nextID(),
@@ -63,8 +63,8 @@ func NewWithInterval(interval time.Duration) Model {
}
// New creates a new stopwatch with 1s interval.
func New() Model {
return NewWithInterval(time.Second)
func New(ctx tea.Context) Model {
return NewWithInterval(ctx, time.Second)
}
// ID returns the unique ID of the model.
@@ -73,7 +73,7 @@ func (m Model) ID() int {
}
// Init starts the stopwatch.
func (m Model) Init() tea.Cmd {
func (m Model) Init(tea.Context) tea.Cmd {
return m.Start()
}
@@ -112,7 +112,7 @@ func (m Model) Running() bool {
}
// Update handles the timer tick.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(_ tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case StartStopMsg:
if msg.ID != m.id {
@@ -141,7 +141,7 @@ func (m Model) Elapsed() time.Duration {
}
// View of the timer component.
func (m Model) View() string {
func (m Model) View(tea.Context) string {
return m.d.String()
}
+18 -14
View File
@@ -24,6 +24,8 @@ type Model struct {
viewport viewport.Model
start int
end int
ctx tea.Context
}
// Row represents one line in the table.
@@ -109,11 +111,11 @@ type Styles struct {
}
// DefaultStyles returns a set of default style definitions for this table.
func DefaultStyles() Styles {
func DefaultStyles(ctx tea.Context) Styles {
return Styles{
Selected: lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")),
Header: lipgloss.NewStyle().Bold(true).Padding(0, 1),
Cell: lipgloss.NewStyle().Padding(0, 1),
Selected: ctx.NewStyle().Bold(true).Foreground(lipgloss.Color("212")),
Header: ctx.NewStyle().Bold(true).Padding(0, 1),
Cell: ctx.NewStyle().Padding(0, 1),
}
}
@@ -129,13 +131,15 @@ 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(),
styles: DefaultStyles(ctx),
ctx: ctx,
}
for _, opt := range opts {
@@ -204,7 +208,7 @@ func WithKeyMap(km KeyMap) Option {
}
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
return m, nil
}
@@ -255,8 +259,8 @@ func (m *Model) Blur() {
}
// View renders the component.
func (m Model) View() string {
return m.headersView() + "\n" + m.viewport.View()
func (m Model) View(ctx tea.Context) string {
return m.headersView() + "\n" + m.viewport.View(ctx)
}
// UpdateViewport updates the list content based on the previously defined
@@ -409,12 +413,12 @@ func (m *Model) FromValues(value, separator string) {
type StyleFunc func(row, col int, value string) lipgloss.Style
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 {
if col.Width <= 0 {
continue
}
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
style := m.ctx.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
s = append(s, m.styles.Header.Render(renderedCell))
}
@@ -422,7 +426,7 @@ func (m Model) headersView() string {
}
func (m *Model) renderRow(r int) string {
var s = make([]string, 0, len(m.cols))
s := make([]string, 0, len(m.cols))
for i, value := range m.rows[r] {
if m.cols[i].Width <= 0 {
continue
@@ -437,7 +441,7 @@ func (m *Model) renderRow(r int) string {
cellStyle = m.styles.Cell
}
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
style := m.ctx.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
renderedCell := cellStyle.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…")))
s = append(s, renderedCell)
}
+32 -32
View File
@@ -245,12 +245,12 @@ type Model struct {
}
// New creates a new model with default settings.
func New() 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()
cur := cursor.New(ctx)
focusedStyle, blurredStyle := DefaultStyles()
focusedStyle, blurredStyle := DefaultStyles(ctx)
m := Model{
CharLimit: defaultCharLimit,
@@ -283,26 +283,26 @@ func New() Model {
// DefaultStyles returns the default styles for focused and blurred states for
// the textarea.
func DefaultStyles() (Style, Style) {
func DefaultStyles(ctx tea.Context) (Style, Style) {
focused := Style{
Base: lipgloss.NewStyle(),
CursorLine: lipgloss.NewStyle().Background(lipgloss.AdaptiveColor{Light: "255", Dark: "0"}),
CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "240"}),
EndOfBuffer: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "254", Dark: "0"}),
LineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
Text: lipgloss.NewStyle(),
Base: ctx.NewStyle(),
CursorLine: ctx.NewStyle().Background(lipgloss.AdaptiveColor{Light: "255", Dark: "0"}),
CursorLineNumber: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "240"}),
EndOfBuffer: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "254", Dark: "0"}),
LineNumber: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
Placeholder: ctx.NewStyle().Foreground(lipgloss.Color("240")),
Prompt: ctx.NewStyle().Foreground(lipgloss.Color("7")),
Text: ctx.NewStyle(),
}
blurred := Style{
Base: lipgloss.NewStyle(),
CursorLine: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}),
CursorLineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
EndOfBuffer: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "254", Dark: "0"}),
LineNumber: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
Text: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}),
Base: ctx.NewStyle(),
CursorLine: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}),
CursorLineNumber: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
EndOfBuffer: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "254", Dark: "0"}),
LineNumber: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "249", Dark: "7"}),
Placeholder: ctx.NewStyle().Foreground(lipgloss.Color("240")),
Prompt: ctx.NewStyle().Foreground(lipgloss.Color("7")),
Text: ctx.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "245", Dark: "7"}),
}
return focused, blurred
@@ -927,7 +927,7 @@ func (m *Model) SetHeight(h int) {
}
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
m.Cursor.Blur()
return m, nil
@@ -1047,12 +1047,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.Err = msg
}
vp, cmd := m.viewport.Update(msg)
vp, cmd := m.viewport.Update(ctx, msg)
m.viewport = &vp
cmds = append(cmds, cmd)
newRow, newCol := m.cursorLineNumber(), m.col
m.Cursor, cmd = m.Cursor.Update(msg)
m.Cursor, cmd = m.Cursor.Update(ctx, msg)
if (newRow != oldRow || newCol != oldCol) && m.Cursor.Mode() == cursor.CursorBlink {
m.Cursor.Blink = false
cmd = m.Cursor.BlinkCmd()
@@ -1065,9 +1065,9 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View renders the text area in its current state.
func (m Model) View() string {
func (m Model) View(ctx tea.Context) string {
if m.Value() == "" && m.row == 0 && m.col == 0 && m.Placeholder != "" {
return m.placeholderView()
return m.placeholderView(ctx)
}
m.Cursor.TextStyle = m.style.CursorLine
@@ -1126,10 +1126,10 @@ func (m Model) View() string {
s.WriteString(style.Render(string(wrappedLine[:lineInfo.ColumnOffset])))
if m.col >= len(line) && lineInfo.CharOffset >= m.width {
m.Cursor.SetChar(" ")
s.WriteString(m.Cursor.View())
s.WriteString(m.Cursor.View(ctx))
} else {
m.Cursor.SetChar(string(wrappedLine[lineInfo.ColumnOffset]))
s.WriteString(style.Render(m.Cursor.View()))
s.WriteString(style.Render(m.Cursor.View(ctx)))
s.WriteString(style.Render(string(wrappedLine[lineInfo.ColumnOffset+1:])))
}
} else {
@@ -1154,7 +1154,7 @@ func (m Model) View() string {
}
m.viewport.SetContent(s.String())
return m.style.Base.Render(m.viewport.View())
return m.style.Base.Render(m.viewport.View(ctx))
}
func (m Model) getPromptString(displayLine int) (prompt string) {
@@ -1171,7 +1171,7 @@ func (m Model) getPromptString(displayLine int) (prompt string) {
}
// placeholderView returns the prompt and placeholder view, if any.
func (m Model) placeholderView() string {
func (m Model) placeholderView(ctx tea.Context) string {
var (
s strings.Builder
p = rw.Truncate(rw.Truncate(m.Placeholder, m.width, "..."), m.width, "")
@@ -1188,7 +1188,7 @@ func (m Model) placeholderView() string {
m.Cursor.TextStyle = m.style.Placeholder
m.Cursor.SetChar(string(p[0]))
s.WriteString(m.style.CursorLine.Render(m.Cursor.View()))
s.WriteString(m.style.CursorLine.Render(m.Cursor.View(ctx)))
// The rest of the placeholder text
s.WriteString(m.style.CursorLine.Render(style.Render(p[1:] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(p))))))
@@ -1207,7 +1207,7 @@ func (m Model) placeholderView() string {
}
m.viewport.SetContent(s.String())
return m.style.Base.Render(m.viewport.View())
return m.style.Base.Render(m.viewport.View(ctx))
}
// Blink returns the blink command for the cursor.
+17 -14
View File
@@ -155,15 +155,18 @@ type Model struct {
}
// New creates a new model with default settings.
func New() Model {
func New(ctx tea.Context) Model {
return Model{
Prompt: "> ",
EchoCharacter: '*',
CharLimit: 0,
PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
PromptStyle: ctx.NewStyle(),
TextStyle: ctx.NewStyle(),
CursorStyle: ctx.NewStyle(),
PlaceholderStyle: ctx.NewStyle().Foreground(lipgloss.Color("240")),
ShowSuggestions: false,
CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
Cursor: cursor.New(),
CompletionStyle: ctx.NewStyle().Foreground(lipgloss.Color("240")),
Cursor: cursor.New(ctx),
KeyMap: DefaultKeyMap,
suggestions: [][]rune{},
@@ -549,7 +552,7 @@ func (m Model) echoTransform(v string) string {
}
// Update is the Bubble Tea update loop.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(ctx tea.Context, msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
return m, nil
}
@@ -636,7 +639,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
m.Cursor, cmd = m.Cursor.Update(msg)
m.Cursor, cmd = m.Cursor.Update(ctx, msg)
cmds = append(cmds, cmd)
if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink {
@@ -649,10 +652,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View renders the textinput in its current state.
func (m Model) View() string {
func (m Model) View(ctx tea.Context) string {
// Placeholder text
if len(m.value) == 0 && m.Placeholder != "" {
return m.placeholderView()
return m.placeholderView(ctx)
}
styleText := m.TextStyle.Inline(true).Render
@@ -664,7 +667,7 @@ func (m Model) View() string {
if pos < len(value) {
char := m.echoTransform(string(value[pos]))
m.Cursor.SetChar(char)
v += m.Cursor.View() // cursor and text under it
v += m.Cursor.View(ctx) // cursor and text under it
v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor
v += m.completionView(0) // suggested completion
} else {
@@ -673,15 +676,15 @@ func (m Model) View() string {
if len(value) < len(suggestion) {
m.Cursor.TextStyle = m.CompletionStyle
m.Cursor.SetChar(m.echoTransform(string(suggestion[pos])))
v += m.Cursor.View()
v += m.Cursor.View(ctx)
v += m.completionView(1)
} else {
m.Cursor.SetChar(" ")
v += m.Cursor.View()
v += m.Cursor.View(ctx)
}
} else {
m.Cursor.SetChar(" ")
v += m.Cursor.View()
v += m.Cursor.View(ctx)
}
}
@@ -700,7 +703,7 @@ func (m Model) View() string {
}
// placeholderView returns the prompt and placeholder view, if any.
func (m Model) placeholderView() string {
func (m Model) placeholderView(ctx tea.Context) string {
var (
v string
p = []rune(m.Placeholder)
@@ -709,7 +712,7 @@ func (m Model) placeholderView() string {
m.Cursor.TextStyle = m.PlaceholderStyle
m.Cursor.SetChar(string(p[:1]))
v += m.Cursor.View()
v += m.Cursor.View(ctx)
// If the entire placeholder is already set and no padding is needed, finish
if m.Width < 1 && len(p) <= 1 {
+6 -6
View File
@@ -90,7 +90,7 @@ type Model struct {
}
// NewWithInterval creates a new timer with the given timeout and tick interval.
func NewWithInterval(timeout, interval time.Duration) Model {
func NewWithInterval(_ tea.Context, timeout, interval time.Duration) Model {
return Model{
Timeout: timeout,
Interval: interval,
@@ -100,8 +100,8 @@ func NewWithInterval(timeout, interval time.Duration) Model {
}
// New creates a new timer with the given timeout and default 1s interval.
func New(timeout time.Duration) Model {
return NewWithInterval(timeout, time.Second)
func New(ctx tea.Context, timeout time.Duration) Model {
return NewWithInterval(ctx, timeout, time.Second)
}
// ID returns the model's identifier. This can be used to determine if messages
@@ -125,12 +125,12 @@ func (m Model) Timedout() bool {
}
// Init starts the timer.
func (m Model) Init() tea.Cmd {
func (m Model) Init(tea.Context) tea.Cmd {
return m.tick()
}
// Update handles the timer tick.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(_ tea.Context, msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case StartStopMsg:
if msg.ID != 0 && msg.ID != m.id {
@@ -151,7 +151,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
// View of the timer component.
func (m Model) View() string {
func (m Model) View(tea.Context) string {
return m.Timeout.String()
}
+5 -5
View File
@@ -11,7 +11,7 @@ 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.setInitialValues()
@@ -64,7 +64,7 @@ func (m *Model) setInitialValues() {
}
// Init exists to satisfy the tea.Model interface for composability purposes.
func (m Model) Init() tea.Cmd {
func (m Model) Init(tea.Context) tea.Cmd {
return nil
}
@@ -276,7 +276,7 @@ func ViewUp(m Model, lines []string) tea.Cmd {
}
// Update handles standard message-based viewport updates.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m Model) Update(_ tea.Context, msg tea.Msg) (Model, tea.Cmd) {
var cmd tea.Cmd
m, cmd = m.updateAsModel(msg)
return m, cmd
@@ -354,7 +354,7 @@ func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) {
}
// View renders the viewport into a string.
func (m Model) View() string {
func (m Model) View(ctx tea.Context) string {
if m.HighPerformanceRendering {
// Just send newlines since we're going to be rendering the actual
// content separately. We still need to send something that equals the
@@ -372,7 +372,7 @@ func (m Model) View() string {
}
contentWidth := w - m.Style.GetHorizontalFrameSize()
contentHeight := h - m.Style.GetVerticalFrameSize()
contents := lipgloss.NewStyle().
contents := ctx.NewStyle().
Width(contentWidth). // pad to width.
Height(contentHeight). // pad to height.
MaxHeight(contentHeight). // truncate height if taller.