Skip to content

Commit

Permalink
refactor(keybindings): Use bubble tea keybindings (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vedu1996 authored Jan 14, 2024
1 parent c65754f commit 99d8ded
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 92 deletions.
11 changes: 5 additions & 6 deletions internal/app/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
71 changes: 43 additions & 28 deletions internal/app/keymap.go
Original file line number Diff line number Diff line change
@@ -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
}
}
17 changes: 11 additions & 6 deletions internal/app/statefiltered.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -16,6 +17,7 @@ type StateFiltered struct {
logEntries source.LogEntries

filterText string
keys KeyMap
}

func newStateFiltered(
Expand All @@ -30,6 +32,7 @@ func newStateFiltered(
table: previousState.table,

filterText: filterText,
keys: defaultKeys,
}
}

Expand Down Expand Up @@ -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
}
Expand Down
13 changes: 9 additions & 4 deletions internal/app/statefiltering.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app

import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"

Expand All @@ -15,6 +16,7 @@ type StateFiltering struct {
table logsTableModel

textInput textinput.Model
keys KeyMap
}

func newStateFiltering(
Expand All @@ -31,6 +33,7 @@ func newStateFiltering(
table: previousState.table,

textInput: textInput,
keys: defaultKeys,
}
}

Expand All @@ -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
Expand Down
37 changes: 20 additions & 17 deletions internal/app/stateloaded.go
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 {
Expand All @@ -29,6 +32,9 @@ func newStateViewLogs(application Application, logEntries source.LogEntries) Sta

table: table,
logEntries: logEntries,

keys: defaultKeys,
help: help.New(),
}
}

Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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())
}

Expand Down
6 changes: 0 additions & 6 deletions internal/app/stateloaded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Expand All @@ -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)
})

Expand Down
15 changes: 10 additions & 5 deletions internal/app/stateviewrow.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -17,6 +18,8 @@ type StateViewRow struct {

logEntry source.LogEntry
jsonView tea.Model

keys KeyMap
}

func newStateViewRow(
Expand All @@ -34,6 +37,8 @@ func newStateViewRow(

logEntry: logEntry,
jsonView: jsonViewModel,

keys: defaultKeys,
}
}

Expand All @@ -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
}
Expand All @@ -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
}

Expand Down
Loading

0 comments on commit 99d8ded

Please sign in to comment.