Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/prevent-quit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ var (
)

func main() {
p := tea.NewProgram(initialModel(), tea.WithFilter(filter))
p := tea.NewProgram(initialModel(), tea.WithFilters(preventUnsavedFilter))

if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}

func filter(teaModel tea.Model, msg tea.Msg) tea.Msg {
func preventUnsavedFilter(teaModel tea.Model, msg tea.Msg) tea.Msg {
if _, ok := msg.(tea.QuitMsg); !ok {
return msg
}
Expand Down
66 changes: 56 additions & 10 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"
"sync/atomic"
"time"

"github.com/charmbracelet/colorprofile"
)
Expand Down Expand Up @@ -101,17 +102,23 @@ func WithoutRenderer() ProgramOption {
}
}

// WithFilter supplies an event filter that will be invoked before Bubble Tea
// processes a tea.Msg. The event filter can return any tea.Msg which will then
// get handled by Bubble Tea instead of the original event. If the event filter
// returns nil, the event will be ignored and Bubble Tea will not process it.
// MsgFilter is a function that can be used to filter messages before they are
// processed by Bubble Tea. If the provided function returns nil, the message will
// be ignored and Bubble Tea will not process it.
type MsgFilter func(Model, Msg) Msg

// WithFilters supplies one or more message filters that will be invoked before
// Bubble Tea processes a [Msg]. The message filter can return any [Msg] which
// will then get handled by Bubble Tea instead of the original message. If the
// filter returns nil for a specific message, the message will be ignored and
// Bubble Tea will not process it, and not continue to the next filter.
//
// As an example, this could be used to prevent a program from shutting down if
// there are unsaved changes.
// there are unsaved changes, or used to throttle/drop high-frequency messages.
//
// Example:
// Example -- preventing a program from shutting down if there are unsaved changes:
//
// func filter(m tea.Model, msg tea.Msg) tea.Msg {
// func preventUnsavedFilter(m tea.Model, msg tea.Msg) tea.Msg {
// if _, ok := msg.(tea.QuitMsg); !ok {
// return msg
// }
Expand All @@ -124,15 +131,54 @@ func WithoutRenderer() ProgramOption {
// return msg
// }
//
// p := tea.NewProgram(Model{}, tea.WithFilter(filter));
// p := tea.NewProgram(Model{}, tea.WithFilters(preventUnsavedFilter));
//
// if _,err := p.Run(); err != nil {
// fmt.Println("Error running program:", err)
// os.Exit(1)
// }
func WithFilter(filter func(Model, Msg) Msg) ProgramOption {
func WithFilters(filters ...MsgFilter) ProgramOption {
return func(p *Program) {
p.filter = filter
if len(filters) == 0 {
p.filter = nil
return
}
p.filter = func(m Model, msg Msg) Msg {
for _, filter := range filters {
msg = filter(m, msg)
if msg == nil {
return nil
}
}
return msg
}
}
}

// MouseThrottleFilter is a message filter that throttles [MouseWheelMsg] and
// [MouseMotionMsg] messages. This is particularly useful when enabling
// [MouseModeCellMotion] or [MouseModeAllMotion] mouse modes, which can often
// send excessive messages when the user is moving the mouse very fast, causing
// high-resource usage and sluggish re-rendering.
//
// If the provided throttle duration is 0, the default value of 15ms will be used.
func MouseThrottleFilter(throttle time.Duration) MsgFilter {
if throttle <= 0 {
throttle = 15 * time.Millisecond
}

var lastMouseMsg, now time.Time

return func(_ Model, msg Msg) Msg {
switch msg.(type) {
case MouseWheelMsg, MouseMotionMsg:
now = time.Now()
if now.Sub(lastMouseMsg) < throttle {
return nil
}
lastMouseMsg = now
}
return msg
}
}

Expand Down
2 changes: 1 addition & 1 deletion options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestOptions(t *testing.T) {
})

t.Run("filter", func(t *testing.T) {
p := NewProgram(nil, WithFilter(func(_ Model, msg Msg) Msg { return msg }))
p := NewProgram(nil, WithFilters(func(_ Model, msg Msg) Msg { return msg }))
if p.filter == nil {
t.Errorf("expected filter to be set")
}
Expand Down
35 changes: 3 additions & 32 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,38 +440,9 @@ type Program struct {
// cleanup on exit.
disableCatchPanics bool

// filter supplies an event filter that will be invoked before Bubble Tea
// processes a tea.Msg. The event filter can return any tea.Msg which will
// then get handled by Bubble Tea instead of the original event. If the
// event filter returns nil, the event will be ignored and Bubble Tea will
// not process it.
//
// As an example, this could be used to prevent a program from shutting
// down if there are unsaved changes.
//
// Example:
//
// func filter(m tea.Model, msg tea.Msg) tea.Msg {
// if _, ok := msg.(tea.QuitMsg); !ok {
// return msg
// }
//
// model := m.(myModel)
// if model.hasChanges {
// return nil
// }
//
// return msg
// }
//
// p := tea.NewProgram(Model{});
// p.filter = filter
//
// if _,err := p.Run(context.Background()); err != nil {
// fmt.Println("Error running program:", err)
// os.Exit(1)
// }
filter func(Model, Msg) Msg
// filter provides a way of filtering messages before they are processed by
// Bubble Tea. See [WithFilters] for more information.
filter MsgFilter

// fps sets a custom maximum fps at which the renderer should run. If less
// than 1, the default value of 60 will be used. If over 120, the fps will
Expand Down
Loading