Hayden Hargreaves 10e37b82af
All checks were successful
Run Test Suite / test (push) Successful in 13s
feat: implemented the command window! Not tested. Maybe we need some?
2026-03-14 23:13:59 -07:00

204 lines
5.6 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,
})
}
win.SetCursorCol(start.Col)
win.SetCursorLine(start.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.Lines[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():
// This shouldn't happen
// if start.Line != end.Line {
// m.SetCommandOutput(&core.CommandOutput{
// Lines: []string{"Start line and end line must match for charwise yank operations."},
// Inline: true,
// IsError: true,
// })
// 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 == core.CharwiseInclusive {
endX++
}
endX = min(endX, len(line)) // Catch overflow
cnt := line[startX:endX]
m.UpdateDefaultRegister(core.CharwiseRegister, []string{cnt})
case mtype == core.Linewise:
// 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)
cnt := buf.Lines[startY : endY+1]
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.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(core.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(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)
cnt := buf.Lines[startY : endY+1]
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.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(core.BlockwiseRegister, content)
}