@theia/keymaps 1.34.3 → 1.34.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,210 +1,210 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2017 Ericsson and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
18
- import { OpenerService, open, WidgetOpenerOptions, Widget } from '@theia/core/lib/browser';
19
- import { KeybindingRegistry, KeybindingScope, ScopedKeybinding } from '@theia/core/lib/browser/keybinding';
20
- import { Keybinding, RawKeybinding } from '@theia/core/lib/common/keybinding';
21
- import { UserStorageUri } from '@theia/userstorage/lib/browser';
22
- import * as jsoncparser from 'jsonc-parser';
23
- import { Emitter } from '@theia/core/lib/common/event';
24
- import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
25
- import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
26
- import { Deferred } from '@theia/core/lib/common/promise-util';
27
- import URI from '@theia/core/lib/common/uri';
28
- import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
29
- import { MessageService } from '@theia/core/lib/common/message-service';
30
- import { MonacoJSONCEditor } from '@theia/preferences/lib/browser/monaco-jsonc-editor';
31
-
32
- @injectable()
33
- export class KeymapsService {
34
-
35
- @inject(MonacoWorkspace)
36
- protected readonly workspace: MonacoWorkspace;
37
-
38
- @inject(MonacoTextModelService)
39
- protected readonly textModelService: MonacoTextModelService;
40
-
41
- @inject(KeybindingRegistry)
42
- protected readonly keybindingRegistry: KeybindingRegistry;
43
-
44
- @inject(OpenerService)
45
- protected readonly opener: OpenerService;
46
-
47
- @inject(MessageService)
48
- protected readonly messageService: MessageService;
49
-
50
- @inject(MonacoJSONCEditor)
51
- protected readonly jsoncEditor: MonacoJSONCEditor;
52
-
53
- protected readonly changeKeymapEmitter = new Emitter<void>();
54
- readonly onDidChangeKeymaps = this.changeKeymapEmitter.event;
55
-
56
- protected model: MonacoEditorModel | undefined;
57
- protected readonly deferredModel = new Deferred<MonacoEditorModel>();
58
-
59
- /**
60
- * Initialize the keybinding service.
61
- */
62
- @postConstruct()
63
- protected async init(): Promise<void> {
64
- const reference = await this.textModelService.createModelReference(UserStorageUri.resolve('keymaps.json'));
65
- this.model = reference.object;
66
- this.deferredModel.resolve(this.model);
67
-
68
- this.reconcile();
69
- this.model.onDidChangeContent(() => this.reconcile());
70
- this.model.onDirtyChanged(() => this.reconcile());
71
- this.model.onDidChangeValid(() => this.reconcile());
72
- this.keybindingRegistry.onKeybindingsChanged(() => this.changeKeymapEmitter.fire(undefined));
73
- }
74
-
75
- /**
76
- * Reconcile all the keybindings, registering them to the registry.
77
- */
78
- protected reconcile(): void {
79
- const model = this.model;
80
- if (!model || model.dirty) {
81
- return;
82
- }
83
- try {
84
- const keybindings: Keybinding[] = [];
85
- if (model.valid) {
86
- const content = model.getText();
87
- const json = jsoncparser.parse(content, undefined, { disallowComments: false });
88
- if (Array.isArray(json)) {
89
- for (const value of json) {
90
- if (Keybinding.is(value)) {
91
- keybindings.push(value);
92
- } else if (RawKeybinding.is(value)) {
93
- keybindings.push(Keybinding.apiObjectify(value));
94
- }
95
- }
96
- }
97
- }
98
- this.keybindingRegistry.setKeymap(KeybindingScope.USER, keybindings);
99
- } catch (e) {
100
- console.error(`Failed to load keymaps from '${model.uri}'.`, e);
101
- }
102
- }
103
-
104
- /**
105
- * Open the keybindings widget.
106
- * @param ref the optional reference for opening the widget.
107
- */
108
- async open(ref?: Widget): Promise<void> {
109
- const model = await this.deferredModel.promise;
110
- const options: WidgetOpenerOptions = {
111
- widgetOptions: ref ? { area: 'main', mode: 'split-right', ref } : { area: 'main' },
112
- mode: 'activate'
113
- };
114
- if (!model.valid) {
115
- await model.save();
116
- }
117
- await open(this.opener, new URI(model.uri), options);
118
- }
119
-
120
- /**
121
- * Set the keybinding in the JSON.
122
- * @param newKeybinding the new JSON keybinding
123
- * @param oldKeybinding the old JSON keybinding
124
- */
125
- async setKeybinding(newKeybinding: Keybinding, oldKeybinding: ScopedKeybinding | undefined): Promise<void> {
126
- return this.updateKeymap(() => {
127
- let newAdded = false;
128
- let isOldKeybindingDisabled = false;
129
- let addedDisabledEntry = false;
130
- const keybindings = [];
131
- for (let keybinding of this.keybindingRegistry.getKeybindingsByScope(KeybindingScope.USER)) {
132
- // search for the old keybinding and modify it
133
- if (oldKeybinding && Keybinding.equals(keybinding, oldKeybinding, false, true)) {
134
- newAdded = true;
135
- keybinding = {
136
- ...keybinding,
137
- keybinding: newKeybinding.keybinding
138
- };
139
- }
140
-
141
- // we have an disabled entry for the same command and the oldKeybinding
142
- if (oldKeybinding?.keybinding &&
143
- Keybinding.equals(keybinding, { ...newKeybinding, keybinding: oldKeybinding.keybinding, command: '-' + newKeybinding.command }, false, true)) {
144
- isOldKeybindingDisabled = true;
145
- }
146
- keybindings.push(keybinding);
147
- }
148
- if (!newAdded) {
149
- keybindings.push({
150
- command: newKeybinding.command,
151
- keybinding: newKeybinding.keybinding,
152
- context: newKeybinding.context,
153
- when: newKeybinding.when,
154
- args: newKeybinding.args
155
- });
156
- newAdded = true;
157
- }
158
- // we want to add a disabled entry for the old keybinding only when we are modifying the default value
159
- if (!isOldKeybindingDisabled && oldKeybinding?.scope === KeybindingScope.DEFAULT) {
160
- const disabledBinding = {
161
- command: '-' + newKeybinding.command,
162
- // TODO key: oldKeybinding, see https://github.com/eclipse-theia/theia/issues/6879
163
- keybinding: oldKeybinding.keybinding,
164
- context: newKeybinding.context,
165
- when: newKeybinding.when,
166
- args: newKeybinding.args
167
- };
168
- // Add disablement of the old keybinding if it isn't already disabled in the list to avoid duplicate disabled entries
169
- if (!keybindings.some(binding => Keybinding.equals(binding, disabledBinding, false, true))) {
170
- keybindings.push(disabledBinding);
171
- }
172
- isOldKeybindingDisabled = true;
173
- addedDisabledEntry = true;
174
- }
175
- if (newAdded || addedDisabledEntry) {
176
- return keybindings;
177
- }
178
- });
179
- }
180
-
181
- /**
182
- * Remove the given keybinding with the given command id from the JSON.
183
- * @param commandId the keybinding command id.
184
- */
185
- removeKeybinding(commandId: string): Promise<void> {
186
- return this.updateKeymap(() => {
187
- const keybindings = this.keybindingRegistry.getKeybindingsByScope(KeybindingScope.USER);
188
- const removedCommand = '-' + commandId;
189
- const filtered = keybindings.filter(a => a.command !== commandId && a.command !== removedCommand);
190
- if (filtered.length !== keybindings.length) {
191
- return filtered;
192
- }
193
- });
194
- }
195
-
196
- protected async updateKeymap(op: () => Keybinding[] | void): Promise<void> {
197
- const model = await this.deferredModel.promise;
198
- try {
199
- const keybindings = op();
200
- if (keybindings && this.model) {
201
- await this.jsoncEditor.setValue(this.model, [], keybindings.map(binding => Keybinding.apiObjectify(binding)));
202
- }
203
- } catch (e) {
204
- const message = `Failed to update a keymap in '${model.uri}'.`;
205
- this.messageService.error(`${message} Please check if it is corrupted.`);
206
- console.error(`${message}`, e);
207
- }
208
- }
209
-
210
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2017 Ericsson and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
18
+ import { OpenerService, open, WidgetOpenerOptions, Widget } from '@theia/core/lib/browser';
19
+ import { KeybindingRegistry, KeybindingScope, ScopedKeybinding } from '@theia/core/lib/browser/keybinding';
20
+ import { Keybinding, RawKeybinding } from '@theia/core/lib/common/keybinding';
21
+ import { UserStorageUri } from '@theia/userstorage/lib/browser';
22
+ import * as jsoncparser from 'jsonc-parser';
23
+ import { Emitter } from '@theia/core/lib/common/event';
24
+ import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
25
+ import { MonacoEditorModel } from '@theia/monaco/lib/browser/monaco-editor-model';
26
+ import { Deferred } from '@theia/core/lib/common/promise-util';
27
+ import URI from '@theia/core/lib/common/uri';
28
+ import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
29
+ import { MessageService } from '@theia/core/lib/common/message-service';
30
+ import { MonacoJSONCEditor } from '@theia/preferences/lib/browser/monaco-jsonc-editor';
31
+
32
+ @injectable()
33
+ export class KeymapsService {
34
+
35
+ @inject(MonacoWorkspace)
36
+ protected readonly workspace: MonacoWorkspace;
37
+
38
+ @inject(MonacoTextModelService)
39
+ protected readonly textModelService: MonacoTextModelService;
40
+
41
+ @inject(KeybindingRegistry)
42
+ protected readonly keybindingRegistry: KeybindingRegistry;
43
+
44
+ @inject(OpenerService)
45
+ protected readonly opener: OpenerService;
46
+
47
+ @inject(MessageService)
48
+ protected readonly messageService: MessageService;
49
+
50
+ @inject(MonacoJSONCEditor)
51
+ protected readonly jsoncEditor: MonacoJSONCEditor;
52
+
53
+ protected readonly changeKeymapEmitter = new Emitter<void>();
54
+ readonly onDidChangeKeymaps = this.changeKeymapEmitter.event;
55
+
56
+ protected model: MonacoEditorModel | undefined;
57
+ protected readonly deferredModel = new Deferred<MonacoEditorModel>();
58
+
59
+ /**
60
+ * Initialize the keybinding service.
61
+ */
62
+ @postConstruct()
63
+ protected async init(): Promise<void> {
64
+ const reference = await this.textModelService.createModelReference(UserStorageUri.resolve('keymaps.json'));
65
+ this.model = reference.object;
66
+ this.deferredModel.resolve(this.model);
67
+
68
+ this.reconcile();
69
+ this.model.onDidChangeContent(() => this.reconcile());
70
+ this.model.onDirtyChanged(() => this.reconcile());
71
+ this.model.onDidChangeValid(() => this.reconcile());
72
+ this.keybindingRegistry.onKeybindingsChanged(() => this.changeKeymapEmitter.fire(undefined));
73
+ }
74
+
75
+ /**
76
+ * Reconcile all the keybindings, registering them to the registry.
77
+ */
78
+ protected reconcile(): void {
79
+ const model = this.model;
80
+ if (!model || model.dirty) {
81
+ return;
82
+ }
83
+ try {
84
+ const keybindings: Keybinding[] = [];
85
+ if (model.valid) {
86
+ const content = model.getText();
87
+ const json = jsoncparser.parse(content, undefined, { disallowComments: false });
88
+ if (Array.isArray(json)) {
89
+ for (const value of json) {
90
+ if (Keybinding.is(value)) {
91
+ keybindings.push(value);
92
+ } else if (RawKeybinding.is(value)) {
93
+ keybindings.push(Keybinding.apiObjectify(value));
94
+ }
95
+ }
96
+ }
97
+ }
98
+ this.keybindingRegistry.setKeymap(KeybindingScope.USER, keybindings);
99
+ } catch (e) {
100
+ console.error(`Failed to load keymaps from '${model.uri}'.`, e);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Open the keybindings widget.
106
+ * @param ref the optional reference for opening the widget.
107
+ */
108
+ async open(ref?: Widget): Promise<void> {
109
+ const model = await this.deferredModel.promise;
110
+ const options: WidgetOpenerOptions = {
111
+ widgetOptions: ref ? { area: 'main', mode: 'split-right', ref } : { area: 'main' },
112
+ mode: 'activate'
113
+ };
114
+ if (!model.valid) {
115
+ await model.save();
116
+ }
117
+ await open(this.opener, new URI(model.uri), options);
118
+ }
119
+
120
+ /**
121
+ * Set the keybinding in the JSON.
122
+ * @param newKeybinding the new JSON keybinding
123
+ * @param oldKeybinding the old JSON keybinding
124
+ */
125
+ async setKeybinding(newKeybinding: Keybinding, oldKeybinding: ScopedKeybinding | undefined): Promise<void> {
126
+ return this.updateKeymap(() => {
127
+ let newAdded = false;
128
+ let isOldKeybindingDisabled = false;
129
+ let addedDisabledEntry = false;
130
+ const keybindings = [];
131
+ for (let keybinding of this.keybindingRegistry.getKeybindingsByScope(KeybindingScope.USER)) {
132
+ // search for the old keybinding and modify it
133
+ if (oldKeybinding && Keybinding.equals(keybinding, oldKeybinding, false, true)) {
134
+ newAdded = true;
135
+ keybinding = {
136
+ ...keybinding,
137
+ keybinding: newKeybinding.keybinding
138
+ };
139
+ }
140
+
141
+ // we have an disabled entry for the same command and the oldKeybinding
142
+ if (oldKeybinding?.keybinding &&
143
+ Keybinding.equals(keybinding, { ...newKeybinding, keybinding: oldKeybinding.keybinding, command: '-' + newKeybinding.command }, false, true)) {
144
+ isOldKeybindingDisabled = true;
145
+ }
146
+ keybindings.push(keybinding);
147
+ }
148
+ if (!newAdded) {
149
+ keybindings.push({
150
+ command: newKeybinding.command,
151
+ keybinding: newKeybinding.keybinding,
152
+ context: newKeybinding.context,
153
+ when: newKeybinding.when,
154
+ args: newKeybinding.args
155
+ });
156
+ newAdded = true;
157
+ }
158
+ // we want to add a disabled entry for the old keybinding only when we are modifying the default value
159
+ if (!isOldKeybindingDisabled && oldKeybinding?.scope === KeybindingScope.DEFAULT) {
160
+ const disabledBinding = {
161
+ command: '-' + newKeybinding.command,
162
+ // TODO key: oldKeybinding, see https://github.com/eclipse-theia/theia/issues/6879
163
+ keybinding: oldKeybinding.keybinding,
164
+ context: newKeybinding.context,
165
+ when: newKeybinding.when,
166
+ args: newKeybinding.args
167
+ };
168
+ // Add disablement of the old keybinding if it isn't already disabled in the list to avoid duplicate disabled entries
169
+ if (!keybindings.some(binding => Keybinding.equals(binding, disabledBinding, false, true))) {
170
+ keybindings.push(disabledBinding);
171
+ }
172
+ isOldKeybindingDisabled = true;
173
+ addedDisabledEntry = true;
174
+ }
175
+ if (newAdded || addedDisabledEntry) {
176
+ return keybindings;
177
+ }
178
+ });
179
+ }
180
+
181
+ /**
182
+ * Remove the given keybinding with the given command id from the JSON.
183
+ * @param commandId the keybinding command id.
184
+ */
185
+ removeKeybinding(commandId: string): Promise<void> {
186
+ return this.updateKeymap(() => {
187
+ const keybindings = this.keybindingRegistry.getKeybindingsByScope(KeybindingScope.USER);
188
+ const removedCommand = '-' + commandId;
189
+ const filtered = keybindings.filter(a => a.command !== commandId && a.command !== removedCommand);
190
+ if (filtered.length !== keybindings.length) {
191
+ return filtered;
192
+ }
193
+ });
194
+ }
195
+
196
+ protected async updateKeymap(op: () => Keybinding[] | void): Promise<void> {
197
+ const model = await this.deferredModel.promise;
198
+ try {
199
+ const keybindings = op();
200
+ if (keybindings && this.model) {
201
+ await this.jsoncEditor.setValue(this.model, [], keybindings.map(binding => Keybinding.apiObjectify(binding)));
202
+ }
203
+ } catch (e) {
204
+ const message = `Failed to update a keymap in '${model.uri}'.`;
205
+ this.messageService.error(`${message} Please check if it is corrupted.`);
206
+ console.error(`${message}`, e);
207
+ }
208
+ }
209
+
210
+ }