Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import * as json from '../../../../../base/common/json.js';
8
import { KeyCode } from '../../../../../base/common/keyCodes.js';
9
import { KeyCodeChord } from '../../../../../base/common/keybindings.js';
10
import { OS } from '../../../../../base/common/platform.js';
11
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
12
import { IEnvironmentService } from '../../../../../platform/environment/common/environment.js';
13
import { IFileService } from '../../../../../platform/files/common/files.js';
14
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
15
import { IUserFriendlyKeybinding } from '../../../../../platform/keybinding/common/keybinding.js';
16
import { ResolvedKeybindingItem } from '../../../../../platform/keybinding/common/resolvedKeybindingItem.js';
17
import { USLayoutResolvedKeybinding } from '../../../../../platform/keybinding/common/usLayoutResolvedKeybinding.js';
18
import { NullLogService } from '../../../../../platform/log/common/log.js';
19
import { KeybindingsEditingService } from '../../common/keybindingEditing.js';
20
import { ITextFileService } from '../../../textfile/common/textfiles.js';
21
import { TestEnvironmentService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
22
import { FileService } from '../../../../../platform/files/common/fileService.js';
23
import { Schemas } from '../../../../../base/common/network.js';
24
import { URI } from '../../../../../base/common/uri.js';
25
import { FileUserDataProvider } from '../../../../../platform/userData/common/fileUserDataProvider.js';
26
import { TestConfigurationService } from '../../../../../platform/configuration/test/common/testConfigurationService.js';
27
import { joinPath } from '../../../../../base/common/resources.js';
28
import { InMemoryFileSystemProvider } from '../../../../../platform/files/common/inMemoryFilesystemProvider.js';
29
import { VSBuffer } from '../../../../../base/common/buffer.js';
30
import { UserDataProfilesService } from '../../../../../platform/userDataProfile/common/userDataProfile.js';
31
import { UserDataProfileService } from '../../../userDataProfile/common/userDataProfileService.js';
32
import { IUserDataProfileService } from '../../../userDataProfile/common/userDataProfile.js';
33
import { UriIdentityService } from '../../../../../platform/uriIdentity/common/uriIdentityService.js';
34
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
35
36
interface Modifiers {
37
metaKey?: boolean;
38
ctrlKey?: boolean;
39
altKey?: boolean;
40
shiftKey?: boolean;
41
}
42
43
const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' });
44
45
suite('KeybindingsEditing', () => {
46
47
const disposables = ensureNoDisposablesAreLeakedInTestSuite();
48
let instantiationService: TestInstantiationService;
49
let fileService: IFileService;
50
let environmentService: IEnvironmentService;
51
let userDataProfileService: IUserDataProfileService;
52
let testObject: KeybindingsEditingService;
53
54
setup(async () => {
55
56
environmentService = TestEnvironmentService;
57
58
const logService = new NullLogService();
59
fileService = disposables.add(new FileService(logService));
60
const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider());
61
disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider));
62
63
const userFolder = joinPath(ROOT, 'User');
64
await fileService.createFolder(userFolder);
65
66
const configService = new TestConfigurationService();
67
configService.setUserConfiguration('files', { 'eol': '\n' });
68
69
const uriIdentityService = disposables.add(new UriIdentityService(fileService));
70
const userDataProfilesService = disposables.add(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
71
userDataProfileService = disposables.add(new UserDataProfileService(userDataProfilesService.defaultProfile));
72
disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, userDataProfilesService, uriIdentityService, new NullLogService()))));
73
74
instantiationService = workbenchInstantiationService({
75
fileService: () => fileService,
76
configurationService: () => configService,
77
environmentService: () => environmentService
78
}, disposables);
79
80
testObject = disposables.add(instantiationService.createInstance(KeybindingsEditingService));
81
});
82
83
test('errors cases - parse errors', async () => {
84
await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(',,,,,,,,,,,,,,'));
85
try {
86
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
87
assert.fail('Should fail with parse errors');
88
} catch (error) {
89
assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.');
90
}
91
});
92
93
test('errors cases - parse errors 2', async () => {
94
await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString('[{"key": }]'));
95
try {
96
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
97
assert.fail('Should fail with parse errors');
98
} catch (error) {
99
assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.');
100
}
101
});
102
103
test('errors cases - dirty', () => {
104
instantiationService.stub(ITextFileService, 'isDirty', true);
105
return testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
106
.then(() => assert.fail('Should fail with dirty error'),
107
error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again.'));
108
});
109
110
test('errors cases - did not find an array', async () => {
111
await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString('{"key": "alt+c", "command": "hello"}'));
112
try {
113
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape } }), 'alt+c', undefined);
114
assert.fail('Should fail');
115
} catch (error) {
116
assert.strictEqual(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.');
117
}
118
});
119
120
test('edit a default keybinding to an empty file', async () => {
121
await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(''));
122
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
123
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
124
assert.deepStrictEqual(await getUserKeybindings(), expected);
125
});
126
127
test('edit a default keybinding to an empty array', async () => {
128
await writeToKeybindingsFile();
129
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
130
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
131
return assert.deepStrictEqual(await getUserKeybindings(), expected);
132
});
133
134
test('edit a default keybinding in an existing array', async () => {
135
await writeToKeybindingsFile({ command: 'b', key: 'shift+c' });
136
const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }];
137
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
138
return assert.deepStrictEqual(await getUserKeybindings(), expected);
139
});
140
141
test('add another keybinding', async () => {
142
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
143
await testObject.addKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined);
144
return assert.deepStrictEqual(await getUserKeybindings(), expected);
145
});
146
147
test('add a new default keybinding', async () => {
148
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
149
await testObject.addKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined);
150
return assert.deepStrictEqual(await getUserKeybindings(), expected);
151
});
152
153
test('add a new default keybinding using edit', async () => {
154
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }];
155
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined);
156
assert.deepStrictEqual(await getUserKeybindings(), expected);
157
});
158
159
test('edit an user keybinding', async () => {
160
await writeToKeybindingsFile({ key: 'escape', command: 'b' });
161
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }];
162
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined);
163
assert.deepStrictEqual(await getUserKeybindings(), expected);
164
});
165
166
test('edit an user keybinding with more than one element', async () => {
167
await writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' });
168
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }];
169
await testObject.editKeybinding(aResolvedKeybindingItem({ firstChord: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined);
170
assert.deepStrictEqual(await getUserKeybindings(), expected);
171
});
172
173
test('remove a default keybinding', async () => {
174
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
175
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
176
assert.deepStrictEqual(await getUserKeybindings(), expected);
177
});
178
179
test('remove a default keybinding should not ad duplicate entries', async () => {
180
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
181
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
182
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
183
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
184
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
185
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } } }));
186
assert.deepStrictEqual(await getUserKeybindings(), expected);
187
});
188
189
test('remove a user keybinding', async () => {
190
await writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
191
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } }, isDefault: false }));
192
assert.deepStrictEqual(await getUserKeybindings(), []);
193
});
194
195
test('reset an edited keybinding', async () => {
196
await writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
197
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', firstChord: { keyCode: KeyCode.KeyC, modifiers: { altKey: true } }, isDefault: false }));
198
assert.deepStrictEqual(await getUserKeybindings(), []);
199
});
200
201
test('reset a removed keybinding', async () => {
202
await writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
203
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }));
204
assert.deepStrictEqual(await getUserKeybindings(), []);
205
});
206
207
test('reset multiple removed keybindings', async () => {
208
await writeToKeybindingsFile({ key: 'alt+c', command: '-b' });
209
await writeToKeybindingsFile({ key: 'alt+shift+c', command: '-b' });
210
await writeToKeybindingsFile({ key: 'escape', command: '-b' });
211
await testObject.resetKeybinding(aResolvedKeybindingItem({ command: 'b', isDefault: false }));
212
assert.deepStrictEqual(await getUserKeybindings(), []);
213
});
214
215
test('add a new keybinding to unassigned keybinding', async () => {
216
await writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
217
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }];
218
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined);
219
assert.deepStrictEqual(await getUserKeybindings(), expected);
220
});
221
222
test('add when expression', async () => {
223
await writeToKeybindingsFile({ key: 'alt+c', command: '-a' });
224
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
225
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus');
226
assert.deepStrictEqual(await getUserKeybindings(), expected);
227
});
228
229
test('update command and when expression', async () => {
230
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
231
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
232
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus');
233
assert.deepStrictEqual(await getUserKeybindings(), expected);
234
});
235
236
test('update when expression', async () => {
237
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' });
238
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }];
239
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false, when: 'editorTextFocus && !editorReadonly' }), 'shift+alt+c', 'editorTextFocus');
240
assert.deepStrictEqual(await getUserKeybindings(), expected);
241
});
242
243
test('remove when expression', async () => {
244
await writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' });
245
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a' }];
246
await testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined);
247
assert.deepStrictEqual(await getUserKeybindings(), expected);
248
});
249
250
async function writeToKeybindingsFile(...keybindings: IUserFriendlyKeybinding[]): Promise<void> {
251
await fileService.writeFile(userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(JSON.stringify(keybindings || [])));
252
}
253
254
async function getUserKeybindings(): Promise<IUserFriendlyKeybinding[]> {
255
return json.parse((await fileService.readFile(userDataProfileService.currentProfile.keybindingsResource)).value.toString());
256
}
257
258
function aResolvedKeybindingItem({ command, when, isDefault, firstChord, secondChord }: { command?: string; when?: string; isDefault?: boolean; firstChord?: { keyCode: KeyCode; modifiers?: Modifiers }; secondChord?: { keyCode: KeyCode; modifiers?: Modifiers } }): ResolvedKeybindingItem {
259
const aSimpleKeybinding = function (chord: { keyCode: KeyCode; modifiers?: Modifiers }): KeyCodeChord {
260
const { ctrlKey, shiftKey, altKey, metaKey } = chord.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false };
261
return new KeyCodeChord(ctrlKey!, shiftKey!, altKey!, metaKey!, chord.keyCode);
262
};
263
const chords: KeyCodeChord[] = [];
264
if (firstChord) {
265
chords.push(aSimpleKeybinding(firstChord));
266
if (secondChord) {
267
chords.push(aSimpleKeybinding(secondChord));
268
}
269
}
270
const keybinding = chords.length > 0 ? new USLayoutResolvedKeybinding(chords, OS) : undefined;
271
return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault, null, false);
272
}
273
});
274
275