The tests are starting to get messy, lots of duplication. Going to resolve that. Lots of this is due to AI generation of tests.
210 lines
6.9 KiB
Go
210 lines
6.9 KiB
Go
package input
|
|
|
|
import (
|
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
|
"git.gophernest.net/azpect/TextEditor/internal/command"
|
|
"git.gophernest.net/azpect/TextEditor/internal/motion"
|
|
"git.gophernest.net/azpect/TextEditor/internal/operator"
|
|
)
|
|
|
|
// Keymap: Maps key sequences to motions, operators, and actions.
|
|
type Keymap struct {
|
|
motions map[string]action.Motion
|
|
operators map[string]action.Operator
|
|
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.
|
|
func NewNormalKeymap() *Keymap {
|
|
return &Keymap{
|
|
motions: map[string]action.Motion{
|
|
"j": motion.MoveDown{Count: 1},
|
|
"k": motion.MoveUp{Count: 1},
|
|
"h": motion.MoveLeft{Count: 1},
|
|
"l": motion.MoveRight{Count: 1},
|
|
"G": motion.MoveToBottom{},
|
|
"gg": motion.MoveToTop{},
|
|
"0": motion.MoveToLineStart{},
|
|
"$": motion.MoveToLineEnd{},
|
|
"_": motion.MoveToLineContentStart{},
|
|
"^": motion.MoveToLineContentStart{},
|
|
"|": motion.MoveToColumn{Count: 0},
|
|
"w": motion.MoveForwardWord{Count: 1},
|
|
"W": motion.MoveForwardWORD{Count: 1},
|
|
"e": motion.MoveForwardWordEnd{Count: 1},
|
|
"E": motion.MoveForwardWORDEnd{Count: 1},
|
|
"b": motion.MoveBackwardWord{Count: 1},
|
|
"ctrl+u": motion.ScrollUpHalfPage{},
|
|
"ctrl+d": motion.ScrollDownHalfPage{},
|
|
";": action.RepeatFind{Count: 1, Reverse: false},
|
|
".": action.RepeatFind{Count: 1, Reverse: true},
|
|
},
|
|
operators: map[string]action.Operator{
|
|
"d": operator.DeleteOperator{},
|
|
"y": operator.YankOperator{},
|
|
"c": operator.ChangeOperator{}, // TODO: Finish implementing
|
|
// "s": SubstitueOp{},
|
|
// "~": SwapCaseOp{},
|
|
},
|
|
actions: map[string]action.Action{
|
|
"i": action.EnterInsert{},
|
|
"a": action.EnterInsertAfter{},
|
|
"I": action.EnterInsertLineStart{},
|
|
"A": action.EnterInsertLineEnd{},
|
|
"o": action.OpenLineBelow{},
|
|
"O": action.OpenLineAbove{},
|
|
"x": action.DeleteChar{Count: 1},
|
|
":": action.EnterComandMode{},
|
|
"v": action.EnterVisualMode{},
|
|
"V": action.EnterVisualLineMode{},
|
|
"ctrl+v": action.EnterVisualBlockMode{},
|
|
"D": action.DeleteToEndOfLine{Count: 1},
|
|
"C": action.ChangeToEndOfLine{Count: 1},
|
|
"s": action.SubstituteChar{Count: 1},
|
|
"S": action.SubstituteLine{Count: 1},
|
|
"p": action.Paste{Count: 1},
|
|
"P": action.PasteBefore{Count: 1},
|
|
},
|
|
charMotions: map[string]action.Motion{
|
|
"f": action.FindChar{Forward: true, Inclusive: true, Repeated: false},
|
|
"F": action.FindChar{Forward: false, Inclusive: true, Repeated: false},
|
|
"t": action.FindChar{Forward: true, Inclusive: false, Repeated: false},
|
|
"T": action.FindChar{Forward: false, Inclusive: false, Repeated: false},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewVisualKeymap: Creates a keymap for visual modes (character, line, block).
|
|
func NewVisualKeymap() *Keymap {
|
|
return &Keymap{
|
|
motions: map[string]action.Motion{
|
|
"j": motion.MoveDown{Count: 1},
|
|
"k": motion.MoveUp{Count: 1},
|
|
"h": motion.MoveLeft{Count: 1},
|
|
"l": motion.MoveRight{Count: 1},
|
|
"G": motion.MoveToBottom{},
|
|
"gg": motion.MoveToTop{},
|
|
"0": motion.MoveToLineStart{},
|
|
"$": motion.MoveToLineEnd{},
|
|
"_": motion.MoveToLineContentStart{},
|
|
"^": motion.MoveToLineContentStart{},
|
|
"|": motion.MoveToColumn{Count: 0},
|
|
"w": motion.MoveForwardWord{Count: 1},
|
|
"W": motion.MoveForwardWORD{Count: 1},
|
|
"e": motion.MoveForwardWordEnd{Count: 1},
|
|
"E": motion.MoveForwardWORDEnd{Count: 1},
|
|
"b": motion.MoveBackwardWord{Count: 1},
|
|
},
|
|
operators: map[string]action.Operator{
|
|
"d": operator.DeleteOperator{},
|
|
"x": operator.DeleteOperator{},
|
|
"y": operator.YankOperator{},
|
|
"c": operator.ChangeOperator{},
|
|
},
|
|
actions: map[string]action.Action{
|
|
"p": action.VisualPaste{Count: 1},
|
|
// ":": 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},
|
|
},
|
|
}
|
|
}
|
|
|
|
// NewInsertKeymap: Creates a keymap for insert mode with editing actions.
|
|
func NewInsertKeymap() *Keymap {
|
|
return &Keymap{
|
|
motions: map[string]action.Motion{
|
|
"down": motion.MoveDown{Count: 1},
|
|
"up": motion.MoveUp{Count: 1},
|
|
"left": motion.MoveLeft{Count: 1},
|
|
"right": motion.MoveRight{Count: 1},
|
|
},
|
|
operators: map[string]action.Operator{}, // this will likely be empty
|
|
actions: map[string]action.Action{
|
|
"enter": action.InsertNewline{},
|
|
"backspace": action.InsertBackspace{},
|
|
"delete": action.InsertDelete{},
|
|
"tab": action.InsertTab{},
|
|
"ctrl+w": action.InsertDeletePreviousWord{},
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
// NewCommandKeymap: Creates a keymap for command mode with command line editing.
|
|
func NewCommandKeymap() *Keymap {
|
|
return &Keymap{
|
|
motions: map[string]action.Motion{
|
|
"left": motion.MoveCommandLeft{},
|
|
"right": motion.MoveCommandRight{},
|
|
"up": motion.MoveCommandHistoryUp{},
|
|
"down": motion.MoveCommandHistoryDown{},
|
|
},
|
|
operators: map[string]action.Operator{}, // this will likely be empty
|
|
actions: map[string]action.Action{
|
|
"esc": action.ExitCommandMode{},
|
|
"enter": action.CommandExecute{Registry: command.DefaultRegistry},
|
|
"backspace": action.CommandBackspace{},
|
|
"delete": action.CommandDelete{},
|
|
"ctrl+w": action.CommandDeletePreviousWord{},
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
// Keymap.Lookup: Returns the type and value of a key binding (motion, operator, action, or char_motion).
|
|
func (km *Keymap) Lookup(key string) (kind string, value any) {
|
|
if m, ok := km.motions[key]; ok {
|
|
return "motion", m
|
|
}
|
|
if o, ok := km.operators[key]; ok {
|
|
return "operator", o
|
|
}
|
|
if a, ok := km.actions[key]; ok {
|
|
return "action", a
|
|
}
|
|
if cm, ok := km.charMotions[key]; ok {
|
|
return "char_motion", cm
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
// Keymap.HasPrefix: Returns true if any binding starts with the given prefix.
|
|
func (km *Keymap) HasPrefix(prefix string) bool {
|
|
for key := range km.motions {
|
|
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
|
return true
|
|
}
|
|
}
|
|
for key := range km.operators {
|
|
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
|
return true
|
|
}
|
|
}
|
|
for key := range km.actions {
|
|
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
|
return true
|
|
}
|
|
}
|
|
for key := range km.charMotions {
|
|
if len(key) > len(prefix) && key[:len(prefix)] == prefix {
|
|
return true
|
|
}
|
|
}
|
|
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
|
|
}
|