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 { win := m.ActiveWindow() 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.")) } win.SetCursorCol(start.Col) win.SetCursorLine(start.Line) return nil } // Verify YankOperator implements DoublePresser var _ action.DoublePresser = YankOperator{} 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(action.LinewiseRegister, lines) return nil } func yankNormalMode(m action.Model, start, end action.Position, mtype action.MotionType) { buf := m.ActiveBuffer() 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 := buf.Lines[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.UpdateDefaultRegister(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 := buf.Lines[startY : endY+1] m.UpdateDefaultRegister(action.LinewiseRegister, cnt) } } func yankVisualMode(m action.Model, start, end action.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(action.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(action.CharwiseRegister, content) } func yankVisualLineMode(m action.Model, start, end action.Position) { buf := m.ActiveBuffer() // 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 := buf.Lines[startY : endY+1] m.UpdateDefaultRegister(action.LinewiseRegister, cnt) } func yankVisualBlockMode(m action.Model, start, end action.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(action.BlockwiseRegister, content) }