package main import tea "github.com/charmbracelet/bubbletea" type mode int const ( NormalMode mode = iota InsertMode CommandMode ) type cursor struct { x int y int } type model struct { lines []string cursor cursor s_gutter int mode mode win_h int win_w int command string input *InputHandler // Insert repetition insertCount int insertKeys []string insertAction Action } func newModel(lines []string) model { return model{ lines: lines, cursor: cursor{ x: 0, y: 0, }, s_gutter: 5, mode: NormalMode, command: "", input: NewInputHandler(), } } func newModelWithPos(lines []string, pos Position) model { return model{ lines: lines, cursor: cursor{ x: pos.Col, y: pos.Line, }, s_gutter: 5, mode: NormalMode, command: "", input: NewInputHandler(), } } func (m model) Init() tea.Cmd { return nil } 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 } } func (m model) getCursorPosition() Position { return Position{Line: m.cursor.y, Col: m.cursor.x} } func (m *model) replayInsert() { // 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 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 case OpenLineAbove: pos := m.cursor.y m.lines = append(m.lines[:pos], append([]string{""}, m.lines[pos:]...)...) m.cursor.x = 0 // 'i' and 'a' don't need setup - just replay keys } // Replay each recorded keystroke for _, key := range m.insertKeys { m.processInsertKey(key) } } } func (m *model) processInsertKey(key string) { switch key { case "enter": y := m.cursor.y x := m.cursor.x // Simple case, at end, just create a line if x == len(m.lines[y]) { m.lines = append(m.lines[:y+1], append([]string{""}, m.lines[y+1:]...)...) // otherwise, splice } else { l := m.lines[y] m.lines[y] = l[:x] m.lines = append(m.lines[:y+1], append([]string{l[x:]}, m.lines[y+1:]...)...) } m.cursor.y++ m.cursor.x = 0 case "backspace": x := m.cursor.x y := m.cursor.y l := m.lines[y] if x > 0 { m.lines[y] = l[:x-1] + l[x:] m.cursor.x-- } else if y > 0 { newX := len(m.lines[y-1]) m.lines[y-1] = m.lines[y-1] + l m.lines = append(m.lines[:y], m.lines[y+1:]...) m.cursor.y-- m.cursor.x = newX } default: // Regular character x := m.cursor.x y := m.cursor.y l := m.lines[y] if x < len(l) { m.lines[y] = l[:x] + key + l[x:] } else { m.lines[y] = l + key } m.cursor.x += len(key) } }