| 14 | * - Remove clear patches with count 0 |
| 15 | */ |
| 16 | export function optimize(diff: Diff): Diff { |
| 17 | if (diff.length <= 1) { |
| 18 | return diff |
| 19 | } |
| 20 | |
| 21 | const result: Diff = [] |
| 22 | let len = 0 |
| 23 | |
| 24 | for (const patch of diff) { |
| 25 | const type = patch.type |
| 26 | |
| 27 | // Skip no-ops |
| 28 | if (type === 'stdout') { |
| 29 | if (patch.content === '') continue |
| 30 | } else if (type === 'cursorMove') { |
| 31 | if (patch.x === 0 && patch.y === 0) continue |
| 32 | } else if (type === 'clear') { |
| 33 | if (patch.count === 0) continue |
| 34 | } |
| 35 | |
| 36 | // Try to merge with previous patch |
| 37 | if (len > 0) { |
| 38 | const lastIdx = len - 1 |
| 39 | const last = result[lastIdx]! |
| 40 | const lastType = last.type |
| 41 | |
| 42 | // Merge consecutive cursorMove |
| 43 | if (type === 'cursorMove' && lastType === 'cursorMove') { |
| 44 | result[lastIdx] = { |
| 45 | type: 'cursorMove', |
| 46 | x: last.x + patch.x, |
| 47 | y: last.y + patch.y, |
| 48 | } |
| 49 | continue |
| 50 | } |
| 51 | |
| 52 | // Collapse consecutive cursorTo (only the last one matters) |
| 53 | if (type === 'cursorTo' && lastType === 'cursorTo') { |
| 54 | result[lastIdx] = patch |
| 55 | continue |
| 56 | } |
| 57 | |
| 58 | // Concat adjacent style patches. styleStr is a transition diff |
| 59 | // (computed by diffAnsiCodes(from, to)), not a setter — dropping |
| 60 | // the first is only sound if its undo-codes are a subset of the |
| 61 | // second's, which is NOT guaranteed. e.g. [\e[49m, \e[2m]: dropping |
| 62 | // the bg reset leaks it into the next \e[2J/\e[2K via BCE. |
| 63 | if (type === 'styleStr' && lastType === 'styleStr') { |
| 64 | result[lastIdx] = { type: 'styleStr', str: last.str + patch.str } |
| 65 | continue |
| 66 | } |
| 67 | |
| 68 | // Dedupe hyperlinks |
| 69 | if ( |
| 70 | type === 'hyperlink' && |
| 71 | lastType === 'hyperlink' && |
| 72 | patch.uri === last.uri |
| 73 | ) { |