Most of the tests were just written poorly, the code was right. Though the yank related questions were actually broken.
196 lines
5.4 KiB
Go
196 lines
5.4 KiB
Go
package operator
|
|
|
|
import (
|
|
"git.gophernest.net/azpect/TextEditor/internal/action"
|
|
"git.gophernest.net/azpect/TextEditor/internal/core"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// YankOperator implements Operator (y) - copies text to register in various modes.
|
|
type YankOperator struct{}
|
|
|
|
// YankOperator.Operate: Copies text to register based on the current mode and motion type.
|
|
func (o YankOperator) Operate(m action.Model, start, end core.Position, mtype core.MotionType) tea.Cmd {
|
|
win := m.ActiveWindow()
|
|
|
|
switch m.Mode() {
|
|
case core.VisualMode:
|
|
yankVisualMode(m, start, end)
|
|
case core.VisualLineMode:
|
|
yankVisualLineMode(m, start, end)
|
|
case core.VisualBlockMode:
|
|
yankVisualBlockMode(m, start, end)
|
|
case core.NormalMode:
|
|
yankNormalMode(m, start, end, mtype)
|
|
default:
|
|
m.SetCommandOutput(&core.CommandOutput{
|
|
Lines: []string{"'y' operator not yet implemented."},
|
|
Inline: true,
|
|
IsError: true,
|
|
})
|
|
}
|
|
|
|
// Normalize so cursor is set to the earlier position (important for backward motions)
|
|
cursorPos := start
|
|
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
|
|
cursorPos = end
|
|
}
|
|
|
|
win.SetCursorCol(cursorPos.Col)
|
|
win.SetCursorLine(cursorPos.Line)
|
|
return nil
|
|
}
|
|
|
|
// Verify YankOperator implements DoublePresser
|
|
var _ action.DoublePresser = YankOperator{}
|
|
|
|
// YankOperator.DoublePress: Handles yy - copies Count entire lines to register.
|
|
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.Line(y+i))
|
|
}
|
|
|
|
// Put her in the register!
|
|
m.UpdateDefaultRegister(core.LinewiseRegister, lines)
|
|
|
|
return nil
|
|
}
|
|
|
|
// yankNormalMode: Copies text to register in normal mode based on motion type.
|
|
func yankNormalMode(m action.Model, start, end core.Position, mtype core.MotionType) {
|
|
buf := m.ActiveBuffer()
|
|
|
|
switch {
|
|
case mtype.IsCharwise():
|
|
line := buf.Line(start.Line)
|
|
|
|
startX := min(start.Col, end.Col)
|
|
endX := max(start.Col, end.Col)
|
|
|
|
// Inclusive motions include the end character
|
|
if mtype == core.CharwiseInclusive {
|
|
endX++
|
|
}
|
|
|
|
endX = min(endX, len(line)) // Catch overflow
|
|
|
|
cnt := line[startX:endX]
|
|
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
|
|
|
|
case mtype == core.Linewise:
|
|
// 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)
|
|
|
|
var cnt []string
|
|
for i := startY; i <= endY; i++ {
|
|
cnt = append(cnt, buf.Line(i))
|
|
}
|
|
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
|
}
|
|
}
|
|
|
|
// yankVisualMode: Copies character-wise visual selection to register.
|
|
func yankVisualMode(m action.Model, start, end core.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.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.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
|
|
return
|
|
}
|
|
|
|
// Multi-line selection
|
|
var content []string
|
|
|
|
// First line: from start.Col to end of line
|
|
firstLine := buf.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, buf.Line(y))
|
|
}
|
|
|
|
// Last line: from beginning to end.Col (inclusive)
|
|
lastLine := buf.Line(end.Line)
|
|
endCol := min(end.Col+1, len(lastLine))
|
|
content = append(content, lastLine[:endCol])
|
|
|
|
m.UpdateDefaultRegister(core.CharwiseRegister, content)
|
|
}
|
|
|
|
// yankVisualLineMode: Copies line-wise visual selection to register.
|
|
func yankVisualLineMode(m action.Model, start, end core.Position) {
|
|
buf := m.ActiveBuffer()
|
|
|
|
// This shouldn't happen
|
|
// if start.Col != end.Col {
|
|
// m.SetCommandOutput(&core.CommandOutput{
|
|
// Lines: []string{"Start column and end column must match for linewise yank operations."},
|
|
// Inline: true,
|
|
// IsError: true,
|
|
// })
|
|
// 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)
|
|
|
|
var cnt []string
|
|
for i := startY; i <= endY; i++ {
|
|
cnt = append(cnt, buf.Line(i))
|
|
}
|
|
m.UpdateDefaultRegister(core.LinewiseRegister, cnt)
|
|
|
|
}
|
|
|
|
// yankVisualBlockMode: Copies block-wise visual selection to register.
|
|
func yankVisualBlockMode(m action.Model, start, end core.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.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.UpdateDefaultRegister(core.BlockwiseRegister, content)
|
|
}
|