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) }