Hayden Hargreaves 6033e58d0e
All checks were successful
Run Test Suite / test (push) Successful in 17s
feat: implement the r action, tested
Began work on replace mode, but not complete.
2026-04-05 22:58:07 -07:00

201 lines
5.1 KiB
Go

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 }