-
Notifications
You must be signed in to change notification settings - Fork 836
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add generic event filter (#536)
`WithFilter` lets you supply 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. Based on the fantastic work by @aschey and supersedes #521. Resolves #472.
- Loading branch information
Showing
5 changed files
with
253 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package main | ||
|
||
// A program demonstrating how to use the WithFilter option to intercept events. | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
"github.com/charmbracelet/bubbles/help" | ||
"github.com/charmbracelet/bubbles/key" | ||
"github.com/charmbracelet/bubbles/textarea" | ||
tea "github.com/charmbracelet/bubbletea" | ||
"github.com/charmbracelet/lipgloss" | ||
) | ||
|
||
var ( | ||
choiceStyle = lipgloss.NewStyle().PaddingLeft(1).Foreground(lipgloss.Color("241")) | ||
saveTextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) | ||
quitViewStyle = lipgloss.NewStyle().Padding(1).Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("170")) | ||
) | ||
|
||
func main() { | ||
p := tea.NewProgram(initialModel(), tea.WithFilter(filter)) | ||
|
||
if _, err := p.Run(); err != nil { | ||
log.Fatal(err) | ||
} | ||
} | ||
|
||
func filter(teaModel tea.Model, msg tea.Msg) tea.Msg { | ||
if _, ok := msg.(tea.QuitMsg); !ok { | ||
return msg | ||
} | ||
|
||
m := teaModel.(model) | ||
if m.hasChanges { | ||
return nil | ||
} | ||
|
||
return msg | ||
} | ||
|
||
type model struct { | ||
textarea textarea.Model | ||
help help.Model | ||
keymap keymap | ||
saveText string | ||
hasChanges bool | ||
quitting bool | ||
} | ||
|
||
type keymap struct { | ||
save key.Binding | ||
quit key.Binding | ||
} | ||
|
||
func initialModel() model { | ||
ti := textarea.New() | ||
ti.Placeholder = "Only the best words" | ||
ti.Focus() | ||
|
||
return model{ | ||
textarea: ti, | ||
help: help.NewModel(), | ||
keymap: keymap{ | ||
save: key.NewBinding( | ||
key.WithKeys("ctrl+s"), | ||
key.WithHelp("ctrl+s", "save"), | ||
), | ||
quit: key.NewBinding( | ||
key.WithKeys("esc", "ctrl+c"), | ||
key.WithHelp("esc", "quit"), | ||
), | ||
}, | ||
} | ||
} | ||
|
||
func (m model) Init() tea.Cmd { | ||
return textarea.Blink | ||
} | ||
|
||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
if m.quitting { | ||
return m.updatePromptView(msg) | ||
} | ||
|
||
return m.updateTextView(msg) | ||
} | ||
|
||
func (m model) updateTextView(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
var cmds []tea.Cmd | ||
var cmd tea.Cmd | ||
|
||
switch msg := msg.(type) { | ||
case tea.KeyMsg: | ||
m.saveText = "" | ||
switch { | ||
case key.Matches(msg, m.keymap.save): | ||
m.saveText = "Changes saved!" | ||
m.hasChanges = false | ||
case key.Matches(msg, m.keymap.quit): | ||
m.quitting = true | ||
return m, tea.Quit | ||
case msg.Type == tea.KeyRunes: | ||
m.saveText = "" | ||
m.hasChanges = true | ||
fallthrough | ||
default: | ||
if !m.textarea.Focused() { | ||
cmd = m.textarea.Focus() | ||
cmds = append(cmds, cmd) | ||
} | ||
} | ||
} | ||
m.textarea, cmd = m.textarea.Update(msg) | ||
cmds = append(cmds, cmd) | ||
return m, tea.Batch(cmds...) | ||
} | ||
|
||
func (m model) updatePromptView(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
switch msg := msg.(type) { | ||
case tea.KeyMsg: | ||
// For simplicity's sake, we'll treat any key besides "y" as "no" | ||
if key.Matches(msg, m.keymap.quit) || msg.String() == "y" { | ||
m.hasChanges = false | ||
return m, tea.Quit | ||
} | ||
m.quitting = false | ||
} | ||
|
||
return m, nil | ||
} | ||
|
||
func (m model) View() string { | ||
if m.quitting { | ||
if m.hasChanges { | ||
text := lipgloss.JoinHorizontal(lipgloss.Top, "You have unsaved changes. Quit without saving?", choiceStyle.Render("[yn]")) | ||
return quitViewStyle.Render(text) | ||
} | ||
return "Very important, thank you\n" | ||
} | ||
|
||
helpView := m.help.ShortHelpView([]key.Binding{ | ||
m.keymap.save, | ||
m.keymap.quit, | ||
}) | ||
|
||
return fmt.Sprintf( | ||
"\nType some important things.\n\n%s\n\n %s\n %s", | ||
m.textarea.View(), | ||
saveTextStyle.Render(m.saveText), | ||
helpView, | ||
) + "\n\n" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters