#18095 Implement first cut of keybindings customisation editor

This commit is contained in:
Sandeep Somavarapu
2017-03-16 02:52:09 +01:00
parent 2cb2af5040
commit 2e8a9c02f4
20 changed files with 1650 additions and 28 deletions
+14 -1
View File
@@ -79,7 +79,20 @@ export function setProperty(text: string, path: JSONPath, value: any, formatting
return withFormatting(text, edit, formattingOptions);
}
} else if (parent.type === 'array' && typeof lastSegment === 'number') {
throw new Error('Array modification not supported yet');
let insertIndex = lastSegment;
if (insertIndex === -1) {
let newProperty = `${JSON.stringify(value)}`;
let edit: Edit;
if (parent.children.length === 0) {
edit = { offset: parent.offset + 1, length: 0, content: newProperty };
} else {
let previous = parent.children[parent.children.length - 1];
edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty };
}
return withFormatting(text, edit, formattingOptions);
} else {
throw new Error('Array modification not supported yet');
}
} else {
throw new Error(`Can not add ${typeof lastSegment !== 'number' ? 'index' : 'property'} to parent of type ${parent.type}`);
}
+12
View File
@@ -119,4 +119,16 @@ suite('JSON - edits', () => {
edits = removeProperty(content, ['a'], formatterOptions);
assertEdit(content, edits, '{\n "x": "y"\n}');
});
test('insert item to empty array', () => {
let content = '[\n]';
let edits = setProperty(content, [-1], 'bar', formatterOptions);
assertEdit(content, edits, '[\n "bar"\n]');
});
test('insert item', () => {
let content = '[\n 1,\n 2\n]';
let edits = setProperty(content, [-1], 'bar', formatterOptions);
assertEdit(content, edits, '[\n 1,\n 2,\n "bar"\n]');
});
});
@@ -13,7 +13,7 @@ import Severity from 'vs/base/common/severity';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { KeybindingResolver, IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { IKeybindingEvent, IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IKeybindingEvent, IKeybindingService, IKeybindingItem2, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey';
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
import { IMessageService } from 'vs/platform/message/common/message';
@@ -169,6 +169,15 @@ export abstract class AbstractKeybindingService implements IKeybindingService {
);
}
public getKeybindings(): IKeybindingItem2[] {
return this._getResolver().getKeybindings().map(keybinding => ({
keybinding: this.resolveKeybinding(keybinding.keybinding),
command: keybinding.command,
when: keybinding.when,
source: keybinding.isDefault ? KeybindingSource.Default : KeybindingSource.User
}));
}
private static _getDefaultKeybindings(defaultKeybindings: NormalizedKeybindingItem[]): string {
let out = new OutputBuilder();
out.writeLine('[');
@@ -43,6 +43,13 @@ export interface IKeybindingItem {
weight2: number;
}
export interface IKeybindingItem2 {
keybinding: ResolvedKeybinding;
command: string;
source: KeybindingSource;
when: ContextKeyExpr;
}
export enum KeybindingSource {
Default = 1,
User
@@ -64,6 +71,8 @@ export interface IKeybindingService {
getDefaultKeybindings(): string;
getKeybindings(): IKeybindingItem2[];
/**
* @deprecated
*/
@@ -16,6 +16,7 @@ export interface IResolveResult {
export class KeybindingResolver {
private readonly _defaultKeybindings: NormalizedKeybindingItem[];
private readonly _keybindings: NormalizedKeybindingItem[];
private readonly _shouldWarnOnConflict: boolean;
private readonly _defaultBoundCommands: Map<string, boolean>;
private readonly _map: Map<string, NormalizedKeybindingItem[]>;
@@ -34,9 +35,9 @@ export class KeybindingResolver {
this._map = new Map<string, NormalizedKeybindingItem[]>();
this._lookupMap = new Map<string, NormalizedKeybindingItem[]>();
let allKeybindings = KeybindingResolver.combine(defaultKeybindings, overrides);
for (let i = 0, len = allKeybindings.length; i < len; i++) {
let k = allKeybindings[i];
this._keybindings = KeybindingResolver.combine(defaultKeybindings, overrides);
for (let i = 0, len = this._keybindings.length; i < len; i++) {
let k = this._keybindings[i];
if (k.keypressFirstPart === null) {
// unbound
continue;
@@ -204,6 +205,10 @@ export class KeybindingResolver {
return this._defaultKeybindings;
}
public getKeybindings(): NormalizedKeybindingItem[] {
return this._keybindings;
}
public lookupKeybindings(commandId: string): NormalizedKeybindingItem[] {
let items = this._lookupMap.get(commandId);
if (typeof items === 'undefined' || items.length === 0) {
@@ -6,7 +6,7 @@
import { ResolvedKeybinding, Keybinding } from 'vs/base/common/keyCodes';
import Event from 'vs/base/common/event';
import { IKeybindingService, IKeybindingEvent } from 'vs/platform/keybinding/common/keybinding';
import { IKeybindingService, IKeybindingEvent, IKeybindingItem2 } from 'vs/platform/keybinding/common/keybinding';
import { IContextKey, IContextKeyService, IContextKeyServiceTarget, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/abstractKeybindingService';
@@ -72,6 +72,10 @@ export class MockKeybindingService2 implements IKeybindingService {
return null;
}
public getKeybindings(): IKeybindingItem2[] {
return [];
}
public resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding {
return new USLayoutResolvedKeybinding(keybinding, OS);
}
@@ -52,6 +52,7 @@ import { IConfigurationEditingService } from 'vs/workbench/services/configuratio
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IActivityBarService } from 'vs/workbench/services/activity/common/activityBarService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -521,6 +522,9 @@ export class Workbench implements IPartService {
this.configurationEditingService = this.instantiationService.createInstance(ConfigurationEditingService);
serviceCollection.set(IConfigurationEditingService, this.configurationEditingService);
// Keybinding Editing
serviceCollection.set(IKeybindingEditingService, this.instantiationService.createInstance(KeybindingsEditingService));
// Configuration Resolver
const workspace = this.contextService.getWorkspace();
serviceCollection.set(IConfigurationResolverService, new SyncDescriptor(ConfigurationResolverService, workspace ? workspace.resource : null, process.env));
@@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/keybindings';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import Event, { Emitter } from 'vs/base/common/event';
import { Widget } from 'vs/base/browser/ui/widget';
import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
import * as dom from 'vs/base/browser/dom';
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { renderHtml } from 'vs/base/browser/htmlContentRenderer';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Dimension } from 'vs/base/browser/builder';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class KeybindingInputWidget extends Widget {
public readonly inputBox: InputBox;
private _onKeybinding = this._register(new Emitter<ResolvedKeybinding>());
public readonly onKeybinding: Event<ResolvedKeybinding> = this._onKeybinding.event;
private _onEnter = this._register(new Emitter<void>());
public readonly onEnter: Event<void> = this._onEnter.event;
private _onEscape = this._register(new Emitter<void>());
public readonly onEscape: Event<void> = this._onEscape.event;
constructor(parent: HTMLElement, private options: IInputOptions,
@IContextViewService private contextViewService: IContextViewService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super();
this.inputBox = this._register(new InputBox(parent, this.contextViewService, this.options));
this.onkeydown(this.inputBox.inputElement, e => this.onKeyDown(e));
}
private onKeyDown(keyboardEvent: IKeyboardEvent): void {
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
switch (keyboardEvent.toKeybinding().value) {
case KeyCode.Enter:
this._onEnter.fire();
return;
case KeyCode.Escape:
this._onEscape.fire();
return;
}
this.printKeybinding(keyboardEvent);
}
private printKeybinding(keyboardEvent: IKeyboardEvent): void {
const keybinding = this.keybindingService.resolveKeybinding(keyboardEvent.toKeybinding());
this.inputBox.value = keybinding.getUserSettingsLabel().toLowerCase();
this.inputBox.inputElement.title = 'keyCode: ' + keyboardEvent.browserEvent.keyCode;
this._onKeybinding.fire(keybinding);
}
}
export class DefineKeybindingWidget extends Widget {
private static WIDTH = 400;
private static HEIGHT = 90;
private _domNode: FastDomNode<HTMLElement>;
private _keybindingInputWidget: KeybindingInputWidget;
private _outputNode: HTMLElement;
private _resolvedKeybinding: ResolvedKeybinding = null;
private _isVisible: boolean = false;
private _onHide = this._register(new Emitter<void>());
constructor(parent: HTMLElement,
@IKeybindingService private keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super();
this.create(parent);
}
define(): TPromise<string> {
return new TPromise((c, e) => {
if (!this._isVisible) {
this._isVisible = true;
this._domNode.setDisplay('block');
this._resolvedKeybinding = null;
this._keybindingInputWidget.inputBox.value = '';
dom.clearNode(this._outputNode);
this._keybindingInputWidget.inputBox.focus();
}
const disposable = this._onHide.event(() => {
if (this._resolvedKeybinding) {
c(this._resolvedKeybinding.getUserSettingsLabel());
} else {
c(null);
}
disposable.dispose();
});
});
}
layout(layout: Dimension): void {
let top = Math.round((layout.height - DefineKeybindingWidget.HEIGHT) / 2);
this._domNode.setTop(top);
let left = Math.round((layout.width - DefineKeybindingWidget.WIDTH) / 2);
this._domNode.setLeft(left);
}
private create(parent: HTMLElement): void {
this._domNode = createFastDomNode(document.createElement('div'));
this._domNode.setDisplay('none');
this._domNode.setClassName('defineKeybindingWidget');
this._domNode.setWidth(DefineKeybindingWidget.WIDTH);
this._domNode.setHeight(DefineKeybindingWidget.HEIGHT);
dom.append(parent, this._domNode.domNode);
dom.append(this._domNode.domNode, dom.$('.message', null, nls.localize('defineKeybinding.initial', "Press desired key combination and ENTER. ESCAPE to cancel.")));
this._keybindingInputWidget = this.instantiationService.createInstance(KeybindingInputWidget, this._domNode.domNode, {});
this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.printKeybinding(keybinding)));
this._register(this._keybindingInputWidget.onEnter(() => this.hide()));
this._register(this._keybindingInputWidget.onEscape(() => this.onCancel()));
this._register(dom.addDisposableListener(this._keybindingInputWidget.inputBox.inputElement, 'blur', e => this.onCancel()));
this._outputNode = dom.append(this._domNode.domNode, dom.$('.output'));;
}
private printKeybinding(keybinding: ResolvedKeybinding): void {
this._resolvedKeybinding = keybinding;
dom.clearNode(this._outputNode);
let htmlkb = this._resolvedKeybinding.getHTMLLabel();
htmlkb.forEach((item) => this._outputNode.appendChild(renderHtml(item)));
}
private onCancel(): void {
this._resolvedKeybinding = null;
this.hide();
}
private hide(): void {
this._isVisible = false;
this._onHide.fire();
}
}
@@ -0,0 +1,574 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/keybindingsEditor';
import { localize } from 'vs/nls';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { Delayer } from 'vs/base/common/async';
import * as DOM from 'vs/base/browser/dom';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IAction } from 'vs/base/common/actions';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { KeybindingsEditorModel, IKeybindingItemEntry, IKeybindingItem } from 'vs/workbench/parts/preferences/common/keybindingsEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService, IKeybindingItem2, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { SearchWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { DefineKeybindingWidget } from 'vs/workbench/parts/preferences/browser/keybindingWidgets';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { renderHtml } from 'vs/base/browser/htmlContentRenderer';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing';
import { IListService } from 'vs/platform/list/browser/listService';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { IThemeService } from 'vs/platform/theme/common/themeService';
let $ = DOM.$;
export interface IKeybindingsEditor {
defineKeybinding(keybindingItem: IKeybindingItem);
}
export class KeybindingsEditorInput extends EditorInput {
public static ID: string = 'worknench.input.keybindings';
public readonly keybindingsModel: KeybindingsEditorModel;
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
super();
this.keybindingsModel = instantiationService.createInstance(KeybindingsEditorModel);
}
getTypeId(): string {
return KeybindingsEditorInput.ID;
}
getName(): string {
return localize('keybindingsInputName', "Keybindings");
}
resolve(refresh?: boolean): TPromise<KeybindingsEditorModel> {
return TPromise.as(this.keybindingsModel);
}
matches(otherInput: any): boolean {
return otherInput instanceof KeybindingsEditorInput;
}
}
export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor {
public static ID: string = 'workbench.editor.keybindings';
private keybindingsContentElement: HTMLElement;
private scrollableElement: DomScrollableElement;
private searchWidget: SearchWidget;
private defineKeybindingWidget: DefineKeybindingWidget;
private overlayContainer: HTMLElement;
private dimension: Dimension;
private activeKeybindingData: IKeybindingItemEntry;
private activeKeybindingRow: HTMLElement;
private delayedFiltering: Delayer<void>;
private keybindingsList: List<IKeybindingItemEntry>;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IKeybindingService private keybindingsService: IKeybindingService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IKeybindingEditingService private keybindingEditingService: IKeybindingEditingService,
@IListService private listService: IListService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(KeybindingsEditor.ID, telemetryService, themeService);
this.delayedFiltering = new Delayer<void>(300);
this._register(keybindingsService.onDidUpdateKeybindings(() => this.render()));
}
createEditor(parent: Builder): void {
const parentElement = parent.getHTMLElement();
const keybindingsEditorElement = DOM.append(parentElement, $('div', { class: 'keybindings-editor' }));
this.createOverlayContainer(keybindingsEditorElement);
this.createHeader(keybindingsEditorElement);
this.createBody(keybindingsEditorElement);
}
setInput(input: KeybindingsEditorInput): TPromise<void> {
const oldInput = this.input;
return super.setInput(input)
.then(() => {
if (!input.matches(oldInput)) {
this.render();
}
});
}
clearInput(): void {
super.clearInput();
this.keybindingsContentElement.removeChild(this.keybindingsContentElement.children.item(0));
this.searchWidget.clear();
}
layout(dimension: Dimension): void {
this.dimension = dimension;
this.searchWidget.layout(dimension);
this.overlayContainer.style.width = dimension.width + 'px';
this.overlayContainer.style.height = dimension.height + 'px';
if (this.scrollableElement) {
this.scrollableElement.scanDomNode();
}
this.defineKeybindingWidget.layout(this.dimension);
if (this.keybindingsList) {
this.keybindingsList.layout(dimension.height);
}
}
focus(): void {
this.searchWidget.focus();
}
private createOverlayContainer(parent: HTMLElement): void {
this.overlayContainer = DOM.append(parent, $('.overlay-container'));
this.overlayContainer.style.position = 'absolute';
this.overlayContainer.style.display = 'none';
this.overlayContainer.style.zIndex = '10';
this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer));
}
private createHeader(parent: HTMLElement): void {
const headerContainer = DOM.append(parent, $('.keybindings-header'));
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, DOM.append(headerContainer, $('.search-container')), {
ariaLabel: localize('SearchKeybindings.AriaLabel', "Search keybindings"),
placeholder: localize('SearchKeybindings.Placeholder', "Search keybindings")
}));
this._register(this.searchWidget.onDidChange(searchValue => this.delayedFiltering.trigger(() => this.render())));
}
private createBody(parent: HTMLElement): void {
const bodyContainer = DOM.append(parent, $('.keybindings-body'));
const scrollContainer = $('.keybindings-scroll-container');
this.scrollableElement = new DomScrollableElement(scrollContainer, { canUseTranslate3d: false });
this.scrollableElement.scanDomNode();
DOM.append(bodyContainer, this.scrollableElement.getDomNode());
const openKeybindingsContainer = DOM.append(scrollContainer, $('.open-keybindings-container'));
DOM.append(openKeybindingsContainer, $('span', null, localize('header-message', "For advanced customizations open and edit ")));
const fileElement = DOM.append(openKeybindingsContainer, $('span.file-name', null, localize('keybindings-file-name', "keybindings.json")));
this._register(DOM.addDisposableListener(fileElement, DOM.EventType.CLICK, () => this.editorService.openEditor({ resource: URI.file(this.environmentService.appKeybindingsPath), options: { pinned: true } })));
this.keybindingsContentElement = DOM.append(scrollContainer, $('.content'));
}
/*private createBody(parent: HTMLElement): void {
const bodyContainer = DOM.append(parent, $('.keybindings-body'));
const openKeybindingsContainer = DOM.append(bodyContainer, $('.open-keybindings-container'));
DOM.append(openKeybindingsContainer, $('span', null, localize('header-message', "For advanced customizations open and edit ")));
const fileElement = DOM.append(openKeybindingsContainer, $('span.file-name', null, localize('keybindings-file-name', "keybindings.json")));
this._register(DOM.addDisposableListener(fileElement, DOM.EventType.CLICK, () => this.editorService.openEditor({ resource: URI.file(this.environmentService.appKeybindingsPath), options: { pinned: true } })));
this.createList(bodyContainer);
}
private createList(parent: HTMLElement): void {
const delegate = new Delegate();
this.createListHeader(parent);
const keybindingListContainer = DOM.append(parent, $('.keybindings-list-container'));
this.keybindingsList = this._register(new List<IKeybindingItemEntry>(keybindingListContainer, delegate, [new KeybindingItemRenderer(this)], { identityProvider: e => e.id }));
this._register(this.listService.register(this.keybindingsList));
}
private createListHeader(parent: HTMLElement): void {
DOM.append(parent, $('.keybindings-list-header', null,
$('.header.actions'),
$('.header.command', null, localize('command', "Command")),
$('.header.keybinding', null, localize('keybinding', "Keybinding")),
$('.header.source', null, localize('source', "Source")),
$('.header.when', null, localize('when', "When"))));
}*/
private render(): TPromise<any> {
if (this.input) {
return this.input.resolve()
.then((keybindingsModel: KeybindingsEditorModel) => keybindingsModel.resolve()
.then(() => this.renderKeybindingsData(keybindingsModel.fetch(this.searchWidget.value()))));
}
return TPromise.as(null);
}
private renderKeybindingsData(keybindingsData: IKeybindingItemEntry[]): void {
if (this.keybindingsList) {
this.keybindingsList.splice(0, this.keybindingsList.length, keybindingsData);
} else {
if (this.keybindingsContentElement.children.item(0)) {
this.keybindingsContentElement.removeChild(this.keybindingsContentElement.children.item(0));
}
DOM.append(this.keybindingsContentElement, $('div', null, this.renderKeybindingsGroup('', keybindingsData)));
this.scrollableElement.scanDomNode();
}
}
private renderKeybindingsGroup(groupName: string, keybindingsEntries: IKeybindingItemEntry[]): HTMLElement {
if (keybindingsEntries.length === 0) {
return null;
}
return $('table', null,
$('tr', null,
$('th.actions'),
$('th.command', null, localize('command', "Command")),
$('th.keybinding', null, localize('keybinding', "Keybinding")),
$('th.source', null, localize('source', "Source")),
$('th.when', null, localize('when', "When")),
),
...keybindingsEntries.map(keybindingData => this.renderKeybindingEntry(keybindingData)));
}
private renderKeybindingEntry(keybindingData: IKeybindingItemEntry): HTMLElement {
const keybindingEntryRow = $('tr', { 'tabindex': '0' });
const actionsColumn = DOM.append(keybindingEntryRow, this.renderActionsColumn(keybindingData.keybindingItem));
DOM.append(keybindingEntryRow, this.renderCommandColumn(keybindingData));
DOM.append(keybindingEntryRow, this.renderKeybindingColumn(keybindingData));
DOM.append(keybindingEntryRow, this.renderSourceColumn(keybindingData.keybindingItem));
DOM.append(keybindingEntryRow, this.renderWhenColumn(keybindingData.keybindingItem));
const focusTracker = this._register(DOM.trackFocus(keybindingEntryRow));
this._register(focusTracker.addFocusListener(() => this.onKeybindingRowFocussed(keybindingEntryRow, keybindingData)));
this._register(focusTracker.addBlurListener(() => this.onKeybindingRowBlurred(keybindingEntryRow)));
this._register(DOM.addDisposableListener(keybindingEntryRow, DOM.EventType.KEY_DOWN, e => this.onkeydown(new StandardKeyboardEvent(e), keybindingEntryRow)));
this._register(DOM.addDisposableListener(keybindingEntryRow, DOM.EventType.MOUSE_MOVE, e => this.activeKeybindingRow ? DOM.removeClass(this.activeKeybindingRow, 'focussed') : null));
this._register(DOM.addDisposableListener(keybindingEntryRow, DOM.EventType.CONTEXT_MENU, (e) => this.renderContextMenu(e, actionsColumn, keybindingData.keybindingItem)));
return keybindingEntryRow;
}
private onKeybindingRowFocussed(keybindingRow: HTMLElement, keybindingData: IKeybindingItemEntry): void {
DOM.addClass(keybindingRow, 'focussed');
this.activeKeybindingRow = keybindingRow;
this.activeKeybindingData = keybindingData;
}
private onKeybindingRowBlurred(keybindingRow: HTMLElement): void {
if (keybindingRow === this.activeKeybindingRow) {
this.activeKeybindingData = null;
this.activeKeybindingRow = null;
}
DOM.removeClass(keybindingRow, 'focussed');
}
private onkeydown(keyboardEvent: StandardKeyboardEvent, rowElement: HTMLElement): void {
let handled = false;
switch (keyboardEvent.keyCode) {
case KeyCode.DownArrow:
handled = this.focusKeybindingRowSibling(rowElement, true);
break;
case KeyCode.UpArrow:
handled = this.focusKeybindingRowSibling(rowElement, false);
break;
case KeyCode.Tab:
if (keyboardEvent.shiftKey) {
handled = this.focusKeybindingRowSibling(rowElement, false);
} else {
handled = this.focusKeybindingRowSibling(rowElement, true);
}
break;
}
if (handled) {
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
}
}
private focusKeybindingRowSibling(element: HTMLElement, next: boolean): boolean {
const elementToFocus = <HTMLElement>(next ? element.nextSibling : element.previousSibling);
if (elementToFocus && element.parentNode.firstChild !== elementToFocus) {
elementToFocus.focus();
return true;
}
return false;
}
private renderContextMenu(e: any, element: HTMLElement, keybindingItem: IKeybindingItem): void {
let anchor: HTMLElement | { x: number, y: number } = element;
if (event instanceof MouseEvent) {
const event = new StandardMouseEvent(e);
anchor = { x: event.posx, y: event.posy };
}
const actions = [this.createRemoveAction(keybindingItem)];
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.wrap(actions)
});
}
private renderActionsColumn(keybindingEntry: IKeybindingItem): HTMLElement {
const actionsContainer = $('td.actions');
const actionbar = new ActionBar(actionsContainer, { animated: false });
const actions = [];
if (keybindingEntry.keybinding) {
actions.push(this.createEditAction(keybindingEntry));
} else {
actions.push(this.createAddAction(keybindingEntry));
}
actionbar.push(actions, { icon: true });
return actionsContainer;
}
private renderCommandColumn(keybindingData: IKeybindingItemEntry): HTMLElement {
const commandColumn = $('td.command', null);
const keybindingItem = keybindingData.keybindingItem;
if (keybindingItem.commandLabel) {
new HighlightedLabel(commandColumn).set(keybindingItem.commandLabel, keybindingData.commandLabelMatches);
}
new HighlightedLabel(DOM.append(commandColumn, $('.code.strong'))).set(keybindingItem.command, keybindingData.commandIdMatches);
return commandColumn;
}
private renderKeybindingColumn(keybindingData: IKeybindingItemEntry): HTMLElement {
const keybindingColumn = $('td.keybinding');
if (keybindingData.keybindingItem.keybinding) {
const htmlkbELement = DOM.append(keybindingColumn, $('.htmlkb'));
let htmlkb = keybindingData.keybindingItem.keybinding.getHTMLLabel();
htmlkb.forEach(item => htmlkbELement.appendChild(renderHtml(item)));
new HighlightedLabel(DOM.append(keybindingColumn, $('.code'))).set(keybindingData.keybindingItem.keybinding.getAriaLabel(), keybindingData.keybindingMatches);
} else {
DOM.append(keybindingColumn, $('.empty', null, '—'));
}
return keybindingColumn;
}
private renderSourceColumn(keybindingItem: IKeybindingItem): HTMLElement {
return $('td.source', null, keybindingItem.source === KeybindingSource.User ? localize('user', "User") : localize('default', "Default"));
}
private renderWhenColumn(keybindingItem: IKeybindingItem): HTMLElement {
return $('td.when', null, keybindingItem.when ? $('.code', null, keybindingItem.when.serialize()) : $('.empty', null, '—'));
}
private createEditAction(keybinding: IKeybindingItem): IAction {
return <IAction>{
class: 'edit',
enabled: true,
id: 'editKeybinding',
tooltip: localize('change', "Change Keybinding"),
run: () => this.defineKeybinding(keybinding)
};
}
private createRemoveAction(keybinding: IKeybindingItem): IAction {
return <IAction>{
label: localize('removeLabel', "Remove Keybinding"),
enabled: !!keybinding.keybinding,
id: 'removeKeybinding',
run: () => this.keybindingEditingService.removeKeybinding(keybinding)
};
}
private createAddAction(keybinding: IKeybindingItem): IAction {
return <IAction>{
class: 'add',
enabled: true,
id: 'addKeybinding',
tooltip: localize('add', "Add Keybinding"),
run: () => this.defineKeybinding(keybinding)
};
}
defineKeybinding(keybindingItem: IKeybindingItem2): void {
this.overlayContainer.style.display = 'block';
this.defineKeybindingWidget.define().then(key => {
this.overlayContainer.style.display = 'none';
if (key) {
this.keybindingEditingService.editKeybinding(key, keybindingItem);
}
}, () => {
this.overlayContainer.style.display = 'none';
this.focus();
});
}
}
class Delegate implements IDelegate<IKeybindingItemEntry> {
getHeight() { return 24; }
getTemplateId(element: IKeybindingItemEntry) {
return KeybindingItemRenderer.TEMPLATE_ID;
}
}
interface KeybindingItemTemplate {
actions: ActionsColumn;
command: CommandColumn;
keybinding: KeybindingColumn;
source: SourceColumn;
when: WhenColumn;
}
class Column {
constructor(protected parent: HTMLElement, protected keybindingsEditor: IKeybindingsEditor) {
this.create(parent);
}
create(parent: HTMLElement) { }
}
class ActionsColumn extends Column {
private actionBar: ActionBar;
create(parent: HTMLElement) {
const actionsContainer = DOM.append(parent, $('.column.actions'));
this.actionBar = new ActionBar(actionsContainer, { animated: false });
}
render(keybindingItemEntry: IKeybindingItemEntry): void {
this.actionBar.clear();
const actions = [];
if (keybindingItemEntry.keybindingItem.keybinding) {
actions.push(this.createEditAction(keybindingItemEntry.keybindingItem));
} else {
actions.push(this.createAddAction(keybindingItemEntry.keybindingItem));
}
this.actionBar.push(actions, { icon: true });
}
private createEditAction(keybinding: IKeybindingItem): IAction {
return <IAction>{
class: 'edit',
enabled: true,
id: 'editKeybinding',
tooltip: localize('change', "Change Keybinding"),
run: () => this.keybindingsEditor.defineKeybinding(keybinding)
};
}
private createAddAction(keybinding: IKeybindingItem): IAction {
return <IAction>{
class: 'add',
enabled: true,
id: 'addKeybinding',
tooltip: localize('add', "Add Keybinding"),
run: () => this.keybindingsEditor.defineKeybinding(keybinding)
};
}
}
class CommandColumn extends Column {
private commandColumn: HTMLElement;
create(parent: HTMLElement) {
this.commandColumn = DOM.append(parent, $('.column.command'));
}
render(keybindingItemEntry: IKeybindingItemEntry): void {
DOM.clearNode(this.commandColumn);
const keybindingItem = keybindingItemEntry.keybindingItem;
if (keybindingItem.commandLabel) {
new HighlightedLabel(this.commandColumn).set(keybindingItem.commandLabel, keybindingItemEntry.commandLabelMatches);
}
new HighlightedLabel(DOM.append(this.commandColumn, $('.code.strong'))).set(keybindingItem.command, keybindingItemEntry.commandIdMatches);
}
}
class KeybindingColumn extends Column {
private keybindingColumn: HTMLElement;
create(parent: HTMLElement) {
this.keybindingColumn = DOM.append(parent, $('.column.keybinding'));
}
render(keybindingItemEntry: IKeybindingItemEntry): void {
DOM.clearNode(this.keybindingColumn);
if (keybindingItemEntry.keybindingItem.keybinding) {
let keybinding = DOM.append(this.keybindingColumn, $('.htmlkb'));
let htmlkb = keybindingItemEntry.keybindingItem.keybinding.getHTMLLabel();
htmlkb.forEach(item => keybinding.appendChild(renderHtml(item)));
new HighlightedLabel(DOM.append(this.keybindingColumn, $('.code'))).set(keybindingItemEntry.keybindingItem.keybinding.getAriaLabel(), keybindingItemEntry.keybindingMatches);
}
}
}
class SourceColumn extends Column {
private sourceColumn: HTMLElement;
create(parent: HTMLElement) {
this.sourceColumn = DOM.append(parent, $('.column.source'));
}
render(keybindingItemEntry: IKeybindingItemEntry): void {
this.sourceColumn.textContent = keybindingItemEntry.keybindingItem.source === KeybindingSource.User ? localize('user', "User") : localize('default', "Default");
}
}
class WhenColumn extends Column {
private whenColumn: HTMLElement;
create(parent: HTMLElement) {
const column = DOM.append(parent, $('.column.when'));
this.whenColumn = DOM.append(column, $('div'));
}
render(keybindingItemEntry: IKeybindingItemEntry): void {
DOM.toggleClass(this.whenColumn, 'code', !!keybindingItemEntry.keybindingItem.when);
DOM.toggleClass(this.whenColumn, 'empty', !keybindingItemEntry.keybindingItem.when);
this.whenColumn.textContent = keybindingItemEntry.keybindingItem.when ? keybindingItemEntry.keybindingItem.when.serialize() : '—';
}
}
class KeybindingItemRenderer implements IRenderer<IKeybindingItemEntry, KeybindingItemTemplate> {
static TEMPLATE_ID = 'keybinding_item_template';
get templateId(): string { return KeybindingItemRenderer.TEMPLATE_ID; }
constructor(private keybindingsEditor: IKeybindingsEditor) { }
renderTemplate(container: HTMLElement): KeybindingItemTemplate {
return {
actions: new ActionsColumn(container, this.keybindingsEditor),
command: new CommandColumn(container, this.keybindingsEditor),
keybinding: new KeybindingColumn(container, this.keybindingsEditor),
source: new SourceColumn(container, this.keybindingsEditor),
when: new WhenColumn(container, this.keybindingsEditor)
};
}
renderElement(keybindingEntry: IKeybindingItemEntry, index: number, template: KeybindingItemTemplate): void {
template.actions.render(keybindingEntry);
template.command.render(keybindingEntry);
template.keybinding.render(keybindingEntry);
template.source.render(keybindingEntry);
template.when.render(keybindingEntry);
}
disposeTemplate(template: KeybindingItemTemplate): void {
}
}
@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#424242"/><rect height="3" width="11" y="7" x="3" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 203 B

