Hayden Hargreaves b1b3edf810 feat: this is huge, and needs some review, but for now its good
The tests are not passing, something to do with view I think.
2026-02-26 23:18:18 -07:00

183 lines
4.7 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 {
win := m.ActiveWindow()
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."))
}
win.SetCursorCol(start.Col)
win.SetCursorLine(start.Line)
return nil
}
// Verify YankOperator implements DoublePresser
var _ action.DoublePresser = YankOperator{}
func (o YankOperator) DoublePress(m action.Model, count int) tea.Cmd {
win := m.ActiveWindow()
buf := m.ActiveBuffer()
y := win.Cursor.Line
// If we have a higher value than lines remaining, we can only run so many times
opCount := min(count, buf.LineCount()-y)
var lines []string
for i := range opCount {
lines = append(lines, buf.Lines[y+i])
}
// Put her in the register!
m.UpdateDefaultRegister(action.LinewiseRegister, lines)
return nil
}
func yankNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) {
buf := m.ActiveBuffer()
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 := buf.Lines[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.UpdateDefaultRegister(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 := buf.Lines[startY : endY+1]
m.UpdateDefaultRegister(action.LinewiseRegister, cnt)
}
}
func yankVisualMode(m action.Model, start, end action.Position) {
buf := m.ActiveBuffer()
// 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 := buf.Lines[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.UpdateDefaultRegister(action.CharwiseRegister, []string{cnt})
return
}
// Multi-line selection
var content []string
// First line: from start.Col to end of line
firstLine := buf.Lines[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, buf.Lines[y])
}
// Last line: from beginning to end.Col (inclusive)
lastLine := buf.Lines[end.Line]
endCol := min(end.Col+1, len(lastLine))
content = append(content, lastLine[:endCol])
m.UpdateDefaultRegister(action.CharwiseRegister, content)
}
func yankVisualLineMode(m action.Model, start, end action.Position) {
buf := m.ActiveBuffer()
// 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 := buf.Lines[startY : endY+1]
m.UpdateDefaultRegister(action.LinewiseRegister, cnt)
}
func yankVisualBlockMode(m action.Model, start, end action.Position) {
buf := m.ActiveBuffer()
// 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 := buf.Lines[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.UpdateDefaultRegister(action.BlockwiseRegister, content)
}