@@ -60,6 +60,15 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
60
60
private throttledUpdateWidgetPosition = throttle ( this . updateWidgetPosition . bind ( this ) , 100 ) ;
61
61
private monacoContainer : HTMLDivElement | undefined ;
62
62
63
+ /**
64
+ * Reference to parameter widget (used by monaco to display parameter docs).
65
+ *
66
+ * @private
67
+ * @type {Element }
68
+ * @memberof MonacoEditor
69
+ */
70
+ private parameterWidget ?: Element ;
71
+
63
72
constructor ( props : IMonacoEditorProps ) {
64
73
super ( props ) ;
65
74
this . state = { editor : undefined , model : null , visibleLineCount : - 1 , attached : false , widgetsReparented : false } ;
@@ -157,6 +166,11 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
157
166
}
158
167
} ) ) ;
159
168
169
+ // When editor loses focus, hide parameter widgets (if any currently displayed).
170
+ this . subscriptions . push ( editor . onDidBlurEditorWidget ( ( ) => {
171
+ this . hideParameterWidget ( ) ;
172
+ } ) ) ;
173
+
160
174
// Track focus changes to make sure we update our widget parent and widget position
161
175
this . subscriptions . push ( editor . onDidFocusEditorWidget ( ( ) => {
162
176
this . throttledUpdateWidgetPosition ( ) ;
@@ -192,7 +206,10 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
192
206
if ( window ) {
193
207
window . removeEventListener ( 'resize' , this . windowResized ) ;
194
208
}
195
-
209
+ if ( this . parameterWidget ) {
210
+ this . parameterWidget . removeEventListener ( 'mouseleave' , this . outermostParentLeave ) ;
211
+ this . parameterWidget = undefined ;
212
+ }
196
213
if ( this . outermostParent ) {
197
214
this . outermostParent . removeEventListener ( 'mouseleave' , this . outermostParentLeave ) ;
198
215
this . outermostParent = null ;
@@ -434,9 +451,108 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
434
451
if ( this . state . editor && ! this . enteredHover ) {
435
452
// If we haven't already entered hover, then act like it shuts down
436
453
this . onHoverLeave ( ) ;
454
+ // Possible user is viewing the parameter hints, wait before user moves the mouse.
455
+ // Waiting for 1s is too long to move the mouse and hide the hints (100ms seems like a good fit).
456
+ setTimeout ( ( ) => this . hideParameterWidget ( ) , 100 ) ;
437
457
}
438
458
}
439
459
460
+ /**
461
+ * This will hide the parameter widget if the user is not hovering over
462
+ * the parameter widget for this monaco editor.
463
+ *
464
+ * Notes: See issue https://github.com/microsoft/vscode-python/issues/7851 for further info.
465
+ * Hide the parameter widget if all of the following conditions have been met:
466
+ * - ditor doesn't have focus
467
+ * - Mouse is not over the editor
468
+ * - Mouse is not over (hovering) the parameter widget
469
+ *
470
+ * @private
471
+ * @returns
472
+ * @memberof MonacoEditor
473
+ */
474
+ private hideParameterWidget ( ) {
475
+ if ( ! this . state . editor || ! this . state . editor . getDomNode ( ) || ! this . widgetParent ) {
476
+ return ;
477
+ }
478
+ // Find all elements that the user is hovering over.
479
+ // Its possible the parameter widget is one of them.
480
+ const hoverElements : Element [ ] = Array . prototype . slice . call ( document . querySelectorAll ( ':hover' ) ) ;
481
+ // Find all parameter widgets related to this monaco editor that are currently displayed.
482
+ const visibleParameterHintsWidgets : Element [ ] = Array . prototype . slice . call ( this . widgetParent . querySelectorAll ( '.parameter-hints-widget.visible' ) ) ;
483
+ if ( hoverElements . length === 0 && visibleParameterHintsWidgets . length === 0 ) {
484
+ // If user is not hovering over anything and there are no visible parameter widgets,
485
+ // then, we have nothing to do but get out of here.
486
+ return ;
487
+ }
488
+
489
+ // Find all parameter widgets related to this monaco editor.
490
+ const knownParameterHintsWidgets : HTMLDivElement [ ] = Array . prototype . slice . call ( this . widgetParent . querySelectorAll ( '.parameter-hints-widget' ) ) ;
491
+
492
+ // Lets not assume we'll have the exact same DOM for parameter widgets.
493
+ // So, just remove the event handler, and add it again later.
494
+ if ( this . parameterWidget ) {
495
+ this . parameterWidget . removeEventListener ( 'mouseleave' , this . outermostParentLeave ) ;
496
+ }
497
+ // These are the classes that will appear on a parameter widget when they are visible.
498
+ const parameterWidgetClasses = [ 'editor-widget' , 'parameter-hints-widget' , 'visible' ] ;
499
+
500
+ // Find the parameter widget the user is currently hovering over.
501
+ this . parameterWidget = hoverElements . find ( item => {
502
+ if ( ! item . className ) {
503
+ return false ;
504
+ }
505
+ // Check if user is hovering over a parameter widget.
506
+ const classes = item . className . split ( ' ' ) ;
507
+ if ( ! parameterWidgetClasses . every ( cls => classes . indexOf ( cls ) >= 0 ) ) {
508
+ // Not all classes required in a parameter hint widget are in this element.
509
+ // Hence this is not a parameter widget.
510
+ return false ;
511
+ }
512
+
513
+ // Ok, this element that the user is hovering over is a parameter widget.
514
+
515
+ // Next, check whether this parameter widget belongs to this monaco editor.
516
+ // We have a list of parameter widgets that belong to this editor, hence a simple lookup.
517
+ return knownParameterHintsWidgets . some ( widget => widget === item ) ;
518
+ } ) ;
519
+
520
+ if ( this . parameterWidget ) {
521
+ // We know the user is hovering over the parameter widget for this editor.
522
+ // Hovering could mean the user is scrolling through a large parameter list.
523
+ // We need to add a mouse leave event handler, so as to hide this.
524
+ this . parameterWidget . addEventListener ( 'mouseleave' , this . outermostParentLeave ) ;
525
+
526
+ // In case the event handler doesn't get fired, have a backup of checking within 1s.
527
+ setTimeout ( ( ) => this . hideParameterWidget ( ) , 1000 ) ;
528
+ return ;
529
+ }
530
+ if ( visibleParameterHintsWidgets . length === 0 ) {
531
+ // There are no parameter widgets displayed for this editor.
532
+ // Hence nothing to do.
533
+ return ;
534
+ }
535
+ // If the editor has focus, don't hide the parameter widget.
536
+ // This is the default behavior. Let the user hit `Escape` or click somewhere
537
+ // to forcefully hide the parameter widget.
538
+ if ( this . state . editor . hasWidgetFocus ( ) ) {
539
+ return ;
540
+ }
541
+
542
+ // If we got here, then the user is not hovering over the paramter widgets.
543
+ // & the editor doesn't have focus.
544
+ // However some of the parameter widgets associated with this monaco editor are visible.
545
+ // We need to hide them.
546
+
547
+ // Solution: Hide the widgets manually.
548
+ knownParameterHintsWidgets . forEach ( widget => {
549
+ widget . setAttribute ( 'class' , widget . className . split ( ' ' ) . filter ( cls => cls !== 'visible' ) . join ( ' ' ) ) ;
550
+ if ( widget . style . visibility !== 'hidden' ) {
551
+ widget . style . visibility = 'hidden' ;
552
+ }
553
+ } ) ;
554
+ }
555
+
440
556
private updateMargin ( editor : monacoEditor . editor . IStandaloneCodeEditor ) {
441
557
const editorNode = editor . getDomNode ( ) ;
442
558
if ( editorNode ) {
0 commit comments