@@ -21,6 +21,7 @@ import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/cursor/curs
21
21
import { PositionAffinity } from 'vs/editor/common/model' ;
22
22
import { InjectedText } from 'vs/editor/common/modelLineProjectionData' ;
23
23
import { Mutable } from 'vs/base/common/types' ;
24
+ import { Lazy } from 'vs/base/common/lazy' ;
24
25
25
26
const enum HitTestResultType {
26
27
Unknown ,
@@ -36,6 +37,9 @@ class UnknownHitTestResult {
36
37
37
38
class ContentHitTestResult {
38
39
readonly type = HitTestResultType . Content ;
40
+
41
+ get hitTarget ( ) : HTMLElement { return this . spanNode ; }
42
+
39
43
constructor (
40
44
readonly position : Position ,
41
45
readonly spanNode : HTMLElement ,
@@ -404,26 +408,53 @@ abstract class BareHitTestRequest {
404
408
405
409
class HitTestRequest extends BareHitTestRequest {
406
410
private readonly _ctx : HitTestContext ;
407
- public readonly target : HTMLElement | null ;
408
- public readonly targetPath : Uint8Array ;
411
+ private readonly _eventTarget : HTMLElement | null ;
412
+ public readonly hitTestResult = new Lazy ( ( ) => MouseTargetFactory . doHitTest ( this . _ctx , this ) ) ;
413
+ private _useHitTestTarget : boolean ;
414
+ private _targetPathCacheElement : HTMLElement | null = null ;
415
+ private _targetPathCacheValue : Uint8Array = new Uint8Array ( 0 ) ;
416
+
417
+ public get target ( ) : HTMLElement | null {
418
+ if ( this . _useHitTestTarget ) {
419
+ return this . hitTestResult . value . hitTarget ;
420
+ }
421
+ return this . _eventTarget ;
422
+ }
409
423
410
- constructor ( ctx : HitTestContext , editorPos : EditorPagePosition , pos : PageCoordinates , relativePos : CoordinatesRelativeToEditor , target : HTMLElement | null ) {
424
+ public get targetPath ( ) : Uint8Array {
425
+ if ( this . _targetPathCacheElement !== this . target ) {
426
+ this . _targetPathCacheElement = this . target ;
427
+ this . _targetPathCacheValue = PartFingerprints . collect ( this . target , this . _ctx . viewDomNode ) ;
428
+ }
429
+ return this . _targetPathCacheValue ;
430
+ }
431
+
432
+ constructor ( ctx : HitTestContext , editorPos : EditorPagePosition , pos : PageCoordinates , relativePos : CoordinatesRelativeToEditor , eventTarget : HTMLElement | null ) {
411
433
super ( ctx , editorPos , pos , relativePos ) ;
412
434
this . _ctx = ctx ;
435
+ this . _eventTarget = eventTarget ;
413
436
414
- if ( target ) {
415
- this . target = target ;
416
- this . targetPath = PartFingerprints . collect ( target , ctx . viewDomNode ) ;
417
- } else {
418
- this . target = null ;
419
- this . targetPath = new Uint8Array ( 0 ) ;
420
- }
437
+ // If no event target is passed in, we will use the hit test target
438
+ const hasEventTarget = Boolean ( this . _eventTarget ) ;
439
+ this . _useHitTestTarget = ! hasEventTarget ;
421
440
}
422
441
423
442
public override toString ( ) : string {
424
443
return `pos(${ this . pos . x } ,${ this . pos . y } ), editorPos(${ this . editorPos . x } ,${ this . editorPos . y } ), relativePos(${ this . relativePos . x } ,${ this . relativePos . y } ), mouseVerticalOffset: ${ this . mouseVerticalOffset } , mouseContentHorizontalOffset: ${ this . mouseContentHorizontalOffset } \n\ttarget: ${ this . target ? ( < HTMLElement > this . target ) . outerHTML : null } ` ;
425
444
}
426
445
446
+ public get wouldBenefitFromHitTestTargetSwitch ( ) : boolean {
447
+ return (
448
+ ! this . _useHitTestTarget
449
+ && this . hitTestResult . value . hitTarget !== null
450
+ && this . target !== this . hitTestResult . value . hitTarget
451
+ ) ;
452
+ }
453
+
454
+ public switchToHitTestTarget ( ) : void {
455
+ this . _useHitTestTarget = true ;
456
+ }
457
+
427
458
private _getMouseColumn ( position : Position | null = null ) : number {
428
459
if ( position && position . column < this . _ctx . viewModel . getLineMaxColumn ( position . lineNumber ) ) {
429
460
// Most likely, the line contains foreign decorations...
@@ -459,10 +490,6 @@ class HitTestRequest extends BareHitTestRequest {
459
490
public fulfillOverlayWidget ( detail : string ) : IMouseTargetOverlayWidget {
460
491
return MouseTarget . createOverlayWidget ( this . target , this . _getMouseColumn ( ) , detail ) ;
461
492
}
462
-
463
- public withTarget ( target : HTMLElement | null ) : HitTestRequest {
464
- return new HitTestRequest ( this . _ctx , this . editorPos , this . pos , this . relativePos , target ) ;
465
- }
466
493
}
467
494
468
495
interface ResolvedHitTestRequest extends HitTestRequest {
@@ -509,7 +536,7 @@ export class MouseTargetFactory {
509
536
const ctx = new HitTestContext ( this . _context , this . _viewHelper , lastRenderData ) ;
510
537
const request = new HitTestRequest ( ctx , editorPos , pos , relativePos , target ) ;
511
538
try {
512
- const r = MouseTargetFactory . _createMouseTarget ( ctx , request , false ) ;
539
+ const r = MouseTargetFactory . _createMouseTarget ( ctx , request ) ;
513
540
514
541
if ( r . type === MouseTargetType . CONTENT_TEXT ) {
515
542
// Snap to the nearest soft tab boundary if atomic soft tabs are enabled.
@@ -528,24 +555,13 @@ export class MouseTargetFactory {
528
555
}
529
556
}
530
557
531
- private static _createMouseTarget ( ctx : HitTestContext , request : HitTestRequest , domHitTestExecuted : boolean ) : IMouseTarget {
558
+ private static _createMouseTarget ( ctx : HitTestContext , request : HitTestRequest ) : IMouseTarget {
532
559
533
560
// console.log(`${domHitTestExecuted ? '=>' : ''}CAME IN REQUEST: ${request}`);
534
561
535
- // First ensure the request has a target
536
562
if ( request . target === null ) {
537
- if ( domHitTestExecuted ) {
538
- // Still no target... and we have already executed hit test...
539
- return request . fulfillUnknown ( ) ;
540
- }
541
-
542
- const hitTestResult = MouseTargetFactory . _doHitTest ( ctx , request ) ;
543
-
544
- if ( hitTestResult . type === HitTestResultType . Content ) {
545
- return MouseTargetFactory . createMouseTargetFromHitTestPosition ( ctx , request , hitTestResult . spanNode , hitTestResult . position , hitTestResult . injectedText ) ;
546
- }
547
-
548
- return this . _createMouseTarget ( ctx , request . withTarget ( hitTestResult . hitTarget ) , true ) ;
563
+ // No target
564
+ return request . fulfillUnknown ( ) ;
549
565
}
550
566
551
567
// we know for a fact that request.target is not null
@@ -566,7 +582,7 @@ export class MouseTargetFactory {
566
582
result = result || MouseTargetFactory . _hitTestMargin ( ctx , resolvedRequest ) ;
567
583
result = result || MouseTargetFactory . _hitTestViewCursor ( ctx , resolvedRequest ) ;
568
584
result = result || MouseTargetFactory . _hitTestTextArea ( ctx , resolvedRequest ) ;
569
- result = result || MouseTargetFactory . _hitTestViewLines ( ctx , resolvedRequest , domHitTestExecuted ) ;
585
+ result = result || MouseTargetFactory . _hitTestViewLines ( ctx , resolvedRequest ) ;
570
586
result = result || MouseTargetFactory . _hitTestScrollbar ( ctx , resolvedRequest ) ;
571
587
572
588
return ( result || request . fulfillUnknown ( ) ) ;
@@ -704,7 +720,7 @@ export class MouseTargetFactory {
704
720
return null ;
705
721
}
706
722
707
- private static _hitTestViewLines ( ctx : HitTestContext , request : ResolvedHitTestRequest , domHitTestExecuted : boolean ) : IMouseTarget | null {
723
+ private static _hitTestViewLines ( ctx : HitTestContext , request : ResolvedHitTestRequest ) : IMouseTarget | null {
708
724
if ( ! ElementPath . isChildOfViewLines ( request . targetPath ) ) {
709
725
return null ;
710
726
}
@@ -721,36 +737,41 @@ export class MouseTargetFactory {
721
737
return request . fulfillContentEmpty ( new Position ( lineCount , maxLineColumn ) , EMPTY_CONTENT_AFTER_LINES ) ;
722
738
}
723
739
724
- if ( domHitTestExecuted ) {
725
- // Check if we are hitting a view-line (can happen in the case of inline decorations on empty lines)
726
- // See https://github.com/microsoft/vscode/issues/46942
727
- if ( ElementPath . isStrictChildOfViewLines ( request . targetPath ) ) {
728
- const lineNumber = ctx . getLineNumberAtVerticalOffset ( request . mouseVerticalOffset ) ;
729
- if ( ctx . viewModel . getLineLength ( lineNumber ) === 0 ) {
730
- const lineWidth = ctx . getLineWidth ( lineNumber ) ;
731
- const detail = createEmptyContentDataInLines ( request . mouseContentHorizontalOffset - lineWidth ) ;
732
- return request . fulfillContentEmpty ( new Position ( lineNumber , 1 ) , detail ) ;
733
- }
734
-
740
+ // Check if we are hitting a view-line (can happen in the case of inline decorations on empty lines)
741
+ // See https://github.com/microsoft/vscode/issues/46942
742
+ if ( ElementPath . isStrictChildOfViewLines ( request . targetPath ) ) {
743
+ const lineNumber = ctx . getLineNumberAtVerticalOffset ( request . mouseVerticalOffset ) ;
744
+ if ( ctx . viewModel . getLineLength ( lineNumber ) === 0 ) {
735
745
const lineWidth = ctx . getLineWidth ( lineNumber ) ;
736
- if ( request . mouseContentHorizontalOffset >= lineWidth ) {
737
- const detail = createEmptyContentDataInLines ( request . mouseContentHorizontalOffset - lineWidth ) ;
738
- const pos = new Position ( lineNumber , ctx . viewModel . getLineMaxColumn ( lineNumber ) ) ;
739
- return request . fulfillContentEmpty ( pos , detail ) ;
740
- }
746
+ const detail = createEmptyContentDataInLines ( request . mouseContentHorizontalOffset - lineWidth ) ;
747
+ return request . fulfillContentEmpty ( new Position ( lineNumber , 1 ) , detail ) ;
741
748
}
742
749
743
- // We have already executed hit test...
744
- return request . fulfillUnknown ( ) ;
750
+ const lineWidth = ctx . getLineWidth ( lineNumber ) ;
751
+ if ( request . mouseContentHorizontalOffset >= lineWidth ) {
752
+ // TODO: This is wrong for RTL
753
+ const detail = createEmptyContentDataInLines ( request . mouseContentHorizontalOffset - lineWidth ) ;
754
+ const pos = new Position ( lineNumber , ctx . viewModel . getLineMaxColumn ( lineNumber ) ) ;
755
+ return request . fulfillContentEmpty ( pos , detail ) ;
756
+ }
745
757
}
746
758
747
- const hitTestResult = MouseTargetFactory . _doHitTest ( ctx , request ) ;
759
+ // Do the hit test (if not already done)
760
+ const hitTestResult = request . hitTestResult . value ;
748
761
749
762
if ( hitTestResult . type === HitTestResultType . Content ) {
750
763
return MouseTargetFactory . createMouseTargetFromHitTestPosition ( ctx , request , hitTestResult . spanNode , hitTestResult . position , hitTestResult . injectedText ) ;
751
764
}
752
765
753
- return this . _createMouseTarget ( ctx , request . withTarget ( hitTestResult . hitTarget ) , true ) ;
766
+ // We didn't hit content...
767
+ if ( request . wouldBenefitFromHitTestTargetSwitch ) {
768
+ // We actually hit something different... Give it one last change by trying again with this new target
769
+ request . switchToHitTestTarget ( ) ;
770
+ return this . _createMouseTarget ( ctx , request ) ;
771
+ }
772
+
773
+ // We have tried everything...
774
+ return request . fulfillUnknown ( ) ;
754
775
}
755
776
756
777
private static _hitTestMinimap ( ctx : HitTestContext , request : ResolvedHitTestRequest ) : IMouseTarget | null {
@@ -1019,7 +1040,7 @@ export class MouseTargetFactory {
1019
1040
return position ;
1020
1041
}
1021
1042
1022
- private static _doHitTest ( ctx : HitTestContext , request : BareHitTestRequest ) : HitTestResult {
1043
+ public static doHitTest ( ctx : HitTestContext , request : BareHitTestRequest ) : HitTestResult {
1023
1044
1024
1045
let result : HitTestResult = new UnknownHitTestResult ( ) ;
1025
1046
if ( typeof ( < any > ctx . viewDomNode . ownerDocument ) . caretRangeFromPoint === 'function' ) {
0 commit comments