Path: blob/main/src/vs/editor/contrib/message/browser/messageController.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js';6import { alert } from '../../../../base/browser/ui/aria/aria.js';7import { Event } from '../../../../base/common/event.js';8import { IMarkdownString, isMarkdownString } from '../../../../base/common/htmlContent.js';9import { KeyCode } from '../../../../base/common/keyCodes.js';10import { DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';11import './messageController.css';12import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from '../../../browser/editorBrowser.js';13import { EditorCommand, EditorContributionInstantiation, registerEditorCommand, registerEditorContribution } from '../../../browser/editorExtensions.js';14import { IPosition } from '../../../common/core/position.js';15import { Range } from '../../../common/core/range.js';16import { IEditorContribution, ScrollType } from '../../../common/editorCommon.js';17import { PositionAffinity } from '../../../common/model.js';18import { openLinkFromMarkdown } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js';19import * as nls from '../../../../nls.js';20import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';21import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';22import { IOpenerService } from '../../../../platform/opener/common/opener.js';23import * as dom from '../../../../base/browser/dom.js';2425export class MessageController implements IEditorContribution {2627public static readonly ID = 'editor.contrib.messageController';2829static readonly MESSAGE_VISIBLE = new RawContextKey<boolean>('messageVisible', false, nls.localize('messageVisible', 'Whether the editor is currently showing an inline message'));3031static get(editor: ICodeEditor): MessageController | null {32return editor.getContribution<MessageController>(MessageController.ID);33}3435private readonly _editor: ICodeEditor;36private readonly _visible: IContextKey<boolean>;37private readonly _messageWidget = new MutableDisposable<MessageWidget>();38private readonly _messageListeners = new DisposableStore();39private _mouseOverMessage: boolean = false;4041constructor(42editor: ICodeEditor,43@IContextKeyService contextKeyService: IContextKeyService,44@IOpenerService private readonly _openerService: IOpenerService45) {4647this._editor = editor;48this._visible = MessageController.MESSAGE_VISIBLE.bindTo(contextKeyService);49}5051dispose(): void {52this._messageListeners.dispose();53this._messageWidget.dispose();54this._visible.reset();55}5657isVisible() {58return this._visible.get();59}6061showMessage(message: IMarkdownString | string, position: IPosition): void {6263alert(isMarkdownString(message) ? message.value : message);6465this._visible.set(true);66this._messageWidget.clear();67this._messageListeners.clear();6869if (isMarkdownString(message)) {70const renderedMessage = this._messageListeners.add(renderMarkdown(message, {71actionHandler: (url, mdStr) => {72this.closeMessage();73openLinkFromMarkdown(this._openerService, url, mdStr.isTrusted);74},75}));76this._messageWidget.value = new MessageWidget(this._editor, position, renderedMessage.element);77} else {78this._messageWidget.value = new MessageWidget(this._editor, position, message);79}8081// close on blur (debounced to allow to tab into the message), cursor, model change, dispose82this._messageListeners.add(Event.debounce(this._editor.onDidBlurEditorText, (last, event) => event, 0)(() => {83if (this._mouseOverMessage) {84return; // override when mouse over message85}8687if (this._messageWidget.value && dom.isAncestor(dom.getActiveElement(), this._messageWidget.value.getDomNode())) {88return; // override when focus is inside the message89}9091this.closeMessage();92}93));94this._messageListeners.add(this._editor.onDidChangeCursorPosition(() => this.closeMessage()));95this._messageListeners.add(this._editor.onDidDispose(() => this.closeMessage()));96this._messageListeners.add(this._editor.onDidChangeModel(() => this.closeMessage()));97this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_ENTER, () => this._mouseOverMessage = true, true));98this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_LEAVE, () => this._mouseOverMessage = false, true));99100// close on mouse move101let bounds: Range;102this._messageListeners.add(this._editor.onMouseMove(e => {103// outside the text area104if (!e.target.position) {105return;106}107108if (!bounds) {109// define bounding box around position and first mouse occurance110bounds = new Range(position.lineNumber - 3, 1, e.target.position.lineNumber + 3, 1);111} else if (!bounds.containsPosition(e.target.position)) {112// check if position is still in bounds113this.closeMessage();114}115}));116}117118closeMessage(): void {119this._visible.reset();120this._messageListeners.clear();121if (this._messageWidget.value) {122this._messageListeners.add(MessageWidget.fadeOut(this._messageWidget.value));123}124}125}126127const MessageCommand = EditorCommand.bindToContribution<MessageController>(MessageController.get);128129130registerEditorCommand(new MessageCommand({131id: 'leaveEditorMessage',132precondition: MessageController.MESSAGE_VISIBLE,133handler: c => c.closeMessage(),134kbOpts: {135weight: KeybindingWeight.EditorContrib + 30,136primary: KeyCode.Escape137}138}));139140class MessageWidget implements IContentWidget {141142// Editor.IContentWidget.allowEditorOverflow143readonly allowEditorOverflow = true;144readonly suppressMouseDown = false;145146private readonly _editor: ICodeEditor;147private readonly _position: IPosition;148private readonly _domNode: HTMLDivElement;149150static fadeOut(messageWidget: MessageWidget): IDisposable {151const dispose = () => {152messageWidget.dispose();153clearTimeout(handle);154messageWidget.getDomNode().removeEventListener('animationend', dispose);155};156const handle = setTimeout(dispose, 110);157messageWidget.getDomNode().addEventListener('animationend', dispose);158messageWidget.getDomNode().classList.add('fadeOut');159return { dispose };160}161162constructor(editor: ICodeEditor, { lineNumber, column }: IPosition, text: HTMLElement | string) {163164this._editor = editor;165this._editor.revealLinesInCenterIfOutsideViewport(lineNumber, lineNumber, ScrollType.Smooth);166this._position = { lineNumber, column };167168this._domNode = document.createElement('div');169this._domNode.classList.add('monaco-editor-overlaymessage');170this._domNode.style.marginLeft = '-6px';171172const anchorTop = document.createElement('div');173anchorTop.classList.add('anchor', 'top');174this._domNode.appendChild(anchorTop);175176const message = document.createElement('div');177if (typeof text === 'string') {178message.classList.add('message');179message.textContent = text;180} else {181text.classList.add('message');182message.appendChild(text);183}184this._domNode.appendChild(message);185186const anchorBottom = document.createElement('div');187anchorBottom.classList.add('anchor', 'below');188this._domNode.appendChild(anchorBottom);189190this._editor.addContentWidget(this);191this._domNode.classList.add('fadeIn');192}193194dispose() {195this._editor.removeContentWidget(this);196}197198getId(): string {199return 'messageoverlay';200}201202getDomNode(): HTMLElement {203return this._domNode;204}205206getPosition(): IContentWidgetPosition {207return {208position: this._position,209preference: [210ContentWidgetPositionPreference.ABOVE,211ContentWidgetPositionPreference.BELOW,212],213positionAffinity: PositionAffinity.Right,214};215}216217afterRender(position: ContentWidgetPositionPreference | null): void {218this._domNode.classList.toggle('below', position === ContentWidgetPositionPreference.BELOW);219}220221}222223registerEditorContribution(MessageController.ID, MessageController, EditorContributionInstantiation.Lazy);224225226