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) }