package motion import ( "git.gophernest.net/azpect/TextEditor/internal/action" "git.gophernest.net/azpect/TextEditor/internal/core" tea "github.com/charmbracelet/bubbletea" ) // MoveToTop implements Motion (gg) - linewise type MoveToTop struct{} // MoveToTop.Execute: Moves the cursor to the first line of the buffer. func (a MoveToTop) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() win.SetCursorLine(0) return nil } func (a MoveToTop) Type() core.MotionType { return core.Linewise } // MoveToBottom implements Motion (G) - linewise type MoveToBottom struct{} // MoveToBottom.Execute: Moves the cursor to the last line of the buffer. func (a MoveToBottom) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() win.SetCursorLine(buf.LineCount() - 1) return nil } func (a MoveToBottom) Type() core.MotionType { return core.Linewise } // MoveToLineStart implements Motion (0) - charwise type MoveToLineStart struct{} // MoveToLineStart.Execute: Moves the cursor to the beginning of the current line. func (a MoveToLineStart) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() win.SetCursorCol(0) return nil } func (a MoveToLineStart) Type() core.MotionType { return core.CharwiseExclusive } // MoveToLineEnd implements Motion ($) - charwise type MoveToLineEnd struct{} // MoveToLineEnd.Execute: Moves the cursor to the end of the current line. func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() win.SetCursorCol(buf.Lines[win.Cursor.Line].Len() - 1) return nil } func (a MoveToLineEnd) Type() core.MotionType { return core.CharwiseInclusive } // MoveToLineContentStart implements Motion (_) - charwise type MoveToLineContentStart struct{} // MoveToLineContentStart.Execute: Moves the cursor to the first non-whitespace // character on the current line. func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() line := buf.Line(win.Cursor.Line) 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-- } win.SetCursorCol(x) return nil } func (a MoveToLineContentStart) Type() core.MotionType { return core.CharwiseExclusive } // MoveToColumn implements Motion (|) - charwise type MoveToColumn struct { Count int } // MoveToColumn.Execute: Moves the cursor to the column specified by Count. func (a MoveToColumn) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() line := buf.Line(win.Cursor.Line) col := min(a.Count-1, len(line)-1) win.SetCursorCol(col) return nil } func (a MoveToColumn) Type() core.MotionType { return core.CharwiseExclusive } func (a MoveToColumn) WithCount(n int) action.Action { return MoveToColumn{Count: n} } // TODO: Count for these, maybe? // ScrollDownPage implements Motion (ctrl+d) - linewise type ScrollDownPage struct { Divisor int } // ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the // cursor's relative position in the viewport. func (a ScrollDownPage) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() viewportHeight := win.ViewportHeight() if viewportHeight <= 0 { return nil } scroll := viewportHeight / a.Divisor scrollOff := win.Options.ScrollOff // Current relative position in viewport relY := win.Cursor.Line - win.ScrollY // Scroll down, clamped to valid range newScrollY := win.ScrollY + scroll maxScroll := max(0, buf.LineCount()-viewportHeight) newScrollY = min(newScrollY, maxScroll) win.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, buf.LineCount()-1)) win.SetCursorLine(newCursorY) return nil } func (a ScrollDownPage) Type() core.MotionType { return core.Linewise } // ScrollUpPage implements Motion (ctrl+u) - linewise type ScrollUpPage struct { Divisor int } // ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the // cursor's relative position in the viewport. func (a ScrollUpPage) Execute(m action.Model) tea.Cmd { win := m.ActiveWindow() buf := m.ActiveBuffer() viewportHeight := win.ViewportHeight() if viewportHeight <= 0 { return nil } scroll := viewportHeight / a.Divisor scrollOff := win.Options.ScrollOff // Current relative position in viewport relY := win.Cursor.Line - win.ScrollY // Scroll up, clamped to valid range newScrollY := win.ScrollY - scroll newScrollY = max(0, newScrollY) win.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, buf.LineCount()-1)) win.SetCursorLine(newCursorY) return nil } func (a ScrollUpPage) Type() core.MotionType { return core.Linewise }