|
| 1 | +--- |
| 2 | +date: '2026-02-23T15:00:00+01:00' |
| 3 | +draft: false |
| 4 | +title: 'Synchronized Output' |
| 5 | +weight: 13 |
| 6 | +--- |
| 7 | + |
| 8 | +Synchronized output (Mode 2026) prevents screen tearing during rapid terminal redraws. When enabled, the terminal buffers all output until the mode is disabled, then paints the entire frame atomically. This is particularly useful for readline — completion lists, multi-line prompts, and screen repaints all cause multiple write calls that can tear without synchronization. |
| 9 | + |
| 10 | +## How It Works |
| 11 | + |
| 12 | +Mode 2026 uses two escape sequences: |
| 13 | + |
| 14 | +| Sequence | Name | Purpose | |
| 15 | +|----------|------|---------| |
| 16 | +| `ESC[?2026h` | BSU (Begin Synchronized Update) | Start buffering output | |
| 17 | +| `ESC[?2026l` | ESU (End Synchronized Update) | Flush buffer and paint | |
| 18 | + |
| 19 | +Everything written between BSU and ESU is held by the terminal and rendered in a single |
| 20 | +operation. On terminals that don't recognize Mode 2026, the sequences are silently ignored |
| 21 | +— so it's always safe to send them. |
| 22 | + |
| 23 | +## Quick Start |
| 24 | + |
| 25 | +```java |
| 26 | +import org.aesh.terminal.tty.TerminalConnection; |
| 27 | +import org.aesh.terminal.utils.ANSI; |
| 28 | + |
| 29 | +TerminalConnection conn = new TerminalConnection(); |
| 30 | + |
| 31 | +// Check if terminal supports synchronized output |
| 32 | +if (conn.supportsSynchronizedOutput()) { |
| 33 | + conn.enableSynchronizedOutput(); |
| 34 | + |
| 35 | + // All writes here are buffered by the terminal |
| 36 | + conn.write("Line 1\n"); |
| 37 | + conn.write("Line 2\n"); |
| 38 | + conn.write("Line 3\n"); |
| 39 | + |
| 40 | + conn.disableSynchronizedOutput(); |
| 41 | + // Terminal paints all three lines at once |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +## Terminal Support |
| 46 | + |
| 47 | +Mode 2026 is supported by the following terminals: |
| 48 | + |
| 49 | +| Terminal | Supported | Notes | |
| 50 | +|----------|-----------|-------| |
| 51 | +| Kitty | Yes | Since early versions | |
| 52 | +| Ghostty | Yes | Since 1.0.0 | |
| 53 | +| WezTerm | Yes | Full support | |
| 54 | +| foot | Yes | Full support | |
| 55 | +| Contour | Yes | Origin of the specification | |
| 56 | +| iTerm2 | Yes | Full support | |
| 57 | +| Mintty | Yes | Full support | |
| 58 | +| xterm | No | Sequences ignored safely | |
| 59 | +| GNOME Terminal | No | Sequences ignored safely | |
| 60 | +| Konsole | No | Sequences ignored safely | |
| 61 | +| Alacritty | No | Sequences ignored safely | |
| 62 | + |
| 63 | +On unsupported terminals, the BSU/ESU sequences are silently ignored, so applications |
| 64 | +can always send them without feature detection. |
| 65 | + |
| 66 | +## Connection API |
| 67 | + |
| 68 | +The `Connection` interface provides four methods for synchronized output: |
| 69 | + |
| 70 | +### Checking Support |
| 71 | + |
| 72 | +```java |
| 73 | +// Heuristic check based on terminal type detection |
| 74 | +boolean supported = connection.supportsSynchronizedOutput(); |
| 75 | +``` |
| 76 | + |
| 77 | +This uses environment-based terminal detection via `TerminalEnvironment` and the |
| 78 | +`Device.TerminalType` enum. No terminal query is sent. |
| 79 | + |
| 80 | +### Runtime Query (DECRPM) |
| 81 | + |
| 82 | +For authoritative detection, query the terminal directly using DECRQM/DECRPM: |
| 83 | + |
| 84 | +```java |
| 85 | +// Send CSI ? 2026 $ p and parse the DECRPM response |
| 86 | +// Returns true (supported), false (not supported), or null (timeout) |
| 87 | +Boolean result = connection.querySynchronizedOutput(500); |
| 88 | + |
| 89 | +if (Boolean.TRUE.equals(result)) { |
| 90 | + // Terminal definitively supports Mode 2026 |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +The DECRPM response `CSI ? 2026 ; Ps $ y` uses these Ps values: |
| 95 | + |
| 96 | +| Ps | Meaning | Result | |
| 97 | +|----|---------|--------| |
| 98 | +| 0 | Not recognized | `false` | |
| 99 | +| 1 | Set (enabled) | `true` | |
| 100 | +| 2 | Reset (recognized but disabled) | `true` | |
| 101 | +| 3 | Permanently set | `true` | |
| 102 | +| 4 | Permanently reset | `false` | |
| 103 | + |
| 104 | +### Enable / Disable |
| 105 | + |
| 106 | +```java |
| 107 | +connection.enableSynchronizedOutput(); // Send ESC[?2026h |
| 108 | +connection.disableSynchronizedOutput(); // Send ESC[?2026l |
| 109 | +``` |
| 110 | + |
| 111 | +Both methods return the `Connection` for chaining. |
| 112 | + |
| 113 | +## Automatic Readline Integration |
| 114 | + |
| 115 | +When using the `Readline` class, synchronized output is automatically applied. If the |
| 116 | +terminal supports Mode 2026, readline wraps prompt drawing, action execution, and resize |
| 117 | +redraws with BSU/ESU sequences. No application code is needed. |
| 118 | + |
| 119 | +```java |
| 120 | +Readline readline = new Readline(); |
| 121 | + |
| 122 | +// Synchronized output is enabled automatically for supporting terminals |
| 123 | +readline.readline(connection, new Prompt("$ "), input -> { |
| 124 | + // handle input |
| 125 | +}); |
| 126 | +``` |
| 127 | + |
| 128 | +### Opting Out |
| 129 | + |
| 130 | +To disable automatic synchronized output, set the `NO_SYNCHRONIZED_OUTPUT` flag: |
| 131 | + |
| 132 | +```java |
| 133 | +import org.aesh.readline.ReadlineFlag; |
| 134 | + |
| 135 | +EnumMap<ReadlineFlag, Integer> flags = new EnumMap<>(ReadlineFlag.class); |
| 136 | +flags.put(ReadlineFlag.NO_SYNCHRONIZED_OUTPUT, 0); |
| 137 | + |
| 138 | +readline.readline(connection, new Prompt("$ "), input -> { |
| 139 | + // handle input |
| 140 | +}, null, null, null, null, flags); |
| 141 | +``` |
| 142 | + |
| 143 | +## Manual Usage |
| 144 | + |
| 145 | +For applications that manage their own rendering loop (outside of `Readline`), use |
| 146 | +the `Connection` methods directly or the ANSI constants: |
| 147 | + |
| 148 | +```java |
| 149 | +// Begin synchronized update — terminal starts buffering |
| 150 | +connection.enableSynchronizedOutput(); |
| 151 | + |
| 152 | +// Each write is buffered by the terminal, not painted yet |
| 153 | +connection.write("\u001B[2J"); // Clear screen |
| 154 | +connection.write("\u001B[1;1H"); // Move to top-left |
| 155 | +connection.write("Header line"); |
| 156 | +connection.write("\u001B[2;1H"); |
| 157 | +connection.write("Content line"); |
| 158 | +// ... more rendering ... |
| 159 | + |
| 160 | +// End synchronized update — terminal paints everything at once |
| 161 | +connection.disableSynchronizedOutput(); |
| 162 | +``` |
| 163 | + |
| 164 | +There is no need to batch writes into a `StringBuilder` — the terminal itself |
| 165 | +buffers all output between BSU and ESU. Multiple `write()` calls are fine. |
| 166 | + |
| 167 | +### ANSI Constants |
| 168 | + |
| 169 | +| Constant | Value | Description | |
| 170 | +|----------|-------|-------------| |
| 171 | +| `ANSI.MODE_2026_QUERY` | `ESC[?2026$p` | DECRQM query | |
| 172 | +| `ANSI.MODE_2026_ENABLE` | `ESC[?2026h` | Begin synchronized update (BSU) | |
| 173 | +| `ANSI.MODE_2026_DISABLE` | `ESC[?2026l` | End synchronized update (ESU) | |
| 174 | + |
| 175 | +### DECRPM Response Parser |
| 176 | + |
| 177 | +Parse a raw terminal DECRPM response: |
| 178 | + |
| 179 | +```java |
| 180 | +// Parse ESC[?2026;Ps$y response |
| 181 | +String response = "\u001B[?2026;2$y"; |
| 182 | +int[] codePoints = response.codePoints().toArray(); |
| 183 | + |
| 184 | +Boolean supported = ANSI.parseMode2026Response(codePoints); |
| 185 | +// true for Ps=1,2,3; false for Ps=0,4; null for invalid |
| 186 | +``` |
| 187 | + |
| 188 | +The parser handles leading noise and trailing data, which is common in real terminal I/O. |
| 189 | + |
| 190 | +## Example: Bouncing Image Demo |
| 191 | + |
| 192 | +The `SyncImageDemoExample` demonstrates synchronized output with inline image display. |
| 193 | +An image bounces around the screen like a screensaver — tearing is immediately visible |
| 194 | +when synchronized output is toggled off. |
| 195 | + |
| 196 | +```bash |
| 197 | +mvn exec:java -pl terminal-tty \ |
| 198 | + -Dexec.mainClass="org.aesh.terminal.tty.example.SyncImageDemoExample" \ |
| 199 | + -Dexec.args="/path/to/image.jpg" |
| 200 | +``` |
| 201 | + |
| 202 | +| Key | Action | |
| 203 | +|-----|--------| |
| 204 | +| `S` | Toggle synchronized output on/off | |
| 205 | +| `+` / `-` | Zoom image in/out | |
| 206 | +| `<` / `>` | Decrease/increase bounce speed | |
| 207 | +| `Q` | Quit | |
| 208 | + |
| 209 | +Source: [`SyncImageDemoExample.java`](https://github.com/aeshell/aesh-readline/blob/master/terminal-tty/src/main/java/org/aesh/terminal/tty/example/SyncImageDemoExample.java) |
| 210 | + |
| 211 | +There is also a simpler dashboard demo without images: |
| 212 | + |
| 213 | +```bash |
| 214 | +mvn exec:java -pl terminal-tty \ |
| 215 | + -Dexec.mainClass="org.aesh.terminal.tty.example.SynchronizedOutputExample" |
| 216 | +``` |
| 217 | + |
| 218 | +Source: [`SynchronizedOutputExample.java`](https://github.com/aeshell/aesh-readline/blob/master/terminal-tty/src/main/java/org/aesh/terminal/tty/example/SynchronizedOutputExample.java) |
| 219 | + |
| 220 | +## Comparison with Mode 2027 |
| 221 | + |
| 222 | +Mode 2026 (synchronized output) and Mode 2027 (grapheme cluster segmentation) are |
| 223 | +independent features that address different problems: |
| 224 | + |
| 225 | +| | Mode 2026 | Mode 2027 | |
| 226 | +|-|-----------|-----------| |
| 227 | +| **Purpose** | Prevent screen tearing | Correct cursor positioning for complex characters | |
| 228 | +| **Scope** | Per-frame toggle (BSU/ESU) | Persistent mode (enable once at startup) | |
| 229 | +| **Cleanup** | None needed — each ESU completes | Must disable before exit | |
| 230 | +| **Effect** | Terminal buffers output | Terminal uses UAX #29 segmentation | |
| 231 | + |
| 232 | +Both modes are automatically managed by `Readline` when supported. |
| 233 | + |
| 234 | +## Specification |
| 235 | + |
| 236 | +The synchronized output protocol was originally specified by the Contour terminal: |
| 237 | + |
| 238 | +[Contour VT Extension: Synchronized Output](https://contour-terminal.org/vt-extensions/synchronized-output/) |
| 239 | + |
| 240 | +## See Also |
| 241 | + |
| 242 | +- [Connection](connection) — Full `Connection` API reference |
| 243 | +- [Terminal Images](terminal-images) — Inline image display |
| 244 | +- [Terminal Environment](terminal-environment) — Terminal type detection |
0 commit comments