Implemented counts for insert elements.

This commit is contained in:
Hayden Hargreaves 2026-02-08 23:24:46 -07:00
parent 6c0c289b52
commit e7405a8d19
4 changed files with 173 additions and 86 deletions

View File

@ -24,6 +24,11 @@ type model struct {
win_w int
command string
input *InputHandler
// Insert repetition
insertCount int
insertKeys []string
insertAction Action
}
func newModel() model {
@ -63,3 +68,76 @@ func (m *model) clampCursorX() {
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)
}
}

View File

@ -69,22 +69,45 @@ func (a MoveRight) WithCount(n int) Action {
return MoveRight{count: n}
}
type EnterInsert struct{}
type EnterInsert struct {
count int
}
func (a EnterInsert) Execute(m *model) tea.Cmd {
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
type EnterInsertAfter struct{}
func (a EnterInsert) WithCount(n int) Action {
return EnterInsert{count: n}
}
type EnterInsertAfter struct {
count int
}
func (a EnterInsertAfter) Execute(m *model) tea.Cmd {
m.cursor.x++
m.clampCursorX()
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
func (a EnterInsertAfter) WithCount(n int) Action {
return EnterInsertAfter{count: n}
}
type Quit struct{}
func (a Quit) Execute(m *model) tea.Cmd {
@ -109,24 +132,48 @@ func (a MoveToBottom) Execute(m *model) tea.Cmd {
return nil
}
type EnterInsertLineStart struct{}
type EnterInsertLineStart struct {
count int
}
func (a EnterInsertLineStart) Execute(m *model) tea.Cmd {
m.cursor.x = 0
m.clampCursorX()
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
type EnterInsertLineEnd struct{}
func (a EnterInsertLineStart) WithCount(n int) Action {
return EnterInsertLineStart{count: n}
}
type EnterInsertLineEnd struct {
count int
}
func (a EnterInsertLineEnd) Execute(m *model) tea.Cmd {
m.cursor.x = len(m.lines[m.cursor.y])
m.clampCursorX()
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
func (a EnterInsertLineEnd) WithCount(n int) Action {
return EnterInsertLineEnd{count: n}
}
type MoveToLineStart struct{}
func (a MoveToLineStart) Execute(m *model) tea.Cmd {
@ -144,7 +191,9 @@ func (a MoveToLineEnd) Execute(m *model) tea.Cmd {
}
// TODO: Count
type OpenLineBelow struct{}
type OpenLineBelow struct {
count int
}
func (a OpenLineBelow) Execute(m *model) tea.Cmd {
pos := m.cursor.y
@ -156,13 +205,24 @@ func (a OpenLineBelow) Execute(m *model) tea.Cmd {
}
m.cursor.y++
m.clampCursorX()
m.cursor.x = 0
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
// TODO: Count
type OpenLineAbove struct{}
func (a OpenLineBelow) WithCount(n int) Action {
return OpenLineBelow{count: n}
}
type OpenLineAbove struct {
count int
}
func (a OpenLineAbove) Execute(m *model) tea.Cmd {
pos := m.cursor.y
@ -173,11 +233,21 @@ func (a OpenLineAbove) Execute(m *model) tea.Cmd {
m.lines = append(m.lines[:pos], append([]string{""}, m.lines[pos:]...)...)
}
m.clampCursorX()
m.cursor.x = 0
// Start recording
m.insertCount = a.count
m.insertKeys = []string{}
m.insertAction = a
m.mode = InsertMode
return nil
}
func (a OpenLineAbove) WithCount(n int) Action {
return OpenLineAbove{count: n}
}
// TODO: Visual mode
type DeleteChar struct {
count int

View File

@ -14,92 +14,31 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case NormalMode:
return m, m.input.Handle(&m, msg.String())
// TODO: This should be handled elsewhere
case InsertMode:
switch msg.String() {
case "ctrl+c", "ctrl+d":
return m, tea.Quit
key := msg.String()
switch key {
case "esc":
if m.insertCount > 1 {
m.replayInsert()
}
// Allow i to step back, but a to stay put
if m.cursor.x > 0 {
m.cursor.x--
}
m.mode = NormalMode
m.insertCount = 0
m.insertKeys = nil
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 m.cursor.x > 0 {
m.lines[y] = l[:x-1] + l[x:]
m.cursor.x--
} else if m.cursor.y > 0 {
new_x := 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 = new_x
}
case "left":
if m.cursor.x > 0 {
m.cursor.x--
}
case "right":
if m.cursor.x < len(m.lines[m.cursor.y]) {
m.cursor.x++
}
case "up":
if m.cursor.y > 0 {
m.cursor.y--
}
if m.cursor.x > len(m.lines[m.cursor.y])-1 {
m.cursor.x = len(m.lines[m.cursor.y])
}
case "down":
if m.cursor.y < len(m.lines)-1 {
m.cursor.y++
}
if m.cursor.x > len(m.lines[m.cursor.y])-1 {
m.cursor.x = len(m.lines[m.cursor.y])
}
case "ctrl+c", "ctrl+d":
return m, tea.Quit
default:
x := m.cursor.x
y := m.cursor.y
l := m.lines[y]
ch := msg.String()
if x < len(l) {
m.lines[y] = l[:x] + ch + l[x:]
} else {
m.lines[y] = l + ch
}
m.cursor.x += len(ch)
// Record and process
m.insertKeys = append(m.insertKeys, key)
m.processInsertKey(key)
}
case CommandMode:
switch msg.String() {

View File

@ -69,9 +69,9 @@ func (m model) View() string {
var bar string
if m.mode == CommandMode {
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) %s | %s", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.command, m.input.buffer)
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) %s ", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.command)
} else {
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) | %s", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.input.buffer)
bar = fmt.Sprintf(" %6s | %d:%d (%d:%d) | %s | %+v | %d", modeString, m.cursor.x, m.cursor.y, m.win_w, m.win_h, m.input.buffer, m.insertKeys, m.insertCount)
}
view.WriteString(bar)