138 lines
2.6 KiB
Go
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)
|
|
}
|