(state: VimState, op: "d" | "c" | "y")
| 1745 | } |
| 1746 | |
| 1747 | function applyVisualOperator(state: VimState, op: "d" | "c" | "y"): VimState { |
| 1748 | const range = getVisualRange(state); |
| 1749 | assert(range != null, "Expected visual range in visual mode"); |
| 1750 | |
| 1751 | const { text, yankBuffer } = state; |
| 1752 | const removed = text.slice(range.start, range.end); |
| 1753 | |
| 1754 | // Record visual edits for dot repeat. |
| 1755 | let visualLastEdit: LastEdit | null = null; |
| 1756 | if (op === "d" || op === "c") { |
| 1757 | if (range.kind === "char") { |
| 1758 | const charCount = range.end - range.start; |
| 1759 | if (charCount > 0) { |
| 1760 | visualLastEdit = { kind: "opVisual", op, rangeKind: "char", count: charCount }; |
| 1761 | } |
| 1762 | } else { |
| 1763 | assert(state.visualAnchor != null, "Expected visualAnchor in visual line mode"); |
| 1764 | const { row: anchorRow } = getRowCol(text, state.visualAnchor); |
| 1765 | const { row: cursorRow } = getRowCol(text, state.cursor); |
| 1766 | const lineCount = Math.abs(cursorRow - anchorRow) + 1; |
| 1767 | visualLastEdit = { kind: "opVisual", op, rangeKind: "line", count: lineCount }; |
| 1768 | } |
| 1769 | } |
| 1770 | |
| 1771 | switch (op) { |
| 1772 | case "d": { |
| 1773 | const result = deleteRange(text, range.start, range.end, true, yankBuffer); |
| 1774 | return completeOperation(state, { |
| 1775 | mode: "normal", |
| 1776 | text: result.text, |
| 1777 | cursor: result.cursor, |
| 1778 | yankBuffer: result.yankBuffer, |
| 1779 | visualAnchor: null, |
| 1780 | ...(visualLastEdit != null ? { lastEdit: visualLastEdit } : {}), |
| 1781 | }); |
| 1782 | } |
| 1783 | |
| 1784 | case "c": { |
| 1785 | if (range.kind === "line") { |
| 1786 | const yankText = removed.endsWith("\n") ? removed.slice(0, -1) : removed; |
| 1787 | |
| 1788 | const replacement = range.end < text.length ? "\n" : ""; |
| 1789 | const newText = text.slice(0, range.start) + replacement + text.slice(range.end); |
| 1790 | |
| 1791 | return completeOperation(state, { |
| 1792 | mode: "insert", |
| 1793 | text: newText, |
| 1794 | cursor: range.start, |
| 1795 | yankBuffer: yankText, |
| 1796 | visualAnchor: null, |
| 1797 | ...(visualLastEdit != null ? { lastEdit: visualLastEdit } : {}), |
| 1798 | }); |
| 1799 | } |
| 1800 | |
| 1801 | const result = changeRange(text, range.start, range.end, yankBuffer); |
| 1802 | return completeOperation(state, { |
| 1803 | mode: "insert", |
| 1804 | text: result.text, |
no test coverage detected