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