monaco-editor-core
Version:
A browser based code editor
461 lines (460 loc) • 17.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.
*--------------------------------------------------------------------------------------------*/
import * as nls from '../../nls.js';
import { URI } from '../../base/common/uri.js';
import { ICodeEditorService } from './services/codeEditorService.js';
import { Position } from '../common/core/position.js';
import { IModelService } from '../common/services/model.js';
import { ITextModelService } from '../common/services/resolverService.js';
import { MenuId, MenuRegistry, Action2 } from '../../platform/actions/common/actions.js';
import { CommandsRegistry } from '../../platform/commands/common/commands.js';
import { ContextKeyExpr, IContextKeyService } from '../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
import { KeybindingsRegistry } from '../../platform/keybinding/common/keybindingsRegistry.js';
import { Registry } from '../../platform/registry/common/platform.js';
import { ITelemetryService } from '../../platform/telemetry/common/telemetry.js';
import { assertType } from '../../base/common/types.js';
import { ILogService } from '../../platform/log/common/log.js';
import { getActiveElement } from '../../base/browser/dom.js';
export class Command {
constructor(opts) {
this.id = opts.id;
this.precondition = opts.precondition;
this._kbOpts = opts.kbOpts;
this._menuOpts = opts.menuOpts;
this.metadata = opts.metadata;
}
register() {
if (Array.isArray(this._menuOpts)) {
this._menuOpts.forEach(this._registerMenuItem, this);
}
else if (this._menuOpts) {
this._registerMenuItem(this._menuOpts);
}
if (this._kbOpts) {
const kbOptsArr = Array.isArray(this._kbOpts) ? this._kbOpts : [this._kbOpts];
for (const kbOpts of kbOptsArr) {
let kbWhen = kbOpts.kbExpr;
if (this.precondition) {
if (kbWhen) {
kbWhen = ContextKeyExpr.and(kbWhen, this.precondition);
}
else {
kbWhen = this.precondition;
}
}
const desc = {
id: this.id,
weight: kbOpts.weight,
args: kbOpts.args,
when: kbWhen,
primary: kbOpts.primary,
secondary: kbOpts.secondary,
win: kbOpts.win,
linux: kbOpts.linux,
mac: kbOpts.mac,
};
KeybindingsRegistry.registerKeybindingRule(desc);
}
}
CommandsRegistry.registerCommand({
id: this.id,
handler: (accessor, args) => this.runCommand(accessor, args),
metadata: this.metadata
});
}
_registerMenuItem(item) {
MenuRegistry.appendMenuItem(item.menuId, {
group: item.group,
command: {
id: this.id,
title: item.title,
icon: item.icon,
precondition: this.precondition
},
when: item.when,
order: item.order
});
}
}
export class MultiCommand extends Command {
constructor() {
super(...arguments);
this._implementations = [];
}
/**
* A higher priority gets to be looked at first
*/
addImplementation(priority, name, implementation, when) {
this._implementations.push({ priority, name, implementation, when });
this._implementations.sort((a, b) => b.priority - a.priority);
return {
dispose: () => {
for (let i = 0; i < this._implementations.length; i++) {
if (this._implementations[i].implementation === implementation) {
this._implementations.splice(i, 1);
return;
}
}
}
};
}
runCommand(accessor, args) {
const logService = accessor.get(ILogService);
const contextKeyService = accessor.get(IContextKeyService);
logService.trace(`Executing Command '${this.id}' which has ${this._implementations.length} bound.`);
for (const impl of this._implementations) {
if (impl.when) {
const context = contextKeyService.getContext(getActiveElement());
const value = impl.when.evaluate(context);
if (!value) {
continue;
}
}
const result = impl.implementation(accessor, args);
if (result) {
logService.trace(`Command '${this.id}' was handled by '${impl.name}'.`);
if (typeof result === 'boolean') {
return;
}
return result;
}
}
logService.trace(`The Command '${this.id}' was not handled by any implementation.`);
}
}
//#endregion
/**
* A command that delegates to another command's implementation.
*
* This lets different commands be registered but share the same implementation
*/
export class ProxyCommand extends Command {
constructor(command, opts) {
super(opts);
this.command = command;
}
runCommand(accessor, args) {
return this.command.runCommand(accessor, args);
}
}
export class EditorCommand extends Command {
/**
* Create a command class that is bound to a certain editor contribution.
*/
static bindToContribution(controllerGetter) {
return class EditorControllerCommandImpl extends EditorCommand {
constructor(opts) {
super(opts);
this._callback = opts.handler;
}
runEditorCommand(accessor, editor, args) {
const controller = controllerGetter(editor);
if (controller) {
this._callback(controller, args);
}
}
};
}
static runEditorCommand(accessor, args, precondition, runner) {
const codeEditorService = accessor.get(ICodeEditorService);
// Find the editor with text focus or active
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
if (!editor) {
// well, at least we tried...
return;
}
return editor.invokeWithinContext((editorAccessor) => {
const kbService = editorAccessor.get(IContextKeyService);
if (!kbService.contextMatchesRules(precondition ?? undefined)) {
// precondition does not hold
return;
}
return runner(editorAccessor, editor, args);
});
}
runCommand(accessor, args) {
return EditorCommand.runEditorCommand(accessor, args, this.precondition, (accessor, editor, args) => this.runEditorCommand(accessor, editor, args));
}
}
export class EditorAction extends EditorCommand {
static convertOptions(opts) {
let menuOpts;
if (Array.isArray(opts.menuOpts)) {
menuOpts = opts.menuOpts;
}
else if (opts.menuOpts) {
menuOpts = [opts.menuOpts];
}
else {
menuOpts = [];
}
function withDefaults(item) {
if (!item.menuId) {
item.menuId = MenuId.EditorContext;
}
if (!item.title) {
item.title = opts.label;
}
item.when = ContextKeyExpr.and(opts.precondition, item.when);
return item;
}
if (Array.isArray(opts.contextMenuOpts)) {
menuOpts.push(...opts.contextMenuOpts.map(withDefaults));
}
else if (opts.contextMenuOpts) {
menuOpts.push(withDefaults(opts.contextMenuOpts));
}
opts.menuOpts = menuOpts;
return opts;
}
constructor(opts) {
super(EditorAction.convertOptions(opts));
this.label = opts.label;
this.alias = opts.alias;
}
runEditorCommand(accessor, editor, args) {
this.reportTelemetry(accessor, editor);
return this.run(accessor, editor, args || {});
}
reportTelemetry(accessor, editor) {
accessor.get(ITelemetryService).publicLog2('editorActionInvoked', { name: this.label, id: this.id });
}
}
export class MultiEditorAction extends EditorAction {
constructor() {
super(...arguments);
this._implementations = [];
}
/**
* A higher priority gets to be looked at first
*/
addImplementation(priority, implementation) {
this._implementations.push([priority, implementation]);
this._implementations.sort((a, b) => b[0] - a[0]);
return {
dispose: () => {
for (let i = 0; i < this._implementations.length; i++) {
if (this._implementations[i][1] === implementation) {
this._implementations.splice(i, 1);
return;
}
}
}
};
}
run(accessor, editor, args) {
for (const impl of this._implementations) {
const result = impl[1](accessor, editor, args);
if (result) {
if (typeof result === 'boolean') {
return;
}
return result;
}
}
}
}
//#endregion EditorAction
//#region EditorAction2
export class EditorAction2 extends Action2 {
run(accessor, ...args) {
// Find the editor with text focus or active
const codeEditorService = accessor.get(ICodeEditorService);
const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
if (!editor) {
// well, at least we tried...
return;
}
// precondition does hold
return editor.invokeWithinContext((editorAccessor) => {
const kbService = editorAccessor.get(IContextKeyService);
const logService = editorAccessor.get(ILogService);
const enabled = kbService.contextMatchesRules(this.desc.precondition ?? undefined);
if (!enabled) {
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
return;
}
return this.runEditorCommand(editorAccessor, editor, ...args);
});
}
}
//#endregion
// --- Registration of commands and actions
export function registerModelAndPositionCommand(id, handler) {
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
const instaService = accessor.get(IInstantiationService);
const [resource, position] = args;
assertType(URI.isUri(resource));
assertType(Position.isIPosition(position));
const model = accessor.get(IModelService).getModel(resource);
if (model) {
const editorPosition = Position.lift(position);
return instaService.invokeFunction(handler, model, editorPosition, ...args.slice(2));
}
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
return new Promise((resolve, reject) => {
try {
const result = instaService.invokeFunction(handler, reference.object.textEditorModel, Position.lift(position), args.slice(2));
resolve(result);
}
catch (err) {
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
});
}
export function registerEditorCommand(editorCommand) {
EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand);
return editorCommand;
}
export function registerEditorAction(ctor) {
const action = new ctor();
EditorContributionRegistry.INSTANCE.registerEditorAction(action);
return action;
}
export function registerMultiEditorAction(action) {
EditorContributionRegistry.INSTANCE.registerEditorAction(action);
return action;
}
export function registerInstantiatedEditorAction(editorAction) {
EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
}
/**
* Registers an editor contribution. Editor contributions have a lifecycle which is bound
* to a specific code editor instance.
*/
export function registerEditorContribution(id, ctor, instantiation) {
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor, instantiation);
}
export var EditorExtensionsRegistry;
(function (EditorExtensionsRegistry) {
function getEditorCommand(commandId) {
return EditorContributionRegistry.INSTANCE.getEditorCommand(commandId);
}
EditorExtensionsRegistry.getEditorCommand = getEditorCommand;
function getEditorActions() {
return EditorContributionRegistry.INSTANCE.getEditorActions();
}
EditorExtensionsRegistry.getEditorActions = getEditorActions;
function getEditorContributions() {
return EditorContributionRegistry.INSTANCE.getEditorContributions();
}
EditorExtensionsRegistry.getEditorContributions = getEditorContributions;
function getSomeEditorContributions(ids) {
return EditorContributionRegistry.INSTANCE.getEditorContributions().filter(c => ids.indexOf(c.id) >= 0);
}
EditorExtensionsRegistry.getSomeEditorContributions = getSomeEditorContributions;
function getDiffEditorContributions() {
return EditorContributionRegistry.INSTANCE.getDiffEditorContributions();
}
EditorExtensionsRegistry.getDiffEditorContributions = getDiffEditorContributions;
})(EditorExtensionsRegistry || (EditorExtensionsRegistry = {}));
// Editor extension points
const Extensions = {
EditorCommonContributions: 'editor.contributions'
};
class EditorContributionRegistry {
static { this.INSTANCE = new EditorContributionRegistry(); }
constructor() {
this.editorContributions = [];
this.diffEditorContributions = [];
this.editorActions = [];
this.editorCommands = Object.create(null);
}
registerEditorContribution(id, ctor, instantiation) {
this.editorContributions.push({ id, ctor: ctor, instantiation });
}
getEditorContributions() {
return this.editorContributions.slice(0);
}
getDiffEditorContributions() {
return this.diffEditorContributions.slice(0);
}
registerEditorAction(action) {
action.register();
this.editorActions.push(action);
}
getEditorActions() {
return this.editorActions;
}
registerEditorCommand(editorCommand) {
editorCommand.register();
this.editorCommands[editorCommand.id] = editorCommand;
}
getEditorCommand(commandId) {
return (this.editorCommands[commandId] || null);
}
}
Registry.add(Extensions.EditorCommonContributions, EditorContributionRegistry.INSTANCE);
function registerCommand(command) {
command.register();
return command;
}
export const UndoCommand = registerCommand(new MultiCommand({
id: 'undo',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
primary: 2048 /* KeyMod.CtrlCmd */ | 56 /* KeyCode.KeyZ */
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
registerCommand(new ProxyCommand(UndoCommand, { id: 'default:undo', precondition: undefined }));
export const RedoCommand = registerCommand(new MultiCommand({
id: 'redo',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
primary: 2048 /* KeyMod.CtrlCmd */ | 55 /* KeyCode.KeyY */,
secondary: [2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */],
mac: { primary: 2048 /* KeyMod.CtrlCmd */ | 1024 /* KeyMod.Shift */ | 56 /* KeyCode.KeyZ */ }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
registerCommand(new ProxyCommand(RedoCommand, { id: 'default:redo', precondition: undefined }));
export const SelectAllCommand = registerCommand(new MultiCommand({
id: 'editor.action.selectAll',
precondition: undefined,
kbOpts: {
weight: 0 /* KeybindingWeight.EditorCore */,
kbExpr: null,
primary: 2048 /* KeyMod.CtrlCmd */ | 31 /* KeyCode.KeyA */
},
menuOpts: [{
menuId: MenuId.MenubarSelectionMenu,
group: '1_basic',
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
}));