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);
|
||||
}));
|
||||
}
|
||||
}
|
||||