Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
@@ -0,0 +1,337 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Disposable, dispose, toDisposable, IDisposable } 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 { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
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';
|
||||
import { SearchWidget, SearchOptions } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
|
||||
export interface KeybindingsSearchOptions extends SearchOptions {
|
||||
recordEnter?: boolean;
|
||||
quoteRecordedKeys?: boolean;
|
||||
}
|
||||
|
||||
export class KeybindingsSearchWidget extends SearchWidget {
|
||||
|
||||
private _firstPart: ResolvedKeybinding | null;
|
||||
private _chordPart: ResolvedKeybinding | null;
|
||||
private _inputValue: string;
|
||||
|
||||
private recordDisposables: IDisposable[] = [];
|
||||
|
||||
private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding | null, ResolvedKeybinding | null]>());
|
||||
readonly onKeybinding: Event<[ResolvedKeybinding | null, ResolvedKeybinding | null]> = this._onKeybinding.event;
|
||||
|
||||
private _onEnter = this._register(new Emitter<void>());
|
||||
readonly onEnter: Event<void> = this._onEnter.event;
|
||||
|
||||
private _onEscape = this._register(new Emitter<void>());
|
||||
readonly onEscape: Event<void> = this._onEscape.event;
|
||||
|
||||
private _onBlur = this._register(new Emitter<void>());
|
||||
readonly onBlur: Event<void> = this._onBlur.event;
|
||||
|
||||
constructor(parent: HTMLElement, options: SearchOptions,
|
||||
@IContextViewService contextViewService: IContextViewService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(parent, options, contextViewService, instantiationService, themeService);
|
||||
this._register(attachInputBoxStyler(this.inputBox, themeService));
|
||||
this._register(toDisposable(() => this.stopRecordingKeys()));
|
||||
|
||||
this._reset();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._reset();
|
||||
super.clear();
|
||||
}
|
||||
|
||||
startRecordingKeys(): void {
|
||||
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => this._onKeyDown(new StandardKeyboardEvent(e))));
|
||||
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.BLUR, () => this._onBlur.fire()));
|
||||
this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.INPUT, () => {
|
||||
// Prevent other characters from showing up
|
||||
this.setInputValue(this._inputValue);
|
||||
}));
|
||||
}
|
||||
|
||||
stopRecordingKeys(): void {
|
||||
this._reset();
|
||||
dispose(this.recordDisposables);
|
||||
}
|
||||
|
||||
setInputValue(value: string): void {
|
||||
this._inputValue = value;
|
||||
this.inputBox.value = this._inputValue;
|
||||
}
|
||||
|
||||
private _reset() {
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
}
|
||||
|
||||
private _onKeyDown(keyboardEvent: IKeyboardEvent): void {
|
||||
keyboardEvent.preventDefault();
|
||||
keyboardEvent.stopPropagation();
|
||||
const options = this.options as KeybindingsSearchOptions;
|
||||
if (!options.recordEnter && 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]}`;
|
||||
const options = this.options as KeybindingsSearchOptions;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
let value = '';
|
||||
if (this._firstPart) {
|
||||
value = (this._firstPart.getUserSettingsLabel() || '');
|
||||
}
|
||||
if (this._chordPart) {
|
||||
value = value + ' ' + this._chordPart.getUserSettingsLabel();
|
||||
}
|
||||
this.setInputValue(options.quoteRecordedKeys ? `"${value}"` : value);
|
||||
|
||||
this.inputBox.inputElement.title = info;
|
||||
this._onKeybinding.fire([this._firstPart, this._chordPart]);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefineKeybindingWidget extends Widget {
|
||||
|
||||
private static readonly WIDTH = 400;
|
||||
private static readonly HEIGHT = 110;
|
||||
|
||||
private _domNode: FastDomNode<HTMLElement>;
|
||||
private _keybindingInputWidget: KeybindingsSearchWidget;
|
||||
private _outputNode: HTMLElement;
|
||||
private _showExistingKeybindingsNode: HTMLElement;
|
||||
|
||||
private _firstPart: ResolvedKeybinding | null = null;
|
||||
private _chordPart: ResolvedKeybinding | null = null;
|
||||
private _isVisible: boolean = false;
|
||||
|
||||
private _onHide = this._register(new Emitter<void>());
|
||||
|
||||
private _onDidChange = this._register(new Emitter<string>());
|
||||
onDidChange: Event<string> = this._onDidChange.event;
|
||||
|
||||
private _onShowExistingKeybindings = this._register(new Emitter<string | null>());
|
||||
readonly onShowExistingKeybidings: Event<string | null> = this._onShowExistingKeybindings.event;
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this.create();
|
||||
if (parent) {
|
||||
dom.append(parent, this._domNode.domNode);
|
||||
}
|
||||
}
|
||||
|
||||
get domNode(): HTMLElement {
|
||||
return this._domNode.domNode;
|
||||
}
|
||||
|
||||
define(): Promise<string | null> {
|
||||
this._keybindingInputWidget.clear();
|
||||
return new Promise<string | null>((c) => {
|
||||
if (!this._isVisible) {
|
||||
this._isVisible = true;
|
||||
this._domNode.setDisplay('block');
|
||||
|
||||
this._firstPart = null;
|
||||
this._chordPart = null;
|
||||
this._keybindingInputWidget.setInputValue('');
|
||||
dom.clearNode(this._outputNode);
|
||||
dom.clearNode(this._showExistingKeybindingsNode);
|
||||
this._keybindingInputWidget.focus();
|
||||
}
|
||||
const disposable = this._onHide.event(() => {
|
||||
c(this.getUserSettingsLabel());
|
||||
disposable.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
layout(layout: dom.Dimension): void {
|
||||
const top = Math.round((layout.height - DefineKeybindingWidget.HEIGHT) / 2);
|
||||
this._domNode.setTop(top);
|
||||
|
||||
const left = Math.round((layout.width - DefineKeybindingWidget.WIDTH) / 2);
|
||||
this._domNode.setLeft(left);
|
||||
}
|
||||
|
||||
printExisting(numberOfExisting: number): void {
|
||||
if (numberOfExisting > 0) {
|
||||
const existingElement = dom.$('span.existingText');
|
||||
const text = numberOfExisting === 1 ? nls.localize('defineKeybinding.oneExists', "1 existing command has this keybinding", numberOfExisting) : nls.localize('defineKeybinding.existing', "{0} existing commands have this keybinding", numberOfExisting);
|
||||
dom.append(existingElement, document.createTextNode(text));
|
||||
this._showExistingKeybindingsNode.appendChild(existingElement);
|
||||
existingElement.onmousedown = (e) => { e.preventDefault(); };
|
||||
existingElement.onmouseup = (e) => { e.preventDefault(); };
|
||||
existingElement.onclick = () => { this._onShowExistingKeybindings.fire(this.getUserSettingsLabel()); };
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
const message = nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER.");
|
||||
dom.append(this._domNode.domNode, dom.$('.message', undefined, message));
|
||||
|
||||
this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, widgetShadow }, colors => {
|
||||
if (colors.editorWidgetBackground) {
|
||||
this._domNode.domNode.style.backgroundColor = colors.editorWidgetBackground.toString();
|
||||
} else {
|
||||
this._domNode.domNode.style.backgroundColor = null;
|
||||
}
|
||||
|
||||
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(KeybindingsSearchWidget, this._domNode.domNode, { ariaLabel: message }));
|
||||
this._keybindingInputWidget.startRecordingKeys();
|
||||
this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(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'));
|
||||
this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing'));
|
||||
}
|
||||
|
||||
private onKeybinding(keybinding: [ResolvedKeybinding | null, ResolvedKeybinding | null]): void {
|
||||
const [firstPart, chordPart] = keybinding;
|
||||
this._firstPart = firstPart;
|
||||
this._chordPart = chordPart;
|
||||
dom.clearNode(this._outputNode);
|
||||
dom.clearNode(this._showExistingKeybindingsNode);
|
||||
new KeybindingLabel(this._outputNode, OS).set(this._firstPart);
|
||||
if (this._chordPart) {
|
||||
this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to")));
|
||||
new KeybindingLabel(this._outputNode, OS).set(this._chordPart);
|
||||
}
|
||||
const label = this.getUserSettingsLabel();
|
||||
if (label) {
|
||||
this._onDidChange.fire(label);
|
||||
}
|
||||
}
|
||||
|
||||
private getUserSettingsLabel(): string | null {
|
||||
let label: string | null = null;
|
||||
if (this._firstPart) {
|
||||
label = this._firstPart.getUserSettingsLabel();
|
||||
if (this._chordPart) {
|
||||
label = label + ' ' + this._chordPart.getUserSettingsLabel();
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
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 readonly 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);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return DefineKeybindingOverlayWidget.ID;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this._widget.domNode;
|
||||
}
|
||||
|
||||
getPosition(): IOverlayWidgetPosition {
|
||||
return {
|
||||
preference: null
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._editor.removeOverlayWidget(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
start(): Promise<string | null> {
|
||||
if (this._editor.hasModel()) {
|
||||
this._editor.revealPositionInCenterIfOutsideViewport(this._editor.getPosition(), ScrollType.Smooth);
|
||||
}
|
||||
const layoutInfo = this._editor.getLayoutInfo();
|
||||
this._widget.layout(new dom.Dimension(layoutInfo.width, layoutInfo.height));
|
||||
return this._widget.define();
|
||||
}
|
||||
}
|
||||
1144
src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { registerEditorContribution, ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter';
|
||||
import { DefineKeybindingOverlayWidget } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { parseTree, Node } from 'vs/base/common/json';
|
||||
import { ScanCodeBinding } from 'vs/base/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';
|
||||
import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness, OverviewRulerLane } from 'vs/editor/common/model';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeybindingParser } from 'vs/base/common/keybindingParser';
|
||||
|
||||
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$/;
|
||||
|
||||
export class DefineKeybindingController extends Disposable implements editorCommon.IEditorContribution {
|
||||
|
||||
private static readonly ID = 'editor.contrib.defineKeybinding';
|
||||
|
||||
static get(editor: ICodeEditor): DefineKeybindingController {
|
||||
return editor.getContribution<DefineKeybindingController>(DefineKeybindingController.ID);
|
||||
}
|
||||
|
||||
private _keybindingWidgetRenderer?: KeybindingWidgetRenderer;
|
||||
private _keybindingDecorationRenderer?: KeybindingEditorDecorationsRenderer;
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._register(this._editor.onDidChangeModel(e => this._update()));
|
||||
this._update();
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return DefineKeybindingController.ID;
|
||||
}
|
||||
|
||||
get keybindingWidgetRenderer(): KeybindingWidgetRenderer | undefined {
|
||||
return this._keybindingWidgetRenderer;
|
||||
}
|
||||
|
||||
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 = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
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 = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class KeybindingWidgetRenderer extends Disposable {
|
||||
|
||||
private _launchWidget: FloatingClickWidget;
|
||||
private _defineWidget: DefineKeybindingOverlayWidget;
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _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();
|
||||
}
|
||||
|
||||
showDefineKeybindingWidget(): void {
|
||||
this._defineWidget.start().then(keybinding => this._onAccepted(keybinding));
|
||||
}
|
||||
|
||||
private _onAccepted(keybinding: string | null): void {
|
||||
this._editor.focus();
|
||||
if (keybinding && this._editor.hasModel()) {
|
||||
const regexp = new RegExp(/\\/g);
|
||||
const 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');
|
||||
|
||||
const 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: IActiveCodeEditor,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._updateDecorations = this._register(new RunOnceScheduler(() => this._updateDecorationsNow(), 500));
|
||||
|
||||
const 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();
|
||||
|
||||
const newDecorations: 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: ITextModel, entry: Node): IModelDeltaDecoration | null {
|
||||
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 = null;
|
||||
if (resolvedKeybinding instanceof WindowsNativeResolvedKeybinding) {
|
||||
usLabel = resolvedKeybinding.getUSLabel();
|
||||
}
|
||||
if (!resolvedKeybinding.isWYSIWYG()) {
|
||||
const uiLabel = resolvedKeybinding.getLabel();
|
||||
if (typeof uiLabel === 'string' && value.value.toLowerCase() === uiLabel.toLowerCase()) {
|
||||
// coincidentally, this is actually WYSIWYG
|
||||
return null;
|
||||
}
|
||||
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 (typeof expectedUserSettingsLabel === 'string' && !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 aParts = KeybindingParser.parseUserBinding(a);
|
||||
const bParts = KeybindingParser.parseUserBinding(b);
|
||||
|
||||
if (aParts.length !== bParts.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0, len = aParts.length; i < len; i++) {
|
||||
if (!this._userBindingEquals(aParts[i], bParts[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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 | null, usLabel: string | null, model: ITextModel, keyNode: Node): 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: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: className,
|
||||
beforeContentClassName: beforeContentClassName,
|
||||
hoverMessage: msg,
|
||||
overviewRuler: {
|
||||
color: overviewRulerColor,
|
||||
position: OverviewRulerLane.Right
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DefineKeybindingCommand extends EditorCommand {
|
||||
|
||||
static readonly ID = 'editor.action.defineKeybinding';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: DefineKeybindingCommand.ID,
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.languageId.isEqualTo('jsonc')),
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_K),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
if (!isInterestingEditorModel(editor) || editor.getConfiguration().readOnly) {
|
||||
return;
|
||||
}
|
||||
const controller = DefineKeybindingController.get(editor);
|
||||
if (controller && controller.keybindingWidgetRenderer) {
|
||||
controller.keybindingWidgetRenderer.showDefineKeybindingWidget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isInterestingEditorModel(editor: ICodeEditor): boolean {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return false;
|
||||
}
|
||||
const url = model.uri.toString();
|
||||
return INTERESTING_FILE.test(url);
|
||||
}
|
||||
|
||||
registerEditorContribution(DefineKeybindingController);
|
||||
registerEditorCommand(new DefineKeybindingCommand());
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 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="#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="14" height="14" viewBox="-0.994 0 16 16" enable-background="new -0.994 0 16 16"><path fill="#C5C5C5" d="M13 6c0 1.461-.636 2.846-1.746 3.797l-5.584 4.951-1.324-1.496 5.595-4.962c.678-.582 1.061-1.413 1.061-2.29 0-1.654-1.345-3-2.997-3-.71 0-1.399.253-1.938.713l-1.521 1.287h2.448l-1.998 2h-3.996v-4l1.998-2v2.692l1.775-1.504c.899-.766 2.047-1.188 3.232-1.188 2.754 0 4.995 2.243 4.995 5z"/></svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="-0.994 0 16 16" enable-background="new -0.994 0 16 16"><path fill="#424242" d="M13 6c0 1.461-.636 2.846-1.746 3.797l-5.584 4.951-1.324-1.496 5.595-4.962c.678-.582 1.061-1.413 1.061-2.29 0-1.654-1.345-3-2.997-3-.71 0-1.399.253-1.938.713l-1.521 1.287h2.448l-1.998 2h-3.996v-4l1.998-2v2.692l1.775-1.504c.899-.766 2.047-1.188 3.232-1.188 2.754 0 4.995 2.243 4.995 5z"/></svg>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>
|
||||
|
After Width: | Height: | Size: 419 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"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 748 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>Ellipsis_bold_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M6,7.5A2.5,2.5,0,1,1,3.5,5,2.5,2.5,0,0,1,6,7.5ZM8.5,5A2.5,2.5,0,1,0,11,7.5,2.5,2.5,0,0,0,8.5,5Zm5,0A2.5,2.5,0,1,0,16,7.5,2.5,2.5,0,0,0,13.5,5Z" style="display: none;"/></g><g id="iconBg"><path class="icon-vs-bg" d="M5,7.5A1.5,1.5,0,1,1,3.5,6,1.5,1.5,0,0,1,5,7.5ZM8.5,6A1.5,1.5,0,1,0,10,7.5,1.5,1.5,0,0,0,8.5,6Zm5,0A1.5,1.5,0,1,0,15,7.5,1.5,1.5,0,0,0,13.5,6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 748 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,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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,
|
||||
.defineKeybindingWidget .existing {
|
||||
margin-top:10px;
|
||||
width: 400px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .input {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .output {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.defineKeybindingWidget .existing .existingText {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 > .keybindings-search-actions-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge {
|
||||
margin-right: 8px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header.small > .search-container > .keybindings-search-actions-container > .recording-badge,
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .recording-badge.disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .icon {
|
||||
width:16px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence {
|
||||
background: url('sort_precedence.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence,
|
||||
.vs-dark .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence {
|
||||
background: url('sort_precedence_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.keybindings-editor .monaco-action-bar .action-item > .record-keys {
|
||||
background: url('record-keys.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .keybindings-editor .monaco-action-bar .action-item > .record-keys,
|
||||
.vs-dark .keybindings-editor .monaco-action-bar .action-item > .record-keys {
|
||||
background: url('record-keys-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.keybindings-editor .monaco-action-bar .action-item > .clear-input {
|
||||
background: url('clear.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.hc-black .keybindings-editor .monaco-action-bar .action-item > .clear-input,
|
||||
.vs-dark .keybindings-editor .monaco-action-bar .action-item > .clear-input {
|
||||
background: url('clear-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container > div {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container > .file-name {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-header .open-keybindings-container > .file-name:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** List based styling **/
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-header,
|
||||
.keybindings-editor > .keybindings-body .keybindings-list-container {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-header,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row {
|
||||
cursor: default;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-header,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row.odd:not(.focused):not(.selected):not(:hover),
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:not(:focus) .monaco-list-row.focused.odd:not(.selected):not(:hover),
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:not(.focused) .monaco-list-row.focused.odd:not(.selected):not(:hover) {
|
||||
background-color: rgba(130, 130, 130, 0.04);
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-header > .header {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-header > .header,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .column {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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 .monaco-highlighted-label {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.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 > .when:not(.input-mode) .monaco-inputbox,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when.input-mode .when-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox {
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox,
|
||||
.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.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: var(--monaco-monospace-font);
|
||||
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:focus .monaco-list-row.selected > .column .highlight,
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected.focused > .column .highlight {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column.actions .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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#C5C5C5" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#75BEFF" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>
|
||||
|
After Width: | Height: | Size: 262 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#656565" points="10,2 7.414,2 8.414,3 9,3 9,3.586 9,4 9,4.414 9,6 12,6 12,13 4,13 4,8 3,8 3,14 13,14 13,5"/><polygon fill="#00539C" points="5,1 3,1 5,3 1,3 1,5 5,5 3,7 5,7 8,4"/></svg>
|
||||
|
After Width: | Height: | Size: 262 B |
@@ -0,0 +1,266 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {
|
||||
padding-left: 27px;
|
||||
padding-right: 32px;
|
||||
padding-bottom: 11px;
|
||||
padding-top: 11px;
|
||||
}
|
||||
|
||||
.preferences-editor > .preferences-editors-container.side-by-side-preferences-editor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preferences-editor > .preferences-editors-container.side-by-side-preferences-editor .preferences-header-container {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item.disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.default-preferences-editor-container > .preferences-header-container > .default-preferences-header,
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.default-preferences-editor-container > .preferences-header-container > .default-preferences-header,
|
||||
.preferences-editor .settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
margin-left: 33px;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label.folder-settings {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .actions-container {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.default-preferences-editor-container > .preferences-header-container > .default-preferences-header,
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item {
|
||||
padding: 3px 0px;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-title {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-details {
|
||||
text-transform: none;
|
||||
margin-left: 0.5em;
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon {
|
||||
padding-left: 0.3em;
|
||||
padding-top: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preferences-header > .settings-header-widget {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-controls > .settings-count-widget {
|
||||
margin: 6px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-controls {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.settings-header-widget > .settings-search-controls > .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 .view-zones > .settings-header-widget {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.monaco-editor .settings-header-widget .title-container {
|
||||
display: flex;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.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;
|
||||
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,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 5H9V4H10V5V5ZM3 6H2V7H3V6V6ZM8 4H7V5H8V4V4ZM4 4H2V5H4V4V4ZM12 11H14V10H12V11V11ZM8 7H9V6H8V7V7ZM4 10H2V11H4V10V10ZM12 4H11V5H12V4V4ZM14 4H13V5H14V4V4ZM12 9H14V6H12V9V9ZM16 3V12C16 12.55 15.55 13 15 13H1C0.45 13 0 12.55 0 12V3C0 2.45 0.45 2 1 2H15C15.55 2 16 2.45 16 3V3ZM15 3H1V12H15V3V3ZM6 7H7V6H6V7V7ZM6 4H5V5H6V4V4ZM4 7H5V6H4V7V7ZM5 11H11V10H5V11V11ZM10 7H11V6H10V7V7ZM3 8H2V9H3V8V8ZM8 8V9H9V8H8V8ZM6 8V9H7V8H6V8ZM5 8H4V9H5V8V8ZM10 9H11V8H10V9V9Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 624 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 5H9V4H10V5V5ZM3 6H2V7H3V6V6ZM8 4H7V5H8V4V4ZM4 4H2V5H4V4V4ZM12 11H14V10H12V11V11ZM8 7H9V6H8V7V7ZM4 10H2V11H4V10V10ZM12 4H11V5H12V4V4ZM14 4H13V5H14V4V4ZM12 9H14V6H12V9V9ZM16 3V12C16 12.55 15.55 13 15 13H1C0.45 13 0 12.55 0 12V3C0 2.45 0.45 2 1 2H15C15.55 2 16 2.45 16 3V3ZM15 3H1V12H15V3V3ZM6 7H7V6H6V7V7ZM6 4H5V5H6V4V4ZM4 7H5V6H4V7V7ZM5 11H11V10H5V11V11ZM10 7H11V6H10V7V7ZM3 8H2V9H3V8V8ZM8 8V9H9V8H8V8ZM6 8V9H7V8H6V8ZM5 8H4V9H5V8V8ZM10 9H11V8H10V9V9Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 624 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#2d2d30" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/><g fill="#C5C5C5"><path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/><rect x="3" y="10" width="3" height="3"/></g></svg>
|
||||
|
After Width: | Height: | Size: 564 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><polygon fill="#F6F6F6" points="13.64,7.396 12.169,2.898 10.706,3.761 11.087,2 6.557,2 6.936,3.762 5.473,2.898 4,7.396 5.682,7.554 4.513,8.561 5.013,9 2,9 2,14 7,14 7,10.747 7.978,11.606 8.82,9.725 9.661,11.602 13.144,8.562 11.968,7.554"/><g fill="#424242"><path d="M12.301 6.518l-2.772.262 2.086 1.788-1.594 1.392-1.201-2.682-1.201 2.682-1.583-1.392 2.075-1.788-2.771-.262.696-2.126 2.358 1.392-.599-2.784h2.053l-.602 2.783 2.359-1.392.696 2.127z"/><rect x="3" y="10" width="3" height="3"/></g></svg>
|
||||
|
After Width: | Height: | Size: 564 B |
@@ -0,0 +1,107 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-item-value > .setting-item-control {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-pattern {
|
||||
margin-right: 3px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-pattern,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling {
|
||||
opacity: 0.7;
|
||||
margin-left: 0.5em;
|
||||
font-size: 0.9em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover .monaco-action-bar,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected .monaco-action-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .action-label {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .setting-excludeAction-edit {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .setting-excludeAction-edit {
|
||||
background: url(edit.svg) center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .setting-excludeAction-edit {
|
||||
background: url(edit_inverse.svg) center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .setting-excludeAction-remove {
|
||||
background: url(action-remove.svg) center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row .monaco-action-bar .setting-excludeAction-remove {
|
||||
background: url(action-remove-dark.svg) center center no-repeat;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .monaco-text-button {
|
||||
width: initial;
|
||||
padding: 2px 14px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-item-control.setting-exclude-new-mode .setting-exclude-new-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .monaco-text-button.setting-exclude-addButton {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-edit-row {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-patternInput,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-siblingInput {
|
||||
height: 22px;
|
||||
max-width: 320px;
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-okButton {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-widget {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5 1v5h-5v9h11v-2.586l1 1 4-4v-8.414zm5 3v2l-2-1.414v-.586zm-2 8h-5v-3h5z" fill="#f6f6f6"/><g fill="#424242"><path d="m3 9h2v2h-2zm3 1h2v2h-2zm8-7v3h.586l.414-.414v-3.586h-9v4h1v-3z"/><path d="m9 10.414v2.586h-7v-5h6v-1h-7v7h9v-2.586zm.414-4.414h.586v-2h-2v.586z"/><path d="m13 5h-2v4l-2-2v2l3 3 3-3v-2l-2 2z"/></g><path d="m8 9.414v-1.414h-6v5h7v-2.586zm-3 1.586h-2v-2h2zm3 1h-2v-2h2zm0-7.414v-.586h6v-1h-7v3h1z" fill="#f0eff1"/></svg>
|
||||
|
After Width: | Height: | Size: 540 B |
@@ -0,0 +1 @@
|
||||
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5 1v5h-5v9h11v-2.586l1 1 4-4v-8.414zm5 3v2l-2-1.414v-.586zm-2 8h-5v-3h5z" fill="#2d2d30"/><g fill="#c5c5c5"><path d="m3 9h2v2h-2zm3 1h2v2h-2zm8-7v3h.586l.414-.414v-3.586h-9v4h1v-3z"/><path d="m9 10.414v2.586h-7v-5h6v-1h-7v7h9v-2.586zm.414-4.414h.586v-2h-2v.586z"/><path d="m13 5h-2v4l-2-2v2l3 3 3-3v-2l-2 2z"/></g><path d="m8 9.414v-1.414h-6v5h7v-2.586zm-3 1.586h-2v-2h2zm3 1h-2v-2h2zm0-7.414v-.586h6v-1h-7v3h1z" fill="#2b282e"/></svg>
|
||||
|
After Width: | Height: | Size: 540 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,271 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
export class OpenRawDefaultSettingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openRawDefaultSettings';
|
||||
static readonly LABEL = nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openRawDefaultSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenSettings2Action extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openSettings2';
|
||||
static readonly LABEL = nls.localize('openSettings2', "Open Settings (UI)");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openSettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenSettingsJsonAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openSettingsJson';
|
||||
static readonly LABEL = nls.localize('openSettingsJson', "Open Settings (JSON)");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGlobalSettingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openGlobalSettings';
|
||||
static readonly LABEL = nls.localize('openGlobalSettings', "Open User Settings");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openGlobalSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGlobalKeybindingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openGlobalKeybindings';
|
||||
static readonly LABEL = nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openGlobalKeybindingSettings(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenGlobalKeybindingsFileAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openGlobalKeybindingsFile';
|
||||
static readonly LABEL = nls.localize('openGlobalKeybindingsFile', "Open Keyboard Shortcuts (JSON)");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openGlobalKeybindingSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenDefaultKeybindingsFileAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openDefaultKeybindingsFile';
|
||||
static readonly LABEL = nls.localize('openDefaultKeybindingsFile', "Open Default Keyboard Shortcuts (JSON)");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openDefaultKeybindingsFile();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceSettingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openWorkspaceSettings';
|
||||
static readonly LABEL = nls.localize('openWorkspaceSettings', "Open Workspace Settings");
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
) {
|
||||
super(id, label);
|
||||
this.update();
|
||||
this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY;
|
||||
}
|
||||
|
||||
run(event?: any): Promise<any> {
|
||||
return this.preferencesService.openWorkspaceSettings();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export const OPEN_FOLDER_SETTINGS_COMMAND = '_workbench.action.openFolderSettings';
|
||||
export const OPEN_FOLDER_SETTINGS_LABEL = nls.localize('openFolderSettings', "Open Folder Settings");
|
||||
export class OpenFolderSettingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.openFolderSettings';
|
||||
static readonly LABEL = OPEN_FOLDER_SETTINGS_LABEL;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super(id, label);
|
||||
this.update();
|
||||
this.workspaceContextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables);
|
||||
this.workspaceContextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
this.enabled = this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceContextService.getWorkspace().folders.length > 0;
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
return this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID)
|
||||
.then(workspaceFolder => {
|
||||
if (workspaceFolder) {
|
||||
return this.preferencesService.openFolderSettings(workspaceFolder.uri);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureLanguageBasedSettingsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.configureLanguageBasedSettings';
|
||||
static readonly LABEL = nls.localize('configureLanguageBasedSettings', "Configure Language Specific Settings...");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: IQuickPickItem[] = languages.sort().map((lang, index) => {
|
||||
const 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 | undefined;
|
||||
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 {
|
||||
label: lang,
|
||||
iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
|
||||
description
|
||||
} as IQuickPickItem;
|
||||
});
|
||||
|
||||
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") })
|
||||
.then(pick => {
|
||||
if (pick) {
|
||||
const modeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase());
|
||||
if (typeof modeId === 'string') {
|
||||
return this.preferencesService.configureSettingsForLanguage(modeId);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
1251
src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts
Normal file
@@ -0,0 +1,999 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
|
||||
import { getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ConfigurationTarget, IConfigurationService, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations';
|
||||
import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
|
||||
|
||||
export interface IPreferencesRenderer<T> extends IDisposable {
|
||||
readonly preferencesModel: IPreferencesEditorModel<T>;
|
||||
|
||||
getAssociatedPreferencesModel(): IPreferencesEditorModel<T>;
|
||||
setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel<T>): void;
|
||||
|
||||
onFocusPreference: Event<T>;
|
||||
onClearFocusPreference: Event<T>;
|
||||
onUpdatePreference: Event<{ key: string, value: any, source: T }>;
|
||||
|
||||
render(): void;
|
||||
updatePreference(key: string, value: any, source: T): void;
|
||||
focusPreference(setting: T): void;
|
||||
clearFocus(setting: T): void;
|
||||
filterPreferences(filterResult: IFilterResult | undefined): void;
|
||||
editPreference(setting: T): boolean;
|
||||
}
|
||||
|
||||
export class UserSettingsRenderer extends Disposable implements IPreferencesRenderer<ISetting> {
|
||||
|
||||
private settingHighlighter: SettingHighlighter;
|
||||
private editSettingActionRenderer: EditSettingRenderer;
|
||||
private highlightMatchesRenderer: HighlightMatchesRenderer;
|
||||
private modelChangeDelayer: Delayer<void> = new Delayer<void>(200);
|
||||
private associatedPreferencesModel: IPreferencesEditorModel<ISetting>;
|
||||
|
||||
private readonly _onFocusPreference = new Emitter<ISetting>();
|
||||
readonly onFocusPreference: Event<ISetting> = this._onFocusPreference.event;
|
||||
|
||||
private readonly _onClearFocusPreference = new Emitter<ISetting>();
|
||||
readonly onClearFocusPreference: Event<ISetting> = this._onClearFocusPreference.event;
|
||||
|
||||
private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>();
|
||||
readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event;
|
||||
|
||||
private filterResult: IFilterResult | undefined;
|
||||
|
||||
constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel,
|
||||
@IPreferencesService protected preferencesService: IPreferencesService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference));
|
||||
this.highlightMatchesRenderer = this._register(instantiationService.createInstance(HighlightMatchesRenderer, editor));
|
||||
this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter));
|
||||
this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this._updatePreference(key, value, source)));
|
||||
this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged())));
|
||||
|
||||
}
|
||||
|
||||
getAssociatedPreferencesModel(): IPreferencesEditorModel<ISetting> {
|
||||
return this.associatedPreferencesModel;
|
||||
}
|
||||
|
||||
setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
|
||||
this.associatedPreferencesModel = associatedPreferencesModel;
|
||||
this.editSettingActionRenderer.associatedPreferencesModel = associatedPreferencesModel;
|
||||
|
||||
// Create header only in Settings editor mode
|
||||
this.createHeader();
|
||||
}
|
||||
|
||||
protected createHeader(): void {
|
||||
this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyUserSettingsHeader', "Place your settings here to override the Default Settings."));
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this.associatedPreferencesModel);
|
||||
if (this.filterResult) {
|
||||
this.filterPreferences(this.filterResult);
|
||||
}
|
||||
}
|
||||
|
||||
private _updatePreference(key: string, value: any, source: IIndexedSetting): void {
|
||||
this._onUpdatePreference.fire({ key, value, source });
|
||||
this.updatePreference(key, value, source);
|
||||
}
|
||||
|
||||
updatePreference(key: string, value: any, source: IIndexedSetting): void {
|
||||
const overrideIdentifier = source.overrideOf ? overrideIdentifierFromKey(source.overrideOf.key) : null;
|
||||
const resource = this.preferencesModel.uri;
|
||||
this.configurationService.updateValue(key, value, { overrideIdentifier, resource }, this.preferencesModel.configurationTarget)
|
||||
.then(() => this.onSettingUpdated(source));
|
||||
}
|
||||
|
||||
private onModelChanged(): void {
|
||||
if (!this.editor.hasModel()) {
|
||||
// model could have been disposed during the delay
|
||||
return;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
private onSettingUpdated(setting: ISetting) {
|
||||
this.editor.focus();
|
||||
setting = this.getSetting(setting)!;
|
||||
if (setting) {
|
||||
// TODO:@sandy Selection range should be template range
|
||||
this.editor.setSelection(setting.valueRange);
|
||||
this.settingHighlighter.highlight(setting, true);
|
||||
}
|
||||
}
|
||||
|
||||
private getSetting(setting: ISetting): ISetting | undefined {
|
||||
const { key, overrideOf } = setting;
|
||||
if (overrideOf) {
|
||||
const setting = this.getSetting(overrideOf);
|
||||
for (const override of setting!.overrides!) {
|
||||
if (override.key === key) {
|
||||
return override;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.preferencesModel.getPreference(key);
|
||||
}
|
||||
|
||||
filterPreferences(filterResult: IFilterResult | undefined): void {
|
||||
this.filterResult = filterResult;
|
||||
this.settingHighlighter.clear(true);
|
||||
this.highlightMatchesRenderer.render(filterResult ? filterResult.matches : []);
|
||||
}
|
||||
|
||||
focusPreference(setting: ISetting): void {
|
||||
const s = this.getSetting(setting);
|
||||
if (s) {
|
||||
this.settingHighlighter.highlight(s, true);
|
||||
this.editor.setPosition({ lineNumber: s.keyRange.startLineNumber, column: s.keyRange.startColumn });
|
||||
} else {
|
||||
this.settingHighlighter.clear(true);
|
||||
}
|
||||
}
|
||||
|
||||
clearFocus(setting: ISetting): void {
|
||||
this.settingHighlighter.clear(true);
|
||||
}
|
||||
|
||||
editPreference(setting: ISetting): boolean {
|
||||
const editableSetting = this.getSetting(setting);
|
||||
return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting));
|
||||
}
|
||||
}
|
||||
|
||||
export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer<ISetting> {
|
||||
|
||||
private workspaceConfigurationRenderer: WorkspaceConfigurationRenderer;
|
||||
|
||||
constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,
|
||||
@IPreferencesService preferencesService: IPreferencesService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super(editor, preferencesModel, preferencesService, configurationService, instantiationService);
|
||||
this.workspaceConfigurationRenderer = this._register(instantiationService.createInstance(WorkspaceConfigurationRenderer, editor, preferencesModel));
|
||||
}
|
||||
|
||||
protected createHeader(): void {
|
||||
this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyWorkspaceSettingsHeader', "Place your settings here to override the User Settings."));
|
||||
}
|
||||
|
||||
setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
|
||||
super.setAssociatedPreferencesModel(associatedPreferencesModel);
|
||||
this.workspaceConfigurationRenderer.render(this.getAssociatedPreferencesModel());
|
||||
}
|
||||
|
||||
render(): void {
|
||||
super.render();
|
||||
this.workspaceConfigurationRenderer.render(this.getAssociatedPreferencesModel());
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer<ISetting> {
|
||||
|
||||
constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel,
|
||||
@IPreferencesService preferencesService: IPreferencesService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super(editor, preferencesModel, preferencesService, configurationService, instantiationService);
|
||||
}
|
||||
|
||||
protected createHeader(): void {
|
||||
this._register(new SettingsHeaderWidget(this.editor, '')).setMessage(nls.localize('emptyFolderSettingsHeader', "Place your folder settings here to override those from the Workspace Settings."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DefaultSettingsRenderer extends Disposable implements IPreferencesRenderer<ISetting> {
|
||||
|
||||
private _associatedPreferencesModel: IPreferencesEditorModel<ISetting>;
|
||||
private settingHighlighter: SettingHighlighter;
|
||||
private settingsHeaderRenderer: DefaultSettingsHeaderRenderer;
|
||||
private settingsGroupTitleRenderer: SettingsGroupTitleRenderer;
|
||||
private filteredMatchesRenderer: FilteredMatchesRenderer;
|
||||
private hiddenAreasRenderer: HiddenAreasRenderer;
|
||||
private editSettingActionRenderer: EditSettingRenderer;
|
||||
private bracesHidingRenderer: BracesHidingRenderer;
|
||||
private filterResult: IFilterResult | undefined;
|
||||
|
||||
private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>();
|
||||
readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event;
|
||||
|
||||
private readonly _onFocusPreference = new Emitter<ISetting>();
|
||||
readonly onFocusPreference: Event<ISetting> = this._onFocusPreference.event;
|
||||
|
||||
private readonly _onClearFocusPreference = new Emitter<ISetting>();
|
||||
readonly onClearFocusPreference: Event<ISetting> = this._onClearFocusPreference.event;
|
||||
|
||||
constructor(protected editor: ICodeEditor, readonly preferencesModel: DefaultSettingsEditorModel,
|
||||
@IPreferencesService protected preferencesService: IPreferencesService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
this.settingHighlighter = this._register(instantiationService.createInstance(SettingHighlighter, editor, this._onFocusPreference, this._onClearFocusPreference));
|
||||
this.settingsHeaderRenderer = this._register(instantiationService.createInstance(DefaultSettingsHeaderRenderer, editor));
|
||||
this.settingsGroupTitleRenderer = this._register(instantiationService.createInstance(SettingsGroupTitleRenderer, editor));
|
||||
this.filteredMatchesRenderer = this._register(instantiationService.createInstance(FilteredMatchesRenderer, editor));
|
||||
this.editSettingActionRenderer = this._register(instantiationService.createInstance(EditSettingRenderer, editor, preferencesModel, this.settingHighlighter));
|
||||
this.bracesHidingRenderer = this._register(instantiationService.createInstance(BracesHidingRenderer, editor, preferencesModel));
|
||||
this.hiddenAreasRenderer = this._register(instantiationService.createInstance(HiddenAreasRenderer, editor, [this.settingsGroupTitleRenderer, this.filteredMatchesRenderer, this.bracesHidingRenderer]));
|
||||
|
||||
this._register(this.editSettingActionRenderer.onUpdateSetting(e => this._onUpdatePreference.fire(e)));
|
||||
this._register(this.settingsGroupTitleRenderer.onHiddenAreasChanged(() => this.hiddenAreasRenderer.render()));
|
||||
this._register(preferencesModel.onDidChangeGroups(() => this.render()));
|
||||
}
|
||||
|
||||
getAssociatedPreferencesModel(): IPreferencesEditorModel<ISetting> {
|
||||
return this._associatedPreferencesModel;
|
||||
}
|
||||
|
||||
setAssociatedPreferencesModel(associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
|
||||
this._associatedPreferencesModel = associatedPreferencesModel;
|
||||
this.editSettingActionRenderer.associatedPreferencesModel = associatedPreferencesModel;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
|
||||
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
|
||||
this.settingHighlighter.clear(true);
|
||||
this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.showGroup(0);
|
||||
this.hiddenAreasRenderer.render();
|
||||
}
|
||||
|
||||
filterPreferences(filterResult: IFilterResult | undefined): void {
|
||||
this.filterResult = filterResult;
|
||||
|
||||
if (filterResult) {
|
||||
this.filteredMatchesRenderer.render(filterResult, this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.render(undefined);
|
||||
this.settingsHeaderRenderer.render(filterResult);
|
||||
this.settingHighlighter.clear(true);
|
||||
this.bracesHidingRenderer.render(filterResult, this.preferencesModel.settingsGroups);
|
||||
this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel);
|
||||
} else {
|
||||
this.settingHighlighter.clear(true);
|
||||
this.filteredMatchesRenderer.render(undefined, this.preferencesModel.settingsGroups);
|
||||
this.settingsHeaderRenderer.render(undefined);
|
||||
this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups);
|
||||
this.settingsGroupTitleRenderer.showGroup(0);
|
||||
this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups);
|
||||
this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel);
|
||||
}
|
||||
|
||||
this.hiddenAreasRenderer.render();
|
||||
}
|
||||
|
||||
focusPreference(s: ISetting): void {
|
||||
const setting = this.getSetting(s);
|
||||
if (setting) {
|
||||
this.settingsGroupTitleRenderer.showSetting(setting);
|
||||
this.settingHighlighter.highlight(setting, true);
|
||||
} else {
|
||||
this.settingHighlighter.clear(true);
|
||||
}
|
||||
}
|
||||
|
||||
private getSetting(setting: ISetting): ISetting | undefined {
|
||||
const { key, overrideOf } = setting;
|
||||
if (overrideOf) {
|
||||
const setting = this.getSetting(overrideOf);
|
||||
for (const override of setting!.overrides!) {
|
||||
if (override.key === key) {
|
||||
return override;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
const settingsGroups = this.filterResult ? this.filterResult.filteredGroups : this.preferencesModel.settingsGroups;
|
||||
return this.getPreference(key, settingsGroups);
|
||||
}
|
||||
|
||||
private getPreference(key: string, settingsGroups: ISettingsGroup[]): ISetting | undefined {
|
||||
for (const group of settingsGroups) {
|
||||
for (const section of group.sections) {
|
||||
for (const setting of section.settings) {
|
||||
if (setting.key === key) {
|
||||
return setting;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
clearFocus(setting: ISetting): void {
|
||||
this.settingHighlighter.clear(true);
|
||||
}
|
||||
|
||||
updatePreference(key: string, value: any, source: ISetting): void {
|
||||
}
|
||||
|
||||
editPreference(setting: ISetting): boolean {
|
||||
return this.editSettingActionRenderer.activateOnSetting(setting);
|
||||
}
|
||||
}
|
||||
|
||||
export interface HiddenAreasProvider {
|
||||
hiddenAreas: IRange[];
|
||||
}
|
||||
|
||||
export class BracesHidingRenderer extends Disposable implements HiddenAreasProvider {
|
||||
private _result: IFilterResult | undefined;
|
||||
private _settingsGroups: ISettingsGroup[];
|
||||
|
||||
constructor(private editor: ICodeEditor) {
|
||||
super();
|
||||
}
|
||||
|
||||
render(result: IFilterResult | undefined, settingsGroups: ISettingsGroup[]): void {
|
||||
this._result = result;
|
||||
this._settingsGroups = settingsGroups;
|
||||
}
|
||||
|
||||
get hiddenAreas(): IRange[] {
|
||||
// Opening square brace
|
||||
const hiddenAreas = [
|
||||
{
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 2,
|
||||
endColumn: 1
|
||||
}
|
||||
];
|
||||
|
||||
const hideBraces = (group: ISettingsGroup, hideExtraLine?: boolean) => {
|
||||
// Opening curly brace
|
||||
hiddenAreas.push({
|
||||
startLineNumber: group.range.startLineNumber - 3,
|
||||
startColumn: 1,
|
||||
endLineNumber: group.range.startLineNumber - (hideExtraLine ? 1 : 3),
|
||||
endColumn: 1
|
||||
});
|
||||
|
||||
// Closing curly brace
|
||||
hiddenAreas.push({
|
||||
startLineNumber: group.range.endLineNumber + 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: group.range.endLineNumber + 4,
|
||||
endColumn: 1
|
||||
});
|
||||
};
|
||||
|
||||
this._settingsGroups.forEach(g => hideBraces(g));
|
||||
if (this._result) {
|
||||
this._result.filteredGroups.forEach((g, i) => hideBraces(g, true));
|
||||
}
|
||||
|
||||
// Closing square brace
|
||||
const lineCount = this.editor.getModel()!.getLineCount();
|
||||
hiddenAreas.push({
|
||||
startLineNumber: lineCount,
|
||||
startColumn: 1,
|
||||
endLineNumber: lineCount,
|
||||
endColumn: 1
|
||||
});
|
||||
|
||||
|
||||
return hiddenAreas;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DefaultSettingsHeaderRenderer extends Disposable {
|
||||
|
||||
private settingsHeaderWidget: DefaultSettingsHeaderWidget;
|
||||
readonly onClick: Event<void>;
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
super();
|
||||
this.settingsHeaderWidget = this._register(new DefaultSettingsHeaderWidget(editor, ''));
|
||||
this.onClick = this.settingsHeaderWidget.onClick;
|
||||
}
|
||||
|
||||
render(filterResult: IFilterResult | undefined) {
|
||||
const hasSettings = !filterResult || filterResult.filteredGroups.length > 0;
|
||||
this.settingsHeaderWidget.toggleMessage(hasSettings);
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsGroupTitleRenderer extends Disposable implements HiddenAreasProvider {
|
||||
|
||||
private readonly _onHiddenAreasChanged = new Emitter<void>();
|
||||
get onHiddenAreasChanged(): Event<void> { return this._onHiddenAreasChanged.event; }
|
||||
|
||||
private settingsGroups: ISettingsGroup[];
|
||||
private hiddenGroups: ISettingsGroup[] = [];
|
||||
private settingsGroupTitleWidgets: SettingsGroupTitleWidget[];
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(private editor: ICodeEditor,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get hiddenAreas(): IRange[] {
|
||||
const hiddenAreas: IRange[] = [];
|
||||
for (const group of this.hiddenGroups) {
|
||||
hiddenAreas.push(group.range);
|
||||
}
|
||||
return hiddenAreas;
|
||||
}
|
||||
|
||||
render(settingsGroups: ISettingsGroup[] | undefined) {
|
||||
this.disposeWidgets();
|
||||
if (!settingsGroups) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingsGroups = settingsGroups.slice();
|
||||
this.settingsGroupTitleWidgets = [];
|
||||
for (const group of this.settingsGroups.slice().reverse()) {
|
||||
if (group.sections.every(sect => sect.settings.length === 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const settingsGroupTitleWidget = this.instantiationService.createInstance(SettingsGroupTitleWidget, this.editor, group);
|
||||
settingsGroupTitleWidget.render();
|
||||
this.settingsGroupTitleWidgets.push(settingsGroupTitleWidget);
|
||||
this.disposables.push(settingsGroupTitleWidget);
|
||||
this.disposables.push(settingsGroupTitleWidget.onToggled(collapsed => this.onToggled(collapsed, settingsGroupTitleWidget.settingsGroup)));
|
||||
}
|
||||
this.settingsGroupTitleWidgets.reverse();
|
||||
}
|
||||
|
||||
showGroup(groupIdx: number) {
|
||||
const shownGroup = this.settingsGroupTitleWidgets[groupIdx].settingsGroup;
|
||||
|
||||
this.hiddenGroups = this.settingsGroups.filter(g => g !== shownGroup);
|
||||
for (const groupTitleWidget of this.settingsGroupTitleWidgets.filter(widget => widget.settingsGroup !== shownGroup)) {
|
||||
groupTitleWidget.toggleCollapse(true);
|
||||
}
|
||||
this._onHiddenAreasChanged.fire();
|
||||
}
|
||||
|
||||
showSetting(setting: ISetting): void {
|
||||
const settingsGroupTitleWidget = this.settingsGroupTitleWidgets.filter(widget => Range.containsRange(widget.settingsGroup.range, setting.range))[0];
|
||||
if (settingsGroupTitleWidget && settingsGroupTitleWidget.isCollapsed()) {
|
||||
settingsGroupTitleWidget.toggleCollapse(false);
|
||||
this.hiddenGroups.splice(this.hiddenGroups.indexOf(settingsGroupTitleWidget.settingsGroup), 1);
|
||||
this._onHiddenAreasChanged.fire();
|
||||
}
|
||||
}
|
||||
|
||||
private onToggled(collapsed: boolean, group: ISettingsGroup) {
|
||||
const index = this.hiddenGroups.indexOf(group);
|
||||
if (collapsed) {
|
||||
const currentPosition = this.editor.getPosition();
|
||||
if (group.range.startLineNumber <= currentPosition!.lineNumber && group.range.endLineNumber >= currentPosition!.lineNumber) {
|
||||
this.editor.setPosition({ lineNumber: group.range.startLineNumber - 1, column: 1 });
|
||||
}
|
||||
this.hiddenGroups.push(group);
|
||||
} else {
|
||||
this.hiddenGroups.splice(index, 1);
|
||||
}
|
||||
this._onHiddenAreasChanged.fire();
|
||||
}
|
||||
|
||||
private disposeWidgets() {
|
||||
this.hiddenGroups = [];
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposeWidgets();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class HiddenAreasRenderer extends Disposable {
|
||||
|
||||
constructor(private editor: ICodeEditor, private hiddenAreasProviders: HiddenAreasProvider[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
render() {
|
||||
const ranges: IRange[] = [];
|
||||
for (const hiddenAreaProvider of this.hiddenAreasProviders) {
|
||||
ranges.push(...hiddenAreaProvider.hiddenAreas);
|
||||
}
|
||||
this.editor.setHiddenAreas(ranges);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.editor.setHiddenAreas([]);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class FilteredMatchesRenderer extends Disposable implements HiddenAreasProvider {
|
||||
|
||||
private decorationIds: string[] = [];
|
||||
hiddenAreas: IRange[] = [];
|
||||
|
||||
constructor(private editor: ICodeEditor
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
render(result: IFilterResult | undefined, allSettingsGroups: ISettingsGroup[]): void {
|
||||
this.hiddenAreas = [];
|
||||
if (result) {
|
||||
this.hiddenAreas = this.computeHiddenRanges(result.filteredGroups, result.allGroups);
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match)));
|
||||
} else {
|
||||
this.hiddenAreas = this.computeHiddenRanges(undefined, allSettingsGroups);
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []);
|
||||
}
|
||||
}
|
||||
|
||||
private createDecoration(range: IRange): IModelDeltaDecoration {
|
||||
return {
|
||||
range,
|
||||
options: FilteredMatchesRenderer._FIND_MATCH
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly _FIND_MATCH = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch'
|
||||
});
|
||||
|
||||
private computeHiddenRanges(filteredGroups: ISettingsGroup[] | undefined, allSettingsGroups: ISettingsGroup[]): IRange[] {
|
||||
// Hide the contents of hidden groups
|
||||
const notMatchesRanges: IRange[] = [];
|
||||
if (filteredGroups) {
|
||||
allSettingsGroups.forEach((group, i) => {
|
||||
notMatchesRanges.push({
|
||||
startLineNumber: group.range.startLineNumber - 1,
|
||||
startColumn: group.range.startColumn,
|
||||
endLineNumber: group.range.endLineNumber,
|
||||
endColumn: group.range.endColumn
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return notMatchesRanges;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class HighlightMatchesRenderer extends Disposable {
|
||||
|
||||
private decorationIds: string[] = [];
|
||||
|
||||
constructor(private editor: ICodeEditor
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
render(matches: IRange[]): void {
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, matches.map(match => this.createDecoration(match)));
|
||||
}
|
||||
|
||||
private static readonly _FIND_MATCH = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch'
|
||||
});
|
||||
|
||||
private createDecoration(range: IRange): IModelDeltaDecoration {
|
||||
return {
|
||||
range,
|
||||
options: HighlightMatchesRenderer._FIND_MATCH
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IIndexedSetting extends ISetting {
|
||||
index: number;
|
||||
groupId: string;
|
||||
}
|
||||
|
||||
class EditSettingRenderer extends Disposable {
|
||||
|
||||
private editPreferenceWidgetForCursorPosition: EditPreferenceWidget<IIndexedSetting>;
|
||||
private editPreferenceWidgetForMouseMove: EditPreferenceWidget<IIndexedSetting>;
|
||||
|
||||
private settingsGroups: ISettingsGroup[] = [];
|
||||
associatedPreferencesModel: IPreferencesEditorModel<ISetting>;
|
||||
private toggleEditPreferencesForMouseMoveDelayer: Delayer<void>;
|
||||
|
||||
private readonly _onUpdateSetting: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>();
|
||||
readonly onUpdateSetting: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdateSetting.event;
|
||||
|
||||
constructor(private editor: ICodeEditor, private masterSettingsModel: ISettingsEditorModel,
|
||||
private settingHighlighter: SettingHighlighter,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.editPreferenceWidgetForCursorPosition = <EditPreferenceWidget<IIndexedSetting>>this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.editPreferenceWidgetForMouseMove = <EditPreferenceWidget<IIndexedSetting>>this._register(this.instantiationService.createInstance(EditPreferenceWidget, editor));
|
||||
this.toggleEditPreferencesForMouseMoveDelayer = new Delayer<void>(75);
|
||||
|
||||
this._register(this.editPreferenceWidgetForCursorPosition.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForCursorPosition, e)));
|
||||
this._register(this.editPreferenceWidgetForMouseMove.onClick(e => this.onEditSettingClicked(this.editPreferenceWidgetForMouseMove, e)));
|
||||
|
||||
this._register(this.editor.onDidChangeCursorPosition(positionChangeEvent => this.onPositionChanged(positionChangeEvent)));
|
||||
this._register(this.editor.onMouseMove(mouseMoveEvent => this.onMouseMoved(mouseMoveEvent)));
|
||||
this._register(this.editor.onDidChangeConfiguration(() => this.onConfigurationChanged()));
|
||||
}
|
||||
|
||||
render(settingsGroups: ISettingsGroup[], associatedPreferencesModel: IPreferencesEditorModel<ISetting>): void {
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
this.settingsGroups = settingsGroups;
|
||||
this.associatedPreferencesModel = associatedPreferencesModel;
|
||||
|
||||
const settings = this.getSettings(this.editor.getPosition()!.lineNumber);
|
||||
if (settings.length) {
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
|
||||
}
|
||||
}
|
||||
|
||||
private isDefaultSettings(): boolean {
|
||||
return this.masterSettingsModel instanceof DefaultSettingsEditorModel;
|
||||
}
|
||||
|
||||
private onConfigurationChanged(): void {
|
||||
if (!this.editor.getConfiguration().viewInfo.glyphMargin) {
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private onPositionChanged(positionChangeEvent: ICursorPositionChangedEvent) {
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
const settings = this.getSettings(positionChangeEvent.position.lineNumber);
|
||||
if (settings.length) {
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings);
|
||||
} else {
|
||||
this.editPreferenceWidgetForCursorPosition.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseMoved(mouseMoveEvent: IEditorMouseEvent): void {
|
||||
const editPreferenceWidget = this.getEditPreferenceWidgetUnderMouse(mouseMoveEvent);
|
||||
if (editPreferenceWidget) {
|
||||
this.onMouseOver(editPreferenceWidget);
|
||||
return;
|
||||
}
|
||||
this.settingHighlighter.clear();
|
||||
this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent));
|
||||
}
|
||||
|
||||
private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget<ISetting> | undefined {
|
||||
if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) {
|
||||
const line = mouseMoveEvent.target.position!.lineNumber;
|
||||
if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) {
|
||||
return this.editPreferenceWidgetForMouseMove;
|
||||
}
|
||||
if (this.editPreferenceWidgetForCursorPosition.getLine() === line && this.editPreferenceWidgetForCursorPosition.isVisible()) {
|
||||
return this.editPreferenceWidgetForCursorPosition;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void {
|
||||
const settings = mouseMoveEvent.target.position ? this.getSettings(mouseMoveEvent.target.position.lineNumber) : null;
|
||||
if (settings && settings.length) {
|
||||
this.showEditPreferencesWidget(this.editPreferenceWidgetForMouseMove, settings);
|
||||
} else {
|
||||
this.editPreferenceWidgetForMouseMove.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private showEditPreferencesWidget(editPreferencesWidget: EditPreferenceWidget<ISetting>, settings: IIndexedSetting[]) {
|
||||
const line = settings[0].valueRange.startLineNumber;
|
||||
if (this.editor.getConfiguration().viewInfo.glyphMargin && this.marginFreeFromOtherDecorations(line)) {
|
||||
editPreferencesWidget.show(line, nls.localize('editTtile', "Edit"), settings);
|
||||
const editPreferenceWidgetToHide = editPreferencesWidget === this.editPreferenceWidgetForCursorPosition ? this.editPreferenceWidgetForMouseMove : this.editPreferenceWidgetForCursorPosition;
|
||||
editPreferenceWidgetToHide.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private marginFreeFromOtherDecorations(line: number): boolean {
|
||||
const decorations = this.editor.getLineDecorations(line);
|
||||
if (decorations) {
|
||||
for (const { options } of decorations) {
|
||||
if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf(EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private getSettings(lineNumber: number): IIndexedSetting[] {
|
||||
const configurationMap = this.getConfigurationsMap();
|
||||
return this.getSettingsAtLineNumber(lineNumber).filter(setting => {
|
||||
const configurationNode = configurationMap[setting.key];
|
||||
if (configurationNode) {
|
||||
if (this.isDefaultSettings()) {
|
||||
if (setting.key === 'launch') {
|
||||
// Do not show because of https://github.com/Microsoft/vscode/issues/32593
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (configurationNode.type === 'boolean' || configurationNode.enum) {
|
||||
if ((<SettingsEditorModel>this.masterSettingsModel).configurationTarget !== ConfigurationTarget.WORKSPACE_FOLDER) {
|
||||
return true;
|
||||
}
|
||||
if (configurationNode.scope === ConfigurationScope.RESOURCE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private getSettingsAtLineNumber(lineNumber: number): IIndexedSetting[] {
|
||||
// index of setting, across all groups/sections
|
||||
let index = 0;
|
||||
|
||||
const settings: IIndexedSetting[] = [];
|
||||
for (const group of this.settingsGroups) {
|
||||
if (group.range.startLineNumber > lineNumber) {
|
||||
break;
|
||||
}
|
||||
if (lineNumber >= group.range.startLineNumber && lineNumber <= group.range.endLineNumber) {
|
||||
for (const section of group.sections) {
|
||||
for (const setting of section.settings) {
|
||||
if (setting.range.startLineNumber > lineNumber) {
|
||||
break;
|
||||
}
|
||||
if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) {
|
||||
if (!this.isDefaultSettings() && setting.overrides!.length) {
|
||||
// Only one level because override settings cannot have override settings
|
||||
for (const overrideSetting of setting.overrides!) {
|
||||
if (lineNumber >= overrideSetting.range.startLineNumber && lineNumber <= overrideSetting.range.endLineNumber) {
|
||||
settings.push({ ...overrideSetting, index, groupId: group.id });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
settings.push({ ...setting, index, groupId: group.id });
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private onMouseOver(editPreferenceWidget: EditPreferenceWidget<ISetting>): void {
|
||||
this.settingHighlighter.highlight(editPreferenceWidget.preferences[0]);
|
||||
}
|
||||
|
||||
private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget<IIndexedSetting>, e: IEditorMouseEvent): void {
|
||||
const anchor = { x: e.event.posx, y: e.event.posy + 10 };
|
||||
const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key])
|
||||
: editPreferenceWidget.preferences.map(setting => new ContextSubMenu(setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key])));
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => actions
|
||||
});
|
||||
}
|
||||
|
||||
activateOnSetting(setting: ISetting): boolean {
|
||||
const startLine = setting.keyRange.startLineNumber;
|
||||
const settings = this.getSettings(startLine);
|
||||
if (!settings.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.editPreferenceWidgetForMouseMove.show(startLine, '', settings);
|
||||
const actions = this.getActions(this.editPreferenceWidgetForMouseMove.preferences[0], this.getConfigurationsMap()[this.editPreferenceWidgetForMouseMove.preferences[0].key]);
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.toAbsoluteCoords(new Position(startLine, 1)),
|
||||
getActions: () => actions
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private toAbsoluteCoords(position: Position): { x: number, y: number } {
|
||||
const positionCoords = this.editor.getScrolledVisiblePosition(position);
|
||||
const editorCoords = getDomNodePagePosition(this.editor.getDomNode()!);
|
||||
const x = editorCoords.left + positionCoords!.left;
|
||||
const y = editorCoords.top + positionCoords!.top + positionCoords!.height;
|
||||
|
||||
return { x, y: y + 10 };
|
||||
}
|
||||
|
||||
private getConfigurationsMap(): { [qualifiedKey: string]: IConfigurationPropertySchema } {
|
||||
return Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
|
||||
}
|
||||
|
||||
private getActions(setting: IIndexedSetting, jsonSchema: IJSONSchema): IAction[] {
|
||||
if (jsonSchema.type === 'boolean') {
|
||||
return [<IAction>{
|
||||
id: 'truthyValue',
|
||||
label: 'true',
|
||||
enabled: true,
|
||||
run: () => this.updateSetting(setting.key, true, setting)
|
||||
}, <IAction>{
|
||||
id: 'falsyValue',
|
||||
label: 'false',
|
||||
enabled: true,
|
||||
run: () => this.updateSetting(setting.key, false, setting)
|
||||
}];
|
||||
}
|
||||
if (jsonSchema.enum) {
|
||||
return jsonSchema.enum.map(value => {
|
||||
return <IAction>{
|
||||
id: value,
|
||||
label: JSON.stringify(value),
|
||||
enabled: true,
|
||||
run: () => this.updateSetting(setting.key, value, setting)
|
||||
};
|
||||
});
|
||||
}
|
||||
return this.getDefaultActions(setting);
|
||||
}
|
||||
|
||||
private getDefaultActions(setting: IIndexedSetting): IAction[] {
|
||||
if (this.isDefaultSettings()) {
|
||||
const settingInOtherModel = this.associatedPreferencesModel.getPreference(setting.key);
|
||||
return [<IAction>{
|
||||
id: 'setDefaultValue',
|
||||
label: settingInOtherModel ? nls.localize('replaceDefaultValue', "Replace in Settings") : nls.localize('copyDefaultValue', "Copy to Settings"),
|
||||
enabled: true,
|
||||
run: () => this.updateSetting(setting.key, setting.value, setting)
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private updateSetting(key: string, value: any, source: IIndexedSetting): void {
|
||||
this._onUpdateSetting.fire({ key, value, source });
|
||||
}
|
||||
}
|
||||
|
||||
class SettingHighlighter extends Disposable {
|
||||
|
||||
private fixedHighlighter: RangeHighlightDecorations;
|
||||
private volatileHighlighter: RangeHighlightDecorations;
|
||||
private highlightedSetting: ISetting;
|
||||
|
||||
constructor(private editor: ICodeEditor, private readonly focusEventEmitter: Emitter<ISetting>, private readonly clearFocusEventEmitter: Emitter<ISetting>,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this.fixedHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));
|
||||
this.volatileHighlighter = this._register(instantiationService.createInstance(RangeHighlightDecorations));
|
||||
this.fixedHighlighter.onHighlghtRemoved(() => this.clearFocusEventEmitter.fire(this.highlightedSetting));
|
||||
this.volatileHighlighter.onHighlghtRemoved(() => this.clearFocusEventEmitter.fire(this.highlightedSetting));
|
||||
}
|
||||
|
||||
highlight(setting: ISetting, fix: boolean = false) {
|
||||
this.highlightedSetting = setting;
|
||||
this.volatileHighlighter.removeHighlightRange();
|
||||
this.fixedHighlighter.removeHighlightRange();
|
||||
|
||||
const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter;
|
||||
highlighter.highlightRange({
|
||||
range: setting.valueRange,
|
||||
resource: this.editor.getModel()!.uri
|
||||
}, this.editor);
|
||||
|
||||
this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth);
|
||||
this.focusEventEmitter.fire(setting);
|
||||
}
|
||||
|
||||
clear(fix: boolean = false): void {
|
||||
this.volatileHighlighter.removeHighlightRange();
|
||||
if (fix) {
|
||||
this.fixedHighlighter.removeHighlightRange();
|
||||
}
|
||||
this.clearFocusEventEmitter.fire(this.highlightedSetting);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceConfigurationRenderer extends Disposable {
|
||||
|
||||
private decorationIds: string[] = [];
|
||||
private associatedSettingsEditorModel: IPreferencesEditorModel<ISetting>;
|
||||
private renderingDelayer: Delayer<void> = new Delayer<void>(200);
|
||||
|
||||
constructor(private editor: ICodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render(this.associatedSettingsEditorModel))));
|
||||
}
|
||||
|
||||
render(associatedSettingsEditorModel: IPreferencesEditorModel<ISetting>): void {
|
||||
this.associatedSettingsEditorModel = associatedSettingsEditorModel;
|
||||
// Dim other configurations in workspace configuration file only in the context of Settings Editor
|
||||
if (this.associatedSettingsEditorModel && this.workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.workspaceSettingsEditorModel instanceof WorkspaceConfigurationEditorModel) {
|
||||
const ranges: IRange[] = [];
|
||||
for (const settingsGroup of this.workspaceSettingsEditorModel.configurationGroups) {
|
||||
for (const section of settingsGroup.sections) {
|
||||
for (const setting of section.settings) {
|
||||
if (setting.key !== 'settings') {
|
||||
ranges.push({
|
||||
startLineNumber: setting.keyRange.startLineNumber,
|
||||
startColumn: setting.keyRange.startColumn - 1,
|
||||
endLineNumber: setting.valueRange.endLineNumber,
|
||||
endColumn: setting.valueRange.endColumn
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range)));
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly _DIM_CONFIGURATION_ = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
inlineClassName: 'dim-configuration'
|
||||
});
|
||||
|
||||
private createDecoration(range: IRange): IModelDeltaDecoration {
|
||||
return {
|
||||
range,
|
||||
options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,852 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ActionBar, ActionsOrientation, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
export class SettingsHeaderWidget extends Widget implements IViewZone {
|
||||
|
||||
private id: number;
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
protected titleContainer: HTMLElement;
|
||||
private messageElement: HTMLElement;
|
||||
|
||||
constructor(protected 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;
|
||||
}
|
||||
|
||||
protected 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();
|
||||
});
|
||||
}
|
||||
|
||||
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 = '6px';
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.editor.changeViewZones(accessor => {
|
||||
accessor.removeZone(this.id);
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget {
|
||||
|
||||
private _onClick = this._register(new Emitter<void>());
|
||||
readonly onClick: Event<void> = this._onClick.event;
|
||||
|
||||
protected create() {
|
||||
super.create();
|
||||
|
||||
this.toggleMessage(true);
|
||||
}
|
||||
|
||||
toggleMessage(hasSettings: boolean): void {
|
||||
if (hasSettings) {
|
||||
this.setMessage(localize('defaultSettings', "Place your settings in the right hand side editor to override."));
|
||||
} else {
|
||||
this.setMessage(localize('noSettingsFound', "No Settings Found."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>());
|
||||
readonly 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));
|
||||
|
||||
this._register(focusTracker.onDidFocus(() => this.toggleFocus(true)));
|
||||
this._register(focusTracker.onDidBlur(() => 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();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.settingsGroup.range) {
|
||||
// #61352
|
||||
return;
|
||||
}
|
||||
|
||||
this._afterLineNumber = this.settingsGroup.range.startLineNumber - 2;
|
||||
this.editor.changeViewZones(accessor => {
|
||||
this.id = accessor.addZone(this);
|
||||
this.layout();
|
||||
});
|
||||
}
|
||||
|
||||
toggleCollapse(collapse: boolean) {
|
||||
DOM.toggleClass(this.titleContainer, 'collapsed', collapse);
|
||||
}
|
||||
|
||||
toggleFocus(focus: boolean): void {
|
||||
DOM.toggleClass(this.titleContainer, 'focused', focus);
|
||||
}
|
||||
|
||||
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;
|
||||
if (this.editor.hasModel()) {
|
||||
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();
|
||||
if (this.editor.hasModel()) {
|
||||
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 (!this.settingsGroup.range) {
|
||||
// #60460?
|
||||
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;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.editor.changeViewZones(accessor => {
|
||||
accessor.removeZone(this.id);
|
||||
});
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderSettingsActionItem extends BaseActionItem {
|
||||
|
||||
private _folder: IWorkspaceFolder | null;
|
||||
private _folderSettingCounts = new Map<string, number>();
|
||||
|
||||
private container: HTMLElement;
|
||||
private anchorElement: HTMLElement;
|
||||
private labelElement: HTMLElement;
|
||||
private detailsElement: HTMLElement;
|
||||
private dropDownElement: HTMLElement;
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
action: IAction,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(null, action);
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
this._folder = workspace.folders.length === 1 ? workspace.folders[0] : null;
|
||||
this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(() => this.onWorkspaceFoldersChanged()));
|
||||
}
|
||||
|
||||
get folder(): IWorkspaceFolder | null {
|
||||
return this._folder;
|
||||
}
|
||||
|
||||
set folder(folder: IWorkspaceFolder | null) {
|
||||
this._folder = folder;
|
||||
this.update();
|
||||
}
|
||||
|
||||
setCount(settingsTarget: URI, count: number): void {
|
||||
const workspaceFolder = this.contextService.getWorkspaceFolder(settingsTarget);
|
||||
if (!workspaceFolder) {
|
||||
throw new Error('unknown folder');
|
||||
}
|
||||
const folder = workspaceFolder.uri;
|
||||
this._folderSettingCounts.set(folder.toString(), count);
|
||||
this.update();
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
this.element = container;
|
||||
|
||||
this.container = container;
|
||||
this.labelElement = DOM.$('.action-title');
|
||||
this.detailsElement = DOM.$('.action-details');
|
||||
this.dropDownElement = DOM.$('.dropdown-icon.octicon.octicon-triangle-down.hide');
|
||||
this.anchorElement = DOM.$('a.action-label.folder-settings', {
|
||||
role: 'button',
|
||||
'aria-haspopup': 'true',
|
||||
'tabindex': '0'
|
||||
}, this.labelElement, this.detailsElement, this.dropDownElement);
|
||||
this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.MOUSE_DOWN, e => DOM.EventHelper.stop(e)));
|
||||
this.disposables.push(DOM.addDisposableListener(this.anchorElement, DOM.EventType.CLICK, e => this.onClick(e)));
|
||||
this.disposables.push(DOM.addDisposableListener(this.anchorElement, DOM.EventType.KEY_UP, e => this.onKeyUp(e)));
|
||||
|
||||
DOM.append(this.container, this.anchorElement);
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
private onKeyUp(event: any): void {
|
||||
const keyboardEvent = new StandardKeyboardEvent(event);
|
||||
switch (keyboardEvent.keyCode) {
|
||||
case KeyCode.Enter:
|
||||
case KeyCode.Space:
|
||||
this.onClick(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onClick(event: DOM.EventLike): void {
|
||||
DOM.EventHelper.stop(event, true);
|
||||
if (!this.folder || this._action.checked) {
|
||||
this.showMenu();
|
||||
} else {
|
||||
this._action.run(this._folder);
|
||||
}
|
||||
}
|
||||
|
||||
protected updateEnabled(): void {
|
||||
this.update();
|
||||
}
|
||||
|
||||
protected updateChecked(): void {
|
||||
this.update();
|
||||
}
|
||||
|
||||
private onWorkspaceFoldersChanged(): void {
|
||||
const oldFolder = this._folder;
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
if (oldFolder) {
|
||||
this._folder = workspace.folders.filter(folder => folder.uri.toString() === oldFolder.uri.toString())[0] || workspace.folders[0];
|
||||
}
|
||||
this._folder = this._folder ? this._folder : workspace.folders.length === 1 ? workspace.folders[0] : null;
|
||||
|
||||
this.update();
|
||||
|
||||
if (this._action.checked) {
|
||||
if ((oldFolder || !this._folder)
|
||||
|| (!oldFolder || this._folder)
|
||||
|| (oldFolder && this._folder && (oldFolder as IWorkspaceFolder).uri.toString() === (this._folder as IWorkspaceFolder).uri.toString())) {
|
||||
this._action.run(this._folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
let total = 0;
|
||||
this._folderSettingCounts.forEach(n => total += n);
|
||||
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
if (this._folder) {
|
||||
this.labelElement.textContent = this._folder.name;
|
||||
this.anchorElement.title = this._folder.name;
|
||||
const detailsText = this.labelWithCount(this._action.label, total);
|
||||
this.detailsElement.textContent = detailsText;
|
||||
DOM.toggleClass(this.dropDownElement, 'hide', workspace.folders.length === 1 || !this._action.checked);
|
||||
} else {
|
||||
const labelText = this.labelWithCount(this._action.label, total);
|
||||
this.labelElement.textContent = labelText;
|
||||
this.detailsElement.textContent = '';
|
||||
this.anchorElement.title = this._action.label;
|
||||
DOM.removeClass(this.dropDownElement, 'hide');
|
||||
}
|
||||
DOM.toggleClass(this.anchorElement, 'checked', this._action.checked);
|
||||
DOM.toggleClass(this.container, 'disabled', !this._action.enabled);
|
||||
}
|
||||
|
||||
private showMenu(): void {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.container,
|
||||
getActions: () => this.getDropdownMenuActions(),
|
||||
getActionItem: () => null,
|
||||
onHide: () => {
|
||||
this.anchorElement.blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getDropdownMenuActions(): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
const workspaceFolders = this.contextService.getWorkspace().folders;
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && workspaceFolders.length > 0) {
|
||||
actions.push(...workspaceFolders.map((folder, index) => {
|
||||
const folderCount = this._folderSettingCounts.get(folder.uri.toString());
|
||||
return <IAction>{
|
||||
id: 'folderSettingsTarget' + index,
|
||||
label: this.labelWithCount(folder.name, folderCount),
|
||||
checked: this.folder && this.folder.uri.toString() === folder.uri.toString(),
|
||||
enabled: true,
|
||||
run: () => this._action.run(folder)
|
||||
};
|
||||
}));
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
private labelWithCount(label: string, count: number | undefined): string {
|
||||
// Append the count if it's >0 and not undefined
|
||||
if (count) {
|
||||
label += ` (${count})`;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export type SettingsTarget = ConfigurationTarget.USER | ConfigurationTarget.WORKSPACE | URI;
|
||||
|
||||
export class SettingsTargetsWidget extends Widget {
|
||||
|
||||
private settingsSwitcherBar: ActionBar;
|
||||
private userSettings: Action;
|
||||
private workspaceSettings: Action;
|
||||
private folderSettings: FolderSettingsActionItem;
|
||||
|
||||
private _settingsTarget: SettingsTarget;
|
||||
|
||||
private readonly _onDidTargetChange = new Emitter<SettingsTarget>();
|
||||
readonly onDidTargetChange: Event<SettingsTarget> = this._onDidTargetChange.event;
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super();
|
||||
this.create(parent);
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.onWorkbenchStateChanged()));
|
||||
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update()));
|
||||
}
|
||||
|
||||
private create(parent: HTMLElement): void {
|
||||
const settingsTabsWidget = DOM.append(parent, DOM.$('.settings-tabs-widget'));
|
||||
this.settingsSwitcherBar = this._register(new ActionBar(settingsTabsWidget, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"),
|
||||
animated: false,
|
||||
actionItemProvider: (action: Action) => action.id === 'folderSettings' ? this.folderSettings : null
|
||||
}));
|
||||
|
||||
this.userSettings = new Action('userSettings', localize('userSettings', "User Settings"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER));
|
||||
this.userSettings.tooltip = this.userSettings.label;
|
||||
|
||||
this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace Settings"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
|
||||
this.workspaceSettings.tooltip = this.workspaceSettings.label;
|
||||
|
||||
const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder Settings"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder ? folder.uri : ConfigurationTarget.USER));
|
||||
this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionItem, folderSettingsAction);
|
||||
|
||||
this.update();
|
||||
|
||||
this.settingsSwitcherBar.push([this.userSettings, this.workspaceSettings, folderSettingsAction]);
|
||||
}
|
||||
|
||||
get settingsTarget(): SettingsTarget {
|
||||
return this._settingsTarget;
|
||||
}
|
||||
|
||||
set settingsTarget(settingsTarget: SettingsTarget) {
|
||||
this._settingsTarget = settingsTarget;
|
||||
this.userSettings.checked = ConfigurationTarget.USER === this.settingsTarget;
|
||||
this.workspaceSettings.checked = ConfigurationTarget.WORKSPACE === this.settingsTarget;
|
||||
if (this.settingsTarget instanceof URI) {
|
||||
this.folderSettings.getAction().checked = true;
|
||||
this.folderSettings.folder = this.contextService.getWorkspaceFolder(this.settingsTarget as URI);
|
||||
} else {
|
||||
this.folderSettings.getAction().checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
setResultCount(settingsTarget: SettingsTarget, count: number): void {
|
||||
if (settingsTarget === ConfigurationTarget.WORKSPACE) {
|
||||
let label = localize('workspaceSettings', "Workspace Settings");
|
||||
if (count) {
|
||||
label += ` (${count})`;
|
||||
}
|
||||
|
||||
this.workspaceSettings.label = label;
|
||||
} else if (settingsTarget === ConfigurationTarget.USER) {
|
||||
let label = localize('userSettings', "User Settings");
|
||||
if (count) {
|
||||
label += ` (${count})`;
|
||||
}
|
||||
|
||||
this.userSettings.label = label;
|
||||
} else if (settingsTarget instanceof URI) {
|
||||
this.folderSettings.setCount(settingsTarget, count);
|
||||
}
|
||||
}
|
||||
|
||||
private onWorkbenchStateChanged(): void {
|
||||
this.folderSettings.folder = null;
|
||||
this.update();
|
||||
if (this.settingsTarget === ConfigurationTarget.WORKSPACE && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
this.updateTarget(ConfigurationTarget.USER);
|
||||
}
|
||||
}
|
||||
|
||||
updateTarget(settingsTarget: SettingsTarget): Promise<void> {
|
||||
const isSameTarget = this.settingsTarget === settingsTarget || settingsTarget instanceof URI && this.settingsTarget instanceof URI && this.settingsTarget.toString() === settingsTarget.toString();
|
||||
if (!isSameTarget) {
|
||||
this.settingsTarget = settingsTarget;
|
||||
this._onDidTargetChange.fire(this.settingsTarget);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
DOM.toggleClass(this.settingsSwitcherBar.domNode, 'empty-workbench', this.contextService.getWorkbenchState() === WorkbenchState.EMPTY);
|
||||
this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
|
||||
this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface SearchOptions extends IInputOptions {
|
||||
focusKey?: IContextKey<boolean>;
|
||||
showResultCount?: boolean;
|
||||
ariaLive?: string;
|
||||
ariaLabelledBy?: string;
|
||||
}
|
||||
|
||||
export class SearchWidget extends Widget {
|
||||
|
||||
domNode: HTMLElement;
|
||||
|
||||
private countElement: HTMLElement;
|
||||
private searchContainer: HTMLElement;
|
||||
inputBox: InputBox;
|
||||
private controlsDiv: HTMLElement;
|
||||
|
||||
private readonly _onDidChange: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidChange: Event<string> = this._onDidChange.event;
|
||||
|
||||
private readonly _onFocus: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onFocus: Event<void> = this._onFocus.event;
|
||||
|
||||
constructor(parent: HTMLElement, protected options: SearchOptions,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly 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.controlsDiv = DOM.append(this.domNode, DOM.$('div.settings-search-controls'));
|
||||
|
||||
if (this.options.showResultCount) {
|
||||
this.countElement = DOM.append(this.controlsDiv, 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;
|
||||
|
||||
const color = this.themeService.getTheme().getColor(badgeForeground);
|
||||
this.countElement.style.color = color ? color.toString() : null;
|
||||
}));
|
||||
}
|
||||
|
||||
this.inputBox.inputElement.setAttribute('aria-live', this.options.ariaLive || 'off');
|
||||
if (this.options.ariaLabelledBy) {
|
||||
this.inputBox.inputElement.setAttribute('aria-labelledBy', this.options.ariaLabelledBy);
|
||||
}
|
||||
const focusTracker = this._register(DOM.trackFocus(this.inputBox.inputElement));
|
||||
this._register(focusTracker.onDidFocus(() => this._onFocus.fire()));
|
||||
|
||||
const focusKey = this.options.focusKey;
|
||||
if (focusKey) {
|
||||
this._register(focusTracker.onDidFocus(() => focusKey.set(true)));
|
||||
this._register(focusTracker.onDidBlur(() => 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)));
|
||||
}
|
||||
|
||||
protected createInputBox(parent: HTMLElement): InputBox {
|
||||
const box = this._register(new InputBox(parent, this.contextViewService, this.options));
|
||||
this._register(attachInputBoxStyler(box, this.themeService));
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
showMessage(message: string): void {
|
||||
// Avoid setting the aria-label unnecessarily, the screenreader will read the count every time it's set, since it's aria-live:assertive. #50968
|
||||
if (this.countElement && message !== this.countElement.textContent) {
|
||||
this.countElement.textContent = message;
|
||||
this.inputBox.inputElement.setAttribute('aria-label', message);
|
||||
this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension) {
|
||||
if (dimension.width < 400) {
|
||||
if (this.countElement) {
|
||||
DOM.addClass(this.countElement, 'hide');
|
||||
}
|
||||
|
||||
this.inputBox.inputElement.style.paddingRight = '0px';
|
||||
} else {
|
||||
if (this.countElement) {
|
||||
DOM.removeClass(this.countElement, 'hide');
|
||||
}
|
||||
|
||||
this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
private getControlsWidth(): number {
|
||||
const countWidth = this.countElement ? DOM.getTotalWidth(this.countElement) : 0;
|
||||
return countWidth + 20;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.inputBox.focus();
|
||||
if (this.getValue()) {
|
||||
this.inputBox.select();
|
||||
}
|
||||
}
|
||||
|
||||
hasFocus(): boolean {
|
||||
return this.inputBox.hasFocus();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.inputBox.value = '';
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.inputBox.value;
|
||||
}
|
||||
|
||||
setValue(value: string): string {
|
||||
return this.inputBox.value = value;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.options.focusKey) {
|
||||
this.options.focusKey.set(false);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class EditPreferenceWidget<T> extends Disposable {
|
||||
|
||||
static readonly GLYPH_MARGIN_CLASS_NAME = 'edit-preferences-widget';
|
||||
|
||||
private _line: number;
|
||||
private _preferences: T[];
|
||||
|
||||
private _editPreferenceDecoration: string[];
|
||||
|
||||
private readonly _onClick = new Emitter<IEditorMouseEvent>();
|
||||
get onClick(): Event<IEditorMouseEvent> { return this._onClick.event; }
|
||||
|
||||
constructor(private editor: ICodeEditor
|
||||
) {
|
||||
super();
|
||||
this._editPreferenceDecoration = [];
|
||||
this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => {
|
||||
const data = e.target.detail as IMarginData;
|
||||
if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !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: IModelDeltaDecoration[] = [];
|
||||
this._line = line;
|
||||
newDecoration.push({
|
||||
options: {
|
||||
glyphMarginClassName: EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME,
|
||||
glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage),
|
||||
stickiness: 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();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus,
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
`);
|
||||
// Title Active
|
||||
const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
|
||||
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
|
||||
if (titleActive || titleActiveBorder) {
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:hover,
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Inactive
|
||||
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
|
||||
if (titleInactive) {
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
color: ${titleInactive};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title focus
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus {
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
}
|
||||
`);
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus {
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
|
||||
collector.addRule(`
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked,
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:hover {
|
||||
outline-color: ${outline};
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.settings-tabs-widget > .monaco-action-bar .action-item .action-label:not(.checked):hover {
|
||||
outline-style: dashed;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
206
src/vs/workbench/contrib/preferences/browser/settingsLayout.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ISetting } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
export interface ITOCEntry {
|
||||
id: string;
|
||||
label: string;
|
||||
|
||||
children?: ITOCEntry[];
|
||||
settings?: Array<string | ISetting>;
|
||||
}
|
||||
|
||||
export const commonlyUsedData: ITOCEntry = {
|
||||
id: 'commonlyUsed',
|
||||
label: localize('commonlyUsed', "Commonly Used"),
|
||||
settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations']
|
||||
};
|
||||
|
||||
export const tocData: ITOCEntry = {
|
||||
id: 'root',
|
||||
label: 'root',
|
||||
children: [
|
||||
{
|
||||
id: 'editor',
|
||||
label: localize('textEditor', "Text Editor"),
|
||||
settings: ['editor.*'],
|
||||
children: [
|
||||
{
|
||||
id: 'editor/cursor',
|
||||
label: localize('cursor', "Cursor"),
|
||||
settings: ['editor.cursor*']
|
||||
},
|
||||
{
|
||||
id: 'editor/find',
|
||||
label: localize('find', "Find"),
|
||||
settings: ['editor.find.*']
|
||||
},
|
||||
{
|
||||
id: 'editor/font',
|
||||
label: localize('font', "Font"),
|
||||
settings: ['editor.font*']
|
||||
},
|
||||
{
|
||||
id: 'editor/format',
|
||||
label: localize('formatting', "Formatting"),
|
||||
settings: ['editor.format*']
|
||||
},
|
||||
{
|
||||
id: 'editor/diffEditor',
|
||||
label: localize('diffEditor', "Diff Editor"),
|
||||
settings: ['diffEditor.*']
|
||||
},
|
||||
{
|
||||
id: 'editor/minimap',
|
||||
label: localize('minimap', "Minimap"),
|
||||
settings: ['editor.minimap.*']
|
||||
},
|
||||
{
|
||||
id: 'editor/suggestions',
|
||||
label: localize('suggestions', "Suggestions"),
|
||||
settings: ['editor.*suggest*']
|
||||
},
|
||||
{
|
||||
id: 'editor/files',
|
||||
label: localize('files', "Files"),
|
||||
settings: ['files.*']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'workbench',
|
||||
label: localize('workbench', "Workbench"),
|
||||
settings: ['workbench.*'],
|
||||
children: [
|
||||
{
|
||||
id: 'workbench/appearance',
|
||||
label: localize('appearance', "Appearance"),
|
||||
settings: ['workbench.activityBar.*', 'workbench.*color*', 'workbench.fontAliasing', 'workbench.iconTheme', 'workbench.sidebar.location', 'workbench.*.visible', 'workbench.tips.enabled', 'workbench.tree.*', 'workbench.view.*']
|
||||
},
|
||||
{
|
||||
id: 'workbench/breadcrumbs',
|
||||
label: localize('breadcrumbs', "Breadcrumbs"),
|
||||
settings: ['breadcrumbs.*']
|
||||
},
|
||||
{
|
||||
id: 'workbench/editor',
|
||||
label: localize('editorManagement', "Editor Management"),
|
||||
settings: ['workbench.editor.*']
|
||||
},
|
||||
{
|
||||
id: 'workbench/settings',
|
||||
label: localize('settings', "Settings Editor"),
|
||||
settings: ['workbench.settings.*']
|
||||
},
|
||||
{
|
||||
id: 'workbench/zenmode',
|
||||
label: localize('zenMode', "Zen Mode"),
|
||||
settings: ['zenmode.*']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'window',
|
||||
label: localize('window', "Window"),
|
||||
settings: ['window.*'],
|
||||
children: [
|
||||
{
|
||||
id: 'window/newWindow',
|
||||
label: localize('newWindow', "New Window"),
|
||||
settings: ['window.*newwindow*']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'features',
|
||||
label: localize('features', "Features"),
|
||||
children: [
|
||||
{
|
||||
id: 'features/explorer',
|
||||
label: localize('fileExplorer', "Explorer"),
|
||||
settings: ['explorer.*', 'outline.*']
|
||||
},
|
||||
{
|
||||
id: 'features/search',
|
||||
label: localize('search', "Search"),
|
||||
settings: ['search.*', 'searchRipgrep.*']
|
||||
}
|
||||
,
|
||||
{
|
||||
id: 'features/debug',
|
||||
label: localize('debug', "Debug"),
|
||||
settings: ['debug.*', 'launch']
|
||||
},
|
||||
{
|
||||
id: 'features/scm',
|
||||
label: localize('scm', "SCM"),
|
||||
settings: ['scm.*']
|
||||
},
|
||||
{
|
||||
id: 'features/extensions',
|
||||
label: localize('extensionViewlet', "Extension Viewlet"),
|
||||
settings: ['extensions.*']
|
||||
},
|
||||
{
|
||||
id: 'features/terminal',
|
||||
label: localize('terminal', "Terminal"),
|
||||
settings: ['terminal.*']
|
||||
},
|
||||
{
|
||||
id: 'features/problems',
|
||||
label: localize('problems', "Problems"),
|
||||
settings: ['problems.*']
|
||||
},
|
||||
{
|
||||
id: 'features/comments',
|
||||
label: localize('comments', "Comments"),
|
||||
settings: ['comments.*']
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'application',
|
||||
label: localize('application', "Application"),
|
||||
children: [
|
||||
{
|
||||
id: 'application/http',
|
||||
label: localize('proxy', "Proxy"),
|
||||
settings: ['http.*']
|
||||
},
|
||||
{
|
||||
id: 'application/keyboard',
|
||||
label: localize('keyboard', "Keyboard"),
|
||||
settings: ['keyboard.*']
|
||||
},
|
||||
{
|
||||
id: 'application/update',
|
||||
label: localize('update', "Update"),
|
||||
settings: ['update.*']
|
||||
},
|
||||
{
|
||||
id: 'application/telemetry',
|
||||
label: localize('telemetry', "Telemetry"),
|
||||
settings: ['telemetry.*']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const knownAcronyms = new Set();
|
||||
[
|
||||
'css',
|
||||
'html',
|
||||
'scss',
|
||||
'less',
|
||||
'json',
|
||||
'js',
|
||||
'ts',
|
||||
'ie',
|
||||
'id',
|
||||
'php',
|
||||
].forEach(str => knownAcronyms.add(str));
|
||||
1423
src/vs/workbench/contrib/preferences/browser/settingsTree.ts
Normal file
@@ -0,0 +1,551 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { isArray } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
|
||||
import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
|
||||
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
|
||||
|
||||
export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices';
|
||||
|
||||
export interface ISettingsEditorViewState {
|
||||
settingsTarget: SettingsTarget;
|
||||
tagFilters?: Set<string>;
|
||||
filterToCategory?: SettingsTreeGroupElement;
|
||||
}
|
||||
|
||||
export abstract class SettingsTreeElement {
|
||||
id: string;
|
||||
parent?: SettingsTreeGroupElement;
|
||||
|
||||
/**
|
||||
* Index assigned in display order, used for paging.
|
||||
*/
|
||||
index: number;
|
||||
}
|
||||
|
||||
export type SettingsTreeGroupChild = (SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement);
|
||||
|
||||
export class SettingsTreeGroupElement extends SettingsTreeElement {
|
||||
count?: number;
|
||||
label: string;
|
||||
level: number;
|
||||
isFirstGroup: boolean;
|
||||
|
||||
private _childSettingKeys: Set<string>;
|
||||
private _children: SettingsTreeGroupChild[];
|
||||
|
||||
get children(): SettingsTreeGroupChild[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
set children(newChildren: SettingsTreeGroupChild[]) {
|
||||
this._children = newChildren;
|
||||
|
||||
this._childSettingKeys = new Set();
|
||||
this._children.forEach(child => {
|
||||
if (child instanceof SettingsTreeSettingElement) {
|
||||
this._childSettingKeys.add(child.setting.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this group contains the given child key (to a depth of 1 only)
|
||||
*/
|
||||
containsSetting(key: string): boolean {
|
||||
return this._childSettingKeys.has(key);
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsTreeNewExtensionsElement extends SettingsTreeElement {
|
||||
extensionIds: string[];
|
||||
}
|
||||
|
||||
export class SettingsTreeSettingElement extends SettingsTreeElement {
|
||||
private static MAX_DESC_LINES = 20;
|
||||
|
||||
setting: ISetting;
|
||||
|
||||
private _displayCategory: string;
|
||||
private _displayLabel: string;
|
||||
|
||||
/**
|
||||
* scopeValue || defaultValue, for rendering convenience.
|
||||
*/
|
||||
value: any;
|
||||
|
||||
/**
|
||||
* The value in the current settings scope.
|
||||
*/
|
||||
scopeValue: any;
|
||||
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: any;
|
||||
|
||||
/**
|
||||
* Whether the setting is configured in the selected scope.
|
||||
*/
|
||||
isConfigured: boolean;
|
||||
|
||||
tags?: Set<string>;
|
||||
overriddenScopeList: string[];
|
||||
description: string;
|
||||
valueType: SettingValueType;
|
||||
|
||||
constructor(setting: ISetting, parent: SettingsTreeGroupElement, index: number, inspectResult: IInspectResult) {
|
||||
super();
|
||||
this.index = index;
|
||||
this.setting = setting;
|
||||
this.parent = parent;
|
||||
this.id = sanitizeId(parent.id + '_' + setting.key);
|
||||
|
||||
this.update(inspectResult);
|
||||
}
|
||||
|
||||
get displayCategory(): string {
|
||||
if (!this._displayCategory) {
|
||||
this.initLabel();
|
||||
}
|
||||
|
||||
return this._displayCategory;
|
||||
}
|
||||
|
||||
get displayLabel(): string {
|
||||
if (!this._displayLabel) {
|
||||
this.initLabel();
|
||||
}
|
||||
|
||||
return this._displayLabel;
|
||||
}
|
||||
|
||||
private initLabel(): void {
|
||||
const displayKeyFormat = settingKeyToDisplayFormat(this.setting.key, this.parent!.id);
|
||||
this._displayLabel = displayKeyFormat.label;
|
||||
this._displayCategory = displayKeyFormat.category;
|
||||
}
|
||||
|
||||
update(inspectResult: IInspectResult): void {
|
||||
const { isConfigured, inspected, targetSelector } = inspectResult;
|
||||
|
||||
const displayValue = isConfigured ? inspected[targetSelector] : inspected.default;
|
||||
const overriddenScopeList: string[] = [];
|
||||
if (targetSelector === 'user' && typeof inspected.workspace !== 'undefined') {
|
||||
overriddenScopeList.push(localize('workspace', "Workspace"));
|
||||
}
|
||||
|
||||
if (targetSelector === 'workspace' && typeof inspected.user !== 'undefined') {
|
||||
overriddenScopeList.push(localize('user', "User"));
|
||||
}
|
||||
|
||||
this.value = displayValue;
|
||||
this.scopeValue = isConfigured && inspected[targetSelector];
|
||||
this.defaultValue = inspected.default;
|
||||
|
||||
this.isConfigured = isConfigured;
|
||||
if (isConfigured || this.setting.tags || this.tags) {
|
||||
// Don't create an empty Set for all 1000 settings, only if needed
|
||||
this.tags = new Set<string>();
|
||||
if (isConfigured) {
|
||||
this.tags.add(MODIFIED_SETTING_TAG);
|
||||
}
|
||||
|
||||
if (this.setting.tags) {
|
||||
this.setting.tags.forEach(tag => this.tags!.add(tag));
|
||||
}
|
||||
}
|
||||
|
||||
this.overriddenScopeList = overriddenScopeList;
|
||||
if (this.setting.description.length > SettingsTreeSettingElement.MAX_DESC_LINES) {
|
||||
const truncatedDescLines = this.setting.description.slice(0, SettingsTreeSettingElement.MAX_DESC_LINES);
|
||||
truncatedDescLines.push('[...]');
|
||||
this.description = truncatedDescLines.join('\n');
|
||||
} else {
|
||||
this.description = this.setting.description.join('\n');
|
||||
}
|
||||
|
||||
if (this.setting.enum && (!this.setting.type || settingTypeEnumRenderable(this.setting.type))) {
|
||||
this.valueType = SettingValueType.Enum;
|
||||
} else if (this.setting.type === 'string') {
|
||||
this.valueType = SettingValueType.String;
|
||||
} else if (isExcludeSetting(this.setting)) {
|
||||
this.valueType = SettingValueType.Exclude;
|
||||
} else if (this.setting.type === 'integer') {
|
||||
this.valueType = SettingValueType.Integer;
|
||||
} else if (this.setting.type === 'number') {
|
||||
this.valueType = SettingValueType.Number;
|
||||
} else if (this.setting.type === 'boolean') {
|
||||
this.valueType = SettingValueType.Boolean;
|
||||
} else if (isArray(this.setting.type) && this.setting.type.indexOf(SettingValueType.Null) > -1 && this.setting.type.length === 2) {
|
||||
if (this.setting.type.indexOf(SettingValueType.Integer) > -1) {
|
||||
this.valueType = SettingValueType.NullableInteger;
|
||||
} else if (this.setting.type.indexOf(SettingValueType.Number) > -1) {
|
||||
this.valueType = SettingValueType.NullableNumber;
|
||||
} else {
|
||||
this.valueType = SettingValueType.Complex;
|
||||
}
|
||||
} else {
|
||||
this.valueType = SettingValueType.Complex;
|
||||
}
|
||||
}
|
||||
|
||||
matchesAllTags(tagFilters?: Set<string>): boolean {
|
||||
if (!tagFilters || !tagFilters.size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.tags) {
|
||||
let hasFilteredTag = true;
|
||||
tagFilters.forEach(tag => {
|
||||
hasFilteredTag = hasFilteredTag && this.tags!.has(tag);
|
||||
});
|
||||
return hasFilteredTag;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
matchesScope(scope: SettingsTarget): boolean {
|
||||
const configTarget = URI.isUri(scope) ? ConfigurationTarget.WORKSPACE_FOLDER : scope;
|
||||
|
||||
if (configTarget === ConfigurationTarget.WORKSPACE_FOLDER) {
|
||||
return this.setting.scope === ConfigurationScope.RESOURCE;
|
||||
}
|
||||
|
||||
if (configTarget === ConfigurationTarget.WORKSPACE) {
|
||||
return this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsTreeModel {
|
||||
protected _root: SettingsTreeGroupElement;
|
||||
protected _treeElementsById = new Map<string, SettingsTreeElement>();
|
||||
private _treeElementsBySettingName = new Map<string, SettingsTreeSettingElement[]>();
|
||||
private _tocRoot: ITOCEntry;
|
||||
|
||||
constructor(
|
||||
protected _viewState: ISettingsEditorViewState,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) { }
|
||||
|
||||
get root(): SettingsTreeGroupElement {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
update(newTocRoot = this._tocRoot): void {
|
||||
this._treeElementsById.clear();
|
||||
this._treeElementsBySettingName.clear();
|
||||
|
||||
const newRoot = this.createSettingsTreeGroupElement(newTocRoot);
|
||||
if (newRoot.children[0] instanceof SettingsTreeGroupElement) {
|
||||
(<SettingsTreeGroupElement>newRoot.children[0]).isFirstGroup = true; // TODO
|
||||
}
|
||||
|
||||
if (this._root) {
|
||||
this._root.children = newRoot.children;
|
||||
} else {
|
||||
this._root = newRoot;
|
||||
}
|
||||
}
|
||||
|
||||
getElementById(id: string): SettingsTreeElement | null {
|
||||
return this._treeElementsById.get(id) || null;
|
||||
}
|
||||
|
||||
getElementsByName(name: string): SettingsTreeSettingElement[] | null {
|
||||
return this._treeElementsBySettingName.get(name) || null;
|
||||
}
|
||||
|
||||
updateElementsByName(name: string): void {
|
||||
if (!this._treeElementsBySettingName.has(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._treeElementsBySettingName.get(name)!.forEach(element => {
|
||||
const inspectResult = inspectSetting(element.setting.key, this._viewState.settingsTarget, this._configurationService);
|
||||
element.update(inspectResult);
|
||||
});
|
||||
}
|
||||
|
||||
private createSettingsTreeGroupElement(tocEntry: ITOCEntry, parent?: SettingsTreeGroupElement): SettingsTreeGroupElement {
|
||||
const element = new SettingsTreeGroupElement();
|
||||
const index = this._treeElementsById.size;
|
||||
element.index = index;
|
||||
element.id = tocEntry.id;
|
||||
element.label = tocEntry.label;
|
||||
element.parent = parent;
|
||||
element.level = this.getDepth(element);
|
||||
|
||||
const children: SettingsTreeGroupChild[] = [];
|
||||
if (tocEntry.settings) {
|
||||
const settingChildren = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(<ISetting>s, element))
|
||||
.filter(el => el.setting.deprecationMessage ? el.isConfigured : true);
|
||||
children.push(...settingChildren);
|
||||
}
|
||||
|
||||
if (tocEntry.children) {
|
||||
const groupChildren = tocEntry.children.map(child => this.createSettingsTreeGroupElement(child, element));
|
||||
children.push(...groupChildren);
|
||||
}
|
||||
|
||||
element.children = children;
|
||||
|
||||
this._treeElementsById.set(element.id, element);
|
||||
return element;
|
||||
}
|
||||
|
||||
private getDepth(element: SettingsTreeElement): number {
|
||||
if (element.parent) {
|
||||
return 1 + this.getDepth(element.parent);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement {
|
||||
const index = this._treeElementsById.size;
|
||||
const inspectResult = inspectSetting(setting.key, this._viewState.settingsTarget, this._configurationService);
|
||||
const element = new SettingsTreeSettingElement(setting, parent, index, inspectResult);
|
||||
this._treeElementsById.set(element.id, element);
|
||||
|
||||
const nameElements = this._treeElementsBySettingName.get(setting.key) || [];
|
||||
nameElements.push(element);
|
||||
this._treeElementsBySettingName.set(setting.key, nameElements);
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
interface IInspectResult {
|
||||
isConfigured: boolean;
|
||||
inspected: any;
|
||||
targetSelector: string;
|
||||
}
|
||||
|
||||
function inspectSetting(key: string, target: SettingsTarget, configurationService: IConfigurationService): IInspectResult {
|
||||
const inspectOverrides = URI.isUri(target) ? { resource: target } : undefined;
|
||||
const inspected = configurationService.inspect(key, inspectOverrides);
|
||||
const targetSelector = target === ConfigurationTarget.USER ? 'user' :
|
||||
target === ConfigurationTarget.WORKSPACE ? 'workspace' :
|
||||
'workspaceFolder';
|
||||
const isConfigured = typeof inspected[targetSelector] !== 'undefined';
|
||||
|
||||
return { isConfigured, inspected, targetSelector };
|
||||
}
|
||||
|
||||
function sanitizeId(id: string): string {
|
||||
return id.replace(/[\.\/]/, '_');
|
||||
}
|
||||
|
||||
export function settingKeyToDisplayFormat(key: string, groupId = ''): { category: string, label: string } {
|
||||
const lastDotIdx = key.lastIndexOf('.');
|
||||
let category = '';
|
||||
if (lastDotIdx >= 0) {
|
||||
category = key.substr(0, lastDotIdx);
|
||||
key = key.substr(lastDotIdx + 1);
|
||||
}
|
||||
|
||||
groupId = groupId.replace(/\//g, '.');
|
||||
category = trimCategoryForGroup(category, groupId);
|
||||
category = wordifyKey(category);
|
||||
|
||||
const label = wordifyKey(key);
|
||||
return { category, label };
|
||||
}
|
||||
|
||||
function wordifyKey(key: string): string {
|
||||
return key
|
||||
.replace(/\.([a-z])/g, (match, p1) => ` › ${p1.toUpperCase()}`)
|
||||
.replace(/([a-z])([A-Z])/g, '$1 $2') // fooBar => foo Bar
|
||||
.replace(/^[a-z]/g, match => match.toUpperCase()) // foo => Foo
|
||||
.replace(/\b\w+\b/g, match => {
|
||||
return knownAcronyms.has(match.toLowerCase()) ?
|
||||
match.toUpperCase() :
|
||||
match;
|
||||
});
|
||||
}
|
||||
|
||||
function trimCategoryForGroup(category: string, groupId: string): string {
|
||||
const doTrim = forward => {
|
||||
const parts = groupId.split('.');
|
||||
while (parts.length) {
|
||||
const reg = new RegExp(`^${parts.join('\\.')}(\\.|$)`, 'i');
|
||||
if (reg.test(category)) {
|
||||
return category.replace(reg, '');
|
||||
}
|
||||
|
||||
if (forward) {
|
||||
parts.pop();
|
||||
} else {
|
||||
parts.shift();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
let trimmed = doTrim(true);
|
||||
if (trimmed === null) {
|
||||
trimmed = doTrim(false);
|
||||
}
|
||||
|
||||
if (trimmed === null) {
|
||||
trimmed = category;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function isExcludeSetting(setting: ISetting): boolean {
|
||||
return setting.key === 'files.exclude' ||
|
||||
setting.key === 'search.exclude' ||
|
||||
setting.key === 'files.watcherExclude';
|
||||
}
|
||||
|
||||
function settingTypeEnumRenderable(_type: string | string[]) {
|
||||
const enumRenderableSettingTypes = ['string', 'boolean', 'null', 'integer', 'number'];
|
||||
const type = isArray(_type) ? _type : [_type];
|
||||
return type.every(type => enumRenderableSettingTypes.indexOf(type) > -1);
|
||||
}
|
||||
|
||||
export const enum SearchResultIdx {
|
||||
Local = 0,
|
||||
Remote = 1,
|
||||
NewExtensions = 2
|
||||
}
|
||||
|
||||
export class SearchResultModel extends SettingsTreeModel {
|
||||
private rawSearchResults: ISearchResult[];
|
||||
private cachedUniqueSearchResults: ISearchResult[] | undefined;
|
||||
private newExtensionSearchResults: ISearchResult;
|
||||
|
||||
readonly id = 'searchResultModel';
|
||||
|
||||
constructor(
|
||||
viewState: ISettingsEditorViewState,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(viewState, configurationService);
|
||||
this.update({ id: 'searchResultModel', label: '' });
|
||||
}
|
||||
|
||||
getUniqueResults(): ISearchResult[] {
|
||||
if (this.cachedUniqueSearchResults) {
|
||||
return this.cachedUniqueSearchResults;
|
||||
}
|
||||
|
||||
if (!this.rawSearchResults) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const localMatchKeys = new Set();
|
||||
const localResult = this.rawSearchResults[SearchResultIdx.Local];
|
||||
if (localResult) {
|
||||
localResult.filterMatches.forEach(m => localMatchKeys.add(m.setting.key));
|
||||
}
|
||||
|
||||
const remoteResult = this.rawSearchResults[SearchResultIdx.Remote];
|
||||
if (remoteResult) {
|
||||
remoteResult.filterMatches = remoteResult.filterMatches.filter(m => !localMatchKeys.has(m.setting.key));
|
||||
}
|
||||
|
||||
if (remoteResult) {
|
||||
this.newExtensionSearchResults = this.rawSearchResults[SearchResultIdx.NewExtensions];
|
||||
}
|
||||
|
||||
this.cachedUniqueSearchResults = [localResult, remoteResult];
|
||||
return this.cachedUniqueSearchResults;
|
||||
}
|
||||
|
||||
getRawResults(): ISearchResult[] {
|
||||
return this.rawSearchResults;
|
||||
}
|
||||
|
||||
setResult(order: SearchResultIdx, result: ISearchResult | null): void {
|
||||
this.cachedUniqueSearchResults = undefined;
|
||||
this.rawSearchResults = this.rawSearchResults || [];
|
||||
if (!result) {
|
||||
delete this.rawSearchResults[order];
|
||||
return;
|
||||
}
|
||||
|
||||
this.rawSearchResults[order] = result;
|
||||
this.updateChildren();
|
||||
}
|
||||
|
||||
updateChildren(): void {
|
||||
this.update({
|
||||
id: 'searchResultModel',
|
||||
label: 'searchResultModel',
|
||||
settings: this.getFlatSettings()
|
||||
});
|
||||
|
||||
// Save time, filter children in the search model instead of relying on the tree filter, which still requires heights to be calculated.
|
||||
this.root.children = this.root.children
|
||||
.filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget));
|
||||
|
||||
if (this.newExtensionSearchResults && this.newExtensionSearchResults.filterMatches.length) {
|
||||
const newExtElement = new SettingsTreeNewExtensionsElement();
|
||||
newExtElement.index = this._treeElementsById.size;
|
||||
newExtElement.parent = this._root;
|
||||
newExtElement.id = 'newExtensions';
|
||||
this._treeElementsById.set(newExtElement.id, newExtElement);
|
||||
|
||||
const resultExtensionIds = this.newExtensionSearchResults.filterMatches
|
||||
.map(result => (<IExtensionSetting>result.setting))
|
||||
.filter(setting => setting.extensionName && setting.extensionPublisher)
|
||||
.map(setting => `${setting.extensionPublisher}.${setting.extensionName}`);
|
||||
newExtElement.extensionIds = arrays.distinct(resultExtensionIds);
|
||||
this._root.children.push(newExtElement);
|
||||
}
|
||||
}
|
||||
|
||||
private getFlatSettings(): ISetting[] {
|
||||
const flatSettings: ISetting[] = [];
|
||||
arrays.coalesce(this.getUniqueResults())
|
||||
.forEach(r => {
|
||||
flatSettings.push(
|
||||
...r.filterMatches.map(m => m.setting));
|
||||
});
|
||||
|
||||
return flatSettings;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IParsedQuery {
|
||||
tags: string[];
|
||||
query: string;
|
||||
}
|
||||
|
||||
const tagRegex = /(^|\s)@tag:("([^"]*)"|[^"]\S*)/g;
|
||||
export function parseQuery(query: string): IParsedQuery {
|
||||
const tags: string[] = [];
|
||||
query = query.replace(tagRegex, (_, __, quotedTag, tag) => {
|
||||
tags.push(tag || quotedTag);
|
||||
return '';
|
||||
});
|
||||
|
||||
query = query.replace(`@${MODIFIED_SETTING_TAG}`, () => {
|
||||
tags.push(MODIFIED_SETTING_TAG);
|
||||
return '';
|
||||
});
|
||||
|
||||
query = query.trim();
|
||||
|
||||
return {
|
||||
tags,
|
||||
query
|
||||
};
|
||||
}
|
||||
492
src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts
Normal file
@@ -0,0 +1,492 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./media/settingsWidgets';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground, editorWidgetBorder, textLinkActiveForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
|
||||
const $ = DOM.$;
|
||||
export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "(For settings editor preview) The foreground color for a section header or active title."));
|
||||
export const modifiedItemIndicator = registerColor('settings.modifiedItemIndicator', {
|
||||
light: new Color(new RGBA(102, 175, 224)),
|
||||
dark: new Color(new RGBA(12, 125, 157)),
|
||||
hc: new Color(new RGBA(0, 73, 122))
|
||||
}, localize('modifiedItemForeground', "(For settings editor preview) The color of the modified setting indicator."));
|
||||
|
||||
// Enum control colors
|
||||
export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "(For settings editor preview) Settings editor dropdown background."));
|
||||
export const settingsSelectForeground = registerColor('settings.dropdownForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsDropdownForeground', "(For settings editor preview) Settings editor dropdown foreground."));
|
||||
export const settingsSelectBorder = registerColor('settings.dropdownBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsDropdownBorder', "(For settings editor preview) Settings editor dropdown border."));
|
||||
export const settingsSelectListBorder = registerColor('settings.dropdownListBorder', { dark: editorWidgetBorder, light: editorWidgetBorder, hc: editorWidgetBorder }, localize('settingsDropdownListBorder', "(For settings editor preview) Settings editor dropdown list border. This surrounds the options and separates the options from the description."));
|
||||
|
||||
// Bool control colors
|
||||
export const settingsCheckboxBackground = registerColor('settings.checkboxBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsCheckboxBackground', "(For settings editor preview) Settings editor checkbox background."));
|
||||
export const settingsCheckboxForeground = registerColor('settings.checkboxForeground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, localize('settingsCheckboxForeground', "(For settings editor preview) Settings editor checkbox foreground."));
|
||||
export const settingsCheckboxBorder = registerColor('settings.checkboxBorder', { dark: selectBorder, light: selectBorder, hc: selectBorder }, localize('settingsCheckboxBorder', "(For settings editor preview) Settings editor checkbox border."));
|
||||
|
||||
// Text control colors
|
||||
export const settingsTextInputBackground = registerColor('settings.textInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('textInputBoxBackground', "(For settings editor preview) Settings editor text input box background."));
|
||||
export const settingsTextInputForeground = registerColor('settings.textInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('textInputBoxForeground', "(For settings editor preview) Settings editor text input box foreground."));
|
||||
export const settingsTextInputBorder = registerColor('settings.textInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('textInputBoxBorder', "(For settings editor preview) Settings editor text input box border."));
|
||||
|
||||
// Number control colors
|
||||
export const settingsNumberInputBackground = registerColor('settings.numberInputBackground', { dark: inputBackground, light: inputBackground, hc: inputBackground }, localize('numberInputBoxBackground', "(For settings editor preview) Settings editor number input box background."));
|
||||
export const settingsNumberInputForeground = registerColor('settings.numberInputForeground', { dark: inputForeground, light: inputForeground, hc: inputForeground }, localize('numberInputBoxForeground', "(For settings editor preview) Settings editor number input box foreground."));
|
||||
export const settingsNumberInputBorder = registerColor('settings.numberInputBorder', { dark: inputBorder, light: inputBorder, hc: inputBorder }, localize('numberInputBoxBorder', "(For settings editor preview) Settings editor number input box border."));
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const checkboxBackgroundColor = theme.getColor(settingsCheckboxBackground);
|
||||
if (checkboxBackgroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { background-color: ${checkboxBackgroundColor} !important; }`);
|
||||
}
|
||||
|
||||
const checkboxBorderColor = theme.getColor(settingsCheckboxBorder);
|
||||
if (checkboxBorderColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox { border-color: ${checkboxBorderColor} !important; }`);
|
||||
}
|
||||
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a { color: ${link}; }`);
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a > code { color: ${link}; }`);
|
||||
collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a { color: ${link}; }`);
|
||||
collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a > code { color: ${link}; }`);
|
||||
}
|
||||
|
||||
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||
if (activeLink) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active { color: ${activeLink}; }`);
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover > code, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active > code { color: ${activeLink}; }`);
|
||||
collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active { color: ${activeLink}; }`);
|
||||
collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover > code, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active > code { color: ${activeLink}; }`);
|
||||
}
|
||||
|
||||
const headerForegroundColor = theme.getColor(settingsHeaderForeground);
|
||||
if (headerForegroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label.checked { color: ${headerForegroundColor}; border-bottom-color: ${headerForegroundColor}; }`);
|
||||
}
|
||||
|
||||
const foregroundColor = theme.getColor(foreground);
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label { color: ${foregroundColor}; }`);
|
||||
}
|
||||
|
||||
// Exclude control
|
||||
const listHoverBackgroundColor = theme.getColor(listHoverBackground);
|
||||
if (listHoverBackgroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover { background-color: ${listHoverBackgroundColor}; }`);
|
||||
}
|
||||
|
||||
const listHoverForegroundColor = theme.getColor(listHoverForeground);
|
||||
if (listHoverForegroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover { color: ${listHoverForegroundColor}; }`);
|
||||
}
|
||||
|
||||
const listSelectBackgroundColor = theme.getColor(listActiveSelectionBackground);
|
||||
if (listSelectBackgroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:focus { background-color: ${listSelectBackgroundColor}; }`);
|
||||
}
|
||||
|
||||
const listInactiveSelectionBackgroundColor = theme.getColor(listInactiveSelectionBackground);
|
||||
if (listInactiveSelectionBackgroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:not(:focus) { background-color: ${listInactiveSelectionBackgroundColor}; }`);
|
||||
}
|
||||
|
||||
const listInactiveSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground);
|
||||
if (listInactiveSelectionForegroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:not(:focus) { color: ${listInactiveSelectionForegroundColor}; }`);
|
||||
}
|
||||
|
||||
const listSelectForegroundColor = theme.getColor(listActiveSelectionForeground);
|
||||
if (listSelectForegroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:focus { color: ${listSelectForegroundColor}; }`);
|
||||
}
|
||||
|
||||
const codeTextForegroundColor = theme.getColor(textPreformatForeground);
|
||||
if (codeTextForegroundColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { color: ${codeTextForegroundColor} }`);
|
||||
collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown code { color: ${codeTextForegroundColor} }`);
|
||||
|
||||
}
|
||||
|
||||
const modifiedItemIndicatorColor = theme.getColor(modifiedItemIndicator);
|
||||
if (modifiedItemIndicatorColor) {
|
||||
collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents > .setting-item-modified-indicator { border-color: ${modifiedItemIndicatorColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
export class ExcludeSettingListModel {
|
||||
private _dataItems: IExcludeDataItem[] = [];
|
||||
private _editKey: string | null;
|
||||
private _selectedIdx: number | null;
|
||||
|
||||
get items(): IExcludeViewItem[] {
|
||||
const items = this._dataItems.map((item, i) => {
|
||||
const editing = item.pattern === this._editKey;
|
||||
return <IExcludeViewItem>{
|
||||
...item,
|
||||
editing,
|
||||
selected: i === this._selectedIdx || editing
|
||||
};
|
||||
});
|
||||
|
||||
if (this._editKey === '') {
|
||||
items.push({
|
||||
editing: true,
|
||||
selected: true,
|
||||
pattern: '',
|
||||
sibling: ''
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
setEditKey(key: string | null): void {
|
||||
this._editKey = key;
|
||||
}
|
||||
|
||||
setValue(excludeData: IExcludeDataItem[]): void {
|
||||
this._dataItems = excludeData;
|
||||
}
|
||||
|
||||
select(idx: number): void {
|
||||
this._selectedIdx = idx;
|
||||
}
|
||||
|
||||
getSelected(): number | null {
|
||||
return this._selectedIdx;
|
||||
}
|
||||
|
||||
selectNext(): void {
|
||||
if (typeof this._selectedIdx === 'number') {
|
||||
this._selectedIdx = Math.min(this._selectedIdx + 1, this._dataItems.length - 1);
|
||||
} else {
|
||||
this._selectedIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
selectPrevious(): void {
|
||||
if (typeof this._selectedIdx === 'number') {
|
||||
this._selectedIdx = Math.max(this._selectedIdx - 1, 0);
|
||||
} else {
|
||||
this._selectedIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExcludeChangeEvent {
|
||||
originalPattern: string;
|
||||
pattern?: string;
|
||||
sibling?: string;
|
||||
}
|
||||
|
||||
export class ExcludeSettingWidget extends Disposable {
|
||||
private listElement: HTMLElement;
|
||||
private listDisposables: IDisposable[] = [];
|
||||
|
||||
private model = new ExcludeSettingListModel();
|
||||
|
||||
private readonly _onDidChangeExclude = new Emitter<IExcludeChangeEvent>();
|
||||
readonly onDidChangeExclude: Event<IExcludeChangeEvent> = this._onDidChangeExclude.event;
|
||||
|
||||
get domNode(): HTMLElement {
|
||||
return this.listElement;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.listElement = DOM.append(container, $('.setting-exclude-widget'));
|
||||
this.listElement.setAttribute('tabindex', '0');
|
||||
DOM.append(container, this.renderAddButton());
|
||||
this.renderList();
|
||||
|
||||
this._register(DOM.addDisposableListener(this.listElement, DOM.EventType.CLICK, e => this.onListClick(e)));
|
||||
this._register(DOM.addDisposableListener(this.listElement, DOM.EventType.DBLCLICK, e => this.onListDoubleClick(e)));
|
||||
|
||||
this._register(DOM.addStandardDisposableListener(this.listElement, 'keydown', (e: KeyboardEvent) => {
|
||||
if (e.keyCode === KeyCode.UpArrow) {
|
||||
this.model.selectPrevious();
|
||||
this.renderList();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
} else if (e.keyCode === KeyCode.DownArrow) {
|
||||
this.model.selectNext();
|
||||
this.renderList();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
setValue(excludeData: IExcludeDataItem[]): void {
|
||||
this.model.setValue(excludeData);
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
private onListClick(e: MouseEvent): void {
|
||||
const targetIdx = this.getClickedItemIndex(e);
|
||||
if (targetIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.model.getSelected() === targetIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.model.select(targetIdx);
|
||||
this.renderList();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private onListDoubleClick(e: MouseEvent): void {
|
||||
const targetIdx = this.getClickedItemIndex(e);
|
||||
if (targetIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.model.items[targetIdx];
|
||||
if (item) {
|
||||
this.editSetting(item.pattern);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private getClickedItemIndex(e: MouseEvent): number {
|
||||
if (!e.target) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const actionbar = DOM.findParentWithClass(<any>e.target, 'monaco-action-bar');
|
||||
if (actionbar) {
|
||||
// Don't handle doubleclicks inside the action bar
|
||||
return -1;
|
||||
}
|
||||
|
||||
const element = DOM.findParentWithClass((<any>e.target), 'setting-exclude-row');
|
||||
if (!element) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const targetIdxStr = element.getAttribute('data-index');
|
||||
if (!targetIdxStr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const targetIdx = parseInt(targetIdxStr);
|
||||
return targetIdx;
|
||||
}
|
||||
|
||||
private renderList(): void {
|
||||
const focused = DOM.isAncestor(document.activeElement, this.listElement);
|
||||
|
||||
DOM.clearNode(this.listElement);
|
||||
this.listDisposables = dispose(this.listDisposables);
|
||||
|
||||
const newMode = this.model.items.some(item => !!(item.editing && !item.pattern));
|
||||
DOM.toggleClass(this.container, 'setting-exclude-new-mode', newMode);
|
||||
|
||||
this.model.items
|
||||
.map((item, i) => this.renderItem(item, i, focused))
|
||||
.forEach(itemElement => this.listElement.appendChild(itemElement));
|
||||
|
||||
const listHeight = 22 * this.model.items.length;
|
||||
this.listElement.style.height = listHeight + 'px';
|
||||
}
|
||||
|
||||
private createDeleteAction(key: string): IAction {
|
||||
return <IAction>{
|
||||
class: 'setting-excludeAction-remove',
|
||||
enabled: true,
|
||||
id: 'workbench.action.removeExcludeItem',
|
||||
tooltip: localize('removeExcludeItem', "Remove Exclude Item"),
|
||||
run: () => this._onDidChangeExclude.fire({ originalPattern: key, pattern: undefined })
|
||||
};
|
||||
}
|
||||
|
||||
private createEditAction(key: string): IAction {
|
||||
return <IAction>{
|
||||
class: 'setting-excludeAction-edit',
|
||||
enabled: true,
|
||||
id: 'workbench.action.editExcludeItem',
|
||||
tooltip: localize('editExcludeItem', "Edit Exclude Item"),
|
||||
run: () => {
|
||||
this.editSetting(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private editSetting(key: string): void {
|
||||
this.model.setEditKey(key);
|
||||
this.renderList();
|
||||
}
|
||||
|
||||
private renderItem(item: IExcludeViewItem, idx: number, listFocused: boolean): HTMLElement {
|
||||
return item.editing ?
|
||||
this.renderEditItem(item) :
|
||||
this.renderDataItem(item, idx, listFocused);
|
||||
}
|
||||
|
||||
private renderDataItem(item: IExcludeViewItem, idx: number, listFocused: boolean): HTMLElement {
|
||||
const rowElement = $('.setting-exclude-row');
|
||||
rowElement.setAttribute('data-index', idx + '');
|
||||
rowElement.setAttribute('tabindex', item.selected ? '0' : '-1');
|
||||
DOM.toggleClass(rowElement, 'selected', item.selected);
|
||||
|
||||
const actionBar = new ActionBar(rowElement);
|
||||
this.listDisposables.push(actionBar);
|
||||
|
||||
const patternElement = DOM.append(rowElement, $('.setting-exclude-pattern'));
|
||||
const siblingElement = DOM.append(rowElement, $('.setting-exclude-sibling'));
|
||||
patternElement.textContent = item.pattern;
|
||||
siblingElement.textContent = item.sibling ? ('when: ' + item.sibling) : null;
|
||||
|
||||
actionBar.push([
|
||||
this.createEditAction(item.pattern),
|
||||
this.createDeleteAction(item.pattern)
|
||||
], { icon: true, label: false });
|
||||
|
||||
rowElement.title = item.sibling ?
|
||||
localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", item.pattern, item.sibling) :
|
||||
localize('excludePatternHintLabel', "Exclude files matching `{0}`", item.pattern);
|
||||
|
||||
if (item.selected) {
|
||||
if (listFocused) {
|
||||
setTimeout(() => {
|
||||
rowElement.focus();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return rowElement;
|
||||
}
|
||||
|
||||
private renderAddButton(): HTMLElement {
|
||||
const rowElement = $('.setting-exclude-new-row');
|
||||
|
||||
const startAddButton = this._register(new Button(rowElement));
|
||||
startAddButton.label = localize('addPattern', "Add Pattern");
|
||||
startAddButton.element.classList.add('setting-exclude-addButton');
|
||||
this._register(attachButtonStyler(startAddButton, this.themeService));
|
||||
|
||||
this._register(startAddButton.onDidClick(() => {
|
||||
this.model.setEditKey('');
|
||||
this.renderList();
|
||||
}));
|
||||
|
||||
return rowElement;
|
||||
}
|
||||
|
||||
private renderEditItem(item: IExcludeViewItem): HTMLElement {
|
||||
const rowElement = $('.setting-exclude-edit-row');
|
||||
|
||||
const onSubmit = edited => {
|
||||
this.model.setEditKey(null);
|
||||
const pattern = patternInput.value.trim();
|
||||
if (edited && pattern) {
|
||||
this._onDidChangeExclude.fire({
|
||||
originalPattern: item.pattern,
|
||||
pattern,
|
||||
sibling: siblingInput && siblingInput.value.trim()
|
||||
});
|
||||
}
|
||||
this.renderList();
|
||||
};
|
||||
|
||||
const onKeydown = (e: StandardKeyboardEvent) => {
|
||||
if (e.equals(KeyCode.Enter)) {
|
||||
onSubmit(true);
|
||||
} else if (e.equals(KeyCode.Escape)) {
|
||||
onSubmit(false);
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const patternInput = new InputBox(rowElement, this.contextViewService, {
|
||||
placeholder: localize('excludePatternInputPlaceholder', "Exclude Pattern...")
|
||||
});
|
||||
patternInput.element.classList.add('setting-exclude-patternInput');
|
||||
this.listDisposables.push(attachInputBoxStyler(patternInput, this.themeService, {
|
||||
inputBackground: settingsTextInputBackground,
|
||||
inputForeground: settingsTextInputForeground,
|
||||
inputBorder: settingsTextInputBorder
|
||||
}));
|
||||
this.listDisposables.push(patternInput);
|
||||
patternInput.value = item.pattern;
|
||||
this.listDisposables.push(DOM.addStandardDisposableListener(patternInput.inputElement, DOM.EventType.KEY_DOWN, onKeydown));
|
||||
|
||||
let siblingInput: InputBox;
|
||||
if (item.sibling) {
|
||||
siblingInput = new InputBox(rowElement, this.contextViewService, {
|
||||
placeholder: localize('excludeSiblingInputPlaceholder', "When Pattern Is Present...")
|
||||
});
|
||||
siblingInput.element.classList.add('setting-exclude-siblingInput');
|
||||
this.listDisposables.push(siblingInput);
|
||||
this.listDisposables.push(attachInputBoxStyler(siblingInput, this.themeService, {
|
||||
inputBackground: settingsTextInputBackground,
|
||||
inputForeground: settingsTextInputForeground,
|
||||
inputBorder: settingsTextInputBorder
|
||||
}));
|
||||
siblingInput.value = item.sibling;
|
||||
this.listDisposables.push(DOM.addStandardDisposableListener(siblingInput.inputElement, DOM.EventType.KEY_DOWN, onKeydown));
|
||||
}
|
||||
|
||||
const okButton = this._register(new Button(rowElement));
|
||||
okButton.label = localize('okButton', "OK");
|
||||
okButton.element.classList.add('setting-exclude-okButton');
|
||||
this.listDisposables.push(attachButtonStyler(okButton, this.themeService));
|
||||
this.listDisposables.push(okButton.onDidClick(() => onSubmit(true)));
|
||||
|
||||
const cancelButton = this._register(new Button(rowElement));
|
||||
cancelButton.label = localize('cancelButton', "Cancel");
|
||||
cancelButton.element.classList.add('setting-exclude-cancelButton');
|
||||
this.listDisposables.push(attachButtonStyler(cancelButton, this.themeService));
|
||||
this.listDisposables.push(cancelButton.onDidClick(() => onSubmit(false)));
|
||||
|
||||
this.listDisposables.push(
|
||||
disposableTimeout(() => {
|
||||
patternInput.focus();
|
||||
patternInput.select();
|
||||
}));
|
||||
|
||||
return rowElement;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this.listDisposables = dispose(this.listDisposables);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IExcludeDataItem {
|
||||
pattern: string;
|
||||
sibling?: string;
|
||||
}
|
||||
|
||||
interface IExcludeViewItem extends IExcludeDataItem {
|
||||
editing?: boolean;
|
||||
selected?: boolean;
|
||||
}
|
||||
228
src/vs/workbench/contrib/preferences/browser/tocTree.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { DefaultStyleController, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
|
||||
import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Iterator } from 'vs/base/common/iterator';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { SettingsTreeFilter } from 'vs/workbench/contrib/preferences/browser/settingsTree';
|
||||
import { ISettingsEditorViewState, SearchResultModel, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
|
||||
import { settingsHeaderForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const $ = DOM.$;
|
||||
|
||||
export class TOCTreeModel {
|
||||
|
||||
private _currentSearchModel: SearchResultModel | null;
|
||||
private _settingsTreeRoot: SettingsTreeGroupElement;
|
||||
|
||||
constructor(private _viewState: ISettingsEditorViewState) {
|
||||
}
|
||||
|
||||
get settingsTreeRoot(): SettingsTreeGroupElement {
|
||||
return this._settingsTreeRoot;
|
||||
}
|
||||
|
||||
set settingsTreeRoot(value: SettingsTreeGroupElement) {
|
||||
this._settingsTreeRoot = value;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get currentSearchModel(): SearchResultModel | null {
|
||||
return this._currentSearchModel;
|
||||
}
|
||||
|
||||
set currentSearchModel(model: SearchResultModel | null) {
|
||||
this._currentSearchModel = model;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get children(): SettingsTreeElement[] {
|
||||
return this._settingsTreeRoot.children;
|
||||
}
|
||||
|
||||
update(): void {
|
||||
if (this._settingsTreeRoot) {
|
||||
this.updateGroupCount(this._settingsTreeRoot);
|
||||
}
|
||||
}
|
||||
|
||||
private updateGroupCount(group: SettingsTreeGroupElement): void {
|
||||
group.children.forEach(child => {
|
||||
if (child instanceof SettingsTreeGroupElement) {
|
||||
this.updateGroupCount(child);
|
||||
}
|
||||
});
|
||||
|
||||
const childCount = group.children
|
||||
.filter(child => child instanceof SettingsTreeGroupElement)
|
||||
.reduce((acc, cur) => acc + (<SettingsTreeGroupElement>cur).count!, 0);
|
||||
|
||||
group.count = childCount + this.getGroupCount(group);
|
||||
}
|
||||
|
||||
private getGroupCount(group: SettingsTreeGroupElement): number {
|
||||
return group.children.filter(child => {
|
||||
if (!(child instanceof SettingsTreeSettingElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._currentSearchModel && !this._currentSearchModel.root.containsSetting(child.setting.key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check everything that the SettingsFilter checks except whether it's filtered by a category
|
||||
return child.matchesScope(this._viewState.settingsTarget) && child.matchesAllTags(this._viewState.tagFilters);
|
||||
}).length;
|
||||
}
|
||||
}
|
||||
|
||||
const TOC_ENTRY_TEMPLATE_ID = 'settings.toc.entry';
|
||||
|
||||
interface ITOCEntryTemplate {
|
||||
labelElement: HTMLElement;
|
||||
countElement: HTMLElement;
|
||||
}
|
||||
|
||||
export class TOCRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, ITOCEntryTemplate> {
|
||||
|
||||
templateId = TOC_ENTRY_TEMPLATE_ID;
|
||||
|
||||
renderTemplate(container: HTMLElement): ITOCEntryTemplate {
|
||||
return {
|
||||
labelElement: DOM.append(container, $('.settings-toc-entry')),
|
||||
countElement: DOM.append(container, $('.settings-toc-count'))
|
||||
};
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<SettingsTreeGroupElement>, index: number, template: ITOCEntryTemplate): void {
|
||||
const element = node.element;
|
||||
const count = element.count;
|
||||
const label = element.label;
|
||||
|
||||
template.labelElement.textContent = label;
|
||||
|
||||
if (count) {
|
||||
template.countElement.textContent = ` (${count})`;
|
||||
} else {
|
||||
template.countElement.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ITOCEntryTemplate): void {
|
||||
}
|
||||
}
|
||||
|
||||
class TOCTreeDelegate implements IListVirtualDelegate<SettingsTreeElement> {
|
||||
getTemplateId(element: SettingsTreeElement): string {
|
||||
return TOC_ENTRY_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
getHeight(element: SettingsTreeElement): number {
|
||||
return 22;
|
||||
}
|
||||
}
|
||||
|
||||
export function createTOCIterator(model: TOCTreeModel | SettingsTreeGroupElement, tree: TOCTree): Iterator<ITreeElement<SettingsTreeGroupElement>> {
|
||||
const groupChildren = <SettingsTreeGroupElement[]>model.children.filter(c => c instanceof SettingsTreeGroupElement);
|
||||
const groupsIt = Iterator.fromArray(groupChildren);
|
||||
|
||||
|
||||
return Iterator.map(groupsIt, g => {
|
||||
let nodeExists = true;
|
||||
try { tree.getNode(g); } catch (e) { nodeExists = false; }
|
||||
|
||||
const hasGroupChildren = g.children.some(c => c instanceof SettingsTreeGroupElement);
|
||||
|
||||
return {
|
||||
element: g,
|
||||
collapsed: nodeExists ? undefined : true,
|
||||
collapsible: hasGroupChildren,
|
||||
children: g instanceof SettingsTreeGroupElement ?
|
||||
createTOCIterator(g, tree) :
|
||||
undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class SettingsAccessibilityProvider implements IAccessibilityProvider<SettingsTreeGroupElement> {
|
||||
getAriaLabel(element: SettingsTreeElement): string {
|
||||
if (!element) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (element instanceof SettingsTreeGroupElement) {
|
||||
return localize('groupRowAriaLabel', "{0}, group", element.label);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
getAriaLevel(element: SettingsTreeGroupElement): number {
|
||||
let i = 1;
|
||||
while (element instanceof SettingsTreeGroupElement && element.parent) {
|
||||
i++;
|
||||
element = element.parent;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
export class TOCTree extends ObjectTree<SettingsTreeGroupElement> {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
viewState: ISettingsEditorViewState,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
// test open mode
|
||||
|
||||
const treeClass = 'settings-toc-tree';
|
||||
const filter = instantiationService.createInstance(SettingsTreeFilter, viewState);
|
||||
const options: IObjectTreeOptions<SettingsTreeGroupElement> = {
|
||||
filter,
|
||||
multipleSelectionSupport: false,
|
||||
identityProvider: {
|
||||
getId(e) {
|
||||
return e.id;
|
||||
}
|
||||
},
|
||||
styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass),
|
||||
accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider)
|
||||
};
|
||||
|
||||
super(container,
|
||||
new TOCTreeDelegate(),
|
||||
[new TOCRenderer()],
|
||||
options);
|
||||
|
||||
this.getHTMLElement().classList.add(treeClass);
|
||||
|
||||
this.disposables.push(attachStyler(themeService, {
|
||||
listActiveSelectionBackground: editorBackground,
|
||||
listActiveSelectionForeground: settingsHeaderForeground,
|
||||
listFocusAndSelectionBackground: editorBackground,
|
||||
listFocusAndSelectionForeground: settingsHeaderForeground,
|
||||
listFocusBackground: editorBackground,
|
||||
listFocusForeground: settingsHeaderForeground,
|
||||
listHoverForeground: settingsHeaderForeground,
|
||||
listHoverBackground: editorBackground,
|
||||
listInactiveSelectionBackground: editorBackground,
|
||||
listInactiveSelectionForeground: settingsHeaderForeground,
|
||||
listInactiveFocusBackground: editorBackground,
|
||||
listInactiveFocusOutline: editorBackground
|
||||
}, colors => {
|
||||
this.style(colors);
|
||||
}));
|
||||
}
|
||||
}
|
||||
112
src/vs/workbench/contrib/preferences/common/preferences.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface IWorkbenchSettingsConfiguration {
|
||||
workbench: {
|
||||
settings: {
|
||||
openDefaultSettings: boolean;
|
||||
naturalLanguageSearchEndpoint: string;
|
||||
naturalLanguageSearchKey: string;
|
||||
naturalLanguageSearchAutoIngestFeedback: boolean;
|
||||
useNaturalLanguageSearchPost: boolean;
|
||||
enableNaturalLanguageSearch: boolean;
|
||||
enableNaturalLanguageSearchFeedback: boolean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface IEndpointDetails {
|
||||
urlBase: string;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export const IPreferencesSearchService = createDecorator<IPreferencesSearchService>('preferencesSearchService');
|
||||
|
||||
export interface IPreferencesSearchService {
|
||||
_serviceBrand: any;
|
||||
|
||||
getLocalSearchProvider(filter: string): ISearchProvider;
|
||||
getRemoteSearchProvider(filter: string, newExtensionsOnly?: boolean): ISearchProvider | undefined;
|
||||
}
|
||||
|
||||
export interface ISearchProvider {
|
||||
searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise<ISearchResult | null>;
|
||||
}
|
||||
|
||||
export interface IKeybindingsEditor extends IEditor {
|
||||
|
||||
readonly activeKeybindingEntry: IKeybindingItemEntry;
|
||||
readonly onDefineWhenExpression: Event<IKeybindingItemEntry>;
|
||||
readonly onLayout: Event<void>;
|
||||
|
||||
search(filter: string): void;
|
||||
focusSearch(): void;
|
||||
clearSearchResults(): void;
|
||||
focusKeybindings(): void;
|
||||
recordSearchKeys(): void;
|
||||
toggleSortByPrecedence(): void;
|
||||
layoutColumns(columns: HTMLElement[]): void;
|
||||
selectKeybinding(keybindingEntry: IKeybindingItemEntry): void;
|
||||
defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<void>;
|
||||
defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void;
|
||||
updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise<any>;
|
||||
removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<any>;
|
||||
resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise<any>;
|
||||
copyKeybinding(keybindingEntry: IKeybindingItemEntry): void;
|
||||
copyKeybindingCommand(keybindingEntry: IKeybindingItemEntry): void;
|
||||
showSimilarKeybindings(keybindingEntry: IKeybindingItemEntry): void;
|
||||
}
|
||||
|
||||
export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEditor', false);
|
||||
export const CONTEXT_SETTINGS_JSON_EDITOR = new RawContextKey<boolean>('inSettingsJSONEditor', false);
|
||||
export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inSettingsSearch', false);
|
||||
export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey<boolean>('settingsTocRowFocus', false);
|
||||
export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey<boolean>('inKeybindings', false);
|
||||
export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inKeybindingsSearch', false);
|
||||
export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey<boolean>('keybindingFocus', false);
|
||||
|
||||
export const SETTINGS_EDITOR_COMMAND_SEARCH = 'settings.action.search';
|
||||
export const SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'settings.action.clearSearchResults';
|
||||
export const SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING = 'settings.action.focusNextSetting';
|
||||
export const SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING = 'settings.action.focusPreviousSetting';
|
||||
export const SETTINGS_EDITOR_COMMAND_FOCUS_FILE = 'settings.action.focusSettingsFile';
|
||||
export const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocusedSetting';
|
||||
export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch';
|
||||
export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList';
|
||||
export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu';
|
||||
|
||||
export const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON';
|
||||
export const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified';
|
||||
export const SETTINGS_EDITOR_COMMAND_FILTER_ONLINE = 'settings.filterByOnline';
|
||||
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN = 'keybindings.editor.defineWhenExpression';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybinding';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_COPY = 'keybindings.editor.copyKeybindingEntry';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND = 'keybindings.editor.copyCommandKeybindingEntry';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR = 'keybindings.editor.showConflicts';
|
||||
export const KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS = 'keybindings.editor.focusKeybindings';
|
||||
export const KEYBINDINGS_EDITOR_CLEAR_INPUT = 'keybindings.editor.showDefaultKeybindings';
|
||||
export const KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS = 'keybindings.editor.showDefaultKeybindings';
|
||||
export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.showUserKeybindings';
|
||||
|
||||
export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings';
|
||||
|
||||
export const MODIFIED_SETTING_TAG = 'modified';
|
||||
|
||||
export const SETTINGS_COMMAND_OPEN_SETTINGS = 'workbench.action.openSettings';
|
||||
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { endsWith } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
const schemaRegistry = Registry.as<JSONContributionRegistry.IJSONContributionRegistry>(JSONContributionRegistry.Extensions.JSONContribution);
|
||||
|
||||
export class PreferencesContribution implements IWorkbenchContribution {
|
||||
private editorOpeningListener: IDisposable;
|
||||
private settingsListener: IDisposable;
|
||||
|
||||
constructor(
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
this.settingsListener = this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(USE_SPLIT_JSON_SETTING)) {
|
||||
this.handleSettingsEditorOverride();
|
||||
}
|
||||
});
|
||||
this.handleSettingsEditorOverride();
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
private handleSettingsEditorOverride(): void {
|
||||
|
||||
// dispose any old listener we had
|
||||
this.editorOpeningListener = dispose(this.editorOpeningListener);
|
||||
|
||||
// install editor opening listener unless user has disabled this
|
||||
if (!!this.configurationService.getValue(USE_SPLIT_JSON_SETTING)) {
|
||||
this.editorOpeningListener = this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group));
|
||||
}
|
||||
}
|
||||
|
||||
private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
|
||||
const resource = editor.getResource();
|
||||
if (
|
||||
!resource ||
|
||||
!endsWith(resource.path, 'settings.json') || // resource must end in settings.json
|
||||
!this.configurationService.getValue(USE_SPLIT_JSON_SETTING) // user has not disabled default settings editor
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If the resource was already opened before in the group, do not prevent
|
||||
// the opening of that resource. Otherwise we would have the same settings
|
||||
// opened twice (https://github.com/Microsoft/vscode/issues/36447)
|
||||
if (group.isOpened(editor)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Global User Settings File
|
||||
if (isEqual(resource, URI.file(this.environmentService.appSettingsPath), !isLinux)) {
|
||||
return { override: this.preferencesService.openGlobalSettings(true, options, group) };
|
||||
}
|
||||
|
||||
// Single Folder Workspace Settings File
|
||||
const state = this.workspaceService.getWorkbenchState();
|
||||
if (state === WorkbenchState.FOLDER) {
|
||||
const folders = this.workspaceService.getWorkspace().folders;
|
||||
if (isEqual(resource, folders[0].toResource(FOLDER_SETTINGS_PATH))) {
|
||||
return { override: this.preferencesService.openWorkspaceSettings(true, options, group) };
|
||||
}
|
||||
}
|
||||
|
||||
// Multi Folder Workspace Settings File
|
||||
else if (state === WorkbenchState.WORKSPACE) {
|
||||
const folders = this.workspaceService.getWorkspace().folders;
|
||||
for (const folder of folders) {
|
||||
if (isEqual(resource, folder.toResource(FOLDER_SETTINGS_PATH))) {
|
||||
return { override: this.preferencesService.openFolderSettings(folder.uri, true, options, group) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private start(): void {
|
||||
|
||||
this.textModelResolverService.registerTextModelContentProvider('vscode', {
|
||||
provideTextContent: (uri: URI): Promise<ITextModel | null> | null => {
|
||||
if (uri.scheme !== 'vscode') {
|
||||
return null;
|
||||
}
|
||||
if (uri.authority === 'schemas') {
|
||||
const schemaModel = this.getSchemaModel(uri);
|
||||
if (schemaModel) {
|
||||
return Promise.resolve(schemaModel);
|
||||
}
|
||||
}
|
||||
return this.preferencesService.resolveModel(uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getSchemaModel(uri: URI): ITextModel | null {
|
||||
let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()];
|
||||
if (schema) {
|
||||
const modelContent = JSON.stringify(schema);
|
||||
const languageSelection = this.modeService.create('jsonc');
|
||||
const model = this.modelService.createModel(modelContent, languageSelection, uri);
|
||||
const disposables: IDisposable[] = [];
|
||||
disposables.push(schemaRegistry.onDidChangeSchema(schemaUri => {
|
||||
if (schemaUri === uri.toString()) {
|
||||
schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()];
|
||||
model.setValue(JSON.stringify(schema));
|
||||
}
|
||||
}));
|
||||
disposables.push(model.onWillDispose(() => dispose(disposables)));
|
||||
|
||||
return model;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.editorOpeningListener = dispose(this.editorOpeningListener);
|
||||
this.settingsListener = dispose(this.settingsListener);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JSONScanner, createScanner as createJSONScanner, SyntaxKind as JSONSyntaxKind } from 'vs/base/common/json';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
|
||||
export interface InsertSnippetResult {
|
||||
position: Position;
|
||||
prepend: string;
|
||||
append: string;
|
||||
}
|
||||
|
||||
export class SmartSnippetInserter {
|
||||
|
||||
private static hasOpenBrace(scanner: JSONScanner): boolean {
|
||||
|
||||
while (scanner.scan() !== JSONSyntaxKind.EOF) {
|
||||
const kind = scanner.getToken();
|
||||
|
||||
if (kind === JSONSyntaxKind.OpenBraceToken) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static offsetToPosition(model: ITextModel, offset: number): Position {
|
||||
let offsetBeforeLine = 0;
|
||||
const eolLength = model.getEOL().length;
|
||||
const lineCount = model.getLineCount();
|
||||
for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
|
||||
const lineTotalLength = model.getLineContent(lineNumber).length + eolLength;
|
||||
const offsetAfterLine = offsetBeforeLine + lineTotalLength;
|
||||
|
||||
if (offsetAfterLine > offset) {
|
||||
return new Position(
|
||||
lineNumber,
|
||||
offset - offsetBeforeLine + 1
|
||||
);
|
||||
}
|
||||
offsetBeforeLine = offsetAfterLine;
|
||||
}
|
||||
return new Position(
|
||||
lineCount,
|
||||
model.getLineMaxColumn(lineCount)
|
||||
);
|
||||
}
|
||||
|
||||
static insertSnippet(model: ITextModel, _position: Position): InsertSnippetResult {
|
||||
|
||||
const desiredPosition = model.getValueLengthInRange(new Range(1, 1, _position.lineNumber, _position.column));
|
||||
|
||||
// <INVALID> [ <BEFORE_OBJECT> { <INVALID> } <AFTER_OBJECT>, <BEFORE_OBJECT> { <INVALID> } <AFTER_OBJECT> ] <INVALID>
|
||||
enum State {
|
||||
INVALID = 0,
|
||||
AFTER_OBJECT = 1,
|
||||
BEFORE_OBJECT = 2,
|
||||
}
|
||||
let currentState = State.INVALID;
|
||||
let lastValidPos = -1;
|
||||
let lastValidState = State.INVALID;
|
||||
|
||||
const scanner = createJSONScanner(model.getValue());
|
||||
let arrayLevel = 0;
|
||||
let objLevel = 0;
|
||||
|
||||
const checkRangeStatus = (pos: number, state: State) => {
|
||||
if (state !== State.INVALID && arrayLevel === 1 && objLevel === 0) {
|
||||
currentState = state;
|
||||
lastValidPos = pos;
|
||||
lastValidState = state;
|
||||
} else {
|
||||
if (currentState !== State.INVALID) {
|
||||
currentState = State.INVALID;
|
||||
lastValidPos = scanner.getTokenOffset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (scanner.scan() !== JSONSyntaxKind.EOF) {
|
||||
const currentPos = scanner.getPosition();
|
||||
const kind = scanner.getToken();
|
||||
|
||||
let goodKind = false;
|
||||
switch (kind) {
|
||||
case JSONSyntaxKind.OpenBracketToken:
|
||||
goodKind = true;
|
||||
arrayLevel++;
|
||||
checkRangeStatus(currentPos, State.BEFORE_OBJECT);
|
||||
break;
|
||||
case JSONSyntaxKind.CloseBracketToken:
|
||||
goodKind = true;
|
||||
arrayLevel--;
|
||||
checkRangeStatus(currentPos, State.INVALID);
|
||||
break;
|
||||
case JSONSyntaxKind.CommaToken:
|
||||
goodKind = true;
|
||||
checkRangeStatus(currentPos, State.BEFORE_OBJECT);
|
||||
break;
|
||||
case JSONSyntaxKind.OpenBraceToken:
|
||||
goodKind = true;
|
||||
objLevel++;
|
||||
checkRangeStatus(currentPos, State.INVALID);
|
||||
break;
|
||||
case JSONSyntaxKind.CloseBraceToken:
|
||||
goodKind = true;
|
||||
objLevel--;
|
||||
checkRangeStatus(currentPos, State.AFTER_OBJECT);
|
||||
break;
|
||||
case JSONSyntaxKind.Trivia:
|
||||
case JSONSyntaxKind.LineBreakTrivia:
|
||||
goodKind = true;
|
||||
}
|
||||
|
||||
if (currentPos >= desiredPosition && (currentState !== State.INVALID || lastValidPos !== -1)) {
|
||||
let acceptPosition: number;
|
||||
let acceptState: State;
|
||||
|
||||
if (currentState !== State.INVALID) {
|
||||
acceptPosition = (goodKind ? currentPos : scanner.getTokenOffset());
|
||||
acceptState = currentState;
|
||||
} else {
|
||||
acceptPosition = lastValidPos;
|
||||
acceptState = lastValidState;
|
||||
}
|
||||
|
||||
if (acceptState as State === State.AFTER_OBJECT) {
|
||||
return {
|
||||
position: this.offsetToPosition(model, acceptPosition),
|
||||
prepend: ',',
|
||||
append: ''
|
||||
};
|
||||
} else {
|
||||
scanner.setPosition(acceptPosition);
|
||||
return {
|
||||
position: this.offsetToPosition(model, acceptPosition),
|
||||
prepend: '',
|
||||
append: this.hasOpenBrace(scanner) ? ',' : ''
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no valid position found!
|
||||
const modelLineCount = model.getLineCount();
|
||||
return {
|
||||
position: new Position(modelLineCount, model.getLineMaxColumn(modelLineCount)),
|
||||
prepend: '\n[',
|
||||
append: ']'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#C5C5C5" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>
|
||||
|
After Width: | Height: | Size: 194 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-2 -2 16 16" enable-background="new -2 -2 16 16"><polygon fill="#424242" points="9,0 4.5,9 3,6 0,6 3,12 6,12 12,0"/></svg>
|
||||
|
After Width: | Height: | Size: 194 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>configure</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,10.015l-2.238.372,1.318,1.847L12.233,15.08l-1.847-1.318L10.013,16H5.986l-.373-2.237L3.767,15.08.919,12.233l1.319-1.847L0,10.013V5.986l2.238-.373L.919,3.767,3.768.919,5.613,2.238,5.986,0h4.028l.372,2.238L12.233.919,15.08,3.768,13.762,5.613,16,5.986Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12.876,9.521,15,9.167V6.834L12.879,6.48a5.12,5.12,0,0,0-.354-.854l1.25-1.75-1.65-1.65L10.373,3.477c-.137-.072-.262-.159-.408-.219s-.3-.087-.444-.133L9.167,1H6.834L6.48,3.121a5.118,5.118,0,0,0-.854.354l-1.75-1.25-1.65,1.65L3.477,5.627c-.072.137-.159.262-.219.408s-.087.3-.133.444L1,6.833V9.166l2.121.354a5.122,5.122,0,0,0,.354.854l-1.25,1.75,1.65,1.65,1.752-1.252c.137.072.262.159.408.22s.3.087.444.133L6.833,15H9.166l.354-2.121a5.121,5.121,0,0,0,.854-.354l1.75,1.25,1.65-1.65-1.252-1.752c.072-.137.159-.263.219-.409S12.83,9.669,12.876,9.521ZM8,10.212A2.212,2.212,0,1,1,10.212,8,2.212,2.212,0,0,1,8,10.212Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>configure</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,10.015l-2.238.372,1.318,1.847L12.233,15.08l-1.847-1.318L10.013,16H5.986l-.373-2.237L3.767,15.08.919,12.233l1.319-1.847L0,10.013V5.986l2.238-.373L.919,3.767,3.768.919,5.613,2.238,5.986,0h4.028l.372,2.238L12.233.919,15.08,3.768,13.762,5.613,16,5.986Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12.876,9.521,15,9.167V6.834L12.879,6.48a5.12,5.12,0,0,0-.354-.854l1.25-1.75-1.65-1.65L10.373,3.477c-.137-.072-.262-.159-.408-.219s-.3-.087-.444-.133L9.167,1H6.834L6.48,3.121a5.118,5.118,0,0,0-.854.354l-1.75-1.25-1.65,1.65L3.477,5.627c-.072.137-.159.262-.219.408s-.087.3-.133.444L1,6.833V9.166l2.121.354a5.122,5.122,0,0,0,.354.854l-1.25,1.75,1.65,1.65,1.752-1.252c.137.072.262.159.408.22s.3.087.444.133L6.833,15H9.166l.354-2.121a5.121,5.121,0,0,0,.854-.354l1.75,1.25,1.65-1.65-1.252-1.752c.072-.137.159-.263.219-.409S12.83,9.669,12.876,9.521ZM8,10.212A2.212,2.212,0,1,1,10.212,8,2.212,2.212,0,0,1,8,10.212Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.22785 7.35986C2.69078 7.35986 3.01627 7.25136 3.20434 7.03436C3.33454 6.87523 3.39964 6.64376 3.39964 6.33996C3.39964 6.20976 3.38517 6.01808 3.35624 5.76492C3.32731 5.51176 3.31284 5.31646 3.31284 5.17902C3.31284 5.04159 3.30561 4.83544 3.29114 4.56058C3.26221 4.31465 3.24774 4.14105 3.24774 4.03978C3.24774 3.34539 3.45027 2.83183 3.85533 2.49909C4.2604 2.16636 4.83183 2 5.56962 2H6.11212V3.28029H5.83002C5.51175 3.28029 5.28391 3.37071 5.14647 3.55154C5.00904 3.73237 4.94033 4.00362 4.94033 4.36528C4.94033 4.45208 4.95479 4.58228 4.98372 4.75588C5.01266 4.97288 5.02712 5.13924 5.02712 5.25497C5.02712 5.34177 5.03436 5.4792 5.04882 5.66727C5.07776 5.92767 5.09222 6.1302 5.09222 6.27486C5.09222 6.82459 4.97649 7.23689 4.74503 7.51175C4.5425 7.75768 4.22423 7.93128 3.79024 8.03255C4.22423 8.14828 4.5425 8.32188 4.74503 8.55334C4.97649 8.84268 5.09222 9.25497 5.09222 9.79024C5.09222 9.94937 5.07776 10.1591 5.04882 10.4195C5.01989 10.6076 5.00904 10.745 5.01627 10.8318C5.02351 10.9186 5.01266 11.0705 4.98372 11.2875C4.95479 11.4467 4.94033 11.5624 4.94033 11.6347C4.94033 11.9964 5.00904 12.2676 5.14647 12.4485C5.28391 12.6293 5.51175 12.7197 5.83002 12.7197H6.11212V14H5.56962C4.0651 14 3.31284 13.3201 3.31284 11.9602C3.31284 11.4684 3.33816 11.0886 3.38879 10.821C3.43942 10.5533 3.46474 10.1664 3.46474 9.66004C3.46474 8.98011 3.08137 8.64014 2.31465 8.64014L2.22785 7.35986ZM13.7722 8.50995C13.0054 8.50995 12.6221 8.84991 12.6221 9.52984C12.6221 9.66004 12.6329 9.85172 12.6546 10.1049C12.6763 10.358 12.6872 10.5497 12.6872 10.6799C12.7306 10.9548 12.7523 11.3382 12.7523 11.83C12.7523 13.1899 11.9855 13.8698 10.4521 13.8698H9.90958V12.7197H10.17C10.4882 12.7197 10.7161 12.6293 10.8535 12.4485C10.991 12.2676 11.0597 11.9964 11.0597 11.6347C11.0597 11.2731 11.038 10.9982 10.9946 10.8101C10.9946 10.6944 10.9801 10.5244 10.9512 10.3002C10.9222 10.0759 10.9078 9.90597 10.9078 9.79024C10.9078 9.25497 11.0235 8.84268 11.255 8.55334C11.4575 8.32188 11.7758 8.14828 12.2098 8.03255C11.7758 7.93128 11.4575 7.75768 11.255 7.51175C11.0235 7.23689 10.9078 6.82459 10.9078 6.27486C10.9078 6.1302 10.9222 5.92767 10.9512 5.66727C10.9801 5.4792 10.9946 5.34177 10.9946 5.25497C11.038 5.02351 11.0597 4.73418 11.0597 4.38698C11.0597 4.03978 10.9873 3.77939 10.8427 3.60579C10.698 3.43219 10.4738 3.32369 10.17 3.28029H9.90958V2H10.4521C11.1754 2 11.7396 2.16636 12.1447 2.49909C12.5497 2.83183 12.7523 3.34539 12.7523 4.03978C12.7523 4.16998 12.7414 4.36166 12.7197 4.61483C12.698 4.86799 12.6872 5.05967 12.6872 5.18987C12.6438 5.46474 12.6148 5.8481 12.6004 6.33996C12.6148 6.64376 12.6799 6.87523 12.7957 7.03436C12.9837 7.25136 13.3092 7.35986 13.7722 7.35986V8.50995Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.22785 7.35986C2.69078 7.35986 3.01627 7.25136 3.20434 7.03436C3.33454 6.87523 3.39964 6.64376 3.39964 6.33996C3.39964 6.20976 3.38517 6.01808 3.35624 5.76492C3.32731 5.51176 3.31284 5.31646 3.31284 5.17902C3.31284 5.04159 3.30561 4.83544 3.29114 4.56058C3.26221 4.31465 3.24774 4.14105 3.24774 4.03978C3.24774 3.34539 3.45027 2.83183 3.85533 2.49909C4.2604 2.16636 4.83183 2 5.56962 2H6.11212V3.28029H5.83002C5.51175 3.28029 5.28391 3.37071 5.14647 3.55154C5.00904 3.73237 4.94033 4.00362 4.94033 4.36528C4.94033 4.45208 4.95479 4.58228 4.98372 4.75588C5.01266 4.97288 5.02712 5.13924 5.02712 5.25497C5.02712 5.34177 5.03436 5.4792 5.04882 5.66727C5.07776 5.92767 5.09222 6.1302 5.09222 6.27486C5.09222 6.82459 4.97649 7.23689 4.74503 7.51175C4.5425 7.75768 4.22423 7.93128 3.79024 8.03255C4.22423 8.14828 4.5425 8.32188 4.74503 8.55334C4.97649 8.84268 5.09222 9.25497 5.09222 9.79024C5.09222 9.94937 5.07776 10.1591 5.04882 10.4195C5.01989 10.6076 5.00904 10.745 5.01627 10.8318C5.02351 10.9186 5.01266 11.0705 4.98372 11.2875C4.95479 11.4467 4.94033 11.5624 4.94033 11.6347C4.94033 11.9964 5.00904 12.2676 5.14647 12.4485C5.28391 12.6293 5.51175 12.7197 5.83002 12.7197H6.11212V14H5.56962C4.0651 14 3.31284 13.3201 3.31284 11.9602C3.31284 11.4684 3.33816 11.0886 3.38879 10.821C3.43942 10.5533 3.46474 10.1664 3.46474 9.66004C3.46474 8.98011 3.08137 8.64014 2.31465 8.64014L2.22785 7.35986ZM13.7722 8.50995C13.0054 8.50995 12.6221 8.84991 12.6221 9.52984C12.6221 9.66004 12.6329 9.85172 12.6546 10.1049C12.6763 10.358 12.6872 10.5497 12.6872 10.6799C12.7306 10.9548 12.7523 11.3382 12.7523 11.83C12.7523 13.1899 11.9855 13.8698 10.4521 13.8698H9.90958V12.7197H10.17C10.4882 12.7197 10.7161 12.6293 10.8535 12.4485C10.991 12.2676 11.0597 11.9964 11.0597 11.6347C11.0597 11.2731 11.038 10.9982 10.9946 10.8101C10.9946 10.6944 10.9801 10.5244 10.9512 10.3002C10.9222 10.0759 10.9078 9.90597 10.9078 9.79024C10.9078 9.25497 11.0235 8.84268 11.255 8.55334C11.4575 8.32188 11.7758 8.14828 12.2098 8.03255C11.7758 7.93128 11.4575 7.75768 11.255 7.51175C11.0235 7.23689 10.9078 6.82459 10.9078 6.27486C10.9078 6.1302 10.9222 5.92767 10.9512 5.66727C10.9801 5.4792 10.9946 5.34177 10.9946 5.25497C11.038 5.02351 11.0597 4.73418 11.0597 4.38698C11.0597 4.03978 10.9873 3.77939 10.8427 3.60579C10.698 3.43219 10.4738 3.32369 10.17 3.28029H9.90958V2H10.4521C11.1754 2 11.7396 2.16636 12.1447 2.49909C12.5497 2.83183 12.7523 3.34539 12.7523 4.03978C12.7523 4.16998 12.7414 4.36166 12.7197 4.61483C12.698 4.86799 12.6872 5.05967 12.6872 5.18987C12.6438 5.46474 12.6148 5.8481 12.6004 6.33996C12.6148 6.64376 12.6799 6.87523 12.7957 7.03436C12.9837 7.25136 13.3092 7.35986 13.7722 7.35986V8.50995Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 2H9V3H10V3.586V4V4.414V6H13V13H5V8H4V14H14V5L11 2Z" fill="#C5C5C5"/>
|
||||
<path d="M7.08971 4.65186L8 4.50014V3.50029L7.091 3.34857C7.05157 3.22225 7.00079 3.09975 6.93929 2.98257L7.475 2.23257L6.76786 1.52543L6.017 2.06157C5.95829 2.03071 5.90471 1.99343 5.84214 1.96771C5.77957 1.942 5.71357 1.93043 5.65186 1.91071L5.50014 1H4.50029L4.34857 1.909C4.22224 1.94843 4.09974 1.99921 3.98257 2.06071L3.23257 1.525L2.52543 2.23214L3.06157 2.983C3.03071 3.04171 2.99343 3.09529 2.96771 3.15786C2.942 3.22043 2.93043 3.28643 2.91071 3.34814L2 3.49986V4.49971L2.909 4.65143C2.94844 4.77775 2.99921 4.90025 3.06071 5.01743L2.525 5.76743L3.23214 6.47457L3.983 5.938C4.04171 5.96886 4.09529 6.00614 4.15786 6.03229C4.22043 6.05843 4.28643 6.06957 4.34814 6.08929L4.49986 7H5.49971L5.65143 6.091C5.77775 6.05157 5.90025 6.00079 6.01743 5.93929L6.76743 6.475L7.47457 5.76786L6.938 5.017C6.96886 4.95829 7.00614 4.90429 7.03186 4.84171C7.05757 4.77914 7.07 4.71529 7.08971 4.65186ZM5 4.948C4.8125 4.948 4.62922 4.8924 4.47332 4.78823C4.31742 4.68407 4.19591 4.53601 4.12416 4.36278C4.05241 4.18956 4.03364 3.99895 4.07022 3.81505C4.10679 3.63116 4.19708 3.46224 4.32966 3.32966C4.46224 3.19708 4.63116 3.10679 4.81505 3.07022C4.99895 3.03364 5.18956 3.05241 5.36278 3.12416C5.53601 3.19591 5.68407 3.31742 5.78823 3.47332C5.8924 3.62922 5.948 3.8125 5.948 4C5.948 4.25143 5.84812 4.49255 5.67034 4.67034C5.49255 4.84812 5.25143 4.948 5 4.948Z" fill="#75BEFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 2H9V3H10V3.586V4V4.414V6H13V13H5V8H4V14H14V5L11 2Z" fill="#656565"/>
|
||||
<path d="M7.08971 4.65186L8 4.50014V3.50029L7.091 3.34857C7.05157 3.22225 7.00079 3.09975 6.93929 2.98257L7.475 2.23257L6.76786 1.52543L6.017 2.06157C5.95829 2.03071 5.90471 1.99343 5.84214 1.96771C5.77957 1.942 5.71357 1.93043 5.65186 1.91071L5.50014 1H4.50029L4.34857 1.909C4.22224 1.94843 4.09974 1.99921 3.98257 2.06071L3.23257 1.525L2.52543 2.23214L3.06157 2.983C3.03071 3.04171 2.99343 3.09529 2.96771 3.15786C2.942 3.22043 2.93043 3.28643 2.91071 3.34814L2 3.49986V4.49971L2.909 4.65143C2.94844 4.77775 2.99921 4.90025 3.06071 5.01743L2.525 5.76743L3.23214 6.47457L3.983 5.938C4.04171 5.96886 4.09529 6.00614 4.15786 6.03229C4.22043 6.05843 4.28643 6.06957 4.34814 6.08929L4.49986 7H5.49971L5.65143 6.091C5.77775 6.05157 5.90025 6.00079 6.01743 5.93929L6.76743 6.475L7.47457 5.76786L6.938 5.017C6.96886 4.95829 7.00614 4.90429 7.03186 4.84171C7.05757 4.77914 7.07 4.71529 7.08971 4.65186ZM5 4.948C4.8125 4.948 4.62922 4.8924 4.47332 4.78823C4.31742 4.68407 4.19591 4.53601 4.12416 4.36278C4.05241 4.18956 4.03364 3.99895 4.07022 3.81505C4.10679 3.63116 4.19708 3.46224 4.32966 3.32966C4.46224 3.19708 4.63116 3.10679 4.81505 3.07022C4.99895 3.03364 5.18956 3.05241 5.36278 3.12416C5.53601 3.19591 5.68407 3.31742 5.78823 3.47332C5.8924 3.62922 5.948 3.8125 5.948 4C5.948 4.25143 5.84812 4.49255 5.67034 4.67034C5.49255 4.84812 5.25143 4.948 5 4.948Z" fill="#00539C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,516 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.editor-instance#workbench\.editor\.settings2:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.settings-editor {
|
||||
padding: 11px 0px 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* header styling */
|
||||
.settings-editor > .settings-header {
|
||||
box-sizing: border-box;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
padding-top: 3px;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-header > .search-container > .suggest-input-container {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .search-container > .settings-count-widget {
|
||||
margin: 6px 0px;
|
||||
padding: 0px 8px;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
border-bottom: solid 1px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.settings-editor .settings-tabs-widget > .monaco-action-bar .action-item .action-details {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label.checked {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-header > .settings-header-controls {
|
||||
border-color: #cccccc;
|
||||
}
|
||||
|
||||
.vs-dark .settings-editor > .settings-header > .settings-header-controls {
|
||||
border-color: #3c3c3c;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header .settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header .settings-tabs-widget .monaco-action-bar .action-item .dropdown-icon {
|
||||
/** The tab widget container height is shorter than elsewhere, need to tweak this */
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item {
|
||||
padding: 0px;
|
||||
/* padding must be on action-label because it has the bottom-border, because that's where the .checked class is */
|
||||
}
|
||||
|
||||
.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item .action-label {
|
||||
text-transform: none;
|
||||
font-size: 13px;
|
||||
|
||||
padding-bottom: 7px;
|
||||
padding-top: 7px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .no-results-message {
|
||||
display: none;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.settings-editor.no-results > .settings-body .settings-toc-container,
|
||||
.settings-editor.no-results > .settings-body .settings-tree-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor.no-results > .settings-body > .no-results-message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .no-results-message a.prominent {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents,
|
||||
.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents {
|
||||
padding-left: 33px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-twistie {
|
||||
/* Hide twisties */
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.settings-editor.mid-width > .settings-body > .settings-tree-container .shadow.top {
|
||||
left: 0;
|
||||
width: calc(100% - 48px);
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .shadow.top {
|
||||
left: 50%;
|
||||
max-width: 952px;
|
||||
/* 1000 - 24*2 padding */
|
||||
margin-left: -476px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container {
|
||||
position: absolute;
|
||||
left: -32px;
|
||||
top: 11px;
|
||||
bottom: 0px;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item.focused .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .toolbar-toggle-more,
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .toolbar-toggle-more {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more {
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .toolbar-toggle-more {
|
||||
background-image: url('configure.svg');
|
||||
}
|
||||
|
||||
.vs-dark .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .toolbar-toggle-more {
|
||||
background-image: url('configure-inverse.svg');
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container {
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list {
|
||||
width: 160px;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.settings-editor.no-toc-search > .settings-body .settings-toc-container,
|
||||
.settings-editor.narrow-width > .settings-body .settings-toc-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-scrollable-element > .shadow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row .monaco-tl-contents {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row .settings-toc-entry {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 22px;
|
||||
opacity: 0.9;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count {
|
||||
display: none;
|
||||
line-height: 22px;
|
||||
opacity: 0.7;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.settings-editor.search-mode > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row .monaco-tl-twistie {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .monaco-tl-twistie {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-container .monaco-list-row.selected .settings-toc-entry {
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container {
|
||||
margin-right: 1px;
|
||||
/* So the item doesn't blend into the edge of the view container */
|
||||
margin-top: 14px;
|
||||
border-spacing: 0;
|
||||
border-collapse: separate;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-wrapper {
|
||||
padding-left: 31px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-toc-wrapper {
|
||||
height: 100%;
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.settings-editor.no-toc-search > .settings-body .settings-tree-container,
|
||||
.settings-editor.narrow-width > .settings-body .settings-tree-container {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .monaco-list-row {
|
||||
line-height: 1.4em !important;
|
||||
|
||||
/* so validation messages don't get clipped */
|
||||
overflow: visible;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-rows {
|
||||
overflow: visible !important; /* Allow validation errors to flow out of the tree container. Override inline style from ScrollableElement. */
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents {
|
||||
max-width: 1000px;
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
padding-left: 217px;
|
||||
padding-right: 20px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents {
|
||||
position: relative;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 18px;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
/* size to contents for hover to show context button */
|
||||
}
|
||||
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-modified-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents.is-configured .setting-item-modified-indicator {
|
||||
display: block;
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
border-left-width: 2px;
|
||||
border-left-style: solid;
|
||||
left: -9px;
|
||||
top: 15px;
|
||||
bottom: 16px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-contents.is-configured .setting-item-modified-indicator {
|
||||
bottom: 23px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides {
|
||||
opacity: 0.5;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides a.modified-scope {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-label {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-cat-label-container {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-label,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category {
|
||||
font-weight: 600;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message {
|
||||
margin-top: 3px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description {
|
||||
margin-top: -1px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-contents.invalid-input .setting-item-validation-message {
|
||||
display: block;
|
||||
position: absolute;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
margin-top: -1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-validation-message {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number .setting-item-validation-message {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number input[type=number]::-webkit-inner-spin-button {
|
||||
/* Hide arrow button that shows in type=number fields */
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown * {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown code {
|
||||
line-height: 15px;
|
||||
/** For some reason, this is needed, otherwise <code> will take up 20px height */
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-enumDescription {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-enumDescription {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-contents {
|
||||
padding-bottom: 26px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-value-description {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
margin-right: 9px;
|
||||
margin-left: 0px;
|
||||
padding: 0px;
|
||||
background-size: 16px !important;
|
||||
}
|
||||
|
||||
.vs .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked {
|
||||
background: url('check.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked,
|
||||
.hc-black .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-value-checkbox.checked {
|
||||
background: url('check-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value {
|
||||
margin-top: 9px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number .setting-item-value > .setting-item-control {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-control {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-enum .setting-item-value > .setting-item-control,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-value > .setting-item-control {
|
||||
min-width: initial;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-enum .setting-item-value > .setting-item-control > select {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:hover,
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:active {
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .monaco-select-box {
|
||||
width: initial;
|
||||
font: inherit;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-new-extensions {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .setting-item-new-extensions .settings-new-extensions-button {
|
||||
margin: auto;
|
||||
width: initial;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .group-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .settings-group-title-label {
|
||||
margin: 0px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .settings-group-level-1 {
|
||||
padding-top: 23px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .settings-group-level-2 {
|
||||
padding-top: 32px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.settings-editor > .settings-body > .settings-tree-container .settings-group-level-1.settings-group-first {
|
||||
padding-top: 7px;
|
||||
}
|
||||
|
||||
.settings-editor.search-mode > .settings-body .settings-toc-container .monaco-list-row .settings-toc-count {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,802 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!../browser/media/preferences';
|
||||
import { Command } from 'vs/editor/browser/editorExtensions';
|
||||
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
|
||||
import * as nls from 'vs/nls';
|
||||
import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor';
|
||||
import { Extensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { EditorInput, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor';
|
||||
import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/contrib/preferences/browser/preferencesActions';
|
||||
import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor';
|
||||
import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, IPreferencesSearchService, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_COMMAND_OPEN_SETTINGS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences';
|
||||
import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution';
|
||||
import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/electron-browser/preferencesSearch';
|
||||
import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/electron-browser/settingsEditor2';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
|
||||
import { ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files';
|
||||
|
||||
registerSingleton(IPreferencesSearchService, PreferencesSearchService, true);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
PreferencesEditor,
|
||||
PreferencesEditor.ID,
|
||||
nls.localize('defaultPreferencesEditor', "Default Preferences Editor")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(PreferencesEditorInput)
|
||||
]
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
SettingsEditor2,
|
||||
SettingsEditor2.ID,
|
||||
nls.localize('settingsEditor2', "Settings Editor 2")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(SettingsEditor2Input)
|
||||
]
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
KeybindingsEditor,
|
||||
KeybindingsEditor.ID,
|
||||
nls.localize('keybindingsEditor', "Keybindings Editor")
|
||||
),
|
||||
[
|
||||
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 {
|
||||
|
||||
serialize(editorInput: EditorInput): string | null {
|
||||
const input = <PreferencesEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
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;
|
||||
}
|
||||
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | null {
|
||||
const deserialized: ISerializedPreferencesEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
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 {
|
||||
|
||||
serialize(editorInput: EditorInput): string {
|
||||
const input = <KeybindingsEditorInput>editorInput;
|
||||
return JSON.stringify({
|
||||
name: input.getName(),
|
||||
typeId: input.getTypeId()
|
||||
});
|
||||
}
|
||||
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
return instantiationService.createInstance(KeybindingsEditorInput);
|
||||
}
|
||||
}
|
||||
|
||||
interface ISerializedSettingsEditor2EditorInput {
|
||||
}
|
||||
|
||||
class SettingsEditor2InputFactory implements IEditorInputFactory {
|
||||
|
||||
serialize(input: SettingsEditor2Input): string {
|
||||
const serialized: ISerializedSettingsEditor2EditorInput = {
|
||||
};
|
||||
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SettingsEditor2Input {
|
||||
return instantiationService.createInstance(
|
||||
SettingsEditor2Input);
|
||||
}
|
||||
}
|
||||
|
||||
interface ISerializedDefaultPreferencesEditorInput {
|
||||
resource: string;
|
||||
}
|
||||
|
||||
// Register Default Preferences Editor Input Factory
|
||||
class DefaultPreferencesEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
serialize(editorInput: EditorInput): string {
|
||||
const input = <DefaultPreferencesEditorInput>editorInput;
|
||||
|
||||
const serialized: ISerializedDefaultPreferencesEditorInput = { resource: input.getResource().toString() };
|
||||
|
||||
return JSON.stringify(serialized);
|
||||
}
|
||||
|
||||
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedDefaultPreferencesEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
return instantiationService.createInstance(DefaultPreferencesEditorInput, URI.parse(deserialized.resource));
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(PreferencesEditorInput.ID, PreferencesEditorInputFactory);
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(DefaultPreferencesEditorInput.ID, DefaultPreferencesEditorInputFactory);
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(KeybindingsEditorInput.ID, KeybindingsEditorInputFactory);
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SettingsEditor2Input.ID, SettingsEditor2InputFactory);
|
||||
|
||||
// Contribute Global Actions
|
||||
const category = nls.localize('preferences', "Preferences");
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Raw Default Settings', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsJsonAction, OpenSettingsJsonAction.ID, OpenSettingsJsonAction.LABEL), 'Preferences: Open Settings (JSON)', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (UI)', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User 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(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts File', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL, { primary: 0 }), 'Preferences: Open Keyboard Shortcuts File', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureLanguageBasedSettingsAction, ConfigureLanguageBasedSettingsAction.ID, ConfigureLanguageBasedSettingsAction.LABEL), 'Preferences: Configure Language Specific Settings...', category);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SETTINGS_COMMAND_OPEN_SETTINGS,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: null,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.US_COMMA,
|
||||
handler: (accessor, args: any) => {
|
||||
accessor.get(IPreferencesService).openSettings();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_DEFINE,
|
||||
weight: KeybindingWeight.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 control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.defineKeybinding(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_E),
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor && control.activeKeybindingEntry.keybindingItem.keybinding) {
|
||||
control.defineWhenExpression(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_REMOVE,
|
||||
weight: KeybindingWeight.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 control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.removeKeybinding(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_RESET,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: 0,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.resetKeybinding(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SEARCH,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_F,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.focusSearch();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS),
|
||||
primary: KeyMod.Alt | KeyCode.KEY_K,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_K },
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.recordSearchKeys();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
|
||||
primary: KeyMod.Alt | KeyCode.KEY_P,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P },
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control && control instanceof KeybindingsEditor) {
|
||||
control.toggleSortByPrecedence();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: 0,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.showSimilarKeybindings(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_COPY,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.copyKeybinding(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS),
|
||||
primary: 0,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.copyKeybindingCommand(control.activeKeybindingEntry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS),
|
||||
primary: KeyCode.DownArrow,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.focusKeybindings();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
class PreferencesActionsContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||
@IWorkspaceContextService private readonly workpsaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: OpenGlobalKeybindingsAction.ID,
|
||||
title: OpenGlobalKeybindingsAction.LABEL,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`))
|
||||
}
|
||||
},
|
||||
when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appKeybindingsPath).toString()),
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
});
|
||||
|
||||
const commandId = '_workbench.openUserSettingsEditor';
|
||||
CommandsRegistry.registerCommand(commandId, () => this.preferencesService.openGlobalSettings(false));
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: commandId,
|
||||
title: OpenSettings2Action.LABEL,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`))
|
||||
}
|
||||
},
|
||||
when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appSettingsPath).toString()),
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
});
|
||||
|
||||
|
||||
this.updatePreferencesEditorMenuItem();
|
||||
this._register(workpsaceContextService.onDidChangeWorkbenchState(() => this.updatePreferencesEditorMenuItem()));
|
||||
this._register(workpsaceContextService.onDidChangeWorkspaceFolders(() => this.updatePreferencesEditorMenuItemForWorkspaceFolders()));
|
||||
}
|
||||
|
||||
private updatePreferencesEditorMenuItem() {
|
||||
const commandId = '_workbench.openWorkspaceSettingsEditor';
|
||||
if (this.workpsaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !CommandsRegistry.getCommand(commandId)) {
|
||||
CommandsRegistry.registerCommand(commandId, () => this.preferencesService.openWorkspaceSettings(false));
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: commandId,
|
||||
title: OpenSettings2Action.LABEL,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`))
|
||||
}
|
||||
},
|
||||
when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')),
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
});
|
||||
}
|
||||
this.updatePreferencesEditorMenuItemForWorkspaceFolders();
|
||||
}
|
||||
|
||||
private updatePreferencesEditorMenuItemForWorkspaceFolders() {
|
||||
for (const folder of this.workpsaceContextService.getWorkspace().folders) {
|
||||
const commandId = `_workbench.openFolderSettings.${folder.uri.toString()}`;
|
||||
if (!CommandsRegistry.getCommand(commandId)) {
|
||||
CommandsRegistry.registerCommand(commandId, () => {
|
||||
if (this.workpsaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) {
|
||||
return this.preferencesService.openWorkspaceSettings(false);
|
||||
} else {
|
||||
return this.preferencesService.openFolderSettings(folder.uri, false);
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: commandId,
|
||||
title: OpenSettings2Action.LABEL,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`))
|
||||
}
|
||||
},
|
||||
when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())),
|
||||
group: 'navigation',
|
||||
order: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(PreferencesActionsContribution, LifecyclePhase.Starting);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(PreferencesContribution, LifecyclePhase.Starting);
|
||||
|
||||
CommandsRegistry.registerCommand(OPEN_FOLDER_SETTINGS_COMMAND, function (accessor: ServicesAccessor, resource: URI) {
|
||||
const preferencesService = accessor.get(IPreferencesService);
|
||||
return preferencesService.openFolderSettings(resource);
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(OpenFolderSettingsAction.ID, serviceAccessor => {
|
||||
serviceAccessor.get(IInstantiationService).createInstance(OpenFolderSettingsAction, OpenFolderSettingsAction.ID, OpenFolderSettingsAction.LABEL).run();
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: OpenFolderSettingsAction.ID,
|
||||
title: { value: OpenFolderSettingsAction.LABEL, original: 'Preferences: Open Folder Settings' },
|
||||
category: nls.localize('preferencesCategory', "Preferences")
|
||||
},
|
||||
when: WorkbenchStateContext.isEqualTo('workspace')
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(OpenWorkspaceSettingsAction.ID, serviceAccessor => {
|
||||
serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceSettingsAction, OpenWorkspaceSettingsAction.ID, OpenWorkspaceSettingsAction.LABEL).run();
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: OpenWorkspaceSettingsAction.ID,
|
||||
title: { value: OpenWorkspaceSettingsAction.LABEL, original: 'Preferences: Open Workspace Settings' },
|
||||
category: nls.localize('preferencesCategory', "Preferences")
|
||||
},
|
||||
when: WorkbenchStateContext.notEqualsTo('empty')
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS),
|
||||
primary: KeyCode.Escape,
|
||||
handler: (accessor, args: any) => {
|
||||
const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.clearSearchResults();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(OpenGlobalKeybindingsFileAction.ID, serviceAccessor => {
|
||||
serviceAccessor.get(IInstantiationService).createInstance(OpenGlobalKeybindingsFileAction, OpenGlobalKeybindingsFileAction.ID, OpenGlobalKeybindingsFileAction.LABEL).run();
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: OpenGlobalKeybindingsFileAction.ID,
|
||||
title: OpenGlobalKeybindingsFileAction.LABEL,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg`))
|
||||
}
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
|
||||
group: 'navigation',
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, serviceAccessor => {
|
||||
const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.search('@source:default');
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS,
|
||||
title: nls.localize('showDefaultKeybindings', "Show Default Keybindings")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
|
||||
group: '1_keyboard_preferences_actions'
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, serviceAccessor => {
|
||||
const control = serviceAccessor.get(IEditorService).activeControl as IKeybindingsEditor;
|
||||
if (control) {
|
||||
control.search('@source:user');
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS,
|
||||
title: nls.localize('showUserKeybindings', "Show User Keybindings")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR),
|
||||
group: '1_keyboard_preferences_actions'
|
||||
});
|
||||
|
||||
abstract class SettingsCommand extends Command {
|
||||
|
||||
protected getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null {
|
||||
const activeControl = accessor.get(IEditorService).activeControl;
|
||||
if (activeControl instanceof PreferencesEditor || activeControl instanceof SettingsEditor2) {
|
||||
return activeControl;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
class StartSearchDefaultSettingsCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor) {
|
||||
preferencesEditor.focusSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
const startSearchCommand = new StartSearchDefaultSettingsCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_SEARCH,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR),
|
||||
kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.KEY_F, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
startSearchCommand.register();
|
||||
|
||||
class ClearSearchResultsCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor) {
|
||||
preferencesEditor.clearSearchResults();
|
||||
}
|
||||
}
|
||||
}
|
||||
const clearSearchResultsCommand = new ClearSearchResultsCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS,
|
||||
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
|
||||
kbOpts: { primary: KeyCode.Escape, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
clearSearchResultsCommand.register();
|
||||
|
||||
class FocusSettingsFileEditorCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof PreferencesEditor) {
|
||||
preferencesEditor.focusSettingsFileEditor();
|
||||
} else if (preferencesEditor) {
|
||||
preferencesEditor.focusSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
const focusSettingsFileEditorCommand = new FocusSettingsFileEditorCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_FILE,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()),
|
||||
kbOpts: { primary: KeyCode.DownArrow, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
focusSettingsFileEditorCommand.register();
|
||||
|
||||
const focusSettingsFromSearchCommand = new FocusSettingsFileEditorCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_SEARCH_FOCUS, SuggestContext.Visible.toNegated()),
|
||||
kbOpts: { primary: KeyCode.DownArrow, weight: KeybindingWeight.WorkbenchContrib }
|
||||
});
|
||||
focusSettingsFromSearchCommand.register();
|
||||
|
||||
class FocusNextSearchResultCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof PreferencesEditor) {
|
||||
preferencesEditor.focusNextResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
const focusNextSearchResultCommand = new FocusNextSearchResultCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING,
|
||||
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
|
||||
kbOpts: { primary: KeyCode.Enter, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
focusNextSearchResultCommand.register();
|
||||
|
||||
class FocusPreviousSearchResultCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof PreferencesEditor) {
|
||||
preferencesEditor.focusPreviousResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
const focusPreviousSearchResultCommand = new FocusPreviousSearchResultCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING,
|
||||
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
|
||||
kbOpts: { primary: KeyMod.Shift | KeyCode.Enter, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
focusPreviousSearchResultCommand.register();
|
||||
|
||||
class EditFocusedSettingCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof PreferencesEditor) {
|
||||
preferencesEditor.editFocusedPreference();
|
||||
}
|
||||
}
|
||||
}
|
||||
const editFocusedSettingCommand = new EditFocusedSettingCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING,
|
||||
precondition: CONTEXT_SETTINGS_SEARCH_FOCUS,
|
||||
kbOpts: { primary: KeyMod.CtrlCmd | KeyCode.US_DOT, weight: KeybindingWeight.EditorContrib }
|
||||
});
|
||||
editFocusedSettingCommand.register();
|
||||
|
||||
class FocusSettingsListCommand extends SettingsCommand {
|
||||
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof SettingsEditor2) {
|
||||
preferencesEditor.focusSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusSettingsListCommand = new FocusSettingsListCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_TOC_ROW_FOCUS),
|
||||
kbOpts: { primary: KeyCode.Enter, weight: KeybindingWeight.WorkbenchContrib }
|
||||
});
|
||||
focusSettingsListCommand.register();
|
||||
|
||||
class ShowContextMenuCommand extends SettingsCommand {
|
||||
runCommand(accessor: ServicesAccessor, args: any): void {
|
||||
const preferencesEditor = this.getPreferencesEditor(accessor);
|
||||
if (preferencesEditor instanceof SettingsEditor2) {
|
||||
preferencesEditor.showContextMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const showContextMenuCommand = new ShowContextMenuCommand({
|
||||
id: SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU,
|
||||
precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR),
|
||||
kbOpts: { primary: KeyMod.Shift | KeyCode.F9, weight: KeybindingWeight.WorkbenchContrib }
|
||||
});
|
||||
showContextMenuCommand.register();
|
||||
|
||||
CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, serviceAccessor => {
|
||||
const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2;
|
||||
if (control instanceof SettingsEditor2) {
|
||||
return control.switchToSettingsFile();
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, serviceAccessor => {
|
||||
const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2;
|
||||
if (control instanceof SettingsEditor2) {
|
||||
control.focusSearch(`@${MODIFIED_SETTING_TAG}`);
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand(SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, serviceAccessor => {
|
||||
const control = serviceAccessor.get(IEditorService).activeControl as SettingsEditor2;
|
||||
if (control instanceof SettingsEditor2) {
|
||||
control.focusSearch(`@tag:usesOnlineServices`);
|
||||
}
|
||||
});
|
||||
|
||||
// Preferences menu
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '1_settings',
|
||||
command: {
|
||||
id: SETTINGS_COMMAND_OPEN_SETTINGS,
|
||||
title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
|
||||
group: '2_keybindings',
|
||||
command: {
|
||||
id: OpenGlobalKeybindingsAction.ID,
|
||||
title: nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
// Editor tool items
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON,
|
||||
title: nls.localize('openSettingsJson', "Open Settings (JSON)"),
|
||||
iconLocation: {
|
||||
dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg')),
|
||||
light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg'))
|
||||
}
|
||||
},
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
when: ContextKeyExpr.and(
|
||||
CONTEXT_SETTINGS_EDITOR,
|
||||
CONTEXT_SETTINGS_JSON_EDITOR.toNegated()
|
||||
)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED,
|
||||
title: nls.localize('filterModifiedLabel', "Show modified settings")
|
||||
},
|
||||
group: '1_filter',
|
||||
order: 1,
|
||||
when: ContextKeyExpr.and(
|
||||
CONTEXT_SETTINGS_EDITOR,
|
||||
CONTEXT_SETTINGS_JSON_EDITOR.toNegated()
|
||||
)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
command: {
|
||||
id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE,
|
||||
title: nls.localize('filterOnlineServicesLabel', "Show settings for online services"),
|
||||
},
|
||||
group: '1_filter',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(
|
||||
CONTEXT_SETTINGS_EDITOR,
|
||||
CONTEXT_SETTINGS_JSON_EDITOR.toNegated()
|
||||
)
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
|
||||
group: '2_workspace',
|
||||
order: 20,
|
||||
command: {
|
||||
id: OPEN_FOLDER_SETTINGS_COMMAND,
|
||||
title: OPEN_FOLDER_SETTINGS_LABEL
|
||||
},
|
||||
when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext)
|
||||
});
|
||||
@@ -0,0 +1,565 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISettingsEditorModel, ISetting, ISettingsGroup, IFilterMetadata, ISearchResult, IGroupFilter, ISettingMatcher, IScoredResults, ISettingMatch, IRemoteSetting, IExtensionSetting } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { distinct, top } from 'vs/base/common/arrays';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IRequestService } from 'vs/platform/request/node/request';
|
||||
import { asJson } from 'vs/base/node/request';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IPreferencesSearchService, ISearchProvider, IWorkbenchSettingsConfiguration } from 'vs/workbench/contrib/preferences/common/preferences';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export interface IEndpointDetails {
|
||||
urlBase?: string;
|
||||
key?: string;
|
||||
}
|
||||
|
||||
export class PreferencesSearchService extends Disposable implements IPreferencesSearchService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private _installedExtensions: Promise<ILocalExtension[]>;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService
|
||||
) {
|
||||
super();
|
||||
|
||||
// This request goes to the shared process but results won't change during a window's lifetime, so cache the results.
|
||||
this._installedExtensions = this.extensionManagementService.getInstalled(ExtensionType.User).then(exts => {
|
||||
// Filter to enabled extensions that have settings
|
||||
return exts
|
||||
.filter(ext => this.extensionEnablementService.isEnabled(ext))
|
||||
.filter(ext => ext.manifest && ext.manifest.contributes && ext.manifest.contributes.configuration)
|
||||
.filter(ext => !!ext.identifier.uuid);
|
||||
});
|
||||
}
|
||||
|
||||
private get remoteSearchAllowed(): boolean {
|
||||
const workbenchSettings = this.configurationService.getValue<IWorkbenchSettingsConfiguration>().workbench.settings;
|
||||
if (!workbenchSettings.enableNaturalLanguageSearch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this._endpoint.urlBase;
|
||||
}
|
||||
|
||||
private get _endpoint(): IEndpointDetails {
|
||||
const workbenchSettings = this.configurationService.getValue<IWorkbenchSettingsConfiguration>().workbench.settings;
|
||||
if (workbenchSettings.naturalLanguageSearchEndpoint) {
|
||||
return {
|
||||
urlBase: workbenchSettings.naturalLanguageSearchEndpoint,
|
||||
key: workbenchSettings.naturalLanguageSearchKey
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
urlBase: this.environmentService.settingsSearchUrl
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getRemoteSearchProvider(filter: string, newExtensionsOnly = false): ISearchProvider | undefined {
|
||||
const opts: IRemoteSearchProviderOptions = {
|
||||
filter,
|
||||
newExtensionsOnly,
|
||||
endpoint: this._endpoint
|
||||
};
|
||||
|
||||
return this.remoteSearchAllowed ? this.instantiationService.createInstance(RemoteSearchProvider, opts, this._installedExtensions) : undefined;
|
||||
}
|
||||
|
||||
getLocalSearchProvider(filter: string): LocalSearchProvider {
|
||||
return this.instantiationService.createInstance(LocalSearchProvider, filter);
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalSearchProvider implements ISearchProvider {
|
||||
static readonly EXACT_MATCH_SCORE = 10000;
|
||||
static readonly START_SCORE = 1000;
|
||||
|
||||
constructor(private _filter: string) {
|
||||
// Remove " and : which are likely to be copypasted as part of a setting name.
|
||||
// Leave other special characters which the user might want to search for.
|
||||
this._filter = this._filter
|
||||
.replace(/[":]/g, ' ')
|
||||
.replace(/ /g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise<ISearchResult | null> {
|
||||
if (!this._filter) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
let orderedScore = LocalSearchProvider.START_SCORE; // Sort is not stable
|
||||
const settingMatcher = (setting: ISetting) => {
|
||||
const matches = new SettingMatches(this._filter, setting, true, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
|
||||
const score = this._filter === setting.key ?
|
||||
LocalSearchProvider.EXACT_MATCH_SCORE :
|
||||
orderedScore--;
|
||||
|
||||
return matches && matches.length ?
|
||||
{
|
||||
matches,
|
||||
score
|
||||
} :
|
||||
null;
|
||||
};
|
||||
|
||||
const filterMatches = preferencesModel.filterSettings(this._filter, this.getGroupFilter(this._filter), settingMatcher);
|
||||
if (filterMatches[0] && filterMatches[0].score === LocalSearchProvider.EXACT_MATCH_SCORE) {
|
||||
return Promise.resolve({
|
||||
filterMatches: filterMatches.slice(0, 1),
|
||||
exactMatch: true
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve({
|
||||
filterMatches
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getGroupFilter(filter: string): IGroupFilter {
|
||||
const regex = strings.createRegExp(filter, false, { global: true });
|
||||
return (group: ISettingsGroup) => {
|
||||
return regex.test(group.title);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IRemoteSearchProviderOptions {
|
||||
filter: string;
|
||||
endpoint: IEndpointDetails;
|
||||
newExtensionsOnly: boolean;
|
||||
}
|
||||
|
||||
interface IBingRequestDetails {
|
||||
url: string;
|
||||
body?: string;
|
||||
hasMoreFilters?: boolean;
|
||||
extensions?: ILocalExtension[];
|
||||
}
|
||||
|
||||
class RemoteSearchProvider implements ISearchProvider {
|
||||
// Must keep extension filter size under 8kb. 42 filters puts us there.
|
||||
private static readonly MAX_REQUEST_FILTERS = 42;
|
||||
private static readonly MAX_REQUESTS = 10;
|
||||
private static readonly NEW_EXTENSIONS_MIN_SCORE = 1;
|
||||
|
||||
private _remoteSearchP: Promise<IFilterMetadata | null>;
|
||||
|
||||
constructor(private options: IRemoteSearchProviderOptions, private installedExtensions: Promise<ILocalExtension[]>,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
this._remoteSearchP = this.options.filter ?
|
||||
Promise.resolve(this.getSettingsForFilter(this.options.filter)) :
|
||||
Promise.resolve(null);
|
||||
}
|
||||
|
||||
searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise<ISearchResult | null> {
|
||||
return this._remoteSearchP.then<ISearchResult | null>((remoteResult) => {
|
||||
if (!remoteResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token && token.isCancellationRequested) {
|
||||
throw canceled();
|
||||
}
|
||||
|
||||
const resultKeys = Object.keys(remoteResult.scoredResults);
|
||||
const highScoreKey = top(resultKeys, (a, b) => remoteResult.scoredResults[b].score - remoteResult.scoredResults[a].score, 1)[0];
|
||||
const highScore = highScoreKey ? remoteResult.scoredResults[highScoreKey].score : 0;
|
||||
const minScore = highScore / 5;
|
||||
if (this.options.newExtensionsOnly) {
|
||||
return this.installedExtensions.then(installedExtensions => {
|
||||
const newExtsMinScore = Math.max(RemoteSearchProvider.NEW_EXTENSIONS_MIN_SCORE, minScore);
|
||||
const passingScoreKeys = resultKeys
|
||||
.filter(k => {
|
||||
const result = remoteResult.scoredResults[k];
|
||||
const resultExtId = (result.extensionPublisher + '.' + result.extensionName).toLowerCase();
|
||||
return !installedExtensions.some(ext => ext.identifier.id.toLowerCase() === resultExtId);
|
||||
})
|
||||
.filter(k => remoteResult.scoredResults[k].score >= newExtsMinScore);
|
||||
|
||||
const filterMatches: ISettingMatch[] = passingScoreKeys.map(k => {
|
||||
const remoteSetting = remoteResult.scoredResults[k];
|
||||
const setting = remoteSettingToISetting(remoteSetting);
|
||||
return <ISettingMatch>{
|
||||
setting,
|
||||
score: remoteSetting.score,
|
||||
matches: [] // TODO
|
||||
};
|
||||
});
|
||||
|
||||
return <ISearchResult>{
|
||||
filterMatches,
|
||||
metadata: remoteResult
|
||||
};
|
||||
});
|
||||
} else {
|
||||
const settingMatcher = this.getRemoteSettingMatcher(remoteResult.scoredResults, minScore, preferencesModel);
|
||||
const filterMatches = preferencesModel.filterSettings(this.options.filter, group => null, settingMatcher);
|
||||
return <ISearchResult>{
|
||||
filterMatches,
|
||||
metadata: remoteResult
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getSettingsForFilter(filter: string): Promise<IFilterMetadata> {
|
||||
const allRequestDetails: IBingRequestDetails[] = [];
|
||||
|
||||
// Only send MAX_REQUESTS requests in total just to keep it sane
|
||||
for (let i = 0; i < RemoteSearchProvider.MAX_REQUESTS; i++) {
|
||||
const details = await this.prepareRequest(filter, i);
|
||||
allRequestDetails.push(details);
|
||||
if (!details.hasMoreFilters) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(allRequestDetails.map(details => this.getSettingsFromBing(details))).then(allResponses => {
|
||||
// Merge all IFilterMetadata
|
||||
const metadata = allResponses[0];
|
||||
metadata.requestCount = 1;
|
||||
|
||||
for (const response of allResponses.slice(1)) {
|
||||
metadata.requestCount++;
|
||||
metadata.scoredResults = { ...metadata.scoredResults, ...response.scoredResults };
|
||||
}
|
||||
|
||||
return metadata;
|
||||
});
|
||||
}
|
||||
|
||||
private getSettingsFromBing(details: IBingRequestDetails): Promise<IFilterMetadata> {
|
||||
this.logService.debug(`Searching settings via ${details.url}`);
|
||||
if (details.body) {
|
||||
this.logService.debug(`Body: ${details.body}`);
|
||||
}
|
||||
|
||||
const requestType = details.body ? 'post' : 'get';
|
||||
const headers = {
|
||||
'User-Agent': 'request',
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
if (this.options.endpoint.key) {
|
||||
headers['api-key'] = this.options.endpoint.key;
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
return this.requestService.request({
|
||||
type: requestType,
|
||||
url: details.url,
|
||||
data: details.body,
|
||||
headers,
|
||||
timeout: 5000
|
||||
}, CancellationToken.None).then(context => {
|
||||
if (typeof context.res.statusCode === 'number' && context.res.statusCode >= 300) {
|
||||
throw new Error(`${JSON.stringify(details)} returned status code: ${context.res.statusCode}`);
|
||||
}
|
||||
|
||||
return asJson(context);
|
||||
}).then((result: any) => {
|
||||
const timestamp = Date.now();
|
||||
const duration = timestamp - start;
|
||||
const remoteSettings: IRemoteSetting[] = (result.value || [])
|
||||
.map(r => {
|
||||
const key = JSON.parse(r.setting || r.Setting);
|
||||
const packageId = r['packageid'];
|
||||
const id = getSettingKey(key, packageId);
|
||||
|
||||
const value = r['value'];
|
||||
const defaultValue = value ? JSON.parse(value) : value;
|
||||
|
||||
const packageName = r['packagename'];
|
||||
let extensionName: string | undefined;
|
||||
let extensionPublisher: string | undefined;
|
||||
if (packageName && packageName.indexOf('##') >= 0) {
|
||||
[extensionPublisher, extensionName] = packageName.split('##');
|
||||
}
|
||||
|
||||
return <IRemoteSetting>{
|
||||
key,
|
||||
id,
|
||||
defaultValue,
|
||||
score: r['@search.score'],
|
||||
description: JSON.parse(r['details']),
|
||||
packageId,
|
||||
extensionName,
|
||||
extensionPublisher
|
||||
};
|
||||
});
|
||||
|
||||
const scoredResults = Object.create(null);
|
||||
remoteSettings.forEach(s => {
|
||||
scoredResults[s.id] = s;
|
||||
});
|
||||
|
||||
return <IFilterMetadata>{
|
||||
requestUrl: details.url,
|
||||
requestBody: details.body,
|
||||
duration,
|
||||
timestamp,
|
||||
scoredResults,
|
||||
context: result['@odata.context']
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getRemoteSettingMatcher(scoredResults: IScoredResults, minScore: number, preferencesModel: ISettingsEditorModel): ISettingMatcher {
|
||||
return (setting: ISetting, group: ISettingsGroup) => {
|
||||
const remoteSetting = scoredResults[getSettingKey(setting.key, group.id)] || // extension setting
|
||||
scoredResults[getSettingKey(setting.key, 'core')] || // core setting
|
||||
scoredResults[getSettingKey(setting.key)]; // core setting from original prod endpoint
|
||||
if (remoteSetting && remoteSetting.score >= minScore) {
|
||||
const settingMatches = new SettingMatches(this.options.filter, setting, false, true, (filter, setting) => preferencesModel.findValueMatches(filter, setting)).matches;
|
||||
return { matches: settingMatches, score: remoteSetting.score };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
private async prepareRequest(query: string, filterPage = 0): Promise<IBingRequestDetails> {
|
||||
const verbatimQuery = query;
|
||||
query = escapeSpecialChars(query);
|
||||
const boost = 10;
|
||||
const boostedQuery = `(${query})^${boost}`;
|
||||
|
||||
// Appending Fuzzy after each word.
|
||||
query = query.replace(/\ +/g, '~ ') + '~';
|
||||
|
||||
const encodedQuery = encodeURIComponent(boostedQuery + ' || ' + query);
|
||||
let url = `${this.options.endpoint.urlBase}`;
|
||||
|
||||
if (this.options.endpoint.key) {
|
||||
url += `${API_VERSION}&${QUERY_TYPE}`;
|
||||
}
|
||||
|
||||
const extensions = await this.installedExtensions;
|
||||
const filters = this.options.newExtensionsOnly ?
|
||||
[`diminish eq 'latest'`] :
|
||||
this.getVersionFilters(extensions, this.environmentService.settingsSearchBuildId);
|
||||
|
||||
const filterStr = filters
|
||||
.slice(filterPage * RemoteSearchProvider.MAX_REQUEST_FILTERS, (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS)
|
||||
.join(' or ');
|
||||
const hasMoreFilters = filters.length > (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS;
|
||||
|
||||
const body = JSON.stringify({
|
||||
query: encodedQuery,
|
||||
filters: encodeURIComponent(filterStr),
|
||||
rawQuery: encodeURIComponent(verbatimQuery)
|
||||
});
|
||||
|
||||
return {
|
||||
url,
|
||||
body,
|
||||
hasMoreFilters
|
||||
};
|
||||
}
|
||||
|
||||
private getVersionFilters(exts: ILocalExtension[], buildNumber?: number): string[] {
|
||||
// Only search extensions that contribute settings
|
||||
const filters = exts
|
||||
.filter(ext => ext.manifest.contributes && ext.manifest.contributes.configuration)
|
||||
.map(ext => this.getExtensionFilter(ext));
|
||||
|
||||
if (buildNumber) {
|
||||
filters.push(`(packageid eq 'core' and startbuildno le '${buildNumber}' and endbuildno ge '${buildNumber}')`);
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
private getExtensionFilter(ext: ILocalExtension): string {
|
||||
const uuid = ext.identifier.uuid;
|
||||
const versionString = ext.manifest.version
|
||||
.split('.')
|
||||
.map(versionPart => strings.pad(<any>versionPart, 10))
|
||||
.join('');
|
||||
|
||||
return `(packageid eq '${uuid}' and startbuildno le '${versionString}' and endbuildno ge '${versionString}')`;
|
||||
}
|
||||
}
|
||||
|
||||
function getSettingKey(name: string, packageId?: string): string {
|
||||
return packageId ?
|
||||
packageId + '##' + name :
|
||||
name;
|
||||
}
|
||||
|
||||
const API_VERSION = 'api-version=2016-09-01-Preview';
|
||||
const QUERY_TYPE = 'querytype=full';
|
||||
|
||||
function escapeSpecialChars(query: string): string {
|
||||
return query.replace(/\./g, ' ')
|
||||
.replace(/[\\/+\-&|!"~*?:(){}\[\]\^]/g, '\\$&')
|
||||
.replace(/ /g, ' ') // collapse spaces
|
||||
.trim();
|
||||
}
|
||||
|
||||
function remoteSettingToISetting(remoteSetting: IRemoteSetting): IExtensionSetting {
|
||||
return {
|
||||
description: remoteSetting.description.split('\n'),
|
||||
descriptionIsMarkdown: false,
|
||||
descriptionRanges: [],
|
||||
key: remoteSetting.key,
|
||||
keyRange: nullRange,
|
||||
value: remoteSetting.defaultValue,
|
||||
range: nullRange,
|
||||
valueRange: nullRange,
|
||||
overrides: [],
|
||||
extensionName: remoteSetting.extensionName,
|
||||
extensionPublisher: remoteSetting.extensionPublisher
|
||||
};
|
||||
}
|
||||
|
||||
class SettingMatches {
|
||||
|
||||
private readonly descriptionMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
|
||||
private readonly keyMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
|
||||
private readonly valueMatchingWords: Map<string, IRange[]> = new Map<string, IRange[]>();
|
||||
|
||||
readonly matches: IRange[];
|
||||
|
||||
constructor(searchString: string, setting: ISetting, private requireFullQueryMatch: boolean, private searchDescription, private valuesMatcher: (filter: string, setting: ISetting) => IRange[]) {
|
||||
this.matches = distinct(this._findMatchesInSetting(searchString, setting), (match) => `${match.startLineNumber}_${match.startColumn}_${match.endLineNumber}_${match.endColumn}_`);
|
||||
}
|
||||
|
||||
private _findMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
|
||||
const result = this._doFindMatchesInSetting(searchString, setting);
|
||||
if (setting.overrides && setting.overrides.length) {
|
||||
for (const subSetting of setting.overrides) {
|
||||
const subSettingMatches = new SettingMatches(searchString, subSetting, this.requireFullQueryMatch, this.searchDescription, this.valuesMatcher);
|
||||
const words = searchString.split(' ');
|
||||
const descriptionRanges: IRange[] = this.getRangesForWords(words, this.descriptionMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
|
||||
const keyRanges: IRange[] = this.getRangesForWords(words, this.keyMatchingWords, [subSettingMatches.descriptionMatchingWords, subSettingMatches.keyMatchingWords, subSettingMatches.valueMatchingWords]);
|
||||
const subSettingKeyRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.keyMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.valueMatchingWords]);
|
||||
const subSettinValueRanges: IRange[] = this.getRangesForWords(words, subSettingMatches.valueMatchingWords, [this.descriptionMatchingWords, this.keyMatchingWords, subSettingMatches.keyMatchingWords]);
|
||||
result.push(...descriptionRanges, ...keyRanges, ...subSettingKeyRanges, ...subSettinValueRanges);
|
||||
result.push(...subSettingMatches.matches);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _doFindMatchesInSetting(searchString: string, setting: ISetting): IRange[] {
|
||||
const registry: { [qualifiedKey: string]: IJSONSchema } = Registry.as<IConfigurationRegistry>(Extensions.Configuration).getConfigurationProperties();
|
||||
const schema: IJSONSchema = registry[setting.key];
|
||||
|
||||
const words = searchString.split(' ');
|
||||
const settingKeyAsWords: string = setting.key.split('.').join(' ');
|
||||
|
||||
for (const word of words) {
|
||||
if (this.searchDescription) {
|
||||
for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
|
||||
const descriptionMatches = matchesWords(word, setting.description[lineIndex], true);
|
||||
if (descriptionMatches) {
|
||||
this.descriptionMatchingWords.set(word, descriptionMatches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const keyMatches = or(matchesWords, matchesCamelCase)(word, settingKeyAsWords);
|
||||
if (keyMatches) {
|
||||
this.keyMatchingWords.set(word, keyMatches.map(match => this.toKeyRange(setting, match)));
|
||||
}
|
||||
|
||||
const valueMatches = typeof setting.value === 'string' ? matchesContiguousSubString(word, setting.value) : null;
|
||||
if (valueMatches) {
|
||||
this.valueMatchingWords.set(word, valueMatches.map(match => this.toValueRange(setting, match)));
|
||||
} else if (schema && schema.enum && schema.enum.some(enumValue => typeof enumValue === 'string' && !!matchesContiguousSubString(word, enumValue))) {
|
||||
this.valueMatchingWords.set(word, []);
|
||||
}
|
||||
}
|
||||
|
||||
const descriptionRanges: IRange[] = [];
|
||||
if (this.searchDescription) {
|
||||
for (let lineIndex = 0; lineIndex < setting.description.length; lineIndex++) {
|
||||
const matches = or(matchesContiguousSubString)(searchString, setting.description[lineIndex] || '') || [];
|
||||
descriptionRanges.push(...matches.map(match => this.toDescriptionRange(setting, match, lineIndex)));
|
||||
}
|
||||
if (descriptionRanges.length === 0) {
|
||||
descriptionRanges.push(...this.getRangesForWords(words, this.descriptionMatchingWords, [this.keyMatchingWords, this.valueMatchingWords]));
|
||||
}
|
||||
}
|
||||
|
||||
const keyMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.key);
|
||||
const keyRanges: IRange[] = keyMatches ? keyMatches.map(match => this.toKeyRange(setting, match)) : this.getRangesForWords(words, this.keyMatchingWords, [this.descriptionMatchingWords, this.valueMatchingWords]);
|
||||
|
||||
let valueRanges: IRange[] = [];
|
||||
if (setting.value && typeof setting.value === 'string') {
|
||||
const valueMatches = or(matchesPrefix, matchesContiguousSubString)(searchString, setting.value);
|
||||
valueRanges = valueMatches ? valueMatches.map(match => this.toValueRange(setting, match)) : this.getRangesForWords(words, this.valueMatchingWords, [this.keyMatchingWords, this.descriptionMatchingWords]);
|
||||
} else {
|
||||
valueRanges = this.valuesMatcher ? this.valuesMatcher(searchString, setting) : [];
|
||||
}
|
||||
|
||||
return [...descriptionRanges, ...keyRanges, ...valueRanges];
|
||||
}
|
||||
|
||||
private getRangesForWords(words: string[], from: Map<string, IRange[]>, others: Map<string, IRange[]>[]): IRange[] {
|
||||
const result: IRange[] = [];
|
||||
for (const word of words) {
|
||||
const ranges = from.get(word);
|
||||
if (ranges) {
|
||||
result.push(...ranges);
|
||||
} else if (this.requireFullQueryMatch && others.every(o => !o.has(word))) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private toKeyRange(setting: ISetting, match: IMatch): IRange {
|
||||
return {
|
||||
startLineNumber: setting.keyRange.startLineNumber,
|
||||
startColumn: setting.keyRange.startColumn + match.start,
|
||||
endLineNumber: setting.keyRange.startLineNumber,
|
||||
endColumn: setting.keyRange.startColumn + match.end
|
||||
};
|
||||
}
|
||||
|
||||
private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange {
|
||||
return {
|
||||
startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber,
|
||||
startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start,
|
||||
endLineNumber: setting.descriptionRanges[lineIndex].endLineNumber,
|
||||
endColumn: setting.descriptionRanges[lineIndex].startColumn + match.end
|
||||
};
|
||||
}
|
||||
|
||||
private toValueRange(setting: ISetting, match: IMatch): IRange {
|
||||
return {
|
||||
startLineNumber: setting.valueRange.startLineNumber,
|
||||
startColumn: setting.valueRange.startColumn + match.start + 1,
|
||||
endLineNumber: setting.valueRange.startLineNumber,
|
||||
endColumn: setting.valueRange.startColumn + match.end + 1
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { KeybindingEditorDecorationsRenderer } from 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution';
|
||||
|
||||
suite('KeybindingsEditorContribution', () => {
|
||||
|
||||
function assertUserSettingsFuzzyEquals(a: string, b: string, expected: boolean): void {
|
||||
const actual = KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(a, b);
|
||||
const message = expected ? `${a} == ${b}` : `${a} != ${b}`;
|
||||
assert.equal(actual, expected, 'fuzzy: ' + message);
|
||||
}
|
||||
|
||||
function assertEqual(a: string, b: string): void {
|
||||
assertUserSettingsFuzzyEquals(a, b, true);
|
||||
}
|
||||
|
||||
function assertDifferent(a: string, b: string): void {
|
||||
assertUserSettingsFuzzyEquals(a, b, false);
|
||||
}
|
||||
|
||||
test('_userSettingsFuzzyEquals', () => {
|
||||
assertEqual('a', 'a');
|
||||
assertEqual('a', 'A');
|
||||
assertEqual('ctrl+a', 'CTRL+A');
|
||||
assertEqual('ctrl+a', ' CTRL+A ');
|
||||
|
||||
assertEqual('ctrl+shift+a', 'shift+ctrl+a');
|
||||
assertEqual('ctrl+shift+a ctrl+alt+b', 'shift+ctrl+a alt+ctrl+b');
|
||||
|
||||
assertDifferent('ctrl+[KeyA]', 'ctrl+a');
|
||||
|
||||
// issue #23335
|
||||
assertEqual('cmd+shift+p', 'shift+cmd+p');
|
||||
assertEqual('cmd+shift+p', 'shift-cmd-p');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,178 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { settingKeyToDisplayFormat, parseQuery, IParsedQuery } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
|
||||
|
||||
suite('SettingsTree', () => {
|
||||
test('settingKeyToDisplayFormat', () => {
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar'),
|
||||
{
|
||||
category: 'Foo',
|
||||
label: 'Bar'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar.etc'),
|
||||
{
|
||||
category: 'Foo › Bar',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('fooBar.etcSomething'),
|
||||
{
|
||||
category: 'Foo Bar',
|
||||
label: 'Etc Something'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Foo'
|
||||
});
|
||||
});
|
||||
|
||||
test('settingKeyToDisplayFormat - with category', () => {
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar', 'foo'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Bar'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('disableligatures.ligatures', 'disableligatures'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Ligatures'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar.etc', 'foo'),
|
||||
{
|
||||
category: 'Bar',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('fooBar.etcSomething', 'foo'),
|
||||
{
|
||||
category: 'Foo Bar',
|
||||
label: 'Etc Something'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar.etc', 'foo/bar'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('foo.bar.etc', 'something/foo'),
|
||||
{
|
||||
category: 'Bar',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('bar.etc', 'something.bar'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('fooBar.etc', 'fooBar'),
|
||||
{
|
||||
category: '',
|
||||
label: 'Etc'
|
||||
});
|
||||
|
||||
|
||||
assert.deepEqual(
|
||||
settingKeyToDisplayFormat('fooBar.somethingElse.etc', 'fooBar'),
|
||||
{
|
||||
category: 'Something Else',
|
||||
label: 'Etc'
|
||||
});
|
||||
});
|
||||
|
||||
test('parseQuery', () => {
|
||||
function testParseQuery(input: string, expected: IParsedQuery) {
|
||||
assert.deepEqual(
|
||||
parseQuery(input),
|
||||
expected,
|
||||
input
|
||||
);
|
||||
}
|
||||
|
||||
testParseQuery(
|
||||
'',
|
||||
<IParsedQuery>{
|
||||
tags: [],
|
||||
query: ''
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'@modified',
|
||||
<IParsedQuery>{
|
||||
tags: ['modified'],
|
||||
query: ''
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'@tag:foo',
|
||||
<IParsedQuery>{
|
||||
tags: ['foo'],
|
||||
query: ''
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'@modified foo',
|
||||
<IParsedQuery>{
|
||||
tags: ['modified'],
|
||||
query: 'foo'
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'@tag:foo @modified',
|
||||
<IParsedQuery>{
|
||||
tags: ['foo', 'modified'],
|
||||
query: ''
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'@tag:foo @modified my query',
|
||||
<IParsedQuery>{
|
||||
tags: ['foo', 'modified'],
|
||||
query: 'my query'
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'test @modified query',
|
||||
<IParsedQuery>{
|
||||
tags: ['modified'],
|
||||
query: 'test query'
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'test @modified',
|
||||
<IParsedQuery>{
|
||||
tags: ['modified'],
|
||||
query: 'test'
|
||||
});
|
||||
|
||||
testParseQuery(
|
||||
'query has @ for some reason',
|
||||
<IParsedQuery>{
|
||||
tags: [],
|
||||
query: 'query has @ for some reason'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
|
||||
suite('SmartSnippetInserter', () => {
|
||||
|
||||
function testSmartSnippetInserter(text: string[], runner: (assert: (desiredPos: Position, pos: Position, prepend: string, append: string) => void) => void): void {
|
||||
let model = TextModel.createFromString(text.join('\n'));
|
||||
runner((desiredPos, pos, prepend, append) => {
|
||||
let actual = SmartSnippetInserter.insertSnippet(model, desiredPos);
|
||||
let expected = {
|
||||
position: pos,
|
||||
prepend,
|
||||
append
|
||||
};
|
||||
assert.deepEqual(actual, expected);
|
||||
});
|
||||
model.dispose();
|
||||
}
|
||||
|
||||
test('empty text', () => {
|
||||
testSmartSnippetInserter([
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(1, 1), '\n[', ']');
|
||||
});
|
||||
|
||||
testSmartSnippetInserter([
|
||||
' '
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(1, 2), '\n[', ']');
|
||||
assert(new Position(1, 2), new Position(1, 2), '\n[', ']');
|
||||
});
|
||||
|
||||
testSmartSnippetInserter([
|
||||
'// just some text'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(1, 18), '\n[', ']');
|
||||
assert(new Position(1, 18), new Position(1, 18), '\n[', ']');
|
||||
});
|
||||
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
''
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 1), '\n[', ']');
|
||||
assert(new Position(1, 18), new Position(2, 1), '\n[', ']');
|
||||
assert(new Position(2, 1), new Position(2, 1), '\n[', ']');
|
||||
});
|
||||
});
|
||||
|
||||
test('empty array 1', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[]'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 3), new Position(2, 2), '', '');
|
||||
});
|
||||
});
|
||||
|
||||
test('empty array 2', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[',
|
||||
']'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', '');
|
||||
assert(new Position(3, 1), new Position(3, 1), '', '');
|
||||
assert(new Position(3, 2), new Position(3, 1), '', '');
|
||||
});
|
||||
});
|
||||
|
||||
test('empty array 3', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[',
|
||||
'// just some text',
|
||||
']'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', '');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', '');
|
||||
assert(new Position(3, 1), new Position(3, 1), '', '');
|
||||
assert(new Position(3, 2), new Position(3, 1), '', '');
|
||||
assert(new Position(4, 1), new Position(4, 1), '', '');
|
||||
assert(new Position(4, 2), new Position(4, 1), '', '');
|
||||
});
|
||||
});
|
||||
|
||||
test('one element array 1', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[',
|
||||
'{}',
|
||||
']'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', ',');
|
||||
assert(new Position(3, 1), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 2), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 3), new Position(3, 3), ',', '');
|
||||
assert(new Position(4, 1), new Position(4, 1), ',', '');
|
||||
assert(new Position(4, 2), new Position(4, 1), ',', '');
|
||||
});
|
||||
});
|
||||
|
||||
test('two elements array 1', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[',
|
||||
'{},',
|
||||
'{}',
|
||||
']'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', ',');
|
||||
assert(new Position(3, 1), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 2), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 3), new Position(3, 3), ',', '');
|
||||
assert(new Position(3, 4), new Position(3, 4), '', ',');
|
||||
assert(new Position(4, 1), new Position(4, 1), '', ',');
|
||||
assert(new Position(4, 2), new Position(4, 1), '', ',');
|
||||
assert(new Position(4, 3), new Position(4, 3), ',', '');
|
||||
assert(new Position(5, 1), new Position(5, 1), ',', '');
|
||||
assert(new Position(5, 2), new Position(5, 1), ',', '');
|
||||
});
|
||||
});
|
||||
|
||||
test('two elements array 2', () => {
|
||||
testSmartSnippetInserter([
|
||||
'// just some text',
|
||||
'[',
|
||||
'{},{}',
|
||||
']'
|
||||
], (assert) => {
|
||||
assert(new Position(1, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 1), new Position(2, 2), '', ',');
|
||||
assert(new Position(2, 2), new Position(2, 2), '', ',');
|
||||
assert(new Position(3, 1), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 2), new Position(3, 1), '', ',');
|
||||
assert(new Position(3, 3), new Position(3, 3), ',', '');
|
||||
assert(new Position(3, 4), new Position(3, 4), '', ',');
|
||||
assert(new Position(3, 5), new Position(3, 4), '', ',');
|
||||
assert(new Position(3, 6), new Position(3, 6), ',', '');
|
||||
assert(new Position(4, 1), new Position(4, 1), ',', '');
|
||||
assert(new Position(4, 2), new Position(4, 1), ',', '');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||