monaco-editor
Version:
A browser based code editor
513 lines (512 loc) • 24.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 __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
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 './parameterHints.css';
import * as nls from '../../../nls.js';
import { dispose, Disposable } from '../../../base/common/lifecycle.js';
import * as dom from '../../../base/browser/dom.js';
import * as aria from '../../../base/browser/ui/aria/aria.js';
import * as modes from '../../common/modes.js';
import { RunOnceScheduler, createCancelablePromise } from '../../../base/common/async.js';
import { onUnexpectedError } from '../../../base/common/errors.js';
import { Emitter, chain } from '../../../base/common/event.js';
import { domEvent, stop } from '../../../base/browser/event.js';
import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
import { Context, provideSignatureHelp } from './provideSignatureHelp.js';
import { DomScrollableElement } from '../../../base/browser/ui/scrollbar/scrollableElement.js';
import { CharacterSet } from '../../common/core/characterClassifier.js';
import { registerThemingParticipant, HIGH_CONTRAST } from '../../../platform/theme/common/themeService.js';
import { editorHoverBackground, editorHoverBorder, textLinkForeground, textCodeBlockBackground } from '../../../platform/theme/common/colorRegistry.js';
import { IOpenerService } from '../../../platform/opener/common/opener.js';
import { IModeService } from '../../common/services/modeService.js';
import { MarkdownRenderer } from '../markdown/markdownRenderer.js';
var $ = dom.$;
var ParameterHintsModel = /** @class */ (function (_super) {
__extends(ParameterHintsModel, _super);
function ParameterHintsModel(editor, delay) {
if (delay === void 0) { delay = ParameterHintsModel.DEFAULT_DELAY; }
var _this = _super.call(this) || this;
_this._onHint = _this._register(new Emitter());
_this.onHint = _this._onHint.event;
_this._onCancel = _this._register(new Emitter());
_this.onCancel = _this._onCancel.event;
_this.active = false;
_this.pending = false;
_this.triggerChars = new CharacterSet();
_this.retriggerChars = new CharacterSet();
_this.editor = editor;
_this.enabled = false;
_this.triggerCharactersListeners = [];
_this.throttledDelayer = new RunOnceScheduler(function () { return _this.doTrigger(); }, delay);
_this._register(_this.editor.onDidChangeConfiguration(function () { return _this.onEditorConfigurationChange(); }));
_this._register(_this.editor.onDidChangeModel(function (e) { return _this.onModelChanged(); }));
_this._register(_this.editor.onDidChangeModelLanguage(function (_) { return _this.onModelChanged(); }));
_this._register(_this.editor.onDidChangeCursorSelection(function (e) { return _this.onCursorChange(e); }));
_this._register(_this.editor.onDidChangeModelContent(function (e) { return _this.onModelContentChange(); }));
_this._register(modes.SignatureHelpProviderRegistry.onDidChange(_this.onModelChanged, _this));
_this._register(_this.editor.onDidType(function (text) { return _this.onDidType(text); }));
_this.onEditorConfigurationChange();
_this.onModelChanged();
return _this;
}
ParameterHintsModel.prototype.cancel = function (silent) {
if (silent === void 0) { silent = false; }
this.active = false;
this.pending = false;
this.triggerContext = undefined;
this.throttledDelayer.cancel();
if (!silent) {
this._onCancel.fire(void 0);
}
if (this.provideSignatureHelpRequest) {
this.provideSignatureHelpRequest.cancel();
this.provideSignatureHelpRequest = undefined;
}
};
ParameterHintsModel.prototype.trigger = function (context, delay) {
if (!modes.SignatureHelpProviderRegistry.has(this.editor.getModel())) {
return;
}
var wasTriggered = this.isTriggered;
this.cancel(true);
this.triggerContext = {
triggerReason: context.triggerReason,
triggerCharacter: context.triggerCharacter,
isRetrigger: wasTriggered
};
return this.throttledDelayer.schedule(delay);
};
ParameterHintsModel.prototype.doTrigger = function () {
var _this = this;
if (this.provideSignatureHelpRequest) {
this.provideSignatureHelpRequest.cancel();
}
this.pending = true;
var triggerContext = this.triggerContext || { triggerReason: modes.SignatureHelpTriggerReason.Invoke, isRetrigger: false };
this.triggerContext = undefined;
this.provideSignatureHelpRequest = createCancelablePromise(function (token) {
return provideSignatureHelp(_this.editor.getModel(), _this.editor.getPosition(), triggerContext, token);
});
this.provideSignatureHelpRequest.then(function (result) {
_this.pending = false;
if (!result || !result.signatures || result.signatures.length === 0) {
_this.cancel();
_this._onCancel.fire(void 0);
return false;
}
_this.active = true;
var event = { hints: result };
_this._onHint.fire(event);
return true;
}).catch(function (error) {
_this.pending = false;
onUnexpectedError(error);
});
};
Object.defineProperty(ParameterHintsModel.prototype, "isTriggered", {
get: function () {
return this.active || this.pending || this.throttledDelayer.isScheduled();
},
enumerable: true,
configurable: true
});
ParameterHintsModel.prototype.onModelChanged = function () {
this.cancel();
// Update trigger characters
this.triggerChars = new CharacterSet();
this.retriggerChars = new CharacterSet();
var model = this.editor.getModel();
if (!model) {
return;
}
for (var _i = 0, _a = modes.SignatureHelpProviderRegistry.ordered(model); _i < _a.length; _i++) {
var support = _a[_i];
if (Array.isArray(support.signatureHelpTriggerCharacters)) {
for (var _b = 0, _c = support.signatureHelpTriggerCharacters; _b < _c.length; _b++) {
var ch = _c[_b];
this.triggerChars.add(ch.charCodeAt(0));
// All trigger characters are also considered retrigger characters
this.retriggerChars.add(ch.charCodeAt(0));
}
}
if (Array.isArray(support.signatureHelpRetriggerCharacters)) {
for (var _d = 0, _e = support.signatureHelpRetriggerCharacters; _d < _e.length; _d++) {
var ch = _e[_d];
this.retriggerChars.add(ch.charCodeAt(0));
}
}
}
};
ParameterHintsModel.prototype.onDidType = function (text) {
if (!this.enabled) {
return;
}
var lastCharIndex = text.length - 1;
var triggerCharCode = text.charCodeAt(lastCharIndex);
if (this.triggerChars.has(triggerCharCode) || this.isTriggered && this.retriggerChars.has(triggerCharCode)) {
this.trigger({
triggerReason: modes.SignatureHelpTriggerReason.TriggerCharacter,
triggerCharacter: text.charAt(lastCharIndex),
});
}
};
ParameterHintsModel.prototype.onCursorChange = function (e) {
if (e.source === 'mouse') {
this.cancel();
}
else if (this.isTriggered) {
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.ContentChange });
}
};
ParameterHintsModel.prototype.onModelContentChange = function () {
if (this.isTriggered) {
this.trigger({ triggerReason: modes.SignatureHelpTriggerReason.ContentChange });
}
};
ParameterHintsModel.prototype.onEditorConfigurationChange = function () {
this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled;
if (!this.enabled) {
this.cancel();
}
};
ParameterHintsModel.prototype.dispose = function () {
this.cancel(true);
this.triggerCharactersListeners = dispose(this.triggerCharactersListeners);
_super.prototype.dispose.call(this);
};
ParameterHintsModel.DEFAULT_DELAY = 120; // ms
return ParameterHintsModel;
}(Disposable));
export { ParameterHintsModel };
var ParameterHintsWidget = /** @class */ (function () {
function ParameterHintsWidget(editor, contextKeyService, openerService, modeService) {
var _this = this;
this.editor = editor;
// Editor.IContentWidget.allowEditorOverflow
this.allowEditorOverflow = true;
this.markdownRenderer = new MarkdownRenderer(editor, modeService, openerService);
this.model = new ParameterHintsModel(editor);
this.keyVisible = Context.Visible.bindTo(contextKeyService);
this.keyMultipleSignatures = Context.MultipleSignatures.bindTo(contextKeyService);
this.visible = false;
this.disposables = [];
this.disposables.push(this.model.onHint(function (e) {
_this.show();
_this.hints = e.hints;
_this.currentSignature = e.hints.activeSignature;
_this.render();
}));
this.disposables.push(this.model.onCancel(function () {
_this.hide();
}));
}
ParameterHintsWidget.prototype.createParamaterHintDOMNodes = function () {
var _this = this;
this.element = $('.editor-widget.parameter-hints-widget');
var wrapper = dom.append(this.element, $('.wrapper'));
var buttons = dom.append(wrapper, $('.buttons'));
var previous = dom.append(buttons, $('.button.previous'));
var next = dom.append(buttons, $('.button.next'));
var onPreviousClick = stop(domEvent(previous, 'click'));
onPreviousClick(this.previous, this, this.disposables);
var onNextClick = stop(domEvent(next, 'click'));
onNextClick(this.next, this, this.disposables);
this.overloads = dom.append(wrapper, $('.overloads'));
var body = $('.body');
this.scrollbar = new DomScrollableElement(body, {});
this.disposables.push(this.scrollbar);
wrapper.appendChild(this.scrollbar.getDomNode());
this.signature = dom.append(body, $('.signature'));
this.docs = dom.append(body, $('.docs'));
this.currentSignature = 0;
this.editor.addContentWidget(this);
this.hide();
this.element.style.userSelect = 'text';
this.disposables.push(this.editor.onDidChangeCursorSelection(function (e) {
if (_this.visible) {
_this.editor.layoutContentWidget(_this);
}
}));
var updateFont = function () {
var fontInfo = _this.editor.getConfiguration().fontInfo;
_this.element.style.fontSize = fontInfo.fontSize + "px";
};
updateFont();
chain(this.editor.onDidChangeConfiguration.bind(this.editor))
.filter(function (e) { return e.fontInfo; })
.on(updateFont, null, this.disposables);
this.disposables.push(this.editor.onDidLayoutChange(function (e) { return _this.updateMaxHeight(); }));
this.updateMaxHeight();
};
ParameterHintsWidget.prototype.show = function () {
var _this = this;
if (!this.model || this.visible) {
return;
}
if (!this.element) {
this.createParamaterHintDOMNodes();
}
this.keyVisible.set(true);
this.visible = true;
setTimeout(function () { return dom.addClass(_this.element, 'visible'); }, 100);
this.editor.layoutContentWidget(this);
};
ParameterHintsWidget.prototype.hide = function () {
if (!this.model || !this.visible) {
return;
}
if (!this.element) {
this.createParamaterHintDOMNodes();
}
this.keyVisible.reset();
this.visible = false;
this.hints = null;
this.announcedLabel = null;
dom.removeClass(this.element, 'visible');
this.editor.layoutContentWidget(this);
};
ParameterHintsWidget.prototype.getPosition = function () {
if (this.visible) {
return {
position: this.editor.getPosition(),
preference: [1 /* ABOVE */, 2 /* BELOW */]
};
}
return null;
};
ParameterHintsWidget.prototype.render = function () {
var multiple = this.hints.signatures.length > 1;
dom.toggleClass(this.element, 'multiple', multiple);
this.keyMultipleSignatures.set(multiple);
this.signature.innerHTML = '';
this.docs.innerHTML = '';
var signature = this.hints.signatures[this.currentSignature];
if (!signature) {
return;
}
var code = dom.append(this.signature, $('.code'));
var hasParameters = signature.parameters.length > 0;
var fontInfo = this.editor.getConfiguration().fontInfo;
code.style.fontSize = fontInfo.fontSize + "px";
code.style.fontFamily = fontInfo.fontFamily;
if (!hasParameters) {
var label = dom.append(code, $('span'));
label.textContent = signature.label;
}
else {
this.renderParameters(code, signature, this.hints.activeParameter);
}
dispose(this.renderDisposeables);
this.renderDisposeables = [];
var activeParameter = signature.parameters[this.hints.activeParameter];
if (activeParameter && activeParameter.documentation) {
var documentation = $('span.documentation');
if (typeof activeParameter.documentation === 'string') {
documentation.textContent = activeParameter.documentation;
}
else {
var renderedContents = this.markdownRenderer.render(activeParameter.documentation);
dom.addClass(renderedContents.element, 'markdown-docs');
this.renderDisposeables.push(renderedContents);
documentation.appendChild(renderedContents.element);
}
dom.append(this.docs, $('p', null, documentation));
}
dom.toggleClass(this.signature, 'has-docs', !!signature.documentation);
if (typeof signature.documentation === 'string') {
dom.append(this.docs, $('p', null, signature.documentation));
}
else {
var renderedContents = this.markdownRenderer.render(signature.documentation);
dom.addClass(renderedContents.element, 'markdown-docs');
this.renderDisposeables.push(renderedContents);
dom.append(this.docs, renderedContents.element);
}
var currentOverload = String(this.currentSignature + 1);
if (this.hints.signatures.length < 10) {
currentOverload += "/" + this.hints.signatures.length;
}
this.overloads.textContent = currentOverload;
if (activeParameter) {
var labelToAnnounce = this.getParameterLabel(signature, this.hints.activeParameter);
// Select method gets called on every user type while parameter hints are visible.
// We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
if (this.announcedLabel !== labelToAnnounce) {
aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
this.announcedLabel = labelToAnnounce;
}
}
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
};
ParameterHintsWidget.prototype.renderParameters = function (parent, signature, currentParameter) {
var _a = this.getParameterLabelOffsets(signature, currentParameter), start = _a[0], end = _a[1];
var beforeSpan = document.createElement('span');
beforeSpan.textContent = signature.label.substring(0, start);
var paramSpan = document.createElement('span');
paramSpan.textContent = signature.label.substring(start, end);
paramSpan.className = 'parameter active';
var afterSpan = document.createElement('span');
afterSpan.textContent = signature.label.substring(end);
dom.append(parent, beforeSpan, paramSpan, afterSpan);
};
ParameterHintsWidget.prototype.getParameterLabel = function (signature, paramIdx) {
var param = signature.parameters[paramIdx];
if (typeof param.label === 'string') {
return param.label;
}
else {
return signature.label.substring(param.label[0], param.label[1]);
}
};
ParameterHintsWidget.prototype.getParameterLabelOffsets = function (signature, paramIdx) {
var param = signature.parameters[paramIdx];
if (!param) {
return [0, 0];
}
else if (Array.isArray(param.label)) {
return param.label;
}
else {
var idx = signature.label.lastIndexOf(param.label);
return idx >= 0
? [idx, idx + param.label.length]
: [0, 0];
}
};
// private select(position: number): void {
// const signature = this.signatureViews[position];
// if (!signature) {
// return;
// }
// this.signatures.style.height = `${ signature.height }px`;
// this.signatures.scrollTop = signature.top;
// let overloads = '' + (position + 1);
// if (this.signatureViews.length < 10) {
// overloads += '/' + this.signatureViews.length;
// }
// this.overloads.textContent = overloads;
// if (this.hints && this.hints.signatures[position].parameters[this.hints.activeParameter]) {
// const labelToAnnounce = this.hints.signatures[position].parameters[this.hints.activeParameter].label;
// // Select method gets called on every user type while parameter hints are visible.
// // We do not want to spam the user with same announcements, so we only announce if the current parameter changed.
// if (this.announcedLabel !== labelToAnnounce) {
// aria.alert(nls.localize('hint', "{0}, hint", labelToAnnounce));
// this.announcedLabel = labelToAnnounce;
// }
// }
// this.editor.layoutContentWidget(this);
// }
ParameterHintsWidget.prototype.next = function () {
var length = this.hints.signatures.length;
var last = (this.currentSignature % length) === (length - 1);
var cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on last signature of list
if ((length < 2 || last) && !cycle) {
this.cancel();
return false;
}
if (last && cycle) {
this.currentSignature = 0;
}
else {
this.currentSignature++;
}
this.render();
return true;
};
ParameterHintsWidget.prototype.previous = function () {
var length = this.hints.signatures.length;
var first = this.currentSignature === 0;
var cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle;
// If there is only one signature, or we're on first signature of list
if ((length < 2 || first) && !cycle) {
this.cancel();
return false;
}
if (first && cycle) {
this.currentSignature = length - 1;
}
else {
this.currentSignature--;
}
this.render();
return true;
};
ParameterHintsWidget.prototype.cancel = function () {
this.model.cancel();
};
ParameterHintsWidget.prototype.getDomNode = function () {
return this.element;
};
ParameterHintsWidget.prototype.getId = function () {
return ParameterHintsWidget.ID;
};
ParameterHintsWidget.prototype.trigger = function (context) {
this.model.trigger(context, 0);
};
ParameterHintsWidget.prototype.updateMaxHeight = function () {
var height = Math.max(this.editor.getLayoutInfo().height / 4, 250);
this.element.style.maxHeight = height + "px";
};
ParameterHintsWidget.prototype.dispose = function () {
this.disposables = dispose(this.disposables);
this.renderDisposeables = dispose(this.renderDisposeables);
if (this.model) {
this.model.dispose();
this.model = null;
}
};
ParameterHintsWidget.ID = 'editor.widget.parameterHintsWidget';
ParameterHintsWidget = __decorate([
__param(1, IContextKeyService),
__param(2, IOpenerService),
__param(3, IModeService)
], ParameterHintsWidget);
return ParameterHintsWidget;
}());
export { ParameterHintsWidget };
registerThemingParticipant(function (theme, collector) {
var border = theme.getColor(editorHoverBorder);
if (border) {
var borderWidth = theme.type === HIGH_CONTRAST ? 2 : 1;
collector.addRule(".monaco-editor .parameter-hints-widget { border: " + borderWidth + "px solid " + border + "; }");
collector.addRule(".monaco-editor .parameter-hints-widget.multiple .body { border-left: 1px solid " + border.transparent(0.5) + "; }");
collector.addRule(".monaco-editor .parameter-hints-widget .signature.has-docs { border-bottom: 1px solid " + border.transparent(0.5) + "; }");
}
var background = theme.getColor(editorHoverBackground);
if (background) {
collector.addRule(".monaco-editor .parameter-hints-widget { background-color: " + background + "; }");
}
var link = theme.getColor(textLinkForeground);
if (link) {
collector.addRule(".monaco-editor .parameter-hints-widget a { color: " + link + "; }");
}
var codeBackground = theme.getColor(textCodeBlockBackground);
if (codeBackground) {
collector.addRule(".monaco-editor .parameter-hints-widget code { background-color: " + codeBackground + "; }");
}
});