wip: first initial commit. Not sure if I like this

This commit is contained in:
Hayden Hargreaves 2026-02-26 12:14:59 -07:00
parent 65f96a5089
commit 3339dd4409
13 changed files with 481 additions and 294 deletions

View File

@ -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)
}
}

80
internal/action/buffer.go Normal file
View File

@ -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)
}

View File

@ -21,6 +21,11 @@ type Model interface {
SetCursorY(y int)
ClampCursorX()
// Windows
Windows() []*Window
ActiveWindowId() int
ActiveWindow() *Window
// Window
ScrollY() int
SetScrollY(y int)

56
internal/action/window.go Normal file
View File

@ -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
}
}

BIN
internal/editor/gim Executable file

Binary file not shown.

View File

@ -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()))
}
})

View File

@ -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)
}

View File

@ -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))
}
})
}

View File

@ -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())

View File

@ -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())
}
})
}

View File

@ -9,19 +9,18 @@ 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
// 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
@ -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

View File

@ -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

View File

@ -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
}