From 99d8ded7f2e78e2ec753d22312ea8d355fd6b5d0 Mon Sep 17 00:00:00 2001 From: Vedu1996 <28388096+Vedu1996@users.noreply.github.com> Date: Sun, 14 Jan 2024 13:43:49 +0530 Subject: [PATCH] refactor(keybindings): Use bubble tea keybindings (#47) --- internal/app/helper.go | 11 +++-- internal/app/keymap.go | 71 +++++++++++++++++++------------- internal/app/statefiltered.go | 17 +++++--- internal/app/statefiltering.go | 13 ++++-- internal/app/stateloaded.go | 37 +++++++++-------- internal/app/stateloaded_test.go | 6 --- internal/app/stateviewrow.go | 15 ++++--- internal/pkg/events/events.go | 31 +++++--------- 8 files changed, 109 insertions(+), 92 deletions(-) diff --git a/internal/app/helper.go b/internal/app/helper.go index 386ff6b..24bb7fc 100644 --- a/internal/app/helper.go +++ b/internal/app/helper.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -110,15 +111,13 @@ func (h helper) handleOpenJSONRowRequestedMsg( func (h helper) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { switch { - case h.isQuitKeyMap(msg): + case key.Matches(msg, defaultKeys.Exit): return tea.Quit - case h.isBackKeyMap(msg): - return events.BackKeyClicked - case h.isFilterKeyMap(msg): + case key.Matches(msg, defaultKeys.Filter): return events.FilterKeyClicked - case h.isEnterKeyMap(msg): + case key.Matches(msg, defaultKeys.ToggleView): return events.EnterKeyClicked - case h.isArrowRightKeyMap(msg): + case key.Matches(msg, defaultKeys.ToggleViewArrow): return events.ArrowRightKeyClicked default: return nil diff --git a/internal/app/keymap.go b/internal/app/keymap.go index 68d3093..7364896 100644 --- a/internal/app/keymap.go +++ b/internal/app/keymap.go @@ -1,39 +1,54 @@ package app -import tea "github.com/charmbracelet/bubbletea" +import "github.com/charmbracelet/bubbles/key" -func (a Application) isQuitKeyMap( - msg tea.KeyMsg, -) bool { - switch msg.String() { - case "ctrl+c", "f10": - return true - default: - return false - } -} - -func (a Application) isEnterKeyMap(msg tea.KeyMsg) bool { - return msg.String() == "enter" -} - -func (a Application) isArrowUpKeyMap(msg tea.KeyMsg) bool { - return msg.Type == tea.KeyUp +type KeyMap struct { + Exit key.Binding + Back key.Binding + ToggleView key.Binding + ToggleViewArrow key.Binding + Up key.Binding + Down key.Binding + Filter key.Binding } -func (a Application) isArrowRightKeyMap(msg tea.KeyMsg) bool { - return msg.Type == tea.KeyRight +var defaultKeys = KeyMap{ + Exit: key.NewBinding( + key.WithKeys("ctrl+c", "f10"), + key.WithHelp("Ctrl+C", "Exit"), + ), + Back: key.NewBinding( + key.WithKeys("esc", "q"), + key.WithHelp("Esc", "Back"), + ), + ToggleView: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("Enter", "Open/Hide"), + ), + ToggleViewArrow: key.NewBinding( + key.WithKeys("right"), + ), + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "Up"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "Down"), + ), + Filter: key.NewBinding( + key.WithKeys("f"), + key.WithHelp("F", "Filter"), + ), } -func (a Application) isFilterKeyMap(msg tea.KeyMsg) bool { - return msg.String() == "f" +func (k KeyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Exit, k.Back, k.ToggleView, k.Up, k.Down, k.Filter} } -func (a Application) isBackKeyMap(msg tea.KeyMsg) bool { - switch msg.String() { - case "esc", "q": - return true - default: - return false +func (k KeyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Back, k.Up, k.Down}, // first column + {k.ToggleView, k.Exit, k.Filter}, // second column } } diff --git a/internal/app/statefiltered.go b/internal/app/statefiltered.go index 0df98e6..48e0e08 100644 --- a/internal/app/statefiltered.go +++ b/internal/app/statefiltered.go @@ -1,6 +1,7 @@ package app import ( + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/hedhyw/json-log-viewer/internal/pkg/events" @@ -16,6 +17,7 @@ type StateFiltered struct { logEntries source.LogEntries filterText string + keys KeyMap } func newStateFiltered( @@ -30,6 +32,7 @@ func newStateFiltered( table: previousState.table, filterText: filterText, + keys: defaultKeys, } } @@ -58,17 +61,19 @@ func (s StateFiltered) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case events.ErrorOccuredMsg: return s.handleErrorOccuredMsg(msg) - case events.BackKeyClickedMsg: - return s.previousState.withApplication(s.Application) - case events.FilterKeyClickedMsg: - return s.handleFilterKeyClickedMsg() - case events.EnterKeyClickedMsg, events.ArrowRightKeyClickedMsg: - return s.handleRequestOpenJSON() case events.LogEntriesLoadedMsg: return s.handleLogEntriesLoadedMsg(msg) case events.OpenJSONRowRequestedMsg: return s.handleOpenJSONRowRequestedMsg(msg, s) case tea.KeyMsg: + switch { + case key.Matches(msg, s.keys.Back): + return s.previousState.withApplication(s.Application) + case key.Matches(msg, s.keys.Filter): + return s.handleFilterKeyClickedMsg() + case key.Matches(msg, s.keys.ToggleViewArrow), key.Matches(msg, s.keys.ToggleView): + return s.handleRequestOpenJSON() + } if cmd := s.handleKeyMsg(msg); cmd != nil { return s, cmd } diff --git a/internal/app/statefiltering.go b/internal/app/statefiltering.go index 58b5928..db08300 100644 --- a/internal/app/statefiltering.go +++ b/internal/app/statefiltering.go @@ -1,6 +1,7 @@ package app import ( + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" @@ -15,6 +16,7 @@ type StateFiltering struct { table logsTableModel textInput textinput.Model + keys KeyMap } func newStateFiltering( @@ -31,6 +33,7 @@ func newStateFiltering( table: previousState.table, textInput: textInput, + keys: defaultKeys, } } @@ -53,11 +56,13 @@ func (s StateFiltering) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case events.ErrorOccuredMsg: return s.handleErrorOccuredMsg(msg) - case events.BackKeyClickedMsg: - return s.previousState.withApplication(s.Application) - case events.EnterKeyClickedMsg: - return s.handleEnterKeyClickedMsg() case tea.KeyMsg: + switch { + case key.Matches(msg, s.keys.Back): + return s.previousState.withApplication(s.Application) + case key.Matches(msg, s.keys.ToggleView): + return s.handleEnterKeyClickedMsg() + } if cmd := s.handleKeyMsg(msg); cmd != nil { // Intercept table update. return s, cmd diff --git a/internal/app/stateloaded.go b/internal/app/stateloaded.go index 730d6ca..f07e614 100644 --- a/internal/app/stateloaded.go +++ b/internal/app/stateloaded.go @@ -1,14 +1,14 @@ package app import ( + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/hedhyw/json-log-viewer/internal/pkg/events" "github.com/hedhyw/json-log-viewer/internal/pkg/source" ) -const defaultFooter = "[Ctrl+C] Exit; [Esc] Back; [Enter] Open/Hide; [↑↓] Navigation; [F] Filter" - // StateLoaded is a state that shows all loaded records. type StateLoaded struct { helper @@ -17,6 +17,9 @@ type StateLoaded struct { table logsTableModel logEntries source.LogEntries + + keys KeyMap + help help.Model } func newStateViewLogs(application Application, logEntries source.LogEntries) StateLoaded { @@ -29,6 +32,9 @@ func newStateViewLogs(application Application, logEntries source.LogEntries) Sta table: table, logEntries: logEntries, + + keys: defaultKeys, + help: help.New(), } } @@ -39,15 +45,15 @@ func (s StateLoaded) Init() tea.Cmd { // View renders component. It implements tea.Model. func (s StateLoaded) View() string { - return s.viewTable() + "\n" + s.viewFooter() + return s.viewTable() + s.viewHelp() } func (s StateLoaded) viewTable() string { return s.BaseStyle.Render(s.table.View()) } -func (s StateLoaded) viewFooter() string { - return s.FooterStyle.Render(defaultFooter) +func (s StateLoaded) viewHelp() string { + return "\n" + s.help.View(s.keys) } // Update handles events. It implements tea.Model. @@ -67,19 +73,16 @@ func (s StateLoaded) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return s.handleViewRowsReloadRequestedMsg() case events.OpenJSONRowRequestedMsg: return s.handleOpenJSONRowRequestedMsg(msg, s) - case events.BackKeyClickedMsg: - return s, tea.Quit - case events.EnterKeyClickedMsg, events.ArrowRightKeyClickedMsg: - return s.handleRequestOpenJSON() - case events.FilterKeyClickedMsg: - return s.handleFilterKeyClickedMsg() case tea.KeyMsg: - cmdBatch = append(cmdBatch, s.handleKeyMsg(msg)...) - - if s.isFilterKeyMap(msg) { - // Intercept table update. - return s, tea.Batch(cmdBatch...) + switch { + case key.Matches(msg, s.keys.Back): + return s, tea.Quit + case key.Matches(msg, s.keys.Filter): + return s.handleFilterKeyClickedMsg() + case key.Matches(msg, s.keys.ToggleViewArrow), key.Matches(msg, s.keys.ToggleView): + return s.handleRequestOpenJSON() } + cmdBatch = append(cmdBatch, s.handleKeyMsg(msg)...) } s.table, cmdBatch = batched(s.table.Update(msg))(cmdBatch) @@ -92,7 +95,7 @@ func (s StateLoaded) handleKeyMsg(msg tea.KeyMsg) []tea.Cmd { cmdBatch = appendCmd(cmdBatch, s.helper.handleKeyMsg(msg)) - if s.isArrowUpKeyMap(msg) { + if key.Matches(msg, s.keys.Up) { cmdBatch = appendCmd(cmdBatch, s.handleArrowUpKeyClicked()) } diff --git a/internal/app/stateloaded_test.go b/internal/app/stateloaded_test.go index 4f06898..e044db8 100644 --- a/internal/app/stateloaded_test.go +++ b/internal/app/stateloaded_test.go @@ -72,9 +72,6 @@ func TestStateLoadedQuit(t *testing.T) { t.Parallel() _, cmd := model.Update(tea.KeyMsg{Type: tea.KeyEsc}) - requireCmdMsg(t, events.BackKeyClickedMsg{}, cmd) - - _, cmd = model.Update(events.BackKeyClickedMsg{}) requireCmdMsg(t, tea.Quit(), cmd) }) @@ -85,9 +82,6 @@ func TestStateLoadedQuit(t *testing.T) { Type: tea.KeyRunes, Runes: []rune{'q'}, }) - requireCmdMsg(t, events.BackKeyClickedMsg{}, cmd) - - _, cmd = model.Update(events.BackKeyClickedMsg{}) requireCmdMsg(t, tea.Quit(), cmd) }) diff --git a/internal/app/stateviewrow.go b/internal/app/stateviewrow.go index a4cebe3..16ae580 100644 --- a/internal/app/stateviewrow.go +++ b/internal/app/stateviewrow.go @@ -1,6 +1,7 @@ package app import ( + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/hedhyw/json-log-viewer/internal/pkg/events" @@ -17,6 +18,8 @@ type StateViewRow struct { logEntry source.LogEntry jsonView tea.Model + + keys KeyMap } func newStateViewRow( @@ -34,6 +37,8 @@ func newStateViewRow( logEntry: logEntry, jsonView: jsonViewModel, + + keys: defaultKeys, } } @@ -56,11 +61,11 @@ func (s StateViewRow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case events.ErrorOccuredMsg: return s.handleErrorOccuredMsg(msg) - case events.BackKeyClickedMsg: - return s.previousState.withApplication(s.Application) - case events.EnterKeyClickedMsg: - return s.previousState.withApplication(s.Application) case tea.KeyMsg: + if key.Matches(msg, s.keys.Back) || key.Matches(msg, s.keys.ToggleView) { + return s.previousState.withApplication(s.Application) + } + if cmd = s.handleKeyMsg(msg); cmd != nil { return s, cmd } @@ -72,7 +77,7 @@ func (s StateViewRow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (s StateViewRow) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { - if s.isArrowRightKeyMap(msg) { + if key.Matches(msg, s.keys.ToggleViewArrow) { return nil } diff --git a/internal/pkg/events/events.go b/internal/pkg/events/events.go index fdd76ee..88379b5 100644 --- a/internal/pkg/events/events.go +++ b/internal/pkg/events/events.go @@ -25,18 +25,6 @@ type ( // ViewRowsReloadRequestedMsg is an event to start reloading of logs. ViewRowsReloadRequestedMsg struct{} - - // EnterKeyClickedMsg is a keyboard event after pressing . - EnterKeyClickedMsg struct{} - - // EnterKeyClickedMsg is a keyboard event after pressing . - ArrowRightKeyClickedMsg struct{} - - // FilterKeyClickedMsg is a keyboard event for "Filtering" key. - FilterKeyClickedMsg struct{} - - // FilterKeyClickedMsg is a keyboard event after pressing any "Back" key. - BackKeyClickedMsg struct{} ) // OpenJSONRowRequested implements tea.Cmd. It creates OpenJSONRowRequestedMsg. @@ -54,22 +42,25 @@ func ViewRowsReloadRequested() tea.Msg { return ViewRowsReloadRequestedMsg{} } -// EnterKeyClicked implements tea.Cmd. It creates EnterKeyClickedMsg. +// EnterKeyClicked implements tea.Cmd. It creates a message indicating 'Enter' has been clicked. func EnterKeyClicked() tea.Msg { - return EnterKeyClickedMsg{} + return tea.KeyMsg{Type: tea.KeyEnter} } -// ArrowRightKeyClicked implements tea.Cmd. It creates ArrowRightKeyClickedMsg. +// ArrowRightKeyClicked implements tea.Cmd. It creates a message indicating 'arrow-right' has been clicked. func ArrowRightKeyClicked() tea.Msg { - return ArrowRightKeyClickedMsg{} + return tea.KeyMsg{Type: tea.KeyRight} } -// FilterKeyClicked implements tea.Cmd. It creates FilterKeyClickedMsg. +// FilterKeyClicked implements tea.Cmd. It creates a message indicating 'f' has been clicked. func FilterKeyClicked() tea.Msg { - return FilterKeyClickedMsg{} + return tea.KeyMsg{ + Type: tea.KeyRunes, + Runes: []rune{'f'}, + } } -// BackKeyClicked implements tea.Cmd. It creates BackKeyClickedMsg. +// BackKeyClicked implements tea.Cmd. It creates a message indicating 'Esc' has been clicked. func BackKeyClicked() tea.Msg { - return BackKeyClickedMsg{} + return tea.KeyMsg{Type: tea.KeyEscape} }