// Text helpers shared by the host and the Source view in the page. The page's // editor always works in "\n" text; documents may use "\r\n". export interface TextChange { from: number; to: number; insert: string; } /** The text with "\r\n" turned into "\n". */ export function normalizeEol(text: string): string { return text.replace(/\r\n/g, '\n'); } /** * The smallest single change that turns `before` into `after`: the common * start and end are kept, so a cursor outside the change stays put. */ export function minimalChange(before: string, after: string): TextChange | undefined { if (before === after) return undefined; let start = 0; const max = Math.min(before.length, after.length); while (start < max && before.charCodeAt(start) === after.charCodeAt(start)) start++; let endBefore = before.length; let endAfter = after.length; while (endBefore > start && endAfter > start && before.charCodeAt(endBefore - 1) === after.charCodeAt(endAfter - 1)) { endBefore--; endAfter--; } return { from: start, to: endBefore, insert: after.slice(start, endAfter) }; } /** Line and character of an offset into "\n" text. */ export function positionOf(text: string, offset: number): { line: number; character: number } { const clamped = Math.max(0, Math.min(offset, text.length)); let line = 0; let lineStart = 0; for (let i = text.indexOf('\n'); i !== -1 && i < clamped; i = text.indexOf('\n', i + 1)) { line++; lineStart = i + 1; } return { line, character: clamped - lineStart }; } /** Offset into "\n" text of a line and character (clamped to the line). */ export function offsetOf(text: string, line: number, character: number): number { let start = 0; for (let l = 0; l < line; l++) { const next = text.indexOf('\n', start); if (next === -1) return text.length; start = next + 1; } const end = text.indexOf('\n', start); return Math.min(start + Math.max(0, character), end === -1 ? text.length : end); } /** Applies changes given in the coordinates of `text` (as one editor transaction reports them). */ export function applyChanges(text: string, changes: TextChange[]): string { let out = text; for (const change of [...changes].sort((a, b) => b.from - a.from)) { out = out.slice(0, change.from) + change.insert + out.slice(change.to); } return out; }