Skip to content

Commit e26aed9

Browse files
committed
fix(terminal): prevent garbled output when resizing terminal panel
Suppress the automatic PTY resize during fitAddon.fit() and clear only the prompt line to avoid duplicate/garbled output caused by readline redrawing the prompt on top of xterm's reflowed buffer. Add ResizeObserver for reliable resize detection and skip redundant fits when dimensions haven't changed.
1 parent 2ee9584 commit e26aed9

1 file changed

Lines changed: 50 additions & 5 deletions

File tree

src/extensionsIntegrated/Terminal/TerminalInstance.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ define(function (require, exports, module) {
104104
this.searchAddon = null;
105105
this.$container = null;
106106
this._resizeTimeout = null;
107+
this._resizeObserver = null;
108+
this._lastCols = 0;
109+
this._lastRows = 0;
110+
this._suppressPtyResize = false;
107111
this._disposed = false;
108112

109113
// Bound event handlers for cleanup
@@ -153,6 +157,12 @@ define(function (require, exports, module) {
153157
// Fit to container
154158
this._fit();
155159

160+
// Use ResizeObserver for reliable resize detection
161+
this._resizeObserver = new ResizeObserver(() => {
162+
this.handleResize();
163+
});
164+
this._resizeObserver.observe(this.$container[0]);
165+
156166
// Set up custom key handler to intercept editor shortcuts
157167
this.terminal.attachCustomKeyEventHandler(this._customKeyHandler.bind(this));
158168

@@ -165,9 +175,9 @@ define(function (require, exports, module) {
165175
}
166176
});
167177

168-
// Wire resize: terminal -> PTY
178+
// Wire resize: terminal -> PTY (suppressed during _fit to control timing)
169179
this.terminal.onResize(({cols, rows}) => {
170-
if (this.isAlive) {
180+
if (this.isAlive && !this._suppressPtyResize) {
171181
this.nodeConnector.execPeer("resizeTerminal", {id: this.id, cols, rows}).catch((err) => {
172182
console.error("Terminal: resize error:", err);
173183
});
@@ -271,12 +281,43 @@ define(function (require, exports, module) {
271281
};
272282

273283
/**
274-
* Fit the terminal to its container
284+
* Fit the terminal to its container.
285+
* Suppresses the automatic PTY resize during fit() and clears the
286+
* prompt area to avoid garbled output caused by readline redrawing
287+
* the prompt on top of xterm's reflowed buffer.
288+
* Historical output above the prompt is preserved.
275289
*/
276290
TerminalInstance.prototype._fit = function () {
277291
if (this.fitAddon && this.$container && this.$container.is(":visible")) {
278292
try {
293+
const dims = this.fitAddon.proposeDimensions();
294+
if (!dims || (dims.cols === this._lastCols && dims.rows === this._lastRows)) {
295+
return;
296+
}
297+
298+
this._lastCols = dims.cols;
299+
this._lastRows = dims.rows;
300+
301+
// Suppress automatic PTY resize from onResize handler
302+
this._suppressPtyResize = true;
279303
this.fitAddon.fit();
304+
this._suppressPtyResize = false;
305+
306+
if (this.isAlive) {
307+
// After reflow, clear only the prompt line and below to
308+
// remove garbled content from the reflow/SIGWINCH conflict.
309+
// Use cursor position after reflow — the cursor sits at the
310+
// end of the (possibly garbled) prompt. Clearing from the
311+
// start of the cursor row preserves all output above.
312+
const cursorY = this.terminal.buffer.active.cursorY;
313+
this.terminal.write("\x1b[" + (cursorY + 1) + ";1H\x1b[J");
314+
315+
this.nodeConnector.execPeer("resizeTerminal", {
316+
id: this.id, cols: dims.cols, rows: dims.rows
317+
}).catch((err) => {
318+
console.error("Terminal: resize error:", err);
319+
});
320+
}
280321
} catch (e) {
281322
// Container might not be visible yet
282323
}
@@ -290,7 +331,7 @@ define(function (require, exports, module) {
290331
clearTimeout(this._resizeTimeout);
291332
this._resizeTimeout = setTimeout(() => {
292333
this._fit();
293-
}, 50);
334+
}, 150);
294335
};
295336

296337
/**
@@ -361,8 +402,12 @@ define(function (require, exports, module) {
361402
this.isAlive = false;
362403
}
363404

364-
// Dispose xterm
405+
// Dispose resize observer and xterm
365406
clearTimeout(this._resizeTimeout);
407+
if (this._resizeObserver) {
408+
this._resizeObserver.disconnect();
409+
this._resizeObserver = null;
410+
}
366411
if (this.terminal) {
367412
this.terminal.dispose();
368413
this.terminal = null;

0 commit comments

Comments
 (0)