refactor: huge refactor, this looks amazing.
Lots of comments from the AI. Some tests are not passing though
This commit is contained in:
parent
154558b790
commit
ccb061989a
@ -3,16 +3,18 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/editor"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// main: Entry point for the Gim text editor. Creates a buffer and window,
|
||||
// initializes the editor model, and runs the BubbleTea TUI program.
|
||||
func main() {
|
||||
buf := action.NewBufferBuilder().
|
||||
buf := core.NewBufferBuilder().
|
||||
Build()
|
||||
|
||||
win := action.NewWindowBuilder().
|
||||
win := core.NewWindowBuilder().
|
||||
WithBuffer(&buf).
|
||||
Build()
|
||||
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
package action
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// ChangeToEndOfLine implements Action (C) - changes from cursor to end of line
|
||||
type ChangeToEndOfLine struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// ChangeToEndOfLine.Execute: Changes from cursor to end of line and enters insert mode (C key).
|
||||
func (a ChangeToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -16,14 +20,14 @@ func (a ChangeToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Save deleted text to register
|
||||
if pos < len(line) {
|
||||
m.UpdateDefaultRegister(CharwiseRegister, []string{line[pos:]})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{line[pos:]})
|
||||
}
|
||||
|
||||
// Delete to end of line
|
||||
buf.SetLine(win.Cursor.Line, line[:pos])
|
||||
|
||||
// Enter insert mode
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -31,6 +35,7 @@ func (a ChangeToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
// Ensure ChangeToEndOfLine implements Repeatable
|
||||
var _ Repeatable = ChangeToEndOfLine{}
|
||||
|
||||
// ChangeToEndOfLine.WithCount: Returns a new ChangeToEndOfLine with the given count.
|
||||
func (a ChangeToEndOfLine) WithCount(n int) Action {
|
||||
return ChangeToEndOfLine{Count: n}
|
||||
}
|
||||
@ -40,6 +45,7 @@ type SubstituteChar struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// SubstituteChar.Execute: Deletes Count characters and enters insert mode (s key).
|
||||
func (a SubstituteChar) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -52,14 +58,14 @@ func (a SubstituteChar) Execute(m Model) tea.Cmd {
|
||||
|
||||
if count > 0 {
|
||||
// Save deleted text to register
|
||||
m.UpdateDefaultRegister(CharwiseRegister, []string{line[pos : pos+count]})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{line[pos : pos+count]})
|
||||
|
||||
// Delete the characters
|
||||
buf.SetLine(win.Cursor.Line, line[:pos]+line[pos+count:])
|
||||
}
|
||||
|
||||
// Enter insert mode
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -67,6 +73,7 @@ func (a SubstituteChar) Execute(m Model) tea.Cmd {
|
||||
// Ensure SubstituteChar implements Repeatable
|
||||
var _ Repeatable = SubstituteChar{}
|
||||
|
||||
// SubstituteChar.WithCount: Returns a new SubstituteChar with the given count.
|
||||
func (a SubstituteChar) WithCount(n int) Action {
|
||||
return SubstituteChar{Count: n}
|
||||
}
|
||||
@ -76,6 +83,7 @@ type SubstituteLine struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// SubstituteLine.Execute: Clears Count lines and enters insert mode (S key).
|
||||
func (a SubstituteLine) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -94,7 +102,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
|
||||
}
|
||||
|
||||
// Save deleted lines to register
|
||||
m.UpdateDefaultRegister(LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
|
||||
// Insert empty line at original position
|
||||
insertY := min(y, buf.LineCount())
|
||||
@ -104,7 +112,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorPos(insertY, 0)
|
||||
|
||||
// Enter insert mode
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -112,6 +120,7 @@ func (a SubstituteLine) Execute(m Model) tea.Cmd {
|
||||
// Ensure SubstituteLine implements Repeatable
|
||||
var _ Repeatable = SubstituteLine{}
|
||||
|
||||
// SubstituteLine.WithCount: Returns a new SubstituteLine with the given count.
|
||||
func (a SubstituteLine) WithCount(n int) Action {
|
||||
return SubstituteLine{Count: n}
|
||||
}
|
||||
|
||||
@ -1,24 +1,29 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// ExitCommandMode implements Action - exits command mode and returns to normal mode.
|
||||
type ExitCommandMode struct{}
|
||||
|
||||
// ExitCommandMode.Execute: Exits command mode and returns to normal mode (Esc key).
|
||||
func (a ExitCommandMode) Execute(m Model) tea.Cmd {
|
||||
m.SetCommandCursor(0)
|
||||
m.SetCommand("")
|
||||
m.SetCommandOutput("")
|
||||
m.SetCommandError(nil)
|
||||
m.SetMode(NormalMode)
|
||||
m.SetMode(core.NormalMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertCommandChar implements Action - inserts a character in command mode.
|
||||
type InsertCommandChar struct {
|
||||
Char string
|
||||
}
|
||||
|
||||
// InsertCommandChar.Execute: Inserts a character at the command cursor position.
|
||||
func (a InsertCommandChar) Execute(m Model) tea.Cmd {
|
||||
cur := m.CommandCursor()
|
||||
cmd := m.Command()
|
||||
@ -28,8 +33,10 @@ func (a InsertCommandChar) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandBackspace implements Action - deletes character before cursor in command mode.
|
||||
type CommandBackspace struct{}
|
||||
|
||||
// CommandBackspace.Execute: Deletes the character before the command cursor (Backspace key).
|
||||
func (a CommandBackspace) Execute(m Model) tea.Cmd {
|
||||
cur := m.CommandCursor()
|
||||
cmd := m.Command()
|
||||
@ -42,8 +49,10 @@ func (a CommandBackspace) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandDelete implements Action - deletes character at cursor in command mode.
|
||||
type CommandDelete struct{}
|
||||
|
||||
// CommandDelete.Execute: Deletes the character at the command cursor (Delete key).
|
||||
func (a CommandDelete) Execute(m Model) tea.Cmd {
|
||||
cur := m.CommandCursor()
|
||||
cmd := m.Command()
|
||||
@ -62,8 +71,10 @@ func (a CommandDelete) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandDeletePreviousWord implements Action - deletes word before cursor in command mode.
|
||||
type CommandDeletePreviousWord struct{}
|
||||
|
||||
// CommandDeletePreviousWord.Execute: Deletes the word before the command cursor (Ctrl+W).
|
||||
func (a CommandDeletePreviousWord) Execute(m Model) tea.Cmd {
|
||||
cur := m.CommandCursor()
|
||||
cmd := m.Command()
|
||||
@ -101,22 +112,24 @@ func (a CommandDeletePreviousWord) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandExecute implements Action - executes the command line.
|
||||
type CommandExecute struct {
|
||||
Registry CommandRegistry
|
||||
}
|
||||
|
||||
// CommandRegistry interface for executing commands
|
||||
// CommandRegistry: Interface for executing commands.
|
||||
type CommandRegistry interface {
|
||||
Execute(m Model, cmdLine string) (tea.Cmd, error)
|
||||
}
|
||||
|
||||
// CommandExecute.Execute: Executes the command line (Enter key).
|
||||
func (a CommandExecute) Execute(m Model) tea.Cmd {
|
||||
cmdLine := m.Command()
|
||||
|
||||
// Clear command state and return to normal mode
|
||||
m.SetCommandCursor(0)
|
||||
m.SetCommandError(nil)
|
||||
m.SetMode(NormalMode)
|
||||
m.SetMode(core.NormalMode)
|
||||
|
||||
if a.Registry == nil || cmdLine == "" {
|
||||
return nil
|
||||
|
||||
@ -7,6 +7,7 @@ type DeleteChar struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// DeleteChar.Execute: Deletes Count characters at the cursor position (x key).
|
||||
func (a DeleteChar) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -21,14 +22,18 @@ func (a DeleteChar) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteChar.WithCount: Returns a new DeleteChar with the given count.
|
||||
func (a DeleteChar) WithCount(n int) Action {
|
||||
return DeleteChar{Count: n}
|
||||
}
|
||||
|
||||
// DeleteToEndOfLine implements Action (D) - deletes from cursor to end of line
|
||||
// and optionally Count-1 additional lines below.
|
||||
type DeleteToEndOfLine struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// DeleteToEndOfLine.Execute: Deletes from cursor to end of line and Count-1 lines below (D key).
|
||||
func (a DeleteToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -75,6 +80,7 @@ func (a DeleteToEndOfLine) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteToEndOfLine.WithCount: Returns a new DeleteToEndOfLine with the given count.
|
||||
func (a DeleteToEndOfLine) WithCount(n int) Action {
|
||||
return DeleteToEndOfLine{Count: n}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package action
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -11,13 +12,15 @@ type EnterInsert struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// EnterInsert.Execute: Enters insert mode at the cursor position (i key).
|
||||
func (a EnterInsert) Execute(m Model) tea.Cmd {
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnterInsert.WithCount: Returns a new EnterInsert with the given count.
|
||||
func (a EnterInsert) WithCount(n int) Action {
|
||||
return EnterInsert{Count: n}
|
||||
}
|
||||
@ -27,16 +30,18 @@ type EnterInsertAfter struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// EnterInsertAfter.Execute: Enters insert mode after the cursor position (a key).
|
||||
func (a EnterInsertAfter) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetCursorCol(win.Cursor.Col + 1)
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnterInsertAfter.WithCount: Returns a new EnterInsertAfter with the given count.
|
||||
func (a EnterInsertAfter) WithCount(n int) Action {
|
||||
return EnterInsertAfter{Count: n}
|
||||
}
|
||||
@ -46,16 +51,18 @@ type EnterInsertLineStart struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// EnterInsertLineStart.Execute: Enters insert mode at the start of the line (I key).
|
||||
func (a EnterInsertLineStart) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetCursorCol(0)
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnterInsertLineStart.WithCount: Returns a new EnterInsertLineStart with the given count.
|
||||
func (a EnterInsertLineStart) WithCount(n int) Action {
|
||||
return EnterInsertLineStart{Count: n}
|
||||
}
|
||||
@ -65,6 +72,7 @@ type EnterInsertLineEnd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// EnterInsertLineEnd.Execute: Enters insert mode at the end of the line (A key).
|
||||
func (a EnterInsertLineEnd) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -72,10 +80,11 @@ func (a EnterInsertLineEnd) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnterInsertLineEnd.WithCount: Returns a new EnterInsertLineEnd with the given count.
|
||||
func (a EnterInsertLineEnd) WithCount(n int) Action {
|
||||
return EnterInsertLineEnd{Count: n}
|
||||
}
|
||||
@ -85,6 +94,7 @@ type OpenLineBelow struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// OpenLineBelow.Execute: Opens a new line below the cursor and enters insert mode (o key).
|
||||
func (a OpenLineBelow) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -101,10 +111,11 @@ func (a OpenLineBelow) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenLineBelow.WithCount: Returns a new OpenLineBelow with the given count.
|
||||
func (a OpenLineBelow) WithCount(n int) Action {
|
||||
return OpenLineBelow{Count: n}
|
||||
}
|
||||
@ -114,6 +125,7 @@ type OpenLineAbove struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// OpenLineAbove.Execute: Opens a new line above the cursor and enters insert mode (O key).
|
||||
func (a OpenLineAbove) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -124,10 +136,11 @@ func (a OpenLineAbove) Execute(m Model) tea.Cmd {
|
||||
|
||||
// Start recording
|
||||
m.SetInsertRecording(a.Count, a)
|
||||
m.SetMode(InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenLineAbove.WithCount: Returns a new OpenLineAbove with the given count.
|
||||
func (a OpenLineAbove) WithCount(n int) Action {
|
||||
return OpenLineAbove{Count: n}
|
||||
}
|
||||
@ -139,6 +152,7 @@ type InsertChar struct {
|
||||
Char string
|
||||
}
|
||||
|
||||
// InsertChar.Execute: Inserts a single character at the cursor position.
|
||||
func (a InsertChar) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -157,6 +171,7 @@ func (a InsertChar) Execute(m Model) tea.Cmd {
|
||||
// InsertNewline splits the current line at the cursor (enter key)
|
||||
type InsertNewline struct{}
|
||||
|
||||
// InsertNewline.Execute: Splits the current line at the cursor (Enter key).
|
||||
func (a InsertNewline) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -176,6 +191,7 @@ func (a InsertNewline) Execute(m Model) tea.Cmd {
|
||||
// InsertBackspace deletes the character before the cursor
|
||||
type InsertBackspace struct{}
|
||||
|
||||
// InsertBackspace.Execute: Deletes the character before the cursor (Backspace key).
|
||||
func (a InsertBackspace) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -198,6 +214,7 @@ func (a InsertBackspace) Execute(m Model) tea.Cmd {
|
||||
// InsertDelete deletes the character under/after the cursor (delete key)
|
||||
type InsertDelete struct{}
|
||||
|
||||
// InsertDelete.Execute: Deletes the character at the cursor position (Delete key).
|
||||
func (a InsertDelete) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -217,6 +234,7 @@ func (a InsertDelete) Execute(m Model) tea.Cmd {
|
||||
// InsertTab inserts spaces equal to the tab size
|
||||
type InsertTab struct{}
|
||||
|
||||
// InsertTab.Execute: Inserts spaces equal to the tab size (Tab key).
|
||||
func (a InsertTab) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -236,6 +254,8 @@ func (a InsertTab) Execute(m Model) tea.Cmd {
|
||||
// InsertDeletePreviousWord deletes the word before the cursor (ctrl+w)
|
||||
type InsertDeletePreviousWord struct{}
|
||||
|
||||
// isWordChar: Returns true if the character is a word character (alphanumeric
|
||||
// or underscore).
|
||||
func isWordChar(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
@ -243,10 +263,13 @@ func isWordChar(c byte) bool {
|
||||
c == '_'
|
||||
}
|
||||
|
||||
// isPunctuation: Returns true if the character is punctuation (not whitespace
|
||||
// and not a word character).
|
||||
func isPunctuation(c byte) bool {
|
||||
return c != ' ' && c != '\t' && !isWordChar(c)
|
||||
}
|
||||
|
||||
// InsertDeletePreviousWord.Execute: Deletes the word before the cursor (Ctrl+W).
|
||||
func (a InsertDeletePreviousWord) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -9,10 +10,10 @@ type Model interface {
|
||||
// ==================================================
|
||||
// Core Data Access
|
||||
// ==================================================
|
||||
Windows() []*Window
|
||||
ActiveWindow() *Window
|
||||
Buffers() []*Buffer
|
||||
ActiveBuffer() *Buffer
|
||||
Windows() []*core.Window
|
||||
ActiveWindow() *core.Window
|
||||
Buffers() []*core.Buffer
|
||||
ActiveBuffer() *core.Buffer
|
||||
|
||||
// ==================================================
|
||||
// Insert Mode State
|
||||
@ -41,70 +42,20 @@ type Model interface {
|
||||
// ==================================================
|
||||
// Editor-wide State
|
||||
// ==================================================
|
||||
Mode() Mode
|
||||
SetMode(mode Mode)
|
||||
Mode() core.Mode
|
||||
SetMode(mode core.Mode)
|
||||
|
||||
Settings() Settings
|
||||
SetSettings(s Settings)
|
||||
Settings() core.Settings
|
||||
SetSettings(s core.Settings)
|
||||
|
||||
// ==================================================
|
||||
// Registers
|
||||
// ==================================================
|
||||
Registers() map[rune]Register
|
||||
GetRegister(name rune) (Register, bool)
|
||||
SetRegister(name rune, t RegisterType, cnt []string) error
|
||||
UpdateDefaultRegister(t RegisterType, cnt []string)
|
||||
Registers() map[rune]core.Register
|
||||
GetRegister(name rune) (core.Register, bool)
|
||||
SetRegister(name rune, t core.RegisterType, cnt []string) error
|
||||
UpdateDefaultRegister(t core.RegisterType, cnt []string)
|
||||
|
||||
// ==================================================
|
||||
// Depreciated
|
||||
// ==================================================
|
||||
// Text buffer
|
||||
// Lines() []string
|
||||
// Line(idx int) string
|
||||
// SetLine(idx int, content string)
|
||||
// InsertLine(idx int, content string)
|
||||
// DeleteLine(idx int)
|
||||
// LineCount() int
|
||||
|
||||
// Cursor
|
||||
// CursorX() int
|
||||
// CursorY() int
|
||||
// SetCursorX(x int)
|
||||
// SetCursorY(y int)
|
||||
// ClampCursorX()
|
||||
|
||||
// Window
|
||||
// ScrollY() int
|
||||
// SetScrollY(y int)
|
||||
// WinH() int
|
||||
// WinW() int
|
||||
// ViewPortH() int
|
||||
//
|
||||
// Anchor
|
||||
// AnchorX() int
|
||||
// AnchorY() int
|
||||
// SetAnchorX(x int)
|
||||
// SetAnchorY(y int)
|
||||
|
||||
}
|
||||
|
||||
// Position represents a location in the buffer
|
||||
type Position struct {
|
||||
Line, Col int
|
||||
}
|
||||
|
||||
// MotionType indicates how a motion operates
|
||||
type MotionType int
|
||||
|
||||
const (
|
||||
CharwiseExclusive MotionType = iota // w, b, h, l, 0, ^ - end position not included
|
||||
CharwiseInclusive // e, $, f - end position is included
|
||||
Linewise // j, k, G, gg, {, } - operates on whole lines
|
||||
)
|
||||
|
||||
// IsCharwise returns true if the motion type is character-based (not linewise)
|
||||
func (mt MotionType) IsCharwise() bool {
|
||||
return mt == CharwiseExclusive || mt == CharwiseInclusive
|
||||
}
|
||||
|
||||
// Action is the base interface - anything executable
|
||||
@ -115,12 +66,12 @@ type Action interface {
|
||||
// Motion moves the cursor and returns the range covered
|
||||
type Motion interface {
|
||||
Action
|
||||
Type() MotionType
|
||||
Type() core.MotionType
|
||||
}
|
||||
|
||||
// Operator acts on a range (delete, yank, change)
|
||||
type Operator interface {
|
||||
Operate(m Model, start, end Position, mtype MotionType) tea.Cmd
|
||||
Operate(m Model, start, end core.Position, mtype core.MotionType) tea.Cmd
|
||||
}
|
||||
|
||||
// DoublePresser is an optional interface for operators that support double-press (dd, yy, cc)
|
||||
|
||||
@ -1,19 +1,24 @@
|
||||
package action
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Quit implements Action (ctrl+c)
|
||||
type Quit struct{}
|
||||
|
||||
// Quit.Execute: Quits the editor (Ctrl+C).
|
||||
func (a Quit) Execute(m Model) tea.Cmd {
|
||||
return tea.Quit
|
||||
}
|
||||
|
||||
// Quit implements Action (:)
|
||||
// EnterComandMode implements Action (:) - enters command mode.
|
||||
type EnterComandMode struct{}
|
||||
|
||||
// EnterComandMode.Execute: Enters command mode (: key).
|
||||
func (a EnterComandMode) Execute(m Model) tea.Cmd {
|
||||
m.SetMode(CommandMode)
|
||||
m.SetMode(core.CommandMode)
|
||||
m.SetCommand("")
|
||||
m.SetCommandOutput("")
|
||||
m.SetCommandError(nil)
|
||||
@ -21,35 +26,38 @@ func (a EnterComandMode) Execute(m Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quit implements Action (v)
|
||||
// EnterVisualMode implements Action (v) - enters visual character mode.
|
||||
type EnterVisualMode struct{}
|
||||
|
||||
// EnterVisualMode.Execute: Enters visual character mode (v key).
|
||||
func (a EnterVisualMode) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetAnchorCol(win.Cursor.Col)
|
||||
win.SetAnchorLine(win.Cursor.Line)
|
||||
m.SetMode(VisualMode)
|
||||
m.SetMode(core.VisualMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quit implements Action (V)
|
||||
// EnterVisualLineMode implements Action (V) - enters visual line mode.
|
||||
type EnterVisualLineMode struct{}
|
||||
|
||||
// EnterVisualLineMode.Execute: Enters visual line mode (V key).
|
||||
func (a EnterVisualLineMode) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetAnchorCol(win.Cursor.Col)
|
||||
win.SetAnchorLine(win.Cursor.Line)
|
||||
m.SetMode(VisualLineMode)
|
||||
m.SetMode(core.VisualLineMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Quit implements Action (ctrl+v)
|
||||
// EnterVisualBlockMode implements Action (Ctrl+V) - enters visual block mode.
|
||||
type EnterVisualBlockMode struct{}
|
||||
|
||||
// EnterVisualBlockMode.Execute: Enters visual block mode (Ctrl+V).
|
||||
func (a EnterVisualBlockMode) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetAnchorCol(win.Cursor.Col)
|
||||
win.SetAnchorLine(win.Cursor.Line)
|
||||
m.SetMode(VisualBlockMode)
|
||||
m.SetMode(core.VisualBlockMode)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -12,6 +13,7 @@ type Paste struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// Paste.Execute: Pastes register content after the cursor position (p key).
|
||||
func (a Paste) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -29,7 +31,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
}
|
||||
|
||||
switch reg.Type {
|
||||
case LinewiseRegister:
|
||||
case core.LinewiseRegister:
|
||||
{
|
||||
initY := win.Cursor.Line
|
||||
lines := reg.Content
|
||||
@ -47,7 +49,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorLine(initY + 1)
|
||||
}
|
||||
}
|
||||
case CharwiseRegister:
|
||||
case core.CharwiseRegister:
|
||||
{
|
||||
lines := reg.Content
|
||||
|
||||
@ -71,7 +73,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
}
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("Register type is not implemented."))
|
||||
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -80,6 +82,7 @@ func (a Paste) Execute(m Model) tea.Cmd {
|
||||
// Ensure Paste implements Repeatable
|
||||
var _ Repeatable = Paste{}
|
||||
|
||||
// Paste.WithCount: Returns a new Paste with the given count.
|
||||
func (a Paste) WithCount(n int) Action {
|
||||
return Paste{Count: n}
|
||||
}
|
||||
@ -89,6 +92,7 @@ type PasteBefore struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// PasteBefore.Execute: Pastes register content before the cursor position (P key).
|
||||
func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -101,7 +105,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
}
|
||||
|
||||
switch reg.Type {
|
||||
case LinewiseRegister:
|
||||
case core.LinewiseRegister:
|
||||
{
|
||||
initY := win.Cursor.Line
|
||||
lines := reg.Content
|
||||
@ -115,7 +119,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
}
|
||||
}
|
||||
}
|
||||
case CharwiseRegister:
|
||||
case core.CharwiseRegister:
|
||||
{
|
||||
lines := reg.Content
|
||||
|
||||
@ -139,7 +143,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
win.SetCursorCol(x + len(cnt))
|
||||
}
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("Register type is not implemented."))
|
||||
m.SetCommandError(fmt.Errorf("core.Register type is not implemented."))
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -148,6 +152,7 @@ func (a PasteBefore) Execute(m Model) tea.Cmd {
|
||||
// Ensure PasteBefore implements Repeatable
|
||||
var _ Repeatable = PasteBefore{}
|
||||
|
||||
// PasteBefore.WithCount: Returns a new PasteBefore with the given count.
|
||||
func (a PasteBefore) WithCount(n int) Action {
|
||||
return PasteBefore{Count: n}
|
||||
}
|
||||
@ -157,6 +162,7 @@ type VisualPaste struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// VisualPaste.Execute: Replaces visual selection with register content (p in visual mode).
|
||||
func (a VisualPaste) Execute(m Model) tea.Cmd {
|
||||
// Get register content to paste
|
||||
reg, found := m.GetRegister('"')
|
||||
@ -171,26 +177,26 @@ func (a VisualPaste) Execute(m Model) tea.Cmd {
|
||||
start, end := normalizeSelection(m)
|
||||
|
||||
switch mode {
|
||||
case VisualMode:
|
||||
case core.VisualMode:
|
||||
visualCharPaste(m, reg, start, end)
|
||||
case VisualBlockMode:
|
||||
case core.VisualBlockMode:
|
||||
visualBlockPaste(m, reg, start, end)
|
||||
case VisualLineMode:
|
||||
case core.VisualLineMode:
|
||||
visualLinePaste(m, reg, start, end)
|
||||
}
|
||||
|
||||
// Exit visual mode
|
||||
m.SetMode(NormalMode)
|
||||
m.SetMode(core.NormalMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeSelection returns start and end positions with start always before end
|
||||
func normalizeSelection(m Model) (Position, Position) {
|
||||
// normalizeSelection: Returns start and end positions with start always before end.
|
||||
func normalizeSelection(m Model) (core.Position, core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
|
||||
start := Position{Line: win.Anchor.Line, Col: win.Anchor.Col}
|
||||
end := Position{Line: win.Cursor.Line, Col: win.Cursor.Col}
|
||||
start := core.Position{Line: win.Anchor.Line, Col: win.Anchor.Col}
|
||||
end := core.Position{Line: win.Cursor.Line, Col: win.Cursor.Col}
|
||||
|
||||
// Normalize so start is always before end
|
||||
if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
|
||||
@ -200,8 +206,8 @@ func normalizeSelection(m Model) (Position, Position) {
|
||||
return start, end
|
||||
}
|
||||
|
||||
// visualCharPaste handles paste in visual (character) mode
|
||||
func visualCharPaste(m Model, reg Register, start, end Position) {
|
||||
// visualCharPaste: Handles paste operation in visual (character) mode.
|
||||
func visualCharPaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -214,7 +220,7 @@ func visualCharPaste(m Model, reg Register, start, end Position) {
|
||||
// Insert the register content at start position
|
||||
if len(reg.Content) == 0 {
|
||||
// Empty register - just delete (already done)
|
||||
} else if reg.Type == CharwiseRegister {
|
||||
} else if reg.Type == core.CharwiseRegister {
|
||||
// Charwise paste: insert text at cursor position
|
||||
if len(reg.Content) == 1 {
|
||||
line := buf.Lines[start.Line]
|
||||
@ -226,7 +232,7 @@ func visualCharPaste(m Model, reg Register, start, end Position) {
|
||||
win.SetCursorCol(insertAt + len(reg.Content[0]) - 1)
|
||||
win.SetCursorLine(start.Line)
|
||||
}
|
||||
} else if reg.Type == LinewiseRegister {
|
||||
} else if reg.Type == core.LinewiseRegister {
|
||||
// Linewise paste in visual char mode: replace selection with lines
|
||||
// Insert each line from register
|
||||
for i, content := range reg.Content {
|
||||
@ -250,11 +256,11 @@ func visualCharPaste(m Model, reg Register, start, end Position) {
|
||||
}
|
||||
|
||||
// Update register with deleted text
|
||||
m.UpdateDefaultRegister(CharwiseRegister, []string{deletedText})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
|
||||
}
|
||||
|
||||
// visualBlockPaste handles paste in visual block mode
|
||||
func visualBlockPaste(m Model, reg Register, start, end Position) {
|
||||
// visualBlockPaste: Handles paste operation in visual block mode.
|
||||
func visualBlockPaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -286,7 +292,7 @@ func visualBlockPaste(m Model, reg Register, start, end Position) {
|
||||
// Insert register content
|
||||
if len(reg.Content) > 0 {
|
||||
pasteContent := reg.Content[0]
|
||||
if reg.Type == LinewiseRegister && len(reg.Content) > 0 {
|
||||
if reg.Type == core.LinewiseRegister && len(reg.Content) > 0 {
|
||||
pasteContent = reg.Content[0]
|
||||
}
|
||||
|
||||
@ -306,11 +312,11 @@ func visualBlockPaste(m Model, reg Register, start, end Position) {
|
||||
win.SetCursorCol(startCol)
|
||||
|
||||
// Update register with deleted block text (joined)
|
||||
m.UpdateDefaultRegister(CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{strings.Join(deletedLines, "\n")})
|
||||
}
|
||||
|
||||
// visualLinePaste handles paste in visual line mode
|
||||
func visualLinePaste(m Model, reg Register, start, end Position) {
|
||||
// visualLinePaste: Handles paste operation in visual line mode.
|
||||
func visualLinePaste(m Model, reg core.Register, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -331,7 +337,7 @@ func visualLinePaste(m Model, reg Register, start, end Position) {
|
||||
if buf.LineCount() == 0 {
|
||||
buf.InsertLine(0, "")
|
||||
}
|
||||
} else if reg.Type == LinewiseRegister {
|
||||
} else if reg.Type == core.LinewiseRegister {
|
||||
// Linewise register: insert each line
|
||||
insertPos := start.Line
|
||||
for _, content := range reg.Content {
|
||||
@ -348,7 +354,7 @@ func visualLinePaste(m Model, reg Register, start, end Position) {
|
||||
buf.InsertLine(0, "")
|
||||
}
|
||||
|
||||
// Position cursor at start of pasted content
|
||||
// core.Position cursor at start of pasted content
|
||||
y := start.Line
|
||||
if y >= buf.LineCount() {
|
||||
y = buf.LineCount() - 1
|
||||
@ -357,11 +363,11 @@ func visualLinePaste(m Model, reg Register, start, end Position) {
|
||||
win.SetCursorCol(0)
|
||||
|
||||
// Update register with deleted lines
|
||||
m.UpdateDefaultRegister(LinewiseRegister, deletedLines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, deletedLines)
|
||||
}
|
||||
|
||||
// extractCharSelection extracts text from a character selection
|
||||
func extractCharSelection(m Model, start, end Position) string {
|
||||
// extractCharSelection: Extracts text from a character selection range.
|
||||
func extractCharSelection(m Model, start, end core.Position) string {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
if start.Line == end.Line {
|
||||
@ -398,8 +404,8 @@ func extractCharSelection(m Model, start, end Position) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// deleteCharSelectionForPaste deletes a character selection (similar to operator/delete.go)
|
||||
func deleteCharSelectionForPaste(m Model, start, end Position) {
|
||||
// deleteCharSelectionForPaste: Deletes a character selection for paste operations.
|
||||
func deleteCharSelectionForPaste(m Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -435,6 +441,7 @@ func deleteCharSelectionForPaste(m Model, start, end Position) {
|
||||
// Ensure VisualPaste implements Repeatable
|
||||
var _ Repeatable = VisualPaste{}
|
||||
|
||||
// VisualPaste.WithCount: Returns a new VisualPaste with the given count.
|
||||
func (a VisualPaste) WithCount(n int) Action {
|
||||
return VisualPaste{Count: n}
|
||||
}
|
||||
|
||||
@ -1,75 +0,0 @@
|
||||
package action
|
||||
|
||||
type RegisterType int
|
||||
|
||||
const (
|
||||
CharwiseRegister RegisterType = iota
|
||||
LinewiseRegister
|
||||
BlockwiseRegister
|
||||
)
|
||||
|
||||
type Register struct {
|
||||
Type RegisterType
|
||||
Content []string
|
||||
}
|
||||
|
||||
func DefaultRegisters() map[rune]Register {
|
||||
reg := make(map[rune]Register)
|
||||
|
||||
addSpecialRegisters(reg)
|
||||
addNamedRegisters(reg)
|
||||
addNumberedRegisters(reg)
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
func addNamedRegisters(reg map[rune]Register) {
|
||||
name := 'a'
|
||||
|
||||
for name <= 'z' {
|
||||
reg[name] = emptyRegister()
|
||||
name++
|
||||
}
|
||||
}
|
||||
|
||||
func addNumberedRegisters(reg map[rune]Register) {
|
||||
name := '0'
|
||||
|
||||
for name <= '9' {
|
||||
reg[name] = emptyRegister()
|
||||
name++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addSpecialRegisters(reg map[rune]Register) {
|
||||
// Unnamed (default)
|
||||
reg['"'] = emptyRegister()
|
||||
|
||||
// Black hole (readonly)
|
||||
reg['_'] = emptyRegister()
|
||||
|
||||
// System clipboard
|
||||
reg['*'] = emptyRegister()
|
||||
|
||||
// Small delete? Expression?
|
||||
|
||||
// Last inserted text (readonly)
|
||||
reg['.'] = emptyRegister()
|
||||
|
||||
// Current file name (readonly)
|
||||
reg['%'] = emptyRegister()
|
||||
|
||||
// Last executed command (readonly)
|
||||
reg[':'] = emptyRegister()
|
||||
|
||||
// Alternate (previous) file (readonly)
|
||||
reg['#'] = emptyRegister()
|
||||
}
|
||||
|
||||
func emptyRegister() Register {
|
||||
return Register{
|
||||
Type: CharwiseRegister,
|
||||
Content: []string{},
|
||||
}
|
||||
}
|
||||
@ -6,32 +6,33 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// QuitMsg signals the application should quit
|
||||
// QuitMsg: Message signaling the application should quit.
|
||||
type QuitMsg struct{}
|
||||
|
||||
// ErrorMsg signals an error to display
|
||||
// ErrorMsg: Message signaling an error to display.
|
||||
type ErrorMsg struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// cmdQuit handles :quit / :q
|
||||
// cmdQuit: Handles :quit / :q command.
|
||||
func cmdQuit(m action.Model, args []string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return tea.Quit()
|
||||
}
|
||||
}
|
||||
|
||||
// cmdQuitAll handles :qall / :qa
|
||||
// cmdQuitAll: Handles :qall / :qa command.
|
||||
func cmdQuitAll(m action.Model, args []string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return tea.Quit()
|
||||
}
|
||||
}
|
||||
|
||||
// cmdWrite handles :write / :w
|
||||
// cmdWrite: Handles :write / :w command (TODO: implement file saving).
|
||||
func cmdWrite(m action.Model, args []string) tea.Cmd {
|
||||
// TODO: Implement file saving
|
||||
// If args provided, save to that filename
|
||||
@ -39,13 +40,13 @@ func cmdWrite(m action.Model, args []string) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdWriteAll handles :wall / :wa
|
||||
// cmdWriteAll: Handles :wall / :wa command (TODO: implement saving all buffers).
|
||||
func cmdWriteAll(m action.Model, args []string) tea.Cmd {
|
||||
// TODO: Implement saving all buffers
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdWriteQuit handles :wq
|
||||
// cmdWriteQuit: Handles :wq command (TODO: save then quit).
|
||||
func cmdWriteQuit(m action.Model, args []string) tea.Cmd {
|
||||
// TODO: Save then quit
|
||||
return func() tea.Msg {
|
||||
@ -53,7 +54,7 @@ func cmdWriteQuit(m action.Model, args []string) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
// cmdRegisters handles :register
|
||||
// cmdRegisters: Handles :register command (debug - displays register content).
|
||||
func cmdRegisters(m action.Model, args []string) tea.Cmd {
|
||||
// TODO: This is temporary, for debugging
|
||||
if len(args) < 1 {
|
||||
@ -79,7 +80,7 @@ func cmdRegisters(m action.Model, args []string) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmdSet handles :set option[=value]
|
||||
// cmdSet: Handles :set option[=value] command for configuring editor settings.
|
||||
// Examples:
|
||||
//
|
||||
// :set number - enable number
|
||||
@ -104,15 +105,16 @@ func cmdSet(m action.Model, args []string) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Setting represents a configurable option
|
||||
// Setting: Represents a configurable editor option.
|
||||
type Setting struct {
|
||||
Name string
|
||||
ShortForm string
|
||||
Type SettingType
|
||||
Get func(s action.Settings) any
|
||||
Get func(s core.Settings) any
|
||||
Set func(m action.Model, val any)
|
||||
}
|
||||
|
||||
// SettingType: Enumeration of setting value types.
|
||||
type SettingType int
|
||||
|
||||
const (
|
||||
@ -127,7 +129,7 @@ var settingsMap = []Setting{
|
||||
Name: "number",
|
||||
ShortForm: "nu",
|
||||
Type: BoolSetting,
|
||||
Get: func(s action.Settings) any { return s.Number },
|
||||
Get: func(s core.Settings) any { return s.Number },
|
||||
Set: func(m action.Model, val any) {
|
||||
s := m.Settings()
|
||||
s.Number = val.(bool)
|
||||
@ -138,7 +140,7 @@ var settingsMap = []Setting{
|
||||
Name: "relativenumber",
|
||||
ShortForm: "rnu",
|
||||
Type: BoolSetting,
|
||||
Get: func(s action.Settings) any { return s.RelativeNumber },
|
||||
Get: func(s core.Settings) any { return s.RelativeNumber },
|
||||
Set: func(m action.Model, val any) {
|
||||
s := m.Settings()
|
||||
s.RelativeNumber = val.(bool)
|
||||
@ -149,7 +151,7 @@ var settingsMap = []Setting{
|
||||
Name: "tabstop",
|
||||
ShortForm: "ts",
|
||||
Type: IntSetting,
|
||||
Get: func(s action.Settings) any { return s.TabSize },
|
||||
Get: func(s core.Settings) any { return s.TabSize },
|
||||
Set: func(m action.Model, val any) {
|
||||
s := m.Settings()
|
||||
s.TabSize = val.(int)
|
||||
@ -160,7 +162,7 @@ var settingsMap = []Setting{
|
||||
Name: "scrolloff",
|
||||
ShortForm: "so",
|
||||
Type: IntSetting,
|
||||
Get: func(s action.Settings) any { return s.ScrollOff },
|
||||
Get: func(s core.Settings) any { return s.ScrollOff },
|
||||
Set: func(m action.Model, val any) {
|
||||
s := m.Settings()
|
||||
s.ScrollOff = val.(int)
|
||||
@ -169,6 +171,7 @@ var settingsMap = []Setting{
|
||||
},
|
||||
}
|
||||
|
||||
// lookupSetting: Finds a setting by name, short form, or prefix.
|
||||
func lookupSetting(name string) *Setting {
|
||||
for i := range settingsMap {
|
||||
s := &settingsMap[i]
|
||||
@ -183,6 +186,7 @@ func lookupSetting(name string) *Setting {
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseSetOption: Parses and applies a single :set option.
|
||||
func parseSetOption(m action.Model, opt string) error {
|
||||
// Handle toggle: option!
|
||||
if name, ok := strings.CutSuffix(opt, "!"); ok {
|
||||
|
||||
@ -8,32 +8,31 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Command represents a command that can be executed from command mode
|
||||
// Command: Represents a command that can be executed from command mode.
|
||||
type Command struct {
|
||||
Name string // Full name: "quit"
|
||||
ShortForm string // Minimum abbreviation: "q"
|
||||
Handler func(m action.Model, args []string) tea.Cmd // Handler function
|
||||
}
|
||||
|
||||
// Registry holds all registered commands
|
||||
// Registry: Holds all registered commands.
|
||||
type Registry struct {
|
||||
commands []Command
|
||||
}
|
||||
|
||||
// NewRegistry creates a new command registry with default commands
|
||||
// NewRegistry: Creates a new command registry with default commands.
|
||||
func NewRegistry() *Registry {
|
||||
r := &Registry{}
|
||||
r.registerDefaults()
|
||||
return r
|
||||
}
|
||||
|
||||
// Register adds a command to the registry
|
||||
// Registry.Register: Adds a command to the registry.
|
||||
func (r *Registry) Register(cmd Command) {
|
||||
r.commands = append(r.commands, cmd)
|
||||
}
|
||||
|
||||
// Lookup finds a command by name or abbreviation
|
||||
// Returns the command and any error (unknown or ambiguous)
|
||||
// Registry.Lookup: Finds a command by name or abbreviation with error handling.
|
||||
func (r *Registry) Lookup(input string) (*Command, error) {
|
||||
if input == "" {
|
||||
return nil, fmt.Errorf("no command given")
|
||||
@ -75,7 +74,7 @@ func (r *Registry) Lookup(input string) (*Command, error) {
|
||||
return matches[0], nil
|
||||
}
|
||||
|
||||
// Parse splits a command line into command name and arguments
|
||||
// Parse: Splits a command line into command name and arguments.
|
||||
func Parse(cmdLine string) (name string, args []string) {
|
||||
parts := strings.Fields(cmdLine)
|
||||
if len(parts) == 0 {
|
||||
@ -84,7 +83,7 @@ func Parse(cmdLine string) (name string, args []string) {
|
||||
return parts[0], parts[1:]
|
||||
}
|
||||
|
||||
// Execute parses and executes a command line
|
||||
// Registry.Execute: Parses and executes a command line.
|
||||
func (r *Registry) Execute(m action.Model, cmdLine string) (tea.Cmd, error) {
|
||||
name, args := Parse(cmdLine)
|
||||
|
||||
@ -99,7 +98,7 @@ func (r *Registry) Execute(m action.Model, cmdLine string) (tea.Cmd, error) {
|
||||
// DefaultRegistry is the global command registry
|
||||
var DefaultRegistry = NewRegistry()
|
||||
|
||||
// registerDefaults registers the built-in commands
|
||||
// Registry.registerDefaults: Registers the built-in commands.
|
||||
func (r *Registry) registerDefaults() {
|
||||
// Quit commands
|
||||
r.Register(Command{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package action
|
||||
package core
|
||||
|
||||
type BufferOptions struct {
|
||||
// tabstop expandtab
|
||||
@ -1,4 +1,4 @@
|
||||
package action
|
||||
package core
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentBufferId int = 1
|
||||
@ -1,4 +1,4 @@
|
||||
package action
|
||||
package core
|
||||
|
||||
// Mode constants for editor mode
|
||||
type Mode int
|
||||
@ -12,6 +12,8 @@ const (
|
||||
VisualBlockMode
|
||||
)
|
||||
|
||||
// Mode.ToString: Returns a human-readable string representation of the mode
|
||||
// for display in the status bar.
|
||||
func (m Mode) ToString() string {
|
||||
switch m {
|
||||
case NormalMode:
|
||||
@ -31,6 +33,7 @@ func (m Mode) ToString() string {
|
||||
}
|
||||
}
|
||||
|
||||
// Mode.IsVisualMode: Returns true if the mode is any visual mode variant.
|
||||
func (m Mode) IsVisualMode() bool {
|
||||
return m == VisualMode ||
|
||||
m == VisualLineMode ||
|
||||
6
internal/core/position.go
Normal file
6
internal/core/position.go
Normal file
@ -0,0 +1,6 @@
|
||||
package core
|
||||
|
||||
// Position represents a location in the buffer
|
||||
type Position struct {
|
||||
Line, Col int
|
||||
}
|
||||
93
internal/core/register.go
Normal file
93
internal/core/register.go
Normal file
@ -0,0 +1,93 @@
|
||||
package core
|
||||
|
||||
// RegisterType: Indicates how the register content should be interpreted when
|
||||
// pasting. Charwise treats content as continuous text, linewise operates on
|
||||
// complete lines, and blockwise represents rectangular selections.
|
||||
type RegisterType int
|
||||
|
||||
const (
|
||||
CharwiseRegister RegisterType = iota
|
||||
LinewiseRegister
|
||||
BlockwiseRegister
|
||||
)
|
||||
|
||||
// Register: Stores yanked or deleted text with metadata about how it should be
|
||||
// pasted. The Type determines paste behavior and Content holds the text lines.
|
||||
type Register struct {
|
||||
Type RegisterType
|
||||
Content []string
|
||||
}
|
||||
|
||||
// DefaultRegisters: Creates and initializes the complete set of vim-style
|
||||
// registers. Returns a map containing special registers (", *, _, etc.),
|
||||
// named registers (a-z), and numbered registers (0-9).
|
||||
func DefaultRegisters() map[rune]Register {
|
||||
reg := make(map[rune]Register)
|
||||
|
||||
addSpecialRegisters(reg)
|
||||
addNamedRegisters(reg)
|
||||
addNumberedRegisters(reg)
|
||||
|
||||
return reg
|
||||
}
|
||||
|
||||
// addNamedRegisters: Initializes the 26 named registers (a-z) used for explicit
|
||||
// yank/delete operations. Users can target these with commands like "ayy or "ap.
|
||||
func addNamedRegisters(reg map[rune]Register) {
|
||||
name := 'a'
|
||||
|
||||
for name <= 'z' {
|
||||
reg[name] = emptyRegister()
|
||||
name++
|
||||
}
|
||||
}
|
||||
|
||||
// addNumberedRegisters: Initializes the numbered registers (0-9) which form the
|
||||
// delete history. Register 0 holds the most recent yank, and 1-9 hold previous
|
||||
// deletes in chronological order.
|
||||
func addNumberedRegisters(reg map[rune]Register) {
|
||||
name := '0'
|
||||
|
||||
for name <= '9' {
|
||||
reg[name] = emptyRegister()
|
||||
name++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// addSpecialRegisters: Initializes special-purpose registers with specific
|
||||
// behaviors. Includes the unnamed register ("), black hole (_), clipboard (*),
|
||||
// last insert (.), current filename (%), last command (:), and alternate file (#).
|
||||
func addSpecialRegisters(reg map[rune]Register) {
|
||||
// Unnamed (default)
|
||||
reg['"'] = emptyRegister()
|
||||
|
||||
// Black hole (readonly)
|
||||
reg['_'] = emptyRegister()
|
||||
|
||||
// System clipboard
|
||||
reg['*'] = emptyRegister()
|
||||
|
||||
// Small delete? Expression?
|
||||
|
||||
// Last inserted text (readonly)
|
||||
reg['.'] = emptyRegister()
|
||||
|
||||
// Current file name (readonly)
|
||||
reg['%'] = emptyRegister()
|
||||
|
||||
// Last executed command (readonly)
|
||||
reg[':'] = emptyRegister()
|
||||
|
||||
// Alternate (previous) file (readonly)
|
||||
reg['#'] = emptyRegister()
|
||||
}
|
||||
|
||||
// emptyRegister: Creates a new register initialized with charwise type and empty
|
||||
// content. Used as the default state for all registers during initialization.
|
||||
func emptyRegister() Register {
|
||||
return Register{
|
||||
Type: CharwiseRegister,
|
||||
Content: []string{},
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package action
|
||||
package core
|
||||
|
||||
// Settings: Configuration options for editor display and behavior.
|
||||
type Settings struct {
|
||||
Number bool
|
||||
RelativeNumber bool
|
||||
@ -9,6 +10,8 @@ type Settings struct {
|
||||
// TODO: Colors
|
||||
}
|
||||
|
||||
// NewDefaultSettings: Creates a Settings struct with sensible defaults for
|
||||
// line numbers, gutter width, tab size, and scroll offset.
|
||||
func NewDefaultSettings() Settings {
|
||||
return Settings{
|
||||
Number: true,
|
||||
15
internal/core/types.go
Normal file
15
internal/core/types.go
Normal file
@ -0,0 +1,15 @@
|
||||
package core
|
||||
|
||||
// MotionType indicates how a motion operates
|
||||
type MotionType int
|
||||
|
||||
const (
|
||||
CharwiseExclusive MotionType = iota // w, b, h, l, 0, ^ - end position not included
|
||||
CharwiseInclusive // e, $, f - end position is included
|
||||
Linewise // j, k, G, gg, {, } - operates on whole lines
|
||||
)
|
||||
|
||||
// IsCharwise returns true if the motion type is character-based (not linewise)
|
||||
func (mt MotionType) IsCharwise() bool {
|
||||
return mt == CharwiseExclusive || mt == CharwiseInclusive
|
||||
}
|
||||
@ -1,8 +1,4 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
package core
|
||||
|
||||
// TODO: No more global settings, window-wide settings
|
||||
type WinOptions struct {
|
||||
@ -84,61 +80,6 @@ func (w *Window) AdjustScroll() {
|
||||
w.ScrollY = max(0, min(w.ScrollY, maxScroll))
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// View methods
|
||||
// ==================================================
|
||||
|
||||
// Window.View ...
|
||||
func (w *Window) View(m Model) string {
|
||||
buf := w.Buffer
|
||||
|
||||
viewport := w.Height - 2 // command bar (1) + status line (1)
|
||||
start := w.ScrollY
|
||||
end := w.ScrollY + viewport
|
||||
|
||||
var view strings.Builder
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
// past the file, just draw the '~'
|
||||
if i >= buf.LineCount() {
|
||||
// TODO: Handle gutter and line numbers
|
||||
view.WriteString("~")
|
||||
view.WriteString("\n")
|
||||
continue
|
||||
}
|
||||
|
||||
line := w.drawLine(m, buf.Line(i), i)
|
||||
view.WriteString(line)
|
||||
|
||||
// Break to next line
|
||||
view.WriteString("\n")
|
||||
}
|
||||
|
||||
return view.String()
|
||||
}
|
||||
|
||||
// TODO: Only pass what we need from the model, not the entire model.
|
||||
func (w *Window) drawLine(m Model, line string, lineNum int) string {
|
||||
chars := []rune(line)
|
||||
|
||||
for col := 0; col <= len(chars); col++ {
|
||||
// Currently on the cursor
|
||||
if w.Cursor.Line == lineNum && w.Cursor.Col == col {
|
||||
if col < len(chars) {
|
||||
return m.Styles().CursorStyle(m.Mode()).Render(string(chars[col]))
|
||||
} else {
|
||||
return m.Styles().CursorStyle(m.Mode()).Render(" ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *Window) drawGutter() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Setters
|
||||
// ==================================================
|
||||
@ -1,4 +1,4 @@
|
||||
package action
|
||||
package core
|
||||
|
||||
// Not great, but maybe the best way
|
||||
var CurrentWindowId int = 1000
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// TestHelperExamples demonstrates the different ways to use the test helpers
|
||||
@ -27,7 +27,7 @@ func TestHelperExamples(t *testing.T) {
|
||||
|
||||
t.Run("custom cursor position", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithCursorPos(action.Position{Line: 2, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 3}),
|
||||
)
|
||||
m := getFinalModel(t, tm)
|
||||
win := m.ActiveWindow()
|
||||
@ -50,7 +50,7 @@ func TestHelperExamples(t *testing.T) {
|
||||
t.Run("with register content for paste testing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"foo"}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"foo"}),
|
||||
)
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -58,7 +58,7 @@ func TestHelperExamples(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("expected register to be set")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("expected charwise register, got %v", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 || reg.Content[0] != "foo" {
|
||||
@ -69,9 +69,9 @@ func TestHelperExamples(t *testing.T) {
|
||||
t.Run("combine multiple options", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 5}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 5}),
|
||||
WithTermSize(100, 30),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"deleted line 1", "deleted line 2"}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"deleted line 1", "deleted line 2"}),
|
||||
)
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -90,7 +90,7 @@ func TestHelperExamples(t *testing.T) {
|
||||
}
|
||||
|
||||
reg, ok := m.GetRegister('"')
|
||||
if !ok || reg.Type != action.LinewiseRegister {
|
||||
if !ok || reg.Type != core.LinewiseRegister {
|
||||
t.Error("register not set correctly")
|
||||
}
|
||||
})
|
||||
@ -104,21 +104,21 @@ func TestHelperExamples(t *testing.T) {
|
||||
t.Error("newTestModelWithLines failed")
|
||||
}
|
||||
|
||||
tm2 := newTestModelWithCursorPos(t, action.Position{Line: 1, Col: 2})
|
||||
tm2 := newTestModelWithCursorPos(t, core.Position{Line: 1, Col: 2})
|
||||
m2 := getFinalModel(t, tm2)
|
||||
win2 := m2.ActiveWindow()
|
||||
if win2.Cursor.Line != 1 {
|
||||
t.Error("newTestModelWithCursorPos failed")
|
||||
}
|
||||
|
||||
tm3 := newTestModelWithLinesAndCursorPos(t, []string{"x"}, action.Position{Line: 0, Col: 0})
|
||||
tm3 := newTestModelWithLinesAndCursorPos(t, []string{"x"}, core.Position{Line: 0, Col: 0})
|
||||
m3 := getFinalModel(t, tm3)
|
||||
buf3 := m3.ActiveBuffer()
|
||||
if buf3.LineCount() != 1 {
|
||||
t.Error("newTestModelWithLinesAndCursorPos failed")
|
||||
}
|
||||
|
||||
tm4 := newTestModelWithTermSize(t, []string{"y"}, action.Position{Line: 0, Col: 0}, 50, 20)
|
||||
tm4 := newTestModelWithTermSize(t, []string{"y"}, core.Position{Line: 0, Col: 0}, 50, 20)
|
||||
m4 := getFinalModel(t, tm4)
|
||||
win4 := m4.ActiveWindow()
|
||||
if win4.Width != 50 {
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/x/exp/teatest"
|
||||
)
|
||||
@ -42,11 +42,11 @@ type TestModelOption func(*testModelConfig)
|
||||
|
||||
type testModelConfig struct {
|
||||
lines []string
|
||||
pos action.Position
|
||||
pos core.Position
|
||||
width int
|
||||
height int
|
||||
regName rune
|
||||
regType action.RegisterType
|
||||
regType core.RegisterType
|
||||
regContent []string
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ func WithLines(lines []string) TestModelOption {
|
||||
}
|
||||
|
||||
// WithCursorPos sets the initial cursor position
|
||||
func WithCursorPos(pos action.Position) TestModelOption {
|
||||
func WithCursorPos(pos core.Position) TestModelOption {
|
||||
return func(c *testModelConfig) {
|
||||
c.pos = pos
|
||||
}
|
||||
@ -73,7 +73,7 @@ func WithTermSize(width, height int) TestModelOption {
|
||||
}
|
||||
|
||||
// WithRegister sets a register's content
|
||||
func WithRegister(name rune, regType action.RegisterType, content []string) TestModelOption {
|
||||
func WithRegister(name rune, regType core.RegisterType, content []string) TestModelOption {
|
||||
return func(c *testModelConfig) {
|
||||
c.regName = name
|
||||
c.regType = regType
|
||||
@ -86,7 +86,7 @@ func newTestModel(t *testing.T, opts ...TestModelOption) *teatest.TestModel {
|
||||
// Default configuration
|
||||
cfg := testModelConfig{
|
||||
lines: []string{"line 1", "line 2", "line 3", "line 4", "line 5", "line 6"},
|
||||
pos: action.Position{Col: 0, Line: 0},
|
||||
pos: core.Position{Col: 0, Line: 0},
|
||||
width: 80,
|
||||
height: 24,
|
||||
}
|
||||
@ -96,11 +96,11 @@ func newTestModel(t *testing.T, opts ...TestModelOption) *teatest.TestModel {
|
||||
opt(&cfg)
|
||||
}
|
||||
|
||||
buf := action.NewBufferBuilder().
|
||||
buf := core.NewBufferBuilder().
|
||||
WithLines(cfg.lines).
|
||||
Build()
|
||||
|
||||
win := action.NewWindowBuilder().
|
||||
win := core.NewWindowBuilder().
|
||||
WithBuffer(&buf).
|
||||
WithCursorPos(cfg.pos.Line, cfg.pos.Col).
|
||||
WithDimensions(cfg.width, cfg.height).
|
||||
@ -131,15 +131,15 @@ func newTestModelWithLines(t *testing.T, lines []string) *teatest.TestModel {
|
||||
return newTestModel(t, WithLines(lines))
|
||||
}
|
||||
|
||||
func newTestModelWithCursorPos(t *testing.T, pos action.Position) *teatest.TestModel {
|
||||
func newTestModelWithCursorPos(t *testing.T, pos core.Position) *teatest.TestModel {
|
||||
return newTestModel(t, WithCursorPos(pos))
|
||||
}
|
||||
|
||||
func newTestModelWithLinesAndCursorPos(t *testing.T, lines []string, pos action.Position) *teatest.TestModel {
|
||||
func newTestModelWithLinesAndCursorPos(t *testing.T, lines []string, pos core.Position) *teatest.TestModel {
|
||||
return newTestModel(t, WithLines(lines), WithCursorPos(pos))
|
||||
}
|
||||
|
||||
func newTestModelWithTermSize(t *testing.T, lines []string, pos action.Position, width, height int) *teatest.TestModel {
|
||||
func newTestModelWithTermSize(t *testing.T, lines []string, pos core.Position, width, height int) *teatest.TestModel {
|
||||
return newTestModel(t, WithLines(lines), WithCursorPos(pos), WithTermSize(width, height))
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// NOTE: AI Generated tests
|
||||
@ -153,7 +153,7 @@ func TestCommandModeNavigation(t *testing.T) {
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// After esc we should be back in normal mode
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode after esc", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -164,7 +164,7 @@ func TestCommandModeNavigation(t *testing.T) {
|
||||
sendKeys(tm, ":", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -175,7 +175,7 @@ func TestCommandModeNavigation(t *testing.T) {
|
||||
sendKeys(tm, ":", "s", "e", "t", " ", "n", "u", "enter")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
func TestDeleteChar(t *testing.T) {
|
||||
@ -20,7 +20,7 @@ func TestDeleteChar(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' in middle of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -31,7 +31,7 @@ func TestDeleteChar(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' at end of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -77,7 +77,7 @@ func TestDeleteCharWithCount(t *testing.T) {
|
||||
|
||||
t.Run("test '2x' from middle", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "2", "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -115,7 +115,7 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' at last char deletes it", func(t *testing.T) {
|
||||
lines := []string{"ab"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -126,7 +126,7 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' with whitespace", func(t *testing.T) {
|
||||
lines := []string{"a b c"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -162,7 +162,7 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' on line with tabs", func(t *testing.T) {
|
||||
lines := []string{"a\tb"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -184,7 +184,7 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'x' in middle preserves surrounding chars", func(t *testing.T) {
|
||||
lines := []string{"abcde"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "x")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -197,7 +197,7 @@ func TestDeleteCharEdgeCases(t *testing.T) {
|
||||
func TestDeleteToEndOfLine(t *testing.T) {
|
||||
t.Run("test 'D' deletes to end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -219,7 +219,7 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' at last character", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -230,7 +230,7 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' cursor position after delete", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -253,7 +253,7 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' with count deletes following lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "hi", "mom"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "2", "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -270,7 +270,7 @@ func TestDeleteToEndOfLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' with count deletes following lines with overflow", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "hi", "mom"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "8", "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -297,7 +297,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' at end of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -311,7 +311,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' preserves lines above", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -325,7 +325,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' cursor clamps to valid position", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -351,7 +351,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' from middle of whitespace-only line", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -362,7 +362,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' with tabs", func(t *testing.T) {
|
||||
lines := []string{"hello\tworld"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -373,7 +373,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' on line with only one char remaining after cursor", func(t *testing.T) {
|
||||
lines := []string{"ab"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -384,7 +384,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' does not affect line below", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -395,7 +395,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' with multiple lines", func(t *testing.T) {
|
||||
lines := []string{"first line", "second line", "third line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -409,7 +409,7 @@ func TestDeleteToEndOfLineEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("test 'D' preserves cursor Y position", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 1})
|
||||
sendKeys(tm, "D")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// --- Insert Mode Entry Tests ---
|
||||
@ -14,8 +14,8 @@ func TestEnterInsert(t *testing.T) {
|
||||
sendKeys(tm, "i")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.mode != action.InsertMode {
|
||||
t.Errorf("mode = %d, want InsertMode (%d)", m.mode, action.InsertMode)
|
||||
if m.mode != core.InsertMode {
|
||||
t.Errorf("mode = %d, want InsertMode (%d)", m.mode, core.InsertMode)
|
||||
}
|
||||
})
|
||||
|
||||
@ -32,7 +32,7 @@ func TestEnterInsert(t *testing.T) {
|
||||
|
||||
t.Run("test 'i' insert in middle", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "i", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -43,7 +43,7 @@ func TestEnterInsert(t *testing.T) {
|
||||
|
||||
t.Run("test 'i' cursor moves back on esc", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "i", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -59,8 +59,8 @@ func TestEnterInsertAfter(t *testing.T) {
|
||||
sendKeys(tm, "a")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.mode != action.InsertMode {
|
||||
t.Errorf("mode = %d, want InsertMode (%d)", m.mode, action.InsertMode)
|
||||
if m.mode != core.InsertMode {
|
||||
t.Errorf("mode = %d, want InsertMode (%d)", m.mode, core.InsertMode)
|
||||
}
|
||||
})
|
||||
|
||||
@ -77,7 +77,7 @@ func TestEnterInsertAfter(t *testing.T) {
|
||||
|
||||
t.Run("test 'a' from middle of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "a", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -90,7 +90,7 @@ func TestEnterInsertAfter(t *testing.T) {
|
||||
func TestEnterInsertLineStart(t *testing.T) {
|
||||
t.Run("test 'I' enters insert mode at line start", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "I", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -101,7 +101,7 @@ func TestEnterInsertLineStart(t *testing.T) {
|
||||
|
||||
t.Run("test 'I' from end of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "I", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -125,7 +125,7 @@ func TestEnterInsertLineEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'A' from middle of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "A", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -154,7 +154,7 @@ func TestOpenLineBelow(t *testing.T) {
|
||||
|
||||
t.Run("test 'o' from middle of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "o", "n", "e", "w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -168,7 +168,7 @@ func TestOpenLineBelow(t *testing.T) {
|
||||
|
||||
t.Run("test 'o' at end of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "o", "n", "e", "w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -233,7 +233,7 @@ func TestOpenLineBelowWithCount(t *testing.T) {
|
||||
func TestOpenLineAbove(t *testing.T) {
|
||||
t.Run("test 'O' creates line above", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "O", "n", "e", "w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -261,7 +261,7 @@ func TestOpenLineAbove(t *testing.T) {
|
||||
|
||||
t.Run("test 'O' cursor at start of new line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1})
|
||||
sendKeys(tm, "O", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -274,7 +274,7 @@ func TestOpenLineAbove(t *testing.T) {
|
||||
func TestOpenLineAboveWithCount(t *testing.T) {
|
||||
t.Run("test '3O' creates 3 lines above", func(t *testing.T) {
|
||||
lines := []string{"line 1"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "3", "O", "x", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -294,7 +294,7 @@ func TestOpenLineAboveWithCount(t *testing.T) {
|
||||
func TestInsertModeEnter(t *testing.T) {
|
||||
t.Run("test enter splits line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "i", "enter", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -311,7 +311,7 @@ func TestInsertModeEnter(t *testing.T) {
|
||||
|
||||
t.Run("test enter at end of line", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "i", "enter", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -347,7 +347,7 @@ func TestInsertModeEnter(t *testing.T) {
|
||||
func TestInsertModeBackspace(t *testing.T) {
|
||||
t.Run("test backspace deletes character", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "i", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -358,7 +358,7 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
|
||||
t.Run("test backspace at start of line joins lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "i", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -383,7 +383,7 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
|
||||
t.Run("test multiple backspaces", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -396,7 +396,7 @@ func TestInsertModeBackspace(t *testing.T) {
|
||||
func TestInsertModeDelete(t *testing.T) {
|
||||
t.Run("test delete deletes character", func(t *testing.T) {
|
||||
lines := []string{"world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "i", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -407,7 +407,7 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test delete at end of line joins lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "i", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -435,7 +435,7 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test delete at end of last line does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "i", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -446,7 +446,7 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test multiple delete", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "i", "delete", "delete", "delete", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -460,7 +460,7 @@ func TestInsertModeDelete(t *testing.T) {
|
||||
func TestInsertModeArrowKeys(t *testing.T) {
|
||||
t.Run("test left arrow moves cursor left", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "i", "left", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -471,7 +471,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test right arrow moves cursor right", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "i", "right", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -482,7 +482,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test up arrow moves cursor up", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 1})
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -496,7 +496,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test down arrow moves cursor down", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -521,7 +521,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test right arrow at end of line does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "a", "right", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -532,7 +532,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test up arrow at first line does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -543,7 +543,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test down arrow at last line does nothing", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -554,7 +554,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test up arrow clamps cursor to shorter line", func(t *testing.T) {
|
||||
lines := []string{"hi", "hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 1})
|
||||
sendKeys(tm, "i", "up", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -565,7 +565,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
|
||||
t.Run("test down arrow clamps cursor to shorter line", func(t *testing.T) {
|
||||
lines := []string{"hello", "hi"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "i", "down", "X", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -589,7 +589,7 @@ func TestInsertModeArrowKeys(t *testing.T) {
|
||||
func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
t.Run("test 'ctrl+w' deletes word", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -603,7 +603,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' deletes word with whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -617,7 +617,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' deletes word until period", func(t *testing.T) {
|
||||
lines := []string{"hello wo..."}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -631,7 +631,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' deletes line when blank", func(t *testing.T) {
|
||||
lines := []string{"", ""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -648,7 +648,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' deletes all whitespace when line is only whitespace", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -682,7 +682,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' from middle of word", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "i", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -696,7 +696,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' deletes word after punctuation", func(t *testing.T) {
|
||||
lines := []string{"...hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -710,7 +710,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' with tabs as whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello\tworld"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -724,7 +724,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' at start of line merges with previous line content", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "i", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -744,7 +744,7 @@ func TestInsertModeDeletePreviousWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+w' with underscore in word", func(t *testing.T) {
|
||||
lines := []string{"hello_world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "a", "ctrl+w", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
func TestMoveDown(t *testing.T) {
|
||||
@ -64,7 +64,7 @@ func TestMoveDownWithOverflow(t *testing.T) {
|
||||
lines := []string{"long line", "small"}
|
||||
|
||||
t.Run("test 'j' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 8, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 0})
|
||||
sendKeys(tm, "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -75,7 +75,7 @@ func TestMoveDownWithOverflow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'j' without overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -87,7 +87,7 @@ func TestMoveDownWithOverflow(t *testing.T) {
|
||||
|
||||
func TestMoveUp(t *testing.T) {
|
||||
t.Run("test 'k'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -97,7 +97,7 @@ func TestMoveUp(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'kkkk'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 4})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 4})
|
||||
sendKeys(tm, "k", "k", "k", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -119,7 +119,7 @@ func TestMoveUp(t *testing.T) {
|
||||
|
||||
func TestMoveUpWithCount(t *testing.T) {
|
||||
t.Run("test '3k'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 5})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 5})
|
||||
sendKeys(tm, "3", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -129,7 +129,7 @@ func TestMoveUpWithCount(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test '10k' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 3})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 3})
|
||||
sendKeys(tm, "1", "0", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -143,7 +143,7 @@ func TestMoveUpWithOverflow(t *testing.T) {
|
||||
lines := []string{"small", "long line"}
|
||||
|
||||
t.Run("test 'k' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -154,7 +154,7 @@ func TestMoveUpWithOverflow(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'k' without overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1})
|
||||
sendKeys(tm, "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -224,7 +224,7 @@ func TestMoveRightWithCount(t *testing.T) {
|
||||
|
||||
func TestMoveLeft(t *testing.T) {
|
||||
t.Run("test 'h'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -234,7 +234,7 @@ func TestMoveLeft(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'hhhh'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "h", "h", "h", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -256,7 +256,7 @@ func TestMoveLeft(t *testing.T) {
|
||||
|
||||
func TestMoveLeftWithCount(t *testing.T) {
|
||||
t.Run("test '3h'", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "3", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -266,7 +266,7 @@ func TestMoveLeftWithCount(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test '10h' with overflow", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "1", "0", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// --- G and gg Tests ---
|
||||
@ -20,7 +20,7 @@ func TestMoveToBottom(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'G' from middle", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -30,7 +30,7 @@ func TestMoveToBottom(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'G' already at bottom", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 5})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 5})
|
||||
sendKeys(tm, "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -41,7 +41,7 @@ func TestMoveToBottom(t *testing.T) {
|
||||
|
||||
t.Run("test 'G' clamps CursorX()", func(t *testing.T) {
|
||||
lines := []string{"long line here", "short"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -68,7 +68,7 @@ func TestMoveToBottom(t *testing.T) {
|
||||
|
||||
func TestMoveToTop(t *testing.T) {
|
||||
t.Run("test 'gg' from bottom", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 5})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 5})
|
||||
sendKeys(tm, "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -78,7 +78,7 @@ func TestMoveToTop(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test 'gg' from middle", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 3})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 3})
|
||||
sendKeys(tm, "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -99,7 +99,7 @@ func TestMoveToTop(t *testing.T) {
|
||||
|
||||
t.Run("test 'gg' clamps CursorX()", func(t *testing.T) {
|
||||
lines := []string{"short", "long line here"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
||||
sendKeys(tm, "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -117,7 +117,7 @@ func TestMoveToTop(t *testing.T) {
|
||||
|
||||
func TestMoveToLineStart(t *testing.T) {
|
||||
t.Run("test '0' from middle of line", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -128,7 +128,7 @@ func TestMoveToLineStart(t *testing.T) {
|
||||
|
||||
t.Run("test '0' from end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: len(lines[0]), Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: len(lines[0]), Line: 0})
|
||||
sendKeys(tm, "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -159,7 +159,7 @@ func TestMoveToLineStart(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test '0' preserves line", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 3, Line: 2})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 3, Line: 2})
|
||||
sendKeys(tm, "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -184,7 +184,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
|
||||
t.Run("test '$' from middle of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -196,7 +196,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
|
||||
t.Run("test '$' already at end", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: len(lines[0]), Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: len(lines[0]), Line: 0})
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -218,7 +218,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("test '$' preserves line", func(t *testing.T) {
|
||||
tm := newTestModelWithCursorPos(t, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithCursorPos(t, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -231,7 +231,7 @@ func TestMoveToLineEnd(t *testing.T) {
|
||||
func TestMoveToLineContentStart(t *testing.T) {
|
||||
t.Run("test '_' from middle of line with no leading whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -242,7 +242,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' from middle of line with leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -253,7 +253,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' from start of line with leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -264,7 +264,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' from start of line with no leading whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -275,7 +275,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' from middle of line with only whitespace", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -286,7 +286,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' from end of line with only whitespace", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -297,7 +297,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
|
||||
t.Run("test '_' on empty line", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -310,7 +310,7 @@ func TestMoveToLineContentStart(t *testing.T) {
|
||||
func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
t.Run("test '^' from middle of line with no leading whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -321,7 +321,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' from middle of line with leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -332,7 +332,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' from start of line with leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -343,7 +343,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' from start of line with no leading whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -354,7 +354,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' from middle of line with only whitespace", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -365,7 +365,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' from end of line with only whitespace", func(t *testing.T) {
|
||||
lines := []string{" "}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -376,7 +376,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
|
||||
t.Run("test '^' on empty line", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -395,7 +395,7 @@ func TestMoveToLineContentStartAlias(t *testing.T) {
|
||||
func TestMoveToColumn(t *testing.T) {
|
||||
t.Run("test '|' alone goes to column 1 (index 0)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -407,7 +407,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
|
||||
t.Run("test '1|' goes to column 1 (index 0)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "1", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -418,7 +418,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
|
||||
t.Run("test '5|' goes to column 5 (index 4)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -430,7 +430,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
|
||||
t.Run("test '10|' goes to column 10 (index 9)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "1", "0", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -442,7 +442,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
|
||||
t.Run("test '|' already at column 1", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -453,7 +453,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
|
||||
t.Run("test '5|' already at column 5", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -466,7 +466,7 @@ func TestMoveToColumn(t *testing.T) {
|
||||
func TestMoveToColumnClamp(t *testing.T) {
|
||||
t.Run("test '20|' clamps to end of short line", func(t *testing.T) {
|
||||
lines := []string{"hello"} // 5 chars, max index 4
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "2", "0", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -478,7 +478,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
|
||||
t.Run("test '100|' clamps to end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"} // 11 chars, max index 10
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "1", "0", "0", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -490,7 +490,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
|
||||
t.Run("test '6|' clamps on 5-char line", func(t *testing.T) {
|
||||
lines := []string{"hello"} // 5 chars
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "6", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -502,7 +502,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
|
||||
t.Run("test '|' on empty line stays at 0", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -513,7 +513,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
|
||||
t.Run("test '5|' on empty line stays at 0", func(t *testing.T) {
|
||||
lines := []string{""}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -524,7 +524,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
|
||||
t.Run("test '3|' on 2-char line clamps", func(t *testing.T) {
|
||||
lines := []string{"ab"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "3", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -538,7 +538,7 @@ func TestMoveToColumnClamp(t *testing.T) {
|
||||
func TestMoveToColumnPreservesLine(t *testing.T) {
|
||||
t.Run("test '|' preserves Y position", func(t *testing.T) {
|
||||
lines := []string{"line one", "line two", "line three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 1})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -549,7 +549,7 @@ func TestMoveToColumnPreservesLine(t *testing.T) {
|
||||
|
||||
t.Run("test '5|' preserves Y position", func(t *testing.T) {
|
||||
lines := []string{"line one", "line two", "line three"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -560,7 +560,7 @@ func TestMoveToColumnPreservesLine(t *testing.T) {
|
||||
|
||||
t.Run("test '|' on different lines", func(t *testing.T) {
|
||||
lines := []string{"short", "longer line here"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -576,7 +576,7 @@ func TestMoveToColumnPreservesLine(t *testing.T) {
|
||||
func TestMoveToColumnWithWhitespace(t *testing.T) {
|
||||
t.Run("test '5|' with leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -588,7 +588,7 @@ func TestMoveToColumnWithWhitespace(t *testing.T) {
|
||||
|
||||
t.Run("test '3|' lands on whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "3", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -600,7 +600,7 @@ func TestMoveToColumnWithWhitespace(t *testing.T) {
|
||||
|
||||
t.Run("test '|' with tabs", func(t *testing.T) {
|
||||
lines := []string{"\thello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -612,7 +612,7 @@ func TestMoveToColumnWithWhitespace(t *testing.T) {
|
||||
|
||||
t.Run("test '2|' with tabs", func(t *testing.T) {
|
||||
lines := []string{"\thello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "2", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -626,7 +626,7 @@ func TestMoveToColumnWithWhitespace(t *testing.T) {
|
||||
func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
t.Run("test 'd|' deletes to column 1", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "d", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -640,7 +640,7 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
|
||||
t.Run("test 'd5|' deletes to column 5", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "d", "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -653,7 +653,7 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
|
||||
t.Run("test 'y5|' yanks to column 5", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "y", "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -669,7 +669,7 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
|
||||
t.Run("test 'y|' yanks to column 1 (nothing if at start)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "y", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -687,7 +687,7 @@ func TestMoveToColumnWithOperator(t *testing.T) {
|
||||
func TestMoveToColumnInVisualMode(t *testing.T) {
|
||||
t.Run("test 'v5|' selects to column 5", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "v", "5", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -701,7 +701,7 @@ func TestMoveToColumnInVisualMode(t *testing.T) {
|
||||
|
||||
t.Run("test 'v|' selects backward to column 1", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "v", "|")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -715,7 +715,7 @@ func TestMoveToColumnInVisualMode(t *testing.T) {
|
||||
|
||||
t.Run("test 'v5|d' deletes selection to column 5", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 0})
|
||||
sendKeys(tm, "v", "5", "|", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// --- w, e, and b Tests ---
|
||||
@ -47,7 +47,7 @@ func TestMoveForwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'w' at end of file preserves position", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -94,7 +94,7 @@ func TestMoveForwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'w' from middle of word skips only remaining chars", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -129,7 +129,7 @@ func TestMoveToWordEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'e' moves to next line when at end of word", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -143,7 +143,7 @@ func TestMoveToWordEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'e' at end of file preserves position", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -201,7 +201,7 @@ func TestMoveToWordEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'e' from middle of word moves to end of current word", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -225,7 +225,7 @@ func TestMoveToWordEnd(t *testing.T) {
|
||||
func TestMoveBackwardWord(t *testing.T) {
|
||||
t.Run("test 'b' moves to start of current word", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -236,7 +236,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' at word start moves to end previous", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -247,7 +247,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'bbb' moves to back three word", func(t *testing.T) {
|
||||
lines := []string{"hello world hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 23, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 23, Line: 0})
|
||||
sendKeys(tm, "b", "b", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -258,7 +258,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' moves to prev line when at start of line", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -283,7 +283,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' stops at punctuation", func(t *testing.T) {
|
||||
lines := []string{"hello.world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -294,7 +294,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' stops at end of punctuation sequence", func(t *testing.T) {
|
||||
lines := []string{"..hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -305,7 +305,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' stops at end of punctuation sequence before word", func(t *testing.T) {
|
||||
lines := []string{"hello..world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -316,7 +316,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' stops at end of punctuation sequence on newline", func(t *testing.T) {
|
||||
lines := []string{"hello.", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -330,7 +330,7 @@ func TestMoveBackwardWord(t *testing.T) {
|
||||
|
||||
t.Run("test 'b' skips underscore", func(t *testing.T) {
|
||||
lines := []string{"hello_world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -394,7 +394,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from middle of WORD", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -406,7 +406,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from last char of WORD", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -418,7 +418,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from space before WORD", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -529,7 +529,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from punctuation to next WORD", func(t *testing.T) {
|
||||
lines := []string{"hello. world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -599,7 +599,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from middle line to next", func(t *testing.T) {
|
||||
lines := []string{"first", "second third", "fourth"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 1})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -616,7 +616,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' at end of file stays put", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -628,7 +628,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' on last WORD of file", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -702,7 +702,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' from leading whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -885,7 +885,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' with URL-like text", func(t *testing.T) {
|
||||
lines := []string{"visit https://example.com/path?q=1 today"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -897,7 +897,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' with email-like text", func(t *testing.T) {
|
||||
lines := []string{"contact user@example.com now"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 8, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -909,7 +909,7 @@ func TestMoveForwardWORD(t *testing.T) {
|
||||
|
||||
t.Run("test 'W' with file path", func(t *testing.T) {
|
||||
lines := []string{"edit /home/user/file.txt now"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1009,7 +1009,7 @@ func TestMoveForwardWORDInVisualMode(t *testing.T) {
|
||||
sendKeys(tm, "v", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualMode {
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
if m.ActiveWindow().Anchor.Col != 0 {
|
||||
@ -1054,7 +1054,7 @@ func TestMoveForwardWORDInVisualMode(t *testing.T) {
|
||||
sendKeys(tm, "V", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualLineMode {
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
// W should still move cursor, but line mode selects whole lines
|
||||
@ -1114,7 +1114,7 @@ func TestMoveForwardWORDEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'E' from middle of WORD", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1126,7 +1126,7 @@ func TestMoveForwardWORDEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'E' from last char of WORD moves to next WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1138,7 +1138,7 @@ func TestMoveForwardWORDEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'E' from space moves to next WORD end", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1261,7 +1261,7 @@ func TestMoveForwardWORDEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'E' from punctuation within WORD", func(t *testing.T) {
|
||||
lines := []string{"hello.world next"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1277,7 +1277,7 @@ func TestMoveForwardWORDEnd(t *testing.T) {
|
||||
|
||||
t.Run("test 'E' moves to next line when at end", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1637,7 +1637,7 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
|
||||
sendKeys(tm, "v", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualMode {
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
// Cursor at end of "hello.world" (index 10)
|
||||
@ -1676,7 +1676,7 @@ func TestMoveForwardWORDEndInVisualMode(t *testing.T) {
|
||||
sendKeys(tm, "V", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualLineMode {
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
// Cursor at end of "hello.world" (index 10)
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
@ -14,12 +14,12 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc changes first line and enters insert mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 2 {
|
||||
@ -37,12 +37,12 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc changes middle line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "line one" {
|
||||
@ -59,12 +59,12 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc changes last line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hello" {
|
||||
@ -78,7 +78,7 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc puts deleted line in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world", "second line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
@ -90,7 +90,7 @@ func TestChangeLine(t *testing.T) {
|
||||
if len(reg.Content) != 1 || reg.Content[0] != "hello world" {
|
||||
t.Errorf("register content = %q, want 'hello world'", reg.Content)
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
})
|
||||
@ -98,12 +98,12 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc on single line file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"only line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
@ -117,7 +117,7 @@ func TestChangeLine(t *testing.T) {
|
||||
t.Run("cc cursor at column 0", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 3}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
@ -136,12 +136,12 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
t.Run("2cc changes two lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three", "line four"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "2", "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have 3 lines: empty + line three + line four
|
||||
@ -159,12 +159,12 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
t.Run("3cc changes three lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one", "two", "three", "four", "five"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "3", "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have 3 lines: one + empty + five
|
||||
@ -185,12 +185,12 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
t.Run("5cc with only 3 lines remaining changes all remaining", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one", "two", "three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "5", "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 1 {
|
||||
@ -204,7 +204,7 @@ func TestChangeLineWithCount(t *testing.T) {
|
||||
t.Run("2cc puts both lines in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"first", "second", "third"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "2", "c", "c")
|
||||
|
||||
@ -227,12 +227,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("cl changes single character", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "ello world" {
|
||||
@ -243,12 +243,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("c2l changes two characters", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "2", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "llo world" {
|
||||
@ -259,12 +259,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("ch changes character to the left", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
)
|
||||
sendKeys(tm, "c", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hell world" {
|
||||
@ -275,12 +275,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("c$ changes to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "c", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
@ -291,12 +291,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("c0 changes to start of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "c", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
@ -307,12 +307,12 @@ func TestChangeWithHorizontalMotion(t *testing.T) {
|
||||
t.Run("c^ changes to first non-blank", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 8}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 8}),
|
||||
)
|
||||
sendKeys(tm, "c", "^")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// ^ is exclusive motion, so position 8 (space) is not included
|
||||
@ -331,12 +331,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("cw changes word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
@ -347,12 +347,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("cw from middle of word changes to next word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}),
|
||||
)
|
||||
sendKeys(tm, "c", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "heworld" {
|
||||
@ -363,12 +363,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("ce changes to end of word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
@ -379,12 +379,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("cb changes backward word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "c", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "world" {
|
||||
@ -395,12 +395,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("c2w changes two words", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one two three four"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "2", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "three four" {
|
||||
@ -411,12 +411,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("cW changes WORD", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello.world next"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "W")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "next" {
|
||||
@ -427,12 +427,12 @@ func TestChangeWithWordMotion(t *testing.T) {
|
||||
t.Run("cE changes to end of WORD", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello.world next"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "E")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != " next" {
|
||||
@ -449,12 +449,12 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
t.Run("cj changes current and next line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have empty line + line three
|
||||
@ -472,12 +472,12 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
t.Run("ck changes current and previous line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have empty line + line three
|
||||
@ -495,12 +495,12 @@ func TestChangeWithVerticalMotion(t *testing.T) {
|
||||
t.Run("c2j changes three lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one", "two", "three", "four", "five"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "2", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have empty + four + five
|
||||
@ -524,12 +524,12 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
t.Run("cG changes from cursor to end of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// All lines should be replaced with one empty line
|
||||
@ -544,12 +544,12 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
t.Run("cgg changes from cursor to start of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// All lines should be replaced with one empty line
|
||||
@ -564,12 +564,12 @@ func TestChangeWithJumpMotion(t *testing.T) {
|
||||
t.Run("cG from middle changes to end", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three", "line four"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have line one + empty
|
||||
@ -593,12 +593,12 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
t.Run("C changes from cursor to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "C")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
@ -609,12 +609,12 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
t.Run("C at start of line clears entire line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "C")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
@ -625,12 +625,12 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
t.Run("C at end of line enters insert mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}),
|
||||
)
|
||||
sendKeys(tm, "C")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should delete last char
|
||||
@ -642,7 +642,7 @@ func TestChangeToEndOfLine(t *testing.T) {
|
||||
t.Run("C puts deleted text in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "C")
|
||||
|
||||
@ -665,12 +665,12 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
t.Run("s deletes character and enters insert mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "s")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "ello" {
|
||||
@ -681,12 +681,12 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
t.Run("2s deletes two characters", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "2", "s")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "llo" {
|
||||
@ -697,12 +697,12 @@ func TestSubstituteCharacter(t *testing.T) {
|
||||
t.Run("s at end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}),
|
||||
)
|
||||
sendKeys(tm, "s")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hell" {
|
||||
@ -719,12 +719,12 @@ func TestSubstituteLine(t *testing.T) {
|
||||
t.Run("S clears line and enters insert mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
)
|
||||
sendKeys(tm, "S")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
@ -735,7 +735,7 @@ func TestSubstituteLine(t *testing.T) {
|
||||
t.Run("S preserves other lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "S")
|
||||
|
||||
@ -754,12 +754,12 @@ func TestSubstituteLine(t *testing.T) {
|
||||
t.Run("2S substitutes two lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one", "two", "three", "four"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "2", "S")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have empty + three + four
|
||||
@ -783,12 +783,12 @@ func TestVisualModeChange(t *testing.T) {
|
||||
t.Run("vc changes single character", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "ello world" {
|
||||
@ -799,12 +799,12 @@ func TestVisualModeChange(t *testing.T) {
|
||||
t.Run("v with motion then c changes selection", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "e", "c") // select "hello"
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != " world" {
|
||||
@ -815,12 +815,12 @@ func TestVisualModeChange(t *testing.T) {
|
||||
t.Run("v$c changes to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "v", "$", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "hello " {
|
||||
@ -831,12 +831,12 @@ func TestVisualModeChange(t *testing.T) {
|
||||
t.Run("visual selection spanning lines then c", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
)
|
||||
sendKeys(tm, "v", "j", "l", "l", "l", "l", "c") // select across lines
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should merge lines with selection removed
|
||||
@ -848,7 +848,7 @@ func TestVisualModeChange(t *testing.T) {
|
||||
t.Run("visual change puts deleted text in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "c") // select "hello"
|
||||
|
||||
@ -867,12 +867,12 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
t.Run("Vc changes line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().LineCount() != 3 {
|
||||
@ -886,12 +886,12 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
t.Run("Vjc changes multiple lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three", "line four"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "j", "c") // select lines two and three
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// Should have: line one, empty, line four
|
||||
@ -912,7 +912,7 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
t.Run("visual line change puts lines in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "j", "c") // select first two lines
|
||||
|
||||
@ -921,7 +921,7 @@ func TestVisualLineModeChange(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 2 {
|
||||
@ -938,12 +938,12 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Run("cc on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "c")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
@ -954,12 +954,12 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Run("cw on last word of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "c", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
// cw on last word should change to end of line
|
||||
@ -971,12 +971,12 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Run("c$ on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
if m.ActiveBuffer().Lines[0] != "" {
|
||||
@ -987,13 +987,13 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Run("cj at last line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// cj at last line should just change current line (can't go down)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -1001,13 +1001,13 @@ func TestChangeEdgeCases(t *testing.T) {
|
||||
t.Run("ck at first line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "c", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
// ck at first line should just change current line (can't go up)
|
||||
if m.Mode() != action.InsertMode {
|
||||
if m.Mode() != core.InsertMode {
|
||||
t.Errorf("Mode() = %v, want InsertMode", m.Mode())
|
||||
}
|
||||
})
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// NOTE: AI Generated tests
|
||||
@ -31,7 +31,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'dd' deletes middle line", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "testing"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -54,7 +54,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'dd' deletes last line", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -74,7 +74,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'dd' deletes line and preserves column", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -94,7 +94,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test '3dd' deletes three lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "testing", "line", "another line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "3", "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -158,7 +158,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test 'dd' clamps cursor when next line is shorter", func(t *testing.T) {
|
||||
lines := []string{"hello world", "hi"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 7, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 7, Line: 0})
|
||||
sendKeys(tm, "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -183,7 +183,7 @@ func TestDeleteLine(t *testing.T) {
|
||||
|
||||
t.Run("test '3dd' starting near end of file", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "testing"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "3", "d", "d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -217,7 +217,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dl' deletes current character from middle", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "d", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -231,7 +231,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dl' deletes current character from end", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "d", "l")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -259,7 +259,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dh' deletes character to the left from middle", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "d", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -274,7 +274,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dh' deletes character to the left from end", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "d", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -311,7 +311,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test '2dh' deletes two characters backwards", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "2", "d", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -324,7 +324,7 @@ func TestDeleteOperatorWithHorozontalMotion(t *testing.T) {
|
||||
func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
t.Run("test 'dj' deletes current and next line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -363,7 +363,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
// linewise motions consistently - they operate on at least the current line.
|
||||
t.Run("test 'dj' from last line deletes current line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -380,7 +380,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd2j' deletes current and next two lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "2", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -397,7 +397,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test '2dj' deletes current and next two lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "2", "d", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -414,7 +414,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dk' deletes current and previous line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -453,7 +453,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dk' from second line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -470,7 +470,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd2k' deletes current and previous two lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 3})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 3})
|
||||
sendKeys(tm, "d", "2", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -487,7 +487,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test '2dk' deletes current and previous two lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 3})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 3})
|
||||
sendKeys(tm, "2", "d", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -504,7 +504,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dj' with count exceeding remaining lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "5", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -519,7 +519,7 @@ func TestDeleteOperatorWithVerticalMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dk' with count exceeding previous lines", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "d", "5", "k")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -551,7 +551,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dw' deletes from middle of word to start of next word", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -565,7 +565,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dw' at last word deletes to end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "w")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -626,7 +626,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'de' deletes to end of word from middle (inclusive)", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "d", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -638,7 +638,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'de' at end of word jumps to next word end", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 4, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 4, Line: 0})
|
||||
sendKeys(tm, "d", "e")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -674,7 +674,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'db' deletes back to start of current word", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 8, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 8, Line: 0})
|
||||
sendKeys(tm, "d", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -686,7 +686,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'db' at start of word deletes previous word", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -698,7 +698,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test '2db' deletes two words backward", func(t *testing.T) {
|
||||
lines := []string{"one two three four"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 14, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 14, Line: 0})
|
||||
sendKeys(tm, "2", "d", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -710,7 +710,7 @@ func TestDeleteOperatorWithWordMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'db' at end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "d", "b")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -736,7 +736,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd0' deletes from cursor to start of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -750,7 +750,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd0' from end of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -761,7 +761,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd0' from middle of line", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "d", "0")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -784,7 +784,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd$' deletes to end of line from middle", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -795,7 +795,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd$' at end of line deletes last char", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 0})
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -806,7 +806,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd$' does not affect other lines", func(t *testing.T) {
|
||||
lines := []string{"hello world", "second line"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "$")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -853,7 +853,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd_' from middle whitespace deletes to first non-blank", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 1, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 1, Line: 0})
|
||||
sendKeys(tm, "d", "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -868,7 +868,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd_' from after first char deletes back to first non-whitespace", func(t *testing.T) {
|
||||
lines := []string{" hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 6, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 6, Line: 0})
|
||||
sendKeys(tm, "d", "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -881,7 +881,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'd_' on line with no leading whitespace", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 5, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 5, Line: 0})
|
||||
sendKeys(tm, "d", "_")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -896,7 +896,7 @@ func TestDeleteOperatorWithLinePositionMotion(t *testing.T) {
|
||||
func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
t.Run("test 'dG' deletes from cursor to end of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -927,7 +927,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dG' from last line deletes only last line", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -944,7 +944,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dG' positions cursor correctly", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 1})
|
||||
sendKeys(tm, "d", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -955,7 +955,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dgg' deletes from cursor to start of file", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4", "line 5"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -972,7 +972,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dgg' from last line deletes everything", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "d", "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1003,7 +1003,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dgg' positions cursor at start", func(t *testing.T) {
|
||||
lines := []string{"line 1", "line 2", "line 3", "line 4"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 2})
|
||||
sendKeys(tm, "d", "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -1042,7 +1042,7 @@ func TestDeleteOperatorWithJumpMotion(t *testing.T) {
|
||||
|
||||
t.Run("test 'dG' clamps cursor when file shrinks", func(t *testing.T) {
|
||||
lines := []string{"short", "this is a longer line", "line 3", "line 4"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 10, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 10, Line: 1})
|
||||
sendKeys(tm, "d", "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
|
||||
@ -3,15 +3,15 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
func TestPasteLinewiseBasic(t *testing.T) {
|
||||
t.Run("p pastes single line after cursor line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -33,8 +33,8 @@ func TestPasteLinewiseBasic(t *testing.T) {
|
||||
t.Run("p moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -47,8 +47,8 @@ func TestPasteLinewiseBasic(t *testing.T) {
|
||||
t.Run("p from middle of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -67,8 +67,8 @@ func TestPasteLinewiseBasic(t *testing.T) {
|
||||
t.Run("p at end of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -86,8 +86,8 @@ func TestPasteLinewiseMultipleLines(t *testing.T) {
|
||||
t.Run("p pastes multiple lines in correct order", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second", "third"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second", "third"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -115,8 +115,8 @@ func TestPasteLinewiseMultipleLines(t *testing.T) {
|
||||
t.Run("p with multiple lines moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -131,8 +131,8 @@ func TestPasteLinewiseWithCount(t *testing.T) {
|
||||
t.Run("2p pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
@ -155,8 +155,8 @@ func TestPasteLinewiseWithCount(t *testing.T) {
|
||||
t.Run("3p pastes content three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"pasted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"pasted"}),
|
||||
)
|
||||
sendKeys(tm, "3", "p")
|
||||
|
||||
@ -174,8 +174,8 @@ func TestPasteLinewiseWithCount(t *testing.T) {
|
||||
t.Run("2p with multiple lines pastes all lines twice in order", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
@ -204,8 +204,8 @@ func TestPasteLinewiseWithCount(t *testing.T) {
|
||||
t.Run("count paste moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "3", "p")
|
||||
|
||||
@ -222,8 +222,8 @@ func TestPasteBeforeLinewiseBasic(t *testing.T) {
|
||||
t.Run("P pastes single line before cursor line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -245,8 +245,8 @@ func TestPasteBeforeLinewiseBasic(t *testing.T) {
|
||||
t.Run("P moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 5}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 5}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -259,8 +259,8 @@ func TestPasteBeforeLinewiseBasic(t *testing.T) {
|
||||
t.Run("P at first line pastes at very top", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -282,8 +282,8 @@ func TestPasteBeforeLinewiseBasic(t *testing.T) {
|
||||
t.Run("P from middle of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -304,8 +304,8 @@ func TestPasteBeforeLinewiseMultipleLines(t *testing.T) {
|
||||
t.Run("P pastes multiple lines in correct order", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second", "third"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second", "third"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -333,8 +333,8 @@ func TestPasteBeforeLinewiseMultipleLines(t *testing.T) {
|
||||
t.Run("P with multiple lines moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -349,8 +349,8 @@ func TestPasteBeforeLinewiseWithCount(t *testing.T) {
|
||||
t.Run("2P pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "2", "P")
|
||||
|
||||
@ -373,8 +373,8 @@ func TestPasteBeforeLinewiseWithCount(t *testing.T) {
|
||||
t.Run("3P pastes content three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"pasted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"pasted"}),
|
||||
)
|
||||
sendKeys(tm, "3", "P")
|
||||
|
||||
@ -395,8 +395,8 @@ func TestPasteBeforeLinewiseWithCount(t *testing.T) {
|
||||
t.Run("2P with multiple lines pastes all lines twice in order", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"first", "second"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"first", "second"}),
|
||||
)
|
||||
sendKeys(tm, "2", "P")
|
||||
|
||||
@ -425,8 +425,8 @@ func TestPasteBeforeLinewiseWithCount(t *testing.T) {
|
||||
t.Run("count paste before moves cursor to first pasted line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "3", "P")
|
||||
|
||||
@ -441,8 +441,8 @@ func TestPasteBeforeLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("P on single line buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"only line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -461,8 +461,8 @@ func TestPasteBeforeLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("P with empty register content does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -475,8 +475,8 @@ func TestPasteBeforeLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("P preserves indentation in pasted lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{" indented", "\ttabbed"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{" indented", "\ttabbed"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -492,8 +492,8 @@ func TestPasteBeforeLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("P with large count", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"x"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"x"}),
|
||||
)
|
||||
sendKeys(tm, "1", "0", "P") // 10P
|
||||
|
||||
@ -512,8 +512,8 @@ func TestPasteLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("p on single line buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"only line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"inserted"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"inserted"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -532,8 +532,8 @@ func TestPasteLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with empty register content does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -546,8 +546,8 @@ func TestPasteLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("p preserves indentation in pasted lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{" indented", "\ttabbed"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{" indented", "\ttabbed"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -563,8 +563,8 @@ func TestPasteLinewiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with large count", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"x"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"x"}),
|
||||
)
|
||||
sendKeys(tm, "1", "0", "p") // 10p
|
||||
|
||||
@ -583,8 +583,8 @@ func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p pastes text after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -598,8 +598,8 @@ func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p at start of line pastes after first char", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -612,8 +612,8 @@ func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p at end of line pastes at end", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"!"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"!"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -626,8 +626,8 @@ func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"text"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"text"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -640,8 +640,8 @@ func TestPasteCharwiseBasic(t *testing.T) {
|
||||
t.Run("p does not add new lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -656,8 +656,8 @@ func TestPasteCharwiseWithCount(t *testing.T) {
|
||||
t.Run("2p pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
@ -670,8 +670,8 @@ func TestPasteCharwiseWithCount(t *testing.T) {
|
||||
t.Run("3p pastes word three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"start end"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 't'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"-"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 't'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"-"}),
|
||||
)
|
||||
sendKeys(tm, "3", "p")
|
||||
|
||||
@ -684,8 +684,8 @@ func TestPasteCharwiseWithCount(t *testing.T) {
|
||||
t.Run("count paste with multi-char content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XY"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"XY"}),
|
||||
)
|
||||
sendKeys(tm, "2", "p")
|
||||
|
||||
@ -700,8 +700,8 @@ func TestPasteCharwiseCursorPosition(t *testing.T) {
|
||||
t.Run("p moves cursor to end of pasted text", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -715,8 +715,8 @@ func TestPasteCharwiseCursorPosition(t *testing.T) {
|
||||
t.Run("p cursor stays on same line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -735,8 +735,8 @@ func TestPasteBeforeCharwiseBasic(t *testing.T) {
|
||||
t.Run("P pastes text before cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}), // on space
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}), // on space
|
||||
WithRegister('"', core.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -750,8 +750,8 @@ func TestPasteBeforeCharwiseBasic(t *testing.T) {
|
||||
t.Run("P at start of line pastes at beginning", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -764,8 +764,8 @@ func TestPasteBeforeCharwiseBasic(t *testing.T) {
|
||||
t.Run("P at end of line pastes before last char", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -778,8 +778,8 @@ func TestPasteBeforeCharwiseBasic(t *testing.T) {
|
||||
t.Run("P on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{""}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"text"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"text"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -794,8 +794,8 @@ func TestPasteBeforeCharwiseWithCount(t *testing.T) {
|
||||
t.Run("2P pastes content twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}), // on first 'l'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}), // on first 'l'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "2", "P")
|
||||
|
||||
@ -808,8 +808,8 @@ func TestPasteBeforeCharwiseWithCount(t *testing.T) {
|
||||
t.Run("3P pastes word three times", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 1}), // on 'b'
|
||||
WithRegister('"', action.CharwiseRegister, []string{"-"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 1}), // on 'b'
|
||||
WithRegister('"', core.CharwiseRegister, []string{"-"}),
|
||||
)
|
||||
sendKeys(tm, "3", "P")
|
||||
|
||||
@ -828,8 +828,8 @@ func TestPasteCharwiseMultiLine(t *testing.T) {
|
||||
t.Run("p with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"line1", "line2"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"line1", "line2"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -843,8 +843,8 @@ func TestPasteCharwiseMultiLine(t *testing.T) {
|
||||
t.Run("P with multi-line charwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"line1", "line2"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"line1", "line2"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -864,8 +864,8 @@ func TestPasteBlockwiseBasic(t *testing.T) {
|
||||
t.Run("p with blockwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"aaaa", "bbbb", "cccc"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -879,8 +879,8 @@ func TestPasteBlockwiseBasic(t *testing.T) {
|
||||
t.Run("P with blockwise content errors gracefully", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"aaaa", "bbbb", "cccc"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.BlockwiseRegister, []string{"XX", "YY", "ZZ"}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -900,8 +900,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with empty charwise register does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -914,8 +914,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with empty string in register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{""}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{""}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -928,8 +928,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p preserves special characters", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"\t"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"\t"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -942,8 +942,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p with spaces", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{" "}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{" "}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -956,8 +956,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("p on line with only whitespace", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" "}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "p")
|
||||
|
||||
@ -970,8 +970,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("P with empty charwise register does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{}),
|
||||
)
|
||||
sendKeys(tm, "P")
|
||||
|
||||
@ -984,8 +984,8 @@ func TestPasteCharwiseEdgeCases(t *testing.T) {
|
||||
t.Run("large count paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"ab"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "1", "0", "p") // 10p
|
||||
|
||||
@ -1004,7 +1004,7 @@ func TestYankThenPasteCharwise(t *testing.T) {
|
||||
t.Run("yw then p pastes yanked word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "w") // yank "hello "
|
||||
sendKeys(tm, "$") // go to end
|
||||
@ -1019,7 +1019,7 @@ func TestYankThenPasteCharwise(t *testing.T) {
|
||||
t.Run("ye then p pastes yanked word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "e") // yank "hello"
|
||||
sendKeys(tm, "$") // go to end
|
||||
@ -1034,7 +1034,7 @@ func TestYankThenPasteCharwise(t *testing.T) {
|
||||
t.Run("visual select then y then p", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "y") // select and yank "hel"
|
||||
sendKeys(tm, "$") // go to end
|
||||
@ -1061,8 +1061,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp replaces selection with charwise register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "p") // select "hello", paste
|
||||
|
||||
@ -1075,8 +1075,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp replaces single character", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "v", "p") // select "h", paste
|
||||
|
||||
@ -1089,8 +1089,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp replaces word in middle of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world goodbye"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"EARTH"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"EARTH"}),
|
||||
)
|
||||
sendKeys(tm, "v", "e", "p") // select "world", paste
|
||||
|
||||
@ -1103,8 +1103,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp replaces to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"universe"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"universe"}),
|
||||
)
|
||||
sendKeys(tm, "v", "$", "p") // select "world", paste
|
||||
|
||||
@ -1117,8 +1117,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp with empty register deletes selection", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{""}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{""}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "p") // select "hello", paste empty
|
||||
|
||||
@ -1135,8 +1135,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp replaces selection spanning multiple lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
// v at (0,5), j goes to (1,5), h goes to (1,4) = space after "line"
|
||||
// Selection: "one\nline " (from 'o' to space)
|
||||
@ -1159,8 +1159,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp cursor at start of pasted content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"EARTH"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"EARTH"}),
|
||||
)
|
||||
sendKeys(tm, "v", "e", "p") // select "world", paste "EARTH"
|
||||
|
||||
@ -1175,13 +1175,13 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp exits visual mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -1193,8 +1193,8 @@ func TestVisualModePasteCharwise(t *testing.T) {
|
||||
t.Run("vp with linewise register replaces selection", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"NEW LINE"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"NEW LINE"}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "p") // select "hello", paste linewise content
|
||||
|
||||
@ -1214,8 +1214,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp replaces line with charwise register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p") // select line two, paste
|
||||
|
||||
@ -1231,8 +1231,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp replaces line with linewise register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"NEW LINE"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"NEW LINE"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p") // select line two, paste
|
||||
|
||||
@ -1248,8 +1248,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp replaces multiple lines with linewise register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three", "line four"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "V", "j", "p") // select lines two and three, paste
|
||||
|
||||
@ -1271,8 +1271,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp replaces multiple lines with multiple linewise register lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two", "line three", "line four"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"NEW A", "NEW B"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"NEW A", "NEW B"}),
|
||||
)
|
||||
sendKeys(tm, "V", "j", "p") // select lines two and three, paste two lines
|
||||
|
||||
@ -1297,8 +1297,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp on first line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"FIRST"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"FIRST"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p")
|
||||
|
||||
@ -1317,8 +1317,8 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp on last line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"LAST"}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"LAST"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p")
|
||||
|
||||
@ -1337,13 +1337,13 @@ func TestVisualLinePaste(t *testing.T) {
|
||||
t.Run("Vp exits visual line mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -1357,8 +1357,8 @@ func TestVisualPasteRegisterBehavior(t *testing.T) {
|
||||
t.Run("vp puts deleted text into register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"NEW"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"NEW"}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "p") // select "hello", paste "NEW"
|
||||
|
||||
@ -1376,8 +1376,8 @@ func TestVisualPasteRegisterBehavior(t *testing.T) {
|
||||
t.Run("Vp puts deleted line into register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line one", "line two"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p")
|
||||
|
||||
@ -1389,7 +1389,7 @@ func TestVisualPasteRegisterBehavior(t *testing.T) {
|
||||
if len(reg.Content) != 1 || reg.Content[0] != "line one" {
|
||||
t.Errorf("register content = %q, want 'line one'", reg.Content)
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
})
|
||||
@ -1403,8 +1403,8 @@ func TestVisualPasteEdgeCases(t *testing.T) {
|
||||
t.Run("vp on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"", "line two"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"NEW"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"NEW"}),
|
||||
)
|
||||
sendKeys(tm, "v", "p")
|
||||
|
||||
@ -1417,8 +1417,8 @@ func TestVisualPasteEdgeCases(t *testing.T) {
|
||||
t.Run("vp selecting entire line content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello", "world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "v", "$", "p") // select entire "hello"
|
||||
|
||||
@ -1431,8 +1431,8 @@ func TestVisualPasteEdgeCases(t *testing.T) {
|
||||
t.Run("vp with backwards selection", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"X"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 5}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"X"}),
|
||||
)
|
||||
sendKeys(tm, "v", "h", "h", "h", "h", "h", "p") // select backwards "hello ", paste
|
||||
|
||||
@ -1445,8 +1445,8 @@ func TestVisualPasteEdgeCases(t *testing.T) {
|
||||
t.Run("vp in single character file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"a"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.CharwiseRegister, []string{"XYZ"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.CharwiseRegister, []string{"XYZ"}),
|
||||
)
|
||||
sendKeys(tm, "v", "p")
|
||||
|
||||
@ -1459,8 +1459,8 @@ func TestVisualPasteEdgeCases(t *testing.T) {
|
||||
t.Run("Vp on single line file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"only line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', action.LinewiseRegister, []string{"REPLACED"}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
WithRegister('"', core.LinewiseRegister, []string{"REPLACED"}),
|
||||
)
|
||||
sendKeys(tm, "V", "p")
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// NOTE: AI Generated tests
|
||||
@ -22,7 +22,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
t.Run("small file does not scroll", func(t *testing.T) {
|
||||
// 10 lines, viewport 24 -> no scrolling needed
|
||||
lines := generateLines(10)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 24)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 24)
|
||||
sendKeys(tm, "G") // go to bottom
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -37,7 +37,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
// Safe zone: lines 8 to 10 (19-1-8=10)
|
||||
// Moving to line 11+ should trigger scroll
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20)
|
||||
|
||||
// Move down 15 times to get to line 15
|
||||
for range 15 {
|
||||
@ -59,7 +59,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
t.Run("scrolls up when cursor moves past top margin", func(t *testing.T) {
|
||||
// Start at line 20, move up
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 20}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 20}, 80, 20)
|
||||
|
||||
// First, let the model adjust (it will scroll to show cursor)
|
||||
// Then move up 15 times
|
||||
@ -80,7 +80,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
|
||||
t.Run("G jumps to bottom and scrolls", func(t *testing.T) {
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20)
|
||||
sendKeys(tm, "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -96,7 +96,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
|
||||
t.Run("gg jumps to top and scrolls", func(t *testing.T) {
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 50}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 20)
|
||||
sendKeys(tm, "g", "g")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -112,7 +112,7 @@ func TestScrollBasic(t *testing.T) {
|
||||
func TestScrollEdgeCases(t *testing.T) {
|
||||
t.Run("scrollY never goes negative", func(t *testing.T) {
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20)
|
||||
|
||||
// Try to move up from top
|
||||
for range 5 {
|
||||
@ -127,7 +127,7 @@ func TestScrollEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("scrollY clamped to max scroll", func(t *testing.T) {
|
||||
lines := generateLines(30)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 20)
|
||||
sendKeys(tm, "G")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -140,7 +140,7 @@ func TestScrollEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("cursor stays visible after delete at bottom", func(t *testing.T) {
|
||||
lines := generateLines(30)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 29}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 29}, 80, 20)
|
||||
|
||||
// Delete some lines at bottom
|
||||
sendKeys(tm, "d", "d", "d", "d")
|
||||
@ -162,7 +162,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
// cursor at line 15 (relY=15, in safe zone), scrollY starts at 0
|
||||
// After ctrl+d: newScrollY=14, newCursorY=14+15=29
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 15}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -177,7 +177,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
t.Run("ctrl+d preserves cursor relative position in viewport", func(t *testing.T) {
|
||||
// relY=15 before and after
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 15}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -191,7 +191,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
// cursor at line 0 (relY=0 < scrollOff=8), clamp to scrollOff
|
||||
// After ctrl+d: newScrollY=14, newCursorY=14+8=22
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -208,7 +208,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
// AdjustScroll puts cursor 35 at scrollY=12 (clamped), relY=23
|
||||
// After ctrl+d: newScrollY clamped to 12, relY=23>19 clamped to 19, newCursorY=31
|
||||
lines := generateLines(40)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 35}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 35}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -225,7 +225,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
// 20 lines < viewport 28: maxScroll=0, scrollY stays 0
|
||||
// relY=0 < scrollOff, clamp to 8; newCursorY=0+8=8
|
||||
lines := generateLines(20)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 0}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 0}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -249,7 +249,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
for i := 15; i < 100; i++ {
|
||||
lines[i] = "hi"
|
||||
}
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 10, Line: 5}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 10, Line: 5}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -266,7 +266,7 @@ func TestHalfPageScrollDown(t *testing.T) {
|
||||
// ctrl+d #1: scrollY=14, cursorY=29, relY=15
|
||||
// ctrl+d #2: scrollY=28, cursorY=43, relY=15
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 15}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d", "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -284,7 +284,7 @@ func TestHalfPageScrollUp(t *testing.T) {
|
||||
// cursor at line 50: AdjustScroll -> scrollY=31, relY=19
|
||||
// After ctrl+u: newScrollY=17, newCursorY=17+19=36
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 50}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -299,7 +299,7 @@ func TestHalfPageScrollUp(t *testing.T) {
|
||||
t.Run("ctrl+u preserves cursor relative position in viewport", func(t *testing.T) {
|
||||
// relY=19 before and after
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 50}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -313,7 +313,7 @@ func TestHalfPageScrollUp(t *testing.T) {
|
||||
// cursor at line 10, scrollY=0, relY=10 (in safe zone)
|
||||
// ctrl+u: newScrollY=max(0,-14)=0, relY=10 preserved, cursorY=10
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 10}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 10}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -332,7 +332,7 @@ func TestHalfPageScrollUp(t *testing.T) {
|
||||
// cursor at line 5, scrollY=0, relY=5 < scrollOff=8
|
||||
// ctrl+u: newScrollY=0; relY clamp to 8; newCursorY=8
|
||||
lines := generateLines(100)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 5}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 5}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -349,7 +349,7 @@ func TestHalfPageScrollUp(t *testing.T) {
|
||||
// ctrl+u #1: newScrollY=47, cursorY=66
|
||||
// ctrl+u #2: newScrollY=33, cursorY=52
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 80}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 80}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u", "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -368,7 +368,7 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
||||
// ctrl+d: scrollY=14, cursorY=29, relY=15
|
||||
// ctrl+u: newScrollY=max(0,14-14)=0, cursorY=0+15=15
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 15}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d", "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -385,7 +385,7 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
||||
// ctrl+u: scrollY=17, cursorY=36, relY=19
|
||||
// ctrl+d: scrollY=31, cursorY=50, relY=19
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 50}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 50}, 80, 30)
|
||||
sendKeys(tm, "ctrl+u", "ctrl+d")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -399,7 +399,7 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
||||
|
||||
t.Run("alternating ctrl+d and ctrl+u maintains scroll stability", func(t *testing.T) {
|
||||
lines := generateLines(200)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 15}, 80, 30)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 15}, 80, 30)
|
||||
sendKeys(tm, "ctrl+d", "ctrl+u", "ctrl+d", "ctrl+u")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -415,7 +415,7 @@ func TestHalfPageScrollRoundTrip(t *testing.T) {
|
||||
func TestScrollWithCount(t *testing.T) {
|
||||
t.Run("5j scrolls appropriately", func(t *testing.T) {
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 5}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 5}, 80, 20)
|
||||
|
||||
sendKeys(tm, "1", "0", "j") // move down 10 lines
|
||||
|
||||
@ -431,7 +431,7 @@ func TestScrollWithCount(t *testing.T) {
|
||||
|
||||
t.Run("5k scrolls appropriately", func(t *testing.T) {
|
||||
lines := generateLines(50)
|
||||
tm := newTestModelWithTermSize(t, lines, action.Position{Col: 0, Line: 25}, 80, 20)
|
||||
tm := newTestModelWithTermSize(t, lines, core.Position{Col: 0, Line: 25}, 80, 20)
|
||||
|
||||
sendKeys(tm, "1", "5", "k") // move up 15 lines
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package editor
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// NOTE: Lots of AI tests here
|
||||
@ -13,11 +13,11 @@ import (
|
||||
func TestVisualModeSelectionState(t *testing.T) {
|
||||
t.Run("test 'v' enters visual mode and sets anchor at cursor", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "v")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualMode {
|
||||
if m.Mode() != core.VisualMode {
|
||||
t.Errorf("Mode() = %v, want VisualMode", m.Mode())
|
||||
}
|
||||
if m.ActiveWindow().Anchor.Col != 3 {
|
||||
@ -44,7 +44,7 @@ func TestVisualModeSelectionState(t *testing.T) {
|
||||
|
||||
t.Run("test 'vh' creates backward selection", func(t *testing.T) {
|
||||
lines := []string{"hello world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "v", "h")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -58,7 +58,7 @@ func TestVisualModeSelectionState(t *testing.T) {
|
||||
|
||||
t.Run("test 'vj' extends selection down", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "v", "j")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
@ -75,11 +75,11 @@ func TestVisualModeSelectionState(t *testing.T) {
|
||||
|
||||
t.Run("test 'V' enters visual line mode and sets anchor at cursor", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 1})
|
||||
sendKeys(tm, "V")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualLineMode {
|
||||
if m.Mode() != core.VisualLineMode {
|
||||
t.Errorf("Mode() = %v, want VisualLineMode", m.Mode())
|
||||
}
|
||||
if m.ActiveWindow().Anchor.Line != 1 {
|
||||
@ -89,11 +89,11 @@ func TestVisualModeSelectionState(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+v' enters visual block mode and sets anchor at cursor", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 1})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 1})
|
||||
sendKeys(tm, "ctrl+v")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.VisualBlockMode {
|
||||
if m.Mode() != core.VisualBlockMode {
|
||||
t.Errorf("Mode() = %v, want VisualBlockMode", m.Mode())
|
||||
}
|
||||
if m.ActiveWindow().Anchor.Col != 2 {
|
||||
@ -110,7 +110,7 @@ func TestVisualModeSelectionState(t *testing.T) {
|
||||
sendKeys(tm, "v", "l", "l", "esc")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -149,7 +149,7 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test 'v' backward selection 'hh d' deletes correct range", func(t *testing.T) {
|
||||
lines := []string{"hello"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "v", "h", "h", "d")
|
||||
|
||||
// anchor=3, cursor=1 → normalized start=1, end=3 → delete "ell" → "ho"
|
||||
@ -164,7 +164,7 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test 'vj d' deletes char selection across two lines", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 2, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 2, Line: 0})
|
||||
sendKeys(tm, "v", "j", "d")
|
||||
|
||||
// start=(2,0), end=(2,1) → prefix="he", suffix="ld" → "held"
|
||||
@ -219,7 +219,7 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test 'Vkd' deletes two lines with backward selection", func(t *testing.T) {
|
||||
lines := []string{"hello", "world", "testing"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 0, Line: 2})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 0, Line: 2})
|
||||
sendKeys(tm, "V", "k", "d")
|
||||
|
||||
// anchor=line2, cursor=line1 → normalized start=line1, end=line2 → delete both
|
||||
@ -257,7 +257,7 @@ func TestVisualModeDelete(t *testing.T) {
|
||||
|
||||
t.Run("test 'ctrl+v' backward col selection deletes correct block", func(t *testing.T) {
|
||||
lines := []string{"hello", "world"}
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, action.Position{Col: 3, Line: 0})
|
||||
tm := newTestModelWithLinesAndCursorPos(t, lines, core.Position{Col: 3, Line: 0})
|
||||
sendKeys(tm, "ctrl+v", "h", "h", "j", "d")
|
||||
|
||||
// anchor=(3,0), cursor=(1,1) → cols min(3,1)=1 to max(3,1)=3, lines 0-1
|
||||
@ -279,7 +279,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'vw' selects to next word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "w")
|
||||
|
||||
@ -296,7 +296,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'vwd' deletes word plus space", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "w", "d")
|
||||
|
||||
@ -310,7 +310,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 've' selects to end of word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "e")
|
||||
|
||||
@ -327,7 +327,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'ved' deletes word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "e", "d")
|
||||
|
||||
@ -341,7 +341,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'vb' selects backward word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "v", "b")
|
||||
|
||||
@ -358,7 +358,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'vbd' deletes backward to word start", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "v", "b", "d")
|
||||
|
||||
@ -372,7 +372,7 @@ func TestVisualModeWordMotions(t *testing.T) {
|
||||
t.Run("test 'v2w' selects two words", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one two three"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "2", "w")
|
||||
|
||||
@ -390,7 +390,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'v$' selects to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "$")
|
||||
|
||||
@ -407,7 +407,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'v$d' deletes to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "v", "$", "d")
|
||||
|
||||
@ -420,7 +420,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'v0' selects to beginning of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}),
|
||||
)
|
||||
sendKeys(tm, "v", "0")
|
||||
|
||||
@ -436,7 +436,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'v0d' deletes to beginning of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "v", "0", "d")
|
||||
|
||||
@ -450,7 +450,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'v_' selects to first non-whitespace", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 10}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 10}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "v", "_")
|
||||
|
||||
@ -467,7 +467,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'vG' selects to bottom of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "G")
|
||||
|
||||
@ -483,7 +483,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'vGd' deletes to bottom of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 3}), // on 'e' of "line"
|
||||
WithCursorPos(core.Position{Line: 0, Col: 3}), // on 'e' of "line"
|
||||
)
|
||||
sendKeys(tm, "v", "G", "d")
|
||||
|
||||
@ -501,7 +501,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'vgg' selects to top of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "g", "g")
|
||||
|
||||
@ -517,7 +517,7 @@ func TestVisualModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'vggd' deletes to top of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 3}),
|
||||
)
|
||||
sendKeys(tm, "v", "g", "g", "d")
|
||||
|
||||
@ -539,7 +539,7 @@ func TestVisualLineModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'VG' selects all lines to bottom", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "G")
|
||||
|
||||
@ -555,7 +555,7 @@ func TestVisualLineModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'VGd' deletes all lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "G", "d")
|
||||
|
||||
@ -572,7 +572,7 @@ func TestVisualLineModeJumpMotions(t *testing.T) {
|
||||
t.Run("test 'Vgg' selects lines to top", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "g", "g")
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
@ -15,7 +15,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
t.Run("yy yanks current line to register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -24,7 +24,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 {
|
||||
@ -38,7 +38,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
t.Run("yy does not modify buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -60,7 +60,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
t.Run("yy does not move cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 3}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -76,7 +76,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
t.Run("yy from middle of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"first", "second", "third", "fourth"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -90,7 +90,7 @@ func TestYankLineBasic(t *testing.T) {
|
||||
t.Run("yy at last line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "last line"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -106,7 +106,7 @@ func TestYankLineWithCount(t *testing.T) {
|
||||
t.Run("2yy yanks two lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3", "line 4"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "2", "y", "y")
|
||||
|
||||
@ -126,7 +126,7 @@ func TestYankLineWithCount(t *testing.T) {
|
||||
t.Run("3yy yanks three lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"a", "b", "c", "d", "e"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "3", "y", "y")
|
||||
|
||||
@ -149,7 +149,7 @@ func TestYankLineWithCount(t *testing.T) {
|
||||
t.Run("yy with count overflow clamps to available lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "1", "0", "y", "y") // 10yy but only 2 lines available
|
||||
|
||||
@ -169,7 +169,7 @@ func TestYankLineWithCount(t *testing.T) {
|
||||
t.Run("yy with count does not modify buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "3", "y", "y")
|
||||
|
||||
@ -184,7 +184,7 @@ func TestYankLineEdgeCases(t *testing.T) {
|
||||
t.Run("yy on empty line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -201,7 +201,7 @@ func TestYankLineEdgeCases(t *testing.T) {
|
||||
t.Run("yy on single line buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"only line"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -215,7 +215,7 @@ func TestYankLineEdgeCases(t *testing.T) {
|
||||
t.Run("yy preserves whitespace", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" indented", "\ttabbed", " spaces "}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "3", "y", "y")
|
||||
|
||||
@ -241,7 +241,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("yj yanks current line and line below", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "j")
|
||||
|
||||
@ -250,7 +250,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 2 {
|
||||
@ -267,7 +267,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("yk yanks current line and line above", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "k")
|
||||
|
||||
@ -276,7 +276,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 2 {
|
||||
@ -293,7 +293,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("yG yanks from cursor to end of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3", "line 4"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "G")
|
||||
|
||||
@ -302,7 +302,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 3 {
|
||||
@ -319,7 +319,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("ygg yanks from cursor to beginning of file", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3", "line 4"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "g", "g")
|
||||
|
||||
@ -328,7 +328,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 3 {
|
||||
@ -345,7 +345,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("y2j yanks current and next two lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"a", "b", "c", "d", "e"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "2", "j")
|
||||
|
||||
@ -371,7 +371,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("yj does not move cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 3}),
|
||||
)
|
||||
sendKeys(tm, "y", "j")
|
||||
|
||||
@ -384,7 +384,7 @@ func TestYankWithLinewiseMotions(t *testing.T) {
|
||||
t.Run("yG does not modify buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "G")
|
||||
|
||||
@ -403,7 +403,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("yw yanks word under cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "w")
|
||||
|
||||
@ -412,7 +412,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// yw includes trailing space
|
||||
@ -427,7 +427,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("ye yanks to end of word (exclusive)", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "e")
|
||||
|
||||
@ -436,7 +436,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// ye is inclusive of last char
|
||||
@ -451,7 +451,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("yb yanks backward word", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "y", "b")
|
||||
|
||||
@ -460,7 +460,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// yb from 'w' back to start of 'hello'
|
||||
@ -475,7 +475,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("y$ yanks to end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "y", "$")
|
||||
|
||||
@ -484,7 +484,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 {
|
||||
@ -498,7 +498,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("y0 yanks to beginning of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "y", "0")
|
||||
|
||||
@ -507,7 +507,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 {
|
||||
@ -521,7 +521,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("y_ yanks to first non-whitespace", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{" hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 10}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 10}), // on 'w'
|
||||
)
|
||||
sendKeys(tm, "y", "_")
|
||||
|
||||
@ -530,7 +530,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// From 'w' back to 'h' (first non-ws)
|
||||
@ -545,7 +545,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("y2w yanks two words", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"one two three four"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "2", "w")
|
||||
|
||||
@ -565,7 +565,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("yw does not move cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "w")
|
||||
|
||||
@ -578,7 +578,7 @@ func TestYankWithCharwiseMotions(t *testing.T) {
|
||||
t.Run("yw does not modify buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "w")
|
||||
|
||||
@ -597,7 +597,7 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
t.Run("v selection then y yanks selected text", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y") // select "hello"
|
||||
|
||||
@ -606,7 +606,7 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 {
|
||||
@ -620,7 +620,7 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
t.Run("v selection across lines yanks with newlines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 3}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 3}),
|
||||
)
|
||||
sendKeys(tm, "v", "j", "l", "l", "y") // select "e 1\nlin"
|
||||
|
||||
@ -629,7 +629,7 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.CharwiseRegister {
|
||||
if reg.Type != core.CharwiseRegister {
|
||||
t.Errorf("register type = %v, want CharwiseRegister", reg.Type)
|
||||
}
|
||||
// Multi-line charwise yank
|
||||
@ -641,12 +641,12 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
t.Run("visual yank exits visual mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "y")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -654,7 +654,7 @@ func TestYankVisualCharwise(t *testing.T) {
|
||||
t.Run("visual yank does not modify buffer", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y")
|
||||
|
||||
@ -669,7 +669,7 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
t.Run("V selection then y yanks entire lines", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "j", "y") // select lines 1 and 2
|
||||
|
||||
@ -678,7 +678,7 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 2 {
|
||||
@ -695,7 +695,7 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
t.Run("V on single line then y yanks that line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "y")
|
||||
|
||||
@ -704,7 +704,7 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.LinewiseRegister {
|
||||
if reg.Type != core.LinewiseRegister {
|
||||
t.Errorf("register type = %v, want LinewiseRegister", reg.Type)
|
||||
}
|
||||
if len(reg.Content) != 1 {
|
||||
@ -718,7 +718,7 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
t.Run("V selection upward yanks in correct order", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "k", "y") // select from line 3 upward to line 2
|
||||
|
||||
@ -742,12 +742,12 @@ func TestYankVisualLinewise(t *testing.T) {
|
||||
t.Run("visual line yank exits visual mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "V", "y")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -757,7 +757,7 @@ func TestYankVisualBlock(t *testing.T) {
|
||||
t.Run("ctrl+v selection then y yanks block", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"abcdef", "ghijkl", "mnopqr"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 1}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 1}),
|
||||
)
|
||||
sendKeys(tm, "ctrl+v", "j", "j", "l", "l", "y") // select 3x3 block starting at col 1
|
||||
|
||||
@ -766,7 +766,7 @@ func TestYankVisualBlock(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatal("unnamed register not found")
|
||||
}
|
||||
if reg.Type != action.BlockwiseRegister {
|
||||
if reg.Type != core.BlockwiseRegister {
|
||||
t.Errorf("register type = %v, want BlockwiseRegister", reg.Type)
|
||||
}
|
||||
// Block should contain "bcd", "hij", "nop"
|
||||
@ -787,12 +787,12 @@ func TestYankVisualBlock(t *testing.T) {
|
||||
t.Run("visual block yank exits visual mode", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"abcd", "efgh"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "ctrl+v", "j", "l", "y")
|
||||
|
||||
m := getFinalModel(t, tm)
|
||||
if m.Mode() != action.NormalMode {
|
||||
if m.Mode() != core.NormalMode {
|
||||
t.Errorf("Mode() = %v, want NormalMode", m.Mode())
|
||||
}
|
||||
})
|
||||
@ -800,7 +800,7 @@ func TestYankVisualBlock(t *testing.T) {
|
||||
t.Run("visual block yank with uneven line lengths", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"abcdefgh", "ij", "klmnop"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "ctrl+v", "j", "j", "l", "l", "l", "y") // 4-wide block
|
||||
|
||||
@ -824,7 +824,7 @@ func TestYankRegisterBehavior(t *testing.T) {
|
||||
t.Run("yy updates register 0 and unnamed register", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -858,7 +858,7 @@ func TestYankRegisterBehavior(t *testing.T) {
|
||||
t.Run("multiple yanks shift numbered registers", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"first", "second", "third"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y") // yank "first"
|
||||
sendKeys(tm, "j")
|
||||
@ -894,7 +894,7 @@ func TestYankRegisterBehavior(t *testing.T) {
|
||||
t.Run("yank then paste uses correct content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original", "to copy"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y") // yank "to copy"
|
||||
sendKeys(tm, "k") // move up
|
||||
@ -918,7 +918,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
t.Run("yy on whitespace-only line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", " ", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -938,7 +938,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
t.Run("yw at end of line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 4}), // on 'o'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 4}), // on 'o'
|
||||
)
|
||||
sendKeys(tm, "y", "w")
|
||||
|
||||
@ -959,7 +959,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
t.Run("y$ at beginning of line yanks entire line content", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "$")
|
||||
|
||||
@ -979,7 +979,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
t.Run("y0 at beginning of line yanks nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "0")
|
||||
|
||||
@ -1001,7 +1001,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
longLine := strings.Repeat("a", 1000)
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{longLine}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -1021,7 +1021,7 @@ func TestYankEdgeCases(t *testing.T) {
|
||||
t.Run("yy with special characters", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello\tworld", "foo\nbar"}), // tab and embedded newline
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y")
|
||||
|
||||
@ -1047,7 +1047,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual charwise yank then paste single line", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// Select "hello", yank it, move to end, paste
|
||||
sendKeys(tm, "v", "l", "l", "l", "l", "y", "$", "p")
|
||||
@ -1061,7 +1061,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual charwise yank then paste before", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 6}), // on 'w'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 6}), // on 'w'
|
||||
)
|
||||
// Select "world", yank it, go to start, paste before
|
||||
sendKeys(tm, "v", "$", "y", "0", "P")
|
||||
@ -1075,7 +1075,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual line yank then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// V yank line 1, go to line 2, paste
|
||||
sendKeys(tm, "V", "y", "j", "p")
|
||||
@ -1092,7 +1092,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual line yank multiple lines then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3", "line 4"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// V select lines 1-2, yank, go to end, paste
|
||||
sendKeys(tm, "V", "j", "y", "G", "p")
|
||||
@ -1112,7 +1112,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual line yank then paste before", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 2, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 2, Col: 0}),
|
||||
)
|
||||
// V yank line 3, go to line 1, paste before
|
||||
sendKeys(tm, "V", "y", "g", "g", "P")
|
||||
@ -1132,7 +1132,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("yy then p duplicates line below", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original", "other"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y", "p")
|
||||
|
||||
@ -1154,7 +1154,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("yy then P duplicates line above", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"original", "other"}),
|
||||
WithCursorPos(action.Position{Line: 1, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 1, Col: 0}),
|
||||
)
|
||||
sendKeys(tm, "y", "y", "P")
|
||||
|
||||
@ -1176,7 +1176,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("yw then p pastes word after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// yw yanks "hello ", move to end of world, paste
|
||||
sendKeys(tm, "y", "w", "$", "p")
|
||||
@ -1190,7 +1190,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("ye then p pastes word after cursor", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello world"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// ye yanks "hello" (inclusive), move to end of line, paste
|
||||
sendKeys(tm, "y", "e", "$", "p")
|
||||
@ -1204,7 +1204,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual select partial word yank then paste", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"abcdefgh"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}), // on 'c'
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}), // on 'c'
|
||||
)
|
||||
// Select "cde", yank, go to end, paste
|
||||
sendKeys(tm, "v", "l", "l", "y", "$", "p")
|
||||
@ -1218,7 +1218,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("visual yank empty selection does nothing", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"hello"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 2}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 2}),
|
||||
)
|
||||
// Enter visual mode then immediately yank (single char)
|
||||
sendKeys(tm, "v", "y")
|
||||
@ -1237,7 +1237,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("dd then p moves deleted line down", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// dd deletes line 1, p pastes it below cursor (now on line 2)
|
||||
sendKeys(tm, "d", "d", "p")
|
||||
@ -1260,7 +1260,7 @@ func TestVisualYankPasteRoundTrip(t *testing.T) {
|
||||
t.Run("2yy then 2p pastes twice", func(t *testing.T) {
|
||||
tm := newTestModel(t,
|
||||
WithLines([]string{"line 1", "line 2", "line 3"}),
|
||||
WithCursorPos(action.Position{Line: 0, Col: 0}),
|
||||
WithCursorPos(core.Position{Line: 0, Col: 0}),
|
||||
)
|
||||
// 2yy yanks lines 1-2, 2p pastes them twice after current line
|
||||
sendKeys(tm, "2", "y", "y", "2", "p")
|
||||
|
||||
@ -5,21 +5,23 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/input"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
// Buffers
|
||||
buffers []*action.Buffer
|
||||
buffers []*core.Buffer
|
||||
//next buffer id?
|
||||
|
||||
// Windows
|
||||
windows []*action.Window
|
||||
windows []*core.Window
|
||||
activeWindowId int
|
||||
|
||||
// Editor wide state
|
||||
mode action.Mode
|
||||
mode core.Mode
|
||||
|
||||
// Terminal dimensions
|
||||
termWidth int
|
||||
@ -40,13 +42,13 @@ type Model struct {
|
||||
commandOutput string
|
||||
|
||||
// Global settings (TODO: This needs to be refactored)
|
||||
settings action.Settings
|
||||
settings core.Settings
|
||||
|
||||
// Registers
|
||||
registers map[rune]action.Register // name -> register
|
||||
registers map[rune]core.Register // name -> register
|
||||
|
||||
// Visual styles
|
||||
styles Styles
|
||||
styles style.Styles
|
||||
}
|
||||
|
||||
// Model.Init: Initialize the model and start any commands that may need to run. Required
|
||||
@ -60,11 +62,11 @@ func (m Model) Init() tea.Cmd {
|
||||
// ==================================================
|
||||
// Core Data Access
|
||||
// ==================================================
|
||||
func (m *Model) Windows() []*action.Window {
|
||||
func (m *Model) Windows() []*core.Window {
|
||||
return m.windows
|
||||
}
|
||||
|
||||
func (m *Model) ActiveWindow() *action.Window {
|
||||
func (m *Model) ActiveWindow() *core.Window {
|
||||
winId := m.activeWindowId
|
||||
for i := range m.Windows() {
|
||||
if m.windows[i].Id == winId {
|
||||
@ -74,11 +76,11 @@ func (m *Model) ActiveWindow() *action.Window {
|
||||
panic("Could not find window")
|
||||
}
|
||||
|
||||
func (m *Model) Buffers() []*action.Buffer {
|
||||
func (m *Model) Buffers() []*core.Buffer {
|
||||
return m.buffers
|
||||
}
|
||||
|
||||
func (m *Model) ActiveBuffer() *action.Buffer {
|
||||
func (m *Model) ActiveBuffer() *core.Buffer {
|
||||
win := m.ActiveWindow()
|
||||
return win.Buffer
|
||||
}
|
||||
@ -108,7 +110,7 @@ func (m *Model) ExitInsertMode() {
|
||||
if win.Cursor.Col > 0 {
|
||||
win.Cursor.Col--
|
||||
}
|
||||
m.mode = action.NormalMode
|
||||
m.mode = core.NormalMode
|
||||
m.insertCount = 0
|
||||
m.insertKeys = nil
|
||||
}
|
||||
@ -272,57 +274,57 @@ func (m *Model) SetCommandOutput(out string) {
|
||||
// ==================================================
|
||||
// Editor-wide State
|
||||
// ==================================================
|
||||
func (m *Model) Mode() action.Mode {
|
||||
func (m *Model) Mode() core.Mode {
|
||||
return m.mode
|
||||
}
|
||||
|
||||
func (m *Model) SetMode(mode action.Mode) {
|
||||
func (m *Model) SetMode(mode core.Mode) {
|
||||
m.mode = mode
|
||||
}
|
||||
|
||||
func (m *Model) Settings() action.Settings {
|
||||
func (m *Model) Settings() core.Settings {
|
||||
return m.settings
|
||||
}
|
||||
|
||||
func (m *Model) SetSettings(s action.Settings) {
|
||||
func (m *Model) SetSettings(s core.Settings) {
|
||||
m.settings = s
|
||||
}
|
||||
|
||||
// Model.Styles: Returns the visual styles used for rendering.
|
||||
func (m *Model) Styles() Styles {
|
||||
func (m *Model) Styles() style.Styles {
|
||||
return m.styles
|
||||
}
|
||||
|
||||
// Model.SetStyles: Sets the visual styles used for rendering.
|
||||
func (m *Model) SetStyles(s Styles) {
|
||||
func (m *Model) SetStyles(s style.Styles) {
|
||||
m.styles = s
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Registers
|
||||
// ==================================================
|
||||
func (m *Model) Registers() map[rune]action.Register {
|
||||
func (m *Model) Registers() map[rune]core.Register {
|
||||
return m.registers
|
||||
}
|
||||
|
||||
func (m *Model) GetRegister(name rune) (action.Register, bool) {
|
||||
func (m *Model) GetRegister(name rune) (core.Register, bool) {
|
||||
reg, found := m.registers[name]
|
||||
return reg, found
|
||||
}
|
||||
|
||||
func (m *Model) SetRegister(name rune, t action.RegisterType, cnt []string) error {
|
||||
func (m *Model) SetRegister(name rune, t core.RegisterType, cnt []string) error {
|
||||
if _, found := m.GetRegister(name); !found {
|
||||
return fmt.Errorf("Register '%c' does not exist.", name)
|
||||
}
|
||||
|
||||
// TODO: This might be slow, pointers maybe?
|
||||
reg := action.Register{Type: t, Content: cnt}
|
||||
reg := core.Register{Type: t, Content: cnt}
|
||||
m.registers[name] = reg
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) UpdateDefaultRegister(t action.RegisterType, cnt []string) {
|
||||
func (m *Model) UpdateDefaultRegister(t core.RegisterType, cnt []string) {
|
||||
// Shift numbered registers: 0 -> 1 -> 2 -> ... -> 9 -> _ (discarded)
|
||||
for i := rune('9'); i > '0'; i-- {
|
||||
m.registers[i] = m.registers[i-1]
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/input"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/style"
|
||||
)
|
||||
|
||||
type ModelBuilder struct {
|
||||
@ -12,10 +13,10 @@ type ModelBuilder struct {
|
||||
func NewModelBuilder() *ModelBuilder {
|
||||
return &ModelBuilder{
|
||||
model: Model{
|
||||
buffers: []*action.Buffer{},
|
||||
windows: []*action.Window{},
|
||||
buffers: []*core.Buffer{},
|
||||
windows: []*core.Window{},
|
||||
activeWindowId: -1,
|
||||
mode: action.NormalMode,
|
||||
mode: core.NormalMode,
|
||||
termWidth: 0,
|
||||
termHeight: 0,
|
||||
input: input.NewHandler(),
|
||||
@ -26,35 +27,35 @@ func NewModelBuilder() *ModelBuilder {
|
||||
commandCursor: 0,
|
||||
commandError: nil,
|
||||
commandOutput: "",
|
||||
settings: action.NewDefaultSettings(),
|
||||
registers: action.DefaultRegisters(),
|
||||
styles: DefaultStyles(),
|
||||
settings: core.NewDefaultSettings(),
|
||||
registers: core.DefaultRegisters(),
|
||||
styles: style.DefaultStyles(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ModelBuilder.WithBuffers: Set the buffers for the model. Buffers represent
|
||||
// the in-memory text content of files being edited.
|
||||
func (mb *ModelBuilder) WithBuffers(buffers []*action.Buffer) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithBuffers(buffers []*core.Buffer) *ModelBuilder {
|
||||
mb.model.buffers = buffers
|
||||
return mb
|
||||
}
|
||||
|
||||
// ModelBuilder.AddBuffer: Add a single buffer to the model's buffer list.
|
||||
func (mb *ModelBuilder) AddBuffer(buffer *action.Buffer) *ModelBuilder {
|
||||
func (mb *ModelBuilder) AddBuffer(buffer *core.Buffer) *ModelBuilder {
|
||||
mb.model.buffers = append(mb.model.buffers, buffer)
|
||||
return mb
|
||||
}
|
||||
|
||||
// ModelBuilder.WithWindows: Set the windows for the model. Windows are viewports
|
||||
// that display buffer content with their own cursor position and scroll state.
|
||||
func (mb *ModelBuilder) WithWindows(windows []*action.Window) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithWindows(windows []*core.Window) *ModelBuilder {
|
||||
mb.model.windows = windows
|
||||
return mb
|
||||
}
|
||||
|
||||
// ModelBuilder.AddWindow: Add a single window to the model's window list.
|
||||
func (mb *ModelBuilder) AddWindow(window *action.Window) *ModelBuilder {
|
||||
func (mb *ModelBuilder) AddWindow(window *core.Window) *ModelBuilder {
|
||||
mb.model.windows = append(mb.model.windows, window)
|
||||
return mb
|
||||
}
|
||||
@ -67,7 +68,7 @@ func (mb *ModelBuilder) WithActiveWindowId(id int) *ModelBuilder {
|
||||
}
|
||||
|
||||
// ModelBuilder.WithMode: Set the editor mode (Normal, Insert, Visual, etc).
|
||||
func (mb *ModelBuilder) WithMode(mode action.Mode) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithMode(mode core.Mode) *ModelBuilder {
|
||||
mb.model.mode = mode
|
||||
return mb
|
||||
}
|
||||
@ -92,13 +93,13 @@ func (mb *ModelBuilder) WithTermHeight(height int) *ModelBuilder {
|
||||
}
|
||||
|
||||
// ModelBuilder.WithSettings: Set the editor settings (tabstop, scrolloff, etc).
|
||||
func (mb *ModelBuilder) WithSettings(settings action.Settings) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithSettings(settings core.Settings) *ModelBuilder {
|
||||
mb.model.settings = settings
|
||||
return mb
|
||||
}
|
||||
|
||||
// ModelBuilder.WithRegisters: Set the register map for yank/delete/paste operations.
|
||||
func (mb *ModelBuilder) WithRegisters(registers map[rune]action.Register) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithRegisters(registers map[rune]core.Register) *ModelBuilder {
|
||||
mb.model.registers = registers
|
||||
return mb
|
||||
}
|
||||
@ -128,7 +129,7 @@ func (mb *ModelBuilder) WithCommandOutput(output string) *ModelBuilder {
|
||||
}
|
||||
|
||||
// ModelBuilder.WithStyles: Set the visual styling for the editor.
|
||||
func (mb *ModelBuilder) WithStyles(styles Styles) *ModelBuilder {
|
||||
func (mb *ModelBuilder) WithStyles(styles style.Styles) *ModelBuilder {
|
||||
mb.model.styles = styles
|
||||
return mb
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Model.Update: Handles BubbleTea messages including window resizes and key
|
||||
// presses. Routes input to the handler and adjusts scroll after updates.
|
||||
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
|
||||
@ -4,19 +4,21 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
)
|
||||
|
||||
// posInsideSelection: Returns true if the given position is inside the current
|
||||
// visual selection, handling all three visual modes differently.
|
||||
func posInsideSelection(m Model, col, line int) bool {
|
||||
win := m.ActiveWindow()
|
||||
|
||||
switch m.Mode() {
|
||||
case action.VisualLineMode:
|
||||
case core.VisualLineMode:
|
||||
startY := min(win.Anchor.Line, win.Cursor.Line)
|
||||
endY := max(win.Anchor.Line, win.Cursor.Line)
|
||||
return line >= startY && line <= endY
|
||||
|
||||
case action.VisualMode:
|
||||
case core.VisualMode:
|
||||
ax := win.Anchor.Col
|
||||
ay := win.Anchor.Line
|
||||
|
||||
@ -38,7 +40,7 @@ func posInsideSelection(m Model, col, line int) bool {
|
||||
beforeEnd := line < endY || (line == endY && col <= endX)
|
||||
return afterStart && beforeEnd
|
||||
|
||||
case action.VisualBlockMode:
|
||||
case core.VisualBlockMode:
|
||||
startX := min(win.Anchor.Col, win.Cursor.Col)
|
||||
startY := min(win.Anchor.Line, win.Cursor.Line)
|
||||
endX := max(win.Anchor.Col, win.Cursor.Col)
|
||||
@ -52,6 +54,8 @@ func posInsideSelection(m Model, col, line int) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// posIsAnchor: Returns true if the given position matches the anchor position
|
||||
// used for visual mode debugging/rendering.
|
||||
func posIsAnchor(m Model, col, line int) bool {
|
||||
win := m.ActiveWindow()
|
||||
ax := win.Anchor.Col
|
||||
@ -59,10 +63,10 @@ func posIsAnchor(m Model, col, line int) bool {
|
||||
return col == ax && line == ay
|
||||
}
|
||||
|
||||
// Model.View: Renders the complete editor view including buffer content, line
|
||||
// numbers, status bar, and command line.
|
||||
func (m Model) View() string {
|
||||
win := m.ActiveWindow()
|
||||
return win.View(m)
|
||||
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
var view strings.Builder
|
||||
@ -154,6 +158,14 @@ func (m Model) View() string {
|
||||
return view.String()
|
||||
}
|
||||
|
||||
func viewWindow(w core.Window) string {
|
||||
var view string
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
// drawStatusBar: Renders the status bar with mode and cursor position,
|
||||
// padding the middle with spaces to fill the terminal width.
|
||||
func drawStatusBar(m Model) string {
|
||||
left := leftBar(m)
|
||||
right := rightBar(m)
|
||||
@ -169,10 +181,13 @@ func drawStatusBar(m Model) string {
|
||||
return left + middle + right
|
||||
}
|
||||
|
||||
// leftBar: Returns the left side of the status bar showing the current mode.
|
||||
func leftBar(m Model) string {
|
||||
return fmt.Sprintf(" %s", m.Mode().ToString())
|
||||
}
|
||||
|
||||
// rightBar: Returns the right side of the status bar showing cursor position
|
||||
// and selection count in visual mode.
|
||||
func rightBar(m Model) (bar string) {
|
||||
win := m.ActiveWindow()
|
||||
|
||||
@ -185,8 +200,10 @@ func rightBar(m Model) (bar string) {
|
||||
return
|
||||
}
|
||||
|
||||
// drawCommandBar: Renders the command line showing command input, errors, or
|
||||
// output depending on the current mode and state.
|
||||
func drawCommandBar(m Model) (bar string) {
|
||||
if m.Mode() == action.CommandMode {
|
||||
if m.Mode() == core.CommandMode {
|
||||
bar = ":"
|
||||
cmd := m.Command()
|
||||
cur := m.CommandCursor()
|
||||
@ -208,6 +225,10 @@ func drawCommandBar(m Model) (bar string) {
|
||||
bar = m.CommandOutput()
|
||||
} else if strings.TrimSpace(m.Command()) != "" {
|
||||
bar = fmt.Sprintf(":%s", m.Command())
|
||||
} else if len(m.input.Pending()) > 0 {
|
||||
// Get width of window and padding
|
||||
rep := m.ActiveWindow().Width - 10 // 10 is padding
|
||||
bar = fmt.Sprintf("%s%s", strings.Repeat(" ", rep), m.input.Pending())
|
||||
}
|
||||
|
||||
return bar
|
||||
|
||||
@ -2,9 +2,11 @@ package input
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// InputState: Represents the current state of the input handler state machine.
|
||||
type InputState int
|
||||
|
||||
const (
|
||||
@ -14,6 +16,8 @@ const (
|
||||
StateMotionCount
|
||||
)
|
||||
|
||||
// Handler: Manages input processing with a state machine for vim-style commands.
|
||||
// Handles counts, operators, motions, and multi-key sequences.
|
||||
type Handler struct {
|
||||
state InputState
|
||||
count1 int
|
||||
@ -32,6 +36,7 @@ type Handler struct {
|
||||
currentKeymap *Keymap
|
||||
}
|
||||
|
||||
// NewHandler: Creates a new input handler with initialized keymaps for all modes.
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
// keymap: NewNormalKeymap(),
|
||||
@ -43,23 +48,25 @@ func NewHandler() *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Handler.Handle: Main entry point for processing a keypress. Routes to appropriate
|
||||
// handler based on current mode and state.
|
||||
func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
// ESC always resets everything
|
||||
if key == "esc" {
|
||||
h.Reset()
|
||||
if m.Mode() == action.InsertMode {
|
||||
if m.Mode() == core.InsertMode {
|
||||
m.ExitInsertMode()
|
||||
} else {
|
||||
m.SetMode(action.NormalMode)
|
||||
m.SetMode(core.NormalMode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert/command mode bypasses the normal state machine entirely
|
||||
switch m.Mode() {
|
||||
case action.InsertMode:
|
||||
case core.InsertMode:
|
||||
return h.handleInsertKey(m, key)
|
||||
case action.CommandMode:
|
||||
case core.CommandMode:
|
||||
return h.handleCommandKey(m, key)
|
||||
}
|
||||
|
||||
@ -73,11 +80,11 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
|
||||
// Set working keymap
|
||||
switch m.Mode() {
|
||||
case action.NormalMode:
|
||||
case core.NormalMode:
|
||||
h.currentKeymap = h.normalKeymap
|
||||
case action.VisualMode,
|
||||
action.VisualLineMode,
|
||||
action.VisualBlockMode:
|
||||
case core.VisualMode,
|
||||
core.VisualLineMode,
|
||||
core.VisualBlockMode:
|
||||
h.currentKeymap = h.visualKeymap
|
||||
}
|
||||
|
||||
@ -111,7 +118,7 @@ func (h *Handler) Handle(m action.Model, key string) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// dispatch routes to the right 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 {
|
||||
switch h.state {
|
||||
case StateReady, StateCount:
|
||||
@ -123,6 +130,8 @@ func (h *Handler) dispatch(m action.Model, kind string, binding any, key string)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handler.handleInitial: Handles input when no operator is pending. Executes
|
||||
// motions, actions, or stores operators waiting for a motion.
|
||||
func (h *Handler) handleInitial(m action.Model, kind string, binding any, key string) tea.Cmd {
|
||||
count := h.effectiveCount()
|
||||
|
||||
@ -142,14 +151,14 @@ func (h *Handler) handleInitial(m action.Model, kind string, binding any, key st
|
||||
if m.Mode().IsVisualMode() {
|
||||
start, end := normalizeVisualSelection(m)
|
||||
// Visual line mode is linewise, others are charwise inclusive
|
||||
mtype := action.CharwiseInclusive
|
||||
if m.Mode() == action.VisualLineMode {
|
||||
mtype = action.Linewise
|
||||
mtype := core.CharwiseInclusive
|
||||
if m.Mode() == core.VisualLineMode {
|
||||
mtype = core.Linewise
|
||||
}
|
||||
cmd := op.Operate(m, start, end, mtype)
|
||||
// Only reset to normal mode if operator didn't enter insert mode
|
||||
if m.Mode() != action.InsertMode {
|
||||
m.SetMode(action.NormalMode)
|
||||
if m.Mode() != core.InsertMode {
|
||||
m.SetMode(core.NormalMode)
|
||||
}
|
||||
h.Reset()
|
||||
return cmd
|
||||
@ -174,6 +183,8 @@ func (h *Handler) handleInitial(m action.Model, kind string, binding any, key st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handler.handleAfterOperator: Handles input when an operator is pending.
|
||||
// Processes double-press operators (dd, yy) or applies operator to motion range.
|
||||
func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any, key string) tea.Cmd {
|
||||
count := h.effectiveCount()
|
||||
win := m.ActiveWindow()
|
||||
@ -209,6 +220,8 @@ func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any,
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (h *Handler) tryAccumulateCount(key string) bool {
|
||||
if len(key) != 1 || key[0] < '0' || key[0] > '9' {
|
||||
return false
|
||||
@ -234,6 +247,7 @@ func (h *Handler) tryAccumulateCount(key string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Handler.currentCount: Returns the count currently being accumulated based on state.
|
||||
func (h *Handler) currentCount() int {
|
||||
if h.state == StateOperatorPending || h.state == StateMotionCount {
|
||||
return h.count2
|
||||
@ -241,6 +255,8 @@ func (h *Handler) currentCount() int {
|
||||
return h.count1
|
||||
}
|
||||
|
||||
// Handler.effectiveCount: Calculates the final count by multiplying count1 and
|
||||
// count2, treating 0 as 1 for both.
|
||||
func (h *Handler) effectiveCount() int {
|
||||
c1, c2 := h.count1, h.count2
|
||||
if c1 == 0 {
|
||||
@ -252,6 +268,7 @@ func (h *Handler) effectiveCount() int {
|
||||
return c1 * c2
|
||||
}
|
||||
|
||||
// Handler.Reset: Clears all handler state including counts, operators, and buffers.
|
||||
func (h *Handler) Reset() {
|
||||
h.state = StateReady
|
||||
h.count1 = 0
|
||||
@ -262,10 +279,13 @@ func (h *Handler) Reset() {
|
||||
h.pending = ""
|
||||
}
|
||||
|
||||
// Handler.Pending: Returns the accumulated input buffer for display.
|
||||
func (h *Handler) Pending() string {
|
||||
return h.buffer
|
||||
}
|
||||
|
||||
// Handler.handleInsertKey: Processes a keypress in insert mode, recording it
|
||||
// for count replay and executing it as an action or character insertion.
|
||||
func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
|
||||
// Record the key for count replay (e.g. 5i...)
|
||||
m.SetInsertKeys(append(m.InsertKeys(), key))
|
||||
@ -283,6 +303,8 @@ func (h *Handler) handleInsertKey(m action.Model, key string) tea.Cmd {
|
||||
return action.InsertChar{Char: key}.Execute(m)
|
||||
}
|
||||
|
||||
// Handler.handleCommandKey: Processes a keypress in command mode, executing
|
||||
// it as an action or inserting it into the command line.
|
||||
func (h *Handler) handleCommandKey(m action.Model, key string) tea.Cmd {
|
||||
kind, binding := h.commandKeymap.Lookup(key)
|
||||
switch kind {
|
||||
@ -296,10 +318,12 @@ func (h *Handler) handleCommandKey(m action.Model, key string) tea.Cmd {
|
||||
return action.InsertCommandChar{Char: key}.Execute(m)
|
||||
}
|
||||
|
||||
func normalizeVisualSelection(m action.Model) (action.Position, action.Position) {
|
||||
// normalizeVisualSelection: Returns the visual selection with start before end,
|
||||
// regardless of which direction the selection was made.
|
||||
func normalizeVisualSelection(m action.Model) (core.Position, core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
a := action.Position{Line: win.Anchor.Line, Col: win.Anchor.Col}
|
||||
c := action.Position{Line: win.Cursor.Line, Col: win.Cursor.Col}
|
||||
a := core.Position{Line: win.Anchor.Line, Col: win.Anchor.Col}
|
||||
c := core.Position{Line: win.Cursor.Line, Col: win.Cursor.Col}
|
||||
if a.Line < c.Line || (a.Line == c.Line && a.Col <= c.Col) {
|
||||
return a, c
|
||||
}
|
||||
|
||||
@ -7,12 +7,14 @@ import (
|
||||
"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'
|
||||
}
|
||||
|
||||
// NewNormalKeymap: Creates a keymap for normal mode with all standard vim bindings.
|
||||
func NewNormalKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
@ -64,6 +66,7 @@ func NewNormalKeymap() *Keymap {
|
||||
}
|
||||
}
|
||||
|
||||
// NewVisualKeymap: Creates a keymap for visual modes (character, line, block).
|
||||
func NewVisualKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
@ -97,6 +100,7 @@ func NewVisualKeymap() *Keymap {
|
||||
}
|
||||
}
|
||||
|
||||
// NewInsertKeymap: Creates a keymap for insert mode with editing actions.
|
||||
func NewInsertKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
@ -117,6 +121,7 @@ func NewInsertKeymap() *Keymap {
|
||||
|
||||
}
|
||||
|
||||
// NewCommandKeymap: Creates a keymap for command mode with command line editing.
|
||||
func NewCommandKeymap() *Keymap {
|
||||
return &Keymap{
|
||||
motions: map[string]action.Motion{
|
||||
@ -135,7 +140,7 @@ func NewCommandKeymap() *Keymap {
|
||||
|
||||
}
|
||||
|
||||
// Lookup returns what type of binding a key is
|
||||
// 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) {
|
||||
if m, ok := km.motions[key]; ok {
|
||||
return "motion", m
|
||||
@ -149,7 +154,7 @@ func (km *Keymap) Lookup(key string) (kind string, value any) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// HasPrefix returns true if any binding starts with this prefix
|
||||
// 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 {
|
||||
|
||||
@ -2,6 +2,7 @@ package motion
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
@ -10,6 +11,8 @@ type MoveDown struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveDown.Execute: Moves the cursor down by Count lines without going past
|
||||
// the last line of the buffer.
|
||||
func (a MoveDown) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -19,7 +22,7 @@ func (a MoveDown) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveDown) Type() action.MotionType { return action.Linewise }
|
||||
func (a MoveDown) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
func (a MoveDown) WithCount(n int) action.Action {
|
||||
return MoveDown{Count: n}
|
||||
@ -30,6 +33,8 @@ type MoveUp struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveUp.Execute: Moves the cursor up by Count lines without going above
|
||||
// line 0.
|
||||
func (a MoveUp) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
for i := 0; i < a.Count && win.Cursor.Line > 0; i++ {
|
||||
@ -38,7 +43,7 @@ func (a MoveUp) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveUp) Type() action.MotionType { return action.Linewise }
|
||||
func (a MoveUp) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
func (a MoveUp) WithCount(n int) action.Action {
|
||||
return MoveUp{Count: n}
|
||||
@ -49,6 +54,8 @@ type MoveLeft struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveLeft.Execute: Moves the cursor left by Count columns without going
|
||||
// past column 0.
|
||||
func (a MoveLeft) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
for i := 0; i < a.Count && win.Cursor.Col > 0; i++ {
|
||||
@ -57,7 +64,7 @@ func (a MoveLeft) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveLeft) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
func (a MoveLeft) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
func (a MoveLeft) WithCount(n int) action.Action {
|
||||
return MoveLeft{Count: n}
|
||||
@ -68,6 +75,8 @@ type MoveRight struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveRight.Execute: Moves the cursor right by Count columns without going
|
||||
// past the end of the line.
|
||||
func (a MoveRight) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -78,7 +87,7 @@ func (a MoveRight) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveRight) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
func (a MoveRight) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
func (a MoveRight) WithCount(n int) action.Action {
|
||||
return MoveRight{Count: n}
|
||||
|
||||
@ -2,25 +2,32 @@ package motion
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// MoveCommandLeft implements Motion - moves cursor left in command line.
|
||||
type MoveCommandLeft struct{}
|
||||
|
||||
// MoveCommandLeft.Execute: Moves the command line cursor one position to the left.
|
||||
func (a MoveCommandLeft) Execute(m action.Model) tea.Cmd {
|
||||
// The set function handles bounds
|
||||
m.SetCommandCursor(m.CommandCursor() - 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveCommandLeft) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
// MoveCommandLeft.Type: Returns CharwiseExclusive for command line motion.
|
||||
func (a MoveCommandLeft) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveCommandRight implements Motion - moves cursor right in command line.
|
||||
type MoveCommandRight struct{}
|
||||
|
||||
// MoveCommandRight.Execute: Moves the command line cursor one position to the right.
|
||||
func (a MoveCommandRight) Execute(m action.Model) tea.Cmd {
|
||||
// The set function handles bounds
|
||||
m.SetCommandCursor(m.CommandCursor() + 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveCommandRight) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
// MoveCommandRight.Type: Returns CharwiseExclusive for command line motion.
|
||||
func (a MoveCommandRight) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
@ -2,23 +2,26 @@ package motion
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// MoveToTop implements Motion (gg) - linewise
|
||||
type MoveToTop struct{}
|
||||
|
||||
// MoveToTop.Execute: Moves the cursor to the first line of the buffer.
|
||||
func (a MoveToTop) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetCursorLine(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToTop) Type() action.MotionType { return action.Linewise }
|
||||
func (a MoveToTop) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
// MoveToBottom implements Motion (G) - linewise
|
||||
type MoveToBottom struct{}
|
||||
|
||||
// MoveToBottom.Execute: Moves the cursor to the last line of the buffer.
|
||||
func (a MoveToBottom) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -26,22 +29,24 @@ func (a MoveToBottom) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToBottom) Type() action.MotionType { return action.Linewise }
|
||||
func (a MoveToBottom) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
// MoveToLineStart implements Motion (0) - charwise
|
||||
type MoveToLineStart struct{}
|
||||
|
||||
// MoveToLineStart.Execute: Moves the cursor to the beginning of the current line.
|
||||
func (a MoveToLineStart) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
win.SetCursorCol(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToLineStart) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
func (a MoveToLineStart) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveToLineEnd implements Motion ($) - charwise
|
||||
type MoveToLineEnd struct{}
|
||||
|
||||
// MoveToLineEnd.Execute: Moves the cursor to the end of the current line.
|
||||
func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -49,11 +54,13 @@ func (a MoveToLineEnd) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToLineEnd) Type() action.MotionType { return action.CharwiseInclusive }
|
||||
func (a MoveToLineEnd) Type() core.MotionType { return core.CharwiseInclusive }
|
||||
|
||||
// MoveToLineContentStart implements Motion (_) - charwise
|
||||
type MoveToLineContentStart struct{}
|
||||
|
||||
// MoveToLineContentStart.Execute: Moves the cursor to the first non-whitespace
|
||||
// character on the current line.
|
||||
func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -77,13 +84,14 @@ func (a MoveToLineContentStart) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToLineContentStart) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
func (a MoveToLineContentStart) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveToColumn implements Motion (|) - charwise
|
||||
type MoveToColumn struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveToColumn.Execute: Moves the cursor to the column specified by Count.
|
||||
func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -95,7 +103,7 @@ func (a MoveToColumn) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveToColumn) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
func (a MoveToColumn) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
func (a MoveToColumn) WithCount(n int) action.Action {
|
||||
return MoveToColumn{Count: n}
|
||||
@ -106,6 +114,8 @@ func (a MoveToColumn) WithCount(n int) action.Action {
|
||||
// ScrollDownHalfPage implements Motion (ctrl+d) - linewise
|
||||
type ScrollDownHalfPage struct{}
|
||||
|
||||
// ScrollDownHalfPage.Execute: Scrolls down half a page while maintaining the
|
||||
// cursor's relative position in the viewport.
|
||||
func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -142,11 +152,13 @@ func (a ScrollDownHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a ScrollDownHalfPage) Type() action.MotionType { return action.Linewise }
|
||||
func (a ScrollDownHalfPage) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
// ScrollUpHalfPage implements Motion (ctrl+u) - linewise
|
||||
type ScrollUpHalfPage struct{}
|
||||
|
||||
// ScrollUpHalfPage.Execute: Scrolls up half a page while maintaining the
|
||||
// cursor's relative position in the viewport.
|
||||
func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -181,4 +193,4 @@ func (a ScrollUpHalfPage) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a ScrollUpHalfPage) Type() action.MotionType { return action.Linewise }
|
||||
func (a ScrollUpHalfPage) Type() core.MotionType { return core.Linewise }
|
||||
|
||||
@ -2,9 +2,12 @@ package motion
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// isWordChar: Returns true if the character is a word character (alphanumeric
|
||||
// or underscore).
|
||||
func isWordChar(c byte) bool {
|
||||
return (c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
@ -12,11 +15,15 @@ func isWordChar(c byte) bool {
|
||||
c == '_'
|
||||
}
|
||||
|
||||
// isWordPunctuation: Returns true if the character is punctuation (not whitespace
|
||||
// and not a word character).
|
||||
func isWordPunctuation(c byte) bool {
|
||||
return c != ' ' && c != '\t' && !isWordChar(c)
|
||||
}
|
||||
|
||||
func nextWordStart(buf *action.Buffer, x, y int) (int, int) {
|
||||
// nextWordStart: Finds the start of the next word from position (x,y), handling
|
||||
// word boundaries and line crossing.
|
||||
func nextWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Skip current class
|
||||
@ -64,7 +71,9 @@ func nextWordStart(buf *action.Buffer, x, y int) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
func nextWORDStart(buf *action.Buffer, x, y int) (int, int) {
|
||||
// nextWORDStart: Finds the start of the next WORD from position (x,y), treating
|
||||
// all non-whitespace as a single class.
|
||||
func nextWORDStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Skip current WORD (all non-whitespace is one class for W)
|
||||
@ -103,7 +112,9 @@ func nextWORDStart(buf *action.Buffer, x, y int) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
func nextWordEnd(buf *action.Buffer, x, y int) (int, int) {
|
||||
// nextWordEnd: Finds the end of the next word from position (x,y), respecting
|
||||
// word character classes.
|
||||
func nextWordEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Advance once to avoid being stuck on the current end
|
||||
@ -160,7 +171,9 @@ func nextWordEnd(buf *action.Buffer, x, y int) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
func nextWORDEnd(buf *action.Buffer, x, y int) (int, int) {
|
||||
// nextWORDEnd: Finds the end of the next WORD from position (x,y), treating
|
||||
// all non-whitespace as a single class.
|
||||
func nextWORDEnd(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Advance once to avoid being stuck on the current end
|
||||
@ -208,7 +221,9 @@ func nextWORDEnd(buf *action.Buffer, x, y int) (int, int) {
|
||||
return x, y
|
||||
}
|
||||
|
||||
func prevWordStart(buf *action.Buffer, x, y int) (int, int) {
|
||||
// prevWordStart: Finds the start of the previous word from position (x,y),
|
||||
// moving backward through character classes.
|
||||
func prevWordStart(buf *core.Buffer, x, y int) (int, int) {
|
||||
line := buf.Lines[y]
|
||||
|
||||
// Back one to avoid being stuck on the current start
|
||||
@ -263,6 +278,7 @@ type MoveForwardWord struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveForwardWord.Execute: Moves the cursor forward by Count words (w motion).
|
||||
func (a MoveForwardWord) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -277,8 +293,10 @@ func (a MoveForwardWord) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveForwardWord) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
// MoveForwardWord.Type: Returns CharwiseExclusive for word motion.
|
||||
func (a MoveForwardWord) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveForwardWord.WithCount: Returns a new MoveForwardWord with the given count.
|
||||
func (a MoveForwardWord) WithCount(n int) action.Action {
|
||||
return MoveForwardWord{Count: n}
|
||||
}
|
||||
@ -288,6 +306,7 @@ type MoveForwardWORD struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveForwardWORD.Execute: Moves the cursor forward by Count WORDs (W motion).
|
||||
func (a MoveForwardWORD) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -302,8 +321,10 @@ func (a MoveForwardWORD) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveForwardWORD) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
// MoveForwardWORD.Type: Returns CharwiseExclusive for WORD motion.
|
||||
func (a MoveForwardWORD) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveForwardWORD.WithCount: Returns a new MoveForwardWORD with the given count.
|
||||
func (a MoveForwardWORD) WithCount(n int) action.Action {
|
||||
return MoveForwardWORD{Count: n}
|
||||
}
|
||||
@ -313,6 +334,7 @@ type MoveForwardWordEnd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveForwardWordEnd.Execute: Moves the cursor to the end of the Count-th word (e motion).
|
||||
func (a MoveForwardWordEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -327,8 +349,10 @@ func (a MoveForwardWordEnd) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveForwardWordEnd) Type() action.MotionType { return action.CharwiseInclusive }
|
||||
// MoveForwardWordEnd.Type: Returns CharwiseInclusive for word-end motion.
|
||||
func (a MoveForwardWordEnd) Type() core.MotionType { return core.CharwiseInclusive }
|
||||
|
||||
// MoveForwardWordEnd.WithCount: Returns a new MoveForwardWordEnd with the given count.
|
||||
func (a MoveForwardWordEnd) WithCount(n int) action.Action {
|
||||
return MoveForwardWordEnd{Count: n}
|
||||
}
|
||||
@ -338,6 +362,7 @@ type MoveForwardWORDEnd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveForwardWORDEnd.Execute: Moves the cursor to the end of the Count-th WORD (E motion).
|
||||
func (a MoveForwardWORDEnd) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -352,8 +377,10 @@ func (a MoveForwardWORDEnd) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveForwardWORDEnd) Type() action.MotionType { return action.CharwiseInclusive }
|
||||
// MoveForwardWORDEnd.Type: Returns CharwiseInclusive for WORD-end motion.
|
||||
func (a MoveForwardWORDEnd) Type() core.MotionType { return core.CharwiseInclusive }
|
||||
|
||||
// MoveForwardWORDEnd.WithCount: Returns a new MoveForwardWORDEnd with the given count.
|
||||
func (a MoveForwardWORDEnd) WithCount(n int) action.Action {
|
||||
return MoveForwardWORDEnd{Count: n}
|
||||
}
|
||||
@ -363,6 +390,7 @@ type MoveBackwardWord struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
// MoveBackwardWord.Execute: Moves the cursor backward by Count words (b motion).
|
||||
func (a MoveBackwardWord) Execute(m action.Model) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -377,8 +405,10 @@ func (a MoveBackwardWord) Execute(m action.Model) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a MoveBackwardWord) Type() action.MotionType { return action.CharwiseExclusive }
|
||||
// MoveBackwardWord.Type: Returns CharwiseExclusive for backward word motion.
|
||||
func (a MoveBackwardWord) Type() core.MotionType { return core.CharwiseExclusive }
|
||||
|
||||
// MoveBackwardWord.WithCount: Returns a new MoveBackwardWord with the given count.
|
||||
func (a MoveBackwardWord) WithCount(n int) action.Action {
|
||||
return MoveBackwardWord{Count: n}
|
||||
}
|
||||
|
||||
@ -2,34 +2,37 @@ package operator
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Implements Operator (c)
|
||||
// ChangeOperator implements Operator (c) - changes (deletes and enters insert mode) text.
|
||||
type ChangeOperator struct{}
|
||||
|
||||
func (o ChangeOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
|
||||
// ChangeOperator.Operate: Changes text based on the current mode and motion type.
|
||||
func (o ChangeOperator) Operate(m action.Model, start, end core.Position, mtype core.MotionType) tea.Cmd {
|
||||
switch m.Mode() {
|
||||
case action.VisualMode:
|
||||
case core.VisualMode:
|
||||
changeCharSelection(m, start, end)
|
||||
case action.VisualLineMode:
|
||||
case core.VisualLineMode:
|
||||
changeLineSelection(m, start, end)
|
||||
case action.VisualBlockMode:
|
||||
case core.VisualBlockMode:
|
||||
changeBlockSelection(m, start, end)
|
||||
case action.NormalMode:
|
||||
case core.NormalMode:
|
||||
changeNormalMode(m, start, end, mtype)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
|
||||
// changeNormalMode: Changes text in normal mode based on motion type.
|
||||
func changeNormalMode(m action.Model, start, end core.Position, mtype core.MotionType) {
|
||||
// Normalize so start is always before or equal to end
|
||||
if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
// Linewise motions (j, k, G, gg) always operate on whole lines
|
||||
if mtype == action.Linewise {
|
||||
if mtype == core.Linewise {
|
||||
changeLineSelection(m, start, end)
|
||||
return
|
||||
}
|
||||
@ -37,18 +40,18 @@ func changeNormalMode(m action.Model, start, end action.Position, mtype action.M
|
||||
// Charwise motions on same line
|
||||
if start.Line == end.Line {
|
||||
// No movement = nothing to change
|
||||
if start.Col == end.Col && mtype == action.CharwiseExclusive {
|
||||
m.SetMode(action.InsertMode)
|
||||
if start.Col == end.Col && mtype == core.CharwiseExclusive {
|
||||
m.SetMode(core.InsertMode)
|
||||
return
|
||||
}
|
||||
// Exclusive motion: end position not included, so back up one
|
||||
if mtype == action.CharwiseExclusive {
|
||||
if mtype == core.CharwiseExclusive {
|
||||
end.Col--
|
||||
}
|
||||
if end.Col >= start.Col {
|
||||
changeCharSelection(m, start, end)
|
||||
} else {
|
||||
m.SetMode(action.InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -57,7 +60,8 @@ func changeNormalMode(m action.Model, start, end action.Position, mtype action.M
|
||||
changeCharSelection(m, start, end)
|
||||
}
|
||||
|
||||
func changeCharSelection(m action.Model, start, end action.Position) {
|
||||
// changeCharSelection: Changes a character-wise selection and enters insert mode.
|
||||
func changeCharSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -95,13 +99,14 @@ func changeCharSelection(m action.Model, start, end action.Position) {
|
||||
|
||||
win.SetCursorLine(start.Line)
|
||||
win.SetCursorCol(start.Col)
|
||||
m.SetMode(action.InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
// Update register with deleted text
|
||||
m.UpdateDefaultRegister(action.CharwiseRegister, []string{deletedText})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{deletedText})
|
||||
}
|
||||
|
||||
func changeLineSelection(m action.Model, start, end action.Position) {
|
||||
// changeLineSelection: Changes entire lines and enters insert mode.
|
||||
func changeLineSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -118,13 +123,14 @@ func changeLineSelection(m action.Model, start, end action.Position) {
|
||||
|
||||
win.SetCursorLine(insertY)
|
||||
win.SetCursorCol(0)
|
||||
m.SetMode(action.InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
// Update registers
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
}
|
||||
|
||||
func changeBlockSelection(m action.Model, start, end action.Position) {
|
||||
// changeBlockSelection: Changes a rectangular block selection and enters insert mode.
|
||||
func changeBlockSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -142,13 +148,13 @@ func changeBlockSelection(m action.Model, start, end action.Position) {
|
||||
|
||||
win.SetCursorLine(start.Line)
|
||||
win.SetCursorCol(startCol)
|
||||
m.SetMode(action.InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
}
|
||||
|
||||
// Verify ChangeOperator implements DoublePresser
|
||||
var _ action.DoublePresser = ChangeOperator{}
|
||||
|
||||
// Double press handles cc - change the entire line
|
||||
// ChangeOperator.DoublePress: Handles cc - changes Count entire lines.
|
||||
func (o ChangeOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -167,7 +173,7 @@ func (o ChangeOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
}
|
||||
|
||||
// Put deleted lines in register
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
|
||||
// Insert empty line at the original position for editing
|
||||
// If we deleted everything, startY might be past end, so clamp it
|
||||
@ -177,7 +183,7 @@ func (o ChangeOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
// Position cursor on the new empty line
|
||||
win.SetCursorLine(insertY)
|
||||
win.SetCursorCol(0)
|
||||
m.SetMode(action.InsertMode)
|
||||
m.SetMode(core.InsertMode)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,21 +2,23 @@ package operator
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Implements Operator (d)
|
||||
// DeleteOperator implements Operator (d) - deletes text in various modes.
|
||||
type DeleteOperator struct{}
|
||||
|
||||
func (o DeleteOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
|
||||
// DeleteOperator.Operate: Deletes text based on the current mode and motion type.
|
||||
func (o DeleteOperator) Operate(m action.Model, start, end core.Position, mtype core.MotionType) tea.Cmd {
|
||||
switch m.Mode() {
|
||||
case action.VisualMode:
|
||||
case core.VisualMode:
|
||||
deleteCharSelection(m, start, end)
|
||||
case action.VisualLineMode:
|
||||
case core.VisualLineMode:
|
||||
deleteLineSelection(m, start, end)
|
||||
case action.VisualBlockMode:
|
||||
case core.VisualBlockMode:
|
||||
deleteBlockSelection(m, start, end)
|
||||
case action.NormalMode:
|
||||
case core.NormalMode:
|
||||
deleteNormalMode(m, start, end, mtype)
|
||||
}
|
||||
return nil
|
||||
@ -25,7 +27,7 @@ func (o DeleteOperator) Operate(m action.Model, start, end action.Position, mtyp
|
||||
// Verify DeleteOperator implements DoublePresser
|
||||
var _ action.DoublePresser = DeleteOperator{}
|
||||
|
||||
// Double press handles dd - delete the entire line
|
||||
// DeleteOperator.DoublePress: Handles dd - deletes Count entire lines.
|
||||
func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -53,19 +55,20 @@ func (o DeleteOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
}
|
||||
|
||||
// Put her in the register!
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
|
||||
// deleteNormalMode: Deletes text in normal mode based on motion type.
|
||||
func deleteNormalMode(m action.Model, start, end core.Position, mtype core.MotionType) {
|
||||
// Normalize so start is always before or equal to end
|
||||
if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
|
||||
start, end = end, start
|
||||
}
|
||||
|
||||
// Linewise motions (j, k, G, gg) always operate on whole lines
|
||||
if mtype == action.Linewise {
|
||||
if mtype == core.Linewise {
|
||||
deleteLineSelection(m, start, end)
|
||||
return
|
||||
}
|
||||
@ -73,11 +76,11 @@ func deleteNormalMode(m action.Model, start, end action.Position, mtype action.M
|
||||
// Charwise motions on same line
|
||||
if start.Line == end.Line {
|
||||
// No movement = nothing to delete
|
||||
if start.Col == end.Col && mtype == action.CharwiseExclusive {
|
||||
if start.Col == end.Col && mtype == core.CharwiseExclusive {
|
||||
return
|
||||
}
|
||||
// Exclusive motion: end position not included, so back up one
|
||||
if mtype == action.CharwiseExclusive {
|
||||
if mtype == core.CharwiseExclusive {
|
||||
end.Col--
|
||||
}
|
||||
if end.Col >= start.Col {
|
||||
@ -90,7 +93,8 @@ func deleteNormalMode(m action.Model, start, end action.Position, mtype action.M
|
||||
deleteCharSelection(m, start, end)
|
||||
}
|
||||
|
||||
func deleteCharSelection(m action.Model, start, end action.Position) {
|
||||
// deleteCharSelection: Deletes a character-wise selection.
|
||||
func deleteCharSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -119,7 +123,8 @@ func deleteCharSelection(m action.Model, start, end action.Position) {
|
||||
win.SetCursorCol(start.Col)
|
||||
}
|
||||
|
||||
func deleteLineSelection(m action.Model, start, end action.Position) {
|
||||
// deleteLineSelection: Deletes entire lines in a selection range.
|
||||
func deleteLineSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
@ -142,10 +147,11 @@ func deleteLineSelection(m action.Model, start, end action.Position) {
|
||||
win.SetCursorLine(y)
|
||||
|
||||
// Update registers
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
}
|
||||
|
||||
func deleteBlockSelection(m action.Model, start, end action.Position) {
|
||||
// deleteBlockSelection: Deletes a rectangular block selection.
|
||||
func deleteBlockSelection(m action.Model, start, end core.Position) {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
|
||||
@ -4,23 +4,25 @@ import (
|
||||
"fmt"
|
||||
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
// Implements Operator (y)
|
||||
// YankOperator implements Operator (y) - copies text to register in various modes.
|
||||
type YankOperator struct{}
|
||||
|
||||
func (o YankOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
|
||||
// YankOperator.Operate: Copies text to register based on the current mode and motion type.
|
||||
func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype core.MotionType) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
|
||||
switch m.Mode() {
|
||||
case action.VisualMode:
|
||||
case core.VisualMode:
|
||||
yankVisualMode(m, start, end)
|
||||
case action.VisualLineMode:
|
||||
case core.VisualLineMode:
|
||||
yankVisualLineMode(m, start, end)
|
||||
case action.VisualBlockMode:
|
||||
case core.VisualBlockMode:
|
||||
yankVisualBlockMode(m, start, end)
|
||||
case action.NormalMode:
|
||||
case core.NormalMode:
|
||||
yankNormalMode(m, start, end, mtype)
|
||||
default:
|
||||
m.SetCommandError(fmt.Errorf("'y' operator not yet implemented."))
|
||||
@ -34,6 +36,7 @@ func (o YankOperator) Operate(m action.Model, start, end action.Position, mtype
|
||||
// Verify YankOperator implements DoublePresser
|
||||
var _ action.DoublePresser = YankOperator{}
|
||||
|
||||
// YankOperator.DoublePress: Handles yy - copies Count entire lines to register.
|
||||
func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
win := m.ActiveWindow()
|
||||
buf := m.ActiveBuffer()
|
||||
@ -50,12 +53,13 @@ func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
|
||||
}
|
||||
|
||||
// Put her in the register!
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func yankNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
|
||||
// yankNormalMode: Copies text to register in normal mode based on motion type.
|
||||
func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionType) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
switch {
|
||||
@ -72,16 +76,16 @@ func yankNormalMode(m action.Model, start, end action.Position, mtype action.Mot
|
||||
endX := max(start.Col, end.Col)
|
||||
|
||||
// Inclusive motions include the end character
|
||||
if mtype == action.CharwiseInclusive {
|
||||
if mtype == core.CharwiseInclusive {
|
||||
endX++
|
||||
}
|
||||
|
||||
endX = min(endX, len(line)) // Catch overflow
|
||||
|
||||
cnt := line[startX:endX]
|
||||
m.UpdateDefaultRegister(action.CharwiseRegister, []string{cnt})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
|
||||
|
||||
case mtype == action.Linewise:
|
||||
case mtype == core.Linewise:
|
||||
// This shouldn't happen
|
||||
if start.Col != end.Col {
|
||||
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
|
||||
@ -93,11 +97,12 @@ func yankNormalMode(m action.Model, start, end action.Position, mtype action.Mot
|
||||
endY := max(start.Line, end.Line)
|
||||
|
||||
cnt := buf.Lines[startY : endY+1]
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, cnt)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
||||
}
|
||||
}
|
||||
|
||||
func yankVisualMode(m action.Model, start, end action.Position) {
|
||||
// yankVisualMode: Copies character-wise visual selection to register.
|
||||
func yankVisualMode(m action.Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
// Normalize so start is before end
|
||||
@ -111,7 +116,7 @@ func yankVisualMode(m action.Model, start, end action.Position) {
|
||||
endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
|
||||
startCol := min(start.Col, len(line))
|
||||
cnt := line[startCol:endCol]
|
||||
m.UpdateDefaultRegister(action.CharwiseRegister, []string{cnt})
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
|
||||
return
|
||||
}
|
||||
|
||||
@ -133,10 +138,11 @@ func yankVisualMode(m action.Model, start, end action.Position) {
|
||||
endCol := min(end.Col+1, len(lastLine))
|
||||
content = append(content, lastLine[:endCol])
|
||||
|
||||
m.UpdateDefaultRegister(action.CharwiseRegister, content)
|
||||
m.UpdateDefaultRegister(core.CharwiseRegister, content)
|
||||
}
|
||||
|
||||
func yankVisualLineMode(m action.Model, start, end action.Position) {
|
||||
// yankVisualLineMode: Copies line-wise visual selection to register.
|
||||
func yankVisualLineMode(m action.Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
// This shouldn't happen
|
||||
@ -150,11 +156,12 @@ func yankVisualLineMode(m action.Model, start, end action.Position) {
|
||||
endY := max(start.Line, end.Line)
|
||||
|
||||
cnt := buf.Lines[startY : endY+1]
|
||||
m.UpdateDefaultRegister(action.LinewiseRegister, cnt)
|
||||
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
||||
|
||||
}
|
||||
|
||||
func yankVisualBlockMode(m action.Model, start, end action.Position) {
|
||||
// yankVisualBlockMode: Copies block-wise visual selection to register.
|
||||
func yankVisualBlockMode(m action.Model, start, end core.Position) {
|
||||
buf := m.ActiveBuffer()
|
||||
|
||||
// Normalize so startY <= endY and startX <= endX
|
||||
@ -178,5 +185,5 @@ func yankVisualBlockMode(m action.Model, start, end action.Position) {
|
||||
content = append(content, line[startX:lineEndX])
|
||||
}
|
||||
|
||||
m.UpdateDefaultRegister(action.BlockwiseRegister, content)
|
||||
m.UpdateDefaultRegister(core.BlockwiseRegister, content)
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
package editor
|
||||
package style
|
||||
|
||||
import (
|
||||
"git.gophernest.net/azpect/TextEditor/internal/action"
|
||||
"git.gophernest.net/azpect/TextEditor/internal/core"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Styles holds all the visual styling for the editor.
|
||||
// Passed to Window.View() so windows can render themselves.
|
||||
type Styles struct {
|
||||
// Cursor styles by mode
|
||||
CursorNormal lipgloss.Style
|
||||
@ -64,62 +63,13 @@ func DefaultStyles() Styles {
|
||||
}
|
||||
|
||||
// CursorStyle returns the appropriate cursor style for the given mode.
|
||||
func (s Styles) CursorStyle(mode action.Mode) lipgloss.Style {
|
||||
func (s Styles) CursorStyle(mode core.Mode) lipgloss.Style {
|
||||
switch mode {
|
||||
case action.InsertMode:
|
||||
case core.InsertMode:
|
||||
return s.CursorInsert
|
||||
case action.CommandMode:
|
||||
case core.CommandMode:
|
||||
return s.CursorCommand
|
||||
default:
|
||||
return s.CursorNormal
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Deprecated
|
||||
// ==================================================
|
||||
// func (m Model) cursorStyle() lipgloss.Style {
|
||||
// switch m.mode {
|
||||
// case action.NormalMode,
|
||||
// action.VisualMode,
|
||||
// action.VisualBlockMode,
|
||||
// action.VisualLineMode:
|
||||
// // Block cursor for normal mode
|
||||
// return lipgloss.NewStyle().Reverse(true)
|
||||
// case action.InsertMode:
|
||||
// // Bar/underline for insert mode
|
||||
// return lipgloss.NewStyle().Underline(true)
|
||||
// case action.CommandMode:
|
||||
// return lipgloss.NewStyle().Reverse(true)
|
||||
// default:
|
||||
// return lipgloss.NewStyle().Reverse(true)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // DEBUGGING STYLE
|
||||
// func (m Model) visualAnchorStyle() lipgloss.Style {
|
||||
// bg := lipgloss.Color("#a89020")
|
||||
// return lipgloss.NewStyle().Background(bg)
|
||||
// }
|
||||
//
|
||||
// func (m Model) gutterStyle(currentLine bool) lipgloss.Style {
|
||||
// bg := lipgloss.Color("236")
|
||||
// fg := lipgloss.Color("243")
|
||||
// if currentLine {
|
||||
// fg = lipgloss.Color("#d69d00")
|
||||
// }
|
||||
// return lipgloss.NewStyle().
|
||||
// Width(m.Settings().GutterSize).
|
||||
// Background(bg).
|
||||
// Foreground(fg)
|
||||
// }
|
||||
//
|
||||
// func (m Model) visualHighlightStyle() lipgloss.Style {
|
||||
// bg := lipgloss.Color("#7a6a00")
|
||||
// return lipgloss.NewStyle().Background(bg)
|
||||
// }
|
||||
//
|
||||
// func (m Model) commandErrorStyle() lipgloss.Style {
|
||||
// fg := lipgloss.Color("#e3203a")
|
||||
// return lipgloss.NewStyle().Foreground(fg)
|
||||
// }
|
||||
Loading…
x
Reference in New Issue
Block a user