monaco-editor
Version:
A browser based code editor
276 lines (275 loc) • 12.9 kB
JavaScript
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); }
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../base/browser/dom.js';
import { KeybindingLabel } from '../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
import { List } from '../../../base/browser/ui/list/listWidget.js';
import { CancellationTokenSource } from '../../../base/common/cancellation.js';
import { Codicon } from '../../../base/common/codicons.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { OS } from '../../../base/common/platform.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import './actionWidget.css';
import { localize } from '../../../nls.js';
import { IContextViewService } from '../../contextview/browser/contextView.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { defaultListStyles } from '../../theme/browser/defaultStyles.js';
import { asCssVariable } from '../../theme/common/colorRegistry.js';
export const acceptSelectedActionCommand = 'acceptSelectedCodeAction';
export const previewSelectedActionCommand = 'previewSelectedCodeAction';
class HeaderRenderer {
get templateId() { return "header" /* ActionListItemKind.Header */; }
renderTemplate(container) {
container.classList.add('group-header');
const text = document.createElement('span');
container.append(text);
return { container, text };
}
renderElement(element, _index, templateData) {
var _a, _b;
templateData.text.textContent = (_b = (_a = element.group) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : '';
}
disposeTemplate(_templateData) {
// noop
}
}
let ActionItemRenderer = class ActionItemRenderer {
get templateId() { return "action" /* ActionListItemKind.Action */; }
constructor(_supportsPreview, _keybindingService) {
this._supportsPreview = _supportsPreview;
this._keybindingService = _keybindingService;
}
renderTemplate(container) {
container.classList.add(this.templateId);
const icon = document.createElement('div');
icon.className = 'icon';
container.append(icon);
const text = document.createElement('span');
text.className = 'title';
container.append(text);
const keybinding = new KeybindingLabel(container, OS);
return { container, icon, text, keybinding };
}
renderElement(element, _index, data) {
var _a, _b, _c;
if ((_a = element.group) === null || _a === void 0 ? void 0 : _a.icon) {
data.icon.className = ThemeIcon.asClassName(element.group.icon);
if (element.group.icon.color) {
data.icon.style.color = asCssVariable(element.group.icon.color.id);
}
}
else {
data.icon.className = ThemeIcon.asClassName(Codicon.lightBulb);
data.icon.style.color = 'var(--vscode-editorLightBulb-foreground)';
}
if (!element.item || !element.label) {
return;
}
data.text.textContent = stripNewlines(element.label);
data.keybinding.set(element.keybinding);
dom.setVisibility(!!element.keybinding, data.keybinding.element);
const actionTitle = (_b = this._keybindingService.lookupKeybinding(acceptSelectedActionCommand)) === null || _b === void 0 ? void 0 : _b.getLabel();
const previewTitle = (_c = this._keybindingService.lookupKeybinding(previewSelectedActionCommand)) === null || _c === void 0 ? void 0 : _c.getLabel();
data.container.classList.toggle('option-disabled', element.disabled);
if (element.disabled) {
data.container.title = element.label;
}
else if (actionTitle && previewTitle) {
if (this._supportsPreview && element.canPreview) {
data.container.title = localize({ key: 'label-preview', comment: ['placeholders are keybindings, e.g "F2 to apply, Shift+F2 to preview"'] }, "{0} to apply, {1} to preview", actionTitle, previewTitle);
}
else {
data.container.title = localize({ key: 'label', comment: ['placeholder is a keybinding, e.g "F2 to apply"'] }, "{0} to apply", actionTitle);
}
}
else {
data.container.title = '';
}
}
disposeTemplate(_templateData) {
// noop
}
};
ActionItemRenderer = __decorate([
__param(1, IKeybindingService)
], ActionItemRenderer);
class AcceptSelectedEvent extends UIEvent {
constructor() { super('acceptSelectedAction'); }
}
class PreviewSelectedEvent extends UIEvent {
constructor() { super('previewSelectedAction'); }
}
function getKeyboardNavigationLabel(item) {
// Filter out header vs. action
if (item.kind === 'action') {
return item.label;
}
return undefined;
}
let ActionList = class ActionList extends Disposable {
constructor(user, preview, items, _delegate, _contextViewService, _keybindingService) {
super();
this._delegate = _delegate;
this._contextViewService = _contextViewService;
this._keybindingService = _keybindingService;
this._actionLineHeight = 24;
this._headerLineHeight = 26;
this.cts = this._register(new CancellationTokenSource());
this.domNode = document.createElement('div');
this.domNode.classList.add('actionList');
const virtualDelegate = {
getHeight: element => element.kind === "header" /* ActionListItemKind.Header */ ? this._headerLineHeight : this._actionLineHeight,
getTemplateId: element => element.kind
};
this._list = this._register(new List(user, this.domNode, virtualDelegate, [
new ActionItemRenderer(preview, this._keybindingService),
new HeaderRenderer(),
], {
keyboardSupport: false,
typeNavigationEnabled: true,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel },
accessibilityProvider: {
getAriaLabel: element => {
if (element.kind === "action" /* ActionListItemKind.Action */) {
let label = element.label ? stripNewlines(element === null || element === void 0 ? void 0 : element.label) : '';
if (element.disabled) {
label = localize({ key: 'customQuickFixWidget.labels', comment: [`Action widget labels for accessibility.`] }, "{0}, Disabled Reason: {1}", label, element.disabled);
}
return label;
}
return null;
},
getWidgetAriaLabel: () => localize({ key: 'customQuickFixWidget', comment: [`An action widget option`] }, "Action Widget"),
getRole: (e) => e.kind === "action" /* ActionListItemKind.Action */ ? 'option' : 'separator',
getWidgetRole: () => 'listbox',
},
}));
this._list.style(defaultListStyles);
this._register(this._list.onMouseClick(e => this.onListClick(e)));
this._register(this._list.onMouseOver(e => this.onListHover(e)));
this._register(this._list.onDidChangeFocus(() => this.onFocus()));
this._register(this._list.onDidChangeSelection(e => this.onListSelection(e)));
this._allMenuItems = items;
this._list.splice(0, this._list.length, this._allMenuItems);
if (this._list.length) {
this.focusNext();
}
}
focusCondition(element) {
return !element.disabled && element.kind === "action" /* ActionListItemKind.Action */;
}
hide(didCancel) {
this._delegate.onHide(didCancel);
this.cts.cancel();
this._contextViewService.hideContextView();
}
layout(minWidth) {
// Updating list height, depending on how many separators and headers there are.
const numHeaders = this._allMenuItems.filter(item => item.kind === 'header').length;
const itemsHeight = this._allMenuItems.length * this._actionLineHeight;
const heightWithHeaders = itemsHeight + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight;
this._list.layout(heightWithHeaders);
let maxWidth = minWidth;
if (this._allMenuItems.length >= 50) {
maxWidth = 380;
}
else {
// For finding width dynamically (not using resize observer)
const itemWidths = this._allMenuItems.map((_, index) => {
const element = this.domNode.ownerDocument.getElementById(this._list.getElementID(index));
if (element) {
element.style.width = 'auto';
const width = element.getBoundingClientRect().width;
element.style.width = '';
return width;
}
return 0;
});
// resize observer - can be used in the future since list widget supports dynamic height but not width
maxWidth = Math.max(...itemWidths, minWidth);
}
const maxVhPrecentage = 0.7;
const height = Math.min(heightWithHeaders, this.domNode.ownerDocument.body.clientHeight * maxVhPrecentage);
this._list.layout(height, maxWidth);
this.domNode.style.height = `${height}px`;
this._list.domFocus();
return maxWidth;
}
focusPrevious() {
this._list.focusPrevious(1, true, undefined, this.focusCondition);
}
focusNext() {
this._list.focusNext(1, true, undefined, this.focusCondition);
}
acceptSelected(preview) {
const focused = this._list.getFocus();
if (focused.length === 0) {
return;
}
const focusIndex = focused[0];
const element = this._list.element(focusIndex);
if (!this.focusCondition(element)) {
return;
}
const event = preview ? new PreviewSelectedEvent() : new AcceptSelectedEvent();
this._list.setSelection([focusIndex], event);
}
onListSelection(e) {
if (!e.elements.length) {
return;
}
const element = e.elements[0];
if (element.item && this.focusCondition(element)) {
this._delegate.onSelect(element.item, e.browserEvent instanceof PreviewSelectedEvent);
}
else {
this._list.setSelection([]);
}
}
onFocus() {
var _a, _b;
const focused = this._list.getFocus();
if (focused.length === 0) {
return;
}
const focusIndex = focused[0];
const element = this._list.element(focusIndex);
(_b = (_a = this._delegate).onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, element.item);
}
async onListHover(e) {
const element = e.element;
if (element && element.item && this.focusCondition(element)) {
if (this._delegate.onHover && !element.disabled && element.kind === "action" /* ActionListItemKind.Action */) {
const result = await this._delegate.onHover(element.item, this.cts.token);
element.canPreview = result ? result.canPreview : undefined;
}
if (e.index) {
this._list.splice(e.index, 1, [element]);
}
}
this._list.setFocus(typeof e.index === 'number' ? [e.index] : []);
}
onListClick(e) {
if (e.element && this.focusCondition(e.element)) {
this._list.setFocus([]);
}
}
};
ActionList = __decorate([
__param(4, IContextViewService),
__param(5, IKeybindingService)
], ActionList);
export { ActionList };
function stripNewlines(str) {
return str.replace(/\r\n|\r|\n/g, ' ');
}