/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { toggleClass } from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; export class ContentHoverWidget extends Widget implements editorBrowser.IContentWidget { private readonly _id: string; protected _editor: editorBrowser.ICodeEditor; private _isVisible: boolean; private readonly _containerDomNode: HTMLElement; private readonly _domNode: HTMLElement; protected _showAtPosition: Position | null; protected _showAtRange: Range | null; private _stoleFocus: boolean; private readonly scrollbar: DomScrollableElement; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; protected get isVisible(): boolean { return this._isVisible; } protected set isVisible(value: boolean) { this._isVisible = value; toggleClass(this._containerDomNode, 'hidden', !this._isVisible); } constructor(id: string, editor: editorBrowser.ICodeEditor) { super(); this._id = id; this._editor = editor; this._isVisible = false; this._stoleFocus = false; this._containerDomNode = document.createElement('div'); this._containerDomNode.className = 'monaco-editor-hover hidden'; this._containerDomNode.tabIndex = 0; this._domNode = document.createElement('div'); this._domNode.className = 'monaco-editor-hover-content'; this.scrollbar = new DomScrollableElement(this._domNode, {}); this._register(this.scrollbar); this._containerDomNode.appendChild(this.scrollbar.getDomNode()); this.onkeydown(this._containerDomNode, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Escape)) { this.hide(); } }); this._register(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.fontInfo) { this.updateFont(); } })); this._editor.onDidLayoutChange(e => this.layout()); this.layout(); this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; this._stoleFocus = false; } public getId(): string { return this._id; } public getDomNode(): HTMLElement { return this._containerDomNode; } public showAt(position: Position, range: Range | null, focus: boolean): void { // Position has changed this._showAtPosition = position; this._showAtRange = range; this.isVisible = true; this._editor.layoutContentWidget(this); // Simply force a synchronous render on the editor // such that the widget does not really render with left = '0px' this._editor.render(); this._stoleFocus = focus; if (focus) { this._containerDomNode.focus(); } } public hide(): void { if (!this.isVisible) { return; } this.isVisible = false; this._editor.layoutContentWidget(this); if (this._stoleFocus) { this._editor.focus(); } } public getPosition(): editorBrowser.IContentWidgetPosition | null { if (this.isVisible) { return { position: this._showAtPosition, range: this._showAtRange, preference: [ editorBrowser.ContentWidgetPositionPreference.ABOVE, editorBrowser.ContentWidgetPositionPreference.BELOW ] }; } return null; } public dispose(): void { this._editor.removeContentWidget(this); super.dispose(); } private updateFont(): void { const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code')); codeClasses.forEach(node => this._editor.applyFontInfo(node)); } protected updateContents(node: Node): void { this._domNode.textContent = ''; this._domNode.appendChild(node); this.updateFont(); this._editor.layoutContentWidget(this); this.onContentsChange(); } protected onContentsChange(): void { this.scrollbar.scanDomNode(); } private layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getConfiguration().fontInfo; this._domNode.style.fontSize = `${fontSize}px`; this._domNode.style.lineHeight = `${lineHeight}px`; this._domNode.style.maxHeight = `${height}px`; this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; } } export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWidget { private readonly _id: string; protected _editor: editorBrowser.ICodeEditor; private _isVisible: boolean; private readonly _domNode: HTMLElement; protected _showAtLineNumber: number; constructor(id: string, editor: editorBrowser.ICodeEditor) { super(); this._id = id; this._editor = editor; this._isVisible = false; this._domNode = document.createElement('div'); this._domNode.className = 'monaco-editor-hover hidden'; this._domNode.setAttribute('aria-hidden', 'true'); this._domNode.setAttribute('role', 'presentation'); this._showAtLineNumber = -1; this._register(this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.fontInfo) { this.updateFont(); } })); this._editor.addOverlayWidget(this); } protected get isVisible(): boolean { return this._isVisible; } protected set isVisible(value: boolean) { this._isVisible = value; toggleClass(this._domNode, 'hidden', !this._isVisible); } public getId(): string { return this._id; } public getDomNode(): HTMLElement { return this._domNode; } public showAt(lineNumber: number): void { this._showAtLineNumber = lineNumber; if (!this.isVisible) { this.isVisible = true; } const editorLayout = this._editor.getLayoutInfo(); const topForLineNumber = this._editor.getTopForLineNumber(this._showAtLineNumber); const editorScrollTop = this._editor.getScrollTop(); const lineHeight = this._editor.getConfiguration().lineHeight; const nodeHeight = this._domNode.clientHeight; const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); this._domNode.style.left = `${editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth}px`; this._domNode.style.top = `${Math.max(Math.round(top), 0)}px`; } public hide(): void { if (!this.isVisible) { return; } this.isVisible = false; } public getPosition(): editorBrowser.IOverlayWidgetPosition | null { return null; } public dispose(): void { this._editor.removeOverlayWidget(this); super.dispose(); } private updateFont(): void { const codeTags: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByTagName('code')); const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code')); [...codeTags, ...codeClasses].forEach(node => this._editor.applyFontInfo(node)); } protected updateContents(node: Node): void { this._domNode.textContent = ''; this._domNode.appendChild(node); this.updateFont(); } }