Skip to content

Commit 78e64e7

Browse files
committed
allow to provide a precondition when registering commands
1 parent 88a2fba commit 78e64e7

4 files changed

Lines changed: 69 additions & 19 deletions

File tree

src/vs/platform/commands/common/commandService.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ICommandService, ICommand, ICommandEvent, CommandsRegistry } from 'vs/p
1010
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
1111
import Event, { Emitter } from 'vs/base/common/event';
1212
import { Disposable } from 'vs/base/common/lifecycle';
13+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1314

1415
export class CommandService extends Disposable implements ICommandService {
1516

@@ -22,7 +23,8 @@ export class CommandService extends Disposable implements ICommandService {
2223

2324
constructor(
2425
@IInstantiationService private _instantiationService: IInstantiationService,
25-
@IExtensionService private _extensionService: IExtensionService
26+
@IExtensionService private _extensionService: IExtensionService,
27+
@IContextKeyService private _contextKeyService: IContextKeyService
2628
) {
2729
super();
2830
this._extensionService.onReady().then(value => this._extensionHostIsReady = value);
@@ -46,6 +48,11 @@ export class CommandService extends Disposable implements ICommandService {
4648
return TPromise.wrapError(new Error(`command '${id}' not found`));
4749
}
4850

51+
if (command.precondition && !this._contextKeyService.contextMatchesRules(command.precondition)) {
52+
// not enabled
53+
return TPromise.wrapError(new Error('NOT_ENABLED'));
54+
}
55+
4956
try {
5057
this._onWillExecuteCommand.fire({ commandId: id });
5158
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler].concat(args));
@@ -55,7 +62,7 @@ export class CommandService extends Disposable implements ICommandService {
5562
}
5663
}
5764

58-
protected _getCommand(id: string): ICommand {
65+
private _getCommand(id: string): ICommand {
5966
return CommandsRegistry.getCommand(id);
6067
}
6168
}

src/vs/platform/commands/common/commands.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
99
import { TypeConstraint, validateConstraints } from 'vs/base/common/types';
1010
import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation';
1111
import Event from 'vs/base/common/event';
12+
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1213

1314
export const ICommandService = createDecorator<ICommandService>('commandService');
1415

@@ -33,6 +34,7 @@ export interface ICommandHandler {
3334

3435
export interface ICommand {
3536
handler: ICommandHandler;
37+
precondition?: ContextKeyExpr;
3638
description?: ICommandHandlerDescription;
3739
}
3840

@@ -52,6 +54,7 @@ export interface ICommandRegistry {
5254
function isCommand(thing: any): thing is ICommand {
5355
return typeof thing === 'object'
5456
&& typeof (<ICommand>thing).handler === 'function'
57+
&& (!(<ICommand>thing).precondition || typeof (<ICommand>thing).precondition === 'object')
5558
&& (!(<ICommand>thing).description || typeof (<ICommand>thing).description === 'object');
5659
}
5760

@@ -71,24 +74,20 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR
7174
command = { handler: commandOrDesc };
7275

7376
} else {
74-
const { handler, description } = commandOrDesc;
75-
if (description) {
77+
if (commandOrDesc.description) {
7678
// add argument validation if rich command metadata is provided
7779
const constraints: TypeConstraint[] = [];
78-
for (let arg of description.args) {
80+
for (let arg of commandOrDesc.description.args) {
7981
constraints.push(arg.constraint);
8082
}
81-
command = {
82-
description,
83-
handler(accessor, ...args: any[]) {
84-
validateConstraints(args, constraints);
85-
return handler(accessor, ...args);
86-
}
83+
const actualHandler = commandOrDesc.handler;
84+
commandOrDesc.handler = function (accessor, ...args: any[]) {
85+
validateConstraints(args, constraints);
86+
return actualHandler(accessor, ...args);
8787
};
88-
} else {
89-
// add as simple handler
90-
command = { handler };
9188
}
89+
90+
command = commandOrDesc;
9291
}
9392

9493
// find a place to store the command

src/vs/platform/commands/test/commandService.test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import { CommandService } from 'vs/platform/commands/common/commandService';
1212
import { IExtensionService, ExtensionPointContribution, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1313
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
1414
import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry';
15+
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
16+
import { SimpleConfigurationService } from 'vs/editor/standalone/browser/simpleServices';
17+
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
1518

1619
class SimpleExtensionService implements IExtensionService {
1720
_serviceBrand: any;
@@ -62,7 +65,7 @@ suite('CommandService', function () {
6265
lastEvent = activationEvent;
6366
return super.activateByEvent(activationEvent);
6467
}
65-
});
68+
}, new ContextKeyService(new SimpleConfigurationService()));
6669

6770
return service.executeCommand('foo').then(() => {
6871
assert.ok(lastEvent, 'onCommand:foo');
@@ -80,7 +83,7 @@ suite('CommandService', function () {
8083
activateByEvent(activationEvent: string): TPromise<void> {
8184
return TPromise.wrapError<void>(new Error('bad_activate'));
8285
}
83-
});
86+
}, new ContextKeyService(new SimpleConfigurationService()));
8487

8588
return service.executeCommand('foo').then(() => assert.ok(false), err => {
8689
assert.equal(err.message, 'bad_activate');
@@ -97,12 +100,37 @@ suite('CommandService', function () {
97100
onReady() {
98101
return new TPromise<boolean>(_resolve => { resolve = _resolve; });
99102
}
100-
});
103+
}, new ContextKeyService(new SimpleConfigurationService()));
101104

102105
return service.executeCommand('bar').then(() => {
103106
reg.dispose();
104107
assert.equal(callCounter, 1);
105108
});
106109
});
107110

108-
});
111+
test('honor command-precondition', function () {
112+
let contextKeyService = new ContextKeyService(new SimpleConfigurationService());
113+
let commandService = new CommandService(
114+
new InstantiationService(),
115+
new SimpleExtensionService(),
116+
contextKeyService
117+
);
118+
119+
let counter = 0;
120+
let reg = CommandsRegistry.registerCommand('bar', {
121+
handler: () => { counter += 1; },
122+
precondition: ContextKeyExpr.has('foocontext')
123+
});
124+
125+
return commandService.executeCommand('bar').then(() => {
126+
assert.throws(() => { });
127+
}, () => {
128+
contextKeyService.setContext('foocontext', true);
129+
return commandService.executeCommand('bar');
130+
}).then(() => {
131+
assert.equal(counter, 1);
132+
reg.dispose();
133+
});
134+
135+
});
136+
});

src/vs/platform/commands/test/commands.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import * as assert from 'assert';
88
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
9-
9+
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
1010

1111
suite('Command Tests', function () {
1212

@@ -76,4 +76,20 @@ suite('Command Tests', function () {
7676
assert.equal(CommandsRegistry.getCommands()['test3'].handler.apply(undefined, [undefined, 1]), true);
7777

7878
});
79+
80+
test('CommandsRegistry with precondition', function () {
81+
let r1 = CommandsRegistry.registerCommand('foo', () => { });
82+
83+
const precondition = new RawContextKey<boolean>('ddd', false);
84+
let r2 = CommandsRegistry.registerCommand('bar', {
85+
handler: () => { },
86+
precondition
87+
});
88+
89+
assert.ok(CommandsRegistry.getCommand('bar').precondition === precondition);
90+
assert.equal(CommandsRegistry.getCommand('foo').precondition, undefined);
91+
92+
r1.dispose();
93+
r2.dispose();
94+
});
7995
});

0 commit comments

Comments
 (0)