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.CharwiseExclusive } // 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.CharwiseInclusive } // 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.CharwiseExclusive } // MoveToColumn implements Motion (|) - charwise type MoveToColumn struct { Count int } func (a MoveToColumn) Execute(m action.Model) tea.Cmd { line := m.Line(m.CursorY()) col := min(a.Count-1, len(line)-1) m.SetCursorX(col) m.ClampCursorX() return nil } func (a MoveToColumn) Type() action.MotionType { return action.CharwiseExclusive } func (a MoveToColumn) WithCount(n int) action.Action { return MoveToColumn{Count: n} } // 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 }