monaco-editor
Version:
A browser based code editor
228 lines (227 loc) • 12.9 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 { isNonEmptyArray } from '../../../../base/common/arrays.js';
import { createCancelablePromise, disposableTimeout } from '../../../../base/common/async.js';
import { onUnexpectedError } from '../../../../base/common/errors.js';
import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
import { basename } from '../../../../base/common/resources.js';
import { Range } from '../../../common/core/range.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { IMarkerDecorationsService } from '../../../common/services/markerDecorations.js';
import { getCodeActions, quickFixCommandId } from '../../codeAction/browser/codeAction.js';
import { CodeActionController } from '../../codeAction/browser/codeActionController.js';
import { CodeActionKind, CodeActionTriggerSource } from '../../codeAction/common/types.js';
import { MarkerController, NextMarkerAction } from '../../gotoError/browser/gotoError.js';
import * as nls from '../../../../nls.js';
import { IMarkerData, MarkerSeverity } from '../../../../platform/markers/common/markers.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { Progress } from '../../../../platform/progress/common/progress.js';
const $ = dom.$;
export class MarkerHover {
constructor(owner, range, marker) {
this.owner = owner;
this.range = range;
this.marker = marker;
}
isValidForHoverAnchor(anchor) {
return (anchor.type === 1 /* HoverAnchorType.Range */
&& this.range.startColumn <= anchor.range.startColumn
&& this.range.endColumn >= anchor.range.endColumn);
}
}
const markerCodeActionTrigger = {
type: 1 /* CodeActionTriggerType.Invoke */,
filter: { include: CodeActionKind.QuickFix },
triggerAction: CodeActionTriggerSource.QuickFixHover
};
let MarkerHoverParticipant = class MarkerHoverParticipant {
constructor(_editor, _markerDecorationsService, _openerService, _languageFeaturesService) {
this._editor = _editor;
this._markerDecorationsService = _markerDecorationsService;
this._openerService = _openerService;
this._languageFeaturesService = _languageFeaturesService;
this.hoverOrdinal = 1;
this.recentMarkerCodeActionsInfo = undefined;
}
computeSync(anchor, lineDecorations) {
if (!this._editor.hasModel() || anchor.type !== 1 /* HoverAnchorType.Range */ && !anchor.supportsMarkerHover) {
return [];
}
const model = this._editor.getModel();
const lineNumber = anchor.range.startLineNumber;
const maxColumn = model.getLineMaxColumn(lineNumber);
const result = [];
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 marker = this._markerDecorationsService.getMarker(model.uri, d);
if (!marker) {
continue;
}
const range = new Range(anchor.range.startLineNumber, startColumn, anchor.range.startLineNumber, endColumn);
result.push(new MarkerHover(this, range, marker));
}
return result;
}
renderHoverParts(context, hoverParts) {
if (!hoverParts.length) {
return Disposable.None;
}
const disposables = new DisposableStore();
hoverParts.forEach(msg => context.fragment.appendChild(this.renderMarkerHover(msg, disposables)));
const markerHoverForStatusbar = hoverParts.length === 1 ? hoverParts[0] : hoverParts.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0];
this.renderMarkerStatusbar(context, markerHoverForStatusbar, disposables);
return disposables;
}
renderMarkerHover(markerHover, disposables) {
const hoverElement = $('div.hover-row');
const markerElement = dom.append(hoverElement, $('div.marker.hover-contents'));
const { source, message, code, relatedInformation } = markerHover.marker;
this._editor.applyFontInfo(markerElement);
const messageElement = dom.append(markerElement, $('span'));
messageElement.style.whiteSpace = 'pre-wrap';
messageElement.innerText = message;
if (source || code) {
// Code has link
if (code && typeof code !== 'string') {
const sourceAndCodeElement = $('span');
if (source) {
const sourceElement = dom.append(sourceAndCodeElement, $('span'));
sourceElement.innerText = source;
}
const codeLink = dom.append(sourceAndCodeElement, $('a.code-link'));
codeLink.setAttribute('href', code.target.toString());
disposables.add(dom.addDisposableListener(codeLink, 'click', (e) => {
this._openerService.open(code.target, { allowCommands: true });
e.preventDefault();
e.stopPropagation();
}));
const codeElement = dom.append(codeLink, $('span'));
codeElement.innerText = code.value;
const detailsElement = dom.append(markerElement, sourceAndCodeElement);
detailsElement.style.opacity = '0.6';
detailsElement.style.paddingLeft = '6px';
}
else {
const detailsElement = dom.append(markerElement, $('span'));
detailsElement.style.opacity = '0.6';
detailsElement.style.paddingLeft = '6px';
detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`;
}
}
if (isNonEmptyArray(relatedInformation)) {
for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
const relatedInfoContainer = dom.append(markerElement, $('div'));
relatedInfoContainer.style.marginTop = '8px';
const a = dom.append(relatedInfoContainer, $('a'));
a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn}): `;
a.style.cursor = 'pointer';
disposables.add(dom.addDisposableListener(a, 'click', (e) => {
e.stopPropagation();
e.preventDefault();
if (this._openerService) {
this._openerService.open(resource, {
fromUserGesture: true,
editorOptions: { selection: { startLineNumber, startColumn } }
}).catch(onUnexpectedError);
}
}));
const messageElement = dom.append(relatedInfoContainer, $('span'));
messageElement.innerText = message;
this._editor.applyFontInfo(messageElement);
}
}
return hoverElement;
}
renderMarkerStatusbar(context, markerHover, disposables) {
if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) {
context.statusBar.addAction({
label: nls.localize('view problem', "View Problem"),
commandId: NextMarkerAction.ID,
run: () => {
var _a;
context.hide();
(_a = MarkerController.get(this._editor)) === null || _a === void 0 ? void 0 : _a.showAtMarker(markerHover.marker);
this._editor.focus();
}
});
}
if (!this._editor.getOption(90 /* EditorOption.readOnly */)) {
const quickfixPlaceholderElement = context.statusBar.append($('div'));
if (this.recentMarkerCodeActionsInfo) {
if (IMarkerData.makeKey(this.recentMarkerCodeActionsInfo.marker) === IMarkerData.makeKey(markerHover.marker)) {
if (!this.recentMarkerCodeActionsInfo.hasCodeActions) {
quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available");
}
}
else {
this.recentMarkerCodeActionsInfo = undefined;
}
}
const updatePlaceholderDisposable = this.recentMarkerCodeActionsInfo && !this.recentMarkerCodeActionsInfo.hasCodeActions ? Disposable.None : disposableTimeout(() => quickfixPlaceholderElement.textContent = nls.localize('checkingForQuickFixes', "Checking for quick fixes..."), 200, disposables);
if (!quickfixPlaceholderElement.textContent) {
// Have some content in here to avoid flickering
quickfixPlaceholderElement.textContent = String.fromCharCode(0xA0); //
}
const codeActionsPromise = this.getCodeActions(markerHover.marker);
disposables.add(toDisposable(() => codeActionsPromise.cancel()));
codeActionsPromise.then(actions => {
updatePlaceholderDisposable.dispose();
this.recentMarkerCodeActionsInfo = { marker: markerHover.marker, hasCodeActions: actions.validActions.length > 0 };
if (!this.recentMarkerCodeActionsInfo.hasCodeActions) {
actions.dispose();
quickfixPlaceholderElement.textContent = nls.localize('noQuickFixes', "No quick fixes available");
return;
}
quickfixPlaceholderElement.style.display = 'none';
let showing = false;
disposables.add(toDisposable(() => {
if (!showing) {
actions.dispose();
}
}));
context.statusBar.addAction({
label: nls.localize('quick fixes', "Quick Fix..."),
commandId: quickFixCommandId,
run: (target) => {
showing = true;
const controller = CodeActionController.get(this._editor);
const elementPosition = dom.getDomNodePagePosition(target);
// Hide the hover pre-emptively, otherwise the editor can close the code actions
// context menu as well when using keyboard navigation
context.hide();
controller === null || controller === void 0 ? void 0 : controller.showCodeActions(markerCodeActionTrigger, actions, {
x: elementPosition.left,
y: elementPosition.top,
width: elementPosition.width,
height: elementPosition.height
});
}
});
}, onUnexpectedError);
}
}
getCodeActions(marker) {
return createCancelablePromise(cancellationToken => {
return getCodeActions(this._languageFeaturesService.codeActionProvider, this._editor.getModel(), new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), markerCodeActionTrigger, Progress.None, cancellationToken);
});
}
};
MarkerHoverParticipant = __decorate([
__param(1, IMarkerDecorationsService),
__param(2, IOpenerService),
__param(3, ILanguageFeaturesService)
], MarkerHoverParticipant);
export { MarkerHoverParticipant };