Hayden Hargreaves 0de38ec837
All checks were successful
Run Test Suite / test (push) Successful in 13s
Run Test Suite / test (pull_request) Successful in 13s
feat: implementing the repeat commands! Tested
2026-03-13 23:00:26 -07:00

138 lines
2.6 KiB
Go

package action
import (
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
type FindChar struct {
Char string
Forward bool
Inclusive bool
Count int
Repeated bool
}
func (m FindChar) WithChar(char string) Motion {
m.Char = char
return m
}
func (m FindChar) Type() core.MotionType {
if m.Inclusive {
return core.CharwiseInclusive
}
return core.CharwiseExclusive
}
// WithCount sets the count (required by Repeatable interface)
func (m FindChar) WithCount(n int) Action {
m.Count = n
return m
}
func (a FindChar) Execute(m Model) tea.Cmd {
// Required to allow ';' and '.'
// But we should not override when we repeat the action
if !a.Repeated {
m.SetLastFind(a.Char, a.Forward, a.Inclusive)
}
win := m.ActiveWindow()
buf := win.Buffer
line := buf.Line(win.Cursor.Line)
col := win.Cursor.Col
if len(line) <= 0 {
return nil
}
forwardStart := col + 1
if a.Repeated && !a.Inclusive {
forwardStart = col + 2
}
if a.Forward {
for x := forwardStart; x < len(line); x++ {
if string(line[x]) == a.Char {
if a.Count == 1 {
if a.Inclusive {
win.SetCursorCol(x)
} else {
win.SetCursorCol(x - 1)
}
break
} else {
a.Count--
}
}
}
}
backwardStart := col - 1
if a.Repeated && !a.Inclusive {
backwardStart = col - 2
}
if !a.Forward {
for x := backwardStart; x >= 0; x-- {
if string(line[x]) == a.Char {
if a.Count == 1 {
if a.Inclusive {
win.SetCursorCol(x)
} else {
win.SetCursorCol(x + 1)
}
break
} else {
a.Count--
}
}
}
}
return nil
}
type RepeatFind struct {
Count int
Reverse bool
}
// Resolve builds the concrete FindChar that this repeat represents.
// The FSM calls this before Execute so that the subsequent Type() call
// returns the correct CharwiseInclusive / CharwiseExclusive value.
func (a RepeatFind) Resolve(m Model) Motion {
// for rev out
// T F T
// F T T
// F F F
// T T F
last := m.GetLastFind()
return FindChar{
Char: last.Char,
Forward: last.Forward != a.Reverse, // Logical XOR
Inclusive: last.Inclusive,
Count: a.Count,
Repeated: true,
}
}
// Type falls back to CharwiseExclusive. In practice the FSM always calls
// Resolve first and uses the returned FindChar, so this is never reached
// during normal operation.
func (a RepeatFind) Type() core.MotionType {
return core.CharwiseExclusive
}
func (m RepeatFind) WithCount(n int) Action {
m.Count = n
return m
}
func (a RepeatFind) Execute(m Model) tea.Cmd {
return a.Resolve(m).Execute(m)
}