keybindingResolver.test.ts 14.9 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

7
import * as assert from 'assert';
A
Alex Dima 已提交
8
import { createKeybinding, SimpleKeybinding, KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
9
import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver';
J
Joao Moreno 已提交
10
import { IContext, ContextKeyAndExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
11
import { NormalizedKeybindingItem } from 'vs/platform/keybinding/common/normalizedKeybindingItem';
E
Erich Gamma 已提交
12

J
Joao Moreno 已提交
13 14
const createContext = ctx => ({ getValue: key => ctx[key] });

15 16 17
suite('KeybindingResolver', () => {

	function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): NormalizedKeybindingItem {
18 19 20 21 22 23 24 25
		return NormalizedKeybindingItem.fromKeybindingItem({
			keybinding: keybinding,
			command: command,
			commandArgs: commandArgs,
			when: when,
			weight1: 0,
			weight2: 0
		}, isDefault);
26
	}
E
Erich Gamma 已提交
27

J
Johannes Rieken 已提交
28
	test('resolve key', function () {
B
Benjamin Pasero 已提交
29
		let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z;
A
Alex Dima 已提交
30
		let contextRules = ContextKeyExpr.equals('bar', 'baz');
31
		let keybindingItem = kbItem(keybinding, 'yes', null, contextRules, true);
E
Erich Gamma 已提交
32

J
Joao Moreno 已提交
33 34
		assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'baz' }), contextRules), true);
		assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false);
E
Erich Gamma 已提交
35

B
Benjamin Pasero 已提交
36
		let resolver = new KeybindingResolver([keybindingItem], []);
J
Joao Moreno 已提交
37 38
		assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, new SimpleKeybinding(keybinding).value.toString()).commandId, 'yes');
		assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, new SimpleKeybinding(keybinding).value.toString()), null);
E
Erich Gamma 已提交
39 40
	});

41 42 43 44
	test('resolve key with arguments', function () {
		let commandArgs = { text: 'no' };
		let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z;
		let contextRules = ContextKeyExpr.equals('bar', 'baz');
45
		let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true);
46 47

		let resolver = new KeybindingResolver([keybindingItem], []);
J
Joao Moreno 已提交
48
		assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, new SimpleKeybinding(keybinding).value.toString()).commandArgs, commandArgs);
49 50
	});

J
Johannes Rieken 已提交
51
	test('KeybindingResolver.combine simple 1', function () {
52 53 54 55 56 57
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false)
		];
58 59
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
60 61
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), false),
62 63 64
		]);
	});

J
Johannes Rieken 已提交
65
	test('KeybindingResolver.combine simple 2', function () {
66 67 68 69 70 71 72
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false)
		];
73 74
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
75 76 77
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true),
			kbItem(KeyCode.KEY_C, 'yes3', null, ContextKeyExpr.equals('3', 'c'), false),
78 79 80
		]);
	});

J
Johannes Rieken 已提交
81
	test('KeybindingResolver.combine removal with not matching when', function () {
82 83 84 85 86 87 88
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'b'), false)
		];
89 90
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
91 92
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
93 94 95
		]);
	});

J
Johannes Rieken 已提交
96
	test('KeybindingResolver.combine removal with not matching keybinding', function () {
97 98 99 100 101 102 103
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_B, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
		];
104 105
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
106 107
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
108 109 110
		]);
	});

J
Johannes Rieken 已提交
111
	test('KeybindingResolver.combine removal with matching keybinding and when', function () {
112 113 114 115 116 117 118
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_A, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
		];
119 120
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
121
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
122 123 124
		]);
	});

J
Johannes Rieken 已提交
125
	test('KeybindingResolver.combine removal with unspecified keybinding', function () {
126 127 128 129 130 131 132
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(0, '-yes1', null, ContextKeyExpr.equals('1', 'a'), false)
		];
133 134
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
135
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
136 137 138
		]);
	});

J
Johannes Rieken 已提交
139
	test('KeybindingResolver.combine removal with unspecified when', function () {
140 141 142 143 144 145 146
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
		];
147 148
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
149
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
150 151 152
		]);
	});

J
Johannes Rieken 已提交
153
	test('KeybindingResolver.combine removal with unspecified when and unspecified keybinding', function () {
154 155 156 157 158 159 160
		let defaults = [
			kbItem(KeyCode.KEY_A, 'yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(0, '-yes1', null, null, false)
		];
161 162
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
163
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
164 165 166
		]);
	});

J
Johannes Rieken 已提交
167
	test('issue #612#issuecomment-222109084 cannot remove keybindings for commands with ^', function () {
168 169 170 171 172 173 174
		let defaults = [
			kbItem(KeyCode.KEY_A, '^yes1', null, ContextKeyExpr.equals('1', 'a'), true),
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
		];
		let overrides = [
			kbItem(KeyCode.KEY_A, '-yes1', null, null, false)
		];
175 176
		let actual = KeybindingResolver.combine(defaults, overrides);
		assert.deepEqual(actual, [
177
			kbItem(KeyCode.KEY_B, 'yes2', null, ContextKeyExpr.equals('2', 'b'), true)
178 179 180
		]);
	});

