diff --git a/table/keys.go b/table/keys.go index ccbee58..3aa9fab 100644 --- a/table/keys.go +++ b/table/keys.go @@ -9,8 +9,10 @@ type KeyMap struct { RowSelectToggle key.Binding - PageDown key.Binding - PageUp key.Binding + PageDown key.Binding + PageUp key.Binding + PageFirst key.Binding + PageLast key.Binding Filter key.Binding } @@ -33,6 +35,12 @@ func DefaultKeyMap() KeyMap { PageUp: key.NewBinding( key.WithKeys("left", "h", "pgup"), ), + PageFirst: key.NewBinding( + key.WithKeys("home", "g"), + ), + PageLast: key.NewBinding( + key.WithKeys("end", "G"), + ), Filter: key.NewBinding( key.WithKeys("/"), ), diff --git a/table/pagination.go b/table/pagination.go index f6418be..dd50038 100644 --- a/table/pagination.go +++ b/table/pagination.go @@ -81,6 +81,24 @@ func (m *Model) pageUp() { m.rowCursorIndex = m.currentPage * m.pageSize } +func (m *Model) pageFirst() { + if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize { + return + } + + m.currentPage = 0 + m.rowCursorIndex = 0 +} + +func (m *Model) pageLast() { + if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize { + return + } + + m.currentPage = m.MaxPages() - 1 + m.rowCursorIndex = m.currentPage * m.pageSize +} + func (m *Model) expectedPageForRowIndex(rowIndex int) int { if m.pageSize == 0 { return 0 diff --git a/table/update.go b/table/update.go index 674a9a4..23e71ef 100644 --- a/table/update.go +++ b/table/update.go @@ -59,6 +59,34 @@ func (m Model) updateFilterTextInput(msg tea.Msg) (Model, tea.Cmd) { return m, cmd } +func (m *Model) handleKeypress(msg tea.KeyMsg) { + switch { + case key.Matches(msg, m.keyMap.RowDown): + m.moveHighlightDown() + + case key.Matches(msg, m.keyMap.RowUp): + m.moveHighlightUp() + + case key.Matches(msg, m.keyMap.RowSelectToggle): + m.toggleSelect() + + case key.Matches(msg, m.keyMap.PageDown): + m.pageDown() + + case key.Matches(msg, m.keyMap.PageUp): + m.pageUp() + + case key.Matches(msg, m.keyMap.PageFirst): + m.pageFirst() + + case key.Matches(msg, m.keyMap.PageLast): + m.pageLast() + + case key.Matches(msg, m.keyMap.Filter): + m.filterTextInput.Focus() + } +} + // Update responds to input from the user or other messages from Bubble Tea. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if !m.focused { @@ -71,25 +99,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: - switch { - case key.Matches(msg, m.keyMap.RowDown): - m.moveHighlightDown() - - case key.Matches(msg, m.keyMap.RowUp): - m.moveHighlightUp() - - case key.Matches(msg, m.keyMap.RowSelectToggle): - m.toggleSelect() - - case key.Matches(msg, m.keyMap.PageDown): - m.pageDown() - - case key.Matches(msg, m.keyMap.PageUp): - m.pageUp() - - case key.Matches(msg, m.keyMap.Filter): - m.filterTextInput.Focus() - } + m.handleKeypress(msg) } return m, nil diff --git a/table/update_test.go b/table/update_test.go index 133d976..7937910 100644 --- a/table/update_test.go +++ b/table/update_test.go @@ -33,9 +33,56 @@ func TestUnfocusedDoesntMove(t *testing.T) { assert.True(t, ok, "Failed to convert to string") assert.Equal(t, "first", id, "Should still be on first row") + + model, _ = model.Update(tea.KeyMsg{ + Type: tea.KeyHome, + }) +} + +func TestPageKeysDoNothingWhenNoPages(t *testing.T) { + cols := []Column{ + NewColumn("id", "ID", 3), + } + + model := New(cols).WithRows([]Row{ + NewRow(RowData{ + "id": "first", + }), + NewRow(RowData{ + "id": "second", + }), + NewRow(RowData{ + "id": "third", + }), + }).Focused(true) + + pageMoveKeys := []tea.Msg{ + tea.KeyMsg{Type: tea.KeyLeft}, + tea.KeyMsg{Type: tea.KeyRight}, + tea.KeyMsg{Type: tea.KeyHome}, + tea.KeyMsg{Type: tea.KeyEnd}, + } + + checkNoMove := func() string { + str, ok := model.HighlightedRow().Data["id"].(string) + + assert.True(t, ok, "Failed to convert to string") + + assert.Equal(t, "first", str, "Shouldn't move") + + return str + } + + for _, msg := range pageMoveKeys { + model, _ = model.Update(msg) + checkNoMove() + } } -func TestFocusedMovesWhenArrowsPressed(t *testing.T) { +// This is a long test with a lot of movement keys pressed, that's okay because +// it's simply repetitive and tracking the same kind of state change many times +// nolint: funlen +func TestFocusedMovesWhenMoveKeysPressedPaged(t *testing.T) { cols := []Column{ NewColumn("id", "ID", 3), } @@ -57,6 +104,8 @@ func TestFocusedMovesWhenArrowsPressed(t *testing.T) { keyDown := tea.KeyMsg{Type: tea.KeyDown} keyLeft := tea.KeyMsg{Type: tea.KeyLeft} keyRight := tea.KeyMsg{Type: tea.KeyRight} + keyHome := tea.KeyMsg{Type: tea.KeyHome} + keyEnd := tea.KeyMsg{Type: tea.KeyEnd} curID := func() string { str, ok := model.HighlightedRow().Data["id"].(string) @@ -91,6 +140,21 @@ func TestFocusedMovesWhenArrowsPressed(t *testing.T) { model, _ = model.Update(keyLeft) assert.Equal(t, "first", curID(), "Moving left should move back to first page") + + model, _ = model.Update(keyDown) + assert.Equal(t, "second", curID(), "Should be back down to second row") + + model, _ = model.Update(keyHome) + assert.Equal(t, "first", curID(), "Hitting home should go to first page and select first row") + + model, _ = model.Update(keyHome) + assert.Equal(t, "first", curID(), "Hitting home a second time should not move pages") + + model, _ = model.Update(keyEnd) + assert.Equal(t, "third", curID(), "Hitting end should move to last page") + + model, _ = model.Update(keyEnd) + assert.Equal(t, "third", curID(), "Hitting end a second time should not move pages") } func TestFocusedMovesWithCustomKeyMap(t *testing.T) {