Skip to content

Commit 9feb963

Browse files
author
mgabor3141
committed
fix: defer viewport DOM sync during synchronized output
Defer Viewport DOM scroll updates while DEC 2026 synchronized output is active, then flush once rendering resumes so DOM and canvas update atomically. Also update demo sizing to avoid relying on removed internal viewport scrollBarWidth.
1 parent f404287 commit 9feb963

File tree

2 files changed

+22
-3
lines changed

2 files changed

+22
-3
lines changed

demo/client/client.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,12 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a
617617
}
618618

619619
function updateTerminalSize(): void {
620+
const showScrollbar = term!.options.scrollbar?.showScrollbar ?? true;
621+
const scrollBarWidth = (term!.options.scrollback === 0 || !showScrollbar)
622+
? 0
623+
: (term!.options.scrollbar?.width ?? 14);
620624
const width = optionsWindow.autoResize ? '100%'
621-
: ((term as any).dimensions.css.canvas.width + (term as any)._core.viewport.scrollBarWidth).toString() + 'px';
625+
: ((term as any).dimensions.css.canvas.width + scrollBarWidth).toString() + 'px';
622626
const height = optionsWindow.autoResize ? '100%'
623627
: ((term as any).dimensions.css.canvas.height).toString() + 'px';
624628
terminalContainer!.style.width = width;

src/browser/Viewport.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class Viewport extends Disposable {
2727
private _isSyncing: boolean = false;
2828
private _isHandlingScroll: boolean = false;
2929
private _suppressOnScrollHandler: boolean = false;
30+
private _needsSyncOnRender: boolean = false;
3031

3132
constructor(
3233
element: HTMLElement,
@@ -105,6 +106,16 @@ export class Viewport extends Disposable {
105106
}));
106107
this._register(this._bufferService.onScroll(() => this._sync()));
107108

109+
// Flush deferred viewport sync after a render completes (e.g. after ESU ends
110+
// synchronized output mode). This ensures DOM scroll position updates atomically
111+
// with the canvas render.
112+
this._register(this._renderService.onRender(() => {
113+
if (this._needsSyncOnRender) {
114+
this._needsSyncOnRender = false;
115+
this._sync();
116+
}
117+
}));
118+
108119
this._register(this._scrollableElement.onScroll(e => this._handleScroll(e)));
109120

110121
}
@@ -162,8 +173,12 @@ export class Viewport extends Disposable {
162173
if (!this._renderService || this._isSyncing) {
163174
return;
164175
}
165-
// Plumb synchronized output state into Viewport for follow-up behavior changes.
166-
void this._coreService.decPrivateModes.synchronizedOutput;
176+
// Defer DOM scroll updates during synchronized output to prevent visible
177+
// scroll position flickering while the canvas content is frozen.
178+
if (this._coreService.decPrivateModes.synchronizedOutput) {
179+
this._needsSyncOnRender = true;
180+
return;
181+
}
167182
this._isSyncing = true;
168183

169184
// Ignore any onScroll event that happens as a result of dimensions changing as this should

0 commit comments

Comments
 (0)