J
Johannes Rieken 已提交
181
	test('contextIsEntirelyIncluded', function () {
A
Alex Dima 已提交
182
		let assertIsIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
A
Alex Dima 已提交
183
			assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), true);
E
Erich Gamma 已提交
184
		};
A
Alex Dima 已提交
185
		let assertIsNotIncluded = (a: ContextKeyExpr[], b: ContextKeyExpr[]) => {
A
Alex Dima 已提交
186
			assert.equal(KeybindingResolver.whenIsEntirelyIncluded(false, new ContextKeyAndExpr(a), new ContextKeyAndExpr(b)), false);
E
Erich Gamma 已提交
187
		};
A
Alex Dima 已提交
188 189 190 191 192 193 194 195
		let key1IsTrue = ContextKeyExpr.equals('key1', true);
		let key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);
		let key1IsFalse = ContextKeyExpr.equals('key1', false);
		let key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
		let key2IsTrue = ContextKeyExpr.equals('key2', true);
		let key2IsNotFalse = ContextKeyExpr.notEquals('key2', false);
		let key3IsTrue = ContextKeyExpr.equals('key3', true);
		let key4IsTrue = ContextKeyExpr.equals('key4', true);
E
Erich Gamma 已提交
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

		assertIsIncluded([key1IsTrue], null);
		assertIsIncluded([key1IsTrue], []);
		assertIsIncluded([key1IsTrue], [key1IsTrue]);
		assertIsIncluded([key1IsTrue], [key1IsNotFalse]);

		assertIsIncluded([key1IsFalse], []);
		assertIsIncluded([key1IsFalse], [key1IsFalse]);
		assertIsIncluded([key1IsFalse], [key1IsNotTrue]);

		assertIsIncluded([key2IsNotFalse], []);
		assertIsIncluded([key2IsNotFalse], [key2IsNotFalse]);
		assertIsIncluded([key2IsNotFalse], [key2IsTrue]);

		assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsTrue]);
		assertIsIncluded([key1IsTrue, key2IsNotFalse], [key2IsNotFalse]);
		assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsTrue]);
		assertIsIncluded([key1IsTrue, key2IsNotFalse], [key1IsNotFalse]);
		assertIsIncluded([key1IsTrue, key2IsNotFalse], []);

		assertIsNotIncluded([key1IsTrue], [key1IsFalse]);
		assertIsNotIncluded([key1IsTrue], [key1IsNotTrue]);
		assertIsNotIncluded([key1IsNotFalse], [key1IsFalse]);
		assertIsNotIncluded([key1IsNotFalse], [key1IsNotTrue]);

		assertIsNotIncluded([key1IsFalse], [key1IsTrue]);
		assertIsNotIncluded([key1IsFalse], [key1IsNotFalse]);
		assertIsNotIncluded([key1IsNotTrue], [key1IsTrue]);
		assertIsNotIncluded([key1IsNotTrue], [key1IsNotFalse]);

		assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key3IsTrue]);
		assertIsNotIncluded([key1IsTrue, key2IsNotFalse], [key4IsTrue]);
		assertIsNotIncluded([key1IsTrue], [key2IsTrue]);
		assertIsNotIncluded([], [key2IsTrue]);
		assertIsNotIncluded(null, [key2IsTrue]);
	});

J
Johannes Rieken 已提交
233
	test('resolve command', function () {
E
Erich Gamma 已提交
234

235 236 237 238 239
		function _kbItem(keybinding: number, command: string, when: ContextKeyExpr): NormalizedKeybindingItem {
			return kbItem(keybinding, command, null, when, true);
		}

		let items = [
240
			// This one will never match because its "when" is always overwritten by another one
241 242 243 244
			_kbItem(
				KeyCode.KEY_X,
				'first',
				ContextKeyExpr.and(
A
Alex Dima 已提交
245 246
					ContextKeyExpr.equals('key1', true),
					ContextKeyExpr.notEquals('key2', false)
247 248
				)
			),
E
Erich Gamma 已提交
249
			// This one always overwrites first
250 251 252 253 254
			_kbItem(
				KeyCode.KEY_X,
				'second',
				ContextKeyExpr.equals('key2', true)
			),
E
Erich Gamma 已提交
255
			// This one is a secondary mapping for `second`
256 257 258 259 260
			_kbItem(
				KeyCode.KEY_Z,
				'second',
				null
			),
E
Erich Gamma 已提交
261
			// This one sometimes overwrites first
262 263 264 265 266
			_kbItem(
				KeyCode.KEY_X,
				'third',
				ContextKeyExpr.equals('key3', true)
			),
E
Erich Gamma 已提交
267
			// This one is always overwritten by another one
268 269 270 271 272
			_kbItem(
				KeyMod.CtrlCmd | KeyCode.KEY_Y,
				'fourth',
				ContextKeyExpr.equals('key4', true)
			),
E
Erich Gamma 已提交
273
			// This one overwrites with a chord the previous one
274 275 276 277 278
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z),
				'fifth',
				null
			),
E
Erich Gamma 已提交
279
			// This one has no keybinding
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
			_kbItem(
				0,
				'sixth',
				null
			),
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
				'seventh',
				null
			),
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K),
				'seventh',
				null
			),
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U),
				'uncomment lines',
				null
			),
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C),
				'comment lines',
				null
			),
			_kbItem(
				KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_G, KeyMod.CtrlCmd | KeyCode.KEY_C),
				'unreachablechord',
				null
			),
			_kbItem(
				KeyMod.CtrlCmd | KeyCode.KEY_G,
				'eleven',
				null
			)
