Gim/internal/motion/jump.go
Hayden Hargreaves 774d0d0071 feat: implement scroll actions, tested
This is control+d and control+u
2026-02-18 18:09:19 -07:00

154 lines
3.6 KiB
Go

package motion
import (
"git.gophernest.net/azpect/TextEditor/internal/action"
tea "github.com/charmbracelet/bubbletea"
)
// MoveToTop implements Motion (gg) - linewise
type MoveToTop struct{}
func (a MoveToTop) Execute(m action.Model) tea.Cmd {
m.SetCursorY(0)
m.ClampCursorX()
return nil
}
func (a MoveToTop) Type() action.MotionType { return action.Linewise }
// MoveToBottom implements Motion (G) - linewise
type MoveToBottom struct{}
func (a MoveToBottom) Execute(m action.Model) tea.Cmd {
m.SetCursorY(m.LineCount() - 1)
m.ClampCursorX()
return nil
}
func (a MoveToBottom) Type() action.MotionType { return action.Linewise }
// MoveToLineStart implements Motion (0) - charwise
type MoveToLineStart struct{}
func (a MoveToLineStart) Execute(m action.Model) tea.Cmd {
m.SetCursorX(0)
m.ClampCursorX()
return nil
}
func (a MoveToLineStart) Type() action.MotionType { return action.Charwise }
// MoveToLineEnd implements Motion ($) - charwise
type MoveToLineEnd struct{}
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
m.SetCursorX(len(m.Line(m.CursorY())))
m.ClampCursorX()
return nil
}
func (a MoveToLineEnd) Type() action.MotionType { return action.Charwise }
// MoveToLineContentStart implements Motion (_) - charwise
type MoveToLineContentStart struct{}
func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
line := m.Line(m.CursorY())
x := 0
for x < len(line) {
ch := line[x]
if ch != ' ' && ch != '\t' {
break
}
x++
}
// If we are on the last char, we overflew, back once
if x == len(line) && x > 0 {
x--
}
m.SetCursorX(x)
return nil
}
func (a MoveToLineContentStart) Type() action.MotionType { return action.Charwise }
// TODO: Count for these, maybe?
// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
type ScrollDownHalfPage struct{}
func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
viewportHeight := m.ViewPortH()
if viewportHeight <= 0 {
return nil
}
scroll := viewportHeight / 2
scrollOff := m.Settings().ScrollOff
// Current relative position in viewport
relY := m.CursorY() - m.ScrollY()
// Scroll down, clamped to valid range
newScrollY := m.ScrollY() + scroll
maxScroll := max(0, m.LineCount()-viewportHeight)
newScrollY = min(newScrollY, maxScroll)
m.SetScrollY(newScrollY)
// Maintain relative position, respecting scrollOff
if relY < scrollOff {
relY = scrollOff
}
if relY > viewportHeight-1-scrollOff {
relY = viewportHeight - 1 - scrollOff
}
newCursorY := newScrollY + relY
newCursorY = max(0, min(newCursorY, m.LineCount()-1))
m.SetCursorY(newCursorY)
m.ClampCursorX()
return nil
}
func (a ScrollDownHalfPage) Type() action.MotionType { return action.Linewise }
// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
type ScrollUpHalfPage struct{}
func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
viewportHeight := m.ViewPortH()
if viewportHeight <= 0 {
return nil
}
scroll := viewportHeight / 2
scrollOff := m.Settings().ScrollOff
// Current relative position in viewport
relY := m.CursorY() - m.ScrollY()
// Scroll up, clamped to valid range
newScrollY := m.ScrollY() - scroll
newScrollY = max(0, newScrollY)
m.SetScrollY(newScrollY)
// Maintain relative position, respecting scrollOff
if relY < scrollOff {
relY = scrollOff
}
if relY > viewportHeight-1-scrollOff {
relY = viewportHeight - 1 - scrollOff
}
newCursorY := newScrollY + relY
newCursorY = max(0, min(newCursorY, m.LineCount()-1))
m.SetCursorY(newCursorY)
m.ClampCursorX()
return nil
}
func (a ScrollUpHalfPage) Type() action.MotionType { return action.Linewise }