monaco-editor-core
Version:
A browser based code editor
379 lines (378 loc) • 21.4 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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 { asArray, compareBy, numberComparator } from '../../../../base/common/arrays.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import { isEmptyMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { MarkdownRenderer } from '../../../browser/widget/markdownRenderer/browser/markdownRenderer.js';
import { DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js';
import { Range } from '../../../common/core/range.js';
import { ILanguageService } from '../../../common/languages/language.js';
import { RenderedHoverParts } from './hoverTypes.js';
import * as nls from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { HoverVerbosityAction } from '../../../common/languages.js';
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { onUnexpectedExternalError } from '../../../../base/common/errors.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { ClickAction, KeyDownAction } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { IHoverService, WorkbenchHoverDelegate } from '../../../../platform/hover/browser/hover.js';
import { AsyncIterableObject } from '../../../../base/common/async.js';
import { getHoverProviderResultsAsAsyncIterable } from './getHover.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
const $ = dom.$;
const increaseHoverVerbosityIcon = registerIcon('hover-increase-verbosity', Codicon.add, nls.localize('increaseHoverVerbosity', 'Icon for increaseing hover verbosity.'));
const decreaseHoverVerbosityIcon = registerIcon('hover-decrease-verbosity', Codicon.remove, nls.localize('decreaseHoverVerbosity', 'Icon for decreasing hover verbosity.'));
export class MarkdownHover {
constructor(owner, range, contents, isBeforeContent, ordinal, source = undefined) {
this.owner = owner;
this.range = range;
this.contents = contents;
this.isBeforeContent = isBeforeContent;
this.ordinal = ordinal;
this.source = source;
}
isValidForHoverAnchor(anchor) {
return (anchor.type === 1 /* HoverAnchorType.Range */
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn);
}
}
class HoverSource {
constructor(hover, hoverProvider, hoverPosition) {
this.hover = hover;
this.hoverProvider = hoverProvider;
this.hoverPosition = hoverPosition;
}
supportsVerbosityAction(hoverVerbosityAction) {
switch (hoverVerbosityAction) {
case HoverVerbosityAction.Increase:
return this.hover.canIncreaseVerbosity ?? false;
case HoverVerbosityAction.Decrease:
return this.hover.canDecreaseVerbosity ?? false;
}
}
}
let MarkdownHoverParticipant = class MarkdownHoverParticipant {
constructor(_editor, _languageService, _openerService, _configurationService, _languageFeaturesService, _keybindingService, _hoverService, _commandService) {
this._editor = _editor;
this._languageService = _languageService;
this._openerService = _openerService;
this._configurationService = _configurationService;
this._languageFeaturesService = _languageFeaturesService;
this._keybindingService = _keybindingService;
this._hoverService = _hoverService;
this._commandService = _commandService;
this.hoverOrdinal = 3;
}
createLoadingMessage(anchor) {
return new MarkdownHover(this, anchor.range, [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))], false, 2000);
}
computeSync(anchor, lineDecorations) {
if (!this._editor.hasModel() || anchor.type !== 1 /* HoverAnchorType.Range */) {
return [];
}
const model = this._editor.getModel();
const lineNumber = anchor.range.startLineNumber;
const maxColumn = model.getLineMaxColumn(lineNumber);
const result = [];
let index = 1000;
const lineLength = model.getLineLength(lineNumber);
const languageId = model.getLanguageIdAtPosition(anchor.range.startLineNumber, anchor.range.startColumn);
const stopRenderingLineAfter = this._editor.getOption(118 /* EditorOption.stopRenderingLineAfter */);
const maxTokenizationLineLength = this._configurationService.getValue('editor.maxTokenizationLineLength', {
overrideIdentifier: languageId
});
let stopRenderingMessage = false;
if (stopRenderingLineAfter >= 0 && lineLength > stopRenderingLineAfter && anchor.range.startColumn >= stopRenderingLineAfter) {
stopRenderingMessage = true;
result.push(new MarkdownHover(this, anchor.range, [{
value: nls.localize('stopped rendering', "Rendering paused for long line for performance reasons. This can be configured via `editor.stopRenderingLineAfter`.")
}], false, index++));
}
if (!stopRenderingMessage && typeof maxTokenizationLineLength === 'number' && lineLength >= maxTokenizationLineLength) {
result.push(new MarkdownHover(this, anchor.range, [{
value: nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. This can be configured via `editor.maxTokenizationLineLength`.")
}], false, index++));
}
let isBeforeContent = false;
for (const d of lineDecorations) {
const startColumn = (d.range.startLineNumber === lineNumber) ? d.range.startColumn : 1;
const endColumn = (d.range.endLineNumber === lineNumber) ? d.range.endColumn : maxColumn;
const hoverMessage = d.options.hoverMessage;
if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
continue;
}
if (d.options.beforeContentClassName) {
isBeforeContent = true;
}
const range = new Range(anchor.range.startLineNumber, startColumn, anchor.range.startLineNumber, endColumn);
result.push(new MarkdownHover(this, range, asArray(hoverMessage), isBeforeContent, index++));
}
return result;
}
computeAsync(anchor, lineDecorations, token) {
if (!this._editor.hasModel() || anchor.type !== 1 /* HoverAnchorType.Range */) {
return AsyncIterableObject.EMPTY;
}
const model = this._editor.getModel();
const hoverProviderRegistry = this._languageFeaturesService.hoverProvider;
if (!hoverProviderRegistry.has(model)) {
return AsyncIterableObject.EMPTY;
}
const markdownHovers = this._getMarkdownHovers(hoverProviderRegistry, model, anchor, token);
return markdownHovers;
}
_getMarkdownHovers(hoverProviderRegistry, model, anchor, token) {
const position = anchor.range.getStartPosition();
const hoverProviderResults = getHoverProviderResultsAsAsyncIterable(hoverProviderRegistry, model, position, token);
const markdownHovers = hoverProviderResults.filter(item => !isEmptyMarkdownString(item.hover.contents))
.map(item => {
const range = item.hover.range ? Range.lift(item.hover.range) : anchor.range;
const hoverSource = new HoverSource(item.hover, item.provider, position);
return new MarkdownHover(this, range, item.hover.contents, false, item.ordinal, hoverSource);
});
return markdownHovers;
}
renderHoverParts(context, hoverParts) {
this._renderedHoverParts = new MarkdownRenderedHoverParts(hoverParts, context.fragment, this, this._editor, this._languageService, this._openerService, this._commandService, this._keybindingService, this._hoverService, this._configurationService, context.onContentsChanged);
return this._renderedHoverParts;
}
updateMarkdownHoverVerbosityLevel(action, index, focus) {
return Promise.resolve(this._renderedHoverParts?.updateMarkdownHoverPartVerbosityLevel(action, index, focus));
}
};
MarkdownHoverParticipant = __decorate([
__param(1, ILanguageService),
__param(2, IOpenerService),
__param(3, IConfigurationService),
__param(4, ILanguageFeaturesService),
__param(5, IKeybindingService),
__param(6, IHoverService),
__param(7, ICommandService)
], MarkdownHoverParticipant);
export { MarkdownHoverParticipant };
class RenderedMarkdownHoverPart {
constructor(hoverPart, hoverElement, disposables) {
this.hoverPart = hoverPart;
this.hoverElement = hoverElement;
this.disposables = disposables;
}
dispose() {
this.disposables.dispose();
}
}
class MarkdownRenderedHoverParts {
constructor(hoverParts, hoverPartsContainer, _hoverParticipant, _editor, _languageService, _openerService, _commandService, _keybindingService, _hoverService, _configurationService, _onFinishedRendering) {
this._hoverParticipant = _hoverParticipant;
this._editor = _editor;
this._languageService = _languageService;
this._openerService = _openerService;
this._commandService = _commandService;
this._keybindingService = _keybindingService;
this._hoverService = _hoverService;
this._configurationService = _configurationService;
this._onFinishedRendering = _onFinishedRendering;
this._ongoingHoverOperations = new Map();
this._disposables = new DisposableStore();
this.renderedHoverParts = this._renderHoverParts(hoverParts, hoverPartsContainer, this._onFinishedRendering);
this._disposables.add(toDisposable(() => {
this.renderedHoverParts.forEach(renderedHoverPart => {
renderedHoverPart.dispose();
});
this._ongoingHoverOperations.forEach(operation => {
operation.tokenSource.dispose(true);
});
}));
}
_renderHoverParts(hoverParts, hoverPartsContainer, onFinishedRendering) {
hoverParts.sort(compareBy(hover => hover.ordinal, numberComparator));
return hoverParts.map(hoverPart => {
const renderedHoverPart = this._renderHoverPart(hoverPart, onFinishedRendering);
hoverPartsContainer.appendChild(renderedHoverPart.hoverElement);
return renderedHoverPart;
});
}
_renderHoverPart(hoverPart, onFinishedRendering) {
const renderedMarkdownPart = this._renderMarkdownHover(hoverPart, onFinishedRendering);
const renderedMarkdownElement = renderedMarkdownPart.hoverElement;
const hoverSource = hoverPart.source;
const disposables = new DisposableStore();
disposables.add(renderedMarkdownPart);
if (!hoverSource) {
return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables);
}
const canIncreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Increase);
const canDecreaseVerbosity = hoverSource.supportsVerbosityAction(HoverVerbosityAction.Decrease);
if (!canIncreaseVerbosity && !canDecreaseVerbosity) {
return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables);
}
const actionsContainer = $('div.verbosity-actions');
renderedMarkdownElement.prepend(actionsContainer);
disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Increase, canIncreaseVerbosity));
disposables.add(this._renderHoverExpansionAction(actionsContainer, HoverVerbosityAction.Decrease, canDecreaseVerbosity));
return new RenderedMarkdownHoverPart(hoverPart, renderedMarkdownElement, disposables);
}
_renderMarkdownHover(markdownHover, onFinishedRendering) {
const renderedMarkdownHover = renderMarkdownInContainer(this._editor, markdownHover, this._languageService, this._openerService, onFinishedRendering);
return renderedMarkdownHover;
}
_renderHoverExpansionAction(container, action, actionEnabled) {
const store = new DisposableStore();
const isActionIncrease = action === HoverVerbosityAction.Increase;
const actionElement = dom.append(container, $(ThemeIcon.asCSSSelector(isActionIncrease ? increaseHoverVerbosityIcon : decreaseHoverVerbosityIcon)));
actionElement.tabIndex = 0;
const hoverDelegate = new WorkbenchHoverDelegate('mouse', false, { target: container, position: { hoverPosition: 0 /* HoverPosition.LEFT */ } }, this._configurationService, this._hoverService);
store.add(this._hoverService.setupManagedHover(hoverDelegate, actionElement, labelForHoverVerbosityAction(this._keybindingService, action)));
if (!actionEnabled) {
actionElement.classList.add('disabled');
return store;
}
actionElement.classList.add('enabled');
const actionFunction = () => this._commandService.executeCommand(action === HoverVerbosityAction.Increase ? INCREASE_HOVER_VERBOSITY_ACTION_ID : DECREASE_HOVER_VERBOSITY_ACTION_ID);
store.add(new ClickAction(actionElement, actionFunction));
store.add(new KeyDownAction(actionElement, actionFunction, [3 /* KeyCode.Enter */, 10 /* KeyCode.Space */]));
return store;
}
async updateMarkdownHoverPartVerbosityLevel(action, index, focus = true) {
const model = this._editor.getModel();
if (!model) {
return undefined;
}
const hoverRenderedPart = this._getRenderedHoverPartAtIndex(index);
const hoverSource = hoverRenderedPart?.hoverPart.source;
if (!hoverRenderedPart || !hoverSource?.supportsVerbosityAction(action)) {
return undefined;
}
const newHover = await this._fetchHover(hoverSource, model, action);
if (!newHover) {
return undefined;
}
const newHoverSource = new HoverSource(newHover, hoverSource.hoverProvider, hoverSource.hoverPosition);
const initialHoverPart = hoverRenderedPart.hoverPart;
const newHoverPart = new MarkdownHover(this._hoverParticipant, initialHoverPart.range, newHover.contents, initialHoverPart.isBeforeContent, initialHoverPart.ordinal, newHoverSource);
const newHoverRenderedPart = this._renderHoverPart(newHoverPart, this._onFinishedRendering);
this._replaceRenderedHoverPartAtIndex(index, newHoverRenderedPart, newHoverPart);
if (focus) {
this._focusOnHoverPartWithIndex(index);
}
return {
hoverPart: newHoverPart,
hoverElement: newHoverRenderedPart.hoverElement
};
}
async _fetchHover(hoverSource, model, action) {
let verbosityDelta = action === HoverVerbosityAction.Increase ? 1 : -1;
const provider = hoverSource.hoverProvider;
const ongoingHoverOperation = this._ongoingHoverOperations.get(provider);
if (ongoingHoverOperation) {
ongoingHoverOperation.tokenSource.cancel();
verbosityDelta += ongoingHoverOperation.verbosityDelta;
}
const tokenSource = new CancellationTokenSource();
this._ongoingHoverOperations.set(provider, { verbosityDelta, tokenSource });
const context = { verbosityRequest: { verbosityDelta, previousHover: hoverSource.hover } };
let hover;
try {
hover = await Promise.resolve(provider.provideHover(model, hoverSource.hoverPosition, tokenSource.token, context));
}
catch (e) {
onUnexpectedExternalError(e);
}
tokenSource.dispose();
this._ongoingHoverOperations.delete(provider);
return hover;
}
_replaceRenderedHoverPartAtIndex(index, renderedHoverPart, hoverPart) {
if (index >= this.renderedHoverParts.length || index < 0) {
return;
}
const currentRenderedHoverPart = this.renderedHoverParts[index];
const currentRenderedMarkdown = currentRenderedHoverPart.hoverElement;
const renderedMarkdown = renderedHoverPart.hoverElement;
const renderedChildrenElements = Array.from(renderedMarkdown.children);
currentRenderedMarkdown.replaceChildren(...renderedChildrenElements);
const newRenderedHoverPart = new RenderedMarkdownHoverPart(hoverPart, currentRenderedMarkdown, renderedHoverPart.disposables);
currentRenderedMarkdown.focus();
currentRenderedHoverPart.dispose();
this.renderedHoverParts[index] = newRenderedHoverPart;
}
_focusOnHoverPartWithIndex(index) {
this.renderedHoverParts[index].hoverElement.focus();
}
_getRenderedHoverPartAtIndex(index) {
return this.renderedHoverParts[index];
}
dispose() {
this._disposables.dispose();
}
}
export function renderMarkdownHovers(context, markdownHovers, editor, languageService, openerService) {
// Sort hover parts to keep them stable since they might come in async, out-of-order
markdownHovers.sort(compareBy(hover => hover.ordinal, numberComparator));
const renderedHoverParts = [];
for (const markdownHover of markdownHovers) {
renderedHoverParts.push(renderMarkdownInContainer(editor, markdownHover, languageService, openerService, context.onContentsChanged));
}
return new RenderedHoverParts(renderedHoverParts);
}
function renderMarkdownInContainer(editor, markdownHover, languageService, openerService, onFinishedRendering) {
const disposables = new DisposableStore();
const renderedMarkdown = $('div.hover-row');
const renderedMarkdownContents = $('div.hover-row-contents');
renderedMarkdown.appendChild(renderedMarkdownContents);
const markdownStrings = markdownHover.contents;
for (const markdownString of markdownStrings) {
if (isEmptyMarkdownString(markdownString)) {
continue;
}
const markdownHoverElement = $('div.markdown-hover');
const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents'));
const renderer = disposables.add(new MarkdownRenderer({ editor }, languageService, openerService));
disposables.add(renderer.onDidRenderAsync(() => {
hoverContentsElement.className = 'hover-contents code-hover-contents';
onFinishedRendering();
}));
const renderedContents = disposables.add(renderer.render(markdownString));
hoverContentsElement.appendChild(renderedContents.element);
renderedMarkdownContents.appendChild(markdownHoverElement);
}
const renderedHoverPart = {
hoverPart: markdownHover,
hoverElement: renderedMarkdown,
dispose() { disposables.dispose(); }
};
return renderedHoverPart;
}
export function labelForHoverVerbosityAction(keybindingService, action) {
switch (action) {
case HoverVerbosityAction.Increase: {
const kb = keybindingService.lookupKeybinding(INCREASE_HOVER_VERBOSITY_ACTION_ID);
return kb ?
nls.localize('increaseVerbosityWithKb', "Increase Hover Verbosity ({0})", kb.getLabel()) :
nls.localize('increaseVerbosity', "Increase Hover Verbosity");
}
case HoverVerbosityAction.Decrease: {
const kb = keybindingService.lookupKeybinding(DECREASE_HOVER_VERBOSITY_ACTION_ID);
return kb ?
nls.localize('decreaseVerbosityWithKb', "Decrease Hover Verbosity ({0})", kb.getLabel()) :
nls.localize('decreaseVerbosity', "Decrease Hover Verbosity");
}
}
}