diff --git a/cmd/gim/main.go b/cmd/gim/main.go index 0ef6d2b..9be303b 100644 --- a/cmd/gim/main.go +++ b/cmd/gim/main.go @@ -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() diff --git a/internal/action/change.go b/internal/action/change.go index 8e410a7..41a1386 100644 --- a/internal/action/change.go +++ b/internal/action/change.go @@ -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} } diff --git a/internal/action/command.go b/internal/action/command.go index 7ce5fc8..2d530ee 100644 --- a/internal/action/command.go +++ b/internal/action/command.go @@ -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 diff --git a/internal/action/delete.go b/internal/action/delete.go index 6d35887..b637aea 100644 --- a/internal/action/delete.go +++ b/internal/action/delete.go @@ -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} } diff --git a/internal/action/insert.go b/internal/action/insert.go index 5181860..144a13b 100644 --- a/internal/action/insert.go +++ b/internal/action/insert.go @@ -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() diff --git a/internal/action/interface.go b/internal/action/interface.go index 0795f39..88781a9 100644 --- a/internal/action/interface.go +++ b/internal/action/interface.go @@ -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) diff --git a/internal/action/misc.go b/internal/action/misc.go index 63160ee..554aff7 100644 --- a/internal/action/misc.go +++ b/internal/action/misc.go @@ -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 } diff --git a/internal/action/paste.go b/internal/action/paste.go index 542d635..12cf974 100644 --- a/internal/action/paste.go +++ b/internal/action/paste.go @@ -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} } diff --git a/internal/action/register.go b/internal/action/register.go deleted file mode 100644 index be2346c..0000000 --- a/internal/action/register.go +++ /dev/null @@ -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{}, - } -} diff --git a/internal/command/handlers.go b/internal/command/handlers.go index a5b3bdf..6cf1384 100644 --- a/internal/command/handlers.go +++ b/internal/command/handlers.go @@ -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 { diff --git a/internal/command/registry.go b/internal/command/registry.go index 7944ca9..a03a0cf 100644 --- a/internal/command/registry.go +++ b/internal/command/registry.go @@ -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{ diff --git a/internal/action/buffer.go b/internal/core/buffer.go similarity index 99% rename from internal/action/buffer.go rename to internal/core/buffer.go index e3eebda..0a1fa8e 100644 --- a/internal/action/buffer.go +++ b/internal/core/buffer.go @@ -1,4 +1,4 @@ -package action +package core type BufferOptions struct { // tabstop expandtab diff --git a/internal/action/buffer_builder.go b/internal/core/buffer_builder.go similarity index 99% rename from internal/action/buffer_builder.go rename to internal/core/buffer_builder.go index 3506b70..f2e1173 100644 --- a/internal/action/buffer_builder.go +++ b/internal/core/buffer_builder.go @@ -1,4 +1,4 @@ -package action +package core // Not great, but maybe the best way var CurrentBufferId int = 1 diff --git a/internal/action/mode.go b/internal/core/mode.go similarity index 74% rename from internal/action/mode.go rename to internal/core/mode.go index 6013edd..39bf5ac 100644 --- a/internal/action/mode.go +++ b/internal/core/mode.go @@ -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 || diff --git a/internal/core/position.go b/internal/core/position.go new file mode 100644 index 0000000..64f59be --- /dev/null +++ b/internal/core/position.go @@ -0,0 +1,6 @@ +package core + +// Position represents a location in the buffer +type Position struct { + Line, Col int +} diff --git a/internal/core/register.go b/internal/core/register.go new file mode 100644 index 0000000..7f6bc22 --- /dev/null +++ b/internal/core/register.go @@ -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{}, + } +} diff --git a/internal/action/settings.go b/internal/core/settings.go similarity index 59% rename from internal/action/settings.go rename to internal/core/settings.go index f9ee011..28482b5 100644 --- a/internal/action/settings.go +++ b/internal/core/settings.go @@ -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, diff --git a/internal/core/types.go b/internal/core/types.go new file mode 100644 index 0000000..b1f90d7 --- /dev/null +++ b/internal/core/types.go @@ -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 +} diff --git a/internal/action/window.go b/internal/core/window.go similarity index 79% rename from internal/action/window.go rename to internal/core/window.go index 783ebc5..4b8a09d 100644 --- a/internal/action/window.go +++ b/internal/core/window.go @@ -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 // ================================================== diff --git a/internal/action/window_builder.go b/internal/core/window_builder.go similarity index 99% rename from internal/action/window_builder.go rename to internal/core/window_builder.go index 9f507f3..75e5645 100644 --- a/internal/action/window_builder.go +++ b/internal/core/window_builder.go @@ -1,4 +1,4 @@ -package action +package core // Not great, but maybe the best way var CurrentWindowId int = 1000 diff --git a/internal/editor/helpers_example_test.go b/internal/editor/helpers_example_test.go index e8f0f90..ee5de86 100644 --- a/internal/editor/helpers_example_test.go +++ b/internal/editor/helpers_example_test.go @@ -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 { diff --git a/internal/editor/helpers_test.go b/internal/editor/helpers_test.go index 0ac1dfc..19f642d 100644 --- a/internal/editor/helpers_test.go +++ b/internal/editor/helpers_test.go @@ -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)) } diff --git a/internal/editor/integration_command_test.go b/internal/editor/integration_command_test.go index 68e89ca..7cdef8b 100644 --- a/internal/editor/integration_command_test.go +++ b/internal/editor/integration_command_test.go @@ -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()) } }) diff --git a/internal/editor/integration_delete_test.go b/internal/editor/integration_delete_test.go index eb121ad..77bedd0 100644 --- a/internal/editor/integration_delete_test.go +++ b/internal/editor/integration_delete_test.go @@ -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) diff --git a/internal/editor/integration_insert_test.go b/internal/editor/integration_insert_test.go index 72bae04..6b6e8cd 100644 --- a/internal/editor/integration_insert_test.go +++ b/internal/editor/integration_insert_test.go @@ -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) diff --git a/internal/editor/integration_motion_basic_test.go b/internal/editor/integration_motion_basic_test.go index 843d8c6..40bd2de 100644 --- a/internal/editor/integration_motion_basic_test.go +++ b/internal/editor/integration_motion_basic_test.go @@ -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) diff --git a/internal/editor/integration_motion_jump_test.go b/internal/editor/integration_motion_jump_test.go index 46ccf4a..df6fb22 100644 --- a/internal/editor/integration_motion_jump_test.go +++ b/internal/editor/integration_motion_jump_test.go @@ -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) diff --git a/internal/editor/integration_motion_word_test.go b/internal/editor/integration_motion_word_test.go index eea63c0..29c7330 100644 --- a/internal/editor/integration_motion_word_test.go +++ b/internal/editor/integration_motion_word_test.go @@ -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) diff --git a/internal/editor/integration_operator_change_test.go b/internal/editor/integration_operator_change_test.go index 1f113ed..ec019bf 100644 --- a/internal/editor/integration_operator_change_test.go +++ b/internal/editor/integration_operator_change_test.go @@ -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()) } }) diff --git a/internal/editor/integration_operator_delete_test.go b/internal/editor/integration_operator_delete_test.go index 7d7e9a3..fbe50c9 100644 --- a/internal/editor/integration_operator_delete_test.go +++ b/internal/editor/integration_operator_delete_test.go @@ -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) diff --git a/internal/editor/integration_paste_test.go b/internal/editor/integration_paste_test.go index 6ee44b9..34be61a 100644 --- a/internal/editor/integration_paste_test.go +++ b/internal/editor/integration_paste_test.go @@ -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") diff --git a/internal/editor/integration_scroll_test.go b/internal/editor/integration_scroll_test.go index 4aaaddd..703338b 100644 --- a/internal/editor/integration_scroll_test.go +++ b/internal/editor/integration_scroll_test.go @@ -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 diff --git a/internal/editor/integration_visual_test.go b/internal/editor/integration_visual_test.go index 44a7437..858b17c 100644 --- a/internal/editor/integration_visual_test.go +++ b/internal/editor/integration_visual_test.go @@ -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") diff --git a/internal/editor/integration_yank_test.go b/internal/editor/integration_yank_test.go index 415157f..1e6fe78 100644 --- a/internal/editor/integration_yank_test.go +++ b/internal/editor/integration_yank_test.go @@ -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") diff --git a/internal/editor/model.go b/internal/editor/model.go index 9855455..71995e7 100644 --- a/internal/editor/model.go +++ b/internal/editor/model.go @@ -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] diff --git a/internal/editor/model_builder.go b/internal/editor/model_builder.go index 9798955..c258999 100644 --- a/internal/editor/model_builder.go +++ b/internal/editor/model_builder.go @@ -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 } diff --git a/internal/editor/update.go b/internal/editor/update.go index fdf5cfe..93c6f88 100644 --- a/internal/editor/update.go +++ b/internal/editor/update.go @@ -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 diff --git a/internal/editor/view.go b/internal/editor/view.go index abde91f..9044c97 100644 --- a/internal/editor/view.go +++ b/internal/editor/view.go @@ -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 diff --git a/internal/input/handler.go b/internal/input/handler.go index 42ed163..15da691 100644 --- a/internal/input/handler.go +++ b/internal/input/handler.go @@ -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 } diff --git a/internal/input/keymap.go b/internal/input/keymap.go index cb9f598..957d70d 100644 --- a/internal/input/keymap.go +++ b/internal/input/keymap.go @@ -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 { diff --git a/internal/motion/basic.go b/internal/motion/basic.go index faffcd3..32f5d5c 100644 --- a/internal/motion/basic.go +++ b/internal/motion/basic.go @@ -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} diff --git a/internal/motion/command.go b/internal/motion/command.go index 48e0594..05d5fc8 100644 --- a/internal/motion/command.go +++ b/internal/motion/command.go @@ -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 } diff --git a/internal/motion/jump.go b/internal/motion/jump.go index 87997c2..c135147 100644 --- a/internal/motion/jump.go +++ b/internal/motion/jump.go @@ -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 } diff --git a/internal/motion/word.go b/internal/motion/word.go index 332c855..a3011c9 100644 --- a/internal/motion/word.go +++ b/internal/motion/word.go @@ -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} } diff --git a/internal/operator/change.go b/internal/operator/change.go index 18960e1..be5cc7e 100644 --- a/internal/operator/change.go +++ b/internal/operator/change.go @@ -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 } diff --git a/internal/operator/delete.go b/internal/operator/delete.go index 5a00481..ecdb5b5 100644 --- a/internal/operator/delete.go +++ b/internal/operator/delete.go @@ -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() diff --git a/internal/operator/yank.go b/internal/operator/yank.go index 9bb949d..0d46299 100644 --- a/internal/operator/yank.go +++ b/internal/operator/yank.go @@ -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) } diff --git a/internal/editor/style.go b/internal/style/style.go similarity index 50% rename from internal/editor/style.go rename to internal/style/style.go index b2d2516..bcdeb43 100644 --- a/internal/editor/style.go +++ b/internal/style/style.go @@ -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) -// }