Compare commits
No commits in common. "a5eb8983ca2abe5b0ed9f60a1dce9cb8d6c0f243" and "f12ce37bebe7f7a1d29aaff36714e8a83e5f700f" have entirely different histories.
a5eb8983ca
...
f12ce37beb
@ -1,83 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (f FindChar) WithCount(n int) Action {
|
|
||||||
f.Count = n
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a FindChar) Execute(m Model) tea.Cmd {
|
|
||||||
// Get the line
|
|
||||||
// Get the current position, moved based on inputs
|
|
||||||
win := m.ActiveWindow()
|
|
||||||
buf := win.Buffer
|
|
||||||
|
|
||||||
line := buf.Line(win.Cursor.Line)
|
|
||||||
col := win.Cursor.Col
|
|
||||||
|
|
||||||
if len(line) <= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.Forward {
|
|
||||||
for x := col; 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--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !a.Forward {
|
|
||||||
for x := col; 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
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -83,10 +83,3 @@ type DoublePresser interface {
|
|||||||
type Repeatable interface {
|
type Repeatable interface {
|
||||||
WithCount(n int) Action
|
WithCount(n int) Action
|
||||||
}
|
}
|
||||||
|
|
||||||
// CharMotion is a motion that requires a character argument (f/t/F/T)
|
|
||||||
// The state machine will call WithChar to set the character before executing
|
|
||||||
type CharMotion interface {
|
|
||||||
Motion
|
|
||||||
WithChar(char string) Motion
|
|
||||||
}
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const (
|
|||||||
StateCount
|
StateCount
|
||||||
StateOperatorPending
|
StateOperatorPending
|
||||||
StateMotionCount
|
StateMotionCount
|
||||||
StateWaitingForChar // Waiting for character argument (f/t/F/T)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler: Manages input processing with a state machine for vim-style commands.
|
// Handler: Manages input processing with a state machine for vim-style commands.
|
||||||
@ -27,7 +26,6 @@ type Handler struct {
|
|||||||
operatorKey string // track which key started operator (for dd, yy, cc)
|
operatorKey string // track which key started operator (for dd, yy, cc)
|
||||||
buffer string // for display (what user has typed)
|
buffer string // for display (what user has typed)
|
||||||
pending string // partial key sequence (e.g., "g" waiting for second key)
|
pending string // partial key sequence (e.g., "g" waiting for second key)
|
||||||
charMotionType string // which char motion is waiting: "f", "t", "F", or "T"
|
|
||||||
|
|
||||||
// Keymaps
|
// Keymaps
|
||||||
normalKeymap *Keymap
|
normalKeymap *Keymap
|
||||||
@ -72,11 +70,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
return h.handleCommandKey(m, key)
|
return h.handleCommandKey(m, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If waiting for character argument (f/t/F/T), capture it
|
|
||||||
if h.state == StateWaitingForChar {
|
|
||||||
return h.handleCharMotion(m, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to accumulate count (only if no pending sequence)
|
// Try to accumulate count (only if no pending sequence)
|
||||||
if h.pending == "" && h.tryAccumulateCount(key) {
|
if h.pending == "" && h.tryAccumulateCount(key) {
|
||||||
return nil
|
return nil
|
||||||
@ -127,13 +120,6 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
|||||||
|
|
||||||
// Handler.dispatch: Routes to the appropriate handler based on current state.
|
// Handler.dispatch: Routes to the appropriate handler based on current state.
|
||||||
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
|
func (h *Handler) dispatch(m action.Model, kind string, binding any, key string) tea.Cmd {
|
||||||
// Handle character motions (f/t/F/T) - transition to waiting state
|
|
||||||
if kind == "char_motion" {
|
|
||||||
h.charMotionType = key
|
|
||||||
h.state = StateWaitingForChar
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch h.state {
|
switch h.state {
|
||||||
case StateReady, StateCount:
|
case StateReady, StateCount:
|
||||||
return h.handleInitial(m, kind, binding, key)
|
return h.handleInitial(m, kind, binding, key)
|
||||||
@ -234,83 +220,6 @@ func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler.handleCharMotion: Handles input when waiting for a character argument
|
|
||||||
// for f/t/F/T motions. Captures the character and creates the appropriate motion.
|
|
||||||
//
|
|
||||||
// USAGE FOR IMPLEMENTING f/t/F/T MOTIONS:
|
|
||||||
//
|
|
||||||
// You need to create a CharMotion interface in internal/action/interface.go:
|
|
||||||
//
|
|
||||||
// type CharMotion interface {
|
|
||||||
// Motion
|
|
||||||
// WithChar(char string) Motion
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Then implement it for your FindChar motion (example):
|
|
||||||
//
|
|
||||||
// type FindChar struct {
|
|
||||||
// Char string
|
|
||||||
// Forward bool // true = f/t, false = F/T
|
|
||||||
// To bool // true = f/F (to char), false = t/T (till before/after char)
|
|
||||||
// Count int
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (f FindChar) WithChar(char string) action.Motion {
|
|
||||||
// f.Char = char
|
|
||||||
// return f
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// The state machine will:
|
|
||||||
// 1. Call WithChar(key) to set the character
|
|
||||||
// 2. Apply count if the motion is Repeatable
|
|
||||||
// 3. If operator pending (df{char}), execute motion and operate on range
|
|
||||||
// 4. Otherwise just execute the motion
|
|
||||||
func (h *Handler) handleCharMotion(m action.Model, key string) tea.Cmd {
|
|
||||||
count := h.effectiveCount()
|
|
||||||
|
|
||||||
// Get the char motion from the keymap
|
|
||||||
// The keymap should have registered f/t/F/T as "char_motion" type
|
|
||||||
// and stored the motion template (without character set yet)
|
|
||||||
motion := h.currentKeymap.LookupCharMotion(h.charMotionType)
|
|
||||||
if motion == nil {
|
|
||||||
// Motion not found - shouldn't happen if keymap configured correctly
|
|
||||||
h.Reset()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type assert to CharMotion interface and set the character
|
|
||||||
charMot, ok := motion.(action.CharMotion)
|
|
||||||
if !ok {
|
|
||||||
// Motion doesn't implement CharMotion interface
|
|
||||||
h.Reset()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the character that was pressed
|
|
||||||
mot := charMot.WithChar(key)
|
|
||||||
|
|
||||||
// Apply count if supported
|
|
||||||
if r, ok := mot.(action.Repeatable); ok {
|
|
||||||
mot = r.WithCount(count).(action.Motion)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If operator pending (e.g., "df{char}"), get range and operate
|
|
||||||
if h.operator != nil {
|
|
||||||
win := m.ActiveWindow()
|
|
||||||
start := win.Cursor
|
|
||||||
mot.Execute(m)
|
|
||||||
end := win.Cursor
|
|
||||||
cmd := h.operator.Operate(m, start, end, mot.Type())
|
|
||||||
h.Reset()
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise just execute the motion
|
|
||||||
cmd := mot.Execute(m)
|
|
||||||
h.Reset()
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler.tryAccumulateCount: Attempts to add a digit to the count. Returns
|
// Handler.tryAccumulateCount: Attempts to add a digit to the count. Returns
|
||||||
// true if successful, false if the key is not a digit or is an invalid count.
|
// true if successful, false if the key is not a digit or is an invalid count.
|
||||||
func (h *Handler) tryAccumulateCount(key string) bool {
|
func (h *Handler) tryAccumulateCount(key string) bool {
|
||||||
@ -368,7 +277,6 @@ func (h *Handler) Reset() {
|
|||||||
h.operatorKey = ""
|
h.operatorKey = ""
|
||||||
h.buffer = ""
|
h.buffer = ""
|
||||||
h.pending = ""
|
h.pending = ""
|
||||||
h.charMotionType = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler.Pending: Returns the accumulated input buffer for display.
|
// Handler.Pending: Returns the accumulated input buffer for display.
|
||||||
|
|||||||
@ -12,7 +12,6 @@ type Keymap struct {
|
|||||||
motions map[string]action.Motion
|
motions map[string]action.Motion
|
||||||
operators map[string]action.Operator
|
operators map[string]action.Operator
|
||||||
actions map[string]action.Action // standalone actions: i.e., 'i', 'a'
|
actions map[string]action.Action // standalone actions: i.e., 'i', 'a'
|
||||||
charMotions map[string]action.Motion // motions that need character argument: f/t/F/T
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNormalKeymap: Creates a keymap for normal mode with all standard vim bindings.
|
// NewNormalKeymap: Creates a keymap for normal mode with all standard vim bindings.
|
||||||
@ -64,12 +63,6 @@ func NewNormalKeymap() *Keymap {
|
|||||||
"p": action.Paste{Count: 1},
|
"p": action.Paste{Count: 1},
|
||||||
"P": action.PasteBefore{Count: 1},
|
"P": action.PasteBefore{Count: 1},
|
||||||
},
|
},
|
||||||
charMotions: map[string]action.Motion{
|
|
||||||
"f": action.FindChar{Forward: true, Inclusive: true},
|
|
||||||
"F": action.FindChar{Forward: false, Inclusive: true},
|
|
||||||
"t": action.FindChar{Forward: true, Inclusive: false},
|
|
||||||
"T": action.FindChar{Forward: false, Inclusive: false},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,12 +97,6 @@ func NewVisualKeymap() *Keymap {
|
|||||||
"p": action.VisualPaste{Count: 1},
|
"p": action.VisualPaste{Count: 1},
|
||||||
// ":": action.EnterComandMode{}, // Different OP
|
// ":": action.EnterComandMode{}, // Different OP
|
||||||
},
|
},
|
||||||
charMotions: map[string]action.Motion{
|
|
||||||
"f": action.FindChar{Forward: true, Inclusive: true},
|
|
||||||
"F": action.FindChar{Forward: false, Inclusive: true},
|
|
||||||
"t": action.FindChar{Forward: true, Inclusive: false},
|
|
||||||
"T": action.FindChar{Forward: false, Inclusive: false},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +140,7 @@ func NewCommandKeymap() *Keymap {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keymap.Lookup: Returns the type and value of a key binding (motion, operator, action, or char_motion).
|
// Keymap.Lookup: Returns the type and value of a key binding (motion, operator, or action).
|
||||||
func (km *Keymap) Lookup(key string) (kind string, value any) {
|
func (km *Keymap) Lookup(key string) (kind string, value any) {
|
||||||
if m, ok := km.motions[key]; ok {
|
if m, ok := km.motions[key]; ok {
|
||||||
return "motion", m
|
return "motion", m
|
||||||
@ -164,9 +151,6 @@ func (km *Keymap) Lookup(key string) (kind string, value any) {
|
|||||||
if a, ok := km.actions[key]; ok {
|
if a, ok := km.actions[key]; ok {
|
||||||
return "action", a
|
return "action", a
|
||||||
}
|
}
|
||||||
if cm, ok := km.charMotions[key]; ok {
|
|
||||||
return "char_motion", cm
|
|
||||||
}
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,19 +171,5 @@ func (km *Keymap) HasPrefix(prefix string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key := range km.charMotions {
|
|
||||||
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keymap.LookupCharMotion: Returns the motion template for character motions (f/t/F/T).
|
|
||||||
// The returned motion should implement the CharMotion interface.
|
|
||||||
func (km *Keymap) LookupCharMotion(key string) action.Motion {
|
|
||||||
if cm, ok := km.charMotions[key]; ok {
|
|
||||||
return cm
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user