Skip to content

Commit

Permalink
Fixes microsoft/monaco-editor#386: Shortcuts for actions added via ed…
Browse files Browse the repository at this point in the history
…itor.addAction don't show up in the Command Palette
  • Loading branch information
alexdima committed Mar 1, 2017
1 parent 3cc9e7f commit fe21b0a
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 135 deletions.
91 changes: 76 additions & 15 deletions src/vs/editor/browser/standalone/standaloneCodeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@

'use strict';

import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { empty as emptyDisposable, IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOptions, IActionDescriptor, ICodeEditorWidgetCreationOptions, IDiffEditorOptions, IModel, IModelChangedEvent, EventType } from 'vs/editor/common/editorCommon';
Expand All @@ -21,6 +22,8 @@ import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { IStandaloneColorService } from 'vs/editor/common/services/standaloneColorService';
import { IOSupport } from 'vs/platform/keybinding/common/keybindingResolver';
import { InternalEditorAction } from 'vs/editor/common/editorAction';
import { MenuId, MenuRegistry, IMenuItem } from 'vs/platform/actions/common/actions';

/**
* The options to create an editor.
Expand Down Expand Up @@ -113,27 +116,85 @@ export class StandaloneCodeEditor extends CodeEditor implements IStandaloneCodeE
return this._contextKeyService.createKey(key, defaultValue);
}

public addAction(descriptor: IActionDescriptor): IDisposable {
let addedAction = this._addAction(descriptor);
let toDispose = [addedAction.disposable];
public addAction(_descriptor: IActionDescriptor): IDisposable {
if ((typeof _descriptor.id !== 'string') || (typeof _descriptor.label !== 'string') || (typeof _descriptor.run !== 'function')) {
throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');
}
if (!this._standaloneKeybindingService) {
console.warn('Cannot add keybinding because the editor is configured with an unrecognized KeybindingService');
return null;
return emptyDisposable;
}
if (Array.isArray(descriptor.keybindings)) {
let handler: ICommandHandler = (accessor) => {
return this.trigger('keyboard', descriptor.id, null);

// Read descriptor options
const id = _descriptor.id;
const label = _descriptor.label;
const precondition = ContextKeyExpr.and(
ContextKeyExpr.equals('editorId', this.getId()),
IOSupport.readKeybindingWhen(_descriptor.precondition)
);
const keybindings = _descriptor.keybindings;
const keybindingsWhen = ContextKeyExpr.and(
precondition,
IOSupport.readKeybindingWhen(_descriptor.keybindingContext)
);
const contextMenuGroupId = _descriptor.contextMenuGroupId || null;
const contextMenuOrder = _descriptor.contextMenuOrder || 0;
const run = (): TPromise<void> => {
return TPromise.as(_descriptor.run(this));
};
// return TPromise.as(this._run(this._editor));


let toDispose: IDisposable[] = [];


// Generate a unique id to allow the same descriptor.id across multiple editor instances
const uniqueId = this.getId() + ':' + id;

// Register the command
toDispose.push(CommandsRegistry.registerCommand(uniqueId, run));

// Register the context menu item
if (contextMenuGroupId) {
let menuItem: IMenuItem = {
command: {
id: uniqueId,
title: label
},
when: precondition,
group: contextMenuGroupId,
order: contextMenuOrder
};
let whenExpression = ContextKeyExpr.and(
IOSupport.readKeybindingWhen(descriptor.precondition),
IOSupport.readKeybindingWhen(descriptor.keybindingContext),
);
toDispose.push(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));
}

// Register the keybindings
if (Array.isArray(keybindings)) {
toDispose = toDispose.concat(
descriptor.keybindings.map((kb) => {
return this._standaloneKeybindingService.addDynamicKeybinding(addedAction.uniqueId, kb, handler, whenExpression);
keybindings.map((kb) => {
return this._standaloneKeybindingService.addDynamicKeybinding(uniqueId, kb, run, keybindingsWhen);
})
);
}

// Finally, register an internal editor action
let internalAction = new InternalEditorAction(
uniqueId,
label,
label,
precondition,
run,
this._contextKeyService
);

// Store it under the original id, such that trigger with the original id will work
this._actions[id] = internalAction;
toDispose.push({
dispose: () => {
delete this._actions[id];
}
});

return combinedDisposable(toDispose);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/browser/standalone/standaloneEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ export function createMonacoEditorAPI(): typeof monaco.editor {
// methods
create: <any>create,
onDidCreateEditor: <any>onDidCreateEditor,
createDiffEditor: createDiffEditor,
createDiffNavigator: createDiffNavigator,
createDiffEditor: <any>createDiffEditor,
createDiffNavigator: <any>createDiffNavigator,

createModel: createModel,
setModelLanguage: setModelLanguage,
Expand Down
14 changes: 13 additions & 1 deletion src/vs/editor/browser/widget/codeEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import 'vs/css!./media/editor';
import 'vs/css!./media/tokens';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IEventEmitter } from 'vs/base/common/eventEmitter';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
Expand Down Expand Up @@ -94,7 +95,18 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito
}

this._getActions().forEach((action) => {
let internalAction = new InternalEditorAction(action, this, this._instantiationService, this._contextKeyService);
const internalAction = new InternalEditorAction(
action.id,
action.label,
action.alias,
action.precondition,
(): void | TPromise<void> => {
return this._instantiationService.invokeFunction((accessor) => {
return action.runEditorCommand(accessor, this, null);
});
},
this._contextKeyService
);
this._actions[internalAction.id] = internalAction;
});

Expand Down
5 changes: 0 additions & 5 deletions src/vs/editor/browser/widget/diffEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { Configuration } from 'vs/editor/browser/config/configuration';
import { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel';
import { IAddedAction } from 'vs/editor/common/commonCodeEditor';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ColorId, MetadataConsts, FontStyle } from 'vs/editor/common/modes';

Expand Down Expand Up @@ -628,10 +627,6 @@ export class DiffEditorWidget extends EventEmitter implements editorBrowser.IDif
this.modifiedEditor.revealRangeAtTop(range);
}

public _addAction(descriptor: editorCommon.IActionDescriptor): IAddedAction {
return this.modifiedEditor._addAction(descriptor);
}

public getActions(): editorCommon.IEditorAction[] {
return this.modifiedEditor.getActions();
}
Expand Down
62 changes: 2 additions & 60 deletions src/vs/editor/common/commonCodeEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import { onUnexpectedError } from 'vs/base/common/errors';
import Event, { fromEventEmitter } from 'vs/base/common/event';
import { EventEmitter, IEventEmitter } from 'vs/base/common/eventEmitter';
import { Disposable, IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ContextKeyExpr, IContextKey, IContextKeyServiceTarget, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextKey, IContextKeyServiceTarget, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CommonEditorConfiguration } from 'vs/editor/common/config/commonEditorConfig';
import { DefaultConfig } from 'vs/editor/common/config/defaultConfig';
import { Cursor } from 'vs/editor/common/controller/cursor';
Expand All @@ -21,26 +21,17 @@ import { EditorState } from 'vs/editor/common/core/editorState';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { DynamicEditorAction } from 'vs/editor/common/editorAction';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import { SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { hash } from 'vs/base/common/hash';
import { EditorModeContext } from 'vs/editor/common/modes/editorModeContext';
import { MenuId, MenuRegistry, IMenuItem } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IOSupport } from 'vs/platform/keybinding/common/keybindingResolver';

import EditorContextKeys = editorCommon.EditorContextKeys;

let EDITOR_ID = 0;

export interface IAddedAction {
uniqueId: string;
disposable: IDisposable;
}

export abstract class CommonCodeEditor extends EventEmitter implements editorCommon.ICommonCodeEditor {

public readonly onDidChangeModelRawContent: Event<editorCommon.IModelContentChangedEvent> = fromEventEmitter(this, editorCommon.EventType.ModelRawContentChanged);
Expand Down Expand Up @@ -529,55 +520,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom
return <T>(this._contributions[id] || null);
}

public _addAction(descriptor: editorCommon.IActionDescriptor): IAddedAction {
if (
(typeof descriptor.id !== 'string')
|| (typeof descriptor.label !== 'string')
|| (typeof descriptor.run !== 'function')
) {
throw new Error('Invalid action descriptor, `id`, `label` and `run` are required properties!');
}
let toDispose: IDisposable[] = [];

// Generate a unique id to allow the same descriptor.id across multiple editor instances
let uniqueId = this.getId() + ':' + descriptor.id;

let action = new DynamicEditorAction(descriptor.id, descriptor.label, descriptor.run, this);

// Register the command
toDispose.push(CommandsRegistry.registerCommand(uniqueId, () => action.run()));

if (descriptor.contextMenuGroupId) {
let menuItem: IMenuItem = {
command: {
id: uniqueId,
title: descriptor.label
},
when: ContextKeyExpr.and(
ContextKeyExpr.equals('editorId', this.getId()),
IOSupport.readKeybindingWhen(descriptor.precondition)
),
group: descriptor.contextMenuGroupId,
order: descriptor.contextMenuOrder || 0
};

// Register the menu item
toDispose.push(MenuRegistry.appendMenuItem(MenuId.EditorContext, menuItem));
}

this._actions[action.id] = action;
toDispose.push({
dispose: () => {
delete this._actions[action.id];
}
});

return {
uniqueId: uniqueId,
disposable: combinedDisposable(toDispose)
};
}

public getActions(): editorCommon.IEditorAction[] {
let result: editorCommon.IEditorAction[] = [];

Expand Down
74 changes: 22 additions & 52 deletions src/vs/editor/common/editorAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,44 @@
'use strict';

import { TPromise } from 'vs/base/common/winjs.base';
import { ICommonCodeEditor, IEditorAction } from 'vs/editor/common/editorCommon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EditorAction } from 'vs/editor/common/editorCommonExtensions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';

export abstract class AbstractInternalEditorAction {
export class InternalEditorAction implements IEditorAction {

public id: string;
public label: string;
public alias: string;
protected _editor: ICommonCodeEditor;
public readonly id: string;
public readonly label: string;
public readonly alias: string;

constructor(id: string, label: string, alias: string, editor: ICommonCodeEditor) {
this.id = id;
this.label = label;
this.alias = alias;
this._editor = editor;
}
}

export class InternalEditorAction extends AbstractInternalEditorAction implements IEditorAction {

private _actual: EditorAction;
private _instantiationService: IInstantiationService;
private _contextKeyService: IContextKeyService;
private readonly _precondition: ContextKeyExpr;
private readonly _run: () => void | TPromise<void>;
private readonly _contextKeyService: IContextKeyService;

constructor(
actual: EditorAction,
editor: ICommonCodeEditor,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService
id: string,
label: string,
alias: string,
precondition: ContextKeyExpr,
run: () => void,
contextKeyService: IContextKeyService
) {
super(actual.id, actual.label, actual.alias, editor);
this._actual = actual;
this._instantiationService = instantiationService;
this.id = id;
this.label = label;
this.alias = alias;
this._precondition = precondition;
this._run = run;
this._contextKeyService = contextKeyService;
}

public isSupported(): boolean {
return this._contextKeyService.contextMatchesRules(this._actual.precondition);
return this._contextKeyService.contextMatchesRules(this._precondition);
}

public run(): TPromise<void> {
if (!this.isSupported()) {
return TPromise.as(void 0);
}

return this._instantiationService.invokeFunction((accessor) => {
return TPromise.as(this._actual.runEditorCommand(accessor, this._editor, null));
});
}
}

export class DynamicEditorAction extends AbstractInternalEditorAction implements IEditorAction {

private _run: (editor: ICommonCodeEditor) => void;

constructor(id: string, label: string, run: (editor: ICommonCodeEditor) => void | TPromise<void>, editor: ICommonCodeEditor) {
super(id, label, label, editor);

this._run = run;
}

public isSupported(): boolean {
return true;
}

public run(): TPromise<void> {
return TPromise.as(this._run(this._editor));
return TPromise.as(this._run());
}
}

0 comments on commit fe21b0a

Please sign in to comment.