mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-12 11:39:57 +01:00
#18095 Implement first cut of keybindings customisation editor
This commit is contained in:
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user