UNPKG

monaco-editor-core

Version:

A browser based code editor

379 lines (378 loc) • 21.4 kB
/*--------------------------------------------------------------------------------------------- * 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"); } } }