E
Erich Gamma 已提交
315 316
		];

B
Benjamin Pasero 已提交
317
		let resolver = new KeybindingResolver(items, [], false);
E
Erich Gamma 已提交
318

B
Benjamin Pasero 已提交
319
		let testKey = (commandId: string, expectedKeys: number[]) => {
E
Erich Gamma 已提交
320
			// Test lookup
321
			let lookupResult = resolver.lookupKeybindings(commandId);
E
Erich Gamma 已提交
322 323
			assert.equal(lookupResult.length, expectedKeys.length, 'Length mismatch @ commandId ' + commandId + '; GOT: ' + JSON.stringify(lookupResult, null, '\t'));
			for (let i = 0, len = lookupResult.length; i < len; i++) {
324
				assert.equal(lookupResult[i].keybinding.value, expectedKeys[i], 'value mismatch @ commandId ' + commandId);
E
Erich Gamma 已提交
325 326 327
			}
		};

J
Joao Moreno 已提交
328
		let testResolve = (ctx: IContext, _expectedKey: number, commandId: string) => {
A
Alex Dima 已提交
329
			let expectedKey = createKeybinding(_expectedKey);
E
Erich Gamma 已提交
330

A
Alex Dima 已提交
331
			if (expectedKey.isChord()) {
332 333
				let firstPart = expectedKey.extractFirstPart().value.toString();
				let chordPart = expectedKey.extractChordPart().value.toString();
E
Erich Gamma 已提交
334

A
Alex Dima 已提交
335
				let result = resolver.resolve(ctx, null, firstPart);
E
Erich Gamma 已提交
336 337
				assert.ok(result !== null, 'Enters chord for ' + commandId);
				assert.equal(result.commandId, null, 'Enters chord for ' + commandId);
338
				assert.equal(result.enterChord, true, 'Enters chord for ' + commandId);
E
Erich Gamma 已提交
339 340 341 342

				result = resolver.resolve(ctx, firstPart, chordPart);
				assert.ok(result !== null, 'Enters chord for ' + commandId);
				assert.equal(result.commandId, commandId, 'Finds chorded command ' + commandId);
343
				assert.equal(result.enterChord, false, 'Finds chorded command ' + commandId);
E
Erich Gamma 已提交
344
			} else {
345
				let result = resolver.resolve(ctx, null, expectedKey.value.toString());
E
Erich Gamma 已提交
346 347
				assert.ok(result !== null, 'Finds command ' + commandId);
				assert.equal(result.commandId, commandId, 'Finds command ' + commandId);
348
				assert.equal(result.enterChord, false, 'Finds command ' + commandId);
E
Erich Gamma 已提交
349
			}
B
Benjamin Pasero 已提交
350
		};
E
Erich Gamma 已提交
351 352 353 354

		testKey('first', []);

		testKey('second', [KeyCode.KEY_Z, KeyCode.KEY_X]);
J
Joao Moreno 已提交
355 356
		testResolve(createContext({ key2: true }), KeyCode.KEY_X, 'second');
		testResolve(createContext({}), KeyCode.KEY_Z, 'second');
E
Erich Gamma 已提交
357 358

		testKey('third', [KeyCode.KEY_X]);
J
Joao Moreno 已提交
359
		testResolve(createContext({ key3: true }), KeyCode.KEY_X, 'third');
E
Erich Gamma 已提交
360 361 362

		testKey('fourth', []);

A
Alex Dima 已提交
363
		testKey('fifth', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z)]);
J
Joao Moreno 已提交
364
		testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z), 'fifth');
E
Erich Gamma 已提交
365

A
Alex Dima 已提交
366
		testKey('seventh', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K)]);
J
Joao Moreno 已提交
367
		testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K), 'seventh');
E
Erich Gamma 已提交
368

A
Alex Dima 已提交
369
		testKey('uncomment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U)]);
J
Joao Moreno 已提交
370
		testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_U), 'uncomment lines');
E
Erich Gamma 已提交
371

A
Alex Dima 已提交
372
		testKey('comment lines', [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C)]);
J
Joao Moreno 已提交
373
		testResolve(createContext({}), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_C), 'comment lines');
E
Erich Gamma 已提交
374 375 376 377

		testKey('unreachablechord', []);

		testKey('eleven', [KeyMod.CtrlCmd | KeyCode.KEY_G]);
J
Joao Moreno 已提交
378
		testResolve(createContext({}), KeyMod.CtrlCmd | KeyCode.KEY_G, 'eleven');
E
Erich Gamma 已提交
379 380

		testKey('sixth', []);
381
	});
E
Erich Gamma 已提交
382
});