( lines: readonly string[], cursor: CursorPosition, text: string, )
| 42 | * @returns Edit result with updated lines and cursor |
| 43 | */ |
| 44 | export function insertText( |
| 45 | lines: readonly string[], |
| 46 | cursor: CursorPosition, |
| 47 | text: string, |
| 48 | ): EditResult { |
| 49 | const newLines = [...lines]; |
| 50 | const { line, column } = cursor; |
| 51 | |
| 52 | // Ensure we have at least one line |
| 53 | if (newLines.length === 0) { |
| 54 | newLines.push(""); |
| 55 | } |
| 56 | |
| 57 | // Clamp cursor to valid position |
| 58 | const safeRow = Math.max(0, Math.min(line, newLines.length - 1)); |
| 59 | const currentLine = newLines[safeRow] ?? ""; |
| 60 | const safeCol = Math.max(0, Math.min(column, currentLine.length)); |
| 61 | |
| 62 | // Split text into lines |
| 63 | const insertLines = text.split("\n"); |
| 64 | const beforeCursor = currentLine.slice(0, safeCol); |
| 65 | const afterCursor = currentLine.slice(safeCol); |
| 66 | |
| 67 | if (insertLines.length === 1) { |
| 68 | // Single line insert |
| 69 | newLines[safeRow] = beforeCursor + insertLines[0] + afterCursor; |
| 70 | return Object.freeze({ |
| 71 | lines: Object.freeze(newLines), |
| 72 | cursor: { line: safeRow, column: safeCol + (insertLines[0]?.length ?? 0) }, |
| 73 | selection: null, |
| 74 | }); |
| 75 | } |
| 76 | |
| 77 | // Multi-line insert |
| 78 | const firstLine = beforeCursor + (insertLines[0] ?? ""); |
| 79 | const lastInsertLine = insertLines[insertLines.length - 1] ?? ""; |
| 80 | const lastLine = lastInsertLine + afterCursor; |
| 81 | |
| 82 | newLines.splice(safeRow, 1, firstLine, ...insertLines.slice(1, -1), lastLine); |
| 83 | |
| 84 | return Object.freeze({ |
| 85 | lines: Object.freeze(newLines), |
| 86 | cursor: { |
| 87 | line: safeRow + insertLines.length - 1, |
| 88 | column: lastInsertLine.length, |
| 89 | }, |
| 90 | selection: null, |
| 91 | }); |
| 92 | } |
| 93 | |
| 94 | /** |
| 95 | * Delete text in a selection range. |
no test coverage detected