@@ -45,7 +45,8 @@ define(function (require, exports, module) {
4545 prefs = null , // Preferences
4646 $previewContainer , // Preview container
4747 $previewContent , // Preview content holder
48- lastPos ; // Last line/ch pos processed by handleMouseMove
48+ lastMousePos , // Last mouse position
49+ animationRequest ; // Request for animation frame
4950
5051 // Constants
5152 var CMD_ENABLE_QUICK_VIEW = "view.enableQuickView" ,
@@ -146,13 +147,13 @@ define(function (require, exports, module) {
146147 . addClass ( "active" ) ;
147148 }
148149
149- function divContainsMouse ( $div , event ) {
150+ function divContainsMouse ( $div , mousePos ) {
150151 var offset = $div . offset ( ) ;
151152
152- return ( event . clientX >= offset . left &&
153- event . clientX <= offset . left + $div . width ( ) &&
154- event . clientY >= offset . top &&
155- event . clientY <= offset . top + $div . height ( ) ) ;
153+ return ( mousePos . clientX >= offset . left &&
154+ mousePos . clientX <= offset . left + $div . width ( ) &&
155+ mousePos . clientY >= offset . top &&
156+ mousePos . clientY <= offset . top + $div . height ( ) ) ;
156157 }
157158
158159
@@ -515,20 +516,74 @@ define(function (require, exports, module) {
515516 return null ;
516517 }
517518
519+ function getHoveredEditor ( mousePos ) {
520+ // Figure out which editor we are over
521+ var fullEditor = EditorManager . getCurrentFullEditor ( ) ;
522+
523+ if ( ! fullEditor || ! mousePos ) {
524+ return ;
525+ }
526+
527+ // Check for inline Editor instances first
528+ var inlines = fullEditor . getInlineWidgets ( ) ,
529+ i ,
530+ editor ;
531+
532+ for ( i = 0 ; i < inlines . length ; i ++ ) {
533+ var $inlineEditorRoot = inlines [ i ] . editor && $ ( inlines [ i ] . editor . getRootElement ( ) ) , // see MultiRangeInlineEditor
534+ $otherDiv = inlines [ i ] . $htmlContent ;
535+
536+ if ( $inlineEditorRoot && divContainsMouse ( $inlineEditorRoot , mousePos ) ) {
537+ editor = inlines [ i ] . editor ;
538+ break ;
539+ } else if ( $otherDiv && divContainsMouse ( $otherDiv , mousePos ) ) {
540+ // Mouse inside unsupported inline editor like Quick Docs or Color Editor
541+ return ;
542+ }
543+ }
544+
545+ // Check main editor
546+ if ( ! editor ) {
547+ if ( divContainsMouse ( $ ( fullEditor . getRootElement ( ) ) , mousePos ) ) {
548+ editor = fullEditor ;
549+ }
550+ }
551+
552+ return editor ;
553+ }
554+
518555 /**
519556 * Changes the current hidden popoverState to visible, showing it in the UI and highlighting
520557 * its matching text in the editor.
521558 */
522559 function showPreview ( editor , popover ) {
523- var token ,
524- cm = editor . _codeMirror ;
560+ var token , cm ;
561+
562+ // Figure out which editor we are over
563+ if ( ! editor ) {
564+ editor = getHoveredEditor ( lastMousePos ) ;
565+ }
566+
567+ if ( ! editor || ! editor . _codeMirror ) {
568+ return ;
569+ }
570+
571+ cm = editor . _codeMirror ;
572+
573+ // Find char mouse is over
574+ var pos = cm . coordsChar ( { left : lastMousePos . clientX , top : lastMousePos . clientY } ) ;
575+
576+ // No preview if mouse is past last char on line
577+ if ( pos . ch >= editor . document . getLine ( pos . line ) . length ) {
578+ return ;
579+ }
525580
526581 if ( popover ) {
527582 popoverState = popover ;
528583 } else {
529584 // Query providers and append to popoverState
530- token = cm . getTokenAt ( lastPos , true ) ;
531- popoverState = $ . extend ( { } , popoverState , queryPreviewProviders ( editor , lastPos , token ) ) ;
585+ token = cm . getTokenAt ( pos , true ) ;
586+ popoverState = $ . extend ( { } , popoverState , queryPreviewProviders ( editor , pos , token ) ) ;
532587 }
533588
534589 if ( popoverState && popoverState . start && popoverState . end ) {
@@ -550,100 +605,78 @@ define(function (require, exports, module) {
550605 }
551606 }
552607 }
608+
609+ function processMouseMove ( ) {
610+ animationRequest = null ;
611+
612+ if ( ! lastMousePos ) {
613+ return ; // should never get here, but safety first!
614+ }
615+
616+ var showImmediately = false ,
617+ editor = null ;
618+
619+ if ( popoverState && popoverState . visible ) {
620+ // Only figure out which editor we are over when there is already a popover
621+ // showing (otherwise wait until after delay to minimize processing)
622+ editor = getHoveredEditor ( lastMousePos ) ;
623+ if ( editor && editor . _codeMirror ) {
624+ // Find char mouse is over
625+ var cm = editor . _codeMirror ,
626+ pos = cm . coordsChar ( { left : lastMousePos . clientX , top : lastMousePos . clientY } ) ;
627+
628+ if ( popoverState . start && popoverState . end &&
629+ editor . posWithinRange ( pos , popoverState . start , popoverState . end , true ) &&
630+ ( pos . ch < editor . document . getLine ( pos . line ) . length ) ) {
631+
632+ // That one's still relevant - nothing more to do
633+ // Note: posWithinRange() includes mouse past end of line, so need to check for that case
634+ return ;
635+ }
636+ }
637+
638+ // That one doesn't cover this pos - hide it and start anew
639+ showImmediately = true ;
640+ }
641+
642+ // Initialize popoverState
643+ hidePreview ( ) ;
644+ popoverState = { } ;
645+
646+ // Set timer to scan and show. This will get cancelled (in hidePreview())
647+ // if mouse movement rendered this popover inapplicable before timer fires.
648+ // When showing "immediately", still use setTimeout() to make this async
649+ // so we return from this mousemove event handler ASAP.
650+ popoverState . hoverTimer = window . setTimeout ( function ( ) {
651+ showPreview ( editor ) ;
652+ } , showImmediately ? 0 : HOVER_DELAY ) ;
653+ }
553654
554655 function handleMouseMove ( event ) {
656+ lastMousePos = null ;
657+
555658 if ( ! enabled ) {
556659 return ;
557660 }
558-
661+
559662 if ( event . which ) {
560663 // Button is down - don't show popovers while dragging
561664 hidePreview ( ) ;
562665 return ;
563666 }
564-
565- // Figure out which editor we are over
566- var fullEditor = EditorManager . getCurrentFullEditor ( ) ;
567-
568- if ( ! fullEditor ) {
569- hidePreview ( ) ;
570- return ;
571- }
572-
573- // Check for inline Editor instances first
574- var inlines = fullEditor . getInlineWidgets ( ) ,
575- i ,
576- editor ;
577-
578- for ( i = 0 ; i < inlines . length ; i ++ ) {
579- var $inlineEditorRoot = inlines [ i ] . editor && $ ( inlines [ i ] . editor . getRootElement ( ) ) , // see MultiRangeInlineEditor
580- $otherDiv = inlines [ i ] . $htmlContent ;
581-
582- if ( $inlineEditorRoot && divContainsMouse ( $inlineEditorRoot , event ) ) {
583- editor = inlines [ i ] . editor ;
584- break ;
585- } else if ( $otherDiv && divContainsMouse ( $otherDiv , event ) ) {
586- // Mouse inside unsupported inline editor like Quick Docs or Color Editor
587- hidePreview ( ) ;
588- return ;
589- }
590- }
591-
592- // Check main editor
593- if ( ! editor ) {
594- if ( divContainsMouse ( $ ( fullEditor . getRootElement ( ) ) , event ) ) {
595- editor = fullEditor ;
596- }
597- }
598-
599- if ( editor && editor . _codeMirror ) {
600- // Find char mouse is over
601- var cm = editor . _codeMirror ,
602- pos = cm . coordsChar ( { left : event . clientX , top : event . clientY } ) ,
603- showImmediately = false ;
604-
605- // Bail if mouse is on same char as last event
606- if ( lastPos && lastPos . line === pos . line && lastPos . ch === pos . ch ) {
607- return ;
608- }
609- lastPos = pos ;
610-
611- // No preview if mouse is past last char on line
612- if ( pos . ch >= editor . document . getLine ( pos . line ) . length ) {
613- hidePreview ( ) ;
614- return ;
615- }
616-
617- // Is there already a popover provider and range?
618- if ( popoverState ) {
619- if ( popoverState . start && popoverState . end &&
620- editor . posWithinRange ( pos , popoverState . start , popoverState . end , 1 ) ) {
621- // That one's still relevant - nothing more to do
622- return ;
623- } else {
624- // That one doesn't cover this pos - hide it and start anew
625- showImmediately = popoverState . visible ;
626- hidePreview ( ) ;
627- }
628- }
629-
630- // Initialize popoverState
631- popoverState = { } ;
632-
633- // Set timer to scan and show. This will get cancelled (in hidePreview())
634- // if mouse movement rendered this popover inapplicable before timer fires.
635- // When showing "immediately", still use setTimeout() to make this async
636- // so we return from this mousemove event handler ASAP.
637- popoverState . hoverTimer = window . setTimeout ( function ( ) {
638- showPreview ( editor , null ) ;
639- } , showImmediately ? 0 : HOVER_DELAY ) ;
640-
641- } else {
642- // Mouse not over any Editor - immediately hide popover
643- hidePreview ( ) ;
667+
668+ // Keep track of last mouse position
669+ lastMousePos = {
670+ clientX : event . clientX ,
671+ clientY : event . clientY
672+ } ;
673+
674+ // Prevent duplicate animation frame requests
675+ if ( ! animationRequest ) {
676+ animationRequest = window . requestAnimationFrame ( processMouseMove ) ;
644677 }
645678 }
646-
679+
647680 function onActiveEditorChange ( event , current , previous ) {
648681 // Hide preview when editor changes
649682 hidePreview ( ) ;
@@ -700,7 +733,16 @@ define(function (require, exports, module) {
700733 function toggleEnableQuickView ( ) {
701734 setEnabled ( ! enabled ) ;
702735 }
703-
736+
737+ function _forceShow ( popover ) {
738+ hidePreview ( ) ;
739+ lastMousePos = {
740+ clientX : popover . xpos ,
741+ clientY : Math . floor ( ( popover . ybot + popover . ytop ) / 2 )
742+ } ;
743+ showPreview ( popover . editor , popover ) ;
744+ }
745+
704746 // Create the preview container
705747 $previewContainer = $ ( previewContainerHTML ) . appendTo ( $ ( "body" ) ) ;
706748 $previewContent = $previewContainer . find ( ".preview-content" ) ;
@@ -726,8 +768,5 @@ define(function (require, exports, module) {
726768
727769 // For unit testing
728770 exports . _queryPreviewProviders = queryPreviewProviders ;
729- exports . _forceShow = function ( popover ) {
730- hidePreview ( ) ;
731- showPreview ( popover . editor , popover ) ;
732- } ;
771+ exports . _forceShow = _forceShow ;
733772} ) ;
0 commit comments