200 lines
5.4 KiB
Go
200 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})
|
|
|
|
win := m.ActiveWindow()
|
|
win.SetCursorCol(startX)
|
|
win.SetCursorLine(start.Line)
|
|
|
|
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)
|
|
}
|