@@ -32,6 +32,7 @@ export default class TerminalComponent {
32
32
rows : options . rows || 24 ,
33
33
cols : options . cols || 80 ,
34
34
port : options . port || 8767 ,
35
+ renderer : options . renderer || "auto" , // 'auto' | 'canvas' | 'webgl'
35
36
fontSize : terminalSettings . fontSize ,
36
37
fontFamily : terminalSettings . fontFamily ,
37
38
fontWeight : terminalSettings . fontWeight ,
@@ -80,7 +81,7 @@ export default class TerminalComponent {
80
81
system . openInBrowser ( uri ) ;
81
82
}
82
83
} ) ;
83
- this . webglAddon = new WebglAddon ( ) ;
84
+ this . webglAddon = null ;
84
85
85
86
// Load addons
86
87
this . terminal . loadAddon ( this . fitAddon ) ;
@@ -244,36 +245,12 @@ export default class TerminalComponent {
244
245
// Ensure scroll position is within valid bounds
245
246
const safeScrollPosition = Math . min ( targetScrollPosition , maxScroll ) ;
246
247
247
- // Only adjust if we have significant content and the position is different
248
+ // Only adjust if we have significant content and the position differs
248
249
if (
249
250
buffer . length > this . terminal . rows &&
250
- Math . abs ( buffer . viewportY - safeScrollPosition ) > 2
251
+ buffer . viewportY !== safeScrollPosition
251
252
) {
252
- // Gradually adjust to prevent jarring movements
253
- const steps = 3 ;
254
- const diff = safeScrollPosition - buffer . viewportY ;
255
- const stepSize = Math . ceil ( Math . abs ( diff ) / steps ) ;
256
-
257
- let currentStep = 0 ;
258
- const adjustStep = ( ) => {
259
- if ( currentStep >= steps ) return ;
260
-
261
- const currentPos = buffer . viewportY ;
262
- const remaining = safeScrollPosition - currentPos ;
263
- const adjustment =
264
- Math . sign ( remaining ) * Math . min ( stepSize , Math . abs ( remaining ) ) ;
265
-
266
- if ( Math . abs ( adjustment ) >= 1 ) {
267
- this . terminal . scrollLines ( adjustment ) ;
268
- }
269
-
270
- currentStep ++ ;
271
- if ( currentStep < steps && Math . abs ( remaining ) > 1 ) {
272
- setTimeout ( adjustStep , 50 ) ;
273
- }
274
- } ;
275
-
276
- setTimeout ( adjustStep , 100 ) ;
253
+ this . terminal . scrollToLine ( safeScrollPosition ) ;
277
254
}
278
255
}
279
256
@@ -468,7 +445,6 @@ export default class TerminalComponent {
468
445
position: relative;
469
446
background: ${ this . options . theme . background } ;
470
447
overflow: hidden;
471
- padding: 0.25rem;
472
448
box-sizing: border-box;
473
449
` ;
474
450
@@ -490,32 +466,49 @@ export default class TerminalComponent {
490
466
this . container . style . background = this . options . theme . background ;
491
467
492
468
try {
493
- try {
494
- this . terminal . loadAddon ( this . webglAddon ) ;
495
- this . terminal . open ( container ) ;
496
- } catch ( error ) {
497
- console . error ( "Failed to load WebglAddon:" , error ) ;
498
- this . webglAddon . dispose ( ) ;
499
- }
500
-
501
- if ( ! this . terminal . element ) {
502
- // webgl loading failed for some reason, attach with DOM renderer
503
- this . terminal . open ( container ) ;
469
+ // Open first to ensure a stable renderer is attached
470
+ this . terminal . open ( container ) ;
471
+
472
+ // Renderer selection: 'canvas' (default core), 'webgl', or 'auto'
473
+ if (
474
+ this . options . renderer === "webgl" ||
475
+ this . options . renderer === "auto"
476
+ ) {
477
+ try {
478
+ const addon = new WebglAddon ( ) ;
479
+ this . terminal . loadAddon ( addon ) ;
480
+ if ( typeof addon . onContextLoss === "function" ) {
481
+ addon . onContextLoss ( ( ) => this . _handleWebglContextLoss ( ) ) ;
482
+ }
483
+ this . webglAddon = addon ;
484
+ } catch ( error ) {
485
+ console . error ( "Failed to enable WebGL renderer:" , error ) ;
486
+ try {
487
+ this . webglAddon ?. dispose ?. ( ) ;
488
+ } catch { }
489
+ this . webglAddon = null ; // stay on canvas
490
+ }
504
491
}
505
492
const terminalSettings = getTerminalSettings ( ) ;
506
493
// Load ligatures addon if enabled
507
494
if ( terminalSettings . fontLigatures ) {
508
495
this . loadLigaturesAddon ( ) ;
509
496
}
510
497
511
- // Wait for terminal to render then fit
512
- setTimeout ( ( ) => {
513
- this . fitAddon . fit ( ) ;
514
- this . terminal . focus ( ) ;
515
-
516
- // Initialize touch selection after terminal is mounted
517
- this . setupTouchSelection ( ) ;
518
- } , 10 ) ;
498
+ // First render pass: schedule a fit + focus once the frame is ready
499
+ if ( typeof requestAnimationFrame === "function" ) {
500
+ requestAnimationFrame ( ( ) => {
501
+ this . fitAddon . fit ( ) ;
502
+ this . terminal . focus ( ) ;
503
+ this . setupTouchSelection ( ) ;
504
+ } ) ;
505
+ } else {
506
+ setTimeout ( ( ) => {
507
+ this . fitAddon . fit ( ) ;
508
+ this . terminal . focus ( ) ;
509
+ this . setupTouchSelection ( ) ;
510
+ } , 0 ) ;
511
+ }
519
512
} catch ( error ) {
520
513
console . error ( "Failed to mount terminal:" , error ) ;
521
514
}
@@ -726,32 +719,6 @@ export default class TerminalComponent {
726
719
* Focus terminal
727
720
*/
728
721
focus ( ) {
729
- // Ensure cursor is visible before focusing to prevent half-visibility
730
- if ( this . terminal . buffer && this . terminal . buffer . active ) {
731
- const buffer = this . terminal . buffer . active ;
732
- const cursorY = buffer . cursorY ;
733
- const cursorViewportPos = buffer . baseY + cursorY ;
734
- const viewportTop = buffer . viewportY ;
735
- const viewportBottom = viewportTop + this . terminal . rows - 1 ;
736
-
737
- // Check if cursor is fully visible (with margin to prevent half-visibility)
738
- const isCursorFullyVisible =
739
- cursorViewportPos >= viewportTop + 1 &&
740
- cursorViewportPos <= viewportBottom - 2 ;
741
-
742
- // If cursor is not fully visible, scroll to make it properly visible
743
- if ( ! isCursorFullyVisible && buffer . length > this . terminal . rows ) {
744
- const targetScroll = Math . max (
745
- 0 ,
746
- Math . min (
747
- buffer . length - this . terminal . rows ,
748
- cursorViewportPos - Math . floor ( this . terminal . rows * 0.25 ) ,
749
- ) ,
750
- ) ;
751
- this . terminal . scrollToLine ( targetScroll ) ;
752
- }
753
- }
754
-
755
722
this . terminal . focus ( ) ;
756
723
}
757
724
@@ -1018,3 +985,16 @@ export default class TerminalComponent {
1018
985
onBell ( ) { }
1019
986
onProcessExit ( exitData ) { }
1020
987
}
988
+
989
+ // Internal helpers for WebGL renderer lifecycle
990
+ TerminalComponent . prototype . _handleWebglContextLoss = function ( ) {
991
+ try {
992
+ console . warn ( "WebGL context lost; falling back to canvas renderer" ) ;
993
+ try {
994
+ this . webglAddon ?. dispose ?. ( ) ;
995
+ } catch { }
996
+ this . webglAddon = null ;
997
+ } catch ( e ) {
998
+ console . error ( "Error handling WebGL context loss:" , e ) ;
999
+ }
1000
+ } ;
0 commit comments