Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/7851.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hide the parameters intellisense widget in the `Notebook Editor` when it is not longer required.
118 changes: 117 additions & 1 deletion src/datascience-ui/react-common/monacoEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
private throttledUpdateWidgetPosition = throttle(this.updateWidgetPosition.bind(this), 100);
private monacoContainer : HTMLDivElement | undefined;

/**
* Reference to parameter widget (used by monaco to display parameter docs).
*
* @private
* @type {Element}
* @memberof MonacoEditor
*/
private parameterWidget?: Element;

constructor(props: IMonacoEditorProps) {
super(props);
this.state = { editor: undefined, model: null, visibleLineCount: -1, attached: false, widgetsReparented: false };
Expand Down Expand Up @@ -157,6 +166,11 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
}
}));

// When editor loses focus, hide parameter widgets (if any currently displayed).
this.subscriptions.push(editor.onDidBlurEditorWidget(() => {
this.hideParameterWidget();
}));

// Track focus changes to make sure we update our widget parent and widget position
this.subscriptions.push(editor.onDidFocusEditorWidget(() => {
this.throttledUpdateWidgetPosition();
Expand Down Expand Up @@ -192,7 +206,10 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
if (window) {
window.removeEventListener('resize', this.windowResized);
}

if (this.parameterWidget){
this.parameterWidget.removeEventListener('mouseleave', this.outermostParentLeave);
this.parameterWidget = undefined;
}
if (this.outermostParent) {
this.outermostParent.removeEventListener('mouseleave', this.outermostParentLeave);
this.outermostParent = null;
Expand Down Expand Up @@ -434,9 +451,108 @@ export class MonacoEditor extends React.Component<IMonacoEditorProps, IMonacoEdi
if (this.state.editor && !this.enteredHover) {
// If we haven't already entered hover, then act like it shuts down
this.onHoverLeave();
// Possible user is viewing the parameter hints, wait before user moves the mouse.
// Waiting for 1s is too long to move the mouse and hide the hints (100ms seems like a good fit).
setTimeout(() => this.hideParameterWidget(), 100);
}
}

/**
* This will hide the parameter widget if the user is not hovering over
* the parameter widget for this monaco editor.
*
* Notes: See issue https://github.com/microsoft/vscode-python/issues/7851 for further info.
* Hide the parameter widget if all of the following conditions have been met:
* - ditor doesn't have focus
* - Mouse is not over the editor
* - Mouse is not over (hovering) the parameter widget
*
* @private
* @returns
* @memberof MonacoEditor
*/
private hideParameterWidget(){
if (!this.state.editor || !this.state.editor.getDomNode() || !this.widgetParent){
return;
}
// Find all elements that the user is hovering over.
// Its possible the parameter widget is one of them.
const hoverElements: Element[] = Array.prototype.slice.call(document.querySelectorAll(':hover'));
// Find all parameter widgets related to this monaco editor that are currently displayed.
const visibleParameterHintsWidgets: Element[] = Array.prototype.slice.call(this.widgetParent.querySelectorAll('.parameter-hints-widget.visible'));
if (hoverElements.length === 0 && visibleParameterHintsWidgets.length === 0){
// If user is not hovering over anything and there are no visible parameter widgets,
// then, we have nothing to do but get out of here.
return;
}

// Find all parameter widgets related to this monaco editor.
const knownParameterHintsWidgets: HTMLDivElement[] = Array.prototype.slice.call(this.widgetParent.querySelectorAll('.parameter-hints-widget'));

// Lets not assume we'll have the exact same DOM for parameter widgets.
// So, just remove the event handler, and add it again later.
if (this.parameterWidget){
this.parameterWidget.removeEventListener('mouseleave', this.outermostParentLeave);
}
// These are the classes that will appear on a parameter widget when they are visible.
const parameterWidgetClasses = ['editor-widget', 'parameter-hints-widget', 'visible'];

// Find the parameter widget the user is currently hovering over.
this.parameterWidget = hoverElements.find(item => {
if (!item.className) {
return false;
}
// Check if user is hovering over a parameter widget.
const classes = item.className.split(' ');
if (!parameterWidgetClasses.every(cls => classes.indexOf(cls) >= 0)){
// Not all classes required in a parameter hint widget are in this element.
// Hence this is not a parameter widget.
return false;
}

// Ok, this element that the user is hovering over is a parameter widget.

// Next, check whether this parameter widget belongs to this monaco editor.
// We have a list of parameter widgets that belong to this editor, hence a simple lookup.
return knownParameterHintsWidgets.some(widget => widget === item);
});

if (this.parameterWidget){
// We know the user is hovering over the parameter widget for this editor.
// Hovering could mean the user is scrolling through a large parameter list.
// We need to add a mouse leave event handler, so as to hide this.
this.parameterWidget.addEventListener('mouseleave', this.outermostParentLeave);

// In case the event handler doesn't get fired, have a backup of checking within 1s.
setTimeout(() => this.hideParameterWidget(), 1000);
return;
}
if (visibleParameterHintsWidgets.length === 0){
// There are no parameter widgets displayed for this editor.
// Hence nothing to do.
return;
}
// If the editor has focus, don't hide the parameter widget.
// This is the default behavior. Let the user hit `Escape` or click somewhere
// to forcefully hide the parameter widget.
if (this.state.editor.hasWidgetFocus()) {
return;
}

// If we got here, then the user is not hovering over the paramter widgets.
// & the editor doesn't have focus.
// However some of the parameter widgets associated with this monaco editor are visible.
// We need to hide them.

// Solution: Hide the widgets manually.
knownParameterHintsWidgets.forEach(widget => {
widget.setAttribute('class', widget.className.split(' ').filter(cls => cls !== 'visible').join(' '));
if (widget.style.visibility !== 'hidden') {
widget.style.visibility = 'hidden';
}
});
}

private updateMargin(editor: monacoEditor.editor.IStandaloneCodeEditor) {
const editorNode = editor.getDomNode();
if (editorNode) {
Expand Down