@@ -393,8 +393,37 @@ export class DomRenderer extends Disposable implements IRenderer {
393393 }
394394
395395 private _setCellUnderline ( x : number , x2 : number , y : number , y2 : number , cols : number , enabled : boolean ) : void {
396- x = this . _cellToRowElements [ y ] [ x ] ;
397- x2 = this . _cellToRowElements [ y2 ] [ x2 ] ;
396+ /**
397+ * NOTE: The linkifier may send out of viewport y-values if:
398+ * - negative y-value: the link started at a higher line
399+ * - y-value >= maxY: the link ends at a line below viewport
400+ *
401+ * For negative y-values we can simply adjust x = 0,
402+ * as higher up link start means, that everything from
403+ * (0,0) is a link under top-down-left-right char progression
404+ *
405+ * Additionally there might be a small chance of out-of-sync x|y-values
406+ * from a race condition of render updates vs. link event handler execution:
407+ * - (sync) resize: chances terminal buffer in sync, schedules render update async
408+ * - (async) link handler race condition: new buffer metrics, but still on old render state
409+ * - (async) render update: brings term metrics and render state back in sync
410+ */
411+ if ( y < 0 ) x = 0 ;
412+ if ( y2 < 0 ) x2 = 0 ;
413+
414+ // avoid out-of-sync y-values, simply clamp into valid area
415+ const maxY = this . _cellToRowElements . length - 1 ;
416+ y = Math . max ( Math . min ( y , maxY ) , 0 ) ;
417+ y2 = Math . max ( Math . min ( y2 , maxY ) , 0 ) ;
418+ const elemY = this . _cellToRowElements [ y ] ;
419+ const elemY2 = this . _cellToRowElements [ y2 ] ;
420+ if ( x >= elemY . length || x2 >= elemY2 . length ) {
421+ // avoid out-of-sync x-values
422+ // simply exit early, gets fixed by the next render update
423+ return ;
424+ }
425+ x = elemY [ x ] ;
426+ x2 = elemY2 [ x2 ] ;
398427
399428 if ( x === - 1 || x2 === - 1 ) {
400429 return ;
0 commit comments