@@ -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