2026-02-21 21:31:31 -07:00

170 lines
4.4 KiB
Go

package operator
import (
"fmt"
"git.gophernest.net/azpect/TextEditor/internal/action"
tea "github.com/charmbracelet/bubbletea"
)
// Implements Operator (y)
type YankOperator struct{}
func (o YankOperator) Operate(m action.Model, start, end action.Position, mtype action.MotionType) tea.Cmd {
switch m.Mode() {
case action.VisualMode:
yankVisualMode(m, start, end)
case action.VisualLineMode:
yankVisualLineMode(m, start, end)
case action.VisualBlockMode:
yankVisualBlockMode(m, start, end)
case action.NormalMode:
yankNormalMode(m, start, end, mtype)
default:
m.SetCommandError(fmt.Errorf("'y' operator not yet implemented."))
}
m.SetCursorX(start.Col)
m.SetCursorY(start.Line)
return nil
}
// Verify YankOperator implements DoublePresser
var _ action.DoublePresser = YankOperator{}
func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
y := m.CursorY()
// If we have a higher value than lines remaining, we can only run so many times
opCount := min(count, m.LineCount()-y)
var lines []string
for i := range opCount {
lines = append(lines, m.Line(y+i))
}
// Put her in the register!
m.UpdateDefault(action.LinewiseRegister, lines)
return nil
}
func yankNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
switch {
case mtype.IsCharwise():
// This shouldn't happen
if start.Line != end.Line {
m.SetCommandError(fmt.Errorf("Start line and end line must match for charwise yank operations."))
return
}
line := m.Line(start.Line)
startX := min(start.Col, end.Col)
endX := max(start.Col, end.Col)
// Inclusive motions include the end character
if mtype == action.CharwiseInclusive {
endX++
}
endX = min(endX, len(line)) // Catch overflow
cnt := line[startX:endX]
m.UpdateDefault(action.CharwiseRegister, []string{cnt})
case mtype == action.Linewise:
// This shouldn't happen
if start.Col != end.Col {
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
return
}
// These don't need to be validated, they are validated before being passed into the function
startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line)
cnt := m.Lines()[startY : endY+1]
m.UpdateDefault(action.LinewiseRegister, cnt)
}
}
func yankVisualMode(m action.Model, start, end action.Position) {
// Normalize so start is before end
if start.Line > end.Line || (start.Line == end.Line && start.Col > end.Col) {
start, end = end, start
}
// Single line selection
if start.Line == end.Line {
line := m.Line(start.Line)
endCol := min(end.Col+1, len(line)) // +1 because visual selection is inclusive
startCol := min(start.Col, len(line))
cnt := line[startCol:endCol]
m.UpdateDefault(action.CharwiseRegister, []string{cnt})
return
}
// Multi-line selection
var content []string
// First line: from start.Col to end of line
firstLine := m.Line(start.Line)
startCol := min(start.Col, len(firstLine))
content = append(content, firstLine[startCol:])
// Middle lines: entire lines
for y := start.Line + 1; y < end.Line; y++ {
content = append(content, m.Line(y))
}
// Last line: from beginning to end.Col (inclusive)
lastLine := m.Line(end.Line)
endCol := min(end.Col+1, len(lastLine))
content = append(content, lastLine[:endCol])
m.UpdateDefault(action.CharwiseRegister, content)
}
func yankVisualLineMode(m action.Model, start, end action.Position) {
// This shouldn't happen
if start.Col != end.Col {
m.SetCommandError(fmt.Errorf("Start column and end column must match for linewise yank operations."))
return
}
// These don't need to be validated, they are validated before being passed into the function
startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line)
cnt := m.Lines()[startY : endY+1]
m.UpdateDefault(action.LinewiseRegister, cnt)
}
func yankVisualBlockMode(m action.Model, start, end action.Position) {
// Normalize so startY <= endY and startX <= endX
startY := min(start.Line, end.Line)
endY := max(start.Line, end.Line)
startX := min(start.Col, end.Col)
endX := max(start.Col, end.Col) + 1 // +1 for inclusive
var content []string
for y := startY; y <= endY; y++ {
line := m.Line(y)
// Handle lines shorter than the block selection
if startX >= len(line) {
content = append(content, "")
continue
}
lineEndX := min(endX, len(line))
content = append(content, line[startX:lineEndX])
}
m.UpdateDefault(action.BlockwiseRegister, content)
}