| 196 | } |
| 197 | |
| 198 | export function writeDiffToTerminal( |
| 199 | terminal: Terminal, |
| 200 | diff: Diff, |
| 201 | skipSyncMarkers = false, |
| 202 | ): number { |
| 203 | // No output if there are no patches |
| 204 | if (diff.length === 0) { |
| 205 | return 0 |
| 206 | } |
| 207 | |
| 208 | // BSU/ESU wrapping is only valid when the terminal actually supports |
| 209 | // DEC 2026. Callers can still opt out explicitly for high-frequency modes |
| 210 | // like alt-screen, but unsupported environments such as tmux must never |
| 211 | // receive the markers on the default main-screen path either. |
| 212 | const startedAt = nowMs() |
| 213 | const useSync = !skipSyncMarkers && isSynchronizedOutputSupported() |
| 214 | const inputPatchTypes = diff.map(patch => patch.type) |
| 215 | |
| 216 | // Buffer all writes into a single string to avoid multiple write calls |
| 217 | let buffer = useSync ? BSU : '' |
| 218 | let stdoutPatchBytes = 0 |
| 219 | let stdoutPatchCount = 0 |
| 220 | |
| 221 | for (const patch of diff) { |
| 222 | switch (patch.type) { |
| 223 | case 'stdout': |
| 224 | buffer += patch.content |
| 225 | stdoutPatchBytes += Buffer.byteLength(patch.content) |
| 226 | stdoutPatchCount += 1 |
| 227 | break |
| 228 | case 'clear': |
| 229 | if (patch.count > 0) { |
| 230 | buffer += eraseLines(patch.count) |
| 231 | } |
| 232 | break |
| 233 | case 'clearTerminal': |
| 234 | buffer += getResetSequenceForReason(patch.reason) |
| 235 | break |
| 236 | case 'cursorHide': |
| 237 | buffer += HIDE_CURSOR |
| 238 | break |
| 239 | case 'cursorShow': |
| 240 | buffer += SHOW_CURSOR |
| 241 | break |
| 242 | case 'cursorMove': |
| 243 | buffer += cursorMove(patch.x, patch.y) |
| 244 | break |
| 245 | case 'cursorTo': |
| 246 | buffer += cursorTo(patch.col) |
| 247 | break |
| 248 | case 'carriageReturn': |
| 249 | buffer += '\r' |
| 250 | break |
| 251 | case 'hyperlink': |
| 252 | buffer += link(patch.uri) |
| 253 | break |
| 254 | case 'styleStr': |
| 255 | buffer += patch.str |