monaco-editor
Version:
A browser based code editor
344 lines (343 loc) • 16.5 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); }
};
var ContextMenuController_1;
import * as dom from '../../../../base/browser/dom.js';
import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
import { Separator, SubmenuAction } from '../../../../base/common/actions.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { isIOS } from '../../../../base/common/platform.js';
import { EditorAction, registerEditorAction, registerEditorContribution } from '../../../browser/editorExtensions.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import * as nls from '../../../../nls.js';
import { IMenuService, SubmenuItemAction } from '../../../../platform/actions/common/actions.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IWorkspaceContextService, isStandaloneEditorWorkspace } from '../../../../platform/workspace/common/workspace.js';
let ContextMenuController = class ContextMenuController {
static { ContextMenuController_1 = this; }
static { this.ID = 'editor.contrib.contextmenu'; }
static get(editor) {
return editor.getContribution(ContextMenuController_1.ID);
}
constructor(editor, _contextMenuService, _contextViewService, _contextKeyService, _keybindingService, _menuService, _configurationService, _workspaceContextService) {
this._contextMenuService = _contextMenuService;
this._contextViewService = _contextViewService;
this._contextKeyService = _contextKeyService;
this._keybindingService = _keybindingService;
this._menuService = _menuService;
this._configurationService = _configurationService;
this._workspaceContextService = _workspaceContextService;
this._toDispose = new DisposableStore();
this._contextMenuIsBeingShownCount = 0;
this._editor = editor;
this._toDispose.add(this._editor.onContextMenu((e) => this._onContextMenu(e)));
this._toDispose.add(this._editor.onMouseWheel((e) => {
if (this._contextMenuIsBeingShownCount > 0) {
const view = this._contextViewService.getContextViewElement();
const target = e.srcElement;
// Event triggers on shadow root host first
// Check if the context view is under this host before hiding it #103169
if (!(target.shadowRoot && dom.getShadowRoot(view) === target.shadowRoot)) {
this._contextViewService.hideContextView();
}
}
}));
this._toDispose.add(this._editor.onKeyDown((e) => {
if (!this._editor.getOption(24 /* EditorOption.contextmenu */)) {
return; // Context menu is turned off through configuration
}
if (e.keyCode === 58 /* KeyCode.ContextMenu */) {
// Chrome is funny like that
e.preventDefault();
e.stopPropagation();
this.showContextMenu();
}
}));
}
_onContextMenu(e) {
if (!this._editor.hasModel()) {
return;
}
if (!this._editor.getOption(24 /* EditorOption.contextmenu */)) {
this._editor.focus();
// Ensure the cursor is at the position of the mouse click
if (e.target.position && !this._editor.getSelection().containsPosition(e.target.position)) {
this._editor.setPosition(e.target.position);
}
return; // Context menu is turned off through configuration
}
if (e.target.type === 12 /* MouseTargetType.OVERLAY_WIDGET */) {
return; // allow native menu on widgets to support right click on input field for example in find
}
if (e.target.type === 6 /* MouseTargetType.CONTENT_TEXT */ && e.target.detail.injectedText) {
return; // allow native menu on injected text
}
e.event.preventDefault();
e.event.stopPropagation();
if (e.target.type === 11 /* MouseTargetType.SCROLLBAR */) {
return this._showScrollbarContextMenu(e.event);
}
if (e.target.type !== 6 /* MouseTargetType.CONTENT_TEXT */ && e.target.type !== 7 /* MouseTargetType.CONTENT_EMPTY */ && e.target.type !== 1 /* MouseTargetType.TEXTAREA */) {
return; // only support mouse click into text or native context menu key for now
}
// Ensure the editor gets focus if it hasn't, so the right events are being sent to other contributions
this._editor.focus();
// Ensure the cursor is at the position of the mouse click
if (e.target.position) {
let hasSelectionAtPosition = false;
for (const selection of this._editor.getSelections()) {
if (selection.containsPosition(e.target.position)) {
hasSelectionAtPosition = true;
break;
}
}
if (!hasSelectionAtPosition) {
this._editor.setPosition(e.target.position);
}
}
// Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position
let anchor = null;
if (e.target.type !== 1 /* MouseTargetType.TEXTAREA */) {
anchor = e.event;
}
// Show the context menu
this.showContextMenu(anchor);
}
showContextMenu(anchor) {
if (!this._editor.getOption(24 /* EditorOption.contextmenu */)) {
return; // Context menu is turned off through configuration
}
if (!this._editor.hasModel()) {
return;
}
// Find actions available for menu
const menuActions = this._getMenuActions(this._editor.getModel(), this._editor.contextMenuId);
// Show menu if we have actions to show
if (menuActions.length > 0) {
this._doShowContextMenu(menuActions, anchor);
}
}
_getMenuActions(model, menuId) {
const result = [];
// get menu groups
const groups = this._menuService.getMenuActions(menuId, this._contextKeyService, { arg: model.uri });
// translate them into other actions
for (const group of groups) {
const [, actions] = group;
let addedItems = 0;
for (const action of actions) {
if (action instanceof SubmenuItemAction) {
const subActions = this._getMenuActions(model, action.item.submenu);
if (subActions.length > 0) {
result.push(new SubmenuAction(action.id, action.label, subActions));
addedItems++;
}
}
else {
result.push(action);
addedItems++;
}
}
if (addedItems) {
result.push(new Separator());
}
}
if (result.length) {
result.pop(); // remove last separator
}
return result;
}
_doShowContextMenu(actions, event = null) {
if (!this._editor.hasModel()) {
return;
}
// Disable hover
const oldHoverSetting = this._editor.getOption(60 /* EditorOption.hover */);
this._editor.updateOptions({
hover: {
enabled: false
}
});
let anchor = event;
if (!anchor) {
// Ensure selection is visible
this._editor.revealPosition(this._editor.getPosition(), 1 /* ScrollType.Immediate */);
this._editor.render();
const cursorCoords = this._editor.getScrolledVisiblePosition(this._editor.getPosition());
// Translate to absolute editor position
const editorCoords = dom.getDomNodePagePosition(this._editor.getDomNode());
const posx = editorCoords.left + cursorCoords.left;
const posy = editorCoords.top + cursorCoords.top + cursorCoords.height;
anchor = { x: posx, y: posy };
}
const useShadowDOM = this._editor.getOption(128 /* EditorOption.useShadowDOM */) && !isIOS; // Do not use shadow dom on IOS #122035
// Show menu
this._contextMenuIsBeingShownCount++;
this._contextMenuService.showContextMenu({
domForShadowRoot: useShadowDOM ? this._editor.getOverflowWidgetsDomNode() ?? this._editor.getDomNode() : undefined,
getAnchor: () => anchor,
getActions: () => actions,
getActionViewItem: (action) => {
const keybinding = this._keybindingFor(action);
if (keybinding) {
return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true });
}
const customActionViewItem = action;
if (typeof customActionViewItem.getActionViewItem === 'function') {
return customActionViewItem.getActionViewItem();
}
return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true });
},
getKeyBinding: (action) => {
return this._keybindingFor(action);
},
onHide: (wasCancelled) => {
this._contextMenuIsBeingShownCount--;
this._editor.updateOptions({
hover: oldHoverSetting
});
}
});
}
_showScrollbarContextMenu(anchor) {
if (!this._editor.hasModel()) {
return;
}
if (isStandaloneEditorWorkspace(this._workspaceContextService.getWorkspace())) {
// can't update the configuration properly in the standalone editor
return;
}
const minimapOptions = this._editor.getOption(73 /* EditorOption.minimap */);
let lastId = 0;
const createAction = (opts) => {
return {
id: `menu-action-${++lastId}`,
label: opts.label,
tooltip: '',
class: undefined,
enabled: (typeof opts.enabled === 'undefined' ? true : opts.enabled),
checked: opts.checked,
run: opts.run
};
};
const createSubmenuAction = (label, actions) => {
return new SubmenuAction(`menu-action-${++lastId}`, label, actions, undefined);
};
const createEnumAction = (label, enabled, configName, configuredValue, options) => {
if (!enabled) {
return createAction({ label, enabled, run: () => { } });
}
const createRunner = (value) => {
return () => {
this._configurationService.updateValue(configName, value);
};
};
const actions = [];
for (const option of options) {
actions.push(createAction({
label: option.label,
checked: configuredValue === option.value,
run: createRunner(option.value)
}));
}
return createSubmenuAction(label, actions);
};
const actions = [];
actions.push(createAction({
label: nls.localize('context.minimap.minimap', "Minimap"),
checked: minimapOptions.enabled,
run: () => {
this._configurationService.updateValue(`editor.minimap.enabled`, !minimapOptions.enabled);
}
}));
actions.push(new Separator());
actions.push(createAction({
label: nls.localize('context.minimap.renderCharacters', "Render Characters"),
enabled: minimapOptions.enabled,
checked: minimapOptions.renderCharacters,
run: () => {
this._configurationService.updateValue(`editor.minimap.renderCharacters`, !minimapOptions.renderCharacters);
}
}));
actions.push(createEnumAction(nls.localize('context.minimap.size', "Vertical size"), minimapOptions.enabled, 'editor.minimap.size', minimapOptions.size, [{
label: nls.localize('context.minimap.size.proportional', "Proportional"),
value: 'proportional'
}, {
label: nls.localize('context.minimap.size.fill', "Fill"),
value: 'fill'
}, {
label: nls.localize('context.minimap.size.fit', "Fit"),
value: 'fit'
}]));
actions.push(createEnumAction(nls.localize('context.minimap.slider', "Slider"), minimapOptions.enabled, 'editor.minimap.showSlider', minimapOptions.showSlider, [{
label: nls.localize('context.minimap.slider.mouseover', "Mouse Over"),
value: 'mouseover'
}, {
label: nls.localize('context.minimap.slider.always', "Always"),
value: 'always'
}]));
const useShadowDOM = this._editor.getOption(128 /* EditorOption.useShadowDOM */) && !isIOS; // Do not use shadow dom on IOS #122035
this._contextMenuIsBeingShownCount++;
this._contextMenuService.showContextMenu({
domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined,
getAnchor: () => anchor,
getActions: () => actions,
onHide: (wasCancelled) => {
this._contextMenuIsBeingShownCount--;
this._editor.focus();
}
});
}
_keybindingFor(action) {
return this._keybindingService.lookupKeybinding(action.id);
}
dispose() {
if (this._contextMenuIsBeingShownCount > 0) {
this._contextViewService.hideContextView();
}
this._toDispose.dispose();
}
};
ContextMenuController = ContextMenuController_1 = __decorate([
__param(1, IContextMenuService),
__param(2, IContextViewService),
__param(3, IContextKeyService),
__param(4, IKeybindingService),
__param(5, IMenuService),
__param(6, IConfigurationService),
__param(7, IWorkspaceContextService)
], ContextMenuController);
export { ContextMenuController };
class ShowContextMenu extends EditorAction {
constructor() {
super({
id: 'editor.action.showContextMenu',
label: nls.localize('action.showContextMenu.label', "Show Editor Context Menu"),
alias: 'Show Editor Context Menu',
precondition: undefined,
kbOpts: {
kbExpr: EditorContextKeys.textInputFocus,
primary: 1024 /* KeyMod.Shift */ | 68 /* KeyCode.F10 */,
weight: 100 /* KeybindingWeight.EditorContrib */
}
});
}
run(accessor, editor) {
ContextMenuController.get(editor)?.showContextMenu();
}
}
registerEditorContribution(ContextMenuController.ID, ContextMenuController, 2 /* EditorContributionInstantiation.BeforeFirstInteraction */);
registerEditorAction(ShowContextMenu);