SQL Operations Studio Public Preview 1 (0.23) release source code
298
src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/keybindings';
|
||||
import * as nls from 'vs/nls';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
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 { 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';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { editorWidgetBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
|
||||
class KeybindingInputWidget extends Widget {
|
||||
|
||||
private readonly inputBox: InputBox;
|
||||
|
||||
private _acceptChords: boolean;
|
||||
private _firstPart: ResolvedKeybinding;
|
||||
private _chordPart: ResolvedKeybinding;
|
||||
private _inputValue: string;
|
||||
|
||||
private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding, ResolvedKeybinding]>());
|
||||
public readonly onKeybinding: Event<[ResolvedKeybinding, 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;
|
||||
|
||||
private _onBlur = this._register(new Emitter<void>());
|
||||
public readonly onBlur: Event<void> = this._onBlur.event;
|
||||
|
||||
constructor(parent: HTMLElement, private options: IInputOptions,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this.inputBox = this._register(new InputBox(parent, this.contextViewService, this.options));
|
||||
this._register(attachInputBoxStyler(this.inputBox, themeService));
|
||||
this.onkeydown(this.inputBox.inputElement, e => this._onKeyDown(e));
|
||||
this.onblur(this.inputBox.inputElement, (e) => this._onBlur.fire());
|
||||
|
||||
this.oninput(this.inputBox.inputElement, (e) => {
|
||||
// Prevent other characters from showing up
|
||||
this.setInputValue(this._inputValue);
|
||||
});
|
||||
|
||||
this._acceptChords = true;
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
}
|
||||
|
||||
public setInputValue(value: string): void {
|
||||
this._inputValue = value;
|
||||
this.inputBox.value = this._inputValue;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.inputBox.focus();
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
}
|
||||
|
||||
public setAcceptChords(acceptChords: boolean) {
|
||||
this._acceptChords = acceptChords;
|
||||
this._chordPart = null;
|
||||
}
|
||||
|
||||
private _onKeyDown(keyboardEvent: IKeyboardEvent): void {
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
if (keyboardEvent.equals(KeyCode.Enter)) {
|
||||
this._onEnter.fire();
|
||||
return;
|
||||
}
|
||||
if (keyboardEvent.equals(KeyCode.Escape)) {
|
||||
this._onEscape.fire();
|
||||
return;
|
||||
}
|
||||
this.printKeybinding(keyboardEvent);
|
||||
}
|
||||
|
||||
private printKeybinding(keyboardEvent: IKeyboardEvent): void {
|
||||
const keybinding = this.keybindingService.resolveKeyboardEvent(keyboardEvent);
|
||||
const info = `code: ${keyboardEvent.browserEvent.code}, keyCode: ${keyboardEvent.browserEvent.keyCode}, key: ${keyboardEvent.browserEvent.key} => UI: ${keybinding.getAriaLabel()}, user settings: ${keybinding.getUserSettingsLabel()}, dispatch: ${keybinding.getDispatchParts()[0]}`;
|
||||
|
||||
if (this._acceptChords) {
|
||||
const hasFirstPart = (this._firstPart && this._firstPart.getDispatchParts()[0] !== null);
|
||||
const hasChordPart = (this._chordPart && this._chordPart.getDispatchParts()[0] !== null);
|
||||
if (hasFirstPart && hasChordPart) {
|
||||
// Reset
|
||||
this._firstPart = keybinding;
|
||||
this._chordPart = null;
|
||||
} else if (!hasFirstPart) {
|
||||
this._firstPart = keybinding;
|
||||
} else {
|
||||
this._chordPart = keybinding;
|
||||
}
|
||||
} else {
|
||||
this._firstPart = keybinding;
|
||||
}
|
||||
|
||||
let value = '';
|
||||
if (this._firstPart) {
|
||||
value = this._firstPart.getUserSettingsLabel();
|
||||
}
|
||||
if (this._chordPart) {
|
||||
value = value + ' ' + this._chordPart.getUserSettingsLabel();
|
||||
}
|
||||
this.setInputValue(value);
|
||||
|
||||
this.inputBox.inputElement.title = info;
|
||||
this._onKeybinding.fire([this._firstPart, this._chordPart]);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefineKeybindingWidget extends Widget {
|
||||
|
||||
private static WIDTH = 400;
|
||||
private static HEIGHT = 90;
|
||||
|
||||
private _domNode: FastDomNode<HTMLElement>;
|
||||
private _keybindingInputWidget: KeybindingInputWidget;
|
||||
private _outputNode: HTMLElement;
|
||||
|
||||
private _firstPart: ResolvedKeybinding = null;
|
||||
private _chordPart: ResolvedKeybinding = null;
|
||||
private _isVisible: boolean = false;
|
||||
|
||||
private _onHide = this._register(new Emitter<void>());
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this.create();
|
||||
if (parent) {
|
||||
dom.append(parent, this._domNode.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
get domNode(): HTMLElement {
|
||||
return this._domNode.domNode;
|
||||
}
|
||||
|
||||
define(): TPromise<string> {
|
||||
this._keybindingInputWidget.reset();
|
||||
return new TPromise<string>((c, e) => {
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
this._domNode.setDisplay('block');
|
||||
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
this._keybindingInputWidget.setInputValue('');
|
||||
dom.clearNode(this._outputNode);
|
||||
this._keybindingInputWidget.focus();
|
||||
}
|
||||
const disposable = this._onHide.event(() => {
|
||||
if (this._firstPart) {
|
||||
let r = this._firstPart.getUserSettingsLabel();
|
||||
if (this._chordPart) {
|
||||
r = r + ' ' + this._chordPart.getUserSettingsLabel();
|
||||
}
|
||||
c(r);
|
||||
} 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(): 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(this._domNode.domNode, dom.$('.message', null, nls.localize('defineKeybinding.initial', "Press desired key combination and ENTER. ESCAPE to cancel.")));
|
||||
|
||||
this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, widgetShadow }, colors => {
|
||||
this._domNode.domNode.style.backgroundColor = colors.editorWidgetBackground;
|
||||
|
||||
if (colors.widgetShadow) {
|
||||
this._domNode.domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`;
|
||||
} else {
|
||||
this._domNode.domNode.style.boxShadow = null;
|
||||
}
|
||||
}));
|
||||
|
||||
this._keybindingInputWidget = this._register(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(this._keybindingInputWidget.onBlur(() => this.onCancel()));
|
||||
|
||||
this._outputNode = dom.append(this._domNode.domNode, dom.$('.output'));
|
||||
}
|
||||
|
||||
private printKeybinding(keybinding: [ResolvedKeybinding, ResolvedKeybinding]): void {
|
||||
const [firstPart, chordPart] = keybinding;
|
||||
this._firstPart = firstPart;
|
||||
this._chordPart = chordPart;
|
||||
dom.clearNode(this._outputNode);
|
||||
new KeybindingLabel(this._outputNode, OS).set(this._firstPart, null);
|
||||
if (this._chordPart) {
|
||||
this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to")));
|
||||
new KeybindingLabel(this._outputNode, OS).set(this._chordPart, null);
|
||||
}
|
||||
}
|
||||
|
||||
private onCancel(): void {
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private hide(): void {
|
||||
this._domNode.setDisplay('none');
|
||||
this._isVisible = false;
|
||||
this._onHide.fire();
|
||||
}
|
||||
}
|
||||
|
||||
export class DefineKeybindingOverlayWidget extends Disposable implements IOverlayWidget {
|
||||
|
||||
private static ID = 'editor.contrib.defineKeybindingWidget';
|
||||
|
||||
private readonly _widget: DefineKeybindingWidget;
|
||||
|
||||
constructor(private _editor: ICodeEditor,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._widget = instantiationService.createInstance(DefineKeybindingWidget, null);
|
||||
this._editor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return DefineKeybindingOverlayWidget.ID;
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._widget.domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IOverlayWidgetPosition {
|
||||
return {
|
||||
preference: null
|
||||
};
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public start(): TPromise<string> {
|
||||
this._editor.revealPositionInCenterIfOutsideViewport(this._editor.getPosition(), ScrollType.Smooth);
|
||||
const layoutInfo = this._editor.getLayoutInfo();
|
||||
this._widget.layout(new Dimension(layoutInfo.width, layoutInfo.height));
|
||||
return this._widget.define();
|
||||
}
|
||||
}
|
||||
808
src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts
Normal file
@@ -0,0 +1,808 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/keybindingsEditor';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { OS } from 'vs/base/common/platform';
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { Builder, Dimension } from 'vs/base/browser/builder';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { ActionBar, Separator } 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 { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { KeybindingsEditorModel, IKeybindingItemEntry, IListEntry, KEYBINDING_ENTRY_TEMPLATE_ID, KEYBINDING_HEADER_TEMPLATE_ID } from 'vs/workbench/parts/preferences/common/keybindingsEditorModel';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { SearchWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
|
||||
import { DefineKeybindingWidget } from 'vs/workbench/parts/preferences/browser/keybindingWidgets';
|
||||
import {
|
||||
IPreferencesService, IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY,
|
||||
KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_SHOW_CONFLICTS
|
||||
} from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
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, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IChoiceService, IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { listHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
let $ = DOM.$;
|
||||
|
||||
export class KeybindingsEditorInput extends EditorInput {
|
||||
|
||||
public static ID: string = 'workbench.input.keybindings';
|
||||
public readonly keybindingsModel: KeybindingsEditorModel;
|
||||
|
||||
constructor( @IInstantiationService private instantiationService: IInstantiationService) {
|
||||
super();
|
||||
this.keybindingsModel = instantiationService.createInstance(KeybindingsEditorModel, OS);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return KeybindingsEditorInput.ID;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return localize('keybindingsInputName', "Keyboard Shortcuts");
|
||||
}
|
||||
|
||||
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 keybindingsEditorModel: KeybindingsEditorModel;
|
||||
|
||||
private headerContainer: HTMLElement;
|
||||
private searchWidget: SearchWidget;
|
||||
|
||||
private overlayContainer: HTMLElement;
|
||||
private defineKeybindingWidget: DefineKeybindingWidget;
|
||||
|
||||
private keybindingsListContainer: HTMLElement;
|
||||
private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry;
|
||||
private listEntries: IListEntry[];
|
||||
private keybindingsList: List<IListEntry>;
|
||||
|
||||
private dimension: Dimension;
|
||||
private delayedFiltering: Delayer<void>;
|
||||
private latestEmptyFilters: string[] = [];
|
||||
private delayedFilterLogging: Delayer<void>;
|
||||
private keybindingsEditorContextKey: IContextKey<boolean>;
|
||||
private keybindingFocusContextKey: IContextKey<boolean>;
|
||||
private searchFocusContextKey: IContextKey<boolean>;
|
||||
private sortByPrecedence: Checkbox;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IKeybindingService private keybindingsService: IKeybindingService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IKeybindingEditingService private keybindingEditingService: IKeybindingEditingService,
|
||||
@IListService private listService: IListService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IChoiceService private choiceService: IChoiceService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IClipboardService private clipboardService: IClipboardService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(KeybindingsEditor.ID, telemetryService, themeService);
|
||||
this.delayedFiltering = new Delayer<void>(300);
|
||||
this._register(keybindingsService.onDidUpdateKeybindings(() => this.render()));
|
||||
|
||||
this.keybindingsEditorContextKey = CONTEXT_KEYBINDINGS_EDITOR.bindTo(this.contextKeyService);
|
||||
this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);
|
||||
this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService);
|
||||
this.delayedFilterLogging = new Delayer<void>(1000);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(parentElement));
|
||||
this._register(focusTracker.addFocusListener(() => this.keybindingsEditorContextKey.set(true)));
|
||||
this._register(focusTracker.addBlurListener(() => this.keybindingsEditorContextKey.reset()));
|
||||
}
|
||||
|
||||
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.searchWidget.clear();
|
||||
this.keybindingsEditorContextKey.reset();
|
||||
this.keybindingFocusContextKey.reset();
|
||||
}
|
||||
|
||||
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';
|
||||
this.defineKeybindingWidget.layout(this.dimension);
|
||||
|
||||
this.layoutKebindingsList();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
const activeKeybindingEntry = this.activeKeybindingEntry;
|
||||
if (activeKeybindingEntry) {
|
||||
this.selectEntry(activeKeybindingEntry);
|
||||
} else {
|
||||
this.searchWidget.focus();
|
||||
}
|
||||
}
|
||||
|
||||
get activeKeybindingEntry(): IKeybindingItemEntry {
|
||||
const focusedElement = this.keybindingsList.getFocusedElements()[0];
|
||||
return focusedElement && focusedElement.templateId === KEYBINDING_ENTRY_TEMPLATE_ID ? <IKeybindingItemEntry>focusedElement : null;
|
||||
}
|
||||
|
||||
defineKeybinding(keybindingEntry: IKeybindingItemEntry): TPromise<any> {
|
||||
this.selectEntry(keybindingEntry);
|
||||
this.showOverlayContainer();
|
||||
return this.defineKeybindingWidget.define().then(key => {
|
||||
this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_DEFINE, keybindingEntry.keybindingItem.command, key);
|
||||
if (key) {
|
||||
return this.keybindingEditingService.editKeybinding(key, keybindingEntry.keybindingItem.keybindingItem)
|
||||
.then(() => {
|
||||
if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering
|
||||
this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}).then(() => {
|
||||
this.hideOverlayContainer();
|
||||
this.selectEntry(keybindingEntry);
|
||||
}, error => {
|
||||
this.hideOverlayContainer();
|
||||
this.onKeybindingEditingError(error);
|
||||
this.selectEntry(keybindingEntry);
|
||||
return error;
|
||||
});
|
||||
}
|
||||
|
||||
removeKeybinding(keybindingEntry: IKeybindingItemEntry): TPromise<any> {
|
||||
this.selectEntry(keybindingEntry);
|
||||
if (keybindingEntry.keybindingItem.keybinding) { // This should be a pre-condition
|
||||
this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_REMOVE, keybindingEntry.keybindingItem.command, keybindingEntry.keybindingItem.keybinding);
|
||||
return this.keybindingEditingService.removeKeybinding(keybindingEntry.keybindingItem.keybindingItem)
|
||||
.then(() => this.focus(),
|
||||
error => {
|
||||
this.onKeybindingEditingError(error);
|
||||
this.selectEntry(keybindingEntry);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
resetKeybinding(keybindingEntry: IKeybindingItemEntry): TPromise<any> {
|
||||
this.selectEntry(keybindingEntry);
|
||||
this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_RESET, keybindingEntry.keybindingItem.command, keybindingEntry.keybindingItem.keybinding);
|
||||
return this.keybindingEditingService.resetKeybinding(keybindingEntry.keybindingItem.keybindingItem)
|
||||
.then(() => {
|
||||
if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering
|
||||
this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry;
|
||||
}
|
||||
this.selectEntry(keybindingEntry);
|
||||
},
|
||||
error => {
|
||||
this.onKeybindingEditingError(error);
|
||||
this.selectEntry(keybindingEntry);
|
||||
});
|
||||
}
|
||||
|
||||
copyKeybinding(keybinding: IKeybindingItemEntry): TPromise<any> {
|
||||
this.selectEntry(keybinding);
|
||||
this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_COPY, keybinding.keybindingItem.command, keybinding.keybindingItem.keybinding);
|
||||
const userFriendlyKeybinding: IUserFriendlyKeybinding = {
|
||||
command: keybinding.keybindingItem.command,
|
||||
key: keybinding.keybindingItem.keybinding ? keybinding.keybindingItem.keybinding.getUserSettingsLabel() : ''
|
||||
};
|
||||
if (keybinding.keybindingItem.when) {
|
||||
userFriendlyKeybinding.when = keybinding.keybindingItem.when;
|
||||
}
|
||||
this.clipboardService.writeText(JSON.stringify(userFriendlyKeybinding, null, ' '));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
search(filter: string): void {
|
||||
this.searchWidget.focus();
|
||||
}
|
||||
|
||||
showConflicts(keybindingEntry: IKeybindingItemEntry): TPromise<any> {
|
||||
const value = `"${keybindingEntry.keybindingItem.keybinding.getAriaLabel()}"`;
|
||||
if (value !== this.searchWidget.getValue()) {
|
||||
this.searchWidget.setValue(value);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private createOverlayContainer(parent: HTMLElement): void {
|
||||
this.overlayContainer = DOM.append(parent, $('.overlay-container'));
|
||||
this.overlayContainer.style.position = 'absolute';
|
||||
this.overlayContainer.style.zIndex = '10';
|
||||
this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer));
|
||||
this.hideOverlayContainer();
|
||||
}
|
||||
|
||||
private showOverlayContainer() {
|
||||
this.overlayContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
private hideOverlayContainer() {
|
||||
this.overlayContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
private createHeader(parent: HTMLElement): void {
|
||||
this.headerContainer = DOM.append(parent, $('.keybindings-header'));
|
||||
|
||||
const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
|
||||
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
|
||||
ariaLabel: localize('SearchKeybindings.AriaLabel', "Search keybindings"),
|
||||
placeholder: localize('SearchKeybindings.Placeholder', "Search keybindings"),
|
||||
focusKey: this.searchFocusContextKey
|
||||
}));
|
||||
this._register(this.searchWidget.onDidChange(searchValue => this.delayedFiltering.trigger(() => this.filterKeybindings())));
|
||||
|
||||
this.sortByPrecedence = this._register(new Checkbox({
|
||||
actionClassName: 'sort-by-precedence',
|
||||
isChecked: false,
|
||||
onChange: () => this.renderKeybindingsEntries(false),
|
||||
title: localize('sortByPrecedene', "Sort by Precedence")
|
||||
}));
|
||||
searchContainer.appendChild(this.sortByPrecedence.domNode);
|
||||
|
||||
this.createOpenKeybindingsElement(this.headerContainer);
|
||||
}
|
||||
|
||||
private createOpenKeybindingsElement(parent: HTMLElement): void {
|
||||
const openKeybindingsContainer = DOM.append(parent, $('.open-keybindings-container'));
|
||||
DOM.append(openKeybindingsContainer, $('', null, localize('header-message', "For advanced customizations open and edit")));
|
||||
const fileElement = DOM.append(openKeybindingsContainer, $('.file-name', null, localize('keybindings-file-name', "keybindings.json")));
|
||||
fileElement.tabIndex = 0;
|
||||
|
||||
this._register(DOM.addDisposableListener(fileElement, DOM.EventType.CLICK, () => this.preferencesService.openGlobalKeybindingSettings(true)));
|
||||
this._register(DOM.addDisposableListener(fileElement, DOM.EventType.KEY_UP, e => {
|
||||
let keyboardEvent = new StandardKeyboardEvent(e);
|
||||
switch (keyboardEvent.keyCode) {
|
||||
case KeyCode.Enter:
|
||||
this.preferencesService.openGlobalKeybindingSettings(true);
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private createBody(parent: HTMLElement): void {
|
||||
const bodyContainer = DOM.append(parent, $('.keybindings-body'));
|
||||
this.createList(bodyContainer);
|
||||
}
|
||||
|
||||
private createList(parent: HTMLElement): void {
|
||||
this.keybindingsListContainer = DOM.append(parent, $('.keybindings-list-container'));
|
||||
|
||||
this.keybindingsList = this._register(new List<IListEntry>(this.keybindingsListContainer, new Delegate(), [new KeybindingHeaderRenderer(), new KeybindingItemRenderer(this, this.keybindingsService)],
|
||||
{ identityProvider: e => e.id, keyboardSupport: false, mouseSupport: true, ariaLabel: localize('keybindingsLabel', "Keybindings") }));
|
||||
this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e)));
|
||||
this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e)));
|
||||
this._register(this.keybindingsList.onDOMFocus(() => {
|
||||
DOM.addClass(this.keybindingsList.getHTMLElement(), 'focused');
|
||||
}));
|
||||
this._register(this.keybindingsList.onDOMBlur(() => {
|
||||
DOM.removeClass(this.keybindingsList.getHTMLElement(), 'focused');
|
||||
this.keybindingFocusContextKey.reset();
|
||||
}));
|
||||
|
||||
this._register(attachListStyler(this.keybindingsList, this.themeService));
|
||||
this._register(this.listService.register(this.keybindingsList));
|
||||
}
|
||||
|
||||
private render(): TPromise<any> {
|
||||
if (this.input) {
|
||||
return this.input.resolve()
|
||||
.then((keybindingsModel: KeybindingsEditorModel) => this.keybindingsEditorModel = keybindingsModel)
|
||||
.then(() => this.keybindingsEditorModel.resolve())
|
||||
.then(() => this.renderKeybindingsEntries(false));
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private filterKeybindings(): void {
|
||||
this.renderKeybindingsEntries(this.searchWidget.hasFocus());
|
||||
this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(this.searchWidget.getValue()));
|
||||
}
|
||||
|
||||
private renderKeybindingsEntries(reset: boolean): void {
|
||||
if (this.keybindingsEditorModel) {
|
||||
const filter = this.searchWidget.getValue();
|
||||
const keybindingsEntries: IKeybindingItemEntry[] = this.keybindingsEditorModel.fetch(filter, this.sortByPrecedence.checked);
|
||||
if (keybindingsEntries.length === 0) {
|
||||
this.latestEmptyFilters.push(filter);
|
||||
}
|
||||
const currentSelectedIndex = this.keybindingsList.getSelection()[0];
|
||||
this.listEntries = [{ id: 'keybinding-header-entry', templateId: KEYBINDING_HEADER_TEMPLATE_ID }, ...keybindingsEntries];
|
||||
this.keybindingsList.splice(0, this.keybindingsList.length, this.listEntries);
|
||||
this.layoutKebindingsList();
|
||||
|
||||
if (reset) {
|
||||
this.keybindingsList.setSelection([]);
|
||||
this.keybindingsList.setFocus([]);
|
||||
} else {
|
||||
if (this.unAssignedKeybindingItemToRevealAndFocus) {
|
||||
const index = this.getNewIndexOfUnassignedKeybinding(this.unAssignedKeybindingItemToRevealAndFocus);
|
||||
if (index !== -1) {
|
||||
this.keybindingsList.reveal(index, 0.2);
|
||||
this.selectEntry(index);
|
||||
}
|
||||
this.unAssignedKeybindingItemToRevealAndFocus = null;
|
||||
} else if (currentSelectedIndex !== -1 && currentSelectedIndex < this.listEntries.length) {
|
||||
this.selectEntry(currentSelectedIndex);
|
||||
} else if (this.editorService.getActiveEditor() === this) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private layoutKebindingsList(): void {
|
||||
const listHeight = this.dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);
|
||||
this.keybindingsListContainer.style.height = `${listHeight}px`;
|
||||
this.keybindingsList.layout(listHeight);
|
||||
}
|
||||
|
||||
private getIndexOf(listEntry: IListEntry): number {
|
||||
const index = this.listEntries.indexOf(listEntry);
|
||||
if (index === -1) {
|
||||
for (let i = 0; i < this.listEntries.length; i++) {
|
||||
if (this.listEntries[i].id === listEntry.id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private getNewIndexOfUnassignedKeybinding(unassignedKeybinding: IKeybindingItemEntry): number {
|
||||
for (let index = 0; index < this.listEntries.length; index++) {
|
||||
const entry = this.listEntries[index];
|
||||
if (entry.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {
|
||||
const keybindingItemEntry = (<IKeybindingItemEntry>entry);
|
||||
if (keybindingItemEntry.keybindingItem.command === unassignedKeybinding.keybindingItem.command) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private selectEntry(keybindingItemEntry: IKeybindingItemEntry | number): void {
|
||||
const index = typeof keybindingItemEntry === 'number' ? keybindingItemEntry : this.getIndexOf(keybindingItemEntry);
|
||||
if (index !== -1) {
|
||||
this.keybindingsList.getHTMLElement().focus();
|
||||
this.keybindingsList.setFocus([index]);
|
||||
this.keybindingsList.setSelection([index]);
|
||||
}
|
||||
}
|
||||
|
||||
focusKeybindings(): void {
|
||||
this.keybindingsList.getHTMLElement().focus();
|
||||
const currentFocusIndices = this.keybindingsList.getFocus();
|
||||
this.keybindingsList.setFocus([currentFocusIndices.length ? currentFocusIndices[0] : 0]);
|
||||
}
|
||||
|
||||
private onContextMenu(e: IListContextMenuEvent<IListEntry>): void {
|
||||
if (e.element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {
|
||||
this.selectEntry(<IKeybindingItemEntry>e.element);
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => TPromise.as([
|
||||
this.createCopyAction(<IKeybindingItemEntry>e.element),
|
||||
new Separator(),
|
||||
this.createDefineAction(<IKeybindingItemEntry>e.element),
|
||||
this.createRemoveAction(<IKeybindingItemEntry>e.element),
|
||||
this.createResetAction(<IKeybindingItemEntry>e.element),
|
||||
new Separator(),
|
||||
this.createShowConflictsAction(<IKeybindingItemEntry>e.element)])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onFocusChange(e: IListEvent<IListEntry>): void {
|
||||
this.keybindingFocusContextKey.reset();
|
||||
const element = e.elements[0];
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
if (element.templateId === KEYBINDING_HEADER_TEMPLATE_ID) {
|
||||
this.keybindingsList.focusNext();
|
||||
return;
|
||||
}
|
||||
if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {
|
||||
this.keybindingFocusContextKey.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
private createDefineAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
|
||||
return <IAction>{
|
||||
label: keybindingItemEntry.keybindingItem.keybinding ? localize('changeLabel', "Change Keybinding") : localize('addLabel', "Add Keybinding"),
|
||||
enabled: true,
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_DEFINE,
|
||||
run: () => this.defineKeybinding(keybindingItemEntry)
|
||||
};
|
||||
}
|
||||
|
||||
private createRemoveAction(keybindingItem: IKeybindingItemEntry): IAction {
|
||||
return <IAction>{
|
||||
label: localize('removeLabel', "Remove Keybinding"),
|
||||
enabled: !!keybindingItem.keybindingItem.keybinding,
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_REMOVE,
|
||||
run: () => this.removeKeybinding(keybindingItem)
|
||||
};
|
||||
}
|
||||
|
||||
private createResetAction(keybindingItem: IKeybindingItemEntry): IAction {
|
||||
return <IAction>{
|
||||
label: localize('resetLabel', "Reset Keybinding"),
|
||||
enabled: !keybindingItem.keybindingItem.keybindingItem.isDefault,
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_RESET,
|
||||
run: () => this.resetKeybinding(keybindingItem)
|
||||
};
|
||||
}
|
||||
|
||||
private createShowConflictsAction(keybindingItem: IKeybindingItemEntry): IAction {
|
||||
return <IAction>{
|
||||
label: localize('showConflictsLabel', "Show Conflicts"),
|
||||
enabled: !!keybindingItem.keybindingItem.keybinding,
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SHOW_CONFLICTS,
|
||||
run: () => this.showConflicts(keybindingItem)
|
||||
};
|
||||
}
|
||||
|
||||
private createCopyAction(keybindingItem: IKeybindingItemEntry): IAction {
|
||||
return <IAction>{
|
||||
label: localize('copyLabel', "Copy"),
|
||||
enabled: true,
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_COPY,
|
||||
run: () => this.copyKeybinding(keybindingItem)
|
||||
};
|
||||
}
|
||||
|
||||
private reportFilteringUsed(filter: string): void {
|
||||
if (filter) {
|
||||
let data = {
|
||||
filter,
|
||||
emptyFilters: this.getLatestEmptyFiltersForTelemetry()
|
||||
};
|
||||
this.latestEmptyFilters = [];
|
||||
this.telemetryService.publicLog('keybindings.filter', data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a rough limit on the size of the telemetry data, since otherwise it could be an unbounded large amount
|
||||
* of data. 8192 is the max size of a property value. This is rough since that probably includes ""s, etc.
|
||||
*/
|
||||
private getLatestEmptyFiltersForTelemetry(): string[] {
|
||||
let cumulativeSize = 0;
|
||||
return this.latestEmptyFilters.filter(filterText => (cumulativeSize += filterText.length) <= 8192);
|
||||
}
|
||||
|
||||
private reportKeybindingAction(action: string, command: string, keybinding: ResolvedKeybinding | string): void {
|
||||
this.telemetryService.publicLog(action, { command, keybinding: keybinding ? (typeof keybinding === 'string' ? keybinding : keybinding.getUserSettingsLabel()) : '' });
|
||||
}
|
||||
|
||||
private onKeybindingEditingError(error: any): void {
|
||||
this.messageService.show(Severity.Error, typeof error === 'string' ? error : localize('error', "Error '{0}' while editing keybinding. Please open 'keybindings.json' file and check.", `${error}`));
|
||||
}
|
||||
}
|
||||
|
||||
class Delegate implements IDelegate<IListEntry> {
|
||||
|
||||
getHeight(element: IListEntry) {
|
||||
if (element.templateId === KEYBINDING_ENTRY_TEMPLATE_ID) {
|
||||
const commandIdMatched = (<IKeybindingItemEntry>element).keybindingItem.commandLabel && (<IKeybindingItemEntry>element).commandIdMatches;
|
||||
const commandDefaultLabelMatched = !!(<IKeybindingItemEntry>element).commandDefaultLabelMatches;
|
||||
if (commandIdMatched && commandDefaultLabelMatched) {
|
||||
return 60;
|
||||
}
|
||||
if (commandIdMatched || commandDefaultLabelMatched) {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
if (element.templateId === KEYBINDING_HEADER_TEMPLATE_ID) {
|
||||
return 30;
|
||||
}
|
||||
return 24;
|
||||
}
|
||||
|
||||
getTemplateId(element: IListEntry) {
|
||||
return element.templateId;
|
||||
}
|
||||
}
|
||||
|
||||
interface KeybindingItemTemplate {
|
||||
parent: HTMLElement;
|
||||
actions: ActionsColumn;
|
||||
command: CommandColumn;
|
||||
keybinding: KeybindingColumn;
|
||||
source: SourceColumn;
|
||||
when: WhenColumn;
|
||||
}
|
||||
|
||||
class KeybindingHeaderRenderer implements IRenderer<IListEntry, any> {
|
||||
|
||||
get templateId(): string { return KEYBINDING_HEADER_TEMPLATE_ID; }
|
||||
|
||||
constructor() { }
|
||||
|
||||
renderTemplate(container: HTMLElement): any {
|
||||
DOM.addClass(container, 'keybindings-list-header');
|
||||
DOM.append(container,
|
||||
$('.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")));
|
||||
return {};
|
||||
}
|
||||
|
||||
renderElement(entry: IListEntry, index: number, template: any): void {
|
||||
}
|
||||
|
||||
disposeTemplate(template: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
class KeybindingItemRenderer implements IRenderer<IKeybindingItemEntry, KeybindingItemTemplate> {
|
||||
|
||||
get templateId(): string { return KEYBINDING_ENTRY_TEMPLATE_ID; }
|
||||
|
||||
constructor(private keybindingsEditor: IKeybindingsEditor, private keybindingsService: IKeybindingService) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): KeybindingItemTemplate {
|
||||
DOM.addClass(container, 'keybinding-item');
|
||||
const actions = new ActionsColumn(container, this.keybindingsEditor, this.keybindingsService);
|
||||
const command = new CommandColumn(container, this.keybindingsEditor);
|
||||
const keybinding = new KeybindingColumn(container, this.keybindingsEditor);
|
||||
const source = new SourceColumn(container, this.keybindingsEditor);
|
||||
const when = new WhenColumn(container, this.keybindingsEditor);
|
||||
container.setAttribute('aria-labelledby', [command.id, keybinding.id, source.id, when.id].join(' '));
|
||||
return {
|
||||
parent: container,
|
||||
actions,
|
||||
command,
|
||||
keybinding,
|
||||
source,
|
||||
when
|
||||
};
|
||||
}
|
||||
|
||||
renderElement(keybindingEntry: IKeybindingItemEntry, index: number, template: KeybindingItemTemplate): void {
|
||||
DOM.toggleClass(template.parent, 'even', index % 2 === 0);
|
||||
template.actions.render(keybindingEntry);
|
||||
template.command.render(keybindingEntry);
|
||||
template.keybinding.render(keybindingEntry);
|
||||
template.source.render(keybindingEntry);
|
||||
template.when.render(keybindingEntry);
|
||||
}
|
||||
|
||||
disposeTemplate(template: KeybindingItemTemplate): void {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Column {
|
||||
|
||||
static COUNTER = 0;
|
||||
|
||||
protected element: HTMLElement;
|
||||
readonly id: string;
|
||||
|
||||
constructor(protected parent: HTMLElement, protected keybindingsEditor: IKeybindingsEditor) {
|
||||
this.element = this.create(parent);
|
||||
this.id = this.element.getAttribute('id');
|
||||
}
|
||||
|
||||
abstract create(parent: HTMLElement): HTMLElement;
|
||||
}
|
||||
|
||||
class ActionsColumn extends Column {
|
||||
|
||||
private actionBar: ActionBar;
|
||||
|
||||
constructor(parent: HTMLElement, keybindingsEditor: IKeybindingsEditor, private keybindingsService: IKeybindingService) {
|
||||
super(parent, keybindingsEditor);
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const actionsContainer = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER }));
|
||||
this.actionBar = new ActionBar(actionsContainer, { animated: false });
|
||||
return actionsContainer;
|
||||
}
|
||||
|
||||
render(keybindingItemEntry: IKeybindingItemEntry): void {
|
||||
this.actionBar.clear();
|
||||
const actions = [];
|
||||
if (keybindingItemEntry.keybindingItem.keybinding) {
|
||||
actions.push(this.createEditAction(keybindingItemEntry));
|
||||
} else {
|
||||
actions.push(this.createAddAction(keybindingItemEntry));
|
||||
}
|
||||
this.actionBar.push(actions, { icon: true });
|
||||
}
|
||||
|
||||
private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
|
||||
const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);
|
||||
return <IAction>{
|
||||
class: 'edit',
|
||||
enabled: true,
|
||||
id: 'editKeybinding',
|
||||
tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"),
|
||||
run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry)
|
||||
};
|
||||
}
|
||||
|
||||
private createAddAction(keybindingItemEntry: IKeybindingItemEntry): IAction {
|
||||
const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE);
|
||||
return <IAction>{
|
||||
class: 'add',
|
||||
enabled: true,
|
||||
id: 'addKeybinding',
|
||||
tooltip: keybinding ? localize('addKeybindingLabelWithKey', "Add Keybinding {0}", `(${keybinding.getLabel()})`) : localize('addKeybindingLabel', "Add Keybinding"),
|
||||
run: () => this.keybindingsEditor.defineKeybinding(keybindingItemEntry)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CommandColumn extends Column {
|
||||
|
||||
private commandColumn: HTMLElement;
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER }));
|
||||
return this.commandColumn;
|
||||
}
|
||||
|
||||
render(keybindingItemEntry: IKeybindingItemEntry): void {
|
||||
DOM.clearNode(this.commandColumn);
|
||||
const keybindingItem = keybindingItemEntry.keybindingItem;
|
||||
const commandIdMatched = !!(keybindingItem.commandLabel && keybindingItemEntry.commandIdMatches);
|
||||
const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches;
|
||||
DOM.toggleClass(this.commandColumn, 'vertical-align-column', commandIdMatched || commandDefaultLabelMatched);
|
||||
this.commandColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry));
|
||||
if (keybindingItem.commandLabel) {
|
||||
const commandLabel = new HighlightedLabel(this.commandColumn);
|
||||
commandLabel.set(keybindingItem.commandLabel, keybindingItemEntry.commandLabelMatches);
|
||||
commandLabel.element.title = keybindingItem.command;
|
||||
}
|
||||
if (keybindingItemEntry.commandDefaultLabelMatches) {
|
||||
new HighlightedLabel(DOM.append(this.commandColumn, $('.command-default-label'))).set(keybindingItem.commandDefaultLabel, keybindingItemEntry.commandDefaultLabelMatches);
|
||||
}
|
||||
if (keybindingItemEntry.commandIdMatches || !keybindingItem.commandLabel) {
|
||||
new HighlightedLabel(DOM.append(this.commandColumn, $('.code'))).set(keybindingItem.command, keybindingItemEntry.commandIdMatches);
|
||||
}
|
||||
}
|
||||
|
||||
private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string {
|
||||
return localize('commandAriaLabel', "Command is {0}.", keybindingItemEntry.keybindingItem.commandLabel ? keybindingItemEntry.keybindingItem.commandLabel : keybindingItemEntry.keybindingItem.command);
|
||||
}
|
||||
}
|
||||
|
||||
class KeybindingColumn extends Column {
|
||||
|
||||
private keybindingColumn: HTMLElement;
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
this.keybindingColumn = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER }));
|
||||
return this.keybindingColumn;
|
||||
}
|
||||
|
||||
render(keybindingItemEntry: IKeybindingItemEntry): void {
|
||||
DOM.clearNode(this.keybindingColumn);
|
||||
this.keybindingColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry));
|
||||
if (keybindingItemEntry.keybindingItem.keybinding) {
|
||||
new KeybindingLabel(this.keybindingColumn, OS).set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches);
|
||||
}
|
||||
}
|
||||
|
||||
private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string {
|
||||
return keybindingItemEntry.keybindingItem.keybinding ? localize('keybindingAriaLabel', "Keybinding is {0}.", keybindingItemEntry.keybindingItem.keybinding.getAriaLabel()) : localize('noKeybinding', "No Keybinding assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
class SourceColumn extends Column {
|
||||
|
||||
private sourceColumn: HTMLElement;
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER }));
|
||||
return this.sourceColumn;
|
||||
}
|
||||
|
||||
render(keybindingItemEntry: IKeybindingItemEntry): void {
|
||||
DOM.clearNode(this.sourceColumn);
|
||||
this.sourceColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry));
|
||||
new HighlightedLabel(this.sourceColumn).set(keybindingItemEntry.keybindingItem.source, keybindingItemEntry.sourceMatches);
|
||||
}
|
||||
|
||||
private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string {
|
||||
return localize('sourceAriaLabel', "Source is {0}.", keybindingItemEntry.keybindingItem.source);
|
||||
}
|
||||
}
|
||||
|
||||
class WhenColumn extends Column {
|
||||
|
||||
private whenColumn: HTMLElement;
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
const column = DOM.append(parent, $('.column.when'));
|
||||
this.whenColumn = DOM.append(column, $('div', { id: 'when_' + ++Column.COUNTER }));
|
||||
return this.whenColumn;
|
||||
}
|
||||
|
||||
render(keybindingItemEntry: IKeybindingItemEntry): void {
|
||||
DOM.clearNode(this.whenColumn);
|
||||
this.whenColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry));
|
||||
DOM.toggleClass(this.whenColumn, 'code', !!keybindingItemEntry.keybindingItem.when);
|
||||
DOM.toggleClass(this.whenColumn, 'empty', !keybindingItemEntry.keybindingItem.when);
|
||||
if (keybindingItemEntry.keybindingItem.when) {
|
||||
new HighlightedLabel(this.whenColumn).set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches);
|
||||
this.whenColumn.title = keybindingItemEntry.keybindingItem.when;
|
||||
} else {
|
||||
this.whenColumn.textContent = '—';
|
||||
}
|
||||
}
|
||||
|
||||
private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string {
|
||||
return keybindingItemEntry.keybindingItem.when ? localize('whenAriaLabel', "When is {0}.", keybindingItemEntry.keybindingItem.when) : localize('noWhen', "No when context.");
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const listHighlightForegroundColor = theme.getColor(listHighlightForeground);
|
||||
if (listHighlightForegroundColor) {
|
||||
collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight { color: ${listHighlightForegroundColor}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,394 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { KeyCode, KeyMod, KeyChord, SimpleKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { SmartSnippetInserter } from 'vs/workbench/parts/preferences/common/smartSnippetInserter';
|
||||
import { DefineKeybindingOverlayWidget } from 'vs/workbench/parts/preferences/browser/keybindingWidgets';
|
||||
import { FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
|
||||
import { parseTree, Node } from 'vs/base/common/json';
|
||||
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
|
||||
import { ScanCodeBinding } from 'vs/workbench/services/keybinding/common/scanCode';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { WindowsNativeResolvedKeybinding } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper';
|
||||
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry';
|
||||
|
||||
const NLS_LAUNCH_MESSAGE = nls.localize('defineKeybinding.start', "Define Keybinding");
|
||||
const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutErrorMessage', "You won't be able to produce this key combination under your current keyboard layout.");
|
||||
|
||||
const INTERESTING_FILE = /keybindings\.json$/;
|
||||
|
||||
@editorContribution
|
||||
export class DefineKeybindingController extends Disposable implements editorCommon.IEditorContribution {
|
||||
|
||||
private static ID = 'editor.contrib.defineKeybinding';
|
||||
|
||||
public static get(editor: editorCommon.ICommonCodeEditor): DefineKeybindingController {
|
||||
return editor.getContribution<DefineKeybindingController>(DefineKeybindingController.ID);
|
||||
}
|
||||
|
||||
private _keybindingWidgetRenderer: KeybindingWidgetRenderer;
|
||||
private _keybindingDecorationRenderer: KeybindingEditorDecorationsRenderer;
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._keybindingWidgetRenderer = null;
|
||||
this._keybindingDecorationRenderer = null;
|
||||
|
||||
this._register(this._editor.onDidChangeModel(e => this._update()));
|
||||
this._update();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return DefineKeybindingController.ID;
|
||||
}
|
||||
|
||||
public get keybindingWidgetRenderer(): KeybindingWidgetRenderer {
|
||||
return this._keybindingWidgetRenderer;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeKeybindingWidgetRenderer();
|
||||
this._disposeKeybindingDecorationRenderer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
if (!isInterestingEditorModel(this._editor)) {
|
||||
this._disposeKeybindingWidgetRenderer();
|
||||
this._disposeKeybindingDecorationRenderer();
|
||||
return;
|
||||
}
|
||||
|
||||
// Decorations are shown for the default keybindings.json **and** for the user keybindings.json
|
||||
this._createKeybindingDecorationRenderer();
|
||||
|
||||
// The button to define keybindings is shown only for the user keybindings.json
|
||||
if (!this._editor.getConfiguration().readOnly) {
|
||||
this._createKeybindingWidgetRenderer();
|
||||
} else {
|
||||
this._disposeKeybindingWidgetRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
private _createKeybindingWidgetRenderer(): void {
|
||||
if (!this._keybindingWidgetRenderer) {
|
||||
this._keybindingWidgetRenderer = this._instantiationService.createInstance(KeybindingWidgetRenderer, this._editor);
|
||||
}
|
||||
}
|
||||
|
||||
private _disposeKeybindingWidgetRenderer(): void {
|
||||
if (this._keybindingWidgetRenderer) {
|
||||
this._keybindingWidgetRenderer.dispose();
|
||||
this._keybindingWidgetRenderer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _createKeybindingDecorationRenderer(): void {
|
||||
if (!this._keybindingDecorationRenderer) {
|
||||
this._keybindingDecorationRenderer = this._instantiationService.createInstance(KeybindingEditorDecorationsRenderer, this._editor);
|
||||
}
|
||||
}
|
||||
|
||||
private _disposeKeybindingDecorationRenderer(): void {
|
||||
if (this._keybindingDecorationRenderer) {
|
||||
this._keybindingDecorationRenderer.dispose();
|
||||
this._keybindingDecorationRenderer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingWidgetRenderer extends Disposable {
|
||||
|
||||
private _launchWidget: FloatingClickWidget;
|
||||
private _defineWidget: DefineKeybindingOverlayWidget;
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this._launchWidget = this._register(this._instantiationService.createInstance(FloatingClickWidget, this._editor, NLS_LAUNCH_MESSAGE, DefineKeybindingCommand.ID));
|
||||
this._register(this._launchWidget.onClick(() => this.showDefineKeybindingWidget()));
|
||||
this._defineWidget = this._register(this._instantiationService.createInstance(DefineKeybindingOverlayWidget, this._editor));
|
||||
|
||||
this._launchWidget.render();
|
||||
}
|
||||
|
||||
public showDefineKeybindingWidget(): void {
|
||||
this._defineWidget.start().then(keybinding => this._onAccepted(keybinding));
|
||||
}
|
||||
|
||||
private _onAccepted(keybinding: string): void {
|
||||
this._editor.focus();
|
||||
if (keybinding) {
|
||||
let regexp = new RegExp(/\\/g);
|
||||
let backslash = regexp.test(keybinding);
|
||||
if (backslash) {
|
||||
keybinding = keybinding.slice(0, -1) + '\\\\';
|
||||
}
|
||||
let snippetText = [
|
||||
'{',
|
||||
'\t"key": ' + JSON.stringify(keybinding) + ',',
|
||||
'\t"command": "${1:commandId}",',
|
||||
'\t"when": "${2:editorTextFocus}"',
|
||||
'}$0'
|
||||
].join('\n');
|
||||
|
||||
let smartInsertInfo = SmartSnippetInserter.insertSnippet(this._editor.getModel(), this._editor.getPosition());
|
||||
snippetText = smartInsertInfo.prepend + snippetText + smartInsertInfo.append;
|
||||
this._editor.setPosition(smartInsertInfo.position);
|
||||
|
||||
SnippetController2.get(this._editor).insert(snippetText, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingEditorDecorationsRenderer extends Disposable {
|
||||
|
||||
private _updateDecorations: RunOnceScheduler;
|
||||
private _dec: string[] = [];
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._updateDecorations = this._register(new RunOnceScheduler(() => this._updateDecorationsNow(), 500));
|
||||
|
||||
let model = this._editor.getModel();
|
||||
this._register(model.onDidChangeContent(() => this._updateDecorations.schedule()));
|
||||
this._register(this._keybindingService.onDidUpdateKeybindings((e) => this._updateDecorations.schedule()));
|
||||
this._register({
|
||||
dispose: () => {
|
||||
this._dec = this._editor.deltaDecorations(this._dec, []);
|
||||
this._updateDecorations.cancel();
|
||||
}
|
||||
});
|
||||
this._updateDecorations.schedule();
|
||||
}
|
||||
|
||||
private _updateDecorationsNow(): void {
|
||||
const model = this._editor.getModel();
|
||||
|
||||
let newDecorations: editorCommon.IModelDeltaDecoration[] = [];
|
||||
|
||||
const root = parseTree(model.getValue());
|
||||
if (root && Array.isArray(root.children)) {
|
||||
for (let i = 0, len = root.children.length; i < len; i++) {
|
||||
const entry = root.children[i];
|
||||
const dec = this._getDecorationForEntry(model, entry);
|
||||
if (dec !== null) {
|
||||
newDecorations.push(dec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._dec = this._editor.deltaDecorations(this._dec, newDecorations);
|
||||
}
|
||||
|
||||
private _getDecorationForEntry(model: editorCommon.IModel, entry: Node): editorCommon.IModelDeltaDecoration {
|
||||
if (!Array.isArray(entry.children)) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0, len = entry.children.length; i < len; i++) {
|
||||
const prop = entry.children[i];
|
||||
if (prop.type !== 'property') {
|
||||
continue;
|
||||
}
|
||||
if (!Array.isArray(prop.children) || prop.children.length !== 2) {
|
||||
continue;
|
||||
}
|
||||
const key = prop.children[0];
|
||||
if (key.value !== 'key') {
|
||||
continue;
|
||||
}
|
||||
const value = prop.children[1];
|
||||
if (value.type !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedKeybindings = this._keybindingService.resolveUserBinding(value.value);
|
||||
if (resolvedKeybindings.length === 0) {
|
||||
return this._createDecoration(true, null, null, model, value);
|
||||
}
|
||||
const resolvedKeybinding = resolvedKeybindings[0];
|
||||
let usLabel: string = null;
|
||||
if (resolvedKeybinding instanceof WindowsNativeResolvedKeybinding) {
|
||||
usLabel = resolvedKeybinding.getUSLabel();
|
||||
}
|
||||
if (!resolvedKeybinding.isWYSIWYG()) {
|
||||
return this._createDecoration(false, resolvedKeybinding.getLabel(), usLabel, model, value);
|
||||
}
|
||||
if (/abnt_|oem_/.test(value.value)) {
|
||||
return this._createDecoration(false, resolvedKeybinding.getLabel(), usLabel, model, value);
|
||||
}
|
||||
const expectedUserSettingsLabel = resolvedKeybinding.getUserSettingsLabel();
|
||||
if (!KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(value.value, expectedUserSettingsLabel)) {
|
||||
return this._createDecoration(false, resolvedKeybinding.getLabel(), usLabel, model, value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static _userSettingsFuzzyEquals(a: string, b: string): boolean {
|
||||
a = a.trim().toLowerCase();
|
||||
b = b.trim().toLowerCase();
|
||||
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [parsedA1, parsedA2] = KeybindingIO._readUserBinding(a);
|
||||
const [parsedB1, parsedB2] = KeybindingIO._readUserBinding(b);
|
||||
|
||||
return (
|
||||
this._userBindingEquals(parsedA1, parsedB1)
|
||||
&& this._userBindingEquals(parsedA2, parsedB2)
|
||||
);
|
||||
}
|
||||
|
||||
private static _userBindingEquals(a: SimpleKeybinding | ScanCodeBinding, b: SimpleKeybinding | ScanCodeBinding): boolean {
|
||||
if (a === null && b === null) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a instanceof SimpleKeybinding && b instanceof SimpleKeybinding) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
if (a instanceof ScanCodeBinding && b instanceof ScanCodeBinding) {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _createDecoration(isError: boolean, uiLabel: string, usLabel: string, model: editorCommon.IModel, keyNode: Node): editorCommon.IModelDeltaDecoration {
|
||||
let msg: MarkdownString;
|
||||
let className: string;
|
||||
let beforeContentClassName: string;
|
||||
let overviewRulerColor: ThemeColor;
|
||||
|
||||
if (isError) {
|
||||
// this is the error case
|
||||
msg = new MarkdownString().appendText(NLS_KB_LAYOUT_ERROR_MESSAGE);
|
||||
className = 'keybindingError';
|
||||
beforeContentClassName = 'inlineKeybindingError';
|
||||
overviewRulerColor = themeColorFromId(overviewRulerError);
|
||||
} else {
|
||||
// this is the info case
|
||||
if (usLabel && uiLabel !== usLabel) {
|
||||
msg = new MarkdownString(
|
||||
nls.localize({
|
||||
key: 'defineKeybinding.kbLayoutLocalAndUSMessage',
|
||||
comment: [
|
||||
'Please translate maintaining the stars (*) around the placeholders such that they will be rendered in bold.',
|
||||
'The placeholders will contain a keyboard combination e.g. Ctrl+Shift+/'
|
||||
]
|
||||
}, "**{0}** for your current keyboard layout (**{1}** for US standard).", uiLabel, usLabel)
|
||||
);
|
||||
} else {
|
||||
msg = new MarkdownString(
|
||||
nls.localize({
|
||||
key: 'defineKeybinding.kbLayoutLocalMessage',
|
||||
comment: [
|
||||
'Please translate maintaining the stars (*) around the placeholder such that it will be rendered in bold.',
|
||||
'The placeholder will contain a keyboard combination e.g. Ctrl+Shift+/'
|
||||
]
|
||||
}, "**{0}** for your current keyboard layout.", uiLabel)
|
||||
);
|
||||
}
|
||||
className = 'keybindingInfo';
|
||||
beforeContentClassName = 'inlineKeybindingInfo';
|
||||
overviewRulerColor = themeColorFromId(overviewRulerInfo);
|
||||
}
|
||||
|
||||
const startPosition = model.getPositionAt(keyNode.offset);
|
||||
const endPosition = model.getPositionAt(keyNode.offset + keyNode.length);
|
||||
const range = new Range(
|
||||
startPosition.lineNumber, startPosition.column,
|
||||
endPosition.lineNumber, endPosition.column
|
||||
);
|
||||
|
||||
// icon + highlight + message decoration
|
||||
return {
|
||||
range: range,
|
||||
options: {
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: className,
|
||||
beforeContentClassName: beforeContentClassName,
|
||||
hoverMessage: msg,
|
||||
overviewRuler: {
|
||||
color: overviewRulerColor,
|
||||
darkColor: overviewRulerColor,
|
||||
position: editorCommon.OverviewRulerLane.Right
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DefineKeybindingCommand extends EditorCommand {
|
||||
|
||||
static ID = 'editor.action.defineKeybinding';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: DefineKeybindingCommand.ID,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.languageId.isEqualTo('json')),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
if (!isInterestingEditorModel(editor) || editor.getConfiguration().readOnly) {
|
||||
return;
|
||||
}
|
||||
let controller = DefineKeybindingController.get(editor);
|
||||
if (controller && controller.keybindingWidgetRenderer) {
|
||||
controller.keybindingWidgetRenderer.showDefineKeybindingWidget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isInterestingEditorModel(editor: editorCommon.ICommonCodeEditor): boolean {
|
||||
let model = editor.getModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
let url = model.uri.toString();
|
||||
return INTERESTING_FILE.test(url);
|
||||
}
|
||||
|
||||
registerEditorCommand(new DefineKeybindingCommand());
|
||||
1
src/vs/workbench/parts/preferences/browser/media/add.svg
Normal file
@@ -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 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 0 16 16" enable-background="new -1 0 16 16"><path fill="#424242" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/><rect x="4" y="9" fill="#00539C" width="3" height="1"/></svg>
|
||||
|
After Width: | Height: | Size: 281 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 0 16 16" enable-background="new -1 0 16 16"><path fill="#C5C5C5" d="M14 1v9h-1v-8h-8v-1h9zm-11 2v1h8v8h1v-9h-9zm7 2v9h-9v-9h9zm-2 2h-5v5h5v-5z"/><rect x="4" y="9" fill="#75BEFF" width="3" height="1"/></svg>
|
||||
|
After Width: | Height: | Size: 281 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 139 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 139 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 4.28l-11.673 11.72h-4.327v-4.406l11.477-11.594h.308l4.215 4.237v.043z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M14.598 4.25l-1.688 1.75-3-3 1.688-1.75 3 3zm-5.688-.25l-7 7 3 3 7-7-3-3zm-7.91 8.09v2.91h2.91l-2.91-2.91z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#2d2d30;} .icon-vs-out{fill:#2d2d30;} .icon-vs-bg{fill:#c5c5c5;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 4.28l-11.673 11.72h-4.327v-4.406l11.477-11.594h.308l4.215 4.237v.043z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M14.598 4.25l-1.688 1.75-3-3 1.688-1.75 3 3zm-5.688-.25l-7 7 3 3 7-7-3-3zm-7.91 8.09v2.91h2.91l-2.91-2.91z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" fill="#1BA1E2"/><path d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.defineKeybindingWidget {
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .message {
|
||||
width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .monaco-inputbox,
|
||||
.defineKeybindingWidget .output {
|
||||
margin-top:10px;
|
||||
width: 400px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .output {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .output .monaco-keybinding {
|
||||
margin: 0px 4px;
|
||||
}
|
||||
|
||||
/* Editor decorations */
|
||||
.monaco-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;
|
||||
}
|
||||
|
||||
.monaco-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;
|
||||
}
|
||||
|
||||
.monaco-editor .keybindingInfo {
|
||||
box-shadow: inset 0 0 0 1px #B9B9B9;
|
||||
background-color: rgba(100, 100, 250, 0.2);
|
||||
}
|
||||
|
||||
.monaco-editor .keybindingError {
|
||||
box-shadow: inset 0 0 0 1px #B9B9B9;
|
||||
background-color: rgba(250, 100, 100, 0.2);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.keybindings-editor {
|
||||
padding: 11px 0px 0px 27px;
|
||||
}
|
||||
|
||||
/* header styling */
|
||||
|
||||
.keybindings-editor > .keybindings-header {
|
||||
padding: 0px 10px 11px 0;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
margin-top: 5px;
|
||||
background: url('sort_precedence.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence,
|
||||
.vs-dark .keybindings-editor > .keybindings-header > .search-container > .sort-by-precedence {
|
||||
background: url('sort_precedence_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.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 .input {
|
||||
font-size: 14px;
|
||||
padding-left:10px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container {
|
||||
margin-top: 10px;
|
||||
opacity: 0.7;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container > .file-name {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/** List based styling **/
|
||||
|
||||
.keybindings-editor > .keybindings-body .keybindings-list-container {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row.even:not(.focused):not(.selected):not(:hover),
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:not(:focus) .monaco-list-row.focused.even:not(.selected):not(:hover),
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:not(.focused) .monaco-list-row.focused.even:not(.selected):not(:hover) {
|
||||
background-color: rgba(130, 130, 130, 0.04);
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body .keybindings-list-container .monaco-list-row > .header {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .header,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .column {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .actions {
|
||||
width: 24px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command {
|
||||
flex: 0.75;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command.vertical-align-column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command .command-default-label {
|
||||
opacity: 0.8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .keybinding {
|
||||
flex: 0.5;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .keybinding .monaco-highlighted-label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .source {
|
||||
flex: 0.25;
|
||||
}
|
||||
|
||||
.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 .command .monaco-highlighted-label,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .source .monaco-highlighted-label,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .when .monaco-highlighted-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.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%;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code.strong {
|
||||
padding: 1px 4px;
|
||||
background-color: rgba(128, 128, 128, 0.17);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.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.selected > .column.actions .monaco-action-bar,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list.focused .monaco-list-row.focused > .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-top: 3px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
241
src/vs/workbench/parts/preferences/browser/media/preferences.css
Normal file
@@ -0,0 +1,241 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.preferences-editor > .preferences-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 27px;
|
||||
padding-right: 32px;
|
||||
padding-bottom: 11px;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
.preferences-editor > .preferences-header.vertical-layout {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.preferences-editor > .preferences-editors-container.side-by-side-preferences-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-targets-widget {
|
||||
flex-wrap: wrap;
|
||||
margin: 4px 0 4px 18px;
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-targets-widget > .settings-target {
|
||||
font-size: 11px;
|
||||
padding: 2px 4px 0 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-targets-widget > .settings-target > .settings-target-label {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.settings-targets-widget > .settings-target > .settings-target-details {
|
||||
margin-left: 0.5em;
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-targets-widget > .settings-target > .settings-target-details.empty {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.settings-targets-widget > .settings-target-dropdown-icon {
|
||||
padding-left: 0.5em;
|
||||
padding-top: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.preferences-header > .settings-header-widget {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-header-container {
|
||||
padding-top: 8px;
|
||||
padding-left: 27px;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-count-widget {
|
||||
margin: 6px 0px;
|
||||
padding: 0px 8px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-count-widget.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-container > .settings-search-input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-container > .settings-search-input > .monaco-inputbox {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.vs .settings-header-widget > .settings-search-container > .settings-search-input > .monaco-inputbox {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-container > .settings-search-input > .monaco-inputbox .input {
|
||||
font-size: 14px;
|
||||
padding-left:10px;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-header-widget .title-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vs .monaco-editor .settings-header-widget .title-container {
|
||||
color: #6f6f6f;
|
||||
}
|
||||
.vs-dark .monaco-editor .settings-header-widget .title-container {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
.hc-black .monaco-editor .settings-header-widget .title-container {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-header-widget .title-container .title {
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-header-widget .title-container .message {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget .title-container {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vs .monaco-editor .settings-group-title-widget .title-container {
|
||||
color: #6f6f6f;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget .title-container .title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-editor .settings-group-title-widget .title-container {
|
||||
color: #bbbbbb;
|
||||
}
|
||||
.hc-black .monaco-editor .settings-group-title-widget .title-container {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .settings-group-title-widget .title-container.focused,
|
||||
.monaco-editor.vs .settings-group-title-widget .title-container.focused {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget .title-container.focused,
|
||||
.monaco-editor .settings-group-title-widget .title-container:hover {
|
||||
background-color: rgba(153, 153, 153, 0.2);
|
||||
}
|
||||
|
||||
.monaco-editor.hc-black .settings-group-title-widget .title-container.focused {
|
||||
outline: 1px dotted #f38518;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget .title-container .expand-collapse-icon {
|
||||
background: url(expanded.svg) 50% 50% no-repeat;
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .settings-group-title-widget .title-container .expand-collapse-icon,
|
||||
.monaco-editor.hc-black .settings-group-title-widget .title-container .expand-collapse-icon {
|
||||
background: url(expanded-dark.svg) 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-group-title-widget .title-container.collapsed .expand-collapse-icon {
|
||||
background: url(collapsed.svg) 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .settings-group-title-widget .title-container.collapsed .expand-collapse-icon,
|
||||
.monaco-editor.hc-black .settings-group-title-widget .title-container.collapsed .expand-collapse-icon {
|
||||
background: url(collapsed-dark.svg) 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .edit-preferences-widget {
|
||||
background: url('edit.svg') center center no-repeat;
|
||||
transform: rotate(-90deg);
|
||||
width:16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-editor .edit-preferences-widget.hidden {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.monaco-editor.hc-black .edit-preferences-widget,
|
||||
.monaco-editor.vs-dark .edit-preferences-widget {
|
||||
background: url('edit_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .unsupportedWorkbenhSettingInfo:before {
|
||||
content:" ";
|
||||
display:inline-block;
|
||||
height:1em;
|
||||
width:1em;
|
||||
background: url(info.svg) 50% 50% no-repeat;
|
||||
background-size: 0.9em;
|
||||
}
|
||||
|
||||
.monaco-editor .dim-configuration {
|
||||
color: #b1b1b1;
|
||||
}
|
||||
|
||||
.monaco-editor .floating-click-widget {
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title-actions .action-item .icon.collapseAll,
|
||||
.editor-actions .action-item .icon.collapseAll {
|
||||
background: url('collapseAll.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .title-actions .action-item .icon.collapseAll,
|
||||
.vs-dark .editor-actions .action-item .icon.collapseAll {
|
||||
background: url('collapseAll_inverse.svg') center center no-repeat;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#f6f6f6}.st2{fill:#424242}.st3{fill:#f0eff1}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M5 1v5H0v9h11v-2.586l1 1 4-4V1H5zm5 3v2L8 4.586V4h2zm-2 8H3V9h5v3z"/></g><g id="icon_x5F_bg"><path class="st2" d="M3 9h2v2H3zM6 10h2v2H6zM14 3v3h.586L15 5.586V2H6v4h1V3z"/><path class="st2" d="M9 10.414V13H2V8h6V7H1v7h9V11.414zM9.414 6H10V4H8v.586z"/><path class="st2" d="M13 5h-2v4L9 7v2l3 3 3-3V7l-2 2z"/></g><g id="icon_x5F_fg"><path class="st3" d="M8 9.414V8H2v5h7v-2.586l-1-1zM5 11H3V9h2v2zm3 1H6v-2h2v2zM8 4.586V4h6V3H7v3h1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#2d2d30}.st2{fill:#c5c5c5}.st3{fill:#2b282e}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M5 1v5H0v9h11v-2.586l1 1 4-4V1H5zm5 3v2L8 4.586V4h2zm-2 8H3V9h5v3z"/></g><g id="icon_x5F_bg"><path class="st2" d="M3 9h2v2H3zM6 10h2v2H6zM14 3v3h.586L15 5.586V2H6v4h1V3z"/><path class="st2" d="M9 10.414V13H2V8h6V7H1v7h9V11.414zM9.414 6H10V4H8v.586z"/><path class="st2" d="M13 5h-2v4L9 7v2l3 3 3-3V7l-2 2z"/></g><g id="icon_x5F_fg"><path class="st3" d="M8 9.414V8H2v5h7v-2.586l-1-1zM5 11H3V9h2v2zm3 1H6v-2h2v2zM8 4.586V4h6V3H7v3h1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle cx="8" cy="8" r="6" fill="#F6F6F6"/><path d="M8 3C5.238 3 3 5.238 3 8s2.238 5 5 5 5-2.238 5-5-2.238-5-5-5zm3 7l-1 1-2-2-2 2-1-1 2-2.027L5 6l1-1 2 2 2-2 1 1-2 1.973L11 10z" fill="#E51400"/><path fill="#fff" d="M11 6l-1-1-2 2-2-2-1 1 2 1.973L5 10l1 1 2-2 2 2 1-1-2-2.027z"/></svg>
|
||||
|
After Width: | Height: | Size: 403 B |
@@ -0,0 +1,249 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory } from 'vs/workbench/common/editor';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
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, OpenGlobalKeybindingsFileAction, OpenWorkspaceSettingsAction, OpenFolderSettingsAction, ConfigureLanguageBasedSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions';
|
||||
import {
|
||||
IPreferencesService, IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_SEARCH,
|
||||
KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SHOW_CONFLICTS, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS
|
||||
} from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { PreferencesService } from 'vs/workbench/parts/preferences/browser/preferencesService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { PreferencesContentProvider } from 'vs/workbench/parts/preferences/common/preferencesContentProvider';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
registerSingleton(IPreferencesService, PreferencesService);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
PreferencesEditor.ID,
|
||||
nls.localize('defaultPreferencesEditor', "Default Preferences Editor"),
|
||||
'vs/workbench/parts/preferences/browser/preferencesEditor',
|
||||
'PreferencesEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(PreferencesEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
detailsSerialized: string;
|
||||
masterSerialized: string;
|
||||
|
||||
detailsTypeId: string;
|
||||
masterTypeId: string;
|
||||
}
|
||||
|
||||
// Register Preferences Editor Input Factory
|
||||
class PreferencesEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
const input = <PreferencesEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
|
||||
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsSerialized = detailsInputFactory.serialize(input.details);
|
||||
const masterSerialized = masterInputFactory.serialize(input.master);
|
||||
|
||||
if (detailsSerialized && masterSerialized) {
|
||||
return JSON.stringify(<ISerializedPreferencesEditorInput>{
|
||||
name: input.getName(),
|
||||
description: input.getDescription(),
|
||||
detailsSerialized,
|
||||
masterSerialized,
|
||||
detailsTypeId: input.details.getTypeId(),
|
||||
masterTypeId: input.master.getTypeId()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedPreferencesEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(deserialized.detailsTypeId);
|
||||
const masterInputFactory = registry.getEditorInputFactory(deserialized.masterTypeId);
|
||||
|
||||
if (detailsInputFactory && masterInputFactory) {
|
||||
const detailsInput = detailsInputFactory.deserialize(instantiationService, deserialized.detailsSerialized);
|
||||
const masterInput = masterInputFactory.deserialize(instantiationService, deserialized.masterSerialized);
|
||||
|
||||
if (detailsInput && masterInput) {
|
||||
return new PreferencesEditorInput(deserialized.name, deserialized.description, detailsInput, masterInput);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Register Default Preferences Editor Input Factory
|
||||
class DefaultPreferencesEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
const input = <DefaultPreferencesEditorInput>editorInput;
|
||||
|
||||
const serialized: ISerializedDefaultPreferencesEditorInput = { resource: input.getResource().toString() };
|
||||
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedDefaultPreferencesEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
return instantiationService.createInstance(DefaultPreferencesEditorInput, URI.parse(deserialized.resource));
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_COMMA }), 'Preferences: Open User Settings', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceSettingsAction, OpenWorkspaceSettingsAction.ID, OpenWorkspaceSettingsAction.LABEL), 'Preferences: Open Workspace Settings', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderSettingsAction, OpenFolderSettingsAction.ID, OpenFolderSettingsAction.LABEL), 'Preferences: Open Folder Settings', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: null }), 'Preferences: Open Keyboard Shortcuts File', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_DEFINE,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K),
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.defineKeybinding(editor.activeKeybindingEntry);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_REMOVE,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.Backspace)
|
||||
},
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.removeKeybinding(editor.activeKeybindingEntry);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_RESET,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: null,
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.resetKeybinding(editor.activeKeybindingEntry);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SEARCH,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
|
||||
handler: (accessor, args: any) => (accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor).search('')
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SHOW_CONFLICTS,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: null,
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.showConflicts(editor.activeKeybindingEntry);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_COPY,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.copyKeybinding(editor.activeKeybindingEntry);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS),
|
||||
primary: KeyCode.DownArrow,
|
||||
handler: (accessor, args: any) => {
|
||||
const editor = accessor.get(IWorkbenchEditorService).getActiveEditor() as IKeybindingsEditor;
|
||||
editor.focusKeybindings();
|
||||
}
|
||||
});
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PreferencesContentProvider);
|
||||
173
src/vs/workbench/parts/preferences/browser/preferencesActions.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IPreferencesService, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
|
||||
export class OpenGlobalSettingsAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openGlobalSettings';
|
||||
public static LABEL = nls.localize('openGlobalSettings', "Open User Settings");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<any> {
|
||||
return this.preferencesService.openGlobalSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGlobalKeybindingsAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openGlobalKeybindings';
|
||||
public static LABEL = nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<any> {
|
||||
return this.preferencesService.openGlobalKeybindingSettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGlobalKeybindingsFileAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openGlobalKeybindingsFile';
|
||||
public static LABEL = nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts File");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<any> {
|
||||
return this.preferencesService.openGlobalKeybindingSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceSettingsAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openWorkspaceSettings';
|
||||
public static LABEL = nls.localize('openWorkspaceSettings', "Open Workspace Settings");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = this.workspaceContextService.hasWorkspace();
|
||||
}
|
||||
|
||||
public run(event?: any): TPromise<any> {
|
||||
return this.preferencesService.openWorkspaceSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenFolderSettingsAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.openFolderSettings';
|
||||
public static LABEL = nls.localize('openFolderSettings', "Open Folder Settings");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = this.workspaceContextService.hasMultiFolderWorkspace();
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const picks: IPickOpenEntry[] = this.workspaceContextService.getWorkspace().roots.map((root, index) => {
|
||||
return <IPickOpenEntry>{
|
||||
label: getSettingsTargetName(ConfigurationTarget.FOLDER, root, this.workspaceContextService),
|
||||
id: `${index}`
|
||||
};
|
||||
});
|
||||
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickFolder', "Select Folder") })
|
||||
.then(pick => {
|
||||
if (pick) {
|
||||
return this.preferencesService.openFolderSettings(this.workspaceContextService.getWorkspace().roots[parseInt(pick.id)]);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureLanguageBasedSettingsAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.configureLanguageBasedSettings';
|
||||
public static LABEL = nls.localize('configureLanguageBasedSettings', "Configure Language Specific Settings...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
|
||||
let description: string = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
|
||||
// construct a fake resource to be able to show nice icons if any
|
||||
let fakeResource: URI;
|
||||
const extensions = this.modeService.getExtensions(lang);
|
||||
if (extensions && extensions.length) {
|
||||
fakeResource = URI.file(extensions[0]);
|
||||
} else {
|
||||
const filenames = this.modeService.getFilenames(lang);
|
||||
if (filenames && filenames.length) {
|
||||
fakeResource = URI.file(filenames[0]);
|
||||
}
|
||||
}
|
||||
return <IFilePickOpenEntry>{
|
||||
label: lang,
|
||||
resource: fakeResource,
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") })
|
||||
.then(pick => {
|
||||
if (pick) {
|
||||
return this.modeService.getOrCreateModeByLanguageName(pick.label)
|
||||
.then(mode => this.preferencesService.configureSettingsForLanguage(mode.getLanguageIdentifier().language));
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
948
src/vs/workbench/parts/preferences/browser/preferencesEditor.ts
Normal file
@@ -0,0 +1,948 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { ArrayNavigator, INavigator } from 'vs/base/common/iterator';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { toResource, SideBySideEditorInput, EditorOptions, EditorInput, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
|
||||
import { IEditorControl, Position, Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import {
|
||||
IPreferencesService, ISettingsGroup, ISetting, IFilterResult,
|
||||
CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, ISettingsEditorModel
|
||||
} from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels';
|
||||
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
|
||||
import { ICodeEditor, IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
|
||||
import { SearchWidget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
|
||||
import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Command } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { VSash } from 'vs/base/browser/ui/sash/sash';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { IPreferencesRenderer, DefaultSettingsRenderer, UserSettingsRenderer, WorkspaceSettingsRenderer, FolderSettingsRenderer } from 'vs/workbench/parts/preferences/browser/preferencesRenderers';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
|
||||
// Ignore following contributions
|
||||
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
|
||||
import { FindController } from 'vs/editor/contrib/find/browser/find';
|
||||
import { SelectionHighlighter } from 'vs/editor/contrib/find/common/findController';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
export class PreferencesEditorInput extends SideBySideEditorInput {
|
||||
public static ID: string = 'workbench.editorinputs.preferencesEditorInput';
|
||||
|
||||
getTypeId(): string {
|
||||
return PreferencesEditorInput.ID;
|
||||
}
|
||||
|
||||
public getTitle(verbosity: Verbosity): string {
|
||||
return this.master.getTitle(verbosity);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultPreferencesEditorInput extends ResourceEditorInput {
|
||||
public static ID = 'workbench.editorinputs.defaultpreferences';
|
||||
constructor(defaultSettingsResource: URI,
|
||||
@ITextModelService textModelResolverService: ITextModelService
|
||||
) {
|
||||
super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService);
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return DefaultPreferencesEditorInput.ID;
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
if (!super.matches(other)) {
|
||||
return false;
|
||||
}
|
||||
if (!(other instanceof DefaultPreferencesEditorInput)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreferencesEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.preferencesEditor';
|
||||
|
||||
private defaultSettingsEditorContextKey: IContextKey<boolean>;
|
||||
private focusSettingsContextKey: IContextKey<boolean>;
|
||||
private headerContainer: HTMLElement;
|
||||
private searchWidget: SearchWidget;
|
||||
private settingsTargetsWidget: SettingsTargetsWidget;
|
||||
private sideBySidePreferencesWidget: SideBySidePreferencesWidget;
|
||||
private preferencesRenderers: PreferencesRenderers;
|
||||
|
||||
private delayedFilterLogging: Delayer<void>;
|
||||
|
||||
private latestEmptyFilters: string[] = [];
|
||||
private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget = null;
|
||||
|
||||
constructor(
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super(PreferencesEditor.ID, telemetryService, themeService);
|
||||
this.defaultSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(this.contextKeyService);
|
||||
this.focusSettingsContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(this.contextKeyService);
|
||||
this.delayedFilterLogging = new Delayer<void>(1000);
|
||||
}
|
||||
|
||||
public createEditor(parent: Builder): void {
|
||||
const parentElement = parent.getHTMLElement();
|
||||
DOM.addClass(parentElement, 'preferences-editor');
|
||||
|
||||
this.headerContainer = DOM.append(parentElement, DOM.$('.preferences-header'));
|
||||
|
||||
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.headerContainer, {
|
||||
ariaLabel: nls.localize('SearchSettingsWidget.AriaLabel', "Search settings"),
|
||||
placeholder: nls.localize('SearchSettingsWidget.Placeholder', "Search Settings"),
|
||||
focusKey: this.focusSettingsContextKey
|
||||
}));
|
||||
this._register(this.searchWidget.onDidChange(value => this.filterPreferences(value.trim())));
|
||||
this._register(this.searchWidget.onNavigate(shift => this.preferencesRenderers.focusNextPreference(!shift)));
|
||||
this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget));
|
||||
this.lastFocusedWidget = this.searchWidget;
|
||||
|
||||
this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, this.headerContainer, this.preferencesService.userSettingsResource, ConfigurationTarget.USER));
|
||||
this._register(this.settingsTargetsWidget.onDidTargetChange(target => this.switchSettings(target)));
|
||||
|
||||
const editorsContainer = DOM.append(parentElement, DOM.$('.preferences-editors-container'));
|
||||
this.sideBySidePreferencesWidget = this._register(this.instantiationService.createInstance(SideBySidePreferencesWidget, editorsContainer));
|
||||
this._register(this.sideBySidePreferencesWidget.onFocus(() => this.lastFocusedWidget = this.sideBySidePreferencesWidget));
|
||||
|
||||
this.preferencesRenderers = this._register(new PreferencesRenderers());
|
||||
this._register(this.workspaceContextService.onDidChangeWorkspaceRoots(() => this.onWorkspaceRootsChanged()));
|
||||
}
|
||||
|
||||
public setInput(newInput: PreferencesEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
this.defaultSettingsEditorContextKey.set(true);
|
||||
const oldInput = <PreferencesEditorInput>this.input;
|
||||
return super.setInput(newInput, options).then(() => this.updateInput(oldInput, newInput, options));
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
DOM.toggleClass(this.headerContainer, 'vertical-layout', dimension.width < 700);
|
||||
this.searchWidget.layout(dimension);
|
||||
const headerHeight = DOM.getTotalHeight(this.headerContainer);
|
||||
this.sideBySidePreferencesWidget.layout(new Dimension(dimension.width, dimension.height - headerHeight));
|
||||
}
|
||||
|
||||
public getControl(): IEditorControl {
|
||||
return this.sideBySidePreferencesWidget.getControl();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.lastFocusedWidget) {
|
||||
this.lastFocusedWidget.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public focusSearch(filter?: string): void {
|
||||
if (filter) {
|
||||
this.searchWidget.setValue(filter);
|
||||
}
|
||||
|
||||
this.searchWidget.focus();
|
||||
}
|
||||
|
||||
public focusSettingsFileEditor(): void {
|
||||
if (this.sideBySidePreferencesWidget) {
|
||||
this.sideBySidePreferencesWidget.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
this.defaultSettingsEditorContextKey.set(false);
|
||||
this.sideBySidePreferencesWidget.clearInput();
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
protected setEditorVisible(visible: boolean, position: Position): void {
|
||||
this.sideBySidePreferencesWidget.setEditorVisible(visible, position);
|
||||
super.setEditorVisible(visible, position);
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
this.sideBySidePreferencesWidget.changePosition(position);
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
private updateInput(oldInput: PreferencesEditorInput, newInput: PreferencesEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const resource = toResource(newInput.master);
|
||||
this.settingsTargetsWidget.setTarget(this.getSettingsConfigurationTargetUri(resource), this.getSettingsConfigurationTarget(resource));
|
||||
|
||||
return this.sideBySidePreferencesWidget.setInput(<DefaultPreferencesEditorInput>newInput.details, <EditorInput>newInput.master, options).then(({ defaultPreferencesRenderer, editablePreferencesRenderer }) => {
|
||||
this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer;
|
||||
this.preferencesRenderers.editablePreferencesRenderer = editablePreferencesRenderer;
|
||||
this.filterPreferences(this.searchWidget.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
private getSettingsConfigurationTarget(resource: URI): ConfigurationTarget {
|
||||
if (this.preferencesService.userSettingsResource.fsPath === resource.fsPath) {
|
||||
return ConfigurationTarget.USER;
|
||||
}
|
||||
if (this.preferencesService.workspaceSettingsResource.fsPath === resource.fsPath) {
|
||||
return ConfigurationTarget.WORKSPACE;
|
||||
}
|
||||
if (this.workspaceContextService.getRoot(resource)) {
|
||||
return ConfigurationTarget.FOLDER;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private getSettingsConfigurationTargetUri(resource: URI): URI {
|
||||
if (this.preferencesService.userSettingsResource.fsPath === resource.fsPath) {
|
||||
return resource;
|
||||
}
|
||||
if (this.preferencesService.workspaceSettingsResource.fsPath === resource.fsPath) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
return this.workspaceContextService.getRoot(resource);
|
||||
}
|
||||
|
||||
private onWorkspaceRootsChanged(): void {
|
||||
if (this.input) {
|
||||
const settingsResource = toResource((<PreferencesEditorInput>this.input).master);
|
||||
const targetResource = this.getSettingsConfigurationTargetUri(settingsResource);
|
||||
if (!targetResource) {
|
||||
this.switchSettings(this.preferencesService.userSettingsResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private switchSettings(resource: URI): void {
|
||||
// Focus the editor if this editor is not active editor
|
||||
if (this.editorService.getActiveEditor() !== this) {
|
||||
this.focus();
|
||||
}
|
||||
const promise = this.input.isDirty() ? this.input.save() : TPromise.as(true);
|
||||
promise.done(value => this.preferencesService.switchSettings(this.getSettingsConfigurationTarget(resource), resource));
|
||||
}
|
||||
|
||||
private filterPreferences(filter: string) {
|
||||
const count = this.preferencesRenderers.filterPreferences(filter);
|
||||
const message = filter ? this.showSearchResultsMessage(count) : nls.localize('totalSettingsMessage', "Total {0} Settings", count);
|
||||
this.searchWidget.showMessage(message, count);
|
||||
if (count === 0) {
|
||||
this.latestEmptyFilters.push(filter);
|
||||
}
|
||||
this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(filter));
|
||||
}
|
||||
|
||||
private showSearchResultsMessage(count: number): string {
|
||||
return count === 0 ? nls.localize('noSettingsFound', "No Results") :
|
||||
count === 1 ? nls.localize('oneSettingFound', "1 Setting matched") :
|
||||
nls.localize('settingsFound', "{0} Settings matched", count);
|
||||
}
|
||||
|
||||
private reportFilteringUsed(filter: string): void {
|
||||
if (filter) {
|
||||
let data = {
|
||||
filter,
|
||||
emptyFilters: this.getLatestEmptyFiltersForTelemetry()
|
||||
};
|
||||
this.latestEmptyFilters = [];
|
||||
this.telemetryService.publicLog('defaultSettings.filter', data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a rough limit on the size of the telemetry data, since otherwise it could be an unbounded large amount
|
||||
* of data. 8192 is the max size of a property value. This is rough since that probably includes ""s, etc.
|
||||
*/
|
||||
private getLatestEmptyFiltersForTelemetry(): string[] {
|
||||
let cumulativeSize = 0;
|
||||
return this.latestEmptyFilters.filter(filterText => (cumulativeSize += filterText.length) <= 8192);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsNavigator implements INavigator<ISetting> {
|
||||
|
||||
private iterator: ArrayNavigator<ISetting>;
|
||||
|
||||
constructor(settings: ISetting[]) {
|
||||
this.iterator = new ArrayNavigator<ISetting>(settings);
|
||||
}
|
||||
|
||||
public next(): ISetting {
|
||||
return this.iterator.next() || this.iterator.first();
|
||||
}
|
||||
|
||||
public previous(): ISetting {
|
||||
return this.iterator.previous() || this.iterator.last();
|
||||
}
|
||||
|
||||
public parent(): ISetting {
|
||||
return this.iterator.parent();
|
||||
}
|
||||
|
||||
public first(): ISetting {
|
||||
return this.iterator.first();
|
||||
}
|
||||
|
||||
public last(): ISetting {
|
||||
return this.iterator.last();
|
||||
}
|
||||
|
||||
public current(): ISetting {
|
||||
return this.iterator.current();
|
||||
}
|
||||
}
|
||||
|
||||
class PreferencesRenderers extends Disposable {
|
||||
|
||||
private _defaultPreferencesRenderer: IPreferencesRenderer<ISetting>;
|
||||
private _editablePreferencesRenderer: IPreferencesRenderer<ISetting>;
|
||||
private _settingsNavigator: SettingsNavigator;
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
public get defaultPreferencesRenderer(): IPreferencesRenderer<ISetting> {
|
||||
return this._defaultPreferencesRenderer;
|
||||
}
|
||||
|
||||
public set defaultPreferencesRenderer(defaultPreferencesRenderer: IPreferencesRenderer<ISetting>) {
|
||||
if (this._defaultPreferencesRenderer !== defaultPreferencesRenderer) {
|
||||
this._defaultPreferencesRenderer = defaultPreferencesRenderer;
|
||||
|
||||
this._disposables = dispose(this._disposables);
|
||||
|
||||
if (this._defaultPreferencesRenderer) {
|
||||
this._defaultPreferencesRenderer.onUpdatePreference(({ key, value, source }) => this._updatePreference(key, value, source, this._editablePreferencesRenderer), this, this._disposables);
|
||||
this._defaultPreferencesRenderer.onFocusPreference(preference => this._focusPreference(preference, this._editablePreferencesRenderer), this, this._disposables);
|
||||
this._defaultPreferencesRenderer.onClearFocusPreference(preference => this._clearFocus(preference, this._editablePreferencesRenderer), this, this._disposables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public set editablePreferencesRenderer(editableSettingsRenderer: IPreferencesRenderer<ISetting>) {
|
||||
this._editablePreferencesRenderer = editableSettingsRenderer;
|
||||
}
|
||||
|
||||
public filterPreferences(filter: string): number {
|
||||
const defaultPreferencesFilterResult = this._filterPreferences(filter, this._defaultPreferencesRenderer);
|
||||
const editablePreferencesFilterResult = this._filterPreferences(filter, this._editablePreferencesRenderer);
|
||||
|
||||
const defaultPreferencesFilteredGroups = defaultPreferencesFilterResult ? defaultPreferencesFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer);
|
||||
const editablePreferencesFilteredGroups = editablePreferencesFilterResult ? editablePreferencesFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer);
|
||||
const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups);
|
||||
this._settingsNavigator = new SettingsNavigator(filter ? consolidatedSettings : []);
|
||||
|
||||
return consolidatedSettings.length;
|
||||
}
|
||||
|
||||
public focusNextPreference(forward: boolean = true) {
|
||||
const setting = forward ? this._settingsNavigator.next() : this._settingsNavigator.previous();
|
||||
this._focusPreference(setting, this._defaultPreferencesRenderer);
|
||||
this._focusPreference(setting, this._editablePreferencesRenderer);
|
||||
}
|
||||
|
||||
private _getAllPreferences(preferencesRenderer: IPreferencesRenderer<ISetting>): ISettingsGroup[] {
|
||||
return preferencesRenderer ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).settingsGroups : [];
|
||||
}
|
||||
|
||||
private _filterPreferences(filter: string, preferencesRenderer: IPreferencesRenderer<ISetting>): IFilterResult {
|
||||
let filterResult = null;
|
||||
if (preferencesRenderer) {
|
||||
filterResult = filter ? (<ISettingsEditorModel>preferencesRenderer.preferencesModel).filterSettings(filter) : null;
|
||||
preferencesRenderer.filterPreferences(filterResult);
|
||||
}
|
||||
return filterResult;
|
||||
}
|
||||
|
||||
private _focusPreference(preference: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
|
||||
if (preference && preferencesRenderer) {
|
||||
preferencesRenderer.focusPreference(preference);
|
||||
}
|
||||
}
|
||||
|
||||
private _clearFocus(preference: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
|
||||
if (preference && preferencesRenderer) {
|
||||
preferencesRenderer.clearFocus(preference);
|
||||
}
|
||||
}
|
||||
|
||||
private _updatePreference(key: string, value: any, source: ISetting, preferencesRenderer: IPreferencesRenderer<ISetting>): void {
|
||||
if (preferencesRenderer) {
|
||||
preferencesRenderer.updatePreference(key, value, source);
|
||||
}
|
||||
}
|
||||
|
||||
private _consolidateSettings(editableSettingsGroups: ISettingsGroup[], defaultSettingsGroups: ISettingsGroup[]): ISetting[] {
|
||||
const editableSettings = this._flatten(editableSettingsGroups);
|
||||
const defaultSettings = this._flatten(defaultSettingsGroups).filter(secondarySetting => !editableSettings.some(primarySetting => primarySetting.key === secondarySetting.key));
|
||||
return [...editableSettings, ...defaultSettings];
|
||||
}
|
||||
|
||||
private _flatten(settingsGroups: ISettingsGroup[]): ISetting[] {
|
||||
const settings: ISetting[] = [];
|
||||
for (const group of settingsGroups) {
|
||||
for (const section of group.sections) {
|
||||
settings.push(...section.settings);
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
dispose(this._disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class SideBySidePreferencesWidget extends Widget {
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
private defaultPreferencesEditor: DefaultPreferencesEditor;
|
||||
private editablePreferencesEditor: BaseEditor;
|
||||
private defaultPreferencesEditorContainer: HTMLElement;
|
||||
private editablePreferencesEditorContainer: HTMLElement;
|
||||
|
||||
private _onFocus: Emitter<void> = new Emitter<void>();
|
||||
readonly onFocus: Event<void> = this._onFocus.event;
|
||||
|
||||
private lastFocusedEditor: BaseEditor;
|
||||
|
||||
private sash: VSash;
|
||||
|
||||
constructor(parent: HTMLElement, @IInstantiationService private instantiationService: IInstantiationService, @IThemeService private themeService: IThemeService) {
|
||||
super();
|
||||
this.create(parent);
|
||||
}
|
||||
|
||||
private create(parentElement: HTMLElement): void {
|
||||
DOM.addClass(parentElement, 'side-by-side-preferences-editor');
|
||||
this.createSash(parentElement);
|
||||
|
||||
this.defaultPreferencesEditorContainer = DOM.append(parentElement, DOM.$('.default-preferences-editor-container'));
|
||||
this.defaultPreferencesEditorContainer.style.position = 'absolute';
|
||||
this.defaultPreferencesEditor = this._register(this.instantiationService.createInstance(DefaultPreferencesEditor));
|
||||
this.defaultPreferencesEditor.create(new Builder(this.defaultPreferencesEditorContainer));
|
||||
this.defaultPreferencesEditor.setVisible(true);
|
||||
(<CodeEditor>this.defaultPreferencesEditor.getControl()).onDidFocusEditor(() => this.lastFocusedEditor = this.defaultPreferencesEditor);
|
||||
|
||||
this.editablePreferencesEditorContainer = DOM.append(parentElement, DOM.$('.editable-preferences-editor-container'));
|
||||
this.editablePreferencesEditorContainer.style.position = 'absolute';
|
||||
|
||||
this._register(attachStylerCallback(this.themeService, { scrollbarShadow }, colors => {
|
||||
const shadow = colors.scrollbarShadow ? colors.scrollbarShadow.toString() : null;
|
||||
|
||||
if (shadow) {
|
||||
this.editablePreferencesEditorContainer.style.boxShadow = `-6px 0 5px -5px ${shadow}`;
|
||||
} else {
|
||||
this.editablePreferencesEditorContainer.style.boxShadow = null;
|
||||
}
|
||||
}));
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(parentElement));
|
||||
this._register(focusTracker.addFocusListener(() => this._onFocus.fire()));
|
||||
}
|
||||
|
||||
public setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options?: EditorOptions): TPromise<{ defaultPreferencesRenderer: IPreferencesRenderer<ISetting>, editablePreferencesRenderer: IPreferencesRenderer<ISetting> }> {
|
||||
return this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput)
|
||||
.then(() => {
|
||||
this.dolayout(this.sash.getVerticalSashLeft());
|
||||
return TPromise.join([this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, toResource(editablePreferencesEditorInput), options),
|
||||
this.updateInput(this.editablePreferencesEditor, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.getResource(), options)])
|
||||
.then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => ({ defaultPreferencesRenderer, editablePreferencesRenderer }));
|
||||
});
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
this.sash.setDimenesion(this.dimension);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
if (this.lastFocusedEditor) {
|
||||
this.lastFocusedEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public getControl(): IEditorControl {
|
||||
return this.editablePreferencesEditor ? this.editablePreferencesEditor.getControl() : null;
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
if (this.editablePreferencesEditor) {
|
||||
this.editablePreferencesEditor.clearInput();
|
||||
}
|
||||
}
|
||||
|
||||
public setEditorVisible(visible: boolean, position: Position): void {
|
||||
if (this.editablePreferencesEditor) {
|
||||
this.editablePreferencesEditor.setVisible(visible, position);
|
||||
}
|
||||
}
|
||||
|
||||
public changePosition(position: Position): void {
|
||||
if (this.editablePreferencesEditor) {
|
||||
this.editablePreferencesEditor.changePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
private getOrCreateEditablePreferencesEditor(editorInput: EditorInput): TPromise<BaseEditor> {
|
||||
if (this.editablePreferencesEditor) {
|
||||
return TPromise.as(this.editablePreferencesEditor);
|
||||
}
|
||||
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editorInput);
|
||||
return this.instantiationService.createInstance(<EditorDescriptor>descriptor)
|
||||
.then((editor: BaseEditor) => {
|
||||
this.editablePreferencesEditor = editor;
|
||||
this.editablePreferencesEditor.create(new Builder(this.editablePreferencesEditorContainer));
|
||||
this.editablePreferencesEditor.setVisible(true);
|
||||
(<CodeEditor>this.editablePreferencesEditor.getControl()).onDidFocusEditor(() => this.lastFocusedEditor = this.editablePreferencesEditor);
|
||||
this.lastFocusedEditor = this.editablePreferencesEditor;
|
||||
return editor;
|
||||
});
|
||||
}
|
||||
|
||||
private updateInput(editor: BaseEditor, input: EditorInput, editorContributionId: string, associatedPreferencesModelUri: URI, options: EditorOptions): TPromise<IPreferencesRenderer<ISetting>> {
|
||||
return editor.setInput(input, options)
|
||||
.then(() => (<CodeEditor>editor.getControl()).getContribution<ISettingsEditorContribution>(editorContributionId).updatePreferencesRenderer(associatedPreferencesModelUri));
|
||||
}
|
||||
|
||||
private createSash(parentElement: HTMLElement): void {
|
||||
this.sash = this._register(new VSash(parentElement, 220));
|
||||
this._register(this.sash.onPositionChange(position => this.dolayout(position)));
|
||||
}
|
||||
|
||||
private dolayout(splitPoint: number): void {
|
||||
if (!this.editablePreferencesEditor || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
const masterEditorWidth = this.dimension.width - splitPoint;
|
||||
const detailsEditorWidth = this.dimension.width - masterEditorWidth;
|
||||
|
||||
this.defaultPreferencesEditorContainer.style.width = `${detailsEditorWidth}px`;
|
||||
this.defaultPreferencesEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.defaultPreferencesEditorContainer.style.left = '0px';
|
||||
|
||||
this.editablePreferencesEditorContainer.style.width = `${masterEditorWidth}px`;
|
||||
this.editablePreferencesEditorContainer.style.height = `${this.dimension.height}px`;
|
||||
this.editablePreferencesEditorContainer.style.left = `${splitPoint}px`;
|
||||
|
||||
this.defaultPreferencesEditor.layout(new Dimension(detailsEditorWidth, this.dimension.height));
|
||||
this.editablePreferencesEditor.layout(new Dimension(masterEditorWidth, this.dimension.height));
|
||||
}
|
||||
|
||||
private disposeEditors(): void {
|
||||
if (this.defaultPreferencesEditor) {
|
||||
this.defaultPreferencesEditor.dispose();
|
||||
this.defaultPreferencesEditor = null;
|
||||
}
|
||||
if (this.editablePreferencesEditor) {
|
||||
this.editablePreferencesEditor.dispose();
|
||||
this.editablePreferencesEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.disposeEditors();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class EditableSettingsEditor extends BaseTextEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.settingsEditor';
|
||||
|
||||
private modelDisposables: IDisposable[] = [];
|
||||
private saveDelayer: Delayer<void>;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(EditableSettingsEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
|
||||
this._register({ dispose: () => dispose(this.modelDisposables) });
|
||||
this.saveDelayer = new Delayer<void>(1000);
|
||||
}
|
||||
|
||||
protected createEditor(parent: Builder): void {
|
||||
super.createEditor(parent);
|
||||
|
||||
const codeEditor = getCodeEditor(this);
|
||||
if (codeEditor) {
|
||||
this._register(codeEditor.onDidChangeModel(() => this.onDidModelChange()));
|
||||
}
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
const input = this.input;
|
||||
const inputName = input && input.getName();
|
||||
|
||||
let ariaLabel: string;
|
||||
if (inputName) {
|
||||
ariaLabel = nls.localize('fileEditorWithInputAriaLabel', "{0}. Text file editor.", inputName);
|
||||
} else {
|
||||
ariaLabel = nls.localize('fileEditorAriaLabel', "Text file editor.");
|
||||
}
|
||||
|
||||
return ariaLabel;
|
||||
}
|
||||
|
||||
setInput(input: EditorInput, options: EditorOptions): TPromise<void> {
|
||||
return super.setInput(input, options)
|
||||
.then(() => this.input.resolve()
|
||||
.then(editorModel => editorModel.load())
|
||||
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
|
||||
}
|
||||
|
||||
clearInput(): void {
|
||||
this.modelDisposables = dispose(this.modelDisposables);
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
private onDidModelChange(): void {
|
||||
this.modelDisposables = dispose(this.modelDisposables);
|
||||
const model = getCodeEditor(this).getModel();
|
||||
if (model) {
|
||||
this.preferencesService.createPreferencesEditorModel(model.uri)
|
||||
.then(preferencesEditorModel => {
|
||||
const settingsEditorModel = <SettingsEditorModel>preferencesEditorModel;
|
||||
this.modelDisposables.push(settingsEditorModel);
|
||||
this.modelDisposables.push(model.onDidChangeContent(() => this.saveDelayer.trigger(() => settingsEditorModel.save())));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultPreferencesEditor extends BaseTextEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.defaultPreferences';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IModeService modeService: IModeService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, modeService, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
public createEditorControl(parent: Builder, configuration: IEditorOptions): editorCommon.IEditor {
|
||||
return this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parent.getHTMLElement(), configuration);
|
||||
}
|
||||
|
||||
protected getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
options.readOnly = true;
|
||||
if (this.input) {
|
||||
options.lineNumbers = 'off';
|
||||
options.renderLineHighlight = 'none';
|
||||
options.scrollBeyondLastLine = false;
|
||||
options.folding = false;
|
||||
options.renderWhitespace = 'none';
|
||||
options.wordWrap = 'on';
|
||||
options.renderIndentGuides = false;
|
||||
options.rulers = [];
|
||||
options.glyphMargin = true;
|
||||
options.minimap = {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
setInput(input: DefaultPreferencesEditorInput, options: EditorOptions): TPromise<void> {
|
||||
return super.setInput(input, options)
|
||||
.then(() => this.input.resolve()
|
||||
.then(editorModel => editorModel.load())
|
||||
.then(editorModel => this.getControl().setModel((<ResourceEditorModel>editorModel).textEditorModel)));
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension) {
|
||||
this.getControl().layout(dimension);
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
return nls.localize('preferencesAriaLabel', "Default preferences. Readonly text editor.");
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultPreferencesCodeEditor extends CodeEditor {
|
||||
|
||||
protected _getContributions(): IEditorContributionCtor[] {
|
||||
let contributions = super._getContributions();
|
||||
let skipContributions = [FoldingController.prototype, SelectionHighlighter.prototype, FindController.prototype];
|
||||
contributions = contributions.filter(c => skipContributions.indexOf(c.prototype) === -1);
|
||||
contributions.push(DefaultSettingsEditorContribution);
|
||||
return contributions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ISettingsEditorContribution extends editorCommon.IEditorContribution {
|
||||
|
||||
updatePreferencesRenderer(associatedPreferencesModelUri: URI): TPromise<IPreferencesRenderer<ISetting>>;
|
||||
|
||||
}
|
||||
|
||||
abstract class AbstractSettingsEditorContribution extends Disposable {
|
||||
|
||||
private preferencesRendererCreationPromise: TPromise<IPreferencesRenderer<ISetting>>;
|
||||
|
||||
constructor(protected editor: ICodeEditor,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IPreferencesService protected preferencesService: IPreferencesService,
|
||||
@IWorkspaceContextService protected workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
this._register(this.editor.onDidChangeModel(() => this._onModelChanged()));
|
||||
}
|
||||
|
||||
updatePreferencesRenderer(associatedPreferencesModelUri: URI): TPromise<IPreferencesRenderer<ISetting>> {
|
||||
if (!this.preferencesRendererCreationPromise) {
|
||||
this.preferencesRendererCreationPromise = this._createPreferencesRenderer();
|
||||
}
|
||||
|
||||
if (this.preferencesRendererCreationPromise) {
|
||||
return this._hasAssociatedPreferencesModelChanged(associatedPreferencesModelUri)
|
||||
.then(changed => changed ? this._updatePreferencesRenderer(associatedPreferencesModelUri) : this.preferencesRendererCreationPromise);
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private _onModelChanged(): void {
|
||||
const model = this.editor.getModel();
|
||||
this.disposePreferencesRenderer();
|
||||
if (model) {
|
||||
this.preferencesRendererCreationPromise = this._createPreferencesRenderer();
|
||||
}
|
||||
}
|
||||
|
||||
private _hasAssociatedPreferencesModelChanged(associatedPreferencesModelUri: URI): TPromise<boolean> {
|
||||
return this.preferencesRendererCreationPromise.then(preferencesRenderer => {
|
||||
return !(preferencesRenderer && preferencesRenderer.associatedPreferencesModel && preferencesRenderer.associatedPreferencesModel.uri.fsPath === associatedPreferencesModelUri.fsPath);
|
||||
});
|
||||
}
|
||||
|
||||
private _updatePreferencesRenderer(associatedPreferencesModelUri: URI): TPromise<IPreferencesRenderer<ISetting>> {
|
||||
return this.preferencesService.createPreferencesEditorModel<ISetting>(associatedPreferencesModelUri)
|
||||
.then(associatedPreferencesEditorModel => {
|
||||
return this.preferencesRendererCreationPromise.then(preferencesRenderer => {
|
||||
if (preferencesRenderer) {
|
||||
if (preferencesRenderer.associatedPreferencesModel) {
|
||||
preferencesRenderer.associatedPreferencesModel.dispose();
|
||||
}
|
||||
preferencesRenderer.associatedPreferencesModel = associatedPreferencesEditorModel;
|
||||
}
|
||||
return preferencesRenderer;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private disposePreferencesRenderer(): void {
|
||||
if (this.preferencesRendererCreationPromise) {
|
||||
this.preferencesRendererCreationPromise.then(preferencesRenderer => {
|
||||
if (preferencesRenderer) {
|
||||
if (preferencesRenderer.associatedPreferencesModel) {
|
||||
preferencesRenderer.associatedPreferencesModel.dispose();
|
||||
}
|
||||
preferencesRenderer.dispose();
|
||||
}
|
||||
});
|
||||
this.preferencesRendererCreationPromise = TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposePreferencesRenderer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected abstract _createPreferencesRenderer(): TPromise<IPreferencesRenderer<ISetting>>;
|
||||
}
|
||||
|
||||
class DefaultSettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution {
|
||||
|
||||
static ID: string = 'editor.contrib.defaultsettings';
|
||||
|
||||
getId(): string {
|
||||
return DefaultSettingsEditorContribution.ID;
|
||||
}
|
||||
|
||||
protected _createPreferencesRenderer(): TPromise<IPreferencesRenderer<ISetting>> {
|
||||
return this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri)
|
||||
.then(editorModel => {
|
||||
if (editorModel instanceof DefaultSettingsEditorModel && this.editor.getModel()) {
|
||||
const preferencesRenderer = this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel);
|
||||
preferencesRenderer.render();
|
||||
return preferencesRenderer;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@editorContribution
|
||||
class SettingsEditorContribution extends AbstractSettingsEditorContribution implements ISettingsEditorContribution {
|
||||
|
||||
static ID: string = 'editor.contrib.settings';
|
||||
|
||||
getId(): string {
|
||||
return SettingsEditorContribution.ID;
|
||||
}
|
||||
|
||||
protected _createPreferencesRenderer(): TPromise<IPreferencesRenderer<ISetting>> {
|
||||
if (this.isSettingsModel()) {
|
||||
return TPromise.join<any>([this.preferencesService.createPreferencesEditorModel(this.preferencesService.defaultSettingsResource), this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri)])
|
||||
.then(([defaultSettingsModel, settingsModel]) => {
|
||||
if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) {
|
||||
switch (settingsModel.configurationTarget) {
|
||||
case ConfigurationTarget.USER:
|
||||
return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
|
||||
case ConfigurationTarget.WORKSPACE:
|
||||
return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
|
||||
case ConfigurationTarget.FOLDER:
|
||||
return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel, defaultSettingsModel);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.then(preferencesRenderer => {
|
||||
if (preferencesRenderer) {
|
||||
preferencesRenderer.render();
|
||||
}
|
||||
return preferencesRenderer;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private isSettingsModel(): boolean {
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.preferencesService.userSettingsResource && this.preferencesService.userSettingsResource.fsPath === model.uri.fsPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.preferencesService.workspaceSettingsResource && this.preferencesService.workspaceSettingsResource.fsPath === model.uri.fsPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const workspace = this.workspaceContextService.getWorkspace();
|
||||
if (workspace) {
|
||||
for (const root of workspace.roots) {
|
||||
const folderSettingsResource = this.preferencesService.getFolderSettingsResource(root);
|
||||
if (folderSettingsResource && folderSettingsResource.fsPath === model.uri.fsPath) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
abstract class SettingsCommand extends Command {
|
||||
|
||||
protected getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor {
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
if (activeEditor instanceof PreferencesEditor) {
|
||||
return activeEditor;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
class StartSearchDefaultSettingsCommand extends SettingsCommand {
|
||||
|
||||
public runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor) {
|
||||
preferencesEditor.focusSearch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const command = new StartSearchDefaultSettingsCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_SEARCH,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR),
|
||||
kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.KEY_F }
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(command.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
|
||||
|
||||
class FocusSettingsFileEditorCommand extends SettingsCommand {
|
||||
|
||||
public runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor) {
|
||||
preferencesEditor.focusSettingsFileEditor();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const focusSettingsFileEditorCommand = new FocusSettingsFileEditorCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE,
|
||||
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
|
||||
kbOpts: { primary: KeyCode.DownArrow }
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(focusSettingsFileEditorCommand.toCommandAndKeybindingRule(KeybindingsRegistry.WEIGHT.editorContrib()));
|
||||
1109
src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts
Normal file
422
src/vs/workbench/parts/preferences/browser/preferencesService.ts
Normal file
@@ -0,0 +1,422 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/preferences';
|
||||
import * as network from 'vs/base/common/network';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import * as labels from 'vs/base/common/labels';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { EditorInput } 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 { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/common/message';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IPreferencesService, IPreferencesEditorModel, ISetting, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { SettingsEditorModel, DefaultSettingsEditorModel, DefaultKeybindingsEditorModel, defaultKeybindingsContents, WorkspaceConfigModel } 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 { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
||||
|
||||
interface IWorkbenchSettingsConfiguration {
|
||||
workbench: {
|
||||
settings: {
|
||||
openDefaultSettings: boolean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const emptyEditableSettingsContent = '{\n}';
|
||||
|
||||
export class PreferencesService extends Disposable implements IPreferencesService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
// TODO:@sandy merge these models into editor inputs by extending resource editor model
|
||||
private defaultPreferencesEditorModels: ResourceMap<TPromise<IPreferencesEditorModel<any>>>;
|
||||
private lastOpenedSettingsInput: PreferencesEditorInput = null;
|
||||
|
||||
private _onDispose: Emitter<void> = new Emitter<void>();
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IChoiceService private choiceService: IChoiceService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@ITextModelService private textModelResolverService: ITextModelService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IJSONEditingService private jsonEditingService: IJSONEditingService
|
||||
) {
|
||||
super();
|
||||
this.defaultPreferencesEditorModels = new ResourceMap<TPromise<IPreferencesEditorModel<any>>>();
|
||||
this.editorGroupService.onEditorsChanged(() => {
|
||||
const activeEditorInput = this.editorService.getActiveEditorInput();
|
||||
if (activeEditorInput instanceof PreferencesEditorInput) {
|
||||
this.lastOpenedSettingsInput = activeEditorInput;
|
||||
}
|
||||
});
|
||||
|
||||
// The default keybindings.json updates based on keyboard layouts, so here we make sure
|
||||
// if a model has been given out we update it accordingly.
|
||||
keybindingService.onDidUpdateKeybindings(() => {
|
||||
const model = modelService.getModel(this.defaultKeybindingsResource);
|
||||
if (!model) {
|
||||
// model has not been given out => nothing to do
|
||||
return;
|
||||
}
|
||||
modelService.updateModel(model, defaultKeybindingsContents(keybindingService));
|
||||
});
|
||||
}
|
||||
|
||||
readonly defaultSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' });
|
||||
readonly defaultResourceSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/resourceSettings.json' });
|
||||
readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' });
|
||||
private readonly workspaceConfigSettingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'settings', path: '/workspaceSettings.json' });
|
||||
|
||||
get userSettingsResource(): URI {
|
||||
return this.getEditableSettingsURI(ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
get workspaceSettingsResource(): URI {
|
||||
return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE);
|
||||
}
|
||||
|
||||
getFolderSettingsResource(resource: URI): URI {
|
||||
return this.getEditableSettingsURI(ConfigurationTarget.FOLDER, resource);
|
||||
}
|
||||
|
||||
resolveContent(uri: URI): TPromise<string> {
|
||||
const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE);
|
||||
if (workspaceSettingsUri && workspaceSettingsUri.fsPath === uri.fsPath) {
|
||||
return this.resolveSettingsContentFromWorkspaceConfiguration();
|
||||
}
|
||||
return this.createPreferencesEditorModel(uri)
|
||||
.then(preferencesEditorModel => preferencesEditorModel ? preferencesEditorModel.content : null);
|
||||
}
|
||||
|
||||
createPreferencesEditorModel(uri: URI): TPromise<IPreferencesEditorModel<any>> {
|
||||
let promise = this.defaultPreferencesEditorModels.get(uri);
|
||||
if (promise) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (this.defaultSettingsResource.fsPath === uri.fsPath) {
|
||||
promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
|
||||
.then(result => {
|
||||
const mostCommonSettings = result[1];
|
||||
const model = this.instantiationService.createInstance(DefaultSettingsEditorModel, uri, mostCommonSettings, ConfigurationScope.WINDOW);
|
||||
return model;
|
||||
});
|
||||
this.defaultPreferencesEditorModels.set(uri, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (this.defaultResourceSettingsResource.fsPath === uri.fsPath) {
|
||||
promise = TPromise.join<any>([this.extensionService.onReady(), this.fetchMostCommonlyUsedSettings()])
|
||||
.then(result => {
|
||||
const mostCommonSettings = result[1];
|
||||
const model = this.instantiationService.createInstance(DefaultSettingsEditorModel, uri, mostCommonSettings, ConfigurationScope.RESOURCE);
|
||||
return model;
|
||||
});
|
||||
this.defaultPreferencesEditorModels.set(uri, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (this.defaultKeybindingsResource.fsPath === uri.fsPath) {
|
||||
const model = this.instantiationService.createInstance(DefaultKeybindingsEditorModel, uri);
|
||||
promise = TPromise.wrap(model);
|
||||
this.defaultPreferencesEditorModels.set(uri, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (this.workspaceConfigSettingsResource.fsPath === uri.fsPath) {
|
||||
promise = this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, uri);
|
||||
this.defaultPreferencesEditorModels.set(uri, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
if (this.getEditableSettingsURI(ConfigurationTarget.USER).fsPath === uri.fsPath) {
|
||||
return this.createEditableSettingsEditorModel(ConfigurationTarget.USER, uri);
|
||||
}
|
||||
|
||||
const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE);
|
||||
if (workspaceSettingsUri && workspaceSettingsUri.fsPath === uri.fsPath) {
|
||||
return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, workspaceSettingsUri);
|
||||
}
|
||||
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
return this.createEditableSettingsEditorModel(ConfigurationTarget.FOLDER, uri);
|
||||
}
|
||||
|
||||
return TPromise.wrap<IPreferencesEditorModel<any>>(null);
|
||||
}
|
||||
|
||||
openGlobalSettings(): TPromise<IEditor> {
|
||||
return this.doOpenSettings(ConfigurationTarget.USER, this.userSettingsResource);
|
||||
}
|
||||
|
||||
openWorkspaceSettings(): TPromise<IEditor> {
|
||||
if (!this.contextService.hasWorkspace()) {
|
||||
this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create workspace settings"));
|
||||
return TPromise.as(null);
|
||||
}
|
||||
return this.doOpenSettings(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource);
|
||||
}
|
||||
|
||||
openFolderSettings(folder: URI): TPromise<IEditor> {
|
||||
return this.doOpenSettings(ConfigurationTarget.FOLDER, this.getEditableSettingsURI(ConfigurationTarget.FOLDER, folder));
|
||||
}
|
||||
|
||||
switchSettings(target: ConfigurationTarget, resource: URI): TPromise<void> {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const activeEditorInput = activeEditor.input;
|
||||
if (activeEditorInput instanceof PreferencesEditorInput) {
|
||||
return this.getOrCreateEditableSettingsEditorInput(target, this.getEditableSettingsURI(target, resource))
|
||||
.then(toInput => {
|
||||
const replaceWith = new PreferencesEditorInput(this.getPreferencesEditorInputName(target, resource), toInput.getDescription(), this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(target)), toInput);
|
||||
return this.editorService.replaceEditors([{
|
||||
toReplace: this.lastOpenedSettingsInput,
|
||||
replaceWith
|
||||
}], activeEditor.position).then(() => {
|
||||
this.lastOpenedSettingsInput = replaceWith;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.doOpenSettings(target, resource);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
openGlobalKeybindingSettings(textual: boolean): TPromise<void> {
|
||||
this.telemetryService.publicLog('openKeybindings', { textual });
|
||||
if (textual) {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true }).then(() => null);
|
||||
}
|
||||
|
||||
configureSettingsForLanguage(language: string): void {
|
||||
this.openGlobalSettings()
|
||||
.then(editor => {
|
||||
const codeEditor = getCodeEditor(editor);
|
||||
this.getPosition(language, codeEditor)
|
||||
.then(position => {
|
||||
codeEditor.setPosition(position);
|
||||
codeEditor.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI): TPromise<IEditor> {
|
||||
const openDefaultSettings = !!this.configurationService.getConfiguration<IWorkbenchSettingsConfiguration>().workbench.settings.openDefaultSettings;
|
||||
return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource)
|
||||
.then(editableSettingsEditorInput => {
|
||||
if (openDefaultSettings) {
|
||||
const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(configurationTarget));
|
||||
const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, <EditorInput>editableSettingsEditorInput);
|
||||
this.lastOpenedSettingsInput = preferencesEditorInput;
|
||||
return this.editorService.openEditor(preferencesEditorInput, { pinned: true });
|
||||
}
|
||||
return this.editorService.openEditor(editableSettingsEditorInput, { pinned: true });
|
||||
});
|
||||
}
|
||||
|
||||
private getDefaultSettingsResource(configurationTarget: ConfigurationTarget): URI {
|
||||
if (configurationTarget === ConfigurationTarget.FOLDER) {
|
||||
return this.defaultResourceSettingsResource;
|
||||
}
|
||||
return this.defaultSettingsResource;
|
||||
}
|
||||
|
||||
private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string {
|
||||
const name = getSettingsTargetName(target, resource, this.contextService);
|
||||
return target === ConfigurationTarget.FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name;
|
||||
}
|
||||
|
||||
private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): TPromise<EditorInput> {
|
||||
return this.createSettingsIfNotExists(target, resource)
|
||||
.then(() => <EditorInput>this.editorService.createInput({ resource }));
|
||||
}
|
||||
|
||||
private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, resource: URI): TPromise<SettingsEditorModel> {
|
||||
const settingsUri = this.getEditableSettingsURI(configurationTarget, resource);
|
||||
if (settingsUri) {
|
||||
if (settingsUri.fsPath === this.workspaceConfigSettingsResource.fsPath) {
|
||||
return TPromise.join([this.textModelResolverService.createModelReference(settingsUri), this.textModelResolverService.createModelReference(this.contextService.getWorkspace().configuration)])
|
||||
.then(([reference, workspaceConfigReference]) => this.instantiationService.createInstance(WorkspaceConfigModel, reference, workspaceConfigReference, configurationTarget, this._onDispose.event));
|
||||
}
|
||||
return this.textModelResolverService.createModelReference(settingsUri)
|
||||
.then(reference => this.instantiationService.createInstance(SettingsEditorModel, reference, configurationTarget));
|
||||
}
|
||||
return TPromise.wrap<SettingsEditorModel>(null);
|
||||
}
|
||||
|
||||
private resolveSettingsContentFromWorkspaceConfiguration(): TPromise<string> {
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
return this.textModelResolverService.createModelReference(this.contextService.getWorkspace().configuration)
|
||||
.then(reference => {
|
||||
const model = reference.object.textEditorModel;
|
||||
const settingsContent = WorkspaceConfigModel.getSettingsContentFromConfigContent(model.getValue());
|
||||
reference.dispose();
|
||||
return TPromise.as(settingsContent ? settingsContent : emptyEditableSettingsContent);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI {
|
||||
switch (configurationTarget) {
|
||||
case ConfigurationTarget.USER:
|
||||
return URI.file(this.environmentService.appSettingsPath);
|
||||
case ConfigurationTarget.WORKSPACE:
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
if (this.contextService.hasFolderWorkspace()) {
|
||||
// {{SQL CARBON EDIT}}
|
||||
return this.toResource(paths.join('.sqlops', 'settings.json'), workspace.roots[0]);
|
||||
}
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
return workspace.configuration;
|
||||
}
|
||||
return null;
|
||||
case ConfigurationTarget.FOLDER:
|
||||
const root = this.contextService.getRoot(resource);
|
||||
// {{SQL CARBON EDIT}}
|
||||
return root ? this.toResource(paths.join('.sqlops', 'settings.json'), root) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private toResource(relativePath: string, root: URI): URI {
|
||||
return URI.file(paths.join(root.fsPath, relativePath));
|
||||
}
|
||||
|
||||
private createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): TPromise<void> {
|
||||
if (this.contextService.hasMultiFolderWorkspace() && target === ConfigurationTarget.WORKSPACE) {
|
||||
if (!this.configurationService.keys().workspace.length) {
|
||||
return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(null, () => { });
|
||||
}
|
||||
}
|
||||
return this.createIfNotExists(resource, emptyEditableSettingsContent).then(() => { });
|
||||
}
|
||||
|
||||
private createIfNotExists(resource: URI, contents: string): TPromise<any> {
|
||||
return this.fileService.resolveContent(resource, { acceptTextOnly: true }).then(null, error => {
|
||||
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
|
||||
return this.fileService.updateContent(resource, contents).then(null, error => {
|
||||
return TPromise.wrapError(new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", labels.getPathLabel(resource, this.contextService, this.environmentService), error)));
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.wrapError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private fetchMostCommonlyUsedSettings(): TPromise<string[]> {
|
||||
return TPromise.wrap([
|
||||
'files.autoSave',
|
||||
'editor.fontSize',
|
||||
'editor.fontFamily',
|
||||
'editor.tabSize',
|
||||
'editor.renderWhitespace',
|
||||
'editor.cursorStyle',
|
||||
'editor.multiCursorModifier',
|
||||
'editor.insertSpaces',
|
||||
'editor.wordWrap',
|
||||
'files.exclude',
|
||||
'files.associations'
|
||||
]);
|
||||
}
|
||||
|
||||
private getPosition(language: string, codeEditor: ICommonCodeEditor): TPromise<IPosition> {
|
||||
return this.createPreferencesEditorModel(this.userSettingsResource)
|
||||
.then((settingsModel: IPreferencesEditorModel<ISetting>) => {
|
||||
const languageKey = `[${language}]`;
|
||||
let setting = settingsModel.getPreference(languageKey);
|
||||
const model = codeEditor.getModel();
|
||||
const configuration = this.configurationService.getConfiguration<{ tabSize: number; insertSpaces: boolean }>('editor');
|
||||
const { eol } = this.configurationService.getConfiguration<{ eol: string }>('files');
|
||||
if (setting) {
|
||||
if (setting.overrides.length) {
|
||||
const lastSetting = setting.overrides[setting.overrides.length - 1];
|
||||
let content;
|
||||
if (lastSetting.valueRange.endLineNumber === setting.range.endLineNumber) {
|
||||
content = ',' + eol + this.spaces(2, configuration) + eol + this.spaces(1, configuration);
|
||||
} else {
|
||||
content = ',' + eol + this.spaces(2, configuration);
|
||||
}
|
||||
const editOperation = EditOperation.insert(new Position(lastSetting.valueRange.endLineNumber, lastSetting.valueRange.endColumn), content);
|
||||
model.pushEditOperations([], [editOperation], () => []);
|
||||
return { lineNumber: lastSetting.valueRange.endLineNumber + 1, column: model.getLineMaxColumn(lastSetting.valueRange.endLineNumber + 1) };
|
||||
}
|
||||
return { lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 };
|
||||
}
|
||||
return this.configurationEditingService.writeConfiguration(ConfigurationTarget.USER, { key: languageKey, value: {} }, { donotSave: true })
|
||||
.then(() => {
|
||||
setting = settingsModel.getPreference(languageKey);
|
||||
let content = eol + this.spaces(2, configuration) + eol + this.spaces(1, configuration);
|
||||
let editOperation = EditOperation.insert(new Position(setting.valueRange.endLineNumber, setting.valueRange.endColumn - 1), content);
|
||||
model.pushEditOperations([], [editOperation], () => []);
|
||||
let lineNumber = setting.valueRange.endLineNumber + 1;
|
||||
settingsModel.dispose();
|
||||
return { lineNumber, column: model.getLineMaxColumn(lineNumber) };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private spaces(count: number, { tabSize, insertSpaces }: { tabSize: number; insertSpaces: boolean }): string {
|
||||
return insertSpaces ? strings.repeat(' ', tabSize * count) : strings.repeat('\t', count);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onDispose.fire();
|
||||
this.defaultPreferencesEditorModels.clear();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
652
src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts
Normal file
@@ -0,0 +1,652 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference, IViewZone, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
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, IPreferencesService, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { attachInputBoxStyler, attachStylerCallback, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { buttonBackground, buttonForeground, badgeForeground, badgeBackground, contrastBorder, errorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ISelectBoxStyles, defaultStyles } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
export class SettingsHeaderWidget extends Widget implements IViewZone {
|
||||
|
||||
private id: number;
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
private titleContainer: HTMLElement;
|
||||
private messageElement: HTMLElement;
|
||||
|
||||
constructor(private editor: ICodeEditor, private title: string) {
|
||||
super();
|
||||
this.create();
|
||||
this._register(this.editor.onDidChangeConfiguration(() => this.layout()));
|
||||
this._register(this.editor.onDidLayoutChange(() => this.layout()));
|
||||
}
|
||||
|
||||
get domNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
get heightInLines(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
get afterLineNumber(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private create() {
|
||||
this._domNode = DOM.$('.settings-header-widget');
|
||||
|
||||
this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container'));
|
||||
if (this.title) {
|
||||
DOM.append(this.titleContainer, DOM.$('.title')).textContent = this.title;
|
||||
}
|
||||
this.messageElement = DOM.append(this.titleContainer, DOM.$('.message'));
|
||||
if (this.title) {
|
||||
this.messageElement.style.paddingLeft = '12px';
|
||||
}
|
||||
|
||||
this.editor.changeViewZones(accessor => {
|
||||
this.id = accessor.addZone(this);
|
||||
this.layout();
|
||||
});
|
||||
}
|
||||
|
||||
public setMessage(message: string): void {
|
||||
this.messageElement.textContent = message;
|
||||
}
|
||||
|
||||
private layout(): void {
|
||||
const configuration = this.editor.getConfiguration();
|
||||
this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px';
|
||||
if (!configuration.contribInfo.folding) {
|
||||
this.titleContainer.style.paddingLeft = '12px';
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.editor.changeViewZones(accessor => {
|
||||
accessor.removeZone(this.id);
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsGroupTitleWidget extends Widget implements IViewZone {
|
||||
|
||||
private id: number;
|
||||
private _afterLineNumber: number;
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
private titleContainer: HTMLElement;
|
||||
private icon: HTMLElement;
|
||||
private title: HTMLElement;
|
||||
|
||||
private _onToggled = this._register(new Emitter<boolean>());
|
||||
public onToggled: Event<boolean> = this._onToggled.event;
|
||||
|
||||
private previousPosition: Position;
|
||||
|
||||
constructor(private editor: ICodeEditor, public settingsGroup: ISettingsGroup) {
|
||||
super();
|
||||
this.create();
|
||||
this._register(this.editor.onDidChangeConfiguration(() => this.layout()));
|
||||
this._register(this.editor.onDidLayoutChange(() => this.layout()));
|
||||
this._register(this.editor.onDidChangeCursorPosition((e) => this.onCursorChange(e)));
|
||||
}
|
||||
|
||||
get domNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
get heightInLines(): number {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
get afterLineNumber(): number {
|
||||
return this._afterLineNumber;
|
||||
}
|
||||
|
||||
private create() {
|
||||
this._domNode = DOM.$('.settings-group-title-widget');
|
||||
|
||||
this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container'));
|
||||
this.titleContainer.tabIndex = 0;
|
||||
this.onclick(this.titleContainer, () => this.toggle());
|
||||
this.onkeydown(this.titleContainer, (e) => this.onKeyDown(e));
|
||||
const focusTracker = this._register(DOM.trackFocus(this.titleContainer));
|
||||
focusTracker.addFocusListener(() => this.toggleFocus(true));
|
||||
focusTracker.addBlurListener(() => this.toggleFocus(false));
|
||||
|
||||
this.icon = DOM.append(this.titleContainer, DOM.$('.expand-collapse-icon'));
|
||||
this.title = DOM.append(this.titleContainer, DOM.$('.title'));
|
||||
this.title.textContent = this.settingsGroup.title + ` (${this.settingsGroup.sections.reduce((count, section) => count + section.settings.length, 0)})`;
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public render() {
|
||||
this._afterLineNumber = this.settingsGroup.range.startLineNumber - 2;
|
||||
this.editor.changeViewZones(accessor => {
|
||||
this.id = accessor.addZone(this);
|
||||
this.layout();
|
||||
});
|
||||
}
|
||||
|
||||
public toggleCollapse(collapse: boolean) {
|
||||
DOM.toggleClass(this.titleContainer, 'collapsed', collapse);
|
||||
}
|
||||
|
||||
public toggleFocus(focus: boolean): void {
|
||||
DOM.toggleClass(this.titleContainer, 'focused', focus);
|
||||
}
|
||||
|
||||
public isCollapsed(): boolean {
|
||||
return DOM.hasClass(this.titleContainer, 'collapsed');
|
||||
}
|
||||
|
||||
private layout(): void {
|
||||
const configuration = this.editor.getConfiguration();
|
||||
const layoutInfo = this.editor.getLayoutInfo();
|
||||
this._domNode.style.width = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth + 'px';
|
||||
this.titleContainer.style.lineHeight = configuration.lineHeight + 3 + 'px';
|
||||
this.titleContainer.style.height = configuration.lineHeight + 3 + 'px';
|
||||
this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px';
|
||||
this.icon.style.minWidth = `${this.getIconSize(16)}px`;
|
||||
}
|
||||
|
||||
private getIconSize(minSize: number): number {
|
||||
const fontSize = this.editor.getConfiguration().fontInfo.fontSize;
|
||||
return fontSize > 8 ? Math.max(fontSize, minSize) : 12;
|
||||
}
|
||||
|
||||
private onKeyDown(keyboardEvent: IKeyboardEvent): void {
|
||||
switch (keyboardEvent.keyCode) {
|
||||
case KeyCode.Enter:
|
||||
case KeyCode.Space:
|
||||
this.toggle();
|
||||
break;
|
||||
case KeyCode.LeftArrow:
|
||||
this.collapse(true);
|
||||
break;
|
||||
case KeyCode.RightArrow:
|
||||
this.collapse(false);
|
||||
break;
|
||||
case KeyCode.UpArrow:
|
||||
if (this.settingsGroup.range.startLineNumber - 3 !== 1) {
|
||||
this.editor.focus();
|
||||
const lineNumber = this.settingsGroup.range.startLineNumber - 2;
|
||||
this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) });
|
||||
}
|
||||
break;
|
||||
case KeyCode.DownArrow:
|
||||
const lineNumber = this.isCollapsed() ? this.settingsGroup.range.startLineNumber : this.settingsGroup.range.startLineNumber - 1;
|
||||
this.editor.focus();
|
||||
this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private toggle() {
|
||||
this.collapse(!this.isCollapsed());
|
||||
}
|
||||
|
||||
private collapse(collapse: boolean) {
|
||||
if (collapse !== this.isCollapsed()) {
|
||||
DOM.toggleClass(this.titleContainer, 'collapsed', collapse);
|
||||
this._onToggled.fire(collapse);
|
||||
}
|
||||
}
|
||||
|
||||
private onCursorChange(e: ICursorPositionChangedEvent): void {
|
||||
if (e.source !== 'mouse' && this.focusTitle(e.position)) {
|
||||
this.titleContainer.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private focusTitle(currentPosition: Position): boolean {
|
||||
const previousPosition = this.previousPosition;
|
||||
this.previousPosition = currentPosition;
|
||||
if (!previousPosition) {
|
||||
return false;
|
||||
}
|
||||
if (previousPosition.lineNumber === currentPosition.lineNumber) {
|
||||
return false;
|
||||
}
|
||||
if (currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 1 || currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 2) {
|
||||
return true;
|
||||
}
|
||||
if (this.isCollapsed() && currentPosition.lineNumber === this.settingsGroup.range.endLineNumber) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.editor.changeViewZones(accessor => {
|
||||
accessor.removeZone(this.id);
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsTargetsWidget extends Widget {
|
||||
|
||||
public actionRunner: IActionRunner;
|
||||
private settingsTargetsContainer: HTMLSelectElement;
|
||||
private targetLabel: HTMLSelectElement;
|
||||
private targetDetails: HTMLSelectElement;
|
||||
|
||||
private _onDidTargetChange: Emitter<URI> = new Emitter<URI>();
|
||||
public readonly onDidTargetChange: Event<URI> = this._onDidTargetChange.event;
|
||||
|
||||
private borderColor: Color;
|
||||
|
||||
constructor(parent: HTMLElement, private uri: URI, private target: ConfigurationTarget,
|
||||
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService) {
|
||||
super();
|
||||
|
||||
this.borderColor = defaultStyles.selectBorder;
|
||||
this.create(parent);
|
||||
this._register(attachSelectBoxStyler(this, themeService, {
|
||||
selectBackground: SIDE_BAR_BACKGROUND
|
||||
}));
|
||||
}
|
||||
|
||||
public setTarget(uri: URI, target: ConfigurationTarget): void {
|
||||
this.uri = uri;
|
||||
this.target = target;
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
private create(parent: HTMLElement): void {
|
||||
this.settingsTargetsContainer = DOM.append(parent, DOM.$('.settings-targets-widget'));
|
||||
this.settingsTargetsContainer.style.width = this.workspaceContextService.hasMultiFolderWorkspace() ? '200px' : '150px';
|
||||
|
||||
const targetElement = DOM.append(this.settingsTargetsContainer, DOM.$('.settings-target'));
|
||||
this.targetLabel = DOM.append(targetElement, DOM.$('.settings-target-label'));
|
||||
this.targetDetails = DOM.append(targetElement, DOM.$('.settings-target-details'));
|
||||
this.updateLabel();
|
||||
|
||||
this.onclick(this.settingsTargetsContainer, e => this.showContextMenu(e));
|
||||
|
||||
DOM.append(this.settingsTargetsContainer, DOM.$('.settings-target-dropdown-icon.octicon.octicon-triangle-down'));
|
||||
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
this.targetLabel.textContent = getSettingsTargetName(this.target, this.uri, this.workspaceContextService);
|
||||
const details = ConfigurationTarget.FOLDER === this.target ? localize('folderSettingsDetails', "Folder Settings") : '';
|
||||
this.targetDetails.textContent = details;
|
||||
DOM.toggleClass(this.targetDetails, 'empty', !details);
|
||||
}
|
||||
|
||||
private showContextMenu(event: IMouseEvent): void {
|
||||
const actions = this.getSettingsTargetsActions();
|
||||
let elementPosition = DOM.getDomNodePagePosition(this.settingsTargetsContainer);
|
||||
const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 5 };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.wrap(actions)
|
||||
});
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private getSettingsTargetsActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
const userSettingsResource = this.preferencesService.userSettingsResource;
|
||||
actions.push(<IAction>{
|
||||
id: 'userSettingsTarget',
|
||||
label: getSettingsTargetName(ConfigurationTarget.USER, userSettingsResource, this.workspaceContextService),
|
||||
checked: this.uri.fsPath === userSettingsResource.fsPath,
|
||||
enabled: true,
|
||||
run: () => this.onTargetClicked(userSettingsResource)
|
||||
});
|
||||
|
||||
if (this.workspaceContextService.hasWorkspace()) {
|
||||
const workspaceSettingsResource = this.preferencesService.workspaceSettingsResource;
|
||||
actions.push(<IAction>{
|
||||
id: 'workspaceSettingsTarget',
|
||||
label: getSettingsTargetName(ConfigurationTarget.WORKSPACE, workspaceSettingsResource, this.workspaceContextService),
|
||||
checked: this.uri.fsPath === workspaceSettingsResource.fsPath,
|
||||
enabled: true,
|
||||
run: () => this.onTargetClicked(workspaceSettingsResource)
|
||||
});
|
||||
}
|
||||
|
||||
if (this.workspaceContextService.hasMultiFolderWorkspace()) {
|
||||
actions.push(new Separator());
|
||||
actions.push(...this.workspaceContextService.getWorkspace().roots.map((root, index) => {
|
||||
return <IAction>{
|
||||
id: 'folderSettingsTarget' + index,
|
||||
label: getSettingsTargetName(ConfigurationTarget.FOLDER, root, this.workspaceContextService),
|
||||
checked: this.uri.fsPath === root.fsPath,
|
||||
enabled: true,
|
||||
run: () => this.onTargetClicked(root)
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private onTargetClicked(target: URI): void {
|
||||
if (this.uri.fsPath === target.fsPath) {
|
||||
return;
|
||||
}
|
||||
this._onDidTargetChange.fire(target);
|
||||
}
|
||||
|
||||
style(styles: ISelectBoxStyles): void {
|
||||
this.borderColor = styles.selectBorder;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
private applyStyles(): void {
|
||||
if (this.settingsTargetsContainer) {
|
||||
this.settingsTargetsContainer.style.border = this.borderColor ? `1px solid ${this.borderColor}` : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchOptions extends IInputOptions {
|
||||
focusKey?: IContextKey<boolean>;
|
||||
}
|
||||
|
||||
export class SearchWidget extends Widget {
|
||||
|
||||
public domNode: HTMLElement;
|
||||
|
||||
private countElement: HTMLElement;
|
||||
private searchContainer: HTMLElement;
|
||||
private inputBox: InputBox;
|
||||
|
||||
private _onDidChange: Emitter<string> = this._register(new Emitter<string>());
|
||||
public readonly onDidChange: Event<string> = this._onDidChange.event;
|
||||
|
||||
private _onNavigate: Emitter<boolean> = this._register(new Emitter<boolean>());
|
||||
public readonly onNavigate: Event<boolean> = this._onNavigate.event;
|
||||
|
||||
private _onFocus: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onFocus: Event<void> = this._onFocus.event;
|
||||
|
||||
constructor(parent: HTMLElement, protected options: SearchOptions,
|
||||
@IContextViewService private contextViewService: IContextViewService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this.create(parent);
|
||||
}
|
||||
|
||||
private create(parent: HTMLElement) {
|
||||
this.domNode = DOM.append(parent, DOM.$('div.settings-header-widget'));
|
||||
this.createSearchContainer(DOM.append(this.domNode, DOM.$('div.settings-search-container')));
|
||||
this.countElement = DOM.append(this.domNode, DOM.$('.settings-count-widget'));
|
||||
this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder }, colors => {
|
||||
const background = colors.badgeBackground ? colors.badgeBackground.toString() : null;
|
||||
const border = colors.contrastBorder ? colors.contrastBorder.toString() : null;
|
||||
|
||||
this.countElement.style.backgroundColor = background;
|
||||
|
||||
this.countElement.style.borderWidth = border ? '1px' : null;
|
||||
this.countElement.style.borderStyle = border ? 'solid' : null;
|
||||
this.countElement.style.borderColor = border;
|
||||
|
||||
this.styleCountElementForeground();
|
||||
}));
|
||||
this.inputBox.inputElement.setAttribute('aria-live', 'assertive');
|
||||
|
||||
const focusTracker = this._register(DOM.trackFocus(this.inputBox.inputElement));
|
||||
this._register(focusTracker.addFocusListener(() => this._onFocus.fire()));
|
||||
|
||||
if (this.options.focusKey) {
|
||||
this._register(focusTracker.addFocusListener(() => this.options.focusKey.set(true)));
|
||||
this._register(focusTracker.addBlurListener(() => this.options.focusKey.set(false)));
|
||||
}
|
||||
}
|
||||
|
||||
private createSearchContainer(searchContainer: HTMLElement) {
|
||||
this.searchContainer = searchContainer;
|
||||
const searchInput = DOM.append(this.searchContainer, DOM.$('div.settings-search-input'));
|
||||
this.inputBox = this._register(this.createInputBox(searchInput));
|
||||
this._register(this.inputBox.onDidChange(value => this._onDidChange.fire(value)));
|
||||
this.onkeydown(this.inputBox.inputElement, (e) => this._onKeyDown(e));
|
||||
}
|
||||
|
||||
protected createInputBox(parent: HTMLElement): InputBox {
|
||||
const box = this._register(new InputBox(parent, this.contextViewService, this.options));
|
||||
this._register(attachInputBoxStyler(box, this.themeService));
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public showMessage(message: string, count: number): void {
|
||||
this.countElement.textContent = message;
|
||||
this.inputBox.inputElement.setAttribute('aria-label', message);
|
||||
DOM.toggleClass(this.countElement, 'no-results', count === 0);
|
||||
this.inputBox.inputElement.style.paddingRight = DOM.getTotalWidth(this.countElement) + 20 + 'px';
|
||||
this.styleCountElementForeground();
|
||||
}
|
||||
|
||||
private styleCountElementForeground() {
|
||||
const colorId = DOM.hasClass(this.countElement, 'no-results') ? errorForeground : badgeForeground;
|
||||
const color = this.themeService.getTheme().getColor(colorId);
|
||||
this.countElement.style.color = color ? color.toString() : null;
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension) {
|
||||
if (dimension.width < 400) {
|
||||
DOM.addClass(this.countElement, 'hide');
|
||||
this.inputBox.inputElement.style.paddingRight = '0px';
|
||||
} else {
|
||||
DOM.removeClass(this.countElement, 'hide');
|
||||
this.inputBox.inputElement.style.paddingRight = DOM.getTotalWidth(this.countElement) + 20 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.inputBox.focus();
|
||||
if (this.getValue()) {
|
||||
this.inputBox.select();
|
||||
}
|
||||
}
|
||||
|
||||
public hasFocus(): boolean {
|
||||
return this.inputBox.hasFocus();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.inputBox.value = '';
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
return this.inputBox.value;
|
||||
}
|
||||
|
||||
public setValue(value: string): string {
|
||||
return this.inputBox.value = value;
|
||||
}
|
||||
|
||||
private _onKeyDown(keyboardEvent: IKeyboardEvent): void {
|
||||
let handled = false;
|
||||
switch (keyboardEvent.keyCode) {
|
||||
case KeyCode.Enter:
|
||||
this._onNavigate.fire(keyboardEvent.shiftKey);
|
||||
handled = true;
|
||||
break;
|
||||
case KeyCode.Escape:
|
||||
this.clear();
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.options.focusKey) {
|
||||
this.options.focusKey.set(false);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class FloatingClickWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
private _onClick: Emitter<void> = this._register(new Emitter<void>());
|
||||
public onClick: Event<void> = this._onClick.event;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
private label: string,
|
||||
private keyBindingAction: string,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
|
||||
if (keyBindingAction) {
|
||||
let keybinding = keybindingService.lookupKeybinding(keyBindingAction);
|
||||
if (keybinding) {
|
||||
this.label += ' (' + keybinding.getLabel() + ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
this._domNode = DOM.$('.floating-click-widget');
|
||||
this._register(attachStylerCallback(this.themeService, { buttonBackground, buttonForeground }, colors => {
|
||||
this._domNode.style.backgroundColor = colors.buttonBackground;
|
||||
this._domNode.style.color = colors.buttonForeground;
|
||||
}));
|
||||
|
||||
DOM.append(this._domNode, DOM.$('')).textContent = this.label;
|
||||
this.onclick(this._domNode, e => this._onClick.fire());
|
||||
this.editor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return 'editor.overlayWidget.floatingClickWidget';
|
||||
}
|
||||
|
||||
public getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
public getPosition(): IOverlayWidgetPosition {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class EditPreferenceWidget<T> extends Disposable {
|
||||
|
||||
public static GLYPH_MARGIN_CLASS_NAME = 'edit-preferences-widget';
|
||||
|
||||
private _line: number;
|
||||
private _preferences: T[];
|
||||
|
||||
private _editPreferenceDecoration: string[];
|
||||
|
||||
private _onClick: Emitter<IEditorMouseEvent> = new Emitter<IEditorMouseEvent>();
|
||||
public get onClick(): Event<IEditorMouseEvent> { return this._onClick.event; }
|
||||
|
||||
constructor(private editor: ICodeEditor
|
||||
) {
|
||||
super();
|
||||
this._editPreferenceDecoration = [];
|
||||
this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => {
|
||||
if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || /* after last line */ e.target.detail || !this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
this._onClick.fire(e);
|
||||
}));
|
||||
}
|
||||
|
||||
get preferences(): T[] {
|
||||
return this._preferences;
|
||||
}
|
||||
|
||||
getLine(): number {
|
||||
return this._line;
|
||||
}
|
||||
|
||||
show(line: number, hoverMessage: string, preferences: T[]): void {
|
||||
this._preferences = preferences;
|
||||
const newDecoration: editorCommon.IModelDeltaDecoration[] = [];
|
||||
this._line = line;
|
||||
newDecoration.push({
|
||||
options: {
|
||||
glyphMarginClassName: EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME,
|
||||
glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage),
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
},
|
||||
range: {
|
||||
startLineNumber: line,
|
||||
startColumn: 1,
|
||||
endLineNumber: line,
|
||||
endColumn: 1
|
||||
}
|
||||
});
|
||||
this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, newDecoration);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, []);
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._editPreferenceDecoration.length > 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.hide();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||