export interface ArrowLigatureChange { from: number insert: string to: number } export interface ArrowLigatureResolution { change: ArrowLigatureChange | null nextLiteralAsciiCursor: number | null } interface ResolveArrowLigatureInputParams { beforeText: string cursor: number inputText: string literalAsciiCursor: number | null } interface ArrowLigatureRule { insert: string nextLiteralAsciiCursor: number | null prefix: string } const ESCAPED_LEFT_ARROW_PREFIX = '\\<' const ESCAPED_RIGHT_ARROW_PREFIX = '\\-' const LEFT_ARROW = '←' const LEFT_ARROW_PREFIX = '<' const LEFT_RIGHT_ARROW = '↔' const LEFT_RIGHT_ARROW_PREFIX = '<-' const RIGHT_ARROW = '→' const RIGHT_ARROW_PREFIX = '-' const NO_LIGATURE_CHANGE: ArrowLigatureResolution = { change: null, nextLiteralAsciiCursor: null, } const INPUT_RULES: Record = { '-': [ { insert: LEFT_RIGHT_ARROW_PREFIX, nextLiteralAsciiCursor: 0, prefix: ESCAPED_LEFT_ARROW_PREFIX, }, { insert: LEFT_ARROW, nextLiteralAsciiCursor: null, prefix: LEFT_ARROW_PREFIX, }, ], '>': [ { insert: `${RIGHT_ARROW_PREFIX}>`, nextLiteralAsciiCursor: null, prefix: ESCAPED_RIGHT_ARROW_PREFIX, }, { insert: LEFT_RIGHT_ARROW, nextLiteralAsciiCursor: null, prefix: LEFT_ARROW, }, { insert: LEFT_RIGHT_ARROW, nextLiteralAsciiCursor: null, prefix: LEFT_RIGHT_ARROW_PREFIX, }, { insert: RIGHT_ARROW, nextLiteralAsciiCursor: null, prefix: RIGHT_ARROW_PREFIX, }, ], } function buildChange(cursor: number, charsBeforeCursor: number, insert: string): ArrowLigatureChange { return { from: cursor - charsBeforeCursor, insert, to: cursor, } } function resolveMatchingRule( beforeText: string, inputText: string, ) { const rules = Reflect.get(INPUT_RULES, inputText) as ArrowLigatureRule[] | undefined return rules?.find((rule) => beforeText.endsWith(rule.prefix)) ?? null } function shouldKeepLiteralAsciiSequence(params: { beforeText: string cursor: number inputText: string literalAsciiCursor: number | null }) { const { beforeText, cursor, inputText, literalAsciiCursor } = params return inputText === '>' && literalAsciiCursor === cursor && beforeText.endsWith(LEFT_RIGHT_ARROW_PREFIX) } function resolveNextLiteralAsciiCursor( cursor: number, rule: ArrowLigatureRule, ) { return rule.nextLiteralAsciiCursor === 0 ? cursor : null } function buildResolution( cursor: number, rule: ArrowLigatureRule | null, ): ArrowLigatureResolution { if (!rule) return NO_LIGATURE_CHANGE return { change: buildChange(cursor, rule.prefix.length, rule.insert), nextLiteralAsciiCursor: resolveNextLiteralAsciiCursor(cursor, rule), } } export function resolveArrowLigatureInput({ beforeText, cursor, inputText, literalAsciiCursor, }: ResolveArrowLigatureInputParams): ArrowLigatureResolution { if (inputText.length !== 1) return NO_LIGATURE_CHANGE if (shouldKeepLiteralAsciiSequence({ beforeText, cursor, inputText, literalAsciiCursor, })) { return NO_LIGATURE_CHANGE } return buildResolution(cursor, resolveMatchingRule(beforeText, inputText)) }