@@ -32,6 +32,7 @@ export default class TerminalComponent {
3232 rows : options . rows || 24 ,
3333 cols : options . cols || 80 ,
3434 port : options . port || 8767 ,
35+ renderer : options . renderer || "auto" , // 'auto' | 'canvas' | 'webgl'
3536 fontSize : terminalSettings . fontSize ,
3637 fontFamily : terminalSettings . fontFamily ,
3738 fontWeight : terminalSettings . fontWeight ,
@@ -80,7 +81,7 @@ export default class TerminalComponent {
8081 system . openInBrowser ( uri ) ;
8182 }
8283 } ) ;
83- this . webglAddon = new WebglAddon ( ) ;
84+ this . webglAddon = null ;
8485
8586 // Load addons
8687 this . terminal . loadAddon ( this . fitAddon ) ;
@@ -244,36 +245,12 @@ export default class TerminalComponent {
244245 // Ensure scroll position is within valid bounds
245246 const safeScrollPosition = Math . min ( targetScrollPosition , maxScroll ) ;
246247
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
248249 if (
249250 buffer . length > this . terminal . rows &&
250- Math . abs ( buffer . viewportY - safeScrollPosition ) > 2
251+ buffer . viewportY !== safeScrollPosition
251252 ) {
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 ) ;
277254 }
278255 }
279256
@@ -468,7 +445,6 @@ export default class TerminalComponent {
468445 position: relative;
469446 background: ${ this . options . theme . background } ;
470447 overflow: hidden;
471- padding: 0.25rem;
472448 box-sizing: border-box;
473449 ` ;
474450
@@ -490,32 +466,49 @@ export default class TerminalComponent {
490466 this . container . style . background = this . options . theme . background ;
491467
492468 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+ }
504491 }
505492 const terminalSettings = getTerminalSettings ( ) ;
506493 // Load ligatures addon if enabled
507494 if ( terminalSettings . fontLigatures ) {
508495 this . loadLigaturesAddon ( ) ;
509496 }
510497
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+ }
519512 } catch ( error ) {
520513 console . error ( "Failed to mount terminal:" , error ) ;
521514 }
@@ -726,32 +719,6 @@ export default class TerminalComponent {
726719 * Focus terminal
727720 */
728721 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-
755722 this . terminal . focus ( ) ;
756723 }
757724
@@ -1018,3 +985,16 @@ export default class TerminalComponent {
1018985 onBell ( ) { }
1019986 onProcessExit ( exitData ) { }
1020987}
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