import type { PromptBuffer } from "./buffer.js"; import { segmentPromptText } from "./segmentation.js"; export type ReadlineMode = "vi-insert" | "vi-command" | "vi-replace"; export type ReadlineState = { readonly mode: ReadlineMode; readonly buffer: PromptBuffer; }; export function initialReadlineState(buffer: PromptBuffer): ReadlineState { return { mode: "vi-insert", buffer }; } /** Match Readline's command-mode cursor placement after leaving insertion. */ export function enterViAppendMode(state: ReadlineState): ReadlineState { const line = state.buffer.lines[state.buffer.cursor.line]; const current = line === undefined ? undefined : segmentPromptText(line, state.buffer.pasteMarkerIds).find( (segment) => segment.start === state.buffer.cursor.column, ); return { mode: "vi-insert", buffer: { ...state.buffer, cursor: { line: state.buffer.cursor.line, column: current?.end ?? state.buffer.cursor.column, }, }, }; } export function enterViCommandMode(state: ReadlineState): ReadlineState { const line = state.buffer.lines[state.buffer.cursor.line]; if (line === undefined || state.buffer.cursor.column === 0) { return { ...state, mode: "vi-command" }; } const priorSegment = segmentPromptText(line, state.buffer.pasteMarkerIds) .filter((segment) => segment.end <= state.buffer.cursor.column) .at(-1); return { mode: "vi-command", buffer: { ...state.buffer, cursor: { line: state.buffer.cursor.line, column: priorSegment?.start ?? 0, }, }, }; }