diff --git a/internal/ui/docs/update.go b/internal/ui/docs/update.go index 8cf351d..29d5353 100644 --- a/internal/ui/docs/update.go +++ b/internal/ui/docs/update.go @@ -4,6 +4,7 @@ import ( "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "github.com/anotherhadi/spilltea/internal/keys" + "github.com/anotherhadi/spilltea/internal/util" ) func (e Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -12,12 +13,7 @@ func (e Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.MouseWheelMsg: - switch msg.Button { - case tea.MouseWheelUp: - e.viewport.SetYOffset(e.viewport.YOffset() - 1) - case tea.MouseWheelDown: - e.viewport.SetYOffset(e.viewport.YOffset() + 1) - } + util.HandleMouseWheel(msg, &e.viewport) case tea.KeyPressMsg: if e.searching { @@ -61,17 +57,9 @@ func (e Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, g.Down): e.viewport.SetYOffset(e.viewport.YOffset() + 1) case key.Matches(msg, g.ScrollUp): - step := e.viewport.Height() / 2 - if step < 1 { - step = 1 - } - e.viewport.SetYOffset(e.viewport.YOffset() - step) + util.ScrollViewport(&e.viewport, -1) case key.Matches(msg, g.ScrollDown): - step := e.viewport.Height() / 2 - if step < 1 { - step = 1 - } - e.viewport.SetYOffset(e.viewport.YOffset() + step) + util.ScrollViewport(&e.viewport, 1) case key.Matches(msg, g.Help): e.help.ShowAll = !e.help.ShowAll e.SetSize(e.width, e.height) diff --git a/internal/ui/findings/update.go b/internal/ui/findings/update.go index 84aaff9..67c309f 100644 --- a/internal/ui/findings/update.go +++ b/internal/ui/findings/update.go @@ -6,6 +6,7 @@ import ( "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "github.com/anotherhadi/spilltea/internal/keys" + "github.com/anotherhadi/spilltea/internal/util" ) func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -42,12 +43,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case tea.MouseWheelMsg: - switch msg.Button { - case tea.MouseWheelUp: - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - 1) - case tea.MouseWheelDown: - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + 1) - } + util.HandleMouseWheel(msg, &m.bodyViewport) return m, nil case tea.KeyPressMsg: @@ -82,17 +78,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, RefreshCmd(m.database) } case key.Matches(msg, g.ScrollUp): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - step) + util.ScrollViewport(&m.bodyViewport, -1) case key.Matches(msg, g.ScrollDown): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + step) + util.ScrollViewport(&m.bodyViewport, 1) case key.Matches(msg, g.GotoTop): m.cursor = 0 m.pager.Page = 0 @@ -100,38 +88,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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() - } + m.cursor = util.CursorGotoBottom(len(m.findings)) + m.pager.Page = util.CursorGotoBottom(m.pager.TotalPages) + 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.cursor = util.CursorMovePage(m.cursor, len(m.findings), m.pager.PerPage, false) 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.cursor = util.CursorMovePage(m.cursor, len(m.findings), m.pager.PerPage, true) m.pager.Page = m.cursor / m.pager.PerPage m.refreshListViewport() m.refreshBody() diff --git a/internal/ui/history/update.go b/internal/ui/history/update.go index 722814b..3fc1ef3 100644 --- a/internal/ui/history/update.go +++ b/internal/ui/history/update.go @@ -93,24 +93,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.bodyViewport.SetXOffset(0) case tea.MouseWheelMsg: - switch msg.Button { - case tea.MouseWheelUp: - if msg.Mod.Contains(tea.ModShift) { - m.bodyViewport.ScrollLeft(6) - } else { - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - 1) - } - case tea.MouseWheelDown: - if msg.Mod.Contains(tea.ModShift) { - m.bodyViewport.ScrollRight(6) - } else { - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + 1) - } - case tea.MouseWheelLeft: - m.bodyViewport.ScrollLeft(6) - case tea.MouseWheelRight: - m.bodyViewport.ScrollRight(6) - } + util.HandleMouseWheel(msg, &m.bodyViewport) case tea.KeyPressMsg: h := keys.Keys.History @@ -276,18 +259,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, m.clearSearch() case key.Matches(msg, g.ScrollUp): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - step) + util.ScrollViewport(&m.bodyViewport, -1) case key.Matches(msg, g.ScrollDown): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + step) + util.ScrollViewport(&m.bodyViewport, 1) case key.Matches(msg, g.Left): m.bodyViewport.ScrollLeft(6) @@ -304,41 +279,21 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 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) - } + m.cursor = util.CursorGotoBottom(len(m.entries)) + 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.cursor = util.CursorMovePage(m.cursor, len(m.entries), m.pager.PerPage, false) 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.cursor = util.CursorMovePage(m.cursor, len(m.entries), m.pager.PerPage, true) m.refreshListViewport() m.refreshBody() m.bodyViewport.SetYOffset(0) diff --git a/internal/ui/intercept/update.go b/internal/ui/intercept/update.go index 3ed3b14..d2eb8d1 100644 --- a/internal/ui/intercept/update.go +++ b/internal/ui/intercept/update.go @@ -52,24 +52,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.MouseWheelMsg: if !m.editing { - switch msg.Button { - case tea.MouseWheelUp: - if msg.Mod.Contains(tea.ModShift) { - m.bodyViewport.ScrollLeft(6) - } else { - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - 1) - } - case tea.MouseWheelDown: - if msg.Mod.Contains(tea.ModShift) { - m.bodyViewport.ScrollRight(6) - } else { - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + 1) - } - case tea.MouseWheelLeft: - m.bodyViewport.ScrollLeft(6) - case tea.MouseWheelRight: - m.bodyViewport.ScrollRight(6) - } + util.HandleMouseWheel(msg, &m.bodyViewport) } case tea.KeyPressMsg: @@ -127,18 +110,10 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model } case key.Matches(msg, keys.Keys.Global.ScrollUp): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() - step) + util.ScrollViewport(&m.bodyViewport, -1) case key.Matches(msg, keys.Keys.Global.ScrollDown): - step := m.bodyViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.bodyViewport.SetYOffset(m.bodyViewport.YOffset() + step) + util.ScrollViewport(&m.bodyViewport, 1) case key.Matches(msg, keys.Keys.Global.Left): m.bodyViewport.ScrollLeft(6) @@ -278,13 +253,9 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model case key.Matches(msg, keys.Keys.Global.GotoBottom): if onResponses { - if len(m.responseQueue) > 0 { - m.responseCursor = len(m.responseQueue) - 1 - } + m.responseCursor = util.CursorGotoBottom(len(m.responseQueue)) } else { - if len(m.queue) > 0 { - m.cursor = len(m.queue) - 1 - } + m.cursor = util.CursorGotoBottom(len(m.queue)) } m.refreshListViewport() m.refreshResponseListViewport() @@ -292,23 +263,9 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model case key.Matches(msg, keys.Keys.Global.PrevPage): if onResponses { - step := m.responsePager.PerPage - if step < 1 { - step = 1 - } - m.responseCursor -= step - if m.responseCursor < 0 { - m.responseCursor = 0 - } + m.responseCursor = util.CursorMovePage(m.responseCursor, len(m.responseQueue), m.responsePager.PerPage, false) } else { - step := m.pager.PerPage - if step < 1 { - step = 1 - } - m.cursor -= step - if m.cursor < 0 { - m.cursor = 0 - } + m.cursor = util.CursorMovePage(m.cursor, len(m.queue), m.pager.PerPage, false) } m.refreshListViewport() m.refreshResponseListViewport() @@ -316,29 +273,9 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg, cmds *[]tea.Cmd) (tea.Model case key.Matches(msg, keys.Keys.Global.NextPage): if onResponses { - step := m.responsePager.PerPage - if step < 1 { - step = 1 - } - m.responseCursor += step - if m.responseCursor >= len(m.responseQueue) { - m.responseCursor = len(m.responseQueue) - 1 - if m.responseCursor < 0 { - m.responseCursor = 0 - } - } + m.responseCursor = util.CursorMovePage(m.responseCursor, len(m.responseQueue), m.responsePager.PerPage, true) } else { - step := m.pager.PerPage - if step < 1 { - step = 1 - } - m.cursor += step - if m.cursor >= len(m.queue) { - m.cursor = len(m.queue) - 1 - if m.cursor < 0 { - m.cursor = 0 - } - } + m.cursor = util.CursorMovePage(m.cursor, len(m.queue), m.pager.PerPage, true) } m.refreshListViewport() m.refreshResponseListViewport() diff --git a/internal/ui/plugins/update.go b/internal/ui/plugins/update.go index 4b87bab..d6dc2b3 100644 --- a/internal/ui/plugins/update.go +++ b/internal/ui/plugins/update.go @@ -4,6 +4,7 @@ import ( "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "github.com/anotherhadi/spilltea/internal/keys" + "github.com/anotherhadi/spilltea/internal/util" ) func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -20,12 +21,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.MouseWheelMsg: if !m.editing { - switch msg.Button { - case tea.MouseWheelUp: - m.detailViewport.SetYOffset(m.detailViewport.YOffset() - 1) - case tea.MouseWheelDown: - m.detailViewport.SetYOffset(m.detailViewport.YOffset() + 1) - } + util.HandleMouseWheel(msg, &m.detailViewport) } case tea.KeyPressMsg: @@ -129,47 +125,22 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } 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.cursor = util.CursorMovePage(m.cursor, len(m.filtered), m.pager.PerPage, false) m.recalcSizes() m.syncTextarea() m.detailViewport.GotoTop() case key.Matches(msg, g.NextPage): - step := m.pager.PerPage - if step < 1 { - step = 1 - } - m.cursor += step - if m.cursor >= len(m.filtered) { - m.cursor = len(m.filtered) - 1 - if m.cursor < 0 { - m.cursor = 0 - } - } + m.cursor = util.CursorMovePage(m.cursor, len(m.filtered), m.pager.PerPage, true) m.recalcSizes() m.syncTextarea() m.detailViewport.GotoTop() case key.Matches(msg, g.ScrollUp): - step := m.detailViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.detailViewport.SetYOffset(m.detailViewport.YOffset() - step) + util.ScrollViewport(&m.detailViewport, -1) case key.Matches(msg, g.ScrollDown): - step := m.detailViewport.Height() / 2 - if step < 1 { - step = 1 - } - m.detailViewport.SetYOffset(m.detailViewport.YOffset() + step) + util.ScrollViewport(&m.detailViewport, 1) case key.Matches(msg, g.Help): m.help.ShowAll = !m.help.ShowAll diff --git a/internal/ui/replay/update.go b/internal/ui/replay/update.go index b0a7f44..626108e 100644 --- a/internal/ui/replay/update.go +++ b/internal/ui/replay/update.go @@ -191,20 +191,12 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { case key.Matches(msg, g.ScrollUp): vp := m.focusedViewport() - step := vp.Height() / 2 - if step < 1 { - step = 1 - } - vp.SetYOffset(vp.YOffset() - step) + util.ScrollViewport(&vp, -1) m.setFocusedViewport(vp) case key.Matches(msg, g.ScrollDown): vp := m.focusedViewport() - step := vp.Height() / 2 - if step < 1 { - step = 1 - } - vp.SetYOffset(vp.YOffset() + step) + util.ScrollViewport(&vp, 1) m.setFocusedViewport(vp) case key.Matches(msg, g.Left): @@ -247,37 +239,17 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { m.refreshBody() case key.Matches(msg, keys.Keys.Global.GotoBottom): - if len(m.entries) > 0 { - m.cursor = len(m.entries) - 1 - m.pager.Page = m.pager.TotalPages - 1 - m.refreshListViewport() - m.refreshBody() - } + m.cursor = util.CursorGotoBottom(len(m.entries)) + m.refreshListViewport() + m.refreshBody() case key.Matches(msg, keys.Keys.Global.PrevPage): - step := m.pager.PerPage - if step < 1 { - step = 1 - } - m.cursor -= step - if m.cursor < 0 { - m.cursor = 0 - } + m.cursor = util.CursorMovePage(m.cursor, len(m.entries), m.pager.PerPage, false) m.refreshListViewport() m.refreshBody() case key.Matches(msg, keys.Keys.Global.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.cursor = util.CursorMovePage(m.cursor, len(m.entries), m.pager.PerPage, true) m.refreshListViewport() m.refreshBody() diff --git a/internal/util/cursor.go b/internal/util/cursor.go new file mode 100644 index 0000000..8ad74f3 --- /dev/null +++ b/internal/util/cursor.go @@ -0,0 +1,30 @@ +package util + +// CursorMovePage moves cursor forward or backward by one page (perPage items), +// clamped to [0, total-1]. +func CursorMovePage(cursor, total, perPage int, forward bool) int { + step := perPage + if step < 1 { + step = 1 + } + if forward { + cursor += step + } else { + cursor -= step + } + if cursor < 0 || total <= 0 { + return 0 + } + if cursor >= total { + return total - 1 + } + return cursor +} + +// CursorGotoBottom returns the last valid cursor index for a list of total items. +func CursorGotoBottom(total int) int { + if total <= 0 { + return 0 + } + return total - 1 +} diff --git a/internal/util/viewport.go b/internal/util/viewport.go new file mode 100644 index 0000000..b115218 --- /dev/null +++ b/internal/util/viewport.go @@ -0,0 +1,39 @@ +package util + +import ( + "charm.land/bubbles/v2/viewport" + tea "charm.land/bubbletea/v2" +) + +// ScrollViewport scrolls vp vertically by half its height. +// delta should be -1 for up, +1 for down. +func ScrollViewport(vp *viewport.Model, delta int) { + step := vp.Height() / 2 + if step < 1 { + step = 1 + } + vp.SetYOffset(vp.YOffset() + delta*step) +} + +// HandleMouseWheel applies standard mouse wheel scrolling to vp. +// Vertical: one line at a time. Shift+vertical or horizontal: scroll 6 columns. +func HandleMouseWheel(msg tea.MouseWheelMsg, vp *viewport.Model) { + switch msg.Button { + case tea.MouseWheelUp: + if msg.Mod.Contains(tea.ModShift) { + vp.ScrollLeft(6) + } else { + vp.SetYOffset(vp.YOffset() - 1) + } + case tea.MouseWheelDown: + if msg.Mod.Contains(tea.ModShift) { + vp.ScrollRight(6) + } else { + vp.SetYOffset(vp.YOffset() + 1) + } + case tea.MouseWheelLeft: + vp.ScrollLeft(6) + case tea.MouseWheelRight: + vp.ScrollRight(6) + } +}