diff --git a/cmd/gim/main.go b/cmd/gim/main.go index 49763c8..0766a75 100644 --- a/cmd/gim/main.go +++ b/cmd/gim/main.go @@ -8,17 +8,21 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -func generateLines(n int) []string { - lines := make([]string, n) - for i := range n { - lines[i] = fmt.Sprintf("line %d", i+1) - } - return lines -} - func main() { - tea.NewProgram( - editor.NewModel(generateLines(64), action.Position{Line: 0, Col: 0}), + m, _ := tea.NewProgram( + editor.NewModel([]string{""}, action.Position{Line: 0, Col: 0}), tea.WithAltScreen(), ).Run() + + final, ok := m.(*editor.Model) + if ok { + fmt.Printf("PRINTING WINDOWS: %+v\n", final.Windows()) + fmt.Printf("PRINTING ACTIVE WINDOW: %+v\n", final.ActiveWindow()) + for _, win := range final.Windows() { + fmt.Printf("\t%+v\n", *win.Buffer) + } + } else { + fmt.Printf("PRINTING ALL: %+v\n", m) + } + } diff --git a/internal/action/buffer.go b/internal/action/buffer.go new file mode 100644 index 0000000..4845cb4 --- /dev/null +++ b/internal/action/buffer.go @@ -0,0 +1,80 @@ +package action + +type BufferOptions struct { + // tabstop expandtab +} + +type Buffer struct { + // Buffer data + Id int + + // File data + Filename string + Filetype string + Lines []string + + // Flags (not used yet) + Modified bool + Loaded bool + Listed bool + + // Options BufferOptions + // UndoTree TODO: This will be big +} + +// Not great, but maybe the best way +var CurrentBufferId int = 1 + +func NewEmptyBuffer(lines []string) *Buffer { + buf := Buffer{ + Id: CurrentBufferId, + Filename: "", + Filetype: "", + Lines: lines, + Modified: false, + Loaded: true, + Listed: true, + } + + CurrentBufferId++ + + return &buf +} + +// Get the line at an index +func (b *Buffer) Line(idx int) string { + if idx < 0 || idx >= len(b.Lines) { + return "" + } + return b.Lines[idx] +} + +// Set the content at an index. +func (b *Buffer) SetLine(idx int, content string) { + if idx >= 0 && idx < len(b.Lines) { + b.Lines[idx] = content + } +} + +// Insert a line with content at an index +func (b *Buffer) InsertLine(idx int, content string) { + if idx < 0 { + idx = 0 + } + if idx > len(b.Lines) { + idx = len(b.Lines) + } + b.Lines = append(b.Lines[:idx], append([]string{content}, b.Lines[idx:]...)...) +} + +// Delete a line at an index +func (b *Buffer) DeleteLine(idx int) { + if idx >= 0 && idx < len(b.Lines) { + b.Lines = append(b.Lines[:idx], b.Lines[idx+1:]...) + } +} + +// Get the number of lines in the buffer +func (b *Buffer) LineCount() int { + return len(b.Lines) +} diff --git a/internal/action/interface.go b/internal/action/interface.go index ea1b320..2ecb910 100644 --- a/internal/action/interface.go +++ b/internal/action/interface.go @@ -21,6 +21,11 @@ type Model interface { SetCursorY(y int) ClampCursorX() + // Windows + Windows() []*Window + ActiveWindowId() int + ActiveWindow() *Window + // Window ScrollY() int SetScrollY(y int) diff --git a/internal/action/window.go b/internal/action/window.go new file mode 100644 index 0000000..273ecf9 --- /dev/null +++ b/internal/action/window.go @@ -0,0 +1,56 @@ +package action + +// TODO: No more global settings, window-wide settings +type WinOptions struct { + // Number bool + // Wrap bool + // Relnumber bool +} + +type Window struct { + Id int + Number int // Ignored for now, will be used when splits come into play + Buffer *Buffer + + Cursor Position + Anchor Position + + ScrollY int + Width int + Height int + // Folds // TODO + // Options WinOptions +} + +// Not great, but maybe the best way +var CurrentWindowId int = 1000 + +func NewEmptyWindow(lines []string, w, h int) *Window { + win := &Window{ + Id: CurrentWindowId, + Number: 1, // Ignored for now + + Buffer: NewEmptyBuffer(lines), + + Cursor: Position{Line: 0, Col: 0}, + Anchor: Position{Line: 0, Col: 0}, + + ScrollY: 0, + Width: w, + Height: h, + } + + // Increment + CurrentWindowId++ + + return win +} + +func (w *Window) ClampCursorX() { + lineLen := len(w.Buffer.Lines[w.Cursor.Line]) + if lineLen == 0 { + w.Cursor.Col = 0 + } else if w.Cursor.Col >= lineLen { + w.Cursor.Col = lineLen + } +} diff --git a/internal/editor/gim b/internal/editor/gim new file mode 100755 index 0000000..b8a7b85 Binary files /dev/null and b/internal/editor/gim differ diff --git a/internal/editor/helpers_example_test.go b/internal/editor/helpers_example_test.go index afc5a28..38bd62b 100644 --- a/internal/editor/helpers_example_test.go +++ b/internal/editor/helpers_example_test.go @@ -19,8 +19,8 @@ func TestHelperExamples(t *testing.T) { WithLines([]string{"hello", "world"}), ) m := getFinalModel(t, tm) - if len(m.lines) != 2 { - t.Errorf("expected 2 lines, got %d", len(m.lines)) + if len(m.Lines()) != 2 { + t.Errorf("expected 2 lines, got %d", len(m.Lines())) } }) diff --git a/internal/editor/helpers_test.go b/internal/editor/helpers_test.go index 3582d2f..1f3b496 100644 --- a/internal/editor/helpers_test.go +++ b/internal/editor/helpers_test.go @@ -123,8 +123,8 @@ func newTestModelWithTermSize(t *testing.T, lines []string, pos action.Position, } // getFinalModel extracts the final model state (sends ctrl+c to quit first) -func getFinalModel(t *testing.T, tm *teatest.TestModel) Model { +func getFinalModel(t *testing.T, tm *teatest.TestModel) *Model { tm.Send(tea.KeyMsg{Type: tea.KeyCtrlC}) fm := tm.FinalModel(t, teatest.WithFinalTimeout(time.Second)) - return fm.(Model) + return fm.(*Model) } diff --git a/internal/editor/integration_delete_test.go b/internal/editor/integration_delete_test.go index c3cdc69..bf3feac 100644 --- a/internal/editor/integration_delete_test.go +++ b/internal/editor/integration_delete_test.go @@ -13,8 +13,8 @@ func TestDeleteChar(t *testing.T) { sendKeys(tm, "x") m := getFinalModel(t, tm) - if m.lines[0] != "ello" { - t.Errorf("lines[0] = %q, want 'ello'", m.lines[0]) + if m.Line(0) != "ello" { + t.Errorf("lines[0] = %q, want 'ello'", m.Line(0)) } }) @@ -24,8 +24,8 @@ func TestDeleteChar(t *testing.T) { sendKeys(tm, "x") m := getFinalModel(t, tm) - if m.lines[0] != "helo" { - t.Errorf("lines[0] = %q, want 'helo'", m.lines[0]) + if m.Line(0) != "helo" { + t.Errorf("lines[0] = %q, want 'helo'", m.Line(0)) } }) @@ -35,8 +35,8 @@ func TestDeleteChar(t *testing.T) { sendKeys(tm, "x") m := getFinalModel(t, tm) - if m.lines[0] != "hell" { - t.Errorf("lines[0] = %q, want 'hell'", m.lines[0]) + if m.Line(0) != "hell" { + t.Errorf("lines[0] = %q, want 'hell'", m.Line(0)) } }) @@ -46,8 +46,8 @@ func TestDeleteChar(t *testing.T) { sendKeys(tm, "x", "x") m := getFinalModel(t, tm) - if m.lines[0] != "llo" { - t.Errorf("lines[0] = %q, want 'llo'", m.lines[0]) + if m.Line(0) != "llo" { + t.Errorf("lines[0] = %q, want 'llo'", m.Line(0)) } }) } @@ -59,8 +59,8 @@ func TestDeleteCharWithCount(t *testing.T) { sendKeys(tm, "3", "x") m := getFinalModel(t, tm) - if m.lines[0] != "lo" { - t.Errorf("lines[0] = %q, want 'lo'", m.lines[0]) + if m.Line(0) != "lo" { + t.Errorf("lines[0] = %q, want 'lo'", m.Line(0)) } }) @@ -70,8 +70,8 @@ func TestDeleteCharWithCount(t *testing.T) { sendKeys(tm, "1", "0", "x") m := getFinalModel(t, tm) - if m.lines[0] != "" { - t.Errorf("lines[0] = %q, want ''", m.lines[0]) + if m.Line(0) != "" { + t.Errorf("lines[0] = %q, want ''", m.Line(0)) } }) @@ -81,8 +81,8 @@ func TestDeleteCharWithCount(t *testing.T) { sendKeys(tm, "2", "x") m := getFinalModel(t, tm) - if m.lines[0] != "hlo" { - t.Errorf("lines[0] = %q, want 'hlo'", m.lines[0]) + if m.Line(0) != "hlo" { + t.Errorf("lines[0] = %q, want 'hlo'", m.Line(0)) } }) } diff --git a/internal/editor/integration_insert_test.go b/internal/editor/integration_insert_test.go index ec4c4ca..9af7015 100644 --- a/internal/editor/integration_insert_test.go +++ b/internal/editor/integration_insert_test.go @@ -25,8 +25,8 @@ func TestEnterInsert(t *testing.T) { sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "Xhello" { - t.Errorf("lines[0] = %q, want 'Xhello'", m.lines[0]) + if m.Line(0) != "Xhello" { + t.Errorf("lines[0] = %q, want 'Xhello'", m.Line(0)) } }) @@ -36,8 +36,8 @@ func TestEnterInsert(t *testing.T) { sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } }) @@ -47,8 +47,8 @@ func TestEnterInsert(t *testing.T) { sendKeys(tm, "i", "X", "esc") m := getFinalModel(t, tm) - if m.cursor.x != 2 { - t.Errorf("cursor.x = %d, want 2", m.cursor.x) + if m.CursorX() != 2 { + t.Errorf("cursor.x = %d, want 2", m.CursorX()) } }) } @@ -70,8 +70,8 @@ func TestEnterInsertAfter(t *testing.T) { sendKeys(tm, "a", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hXello" { - t.Errorf("lines[0] = %q, want 'hXello'", m.lines[0]) + if m.Line(0) != "hXello" { + t.Errorf("lines[0] = %q, want 'hXello'", m.Line(0)) } }) @@ -81,8 +81,8 @@ func TestEnterInsertAfter(t *testing.T) { sendKeys(tm, "a", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "helXlo" { - t.Errorf("lines[0] = %q, want 'helXlo'", m.lines[0]) + if m.Line(0) != "helXlo" { + t.Errorf("lines[0] = %q, want 'helXlo'", m.Line(0)) } }) } @@ -94,8 +94,8 @@ func TestEnterInsertLineStart(t *testing.T) { sendKeys(tm, "I", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "Xhello" { - t.Errorf("lines[0] = %q, want 'Xhello'", m.lines[0]) + if m.Line(0) != "Xhello" { + t.Errorf("lines[0] = %q, want 'Xhello'", m.Line(0)) } }) @@ -105,8 +105,8 @@ func TestEnterInsertLineStart(t *testing.T) { sendKeys(tm, "I", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "Xhello" { - t.Errorf("lines[0] = %q, want 'Xhello'", m.lines[0]) + if m.Line(0) != "Xhello" { + t.Errorf("lines[0] = %q, want 'Xhello'", m.Line(0)) } }) } @@ -118,8 +118,8 @@ func TestEnterInsertLineEnd(t *testing.T) { sendKeys(tm, "A", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "helloX" { - t.Errorf("lines[0] = %q, want 'helloX'", m.lines[0]) + if m.Line(0) != "helloX" { + t.Errorf("lines[0] = %q, want 'helloX'", m.Line(0)) } }) @@ -129,8 +129,8 @@ func TestEnterInsertLineEnd(t *testing.T) { sendKeys(tm, "A", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "helloX" { - t.Errorf("lines[0] = %q, want 'helloX'", m.lines[0]) + if m.Line(0) != "helloX" { + t.Errorf("lines[0] = %q, want 'helloX'", m.Line(0)) } }) } @@ -144,11 +144,11 @@ func TestOpenLineBelow(t *testing.T) { sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 3 { - t.Errorf("len(lines) = %d, want 3", len(m.lines)) + if m.LineCount() != 3 { + t.Errorf("len(lines) = %d, want 3", m.LineCount()) } - if m.lines[1] != "new" { - t.Errorf("lines[1] = %q, want 'new'", m.lines[1]) + if m.Line(1) != "new" { + t.Errorf("lines[1] = %q, want 'new'", m.Line(1)) } }) @@ -158,11 +158,11 @@ func TestOpenLineBelow(t *testing.T) { sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 4 { - t.Errorf("len(lines) = %d, want 4", len(m.lines)) + if m.LineCount() != 4 { + t.Errorf("len(lines) = %d, want 4", m.LineCount()) } - if m.lines[2] != "new" { - t.Errorf("lines[2] = %q, want 'new'", m.lines[2]) + if m.Line(2) != "new" { + t.Errorf("lines[2] = %q, want 'new'", m.Line(2)) } }) @@ -172,11 +172,11 @@ func TestOpenLineBelow(t *testing.T) { sendKeys(tm, "o", "n", "e", "w", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 3 { - t.Errorf("len(lines) = %d, want 3", len(m.lines)) + if m.LineCount() != 3 { + t.Errorf("len(lines) = %d, want 3", m.LineCount()) } - if m.lines[2] != "new" { - t.Errorf("lines[2] = %q, want 'new'", m.lines[2]) + if m.Line(2) != "new" { + t.Errorf("lines[2] = %q, want 'new'", m.Line(2)) } }) @@ -186,11 +186,11 @@ func TestOpenLineBelow(t *testing.T) { sendKeys(tm, "o", "esc") m := getFinalModel(t, tm) - if m.cursor.y != 1 { - t.Errorf("cursor.y = %d, want 1", m.cursor.y) + if m.CursorY() != 1 { + t.Errorf("cursor.y = %d, want 1", m.CursorY()) } - if m.cursor.x != 0 { - t.Errorf("cursor.x = %d, want 0", m.cursor.x) + if m.CursorX() != 0 { + t.Errorf("cursor.x = %d, want 0", m.CursorX()) } }) } @@ -202,12 +202,12 @@ func TestOpenLineBelowWithCount(t *testing.T) { sendKeys(tm, "3", "o", "x", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 4 { - t.Errorf("len(lines) = %d, want 4", len(m.lines)) + if m.LineCount() != 4 { + t.Errorf("len(lines) = %d, want 4", m.LineCount()) } for i := 1; i <= 3; i++ { - if m.lines[i] != "x" { - t.Errorf("lines[%d] = %q, want 'x'", i, m.lines[i]) + if m.Line(i) != "x" { + t.Errorf("lines[%d] = %q, want 'x'", i, m.Line(i)) } } }) @@ -218,14 +218,14 @@ func TestOpenLineBelowWithCount(t *testing.T) { sendKeys(tm, "2", "o", "a", "b", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 3 { - t.Errorf("len(lines) = %d, want 3", len(m.lines)) + if m.LineCount() != 3 { + t.Errorf("len(lines) = %d, want 3", m.LineCount()) } - if m.lines[1] != "ab" { - t.Errorf("lines[1] = %q, want 'ab'", m.lines[1]) + if m.Line(1) != "ab" { + t.Errorf("lines[1] = %q, want 'ab'", m.Line(1)) } - if m.lines[2] != "ab" { - t.Errorf("lines[2] = %q, want 'ab'", m.lines[2]) + if m.Line(2) != "ab" { + t.Errorf("lines[2] = %q, want 'ab'", m.Line(2)) } }) } @@ -237,11 +237,11 @@ func TestOpenLineAbove(t *testing.T) { sendKeys(tm, "O", "n", "e", "w", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 3 { - t.Errorf("len(lines) = %d, want 3", len(m.lines)) + if m.LineCount() != 3 { + t.Errorf("len(lines) = %d, want 3", m.LineCount()) } - if m.lines[1] != "new" { - t.Errorf("lines[1] = %q, want 'new'", m.lines[1]) + if m.Line(1) != "new" { + t.Errorf("lines[1] = %q, want 'new'", m.Line(1)) } }) @@ -251,11 +251,11 @@ func TestOpenLineAbove(t *testing.T) { sendKeys(tm, "O", "n", "e", "w", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 3 { - t.Errorf("len(lines) = %d, want 3", len(m.lines)) + if m.LineCount() != 3 { + t.Errorf("len(lines) = %d, want 3", m.LineCount()) } - if m.lines[0] != "new" { - t.Errorf("lines[0] = %q, want 'new'", m.lines[0]) + if m.Line(0) != "new" { + t.Errorf("lines[0] = %q, want 'new'", m.Line(0)) } }) @@ -265,8 +265,8 @@ func TestOpenLineAbove(t *testing.T) { sendKeys(tm, "O", "esc") m := getFinalModel(t, tm) - if m.cursor.x != 0 { - t.Errorf("cursor.x = %d, want 0", m.cursor.x) + if m.CursorX() != 0 { + t.Errorf("cursor.x = %d, want 0", m.CursorX()) } }) } @@ -278,12 +278,12 @@ func TestOpenLineAboveWithCount(t *testing.T) { sendKeys(tm, "3", "O", "x", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 4 { - t.Errorf("len(lines) = %d, want 4", len(m.lines)) + if m.LineCount() != 4 { + t.Errorf("len(lines) = %d, want 4", m.LineCount()) } for i := 0; i < 3; i++ { - if m.lines[i] != "x" { - t.Errorf("lines[%d] = %q, want 'x'", i, m.lines[i]) + if m.Line(i) != "x" { + t.Errorf("lines[%d] = %q, want 'x'", i, m.Line(i)) } } }) @@ -298,14 +298,14 @@ func TestInsertModeEnter(t *testing.T) { sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 2 { - t.Errorf("len(lines) = %d, want 2", len(m.lines)) + if m.LineCount() != 2 { + t.Errorf("len(lines) = %d, want 2", m.LineCount()) } - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } - if m.lines[1] != " world" { - t.Errorf("lines[1] = %q, want ' world'", m.lines[1]) + if m.Line(1) != " world" { + t.Errorf("lines[1] = %q, want ' world'", m.Line(1)) } }) @@ -315,14 +315,14 @@ func TestInsertModeEnter(t *testing.T) { sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 2 { - t.Errorf("len(lines) = %d, want 2", len(m.lines)) + if m.LineCount() != 2 { + t.Errorf("len(lines) = %d, want 2", m.LineCount()) } - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } - if m.lines[1] != "" { - t.Errorf("lines[1] = %q, want ''", m.lines[1]) + if m.Line(1) != "" { + t.Errorf("lines[1] = %q, want ''", m.Line(1)) } }) @@ -332,14 +332,14 @@ func TestInsertModeEnter(t *testing.T) { sendKeys(tm, "i", "enter", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 2 { - t.Errorf("len(lines) = %d, want 2", len(m.lines)) + if m.LineCount() != 2 { + t.Errorf("len(lines) = %d, want 2", m.LineCount()) } - if m.lines[0] != "" { - t.Errorf("lines[0] = %q, want ''", m.lines[0]) + if m.Line(0) != "" { + t.Errorf("lines[0] = %q, want ''", m.Line(0)) } - if m.lines[1] != "hello" { - t.Errorf("lines[1] = %q, want 'hello'", m.lines[1]) + if m.Line(1) != "hello" { + t.Errorf("lines[1] = %q, want 'hello'", m.Line(1)) } }) } @@ -351,8 +351,8 @@ func TestInsertModeBackspace(t *testing.T) { sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "helo" { - t.Errorf("lines[0] = %q, want 'helo'", m.lines[0]) + if m.Line(0) != "helo" { + t.Errorf("lines[0] = %q, want 'helo'", m.Line(0)) } }) @@ -362,11 +362,11 @@ func TestInsertModeBackspace(t *testing.T) { sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 1 { - t.Errorf("len(lines) = %d, want 1", len(m.lines)) + if m.LineCount() != 1 { + t.Errorf("len(lines) = %d, want 1", m.LineCount()) } - if m.lines[0] != "helloworld" { - t.Errorf("lines[0] = %q, want 'helloworld'", m.lines[0]) + if m.Line(0) != "helloworld" { + t.Errorf("lines[0] = %q, want 'helloworld'", m.Line(0)) } }) @@ -376,8 +376,8 @@ func TestInsertModeBackspace(t *testing.T) { sendKeys(tm, "i", "backspace", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } }) @@ -387,8 +387,8 @@ func TestInsertModeBackspace(t *testing.T) { sendKeys(tm, "i", "backspace", "backspace", "backspace", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "he" { - t.Errorf("lines[0] = %q, want 'he'", m.lines[0]) + if m.Line(0) != "he" { + t.Errorf("lines[0] = %q, want 'he'", m.Line(0)) } }) } @@ -400,8 +400,8 @@ func TestInsertModeDelete(t *testing.T) { sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "word" { - t.Errorf("lines[0] = %q, want 'word'", m.lines[0]) + if m.Line(0) != "word" { + t.Errorf("lines[0] = %q, want 'word'", m.Line(0)) } }) @@ -411,11 +411,11 @@ func TestInsertModeDelete(t *testing.T) { sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 1 { - t.Errorf("len(lines) = %d, want 1", len(m.lines)) + if m.LineCount() != 1 { + t.Errorf("len(lines) = %d, want 1", m.LineCount()) } - if m.lines[0] != "helloworld" { - t.Errorf("lines[0] = %q, want 'helloworld'", m.lines[0]) + if m.Line(0) != "helloworld" { + t.Errorf("lines[0] = %q, want 'helloworld'", m.Line(0)) } }) @@ -425,11 +425,11 @@ func TestInsertModeDelete(t *testing.T) { sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) - if len(m.lines) != 1 { - t.Errorf("len(lines) = %d, want 1", len(m.lines)) + if m.LineCount() != 1 { + t.Errorf("len(lines) = %d, want 1", m.LineCount()) } - if m.lines[0] != "world" { - t.Errorf("lines[0] = %q, want 'world'", m.lines[0]) + if m.Line(0) != "world" { + t.Errorf("lines[0] = %q, want 'world'", m.Line(0)) } }) @@ -439,8 +439,8 @@ func TestInsertModeDelete(t *testing.T) { sendKeys(tm, "i", "delete", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } }) @@ -450,8 +450,8 @@ func TestInsertModeDelete(t *testing.T) { sendKeys(tm, "i", "delete", "delete", "delete", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "ho" { - t.Errorf("lines[0] = %q, want 'he'", m.lines[0]) + if m.Line(0) != "ho" { + t.Errorf("lines[0] = %q, want 'he'", m.Line(0)) } }) @@ -464,8 +464,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "left", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } }) @@ -475,8 +475,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "right", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } }) @@ -486,11 +486,11 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } - if m.lines[1] != "world" { - t.Errorf("lines[1] = %q, want 'world'", m.lines[1]) + if m.Line(1) != "world" { + t.Errorf("lines[1] = %q, want 'world'", m.Line(1)) } }) @@ -500,11 +500,11 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } - if m.lines[1] != "woXrld" { - t.Errorf("lines[1] = %q, want 'woXrld'", m.lines[1]) + if m.Line(1) != "woXrld" { + t.Errorf("lines[1] = %q, want 'woXrld'", m.Line(1)) } }) @@ -514,8 +514,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "left", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "Xhello" { - t.Errorf("lines[0] = %q, want 'Xhello'", m.lines[0]) + if m.Line(0) != "Xhello" { + t.Errorf("lines[0] = %q, want 'Xhello'", m.Line(0)) } }) @@ -525,8 +525,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "a", "right", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "helloX" { - t.Errorf("lines[0] = %q, want 'helloX'", m.lines[0]) + if m.Line(0) != "helloX" { + t.Errorf("lines[0] = %q, want 'helloX'", m.Line(0)) } }) @@ -536,8 +536,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } }) @@ -547,8 +547,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "heXllo" { - t.Errorf("lines[0] = %q, want 'heXllo'", m.lines[0]) + if m.Line(0) != "heXllo" { + t.Errorf("lines[0] = %q, want 'heXllo'", m.Line(0)) } }) @@ -558,8 +558,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "up", "X", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hiX" { - t.Errorf("lines[0] = %q, want 'hiX'", m.lines[0]) + if m.Line(0) != "hiX" { + t.Errorf("lines[0] = %q, want 'hiX'", m.Line(0)) } }) @@ -569,8 +569,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "down", "X", "esc") m := getFinalModel(t, tm) - if m.lines[1] != "hiX" { - t.Errorf("lines[1] = %q, want 'hiX'", m.lines[1]) + if m.Line(1) != "hiX" { + t.Errorf("lines[1] = %q, want 'hiX'", m.Line(1)) } }) @@ -580,8 +580,8 @@ func TestInsertModeArrowKeys(t *testing.T) { sendKeys(tm, "i", "right", "right", "down", "X", "esc") m := getFinalModel(t, tm) - if m.lines[1] != "woXrld" { - t.Errorf("lines[1] = %q, want 'woXrld'", m.lines[1]) + if m.Line(1) != "woXrld" { + t.Errorf("lines[1] = %q, want 'woXrld'", m.Line(1)) } }) } @@ -593,8 +593,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello " { - t.Errorf("lines[0] = %q, want 'hello '", m.lines[0]) + if m.Line(0) != "hello " { + t.Errorf("lines[0] = %q, want 'hello '", m.Line(0)) } if m.CursorX() != 5 { t.Errorf("CursorX() = %d, want '5'", m.CursorX()) @@ -607,8 +607,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "" { - t.Errorf("lines[0] = %q, want ''", m.lines[0]) + if m.Line(0) != "" { + t.Errorf("lines[0] = %q, want ''", m.Line(0)) } if m.CursorX() != 0 { t.Errorf("CursorX() = %d, want '0'", m.CursorX()) @@ -621,8 +621,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello wo" { - t.Errorf("lines[0] = %q, want 'hello wo'", m.lines[0]) + if m.Line(0) != "hello wo" { + t.Errorf("lines[0] = %q, want 'hello wo'", m.Line(0)) } if m.CursorX() != 7 { t.Errorf("CursorX() = %d, want '7'", m.CursorX()) @@ -672,8 +672,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "i", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello" { - t.Errorf("lines[0] = %q, want 'hello'", m.lines[0]) + if m.Line(0) != "hello" { + t.Errorf("lines[0] = %q, want 'hello'", m.Line(0)) } if m.CursorX() != 0 { t.Errorf("CursorX() = %d, want 0", m.CursorX()) @@ -686,8 +686,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "i", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "lo" { - t.Errorf("lines[0] = %q, want 'lo'", m.lines[0]) + if m.Line(0) != "lo" { + t.Errorf("lines[0] = %q, want 'lo'", m.Line(0)) } if m.CursorX() != 0 { t.Errorf("CursorX() = %d, want 0", m.CursorX()) @@ -700,8 +700,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "..." { - t.Errorf("lines[0] = %q, want '...'", m.lines[0]) + if m.Line(0) != "..." { + t.Errorf("lines[0] = %q, want '...'", m.Line(0)) } if m.CursorX() != 2 { t.Errorf("CursorX() = %d, want 2", m.CursorX()) @@ -714,8 +714,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "hello\t" { - t.Errorf("lines[0] = %q, want 'hello\\t'", m.lines[0]) + if m.Line(0) != "hello\t" { + t.Errorf("lines[0] = %q, want 'hello\\t'", m.Line(0)) } if m.CursorX() != 5 { t.Errorf("CursorX() = %d, want 5", m.CursorX()) @@ -731,8 +731,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { if m.LineCount() != 1 { t.Errorf("LineCount() = %d, want 1", m.LineCount()) } - if m.lines[0] != "helloworld" { - t.Errorf("lines[0] = %q, want 'helloworld'", m.lines[0]) + if m.Line(0) != "helloworld" { + t.Errorf("lines[0] = %q, want 'helloworld'", m.Line(0)) } if m.CursorX() != 4 { t.Errorf("CursorX() = %d, want 4", m.CursorX()) @@ -748,8 +748,8 @@ func TestInsertModeDeletePreviousWord(t *testing.T) { sendKeys(tm, "a", "ctrl+w", "esc") m := getFinalModel(t, tm) - if m.lines[0] != "" { - t.Errorf("lines[0] = %q, want ''", m.lines[0]) + if m.Line(0) != "" { + t.Errorf("lines[0] = %q, want ''", m.Line(0)) } if m.CursorX() != 0 { t.Errorf("CursorX() = %d, want 0", m.CursorX()) diff --git a/internal/editor/integration_motion_basic_test.go b/internal/editor/integration_motion_basic_test.go index edb4b5f..f5bf615 100644 --- a/internal/editor/integration_motion_basic_test.go +++ b/internal/editor/integration_motion_basic_test.go @@ -12,8 +12,8 @@ func TestMoveDown(t *testing.T) { sendKeys(tm, "j") m := getFinalModel(t, tm) - if m.cursor.y != 1 { - t.Errorf("cursor.y = %d, want 1", m.cursor.y) + if m.CursorY() != 1 { + t.Errorf("cursor.y = %d, want 1", m.CursorY()) } }) @@ -22,8 +22,8 @@ func TestMoveDown(t *testing.T) { sendKeys(tm, "j", "j", "j", "j") m := getFinalModel(t, tm) - if m.cursor.y != 4 { - t.Errorf("cursor.y = %d, want 4", m.cursor.y) + if m.CursorY() != 4 { + t.Errorf("cursor.y = %d, want 4", m.CursorY()) } }) @@ -32,8 +32,8 @@ func TestMoveDown(t *testing.T) { sendKeys(tm, "j", "j", "j", "j", "j", "j", "j", "j", "j") m := getFinalModel(t, tm) - if m.cursor.y != 5 { - t.Errorf("cursor.y = %d, want 5", m.cursor.y) + if m.CursorY() != 5 { + t.Errorf("cursor.y = %d, want 5", m.CursorY()) } }) } @@ -44,8 +44,8 @@ func TestMoveDownWithCount(t *testing.T) { sendKeys(tm, "3", "j") m := getFinalModel(t, tm) - if m.cursor.y != 3 { - t.Errorf("cursor.y = %d, want 3", m.cursor.y) + if m.CursorY() != 3 { + t.Errorf("cursor.y = %d, want 3", m.CursorY()) } }) @@ -54,8 +54,8 @@ func TestMoveDownWithCount(t *testing.T) { sendKeys(tm, "1", "0", "j") m := getFinalModel(t, tm) - if m.cursor.y != 5 { - t.Errorf("cursor.y = %d, want 5", m.cursor.y) + if m.CursorY() != 5 { + t.Errorf("cursor.y = %d, want 5", m.CursorY()) } }) } @@ -69,8 +69,8 @@ func TestMoveDownWithOverflow(t *testing.T) { m := getFinalModel(t, tm) want := len(lines[1]) - if m.cursor.x != want { - t.Errorf("cursor.x = %d, want %d", m.cursor.x, want) + if m.CursorX() != want { + t.Errorf("cursor.x = %d, want %d", m.CursorX(), want) } }) @@ -79,8 +79,8 @@ func TestMoveDownWithOverflow(t *testing.T) { sendKeys(tm, "j") m := getFinalModel(t, tm) - if m.cursor.x != 3 { - t.Errorf("cursor.x = %d, want 3", m.cursor.x) + if m.CursorX() != 3 { + t.Errorf("cursor.x = %d, want 3", m.CursorX()) } }) } @@ -91,8 +91,8 @@ func TestMoveUp(t *testing.T) { sendKeys(tm, "k") m := getFinalModel(t, tm) - if m.cursor.y != 1 { - t.Errorf("cursor.y = %d, want 1", m.cursor.y) + if m.CursorY() != 1 { + t.Errorf("cursor.y = %d, want 1", m.CursorY()) } }) @@ -101,8 +101,8 @@ func TestMoveUp(t *testing.T) { sendKeys(tm, "k", "k", "k", "k") m := getFinalModel(t, tm) - if m.cursor.y != 0 { - t.Errorf("cursor.y = %d, want 0", m.cursor.y) + if m.CursorY() != 0 { + t.Errorf("cursor.y = %d, want 0", m.CursorY()) } }) @@ -111,8 +111,8 @@ func TestMoveUp(t *testing.T) { sendKeys(tm, "k", "k", "k") m := getFinalModel(t, tm) - if m.cursor.y != 0 { - t.Errorf("cursor.y = %d, want 0", m.cursor.y) + if m.CursorY() != 0 { + t.Errorf("cursor.y = %d, want 0", m.CursorY()) } }) } @@ -123,8 +123,8 @@ func TestMoveUpWithCount(t *testing.T) { sendKeys(tm, "3", "k") m := getFinalModel(t, tm) - if m.cursor.y != 2 { - t.Errorf("cursor.y = %d, want 2", m.cursor.y) + if m.CursorY() != 2 { + t.Errorf("cursor.y = %d, want 2", m.CursorY()) } }) @@ -133,8 +133,8 @@ func TestMoveUpWithCount(t *testing.T) { sendKeys(tm, "1", "0", "k") m := getFinalModel(t, tm) - if m.cursor.y != 0 { - t.Errorf("cursor.y = %d, want 0", m.cursor.y) + if m.CursorY() != 0 { + t.Errorf("cursor.y = %d, want 0", m.CursorY()) } }) } @@ -148,8 +148,8 @@ func TestMoveUpWithOverflow(t *testing.T) { m := getFinalModel(t, tm) want := len(lines[0]) - if m.cursor.x != want { - t.Errorf("cursor.x = %d, want %d", m.cursor.x, want) + if m.CursorX() != want { + t.Errorf("cursor.x = %d, want %d", m.CursorX(), want) } }) @@ -158,8 +158,8 @@ func TestMoveUpWithOverflow(t *testing.T) { sendKeys(tm, "k") m := getFinalModel(t, tm) - if m.cursor.x != 3 { - t.Errorf("cursor.x = %d, want 3", m.cursor.x) + if m.CursorX() != 3 { + t.Errorf("cursor.x = %d, want 3", m.CursorX()) } }) } @@ -170,8 +170,8 @@ func TestMoveRight(t *testing.T) { sendKeys(tm, "l") m := getFinalModel(t, tm) - if m.cursor.x != 1 { - t.Errorf("cursor.x = %d, want 1", m.cursor.x) + if m.CursorX() != 1 { + t.Errorf("cursor.x = %d, want 1", m.CursorX()) } }) @@ -180,8 +180,8 @@ func TestMoveRight(t *testing.T) { sendKeys(tm, "l", "l", "l", "l") m := getFinalModel(t, tm) - if m.cursor.x != 4 { - t.Errorf("cursor.x = %d, want 4", m.cursor.x) + if m.CursorX() != 4 { + t.Errorf("cursor.x = %d, want 4", m.CursorX()) } }) @@ -192,8 +192,8 @@ func TestMoveRight(t *testing.T) { m := getFinalModel(t, tm) want := len(lines[0]) - if m.cursor.x != want { - t.Errorf("cursor.x = %d, want %d", m.cursor.x, want) + if m.CursorX() != want { + t.Errorf("cursor.x = %d, want %d", m.CursorX(), want) } }) } @@ -204,8 +204,8 @@ func TestMoveRightWithCount(t *testing.T) { sendKeys(tm, "3", "l") m := getFinalModel(t, tm) - if m.cursor.x != 3 { - t.Errorf("cursor.x = %d, want 3", m.cursor.x) + if m.CursorX() != 3 { + t.Errorf("cursor.x = %d, want 3", m.CursorX()) } }) @@ -216,8 +216,8 @@ func TestMoveRightWithCount(t *testing.T) { m := getFinalModel(t, tm) want := len(lines[0]) - if m.cursor.x != want { - t.Errorf("cursor.x = %d, want %d", m.cursor.x, want) + if m.CursorX() != want { + t.Errorf("cursor.x = %d, want %d", m.CursorX(), want) } }) } @@ -228,8 +228,8 @@ func TestMoveLeft(t *testing.T) { sendKeys(tm, "h") m := getFinalModel(t, tm) - if m.cursor.x != 2 { - t.Errorf("cursor.x = %d, want 2", m.cursor.x) + if m.CursorX() != 2 { + t.Errorf("cursor.x = %d, want 2", m.CursorX()) } }) @@ -238,8 +238,8 @@ func TestMoveLeft(t *testing.T) { sendKeys(tm, "h", "h", "h", "h") m := getFinalModel(t, tm) - if m.cursor.x != 0 { - t.Errorf("cursor.x = %d, want 0", m.cursor.x) + if m.CursorX() != 0 { + t.Errorf("cursor.x = %d, want 0", m.CursorX()) } }) @@ -248,8 +248,8 @@ func TestMoveLeft(t *testing.T) { sendKeys(tm, "h", "h", "h") m := getFinalModel(t, tm) - if m.cursor.x != 0 { - t.Errorf("cursor.x = %d, want 0", m.cursor.x) + if m.CursorX() != 0 { + t.Errorf("cursor.x = %d, want 0", m.CursorX()) } }) } @@ -260,8 +260,8 @@ func TestMoveLeftWithCount(t *testing.T) { sendKeys(tm, "3", "h") m := getFinalModel(t, tm) - if m.cursor.x != 2 { - t.Errorf("cursor.x = %d, want 2", m.cursor.x) + if m.CursorX() != 2 { + t.Errorf("cursor.x = %d, want 2", m.CursorX()) } }) @@ -270,8 +270,8 @@ func TestMoveLeftWithCount(t *testing.T) { sendKeys(tm, "1", "0", "h") m := getFinalModel(t, tm) - if m.cursor.x != 0 { - t.Errorf("cursor.x = %d, want 0", m.cursor.x) + if m.CursorX() != 0 { + t.Errorf("cursor.x = %d, want 0", m.CursorX()) } }) } diff --git a/internal/editor/model.go b/internal/editor/model.go index 85331ed..081d4dd 100644 --- a/internal/editor/model.go +++ b/internal/editor/model.go @@ -9,20 +9,19 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -type cursor struct { - x int - y int -} - type Model struct { - lines []string - cursor cursor - anchor cursor // starting point for visual modes - scrollY int - mode action.Mode - win_h int - win_w int - input *input.Handler + // lines []string + // cursor cursor + // anchor cursor // starting point for visual modes + // scrollY int + mode action.Mode + win_h int + win_w int + + activeWindow int + windows []*action.Window + + input *input.Handler // Insert repetition insertCount int @@ -42,20 +41,30 @@ type Model struct { registers map[rune]action.Register // name -> register } -func NewModel(lines []string, pos action.Position) Model { - return Model{ - lines: lines, - cursor: cursor{ - x: pos.Col, - y: pos.Line, - }, - scrollY: 0, +func NewModel(lines []string, pos action.Position) *Model { + m := Model{ + // lines: lines, + // cursor: cursor{ + // x: pos.Col, + // y: pos.Line, + // }, + // scrollY: 0, mode: action.NormalMode, command: "", input: input.NewHandler(), settings: action.NewDefaultSettings(), registers: action.DefaultRegisters(), + + windows: []*action.Window{}, } + + // Temporary + win := action.NewEmptyWindow(lines, 0, 0) + win.Cursor = pos // Set initial cursor position + m.windows = append(m.windows, win) + m.activeWindow = win.Id + + return &m } func (m Model) Init() tea.Cmd { @@ -65,73 +74,74 @@ func (m Model) Init() tea.Cmd { // Implement action.Model interface func (m *Model) Lines() []string { - return m.lines + win := m.ActiveWindow() + return win.Buffer.Lines } func (m *Model) Line(idx int) string { - if idx < 0 || idx >= len(m.lines) { - return "" - } - return m.lines[idx] + win := m.ActiveWindow() + return win.Buffer.Line(idx) } func (m *Model) SetLine(idx int, content string) { - if idx >= 0 && idx < len(m.lines) { - m.lines[idx] = content - } + win := m.ActiveWindow() + win.Buffer.SetLine(idx, content) } func (m *Model) InsertLine(idx int, content string) { - if idx < 0 { - idx = 0 - } - if idx > len(m.lines) { - idx = len(m.lines) - } - m.lines = append(m.lines[:idx], append([]string{content}, m.lines[idx:]...)...) + win := m.ActiveWindow() + win.Buffer.InsertLine(idx, content) } func (m *Model) DeleteLine(idx int) { - if idx >= 0 && idx < len(m.lines) { - m.lines = append(m.lines[:idx], m.lines[idx+1:]...) - } + win := m.ActiveWindow() + win.Buffer.DeleteLine(idx) } func (m *Model) LineCount() int { - return len(m.lines) + win := m.ActiveWindow() + return win.Buffer.LineCount() } func (m *Model) CursorX() int { - return m.cursor.x + win := m.ActiveWindow() + return win.Cursor.Col } func (m *Model) CursorY() int { - return m.cursor.y + win := m.ActiveWindow() + return win.Cursor.Line } func (m *Model) SetCursorX(x int) { - m.cursor.x = x + win := m.ActiveWindow() + win.Cursor.Col = x } func (m *Model) SetCursorY(y int) { - m.cursor.y = y + win := m.ActiveWindow() + win.Cursor.Line = y } // Anchor methods func (m *Model) AnchorX() int { - return m.anchor.x + win := m.ActiveWindow() + return win.Anchor.Col } func (m *Model) AnchorY() int { - return m.anchor.y + win := m.ActiveWindow() + return win.Anchor.Line } func (m *Model) SetAnchorX(x int) { - m.anchor.x = x + win := m.ActiveWindow() + win.Anchor.Col = x } func (m *Model) SetAnchorY(y int) { - m.anchor.y = y + win := m.ActiveWindow() + win.Anchor.Line = y } // Insert methods @@ -226,32 +236,33 @@ func (m *Model) UpdateDefaultRegister(t action.RegisterType, cnt []string) { // Window func (m *Model) ScrollY() int { - return m.scrollY + win := m.ActiveWindow() + return win.ScrollY } func (m *Model) SetScrollY(y int) { - m.scrollY = y + win := m.ActiveWindow() + win.ScrollY = y } func (m *Model) WinH() int { - return m.win_h + win := m.ActiveWindow() + return win.Height } func (m *Model) WinW() int { - return m.win_w + win := m.ActiveWindow() + return win.Width } func (m *Model) ViewPortH() int { - return m.win_h - 2 // -2 for status bar and commmand bar + win := m.ActiveWindow() + return win.Height - 2 } func (m *Model) ClampCursorX() { - lineLen := len(m.lines[m.cursor.y]) - if lineLen == 0 { - m.cursor.x = 0 - } else if m.cursor.x >= lineLen { - m.cursor.x = lineLen - } + win := m.ActiveWindow() + win.ClampCursorX() } // AdjustScroll ensures the cursor stays within the viewport with scrollOff margins. @@ -280,6 +291,25 @@ func (m *Model) AdjustScroll() { m.SetScrollY(max(0, min(m.ScrollY(), maxScroll))) } +// Windows +func (m *Model) Windows() []*action.Window { + return m.windows +} + +func (m *Model) ActiveWindowId() int { + return m.activeWindow +} + +func (m *Model) ActiveWindow() *action.Window { + winId := m.ActiveWindowId() + for i := range m.Windows() { + if m.windows[i].Id == winId { + return m.windows[i] + } + } + panic("Could not find window") +} + func (m *Model) Mode() action.Mode { return m.mode } @@ -294,24 +324,29 @@ func (m *Model) SetInsertRecording(count int, act action.Action) { m.insertAction = act } -func (m *Model) GetCursorPosition() action.Position { - return action.Position{Line: m.cursor.y, Col: m.cursor.x} +func (m *Model) GetCursorPosition() *action.Position { + // Return a copy of the position + win := m.ActiveWindow() + pos := win.Cursor + return &pos } func (m *Model) replayInsert() { + win := m.ActiveWindow() + // Replay (count - 1) more times for i := 1; i < m.insertCount; i++ { // For 'o' and 'O', we need to create a new line first switch m.insertAction.(type) { case action.OpenLineBelow: - pos := m.cursor.y - m.lines = append(m.lines[:pos+1], append([]string{""}, m.lines[pos+1:]...)...) - m.cursor.y++ - m.cursor.x = 0 + pos := win.Cursor.Line + win.Buffer.Lines = append(win.Buffer.Lines[:pos+1], append([]string{""}, win.Buffer.Lines[pos+1:]...)...) + win.Cursor.Line++ + win.Cursor.Col = 0 case action.OpenLineAbove: - pos := m.cursor.y - m.lines = append(m.lines[:pos], append([]string{""}, m.lines[pos:]...)...) - m.cursor.x = 0 + pos := win.Cursor.Line + win.Buffer.Lines = append(win.Buffer.Lines[:pos], append([]string{""}, win.Buffer.Lines[pos:]...)...) + win.Cursor.Col = 0 // 'i' and 'a' don't need setup - just replay keys } @@ -323,11 +358,12 @@ func (m *Model) replayInsert() { } func (m *Model) ExitInsertMode() { + win := m.ActiveWindow() if m.insertCount > 1 { m.replayInsert() } - if m.cursor.x > 0 { - m.cursor.x-- + if win.Cursor.Col > 0 { + win.Cursor.Col-- } m.mode = action.NormalMode m.insertCount = 0 diff --git a/internal/editor/update.go b/internal/editor/update.go index 936fd1c..22dcf07 100644 --- a/internal/editor/update.go +++ b/internal/editor/update.go @@ -4,7 +4,7 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { @@ -13,13 +13,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.win_h = msg.Height m.win_w = msg.Width + // TODO: Temp, this is lame + for i := range m.windows { + m.windows[i].Height = msg.Height + m.windows[i].Width = msg.Width + } + case tea.KeyMsg: // TODO: This needs to be removed, but for now its required for the tests. // Ctrl+C always quits regardless of mode if msg.Type == tea.KeyCtrlC { return m, tea.Quit } - cmd = m.input.Handle(&m, msg.String()) + cmd = m.input.Handle(m, msg.String()) } // Keep cursor in view after any update diff --git a/internal/input/handler.go b/internal/input/handler.go index caa8c52..f94323b 100644 --- a/internal/input/handler.go +++ b/internal/input/handler.go @@ -16,7 +16,7 @@ const ( // PositionGetter is used to get cursor position for operator ranges type PositionGetter interface { - GetCursorPosition() action.Position + GetCursorPosition() *action.Position } type Handler struct { @@ -205,7 +205,7 @@ func (h *Handler) handleAfterOperator(m action.Model, kind string, binding any, start := pg.GetCursorPosition() mot.Execute(m) end := pg.GetCursorPosition() - cmd := h.operator.Operate(m, start, end, mot.Type()) + cmd := h.operator.Operate(m, *start, *end, mot.Type()) h.Reset() return cmd }