feat: add GotoTop/Bottom/PrevPage/NextPage navigation keys

- New global keybindings: GotoTop (Home), GotoBottom (G/End), PrevPage ([), NextPage (])
- Wired in history, findings, and intercept update handlers
- Removes duplicate tea.Quit case in intercept/update.go

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hadi
2026-05-19 13:39:01 +02:00
parent 6a9935ec27
commit 385b6e84e0
6 changed files with 133 additions and 3 deletions
+4
View File
@@ -60,6 +60,10 @@ keybindings:
scroll_up: "pgup" scroll_up: "pgup"
scroll_down: "pgdown" scroll_down: "pgdown"
send_to_diff: "ctrl+d" send_to_diff: "ctrl+d"
goto_top: "home"
goto_bottom: "G,end"
prev_page: "["
next_page: "]"
intercept: intercept:
forward: "f" forward: "f"
+4
View File
@@ -16,6 +16,10 @@ type GlobalKeys struct {
ScrollUp string `mapstructure:"scroll_up"` ScrollUp string `mapstructure:"scroll_up"`
ScrollDown string `mapstructure:"scroll_down"` ScrollDown string `mapstructure:"scroll_down"`
SendToDiff string `mapstructure:"send_to_diff"` SendToDiff string `mapstructure:"send_to_diff"`
GotoTop string `mapstructure:"goto_top"`
GotoBottom string `mapstructure:"goto_bottom"`
PrevPage string `mapstructure:"prev_page"`
NextPage string `mapstructure:"next_page"`
} }
type InterceptKeys struct { type InterceptKeys struct {
+9
View File
@@ -22,6 +22,10 @@ type GlobalKeyMap struct {
ScrollUp key.Binding ScrollUp key.Binding
ScrollDown key.Binding ScrollDown key.Binding
SendToDiff key.Binding SendToDiff key.Binding
GotoTop key.Binding
GotoBottom key.Binding
PrevPage key.Binding
NextPage key.Binding
} }
func newGlobalKeyMap(cfg config.GlobalKeys) GlobalKeyMap { func newGlobalKeyMap(cfg config.GlobalKeys) GlobalKeyMap {
@@ -42,6 +46,10 @@ func newGlobalKeyMap(cfg config.GlobalKeys) GlobalKeyMap {
ScrollUp: binding(cfg.ScrollUp, "scroll up"), ScrollUp: binding(cfg.ScrollUp, "scroll up"),
ScrollDown: binding(cfg.ScrollDown, "scroll down"), ScrollDown: binding(cfg.ScrollDown, "scroll down"),
SendToDiff: binding(cfg.SendToDiff, "send to diff"), SendToDiff: binding(cfg.SendToDiff, "send to diff"),
GotoTop: binding(cfg.GotoTop, "go to top"),
GotoBottom: binding(cfg.GotoBottom, "go to bottom"),
PrevPage: binding(cfg.PrevPage, "prev page"),
NextPage: binding(cfg.NextPage, "next page"),
} }
} }
@@ -52,6 +60,7 @@ func (g GlobalKeyMap) Bindings() []key.Binding {
g.OpenLogs, g.ToggleSidebar, g.CopyAs, g.Copy, g.OpenLogs, g.ToggleSidebar, g.CopyAs, g.Copy,
g.SendToReplay, g.SendToDiff, g.SendToReplay, g.SendToDiff,
g.ScrollUp, g.ScrollDown, g.ScrollUp, g.ScrollDown,
g.GotoTop, g.GotoBottom, g.PrevPage, g.NextPage,
} }
} }
+43
View File
@@ -81,6 +81,49 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
step = 1 step = 1
} }
m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + step) m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + step)
case key.Matches(msg, g.GotoTop):
m.cursor = 0
m.pager.Page = 0
m.refreshListViewport()
m.refreshBody()
case key.Matches(msg, g.GotoBottom):
if len(m.findings) > 0 {
m.cursor = len(m.findings) - 1
m.pager.Page = m.pager.TotalPages - 1
m.refreshListViewport()
m.refreshBody()
}
case key.Matches(msg, g.PrevPage):
step := m.pager.PerPage
if step < 1 {
step = 1
}
m.cursor -= step
if m.cursor < 0 {
m.cursor = 0
}
m.pager.Page = m.cursor / m.pager.PerPage
m.refreshListViewport()
m.refreshBody()
case key.Matches(msg, g.NextPage):
step := m.pager.PerPage
if step < 1 {
step = 1
}
m.cursor += step
if m.cursor >= len(m.findings) {
m.cursor = len(m.findings) - 1
if m.cursor < 0 {
m.cursor = 0
}
}
m.pager.Page = m.cursor / m.pager.PerPage
m.refreshListViewport()
m.refreshBody()
case key.Matches(msg, g.Help): case key.Matches(msg, g.Help):
m.help.ShowAll = !m.help.ShowAll m.help.ShowAll = !m.help.ShowAll
m.recalcSizes() m.recalcSizes()
+49
View File
@@ -271,6 +271,55 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, g.Right): case key.Matches(msg, g.Right):
m.bodyViewport.ScrollRight(6) m.bodyViewport.ScrollRight(6)
case key.Matches(msg, g.GotoTop):
m.cursor = 0
m.pager.Page = 0
m.refreshListViewport()
m.refreshBody()
m.bodyViewport.SetYOffset(0)
m.bodyViewport.SetXOffset(0)
case key.Matches(msg, g.GotoBottom):
if len(m.entries) > 0 {
m.cursor = len(m.entries) - 1
m.pager.Page = m.pager.TotalPages - 1
m.refreshListViewport()
m.refreshBody()
m.bodyViewport.SetYOffset(0)
m.bodyViewport.SetXOffset(0)
}
case key.Matches(msg, g.PrevPage):
step := m.pager.PerPage
if step < 1 {
step = 1
}
m.cursor -= step
if m.cursor < 0 {
m.cursor = 0
}
m.refreshListViewport()
m.refreshBody()
m.bodyViewport.SetYOffset(0)
m.bodyViewport.SetXOffset(0)
case key.Matches(msg, g.NextPage):
step := m.pager.PerPage
if step < 1 {
step = 1
}
m.cursor += step
if m.cursor >= len(m.entries) {
m.cursor = len(m.entries) - 1
if m.cursor < 0 {
m.cursor = 0
}
}
m.refreshListViewport()
m.refreshBody()
m.bodyViewport.SetYOffset(0)
m.bodyViewport.SetXOffset(0)
case key.Matches(msg, keys.Keys.Global.Help): case key.Matches(msg, keys.Keys.Global.Help):
m.help.ShowAll = !m.help.ShowAll m.help.ShowAll = !m.help.ShowAll
m.recalcSizes() m.recalcSizes()
+24 -3
View File
@@ -146,9 +146,6 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model
case key.Matches(msg, keys.Keys.Global.Right): case key.Matches(msg, keys.Keys.Global.Right):
m.bodyViewport.ScrollRight(6) m.bodyViewport.ScrollRight(6)
case key.Matches(msg, keys.Keys.Global.Quit):
return m, tea.Quit
case key.Matches(msg, keys.Keys.Intercept.UndoEdits): case key.Matches(msg, keys.Keys.Intercept.UndoEdits):
if onResponses { if onResponses {
if len(m.responseQueue) > 0 { if len(m.responseQueue) > 0 {
@@ -268,6 +265,30 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model
return diffUI.SendToDiffMsg{Label: label, Raw: raw} return diffUI.SendToDiffMsg{Label: label, Raw: raw}
} }
} }
case key.Matches(msg, keys.Keys.Global.GotoTop):
if onResponses {
m.responseCursor = 0
} else {
m.cursor = 0
}
m.refreshListViewport()
m.refreshResponseListViewport()
m.refreshBody()
case key.Matches(msg, keys.Keys.Global.GotoBottom):
if onResponses {
if len(m.responseQueue) > 0 {
m.responseCursor = len(m.responseQueue) - 1
}
} else {
if len(m.queue) > 0 {
m.cursor = len(m.queue) - 1
}
}
m.refreshListViewport()
m.refreshResponseListViewport()
m.refreshBody()
} }
return m, tea.Batch(*cmds...) return m, tea.Batch(*cmds...)