214 lines
4.3 KiB
Go
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
|
|
}
|