import type { PromptBuffer } from "./buffer.js"; import { segmentPromptText } from "./segmentation.js"; import { replaceRange, type TextRange } from "./transactions.js"; export type ViRegister = { readonly text: string; readonly linewise: boolean; }; export type OperatorResult = { readonly buffer: PromptBuffer; readonly register: ViRegister | undefined; readonly changed: boolean; }; function rangesEqual(range: TextRange): boolean { return ( range.start.line === range.end.line && range.start.column === range.end.column ); } function readRange(buffer: PromptBuffer, range: TextRange): string { const first = buffer.lines[range.start.line]; const last = buffer.lines[range.end.line]; if (first === undefined || last === undefined) throw new RangeError("Operator range is outside the prompt"); if (range.start.line === range.end.line) return first.slice(range.start.column, range.end.column); return [ first.slice(range.start.column), ...buffer.lines.slice(range.start.line + 1, range.end.line), last.slice(0, range.end.column), ].join("\n"); } export function deleteOperatorRange( buffer: PromptBuffer, range: TextRange, previousRegister?: ViRegister, options: { linewise?: boolean } = {}, ): OperatorResult { if (rangesEqual(range)) return { buffer, register: previousRegister, changed: false }; const register = { text: readRange(buffer, range), linewise: options.linewise ?? false, }; return { buffer: replaceRange(buffer, { ...range, text: "" }), register, changed: true, }; } export function yankOperatorRange( buffer: PromptBuffer, range: TextRange, options: { linewise?: boolean } = {}, ): OperatorResult { if (rangesEqual(range)) return { buffer, register: undefined, changed: false }; return { buffer, register: { text: readRange(buffer, range), linewise: options.linewise ?? false, }, changed: false, }; } export function putRegister( buffer: PromptBuffer, register: ViRegister, placement: "before" | "after", ): PromptBuffer { if (register.text.length === 0) return buffer; if (register.linewise) { const insertionLine = buffer.cursor.line + (placement === "after" ? 1 : 0); const inserted = register.text.split("\n"); return { ...buffer, lines: [ ...buffer.lines.slice(0, insertionLine), ...inserted, ...buffer.lines.slice(insertionLine), ], cursor: { line: insertionLine, column: 0 }, }; } const line = buffer.lines[buffer.cursor.line] ?? ""; const segment = segmentPromptText(line, buffer.pasteMarkerIds).find( (candidate) => candidate.start === buffer.cursor.column, ); const column = placement === "before" ? buffer.cursor.column : (segment?.end ?? buffer.cursor.column); return replaceRange(buffer, { start: { line: buffer.cursor.line, column }, end: { line: buffer.cursor.line, column }, text: register.text, }); }