Gim/internal/action/replace.go
Hayden Hargreaves 32fe3f1edd
All checks were successful
Run Test Suite / test (push) Successful in 18s
fix: fixed replace action (r) in visual mode, tested
2026-04-06 21:14:56 -07:00

214 lines
4.3 KiB
Go

package action
import (
"strings"
"git.gophernest.net/azpect/TextEditor/internal/core"
tea "github.com/charmbracelet/bubbletea"
)
type ReplaceChar struct {
Char string
Count int
}
func (m ReplaceChar) WithChar(char string) Motion {
m.Char = char
return m
}
func (m ReplaceChar) Type() core.MotionType {
return core.CharwiseInclusive
}
// WithCount sets the count (required by Repeatable interface)
func (m ReplaceChar) WithCount(n int) Action {
m.Count = n
return m
}
func (a ReplaceChar) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
if buf.UndoStack != nil && !buf.UndoStack.Recording() {
buf.UndoStack.BeginBlock(win.Cursor)
}
switch m.Mode() {
case core.VisualMode:
replaceVisualCharSelection(m, a.Char)
case core.VisualLineMode:
replaceVisualLineSelection(m, a.Char)
case core.VisualBlockMode:
replaceVisualBlockSelection(m, a.Char)
default:
pos := win.Cursor.Col
line := buf.Line(win.Cursor.Line)
for i := 0; i < a.Count && pos < len(line); i++ {
line = line[:pos] + a.Char + line[pos+1:]
buf.SetLine(win.Cursor.Line, line)
pos++
}
win.SetCursorCol(pos - 1)
}
m.SetMode(core.NormalMode)
if buf.UndoStack != nil {
buf.UndoStack.EndBlock(win.Cursor)
}
return nil
}
func replaceVisualCharSelection(m Model, char string) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
start, end := normalizeSelection(m)
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
if len(line) == 0 {
continue
}
from := 0
to := len(line) - 1
if y == start.Line {
from = start.Col
}
if y == end.Line {
to = min(end.Col, len(line)-1)
}
if from < 0 {
from = 0
}
if from >= len(line) || to < from {
continue
}
replaced := strings.Repeat(char, to-from+1)
buf.SetLine(y, line[:from]+replaced+line[to+1:])
}
win.SetCursorPos(start.Line, start.Col)
}
func replaceVisualLineSelection(m Model, char string) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
start, end := normalizeSelection(m)
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
if len(line) == 0 {
continue
}
buf.SetLine(y, strings.Repeat(char, len(line)))
}
win.SetCursorPos(start.Line, 0)
}
func replaceVisualBlockSelection(m Model, char string) {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
start, end := normalizeSelection(m)
startCol := min(start.Col, end.Col)
endCol := max(start.Col, end.Col)
for y := start.Line; y <= end.Line; y++ {
line := buf.Line(y)
if startCol >= len(line) {
continue
}
ec := min(endCol, len(line)-1)
replaced := strings.Repeat(char, ec-startCol+1)
buf.SetLine(y, line[:startCol]+replaced+line[ec+1:])
}
win.SetCursorPos(start.Line, startCol)
}
type EnterReplace struct {
Count int
}
func (a EnterReplace) WithCount(n int) Action {
a.Count = n
return a
}
func (a EnterReplace) Execute(m Model) tea.Cmd {
m.SetMode(core.ReplaceMode)
return nil
}
type ReplaceModeChar struct {
Char string
}
// ReplaceModeChar.Execute: Inserts a single character at the cursor position, overwriting current
// character.
func (a ReplaceModeChar) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
if x < len(l) {
buf.SetLine(y, l[:x]+a.Char+l[x+1:])
} else {
buf.SetLine(y, l+a.Char)
}
win.SetCursorCol(x + len(a.Char))
return nil
}
// ReplaceNewline splits the current line at the cursor (enter key)
type ReplaceNewline struct{}
// ReplaceNewline.Execute: Splits the current line at the cursor (Enter key).
func (a ReplaceNewline) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
if x == len(l) {
buf.InsertLine(y+1, "")
} else {
buf.SetLine(y, l[:x])
buf.InsertLine(y+1, l[x+1:])
}
win.SetCursorPos(y+1, 0)
return nil
}
// ReplaceTab inserts spaces equal to the tab size
type ReplaceTab struct{}
// ReplaceTab.Execute: Inserts spaces equal to the tab size (Tab key).
func (a ReplaceTab) Execute(m Model) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
x, y := win.Cursor.Col, win.Cursor.Line
l := buf.Line(y)
tabs := strings.Repeat(" ", m.Settings().TabStop)
if x < len(l) {
buf.SetLine(y, l[:x]+tabs+l[x+1:])
} else {
buf.SetLine(y, l+tabs)
}
win.SetCursorCol(x + len(tabs))
return nil
}