import type { Diff } from './frame.js' /** * Optimize a diff by applying all optimization rules in a single pass. * This reduces the number of patches that need to be written to the terminal. * * Rules applied: * - Remove empty stdout patches * - Merge consecutive cursorMove patches * - Remove no-op cursorMove (0,0) patches * - Concat adjacent style patches (transition diffs — can't drop either) * - Dedupe consecutive hyperlinks with same URI * - Cancel cursor hide/show pairs * - Remove clear patches with count 0 */ export function optimize(diff: Diff): Diff { if (diff.length <= 1) { return diff } const result: Diff = [] let len = 0 for (const patch of diff) { const type = patch.type // Skip no-ops if (type === 'stdout') { if (patch.content === '') continue } else if (type === 'cursorMove') { if (patch.x === 0 && patch.y === 0) continue } else if (type === 'clear') { if (patch.count === 0) continue } // Try to merge with previous patch if (len > 0) { const lastIdx = len - 1 const last = result[lastIdx]! const lastType = last.type // Merge consecutive cursorMove if (type === 'cursorMove' && lastType === 'cursorMove') { result[lastIdx] = { type: 'cursorMove', x: last.x + patch.x, y: last.y + patch.y, } continue } // Collapse consecutive cursorTo (only the last one matters) if (type === 'cursorTo' && lastType === 'cursorTo') { result[lastIdx] = patch continue } // Concat adjacent style patches. styleStr is a transition diff // (computed by diffAnsiCodes(from, to)), not a setter — dropping // the first is only sound if its undo-codes are a subset of the // second's, which is NOT guaranteed. e.g. [\e[49m, \e[2m]: dropping // the bg reset leaks it into the next \e[2J/\e[2K via BCE. if (type === 'styleStr' && lastType === 'styleStr') { result[lastIdx] = { type: 'styleStr', str: last.str + patch.str } continue } // Dedupe hyperlinks if ( type === 'hyperlink' && lastType === 'hyperlink' && patch.uri === last.uri ) { continue } // Cancel cursor hide/show pairs if ( (type === 'cursorShow' && lastType === 'cursorHide') || (type === 'cursorHide' && lastType === 'cursorShow') ) { result.pop() len-- continue } } result.push(patch) len++ } return result }