diff --git a/packages/core/src/browser/keybinding.ts b/packages/core/src/browser/keybinding.ts
index c9f820f78d5e8..abba68e69e811 100644
--- a/packages/core/src/browser/keybinding.ts
+++ b/packages/core/src/browser/keybinding.ts
@@ -38,35 +38,11 @@ export namespace KeybindingScope {
export const length = KeybindingScope.END - KeybindingScope.DEFAULT;
}
-export namespace Keybinding {
-
- /**
- * Returns with the string representation of the binding.
- * Any additional properties which are not described on
- * the `Keybinding` API will be ignored.
- *
- * @param binding the binding to stringify.
- */
- export function stringify(binding: Keybinding): string {
- const copy: Keybinding = {
- command: binding.command,
- keybinding: binding.keybinding,
- context: binding.context
- };
- return JSON.stringify(copy);
- }
-
- /* Determine whether object is a KeyBinding */
- // tslint:disable-next-line:no-any
- export function is(arg: Keybinding | any): arg is Keybinding {
- return !!arg && arg === Object(arg) && 'command' in arg && 'keybinding' in arg;
- }
-}
-
/**
* @deprecated import from `@theia/core/lib/common/keybinding` instead
*/
export type Keybinding = common.Keybinding;
+export const Keybinding = common.Keybinding;
export interface ResolvedKeybinding extends Keybinding {
/**
diff --git a/packages/core/src/common/keybinding.ts b/packages/core/src/common/keybinding.ts
index cdffc95b36ae7..422d9cf633160 100644
--- a/packages/core/src/common/keybinding.ts
+++ b/packages/core/src/common/keybinding.ts
@@ -36,3 +36,29 @@ export interface Keybinding {
// tslint:disable-next-line no-any
args?: any;
}
+export namespace Keybinding {
+
+ /**
+ * Returns with the string representation of the binding.
+ * Any additional properties which are not described on
+ * the `Keybinding` API will be ignored.
+ *
+ * @param binding the binding to stringify.
+ */
+ export function stringify(binding: Keybinding): string {
+ const copy: Keybinding = {
+ command: binding.command,
+ keybinding: binding.keybinding,
+ context: binding.context,
+ when: binding.when,
+ args: binding.args
+ };
+ return JSON.stringify(copy);
+ }
+
+ /* Determine whether object is a KeyBinding */
+ // tslint:disable-next-line:no-any
+ export function is(arg: Keybinding | any): arg is Keybinding {
+ return !!arg && arg === Object(arg) && 'command' in arg && 'keybinding' in arg;
+ }
+}
diff --git a/packages/keymaps/src/browser/keybindings-widget.tsx b/packages/keymaps/src/browser/keybindings-widget.tsx
index 986d4f947b2c9..1aa86d8a17c66 100644
--- a/packages/keymaps/src/browser/keybindings-widget.tsx
+++ b/packages/keymaps/src/browser/keybindings-widget.tsx
@@ -18,37 +18,27 @@ import React = require('react');
import debounce = require('lodash.debounce');
import * as fuzzy from 'fuzzy';
import { injectable, inject, postConstruct } from 'inversify';
-import { CommandRegistry, Emitter, Event } from '@theia/core/lib/common';
+import { Emitter, Event } from '@theia/core/lib/common/event';
+import { CommandRegistry, Command } from '@theia/core/lib/common/command';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
-import { KeybindingRegistry, SingleTextInputDialog, KeySequence, ConfirmDialog, Message, KeybindingScope, SingleTextInputDialogProps, Key } from '@theia/core/lib/browser';
-import { KeymapsParser } from './keymaps-parser';
-import { KeymapsService, KeybindingJson } from './keymaps-service';
+import { KeybindingRegistry, SingleTextInputDialog, KeySequence, ConfirmDialog, Message, KeybindingScope, SingleTextInputDialogProps, Key, ScopedKeybinding } from '@theia/core/lib/browser';
+import { KeymapsService } from './keymaps-service';
import { AlertMessage } from '@theia/core/lib/browser/widgets/alert-message';
/**
* Representation of a keybinding item for the view.
*/
export interface KeybindingItem {
- /**
- * The id of the command.
- */
- id: string,
- /**
- * The human-readable label of the command.
- */
- command: string,
- /**
- * The keybinding of the command.
- */
- keybinding?: string,
- /**
- * The context / when closure of the command.
- */
- context?: string,
- /**
- * The source of the command.
- */
- source?: string,
+ command: Command
+ keybinding?: ScopedKeybinding
+ /** human-readable labels can contain highlighting */
+ labels: {
+ id: string
+ command: string
+ keybinding: string
+ context: string
+ source: string
+ }
}
/**
@@ -74,9 +64,6 @@ export class KeybindingWidget extends ReactWidget {
@inject(KeybindingRegistry)
protected readonly keybindingRegistry: KeybindingRegistry;
- @inject(KeymapsParser)
- protected readonly keymapsParser: KeymapsParser;
-
@inject(KeymapsService)
protected readonly keymapsService: KeymapsService;
@@ -178,14 +165,14 @@ export class KeybindingWidget extends ReactWidget {
this.query = searchField ? searchField.value.trim().toLocaleLowerCase() : '';
const items = this.getItems();
items.forEach(item => {
- const keys: (keyof KeybindingItem)[] = ['command', 'keybinding', 'context', 'source'];
+ const keys: (keyof KeybindingItem['labels'])[] = ['command', 'keybinding', 'context', 'source'];
let matched = false;
for (const key of keys) {
- const string = item[key];
+ const string = item.labels[key];
if (string) {
const fuzzyMatch = fuzzy.match(this.query, string, this.fuzzyOptions);
if (fuzzyMatch) {
- item[key] = fuzzyMatch.rendered;
+ item.labels[key] = fuzzyMatch.rendered;
matched = true;
} else {
// Match identical keybindings that have different orders.
@@ -250,10 +237,10 @@ export class KeybindingWidget extends ReactWidget {
chordRenderedResult.concat('+' + resultKey);
}
});
- item[key] = chordRenderedResult;
+ item.labels[key] = chordRenderedResult;
}
- item[key] = renderedResult.join('+');
+ item.labels[key] = renderedResult.join('+');
matched = true;
}
}
@@ -354,30 +341,32 @@ export class KeybindingWidget extends ReactWidget {
*/
protected renderRows(): React.ReactNode {
return
- {
- this.items.map((item, index) =>
- this.editKeybinding(item)}>
-
- {this.renderActions(item)}
- |
-
- {this.renderMatchedData(item.command)}
- |
-
- {item.keybinding ? this.renderKeybinding(item.keybinding) : ''}
- |
-
- {(item.context) ? this.renderMatchedData(item.context) : ''}
- |
-
- {item.source ? this.renderMatchedData(item.source) : ''}
- |
-
- )
- }
+ {this.items.map((item, index) => this.renderRow(item, index))}
;
}
+ protected renderRow(item: KeybindingItem, index: number): React.ReactNode {
+ const { command, keybinding } = item;
+ // TODO get rid of array functions in event handlers
+ return
this.editKeybinding(item)}>
+
+ {this.renderActions(item)}
+ |
+
+ {this.renderMatchedData(item.labels.command)}
+ |
+
+ {this.renderKeybinding(item.labels.keybinding)}
+ |
+
+ {this.renderMatchedData(item.labels.context)}
+ |
+
+ {this.renderMatchedData(item.labels.source)}
+ |
+
;
+ }
+
/**
* Render the actions container with action icons.
* @param item the keybinding item for the row.
@@ -400,7 +389,7 @@ export class KeybindingWidget extends ReactWidget {
* @param item the keybinding item for the row.
*/
protected renderReset(item: KeybindingItem): React.ReactNode {
- return (item.source && this.getRawValue(item.source) === KeybindingScope[1].toLocaleLowerCase())
+ return (item.keybinding && item.keybinding.scope === KeybindingScope.USER)
? this.resetKeybinding(item)}> : '';
}
@@ -409,6 +398,9 @@ export class KeybindingWidget extends ReactWidget {
* @param keybinding the keybinding value.
*/
protected renderKeybinding(keybinding: string): React.ReactNode {
+ if (!keybinding.length) {
+ return undefined;
+ }
const regex = new RegExp(this.keybindingSeparator);
keybinding = keybinding.replace(regex, '+');
const keys = keybinding.split('+');
@@ -455,37 +447,58 @@ export class KeybindingWidget extends ReactWidget {
const items: KeybindingItem[] = [];
// Build the keybinding items.
for (let i = 0; i < commands.length; i++) {
+ const command = commands[i];
// Skip internal commands prefixed by `_`.
- if (commands[i].id.startsWith('_')) {
+ if (command.id.startsWith('_')) {
continue;
}
- // Obtain the keybinding for the given command.
- const keybindings = this.keybindingRegistry.getKeybindingsForCommand(commands[i].id);
- const item: KeybindingItem = {
- id: commands[i].id,
- // Get the command label if available, else use the keybinding id.
- command: commands[i].label || commands[i].id,
- keybinding: (keybindings && keybindings[0]) ? keybindings[0].keybinding : '',
- context: (keybindings && keybindings[0])
- ? keybindings[0].context
- ? keybindings[0].context : keybindings[0].when
- : '',
- source: (keybindings && keybindings[0] && typeof keybindings[0].scope !== 'undefined')
- ? KeybindingScope[keybindings[0].scope!].toLocaleLowerCase() : '',
- };
- items.push(item);
+ const keybinding = this.keybindingRegistry.getKeybindingsForCommand(command.id)[0];
+ items.push({
+ command,
+ keybinding,
+ labels: {
+ id: command.id,
+ command: this.getCommandLabel(command),
+ keybinding: this.getKeybindingLabel(keybinding) || '',
+ context: this.getContextLabel(keybinding) || '',
+ source: this.getScopeLabel(keybinding) || ''
+ }
+ });
}
// Sort the keybinding item by label.
- const sorted: KeybindingItem[] = items.sort((a: KeybindingItem, b: KeybindingItem) => this.compareItem(a.command, b.command));
+ const sorted: KeybindingItem[] = items.sort((a, b) => this.compareItem(a.labels.id, b.labels.id));
// Get the list of keybinding item with keybindings (visually put them at the top of the table).
- const keyItems: KeybindingItem[] = sorted.filter((a: KeybindingItem) => !!a.keybinding);
+ const keyItems: KeybindingItem[] = sorted.filter(a => !!a.labels.keybinding);
// Get the remaining keybinding items (without keybindings).
- const otherItems: KeybindingItem[] = sorted.filter((a: KeybindingItem) => !a.keybinding);
+ const otherItems: KeybindingItem[] = sorted.filter(a => !a.labels.keybinding);
// Return the list of keybinding items prioritizing those with a defined keybinding.
return [...keyItems, ...otherItems];
}
+ protected getCommandLabel(command: Command): string {
+ return command.label || command.id;
+ }
+
+ protected getKeybindingLabel(keybinding: ScopedKeybinding | undefined): string | undefined {
+ return keybinding && keybinding.keybinding;
+ }
+
+ protected getContextLabel(keybinding: ScopedKeybinding | undefined): string | undefined {
+ return keybinding ? keybinding.context || keybinding.when : undefined;
+ }
+
+ protected getScopeLabel(keybinding: ScopedKeybinding | undefined): string | undefined {
+ let scope = keybinding && keybinding.scope;
+ if (scope !== undefined) {
+ if (scope < KeybindingScope.USER) {
+ scope = KeybindingScope.DEFAULT;
+ }
+ return KeybindingScope[scope].toLocaleLowerCase();
+ }
+ return undefined;
+ }
+
/**
* Compare two strings.
* - Strings are first normalized before comparison (`toLowerCase`).
@@ -504,37 +517,25 @@ export class KeybindingWidget extends ReactWidget {
return 0;
}
- /**
- * Determine if the keybinding currently exists in a user's `keymaps.json`.
- *
- * @returns `true` if the keybinding exists.
- */
- protected keybindingExistsInJson(keybindings: KeybindingJson[], command: string): boolean {
- for (let i = 0; i < keybindings.length; i++) {
- if (keybindings[i].command === command) {
- return true;
- }
- }
- return false;
- }
-
/**
* Prompt users to update the keybinding for the given command.
* @param item the keybinding item.
*/
protected editKeybinding(item: KeybindingItem): void {
- const command = this.getRawValue(item.command);
- const id = this.getRawValue(item.id);
- const keybinding = (item.keybinding) ? this.getRawValue(item.keybinding) : '';
- const context = (item.context) ? this.getRawValue(item.context) : '';
+ const command = item.command.id;
+ const oldKeybinding = item.keybinding && item.keybinding.keybinding;
const dialog = new EditKeybindingDialog({
title: `Edit Keybinding For ${command}`,
- initialValue: keybinding,
- validate: newKeybinding => this.validateKeybinding(command, keybinding, newKeybinding),
+ initialValue: oldKeybinding,
+ validate: newKeybinding => this.validateKeybinding(command, oldKeybinding, newKeybinding),
}, this.keymapsService, item);
- dialog.open().then(async newKeybinding => {
- if (newKeybinding) {
- await this.keymapsService.setKeybinding({ 'command': id, 'keybinding': newKeybinding, 'context': context });
+ dialog.open().then(async keybinding => {
+ if (keybinding) {
+ await this.keymapsService.setKeybinding({
+ ...item.keybinding,
+ command,
+ keybinding
+ }, oldKeybinding);
}
});
}
@@ -545,9 +546,9 @@ export class KeybindingWidget extends ReactWidget {
*
* @returns a Promise which resolves to `true` if a user accepts resetting.
*/
- protected async confirmResetKeybinding(command: string): Promise {
+ protected async confirmResetKeybinding(item: KeybindingItem): Promise {
const dialog = new ConfirmDialog({
- title: `Reset keybinding for '${command}'`,
+ title: `Reset keybinding for '${this.getCommandLabel(item.command)}'`,
msg: 'Do you really want to reset this keybinding to its default value?'
});
return !!await dialog.open();
@@ -558,11 +559,9 @@ export class KeybindingWidget extends ReactWidget {
* @param item the keybinding item.
*/
protected async resetKeybinding(item: KeybindingItem): Promise {
- const rawCommandId = this.getRawValue(item.id);
- const rawCommand = this.getRawValue(item.command);
- const confirmed = await this.confirmResetKeybinding(rawCommand);
+ const confirmed = await this.confirmResetKeybinding(item);
if (confirmed) {
- this.keymapsService.removeKeybinding(rawCommandId);
+ this.keymapsService.removeKeybinding(item.command.id);
}
}
@@ -574,12 +573,12 @@ export class KeybindingWidget extends ReactWidget {
*
* @returns the end user message to display.
*/
- protected validateKeybinding(command: string, oldKeybinding: string, keybinding: string): string {
+ protected validateKeybinding(command: string, oldKeybinding: string | undefined, keybinding: string): string {
if (!keybinding) {
return 'keybinding value is required';
}
try {
- const binding = { 'command': command, 'keybinding': keybinding };
+ const binding = { command, keybinding };
KeySequence.parse(keybinding);
if (oldKeybinding === keybinding) {
return ' '; // if old and new keybindings match, quietly reject update
@@ -649,14 +648,6 @@ export class KeybindingWidget extends ReactWidget {
}
}
- /**
- * Render the raw value of a item without fuzzy highlighting.
- * @param property one of the `KeybindingItem` properties.
- */
- protected getRawValue(property: string): string {
- return property.replace(new RegExp(this.regexp), '$1');
- }
-
}
/**
* Dialog used to edit keybindings, and reset custom keybindings.
@@ -682,8 +673,7 @@ class EditKeybindingDialog extends SingleTextInputDialog {
super(props);
this.item = item;
// Add the `Reset` button if the command currently has a custom keybinding.
- if (this.item.source &&
- this.getRaw(this.item.source) === KeybindingScope[1].toLocaleLowerCase()) {
+ if (this.item.keybinding && this.item.keybinding.scope === KeybindingScope.USER) {
this.appendResetButton();
}
}
@@ -726,20 +716,7 @@ class EditKeybindingDialog extends SingleTextInputDialog {
* Perform keybinding reset.
*/
protected reset(): void {
- // Extract the raw id from the keybinding item (without fuzzy matching).
- const id = this.getRaw(this.item.id);
- // Remove the custom keybinding, resetting it to its default value.
- this.keymapsService.removeKeybinding(id);
- }
-
- /**
- * Extract the raw value from a string (without fuzzy matching).
- * @param a given string value for extraction.
- *
- * @returns the raw value of a string without any fuzzy matching.
- */
- protected getRaw(a: string): string {
- return a.replace(new RegExp(/(.*?)<\/match>/g), '$1');
+ this.keymapsService.removeKeybinding(this.item.command.id);
}
}
diff --git a/packages/keymaps/src/browser/keymaps-frontend-module.ts b/packages/keymaps/src/browser/keymaps-frontend-module.ts
index f81e86a282013..7b1728738528a 100644
--- a/packages/keymaps/src/browser/keymaps-frontend-module.ts
+++ b/packages/keymaps/src/browser/keymaps-frontend-module.ts
@@ -19,7 +19,6 @@ import { KeymapsService } from './keymaps-service';
import { KeymapsFrontendContribution } from './keymaps-frontend-contribution';
import { CommandContribution, MenuContribution } from '@theia/core/lib/common';
import { KeybindingContribution } from '@theia/core/lib/browser/keybinding';
-import { KeymapsParser } from './keymaps-parser';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import './keymaps-monaco-contribution';
@@ -29,7 +28,6 @@ import { KeybindingWidget } from './keybindings-widget';
import '../../src/browser/style/index.css';
export default new ContainerModule(bind => {
- bind(KeymapsParser).toSelf().inSingletonScope();
bind(KeymapsService).toSelf().inSingletonScope();
bind(KeymapsFrontendContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(KeymapsFrontendContribution);
diff --git a/packages/keymaps/src/browser/keymaps-parser.spec.ts b/packages/keymaps/src/browser/keymaps-parser.spec.ts
deleted file mode 100644
index 8be9ed5ccfc05..0000000000000
--- a/packages/keymaps/src/browser/keymaps-parser.spec.ts
+++ /dev/null
@@ -1,120 +0,0 @@
-/********************************************************************************
- * Copyright (C) 2018 TypeFox and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the Eclipse
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
- * with the GNU Classpath Exception which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- ********************************************************************************/
-
-import * as assert from 'assert';
-import { KeymapsParser } from './keymaps-parser';
-
-describe('keymaps-parser', () => {
-
- const parser = new KeymapsParser();
-
- it('well formatted raw text', () => {
- assertParsing(`{
- "keybindings": [
- {
- "keybinding": "ctrl+p",
- "command": "command1"
- },
- {
- "keybinding": "ctrl+shift+p",
- "command": "command2"
- }
- ],
- "errors": []
-}`, `[
- {
- "keybinding": "ctrl+p",
- "command": "command1"
- },
- {
- "keybinding": "ctrl+shift+p",
- "command": "command2"
- }
-]`);
- });
-
- it('no array', () => {
- assertParsing(`{
- "keybindings": [],
- "errors": [
- "should be array at "
- ]
-}`, `{
- "keybinding": "ctrl+p",
- "command": "command"
-}`);
- });
-
- it('additional property', () => {
- assertParsing(`{
- "keybindings": [],
- "errors": [
- "should NOT have additional properties at /0"
- ]
-}`, `[
- {
- "keybinding": "ctrl+p",
- "command": "command",
- "extra": 0
- }
-]`);
- });
-
- it('wrong type', () => {
- assertParsing(`{
- "keybindings": [],
- "errors": [
- "should be string at /0/keybinding"
- ]
-}`, `[
- {
- "keybinding": 0,
- "command": "command1"
- },
- {
- "keybinding": "ctrl+shift+p",
- "command": 0
- }
-]`);
- });
-
- it('missing property', () => {
- assertParsing(`{
- "keybindings": [],
- "errors": [
- "PropertyNameExpected at 44 offset of 1 length",
- "ValueExpected at 44 offset of 1 length",
- "should have required property 'command' at /0"
- ]
-}`, `[
- {
- "keybinding": "ctrl+p",
- }
-]`);
- });
-
- /**
- * Assert that the content equals the expected content.
- * @param expectation the expected string.
- * @param content the content to verify.
- */
- function assertParsing(expectation: string, content: string): void {
- const errors: string[] = [];
- const keybindings = parser.parse(content, errors);
- assert.deepEqual(expectation, JSON.stringify({ keybindings, errors }, undefined, 2));
- }
-
-});
diff --git a/packages/keymaps/src/browser/keymaps-parser.ts b/packages/keymaps/src/browser/keymaps-parser.ts
deleted file mode 100644
index b0548bb5c74a2..0000000000000
--- a/packages/keymaps/src/browser/keymaps-parser.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-/********************************************************************************
- * Copyright (C) 2018 TypeFox and others.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v. 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0.
- *
- * This Source Code may also be made available under the following Secondary
- * Licenses when the conditions for such availability set forth in the Eclipse
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
- * with the GNU Classpath Exception which is available at
- * https://www.gnu.org/software/classpath/license.html.
- *
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
- ********************************************************************************/
-
-import * as Ajv from 'ajv';
-import * as parser from 'jsonc-parser';
-import { injectable } from 'inversify';
-import { Keybinding } from '@theia/core/lib/browser';
-
-export const keymapsSchema = {
- type: 'array',
- items: {
- type: 'object',
- properties: {
- keybinding: {
- type: 'string'
- },
- command: {
- type: 'string'
- },
- context: {
- type: 'string'
- },
- when: {
- type: 'string'
- },
- args: {}
- },
- required: ['command', 'keybinding'],
- optional: ['context', 'when', 'args'],
- additionalProperties: false
- }
-};
-
-@injectable()
-export class KeymapsParser {
-
- protected readonly validate: Ajv.ValidateFunction;
-
- constructor() {
- // https://github.com/epoberezkin/ajv#options
- this.validate = new Ajv({
- jsonPointers: true
- }).compile(keymapsSchema);
- }
-
- /**
- * Parse the keybindings for potential errors.
- * @param content the content.
- * @param errors the optional list of parsing errors.
- */
- parse(content: string, errors?: string[]): Keybinding[] {
- const strippedContent = parser.stripComments(content);
- const parsingErrors: parser.ParseError[] | undefined = errors ? [] : undefined;
- const bindings = parser.parse(strippedContent, parsingErrors);
- if (parsingErrors && errors) {
- for (const error of parsingErrors) {
- errors.push(`${this.printParseErrorCode(error.error)} at ${error.offset} offset of ${error.length} length`);
- }
- }
- if (this.validate(bindings)) {
- return bindings;
- }
- if (errors && this.validate.errors) {
- for (const error of this.validate.errors) {
- errors.push(`${error.message} at ${error.dataPath}`);
- }
- }
- return [];
- }
-
- /**
- * Print the parsed error code.
- * @param code the error code if available.
- */
- // https://github.com/Microsoft/node-jsonc-parser/issues/13
- // tslint:disable-next-line:typedef
- protected printParseErrorCode(code: number | undefined) {
- switch (code) {
- case parser.ParseErrorCode.InvalidSymbol: return 'InvalidSymbol';
- case parser.ParseErrorCode.InvalidNumberFormat: return 'InvalidNumberFormat';
- case parser.ParseErrorCode.PropertyNameExpected: return 'PropertyNameExpected';
- case parser.ParseErrorCode.ValueExpected: return 'ValueExpected';
- case parser.ParseErrorCode.ColonExpected: return 'ColonExpected';
- case parser.ParseErrorCode.CommaExpected: return 'CommaExpected';
- case parser.ParseErrorCode.CloseBraceExpected: return 'CloseBraceExpected';
- case parser.ParseErrorCode.CloseBracketExpected: return 'CloseBracketExpected';
- case parser.ParseErrorCode.EndOfFileExpected: return 'EndOfFileExpected';
- case parser.ParseErrorCode.InvalidCommentToken: return 'InvalidCommentToken';
- case parser.ParseErrorCode.UnexpectedEndOfComment: return 'UnexpectedEndOfComment';
- case parser.ParseErrorCode.UnexpectedEndOfString: return 'UnexpectedEndOfString';
- case parser.ParseErrorCode.UnexpectedEndOfNumber: return 'UnexpectedEndOfNumber';
- case parser.ParseErrorCode.InvalidUnicode: return 'InvalidUnicode';
- case parser.ParseErrorCode.InvalidEscapeCharacter: return 'InvalidEscapeCharacter';
- case parser.ParseErrorCode.InvalidCharacter: return 'InvalidCharacter';
- }
- return '';
- }
-
-}
diff --git a/packages/keymaps/src/browser/keymaps-service.ts b/packages/keymaps/src/browser/keymaps-service.ts
index 27b4ad362cb29..a99b974778e55 100644
--- a/packages/keymaps/src/browser/keymaps-service.ts
+++ b/packages/keymaps/src/browser/keymaps-service.ts
@@ -16,30 +16,13 @@
import { inject, injectable, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
-import { ResourceProvider, Resource } from '@theia/core/lib/common';
-import { Keybinding, KeybindingRegistry, KeybindingScope, OpenerService, open, WidgetOpenerOptions, Widget } from '@theia/core/lib/browser';
+import { ResourceProvider, Resource } from '@theia/core/lib/common/resource';
+import { OpenerService, open, WidgetOpenerOptions, Widget } from '@theia/core/lib/browser';
+import { KeybindingRegistry, KeybindingScope } from '@theia/core/lib/browser/keybinding';
+import { Keybinding } from '@theia/core/lib/common/keybinding';
import { UserStorageUri } from '@theia/userstorage/lib/browser';
-import { KeymapsParser } from './keymaps-parser';
import * as jsoncparser from 'jsonc-parser';
-import { Emitter } from '@theia/core/lib/common/';
-
-/**
- * Representation of a JSON keybinding.
- */
-export interface KeybindingJson {
- /**
- * The keybinding command.
- */
- command: string,
- /**
- * The actual keybinding.
- */
- keybinding: string,
- /**
- * The keybinding context.
- */
- context: string,
-}
+import { Emitter } from '@theia/core/lib/common/event';
@injectable()
export class KeymapsService {
@@ -53,11 +36,8 @@ export class KeymapsService {
@inject(OpenerService)
protected readonly opener: OpenerService;
- @inject(KeymapsParser)
- protected readonly parser: KeymapsParser;
-
protected readonly changeKeymapEmitter = new Emitter();
- onDidChangeKeymaps = this.changeKeymapEmitter.event;
+ readonly onDidChangeKeymaps = this.changeKeymapEmitter.event;
protected resource: Resource;
@@ -87,12 +67,17 @@ export class KeymapsService {
* Parsed the read keybindings.
*/
protected async parseKeybindings(): Promise {
- try {
- const content = await this.resource.readContents();
- return this.parser.parse(content);
- } catch (error) {
- return error;
+ const content = await this.resource.readContents();
+ const keybindings: Keybinding[] = [];
+ const json = jsoncparser.parse(content, undefined, { disallowComments: false });
+ if (Array.isArray(json)) {
+ for (const value of json) {
+ if (Keybinding.is(value)) {
+ keybindings.push(value);
+ }
+ }
}
+ return keybindings;
}
/**
@@ -109,25 +94,52 @@ export class KeymapsService {
/**
* Set the keybinding in the JSON.
- * @param keybindingJson the JSON keybindings.
+ * @param newKeybinding the JSON keybindings.
*/
- async setKeybinding(keybindingJson: KeybindingJson): Promise {
+ async setKeybinding(newKeybinding: Keybinding, oldKeybinding: string | undefined): Promise {
if (!this.resource.saveContents) {
return;
}
- const content = await this.resource.readContents();
- const keybindings: KeybindingJson[] = content ? jsoncparser.parse(content) : [];
- let updated = false;
- for (let i = 0; i < keybindings.length; i++) {
- if (keybindings[i].command === keybindingJson.command) {
- updated = true;
- keybindings[i].keybinding = keybindingJson.keybinding;
+ const keybindings = await this.parseKeybindings();
+ let newAdded = false;
+ let oldRemoved = false;
+ for (const keybinding of keybindings) {
+ if (keybinding.command === newKeybinding.command &&
+ (keybinding.context || '') === (newKeybinding.context || '') &&
+ (keybinding.when || '') === (newKeybinding.when || '')) {
+ newAdded = true;
+ keybinding.keybinding = newKeybinding.keybinding;
+ }
+ if (oldKeybinding && keybinding.keybinding === oldKeybinding &&
+ keybinding.command === '-' + newKeybinding.command &&
+ (keybinding.context || '') === (newKeybinding.context || '') &&
+ (keybinding.when || '') === (newKeybinding.when || '')) {
+ oldRemoved = true;
}
}
- if (!updated) {
- const item: KeybindingJson = { ...keybindingJson };
- keybindings.push(item);
+ if (!newAdded) {
+ keybindings.push({
+ command: newKeybinding.command,
+ keybinding: newKeybinding.keybinding,
+ context: newKeybinding.context,
+ when: newKeybinding.when,
+ args: newKeybinding.args
+ });
+ }
+ if (!oldRemoved && oldKeybinding) {
+ keybindings.push({
+ command: '-' + newKeybinding.command,
+ // TODO key: oldKeybinding, see https://github.com/eclipse-theia/theia/issues/6879
+ keybinding: oldKeybinding,
+ context: newKeybinding.context,
+ when: newKeybinding.when,
+ args: newKeybinding.args
+ });
}
+ // TODO use preference values to get proper json settings
+ // TODO handle dirty models properly
+ // TODO handle race conditions properly
+ // TODO only apply mimimal edits
await this.resource.saveContents(JSON.stringify(keybindings, undefined, 4));
}
@@ -139,23 +151,14 @@ export class KeymapsService {
if (!this.resource.saveContents) {
return;
}
- const content = await this.resource.readContents();
- const keybindings: KeybindingJson[] = content ? jsoncparser.parse(content) : [];
- const filtered = keybindings.filter(a => a.command !== commandId);
+ const keybindings = await this.parseKeybindings();
+ const removedCommand = '-' + commandId;
+ const filtered = keybindings.filter(a => a.command !== commandId && a.command !== removedCommand);
+ // TODO use preference values to get proper json settings
+ // TODO handle dirty models properly
+ // TODO handle race conditions properly
+ // TODO only apply mimimal edits
await this.resource.saveContents(JSON.stringify(filtered, undefined, 4));
}
- /**
- * Get the list of keybindings from the JSON.
- *
- * @returns the list of keybindings in JSON.
- */
- async getKeybindings(): Promise {
- if (!this.resource.saveContents) {
- return [];
- }
- const content = await this.resource.readContents();
- return content ? jsoncparser.parse(content) : [];
- }
-
}
diff --git a/packages/keymaps/src/package.spec.ts b/packages/keymaps/src/package.spec.ts
new file mode 100644
index 0000000000000..e8c9b16f8d7a4
--- /dev/null
+++ b/packages/keymaps/src/package.spec.ts
@@ -0,0 +1,28 @@
+/********************************************************************************
+ * Copyright (C) 2017 Ericsson and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+/* note: this bogus test file is required so that
+ we are able to run mocha unit tests on this
+ package, without having any actual unit tests in it.
+ This way a coverage report will be generated,
+ showing 0% coverage, instead of no report.
+ This file can be removed once we have real unit
+ tests in place. */
+
+describe('keymaps package', () => {
+
+ it('support code coverage statistics', () => true);
+});
diff --git a/packages/monaco/src/browser/monaco-editor-provider.ts b/packages/monaco/src/browser/monaco-editor-provider.ts
index 3efec40d79afc..64d965f56f2c4 100644
--- a/packages/monaco/src/browser/monaco-editor-provider.ts
+++ b/packages/monaco/src/browser/monaco-editor-provider.ts
@@ -36,6 +36,8 @@ import { MonacoBulkEditService } from './monaco-bulk-edit-service';
import IEditorOverrideServices = monaco.editor.IEditorOverrideServices;
import { ApplicationServer } from '@theia/core/lib/common/application-protocol';
import { OS } from '@theia/core';
+import { KeybindingRegistry } from '@theia/core/lib/browser';
+import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
@injectable()
export class MonacoEditorProvider {
@@ -46,6 +48,9 @@ export class MonacoEditorProvider {
@inject(MonacoEditorServices)
protected readonly services: MonacoEditorServices;
+ @inject(KeybindingRegistry)
+ protected keybindingRegistry: KeybindingRegistry;
+
private isWindowsBackend: boolean = false;
protected _current: MonacoEditor | undefined;
@@ -125,6 +130,9 @@ export class MonacoEditorProvider {
}, toDispose);
editor.onDispose(() => toDispose.dispose());
+ this.suppressMonacoKeybindingListener(editor);
+ this.injectKeybindingResolver(editor);
+
const standaloneCommandService = new monaco.services.StandaloneCommandService(editor.instantiationService);
commandService.setDelegate(standaloneCommandService);
this.installQuickOpenService(editor);
@@ -144,6 +152,39 @@ export class MonacoEditorProvider {
return editor;
}
+ /**
+ * Suppresses Monaco keydown listener to avoid triggering default Monaco keybindings
+ * if they are overriden by a user. Monaco keybindings should be registered as Theia keybindings
+ * to allow a user to customize them.
+ */
+ protected suppressMonacoKeybindingListener(editor: MonacoEditor): void {
+ let keydownListener: monaco.IDisposable | undefined;
+ for (const listener of editor.getControl()._standaloneKeybindingService._store._toDispose) {
+ if ('_type' in listener && listener['_type'] === 'keydown') {
+ keydownListener = listener;
+ break;
+ }
+ }
+ if (keydownListener) {
+ keydownListener.dispose();
+ }
+ }
+
+ protected injectKeybindingResolver(editor: MonacoEditor): void {
+ const keybindingService = editor.getControl()._standaloneKeybindingService;
+ keybindingService.resolveKeybinding = keybinding => [new MonacoResolvedKeybinding(MonacoResolvedKeybinding.keySequence(keybinding), this.keybindingRegistry)];
+ keybindingService.resolveKeyboardEvent = keyboardEvent => {
+ const keybinding = new monaco.keybindings.SimpleKeybinding(
+ keyboardEvent.ctrlKey,
+ keyboardEvent.shiftKey,
+ keyboardEvent.altKey,
+ keyboardEvent.metaKey,
+ keyboardEvent.keyCode
+ ).toChord();
+ return new MonacoResolvedKeybinding(MonacoResolvedKeybinding.keySequence(keybinding), this.keybindingRegistry);
+ };
+ }
+
protected createEditor(uri: URI, override: IEditorOverrideServices, toDispose: DisposableCollection): Promise {
if (DiffUris.isDiffUri(uri)) {
return this.createMonacoDiffEditor(uri, override, toDispose);
diff --git a/packages/monaco/src/browser/monaco-keybinding.ts b/packages/monaco/src/browser/monaco-keybinding.ts
index 2a96e194e6357..b66dd53803e75 100644
--- a/packages/monaco/src/browser/monaco-keybinding.ts
+++ b/packages/monaco/src/browser/monaco-keybinding.ts
@@ -15,21 +15,12 @@
********************************************************************************/
import { injectable, inject } from 'inversify';
-import { KeybindingContribution, KeybindingRegistry, Key, KeyCode, Keystroke, KeyModifier, KeySequence } from '@theia/core/lib/browser';
+import { KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
import { EditorKeybindingContexts } from '@theia/editor/lib/browser';
import { MonacoCommands } from './monaco-command';
import { MonacoCommandRegistry } from './monaco-command-registry';
-import { KEY_CODE_MAP } from './monaco-keycode-map';
-import { isOSX, environment } from '@theia/core';
-
-function monaco2BrowserKeyCode(keyCode: monaco.KeyCode): number {
- for (let i = 0; i < KEY_CODE_MAP.length; i++) {
- if (KEY_CODE_MAP[i] === keyCode) {
- return i;
- }
- }
- return -1;
-}
+import { environment } from '@theia/core';
+import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
@injectable()
export class MonacoKeybindingContribution implements KeybindingContribution {
@@ -45,15 +36,12 @@ export class MonacoKeybindingContribution implements KeybindingContribution {
const item = defaultKeybindings[i];
const command = this.commands.validate(item.command);
if (command) {
- const raw = item.keybinding;
const when = item.when && item.when.serialize();
let keybinding;
if (item.command === MonacoCommands.GO_TO_DEFINITION && !environment.electron.is()) {
keybinding = 'ctrlcmd+f11';
} else {
- keybinding = raw instanceof monaco.keybindings.SimpleKeybinding
- ? this.keyCode(raw).toString()
- : this.keySequence(raw as monaco.keybindings.ChordKeybinding).join(' ');
+ keybinding = MonacoResolvedKeybinding.toKeybinding(item.keybinding);
}
registry.registerKeybinding({ command, keybinding, when });
}
@@ -69,33 +57,4 @@ export class MonacoKeybindingContribution implements KeybindingContribution {
});
}
}
-
- protected keyCode(keybinding: monaco.keybindings.SimpleKeybinding): KeyCode {
- const keyCode = keybinding.keyCode;
- const sequence: Keystroke = {
- first: Key.getKey(monaco2BrowserKeyCode(keyCode & 0xff)),
- modifiers: []
- };
- if (keybinding.ctrlKey) {
- if (isOSX) {
- sequence.modifiers!.push(KeyModifier.MacCtrl);
- } else {
- sequence.modifiers!.push(KeyModifier.CtrlCmd);
- }
- }
- if (keybinding.shiftKey) {
- sequence.modifiers!.push(KeyModifier.Shift);
- }
- if (keybinding.altKey) {
- sequence.modifiers!.push(KeyModifier.Alt);
- }
- if (keybinding.metaKey && sequence.modifiers!.indexOf(KeyModifier.CtrlCmd) === -1) {
- sequence.modifiers!.push(KeyModifier.CtrlCmd);
- }
- return KeyCode.createKeyCode(sequence);
- }
-
- protected keySequence(keybinding: monaco.keybindings.ChordKeybinding): KeySequence {
- return keybinding.parts.map(part => this.keyCode(part));
- }
}
diff --git a/packages/monaco/src/browser/monaco-quick-open-service.ts b/packages/monaco/src/browser/monaco-quick-open-service.ts
index 6c2fa6df948da..7b5591ebe7d68 100644
--- a/packages/monaco/src/browser/monaco-quick-open-service.ts
+++ b/packages/monaco/src/browser/monaco-quick-open-service.ts
@@ -18,14 +18,13 @@ import { injectable, inject, postConstruct } from 'inversify';
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
import {
QuickOpenService, QuickOpenOptions, QuickOpenItem, QuickOpenGroupItem,
- QuickOpenMode, KeySequence, ResolvedKeybinding,
- KeyCode, Key, KeybindingRegistry
+ QuickOpenMode, KeySequence, KeybindingRegistry
} from '@theia/core/lib/browser';
import { QuickOpenModel, QuickOpenActionProvider, QuickOpenAction } from '@theia/core/lib/common/quick-open-model';
-import { KEY_CODE_MAP } from './monaco-keycode-map';
import { ContextKey } from '@theia/core/lib/browser/context-key-service';
import { MonacoContextKeyService } from './monaco-context-key-service';
import { QuickOpenHideReason } from '@theia/core/lib/common/quick-open-service';
+import { MonacoResolvedKeybinding } from './monaco-resolved-keybinding';
export interface MonacoQuickOpenControllerOpts extends monaco.quickOpen.IQuickOpenControllerOpts {
valueSelection?: Readonly<[number, number]>;
@@ -289,7 +288,7 @@ export class MonacoQuickOpenControllerOptsImpl implements MonacoQuickOpenControl
constructor(
protected readonly model: QuickOpenModel,
- protected readonly keybindingService: TheiaKeybindingService,
+ protected readonly keybindingService: KeybindingRegistry,
options?: QuickOpenOptions
) {
this.model = model;
@@ -397,7 +396,7 @@ export class QuickOpenEntry extends monaco.quickOpen.QuickOpenEntry {
constructor(
public readonly item: QuickOpenItem,
- protected readonly keybindingService: TheiaKeybindingService
+ protected readonly keybindingService: KeybindingRegistry
) {
super();
}
@@ -443,7 +442,7 @@ export class QuickOpenEntry extends monaco.quickOpen.QuickOpenEntry {
} catch (error) {
return undefined;
}
- return new TheiaResolvedKeybinding(keySequence, this.keybindingService);
+ return new MonacoResolvedKeybinding(keySequence, this.keybindingService);
}
run(mode: monaco.quickOpen.Mode): boolean {
@@ -465,7 +464,7 @@ export class QuickOpenEntryGroup extends monaco.quickOpen.QuickOpenEntryGroup {
constructor(
public readonly item: QuickOpenGroupItem,
- protected readonly keybindingService: TheiaKeybindingService
+ protected readonly keybindingService: KeybindingRegistry
) {
super(new QuickOpenEntry(item, keybindingService));
}
@@ -540,80 +539,3 @@ export class MonacoQuickOpenActionProvider implements monaco.quickOpen.IActionPr
return actions.map(action => new MonacoQuickOpenAction(action));
}
}
-
-interface TheiaKeybindingService {
- resolveKeybinding(binding: ResolvedKeybinding): KeyCode[];
- acceleratorForKey(key: Key): string;
- acceleratorForKeyCode(keyCode: KeyCode, separator?: string): string
- acceleratorForSequence(keySequence: KeySequence, separator?: string): string[];
-}
-
-class TheiaResolvedKeybinding extends monaco.keybindings.ResolvedKeybinding {
-
- protected readonly parts: monaco.keybindings.ResolvedKeybindingPart[];
-
- constructor(protected readonly keySequence: KeySequence, keybindingService: TheiaKeybindingService) {
- super();
- this.parts = keySequence.map(keyCode => {
- // tslint:disable-next-line:no-null-keyword
- const keyLabel = keyCode.key ? keybindingService.acceleratorForKey(keyCode.key) : null;
- const keyAriaLabel = keyLabel;
- return new monaco.keybindings.ResolvedKeybindingPart(
- keyCode.ctrl,
- keyCode.shift,
- keyCode.alt,
- keyCode.meta,
- keyLabel,
- keyAriaLabel
- );
- });
- }
-
- public getLabel(): string | null {
- return monaco.keybindings.UILabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
- }
-
- public getAriaLabel(): string | null {
- return monaco.keybindings.UILabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyAriaLabel);
- }
-
- public getElectronAccelerator(): string | null {
- if (this.isChord) {
- // Electron cannot handle chords
- // tslint:disable-next-line:no-null-keyword
- return null;
- }
- return monaco.keybindings.ElectronAcceleratorLabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
- }
-
- public getUserSettingsLabel(): string | null {
- return monaco.keybindings.UserSettingsLabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
- }
-
- public isWYSIWYG(): boolean {
- return true;
- }
-
- public isChord(): boolean {
- return this.parts.length > 1;
- }
-
- public getDispatchParts(): (string | null)[] {
- return this.keySequence.map(keyCode => monaco.keybindings.USLayoutResolvedKeybinding.getDispatchStr(this.toKeybinding(keyCode)));
- }
-
- private toKeybinding(keyCode: KeyCode): monaco.keybindings.SimpleKeybinding {
- return new monaco.keybindings.SimpleKeybinding(
- keyCode.ctrl,
- keyCode.shift,
- keyCode.alt,
- keyCode.meta,
- KEY_CODE_MAP[keyCode.key!.keyCode]
- );
- }
-
- public getParts(): monaco.keybindings.ResolvedKeybindingPart[] {
- return this.parts;
- }
-
-}
diff --git a/packages/monaco/src/browser/monaco-resolved-keybinding.ts b/packages/monaco/src/browser/monaco-resolved-keybinding.ts
new file mode 100644
index 0000000000000..4825f42e3c334
--- /dev/null
+++ b/packages/monaco/src/browser/monaco-resolved-keybinding.ts
@@ -0,0 +1,134 @@
+/********************************************************************************
+ * Copyright (C) 2017 TypeFox and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the Eclipse
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
+ * with the GNU Classpath Exception which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ ********************************************************************************/
+
+import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
+import { KeyCode, KeySequence, Keystroke, Key, KeyModifier } from '@theia/core/lib/browser/keys';
+import { isOSX } from '@theia/core/lib/common/os';
+import { KEY_CODE_MAP } from './monaco-keycode-map';
+
+export class MonacoResolvedKeybinding extends monaco.keybindings.ResolvedKeybinding {
+
+ protected readonly parts: monaco.keybindings.ResolvedKeybindingPart[];
+
+ constructor(protected readonly keySequence: KeySequence, keybindingService: KeybindingRegistry) {
+ super();
+ this.parts = keySequence.map(keyCode => {
+ // tslint:disable-next-line:no-null-keyword
+ const keyLabel = keyCode.key ? keybindingService.acceleratorForKey(keyCode.key) : null;
+ const keyAriaLabel = keyLabel;
+ return new monaco.keybindings.ResolvedKeybindingPart(
+ keyCode.ctrl,
+ keyCode.shift,
+ keyCode.alt,
+ keyCode.meta,
+ keyLabel,
+ keyAriaLabel
+ );
+ });
+ }
+
+ public getLabel(): string | null {
+ return monaco.keybindings.UILabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
+ }
+
+ public getAriaLabel(): string | null {
+ return monaco.keybindings.UILabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyAriaLabel);
+ }
+
+ public getElectronAccelerator(): string | null {
+ if (this.isChord) {
+ // Electron cannot handle chords
+ // tslint:disable-next-line:no-null-keyword
+ return null;
+ }
+ return monaco.keybindings.ElectronAcceleratorLabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
+ }
+
+ public getUserSettingsLabel(): string | null {
+ return monaco.keybindings.UserSettingsLabelProvider.toLabel(monaco.platform.OS, this.parts, p => p.keyLabel);
+ }
+
+ public isWYSIWYG(): boolean {
+ return true;
+ }
+
+ public isChord(): boolean {
+ return this.parts.length > 1;
+ }
+
+ public getDispatchParts(): (string | null)[] {
+ return this.keySequence.map(keyCode => monaco.keybindings.USLayoutResolvedKeybinding.getDispatchStr(this.toKeybinding(keyCode)));
+ }
+
+ private toKeybinding(keyCode: KeyCode): monaco.keybindings.SimpleKeybinding {
+ return new monaco.keybindings.SimpleKeybinding(
+ keyCode.ctrl,
+ keyCode.shift,
+ keyCode.alt,
+ keyCode.meta,
+ KEY_CODE_MAP[keyCode.key!.keyCode]
+ );
+ }
+
+ public getParts(): monaco.keybindings.ResolvedKeybindingPart[] {
+ return this.parts;
+ }
+
+ static toKeybinding(keybinding: monaco.keybindings.Keybinding): string {
+ return keybinding instanceof monaco.keybindings.SimpleKeybinding
+ ? this.keyCode(keybinding).toString()
+ : this.keySequence(keybinding as monaco.keybindings.ChordKeybinding).join(' ');
+ }
+
+ static keyCode(keybinding: monaco.keybindings.SimpleKeybinding): KeyCode {
+ const keyCode = keybinding.keyCode;
+ const sequence: Keystroke = {
+ first: Key.getKey(this.monaco2BrowserKeyCode(keyCode & 0xff)),
+ modifiers: []
+ };
+ if (keybinding.ctrlKey) {
+ if (isOSX) {
+ sequence.modifiers!.push(KeyModifier.MacCtrl);
+ } else {
+ sequence.modifiers!.push(KeyModifier.CtrlCmd);
+ }
+ }
+ if (keybinding.shiftKey) {
+ sequence.modifiers!.push(KeyModifier.Shift);
+ }
+ if (keybinding.altKey) {
+ sequence.modifiers!.push(KeyModifier.Alt);
+ }
+ if (keybinding.metaKey && sequence.modifiers!.indexOf(KeyModifier.CtrlCmd) === -1) {
+ sequence.modifiers!.push(KeyModifier.CtrlCmd);
+ }
+ return KeyCode.createKeyCode(sequence);
+ }
+
+ static keySequence(keybinding: monaco.keybindings.ChordKeybinding): KeySequence {
+ return keybinding.parts.map(part => this.keyCode(part));
+ }
+
+ private static monaco2BrowserKeyCode(keyCode: monaco.KeyCode): number {
+ for (let i = 0; i < KEY_CODE_MAP.length; i++) {
+ if (KEY_CODE_MAP[i] === keyCode) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts
index 39a847a28fad3..6d558fc055410 100644
--- a/packages/monaco/src/typings/monaco/index.d.ts
+++ b/packages/monaco/src/typings/monaco/index.d.ts
@@ -48,6 +48,13 @@ declare module monaco.editor {
setDecorations(decorationTypeKey: string, ranges: IDecorationOptions[]): void;
setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void;
trigger(source: string, handlerId: string, payload: any): void
+ _standaloneKeybindingService: {
+ _store: {
+ _toDispose: monaco.IDisposable[]
+ }
+ resolveKeybinding(keybinding: monaco.keybindings.ChordKeybinding): monaco.keybindings.ResolvedKeybinding[];
+ resolveKeyboardEvent(keyboardEvent: monaco.IKeyboardEvent): monaco.keybindings.ResolvedKeybinding;
+ }
}
// https://github.com/TypeFox/vscode/blob/monaco/0.18.0/src/vs/editor/browser/widget/codeEditorWidget.ts#L107
@@ -346,6 +353,7 @@ declare module monaco.keybindings {
public readonly keyCode: KeyCode;
constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, keyCode: KeyCode);
+ toChord(): ChordKeybinding;
}
// https://github.com/TypeFox/vscode/blob/monaco/0.18.0/src/vs/base/common/keyCodes.ts#L503