(private readonly options: Options)
| 178 | y: number; |
| 179 | } | null = null; |
| 180 | constructor(private readonly options: Options) { |
| 181 | autoBind(this); |
| 182 | if (this.options.patchConsole) { |
| 183 | this.restoreConsole = this.patchConsole(); |
| 184 | this.restoreStderr = this.patchStderr(); |
| 185 | } |
| 186 | this.terminal = { |
| 187 | stdout: options.stdout, |
| 188 | stderr: options.stderr |
| 189 | }; |
| 190 | this.terminalColumns = options.stdout.columns || 80; |
| 191 | this.terminalRows = options.stdout.rows || 24; |
| 192 | this.altScreenParkPatch = makeAltScreenParkPatch(this.terminalRows); |
| 193 | this.stylePool = new StylePool(); |
| 194 | this.charPool = new CharPool(); |
| 195 | this.hyperlinkPool = new HyperlinkPool(); |
| 196 | this.frontFrame = emptyFrame(this.terminalRows, this.terminalColumns, this.stylePool, this.charPool, this.hyperlinkPool); |
| 197 | this.backFrame = emptyFrame(this.terminalRows, this.terminalColumns, this.stylePool, this.charPool, this.hyperlinkPool); |
| 198 | this.log = new LogUpdate({ |
| 199 | isTTY: options.stdout.isTTY as boolean | undefined || false, |
| 200 | stylePool: this.stylePool |
| 201 | }); |
| 202 | |
| 203 | // scheduleRender is called from the reconciler's resetAfterCommit, which |
| 204 | // runs BEFORE React's layout phase (ref attach + useLayoutEffect). Any |
| 205 | // state set in layout effects — notably the cursorDeclaration from |
| 206 | // useDeclaredCursor — would lag one commit behind if we rendered |
| 207 | // synchronously. Deferring to a microtask runs onRender after layout |
| 208 | // effects have committed, so the native cursor tracks the caret without |
| 209 | // a one-keystroke lag. Same event-loop tick, so throughput is unchanged. |
| 210 | // Test env uses onImmediateRender (direct onRender, no throttle) so |
| 211 | // existing synchronous lastFrame() tests are unaffected. |
| 212 | const deferredRender = (): void => queueMicrotask(this.onRender); |
| 213 | this.scheduleRender = throttle(deferredRender, FRAME_INTERVAL_MS, { |
| 214 | leading: true, |
| 215 | trailing: true |
| 216 | }); |
| 217 | |
| 218 | // Ignore last render after unmounting a tree to prevent empty output before exit |
| 219 | this.isUnmounted = false; |
| 220 | |
| 221 | // Unmount when process exits |
| 222 | this.unsubscribeExit = onExit(this.unmount, { |
| 223 | alwaysLast: false |
| 224 | }); |
| 225 | if (options.stdout.isTTY) { |
| 226 | options.stdout.on('resize', this.handleResize); |
| 227 | process.on('SIGCONT', this.handleResume); |
| 228 | this.unsubscribeTTYHandlers = () => { |
| 229 | options.stdout.off('resize', this.handleResize); |
| 230 | process.off('SIGCONT', this.handleResume); |
| 231 | }; |
| 232 | } |
| 233 | this.rootNode = dom.createNode('ink-root'); |
| 234 | this.focusManager = new FocusManager((target, event) => dispatcher.dispatchDiscrete(target, event)); |
| 235 | this.rootNode.focusManager = this.focusManager; |
| 236 | this.renderer = createRenderer(this.rootNode, this.stylePool); |
| 237 | this.rootNode.onRender = this.scheduleRender; |
nothing calls this directly
no test coverage detected