123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
- return c > 3 && r && Object.defineProperty(target, key, r), r;
- };
- var __param = (this && this.__param) || function (paramIndex, decorator) {
- return function (target, key) { decorator(target, key, paramIndex); }
- };
- import * as dom from '../../../base/browser/dom.js';
- import { HoverAction, HoverWidget } from '../../../base/browser/ui/hover/hoverWidget.js';
- import { Widget } from '../../../base/browser/ui/widget.js';
- import { coalesce } from '../../../base/common/arrays.js';
- import { Disposable, DisposableStore } from '../../../base/common/lifecycle.js';
- import { Position } from '../../common/core/position.js';
- import { Range } from '../../common/core/range.js';
- import { ModelDecorationOptions } from '../../common/model/textModel.js';
- import { TokenizationRegistry } from '../../common/modes.js';
- import { ColorHoverParticipant } from './colorHoverParticipant.js';
- import { HoverOperation } from './hoverOperation.js';
- import { HoverRangeAnchor } from './hoverTypes.js';
- import { MarkdownHoverParticipant } from './markdownHoverParticipant.js';
- import { MarkerHoverParticipant } from './markerHoverParticipant.js';
- import { InlineCompletionsHoverParticipant } from '../inlineCompletions/inlineCompletionsHoverParticipant.js';
- import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
- import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
- import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';
- import { Context as SuggestContext } from '../suggest/suggest.js';
- import { UnicodeHighlighterHoverParticipant } from '../unicodeHighlighter/unicodeHighlighter.js';
- import { AsyncIterableObject } from '../../../base/common/async.js';
- const $ = dom.$;
- let EditorHoverStatusBar = class EditorHoverStatusBar extends Disposable {
- constructor(_keybindingService) {
- super();
- this._keybindingService = _keybindingService;
- this._hasContent = false;
- this.hoverElement = $('div.hover-row.status-bar');
- this.actionsElement = dom.append(this.hoverElement, $('div.actions'));
- }
- get hasContent() {
- return this._hasContent;
- }
- addAction(actionOptions) {
- const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId);
- const keybindingLabel = keybinding ? keybinding.getLabel() : null;
- this._hasContent = true;
- return this._register(HoverAction.render(this.actionsElement, actionOptions, keybindingLabel));
- }
- append(element) {
- const result = dom.append(this.actionsElement, element);
- this._hasContent = true;
- return result;
- }
- };
- EditorHoverStatusBar = __decorate([
- __param(0, IKeybindingService)
- ], EditorHoverStatusBar);
- class ModesContentComputer {
- constructor(editor, _participants) {
- this._participants = _participants;
- this._editor = editor;
- this._result = [];
- this._anchor = null;
- }
- setAnchor(anchor) {
- this._anchor = anchor;
- this._result = [];
- }
- clearResult() {
- this._result = [];
- }
- static _getLineDecorations(editor, anchor) {
- if (anchor.type !== 1 /* Range */) {
- return [];
- }
- const model = editor.getModel();
- const lineNumber = anchor.range.startLineNumber;
- const maxColumn = model.getLineMaxColumn(lineNumber);
- return editor.getLineDecorations(lineNumber).filter((d) => {
- if (d.options.isWholeLine) {
- return true;
- }
- const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
- const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
- if (d.options.showIfCollapsed) {
- // Relax check around `showIfCollapsed` decorations to also include +/- 1 character
- if (startColumn > anchor.range.startColumn + 1 || anchor.range.endColumn - 1 > endColumn) {
- return false;
- }
- }
- else {
- if (startColumn > anchor.range.startColumn || anchor.range.endColumn > endColumn) {
- return false;
- }
- }
- return true;
- });
- }
- computeAsync(token) {
- const anchor = this._anchor;
- if (!this._editor.hasModel() || !anchor) {
- return AsyncIterableObject.EMPTY;
- }
- const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, anchor);
- return AsyncIterableObject.merge(this._participants.map(participant => this._computeAsync(participant, lineDecorations, anchor, token)));
- }
- _computeAsync(participant, lineDecorations, anchor, token) {
- if (!participant.computeAsync) {
- return AsyncIterableObject.EMPTY;
- }
- return participant.computeAsync(anchor, lineDecorations, token);
- }
- computeSync() {
- if (!this._editor.hasModel() || !this._anchor) {
- return [];
- }
- const lineDecorations = ModesContentComputer._getLineDecorations(this._editor, this._anchor);
- let result = [];
- for (const participant of this._participants) {
- result = result.concat(participant.computeSync(this._anchor, lineDecorations));
- }
- return coalesce(result);
- }
- onResult(result, isFromSynchronousComputation) {
- // Always put synchronous messages before asynchronous ones
- if (isFromSynchronousComputation) {
- this._result = result.concat(this._result);
- }
- else {
- this._result = this._result.concat(result);
- }
- }
- getResult() {
- return this._result.slice(0);
- }
- getResultWithLoadingMessage() {
- if (this._anchor) {
- for (const participant of this._participants) {
- if (participant.createLoadingMessage) {
- const loadingMessage = participant.createLoadingMessage(this._anchor);
- if (loadingMessage) {
- return this._result.slice(0).concat([loadingMessage]);
- }
- }
- }
- }
- return this._result.slice(0);
- }
- }
- let ModesContentHoverWidget = class ModesContentHoverWidget extends Widget {
- constructor(editor, _hoverVisibleKey, instantiationService, _keybindingService, _contextKeyService) {
- super();
- this._hoverVisibleKey = _hoverVisibleKey;
- this._keybindingService = _keybindingService;
- this._contextKeyService = _contextKeyService;
- // IContentWidget.allowEditorOverflow
- this.allowEditorOverflow = true;
- this._participants = [
- instantiationService.createInstance(ColorHoverParticipant, editor, this),
- instantiationService.createInstance(MarkdownHoverParticipant, editor, this),
- instantiationService.createInstance(InlineCompletionsHoverParticipant, editor, this),
- instantiationService.createInstance(UnicodeHighlighterHoverParticipant, editor, this),
- instantiationService.createInstance(MarkerHoverParticipant, editor, this),
- ];
- this._editor = editor;
- this._isVisible = false;
- this._stoleFocus = false;
- this._renderDisposable = null;
- this._hover = this._register(new HoverWidget());
- this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
- this.onkeydown(this._hover.containerDomNode, (e) => {
- if (e.equals(9 /* Escape */)) {
- this.hide();
- }
- });
- this._register(this._editor.onDidChangeConfiguration((e) => {
- if (e.hasChanged(43 /* fontInfo */)) {
- this._updateFont();
- }
- }));
- this._editor.onDidLayoutChange(() => this.layout());
- this.layout();
- this._editor.addContentWidget(this);
- this._showAtPosition = null;
- this._showAtRange = null;
- this._stoleFocus = false;
- this._messages = [];
- this._messagesAreComplete = false;
- this._lastAnchor = null;
- this._computer = new ModesContentComputer(this._editor, this._participants);
- this._highlightDecorations = [];
- this._isChangingDecorations = false;
- this._shouldFocus = false;
- this._colorPicker = null;
- this._preferAbove = this._editor.getOption(52 /* hover */).above;
- this._hoverOperation = new HoverOperation(this._computer, result => this._withResult(result, true), null, result => this._withResult(result, false), this._editor.getOption(52 /* hover */).delay);
- this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => {
- if (this._colorPicker) {
- this.getDomNode().classList.add('colorpicker-hover');
- }
- }));
- this._register(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.BLUR, () => {
- this.getDomNode().classList.remove('colorpicker-hover');
- }));
- this._register(editor.onDidChangeConfiguration(() => {
- this._hoverOperation.setHoverTime(this._editor.getOption(52 /* hover */).delay);
- this._preferAbove = this._editor.getOption(52 /* hover */).above;
- }));
- this._register(TokenizationRegistry.onDidChange(() => {
- if (this._isVisible && this._lastAnchor && this._messages.length > 0) {
- this._hover.contentsDomNode.textContent = '';
- this._renderMessages(this._lastAnchor, this._messages);
- }
- }));
- }
- dispose() {
- this._hoverOperation.cancel();
- this._editor.removeContentWidget(this);
- super.dispose();
- }
- getId() {
- return ModesContentHoverWidget.ID;
- }
- getDomNode() {
- return this._hover.containerDomNode;
- }
- _shouldShowAt(mouseEvent) {
- const targetType = mouseEvent.target.type;
- if (targetType === 6 /* CONTENT_TEXT */) {
- return true;
- }
- if (targetType === 7 /* CONTENT_EMPTY */) {
- const epsilon = this._editor.getOption(43 /* fontInfo */).typicalHalfwidthCharacterWidth / 2;
- const data = mouseEvent.target.detail;
- if (data && !data.isAfterLines && typeof data.horizontalDistanceToText === 'number' && data.horizontalDistanceToText < epsilon) {
- // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough
- return true;
- }
- }
- return false;
- }
- maybeShowAt(mouseEvent) {
- var _a;
- const anchorCandidates = [];
- for (const participant of this._participants) {
- if (typeof participant.suggestHoverAnchor === 'function') {
- const anchor = participant.suggestHoverAnchor(mouseEvent);
- if (anchor) {
- anchorCandidates.push(anchor);
- }
- }
- }
- if (this._shouldShowAt(mouseEvent) && mouseEvent.target.range) {
- // TODO@rebornix. This should be removed if we move Color Picker out of Hover component.
- // Check if mouse is hovering on color decorator
- const hoverOnColorDecorator = [...((_a = mouseEvent.target.element) === null || _a === void 0 ? void 0 : _a.classList.values()) || []].find(className => className.startsWith('ced-colorBox'))
- && mouseEvent.target.range.endColumn - mouseEvent.target.range.startColumn === 1;
- const showAtRange = (hoverOnColorDecorator // shift the mouse focus by one as color decorator is a `before` decoration of next character.
- ? new Range(mouseEvent.target.range.startLineNumber, mouseEvent.target.range.startColumn + 1, mouseEvent.target.range.endLineNumber, mouseEvent.target.range.endColumn + 1)
- : mouseEvent.target.range);
- anchorCandidates.push(new HoverRangeAnchor(0, showAtRange));
- }
- if (anchorCandidates.length === 0) {
- return false;
- }
- anchorCandidates.sort((a, b) => b.priority - a.priority);
- this._startShowingAt(anchorCandidates[0], 0 /* Delayed */, false);
- return true;
- }
- _showAt(position, range, focus) {
- // Position has changed
- this._showAtPosition = position;
- this._showAtRange = range;
- this._hoverVisibleKey.set(true);
- this._isVisible = true;
- this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
- 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._hover.containerDomNode.focus();
- }
- }
- getPosition() {
- if (this._isVisible) {
- let preferAbove = this._preferAbove;
- if (!preferAbove && this._contextKeyService.getContextKeyValue(SuggestContext.Visible.key)) {
- // Prefer rendering above if the suggest widget is visible
- preferAbove = true;
- }
- return {
- position: this._showAtPosition,
- range: this._showAtRange,
- preference: preferAbove ? [
- 1 /* ABOVE */,
- 2 /* BELOW */,
- ] : [
- 2 /* BELOW */,
- 1 /* ABOVE */,
- ],
- };
- }
- return null;
- }
- _updateFont() {
- const codeClasses = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code'));
- codeClasses.forEach(node => this._editor.applyFontInfo(node));
- }
- _updateContents(node) {
- this._hover.contentsDomNode.textContent = '';
- this._hover.contentsDomNode.appendChild(node);
- this._updateFont();
- this._editor.layoutContentWidget(this);
- this._hover.onContentsChanged();
- }
- layout() {
- const height = Math.max(this._editor.getLayoutInfo().height / 4, 250);
- const { fontSize, lineHeight } = this._editor.getOption(43 /* fontInfo */);
- this._hover.contentsDomNode.style.fontSize = `${fontSize}px`;
- this._hover.contentsDomNode.style.lineHeight = `${lineHeight / fontSize}`;
- this._hover.contentsDomNode.style.maxHeight = `${height}px`;
- this._hover.contentsDomNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`;
- }
- onModelDecorationsChanged() {
- if (this._isChangingDecorations) {
- return;
- }
- if (this._isVisible) {
- // The decorations have changed and the hover is visible,
- // we need to recompute the displayed text
- this._hoverOperation.cancel();
- this._computer.clearResult();
- if (!this._colorPicker) { // TODO@Michel ensure that displayed text for other decorations is computed even if color picker is in place
- this._hoverOperation.start(0 /* Delayed */);
- }
- }
- }
- startShowingAtRange(range, mode, focus) {
- this._startShowingAt(new HoverRangeAnchor(0, range), mode, focus);
- }
- _startShowingAt(anchor, mode, focus) {
- if (this._lastAnchor && this._lastAnchor.equals(anchor)) {
- // We have to show the widget at the exact same range as before, so no work is needed
- return;
- }
- this._hoverOperation.cancel();
- if (this._isVisible) {
- // The range might have changed, but the hover is visible
- // Instead of hiding it completely, filter out messages that are still in the new range and
- // kick off a new computation
- if (!this._showAtPosition || !this._lastAnchor || !anchor.canAdoptVisibleHover(this._lastAnchor, this._showAtPosition)) {
- this.hide();
- }
- else {
- const filteredMessages = this._messages.filter((m) => m.isValidForHoverAnchor(anchor));
- if (filteredMessages.length === 0) {
- this.hide();
- }
- else if (filteredMessages.length === this._messages.length && this._messagesAreComplete) {
- // no change
- return;
- }
- else {
- this._renderMessages(anchor, filteredMessages);
- }
- }
- }
- this._lastAnchor = anchor;
- this._computer.setAnchor(anchor);
- this._shouldFocus = focus;
- this._hoverOperation.start(mode);
- }
- hide() {
- this._lastAnchor = null;
- this._hoverOperation.cancel();
- if (this._isVisible) {
- setTimeout(() => {
- // Give commands a chance to see the key
- if (!this._isVisible) {
- this._hoverVisibleKey.set(false);
- }
- }, 0);
- this._isVisible = false;
- this._hover.containerDomNode.classList.toggle('hidden', !this._isVisible);
- this._editor.layoutContentWidget(this);
- if (this._stoleFocus) {
- this._editor.focus();
- }
- }
- this._isChangingDecorations = true;
- this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, []);
- this._isChangingDecorations = false;
- if (this._renderDisposable) {
- this._renderDisposable.dispose();
- this._renderDisposable = null;
- }
- this._colorPicker = null;
- }
- isColorPickerVisible() {
- return !!this._colorPicker;
- }
- setColorPicker(widget) {
- this._colorPicker = widget;
- }
- onContentsChanged() {
- this._hover.onContentsChanged();
- }
- _withResult(result, complete) {
- this._messages = result;
- this._messagesAreComplete = complete;
- if (this._lastAnchor && this._messages.length > 0) {
- this._renderMessages(this._lastAnchor, this._messages);
- }
- else if (complete) {
- this.hide();
- }
- }
- _renderMessages(anchor, messages) {
- if (this._renderDisposable) {
- this._renderDisposable.dispose();
- this._renderDisposable = null;
- }
- this._colorPicker = null; // TODO: TypeScript thinks this is always null
- // update column from which to show
- let renderColumn = 1073741824 /* MAX_SAFE_SMALL_INTEGER */;
- let highlightRange = messages[0].range;
- let forceShowAtRange = null;
- let fragment = document.createDocumentFragment();
- const disposables = new DisposableStore();
- const hoverParts = new Map();
- for (const msg of messages) {
- renderColumn = Math.min(renderColumn, msg.range.startColumn);
- highlightRange = Range.plusRange(highlightRange, msg.range);
- if (msg.forceShowAtRange) {
- forceShowAtRange = msg.range;
- }
- if (!hoverParts.has(msg.owner)) {
- hoverParts.set(msg.owner, []);
- }
- const dest = hoverParts.get(msg.owner);
- dest.push(msg);
- }
- const statusBar = disposables.add(new EditorHoverStatusBar(this._keybindingService));
- for (const participant of this._participants) {
- if (hoverParts.has(participant)) {
- const participantHoverParts = hoverParts.get(participant);
- disposables.add(participant.renderHoverParts(participantHoverParts, fragment, statusBar));
- }
- }
- if (statusBar.hasContent) {
- fragment.appendChild(statusBar.hoverElement);
- }
- this._renderDisposable = disposables;
- // show
- if (fragment.hasChildNodes()) {
- if (forceShowAtRange) {
- this._showAt(forceShowAtRange.getStartPosition(), forceShowAtRange, this._shouldFocus);
- }
- else {
- this._showAt(new Position(anchor.range.startLineNumber, renderColumn), highlightRange, this._shouldFocus);
- }
- this._updateContents(fragment);
- }
- if (this._colorPicker) {
- this._colorPicker.layout();
- }
- this._isChangingDecorations = true;
- this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, highlightRange ? [{
- range: highlightRange,
- options: ModesContentHoverWidget._DECORATION_OPTIONS
- }] : []);
- this._isChangingDecorations = false;
- }
- };
- ModesContentHoverWidget.ID = 'editor.contrib.modesContentHoverWidget';
- ModesContentHoverWidget._DECORATION_OPTIONS = ModelDecorationOptions.register({
- description: 'content-hover-highlight',
- className: 'hoverHighlight'
- });
- ModesContentHoverWidget = __decorate([
- __param(2, IInstantiationService),
- __param(3, IKeybindingService),
- __param(4, IContextKeyService)
- ], ModesContentHoverWidget);
- export { ModesContentHoverWidget };
|