@@ -0,0 +1 @@
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><rect height="11" width="3" y="3" x="7" fill="#C5C5C5"/><rect height="3" width="11" y="7" x="3" fill="#C5C5C5"/></svg>

After

Width:  |  Height:  |  Size: 203 B

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.keybindings-editor .defineKeybindingWidget {
padding: 10px;
position: absolute;
}
.keybindings-editor .defineKeybindingWidget .message {
width: 400px;
text-align: center;
white-space: nowrap;
}
.keybindings-editor .defineKeybindingWidget .monaco-inputbox,
.keybindings-editor .defineKeybindingWidget .output {
margin-top:10px;
display: block;
text-align: center;
}
/* Theming */
.keybindings-editor .defineKeybindingWidget {
background-color: #EFEFF2;
box-shadow: 0 2px 8px #A8A8A8;
}
.vs-dark .keybindings-editor .defineKeybindingWidget {
background-color: #2D2D30;
box-shadow: 0 2px 8px #000;
}
.keybindings-editor .inlineKeybindingInfo:before {
margin: 0.2em 0.1em 0 0.1em;
content:" ";
display:inline-block;
height:0.8em;
width:1em;
background: url(info.svg) 0px -0.1em no-repeat;
background-size: 0.9em;
}
.keybindings-editor .inlineKeybindingError:before {
margin: 0.1em 0.1em 0 0.1em;
content:" ";
display:inline-block;
height:0.8em;
width:1em;
background: url(status-error.svg) 0px -0.1em no-repeat;
background-size: 1em;
}
.keybindings-editor .keybindingInfo {
box-shadow: inset 0 0 0 1px #B9B9B9;
background-color: rgba(100, 100, 250, 0.2);
}
.keybindings-editor .keybindingError {
box-shadow: inset 0 0 0 1px #B9B9B9;
background-color: rgba(250, 100, 100, 0.2);
}
/* for keybindings rendered as HTML */
/* for keybindings rendered as HTML */
.monaco-kb {
white-space: nowrap;
}
.monaco-kbkey {
display: inline-block;
border: solid 1px #ccc;
border-bottom-color: #bbb;
border-radius: 3px;
box-shadow: inset 0 -1px 0 #bbb;
background-color: #ddd;
vertical-align: middle;
color: #555;
line-height: 10px;
font-size: 11px;
padding: 3px 5px;
}
@@ -0,0 +1,364 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.keybindings-editor {
padding: 11px 0px 0px 27px;
}
.keybindings-editor,
.keybindings-editor > .keybindings-body {
height: 100%;
}
/* header styling */
.keybindings-editor > .keybindings-header {
padding: 0px 10px 11px 0;
border-bottom: 1px solid #efeff2;
display: flex;
flex-wrap: wrap;
}
.vs-dark .keybindings-editor > .keybindings-header {
border-bottom: 1px solid #2d2d2d;
}
.keybindings-editor > .keybindings-header .search-container {
flex: 1;
}
.keybindings-editor > .keybindings-header .search-container > .settings-search-input {
vertical-align: middle;
}
.keybindings-editor > .keybindings-header .search-container > .settings-search-input > .monaco-inputbox {
height: 30px;
}
.keybindings-editor > .keybindings-header .search-container > .settings-search-input > .monaco-inputbox {
border: 1px solid #ddd;
}
.keybindings-editor > .keybindings-header .search-container > .settings-search-input > .monaco-inputbox .input {
font-size: 14px;
padding-left:10px;
}
/* body styling */
.keybindings-editor > .keybindings-body .open-keybindings-container {
margin-top: 10px;
color: #aaa;
}
.keybindings-editor > .keybindings-body .open-keybindings-container > .file-name {
text-decoration: underline;
cursor: pointer;
}
.keybindings-editor > .keybindings-body > .monaco-scrollable-element {
height: 100%;
overflow: hidden;
}
.keybindings-editor > .keybindings-body > .monaco-scrollable-element > .keybindings-scroll-container {
height: 100%;
overflow-y: scroll;
box-sizing: border-box;
margin-right: 10px;
}
.keybindings-editor > .keybindings-body .content details > summary {
cursor: pointer;
margin-bottom: 10px;
font-weight: bold;
font-size: 120%;
border-bottom: 1px solid rgba(128, 128, 128, 0.22);
padding: 6px 4px;
}
.keybindings-editor > .keybindings-body .content table {
width: 100%;
border-spacing: 0;
border-collapse: separate;
margin-top: 10px;
}
.keybindings-editor > .keybindings-body .content table tr {
display: flex;
}
.keybindings-editor > .keybindings-body .content table tr:focus {
outline: none;
}
.keybindings-editor > .keybindings-body .content table tr:nth-child(odd) {
background-color: rgba(130, 130, 130, 0.04);
}
.keybindings-editor > .keybindings-body .content details > summary:focus,
.keybindings-editor > .keybindings-body .content table tr:not(:first-child).focussed,
.keybindings-editor > .keybindings-body .content table tr:not(:first-child):hover {
background-color: rgba(128, 128, 128, 0.15);
}
.keybindings-editor > .keybindings-body .content .empty-message {
margin-left: 40px;
}
.keybindings-editor > .keybindings-body .content table th,
.keybindings-editor > .keybindings-body .content table td {
padding: 2px 16px 2px 4px;
align-items: center;
display: flex;
overflow: hidden;
}
.keybindings-editor > .keybindings-body .content table th.actions,
.keybindings-editor > .keybindings-body .content table td.actions {
width: 25px;
padding: 0px;
}
.keybindings-editor > .keybindings-body .content table th.command,
.keybindings-editor > .keybindings-body .content table td.command {
flex: 2.5;
flex-wrap: wrap;
}
.keybindings-editor > .keybindings-body .content table th.keybinding,
.keybindings-editor > .keybindings-body .content table td.keybinding {
flex: 2;
}
.keybindings-editor > .keybindings-body .content table th.when,
.keybindings-editor > .keybindings-body .content table td.when {
flex: 2;
}
.keybindings-editor > .keybindings-body .content table th.source,
.keybindings-editor > .keybindings-body .content table td.source {
flex: 0.5;
}
.keybindings-editor > .keybindings-body .content table th:last-child,
.keybindings-editor > .keybindings-body .content table td:last-child {
padding: 2px 4px;
}
.keybindings-editor > .keybindings-body .content table th {
text-align: left;
}
.keybindings-editor > .keybindings-body .content details {
margin-top: 20px;
}
.keybindings-editor > .keybindings-body .content table td .empty {
padding-left: 4px;
}
.keybindings-editor > .keybindings-body .content table > tr > td > .code {
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
font-size: 90%;
padding: 1px 4px;
opacity: 0.8;
}
.keybindings-editor > .keybindings-body .content table > tr > td > .code.strong {
background-color: rgba(128, 128, 128, 0.17);
border-radius: 4px;
}
.keybindings-editor > .keybindings-body .content table > tr > td > .code:not(:first-child) {
margin-left: 0.5em;
}
.keybindings-editor > .keybindings-body .content table td .highlight {
color: #007ACC;
font-weight: bold;
}
.vs-dark .keybindings-editor > .keybindings-body .content table td .highlight {
color: #0097FB;
}
.keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar {
display: none;
flex: 1;
}
.keybindings-editor > .keybindings-body .content table > tr.focussed > td.actions .monaco-action-bar,
.keybindings-editor > .keybindings-body .content table > tr:hover > td .monaco-action-bar {
display: flex;
}
.keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon {
width:16px;
height: 16px;
cursor: pointer;
margin-right: 4px;
}
.keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.edit {
background: url('edit.svg') center center no-repeat;
transform: rotate(-90deg);
}
.hc-black .keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.edit,
.vs-dark .keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.edit {
background: url('edit_inverse.svg') center center no-repeat;
}
.keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.add {
background: url('add.svg') center center no-repeat;
}
.hc-black .keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.add,
.vs-dark .keybindings-editor > .keybindings-body .content table > tr > td .monaco-action-bar .action-item > .icon.add {
background: url('add_inverse.svg') center center no-repeat;
}
/** List based styling **/
/*
.keybindings-editor > .keybindings-body > .keybindings-list-header,
.keybindings-editor > .keybindings-body .keybindings-list-container {
width: 100%;
border-spacing: 0;
border-collapse: separate;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header {
margin-top: 10px;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row {
display: flex;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row:focus {
outline:none;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row:nth-child(odd) {
background-color: rgba(130, 130, 130, 0.04);
}
.keybindings-editor > .keybindings-body .keybindings-list-container .monaco-list-row:not(:first-child).focussed,
.keybindings-editor > .keybindings-body .keybindings-list-container .monaco-list-row:not(:first-child):hover {
background-color: rgba(128, 128, 128, 0.15);
}
.keybindings-editor > .keybindings-body .keybindings-list-header > .header:last-child,
.keybindings-editor > .keybindings-body .keybindings-list-container .monaco-list-row > .column:last-child {
padding: 2px 4px;
}
.keybindings-editor > .keybindings-body .keybindings-list-header > .header {
text-align: left;
font-weight: bold;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .header,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .column {
padding: 2px 16px 2px 4px;
align-items: center;
display: flex;
overflow: hidden;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .actions,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .actions {
width: 25px;
padding: 0px;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .command,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command {
flex: 3;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .keybinding,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .keybinding {
flex: 2;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .source,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .source {
flex: 0.5;
}
.keybindings-editor > .keybindings-body > .keybindings-list-header .when,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .when {
flex: 1;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .empty {
padding-left: 4px;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code {
font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback";
font-size: 90%;
padding: 1px 4px;
opacity: 0.8;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code.strong {
background-color: rgba(128, 128, 128, 0.17);
border-radius: 4px;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code:not(:first-child) {
margin-left: 0.5em;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight {
color: #007ACC;
font-weight: bold;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight {
color: #0097FB;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar {
display: none;
flex: 1;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row.focussed > .column.actions .monaco-action-bar,
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row:hover > .column.actions .monaco-action-bar {
display: flex;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon {
width:16px;
height: 16px;
cursor: pointer;
margin-right: 4px;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.edit {
background: url('edit.svg') center center no-repeat;
transform: rotate(-90deg);
}
.hc-black .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.edit,
.vs-dark .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.edit {
background: url('edit_inverse.svg') center center no-repeat;
}
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.add {
background: url('add.svg') center center no-repeat;
}
.hc-black .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.add,
.vs-dark .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar .action-item > .icon.add {
background: url('add_inverse.svg') center center no-repeat;
}*/
@@ -15,6 +15,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { DefaultPreferencesEditorInput, PreferencesEditor, PreferencesEditorInput } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
import { KeybindingsEditor, KeybindingsEditorInput } from 'vs/workbench/parts/preferences/browser/keybindingsEditor';
import { OpenGlobalSettingsAction, OpenGlobalKeybindingsAction, OpenWorkspaceSettingsAction, ConfigureLanguageBasedSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
import { PreferencesService } from 'vs/workbench/parts/preferences/browser/preferencesService';
@@ -36,6 +37,18 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
]
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
new EditorDescriptor(
KeybindingsEditor.ID,
nls.localize('keybindingsEditor', "Keybindings Editor"),
'vs/workbench/parts/preferences/browser/keybindingsEditor',
'KeybindingsEditor'
),
[
new SyncDescriptor(KeybindingsEditorInput)
]
);
interface ISerializedPreferencesEditorInput {
name: string;
description: string;
@@ -98,6 +111,21 @@ class PreferencesEditorInputFactory implements IEditorInputFactory {
}
}
class KeybindingsEditorInputFactory implements IEditorInputFactory {
public serialize(editorInput: EditorInput): string {
const input = <KeybindingsEditorInput>editorInput;
return JSON.stringify({
name: input.getName(),
typeId: input.getTypeId()
});
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
return instantiationService.createInstance(KeybindingsEditorInput);
}
}
interface ISerializedDefaultPreferencesEditorInput {
resource: string;
@@ -123,6 +151,7 @@ class DefaultPreferencesEditorInputFactory implements IEditorInputFactory {
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(PreferencesEditorInput.ID, PreferencesEditorInputFactory);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(DefaultPreferencesEditorInput.ID, DefaultPreferencesEditorInputFactory);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(KeybindingsEditorInput.ID, KeybindingsEditorInputFactory);
// Contribute Global Actions
const category = nls.localize('preferences', "Preferences");
@@ -123,7 +123,10 @@ export class PreferencesEditor extends BaseEditor {
this.headerContainer = DOM.append(parentElement, DOM.$('.preferences-header'));
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer));
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer, {
ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"),
placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings")
}));
this._register(this.searchWidget.onDidChange(value => this.filterPreferences(value.trim())));
this._register(this.searchWidget.onEnter(value => this.preferencesRenderers.focusNextPreference()));
@@ -16,7 +16,7 @@ import { EditorInput, toResource } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { Position as EditorPosition, IEditor } from 'vs/platform/editor/common/editor';
import { IEditor } from 'vs/platform/editor/common/editor';
import { ICommonCodeEditor, IPosition } from 'vs/editor/common/editorCommon';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IStorageService } from 'vs/platform/storage/common/storage';
@@ -30,6 +30,7 @@ import { IPreferencesService, IPreferencesEditorModel, ISetting } from 'vs/workb
import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
import { KeybindingsEditorInput } from 'vs/workbench/parts/preferences/browser/keybindingsEditor';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { EditOperation } from 'vs/editor/common/core/editOperation';
@@ -164,18 +165,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic
}
openGlobalKeybindingSettings(): TPromise<void> {
const emptyContents = '// ' + nls.localize('emptyKeybindingsHeader', "Place your key bindings in this file to overwrite the defaults") + '\n[\n]';
const editableKeybindings = URI.file(this.environmentService.appKeybindingsPath);
return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true }).then(() => null);
// Create as needed and open in editor
return this.createIfNotExists(editableKeybindings, emptyContents).then(() => {
return this.editorService.openEditors([
{ input: { resource: this.defaultKeybindingsResource, options: { pinned: true }, label: nls.localize('defaultKeybindings', "Default Keybindings"), description: '' }, position: EditorPosition.ONE },
{ input: { resource: editableKeybindings, options: { pinned: true } }, position: EditorPosition.TWO },
]).then(() => {
this.editorGroupService.focusGroup(EditorPosition.TWO);
});
});
}
configureSettingsForLanguage(language: string): void {
@@ -13,7 +13,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference, IViewZone } from 'vs/editor/browser/editorBrowser';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { InputBox, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ISettingsGroup } from 'vs/workbench/parts/preferences/common/preferences';
@@ -238,10 +238,10 @@ export class SearchWidget extends Widget {
private _onEnter = this._register(new Emitter<void>());
public onEnter: Event<void> = this._onEnter.event;
constructor(parent: HTMLElement,
constructor(parent: HTMLElement, protected options: IInputOptions,
@IContextViewService private contextViewService: IContextViewService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
this.create(parent);
@@ -257,14 +257,15 @@ export class SearchWidget extends Widget {
private createSearchContainer(searchContainer: HTMLElement) {
this.searchContainer = searchContainer;
const searchInput = DOM.append(this.searchContainer, DOM.$('div.settings-search-input'));
this.inputBox = this._register(new InputBox(searchInput, this.contextViewService, {
ariaLabel: localize('SearchSettingsWidget.AriaLabel', "Search settings"),
placeholder: localize('SearchSettingsWidget.Placeholder', "Search Settings")
}));
this.inputBox = this.createInputBox(searchInput);
this.inputBox.onDidChange(value => this._onDidChange.fire(value));
this.onkeyup(this.inputBox.inputElement, (e) => this._onKeyUp(e));
}
protected createInputBox(parent: HTMLElement): InputBox {
return this._register(new InputBox(parent, this.contextViewService, this.options));
}
public showMessage(message: string, count: number): void {
this.countElement.textContent = message;
this.inputBox.inputElement.setAttribute('aria-label', message);
@@ -280,7 +281,6 @@ export class SearchWidget extends Widget {
DOM.removeClass(this.countElement, 'hide');
this.inputBox.inputElement.style.paddingRight = DOM.getTotalWidth(this.countElement) + 20 + 'px';
}
}
public focus() {
@@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMatch, IFilter, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters';
import { Registry } from 'vs/platform/platform';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommonEditorRegistry, EditorAction } from 'vs/editor/common/editorCommonExtensions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import { EditorModel } from 'vs/workbench/common/editor';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IKeybindingService, IKeybindingItem2, KeybindingSource } from 'vs/platform/keybinding/common/keybinding';
export interface IKeybindingItemEntry {
id: string;
keybindingItem: IKeybindingItem;
commandIdMatches?: IMatch[];
commandLabelMatches?: IMatch[];
keybindingMatches?: IMatch[];
}
export interface IKeybindingItem extends IKeybindingItem2 {
commandLabel: string;
}
const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString);
export class KeybindingsEditorModel extends EditorModel {
private _keybindingItems: IKeybindingItem[];
constructor(
@IKeybindingService private keybindingsService: IKeybindingService,
@IExtensionService private extensionService: IExtensionService
) {
super();
}
public fetch(searchValue: string): IKeybindingItemEntry[] {
searchValue = searchValue.trim();
return searchValue ? this.fetchKeybindingItems(searchValue) :
this._keybindingItems.map(keybindingItem => ({ id: KeybindingsEditorModel.getId(keybindingItem), keybindingItem }));
}
private fetchKeybindingItems(searchValue: string): IKeybindingItemEntry[] {
const result: IKeybindingItemEntry[] = [];
for (const keybindingItem of this._keybindingItems) {
let keybindingMatches: IMatch[] = keybindingItem.keybinding ? KeybindingsEditorModel.matches(searchValue, keybindingItem.keybinding.getAriaLabel(), or(matchesWords, matchesCamelCase)) : null;
let commandIdMatches: IMatch[] = KeybindingsEditorModel.matches(searchValue, keybindingItem.command, or(matchesWords, matchesCamelCase));
let commandLabelMatches: IMatch[] = keybindingItem.commandLabel ? KeybindingsEditorModel.matches(searchValue, keybindingItem.commandLabel, (word, wordToMatchAgainst) => matchesWords(word, keybindingItem.commandLabel, true)) : null;
if (keybindingMatches || commandIdMatches || commandLabelMatches) {
result.push({
id: KeybindingsEditorModel.getId(keybindingItem),
commandLabelMatches,
keybindingItem,
keybindingMatches,
commandIdMatches
});
}
}
return result;
}
public resolve(): TPromise<EditorModel> {
return this.extensionService.onReady()
.then(() => {
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
const editorActions = CommonEditorRegistry.getEditorActions().reduce((editorActions, editorAction) => {
editorActions[editorAction.id] = editorAction;
return editorActions;
}, {});
this._keybindingItems = this.keybindingsService.getKeybindings().map(keybinding => KeybindingsEditorModel.toKeybindingEntry(keybinding, workbenchActionsRegistry, editorActions));
const boundCommands = this._keybindingItems.reduce((boundCommands, keybinding) => {
boundCommands[keybinding.command] = true;
return boundCommands;
}, {});
const commandsMap = CommandsRegistry.getCommands();
for (const command in commandsMap) {
if (!boundCommands[command]) {
this._keybindingItems.push(KeybindingsEditorModel.toKeybindingEntry({
keybinding: null,
command,
when: null,
source: KeybindingSource.Default
}, workbenchActionsRegistry, editorActions));
}
}
this._keybindingItems = this._keybindingItems.sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b));
return this;
});
}
private static getId(keybindingItem: IKeybindingItem2): string {
return keybindingItem.command + (keybindingItem.keybinding ? keybindingItem.keybinding.getAriaLabel() : '') + keybindingItem.source + (keybindingItem.when ? keybindingItem.when.serialize() : '');
}
private static compareKeybindingData(a: IKeybindingItem, b: IKeybindingItem): number {
if (a.keybinding && !b.keybinding) {
return -1;
}
if (b.keybinding && !a.keybinding) {
return 1;
}
if (a.commandLabel && !b.commandLabel) {
return -1;
}
if (b.commandLabel && !a.commandLabel) {
return 1;
}
if (a.commandLabel && b.commandLabel) {
if (a.commandLabel !== b.commandLabel) {
return a.commandLabel.localeCompare(b.commandLabel);
}
}
if (a.command === b.command) {
return a.source === KeybindingSource.User ? -1 : 1;
}
return a.command.localeCompare(b.command);
}
private static toKeybindingEntry(keybinding: IKeybindingItem2, workbenchActionsRegistry: IWorkbenchActionRegistry, editorActions: {}): IKeybindingItem {
const workbenchAction = workbenchActionsRegistry.getWorkbenchAction(keybinding.command);
const editorAction: EditorAction = editorActions[keybinding.command];
return <IKeybindingItem>{
keybinding: keybinding.keybinding,
command: keybinding.command,
commandLabel: editorAction ? editorAction.label : workbenchAction ? workbenchAction.label : '',
when: keybinding.when,
source: keybinding.source,
category: editorAction ? localize('editorCategory', "Editor") : workbenchAction ? workbenchActionsRegistry.getCategory(workbenchAction.id) ? workbenchActionsRegistry.getCategory(workbenchAction.id) : null : null
};
}
private static matches(searchValue: string, wordToMatchAgainst: string, wordMatchesFilter: IFilter): IMatch[] {
let matches = wordFilter(searchValue, wordToMatchAgainst);
if (!matches) {
const words = searchValue.split(' ');
for (const word of words) {
const wordMatches = wordMatchesFilter(word, wordToMatchAgainst);
if (wordMatches) {
matches = [...(matches || []), ...wordMatches];
} else {
matches = null;
break;
}
}
}
if (matches) {
matches.sort((a, b) => {
return a.start - b.start;
});
}
return matches;
}
}
@@ -81,4 +81,7 @@ export interface IPreferencesService {
}
export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('settingsEditor', false);
export const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search';
export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey<boolean>('inKeybindingsEditor', false);
export const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search';
export const ACTION_DEFINE_KEYBINDING = 'kebindings.action.define';
@@ -0,0 +1,207 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { Queue } from 'vs/base/common/async';
import { IReference, Disposable } from 'vs/base/common/lifecycle';
import * as json from 'vs/base/common/json';
import { Edit } from 'vs/base/common/jsonFormatter';
import { setProperty } from 'vs/base/common/jsonEdit';
import Event, { Emitter } from 'vs/base/common/event';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { IKeybindingItem2, KeybindingSource, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextModelResolverService, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
export const IKeybindingEditingService = createDecorator<IKeybindingEditingService>('keybindingEditingService');
export interface IKeybindingEditingService {
_serviceBrand: ServiceIdentifier<any>;
editKeybinding(key: string, keybindingItem: IKeybindingItem2): TPromise<void>;
removeKeybinding(keybindingItem: IKeybindingItem2): TPromise<void>;
}
export class KeybindingsEditingService extends Disposable implements IKeybindingEditingService {
public _serviceBrand: any;
private queue: Queue<void>;
private resource: URI = URI.file(this.environmentService.appKeybindingsPath);
private _onUpdate = this._register(new Emitter<void>());
public readonly onUpdate: Event<void> = this._onUpdate.event;
constructor(
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@ITextFileService private textFileService: ITextFileService,
@IFileService private fileService: IFileService,
@IEnvironmentService private environmentService: IEnvironmentService
) {
super();
this.queue = new Queue<void>();
}
editKeybinding(key: string, keybindingItem: IKeybindingItem2): TPromise<void> {
return this.queue.queue(() => this.doEditKeybinding(key, keybindingItem)); // queue up writes to prevent race conditions
}
removeKeybinding(keybindingItem: IKeybindingItem2): TPromise<void> {
return this.queue.queue(() => this.doRemoveKeybinding(keybindingItem)); // queue up writes to prevent race conditions
}
private doEditKeybinding(key: string, keybindingItem: IKeybindingItem2): TPromise<void> {
return this.resolveAndValidate()
.then(reference => {
key = new RegExp(/\\/g).test(key) ? key.slice(0, -1) + '\\\\' : key;
const model = reference.object.textEditorModel;
if (keybindingItem.source === KeybindingSource.User) {
this.updateUserKeybinding(key, keybindingItem, model);
} else {
this.updateDefaultKeybinding(key, keybindingItem, model);
}
return this.save().then(() => reference.dispose());
});
}
private doRemoveKeybinding(keybindingItem: IKeybindingItem2): TPromise<void> {
return this.resolveAndValidate()
.then(reference => {
const model = reference.object.textEditorModel;
if (keybindingItem.source === KeybindingSource.User) {
this.removeUserKeybinding(keybindingItem, model);
} else {
this.removeDefaultKeybinding(keybindingItem, model);
}
return this.save().then(() => reference.dispose());
});
}
private save(): TPromise<any> {
return this.textFileService.save(this.resource).then(() => this._onUpdate.fire());
}
private updateUserKeybinding(newKey: string, keybindingItem: IKeybindingItem2, model: editorCommon.IModel): void {
const {tabSize, insertSpaces} = model.getOptions();
const eol = model.getEOL();
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
const userKeybindingEntry = this.findUserKeybindingEntry(keybindingItem, userKeybindingEntries);
if (userKeybindingEntry) {
this.applyEditsToBuffer(setProperty(model.getValue(), [userKeybindingEntries.indexOf(userKeybindingEntry), 'key'], newKey, { tabSize, insertSpaces, eol })[0], model);
}
}
private updateDefaultKeybinding(newKey: string, keybindingItem: IKeybindingItem2, model: editorCommon.IModel): void {
const {tabSize, insertSpaces} = model.getOptions();
const eol = model.getEOL();
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
const userKeybindingEntry = this.findUserKeybindingEntry(keybindingItem, userKeybindingEntries);
if (userKeybindingEntry) {
// Update the keybinding with new key
this.applyEditsToBuffer(setProperty(model.getValue(), [userKeybindingEntries.indexOf(userKeybindingEntry), 'key'], newKey, { tabSize, insertSpaces, eol })[0], model);
} else {
// Add the new keybinidng with new key
this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(newKey, keybindingItem.command, keybindingItem.when, false), { tabSize, insertSpaces, eol })[0], model);
}
if (keybindingItem.keybinding) {
// Unassign the default keybinding
this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(keybindingItem.keybinding.getUserSettingsLabel(), keybindingItem.command, keybindingItem.when, true), { tabSize, insertSpaces, eol })[0], model);
}
}
private removeUserKeybinding(keybindingItem: IKeybindingItem2, model: editorCommon.IModel): void {
const {tabSize, insertSpaces} = model.getOptions();
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
const userKeybindingEntry = this.findUserKeybindingEntry(keybindingItem, userKeybindingEntries);
if (userKeybindingEntry) {
userKeybindingEntries.splice(userKeybindingEntries.indexOf(userKeybindingEntry), 1);
model.setValue(JSON.stringify(userKeybindingEntries, null, insertSpaces ? strings.repeat(' ', tabSize) : '\t'));
}
}
private removeDefaultKeybinding(keybindingItem: IKeybindingItem2, model: editorCommon.IModel): void {
const {tabSize, insertSpaces} = model.getOptions();
const eol = model.getEOL();
this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(keybindingItem.keybinding.getUserSettingsLabel(), keybindingItem.command, keybindingItem.when, true), { tabSize, insertSpaces, eol })[0], model);
}
private findUserKeybindingEntry(keybindingItem: IKeybindingItem2, userKeybindingEntries: IUserFriendlyKeybinding[]): IUserFriendlyKeybinding {
return userKeybindingEntries.filter(keybinding => {
if (keybinding.command !== keybindingItem.command) {
return false;
}
if (!keybinding.when && !keybindingItem.when) {
return true;
}
if (keybinding.when && keybindingItem.when) {
return ContextKeyExpr.deserialize(keybinding.when).serialize() === keybindingItem.when.serialize();
}
return false;
})[0];
}
private asObject(key: string, command: string, when: ContextKeyExpr, negate: boolean): any {
const object = { key };
object['command'] = negate ? `-${command}` : command;
if (when) {
object['when'] = when.serialize();
}
return object;
}
private applyEditsToBuffer(edit: Edit, model: editorCommon.IModel): void {
const startPosition = model.getPositionAt(edit.offset);
const endPosition = model.getPositionAt(edit.offset + edit.length);
const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column);
let currentText = model.getValueInRange(range);
const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content);
model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []);
}
private resolveModelReference(): TPromise<IReference<ITextEditorModel>> {
return this.fileService.existsFile(this.resource)
.then(exists => {
const result = exists ? TPromise.as(null) : this.fileService.updateContent(this.resource, '{}', { encoding: 'utf8' });
return result.then(() => this.textModelResolverService.createModelReference(this.resource));
});
}
private resolveAndValidate(): TPromise<IReference<ITextEditorModel>> {
// Target cannot be dirty if not writing into buffer
if (this.textFileService.isDirty(this.resource)) {
return TPromise.wrapError(localize('errorKeybindingsFileDirty', "Unable to write because the file is dirty. Please save the **Keybindings** file and try again."));
}
return this.resolveModelReference()
.then(reference => {
const model = reference.object.textEditorModel;
if (this.hasParseErrors(model)) {
return TPromise.wrapError(localize('errorInvalidConfiguration', "Unable to write keybindings. Please open **Keybindings file** to correct errors/warnings in the file and try again."));
}
return reference;
});
}
private hasParseErrors(model: editorCommon.IModel): boolean {
const parseErrors: json.ParseError[] = [];
json.parse(model.getValue(), parseErrors, { allowTrailingComma: true });
return parseErrors.length > 0;
}
}