提交 da732ab0 编写于 作者: R Rob Lourens

Use new tree for settings editor

上级 1bea381d
......@@ -374,6 +374,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const item = this.items[index];
const renderer = this.renderers.get(item.templateId);
if (!item.row) {
return;
}
if (renderer.disposeElement) {
renderer.disposeElement(item.element, index, item.row!.templateData);
}
......
......@@ -12,8 +12,12 @@ import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria';
import { Button } from 'vs/base/browser/ui/button/button';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { ITreeModel, ITreeNode, ITreeRenderer, ITreeFilter, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree';
import { Action, IAction } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
import { Color, RGBA } from 'vs/base/common/color';
......@@ -21,26 +25,24 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ISpliceable } from 'vs/base/common/sequence';
import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IAccessibilityProvider, IDataSource, IFilter, IRenderer as ITreeRenderer, ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree';
import { DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { ITree, IFilter, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry';
import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry';
import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ITOCEntry } from 'vs/workbench/parts/preferences/browser/settingsLayout';
import { ISettingsEditorViewState, isExcludeSetting, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels';
import { ExcludeSettingWidget, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets';
import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels';
import { ExcludeSettingWidget, IExcludeChangeEvent, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets';
import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/parts/preferences/common/preferences';
import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
......@@ -170,102 +172,6 @@ function getFlatSettings(settingsGroups: ISettingsGroup[]) {
return result;
}
export class SettingsDataSource implements IDataSource {
getId(tree: ITree, element: SettingsTreeElement): string {
return element.id;
}
hasChildren(tree: ITree, element: SettingsTreeElement): boolean {
if (element instanceof SettingsTreeGroupElement) {
return true;
}
return false;
}
getChildren(tree: ITree, element: SettingsTreeElement): Promise<any> {
return Promise.resolve(this._getChildren(element));
}
private _getChildren(element: SettingsTreeElement): SettingsTreeElement[] {
if (element instanceof SettingsTreeGroupElement) {
return element.children;
} else {
// No children...
return null;
}
}
getParent(tree: ITree, element: SettingsTreeElement): Promise<any> {
return Promise.resolve(element && element.parent);
}
shouldAutoexpand(): boolean {
return true;
}
}
export class SimplePagedDataSource implements IDataSource {
private static readonly SETTINGS_PER_PAGE = 30;
private static readonly BUFFER = 5;
private loadedToIndex: number;
constructor(private realDataSource: IDataSource) {
this.reset();
}
reset(): void {
this.loadedToIndex = SimplePagedDataSource.SETTINGS_PER_PAGE;
}
pageTo(index: number, top = false): boolean {
const buffer = top ? SimplePagedDataSource.SETTINGS_PER_PAGE : SimplePagedDataSource.BUFFER;
if (index > this.loadedToIndex - buffer) {
this.loadedToIndex = (Math.ceil(index / SimplePagedDataSource.SETTINGS_PER_PAGE) + 1) * SimplePagedDataSource.SETTINGS_PER_PAGE;
return true;
} else {
return false;
}
}
getId(tree: ITree, element: any): string {
return this.realDataSource.getId(tree, element);
}
hasChildren(tree: ITree, element: any): boolean {
return this.realDataSource.hasChildren(tree, element);
}
getChildren(tree: ITree, element: SettingsTreeGroupElement): Promise<any> {
return this.realDataSource.getChildren(tree, element).then(realChildren => {
return this._getChildren(realChildren);
});
}
_getChildren(realChildren: SettingsTreeElement[]): any[] {
const lastChild = realChildren[realChildren.length - 1];
if (lastChild && lastChild.index > this.loadedToIndex) {
return realChildren.filter(child => {
return child.index < this.loadedToIndex;
});
} else {
return realChildren;
}
}
getParent(tree: ITree, element: any): Promise<any> {
return this.realDataSource.getParent(tree, element);
}
shouldAutoexpand(tree: ITree, element: any): boolean {
return this.realDataSource.shouldAutoexpand(tree, element);
}
}
interface IDisposableTemplate {
toDispose: IDisposable[];
}
......@@ -325,7 +231,7 @@ const SETTINGS_BOOL_TEMPLATE_ID = 'settings.bool.template';
const SETTINGS_EXCLUDE_TEMPLATE_ID = 'settings.exclude.template';
const SETTINGS_COMPLEX_TEMPLATE_ID = 'settings.complex.template';
const SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID = 'settings.newExtensions.template';
const SETTINGS_GROUP_ELEMENT_TEMPLATE_ID = 'settings.group.template';
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.group.template';
export interface ISettingChangeEvent {
key: string;
......@@ -343,20 +249,22 @@ export interface ISettingOverrideClickEvent {
targetKey: string;
}
export class SettingsRenderer implements ITreeRenderer {
export abstract class AbstractSettingRenderer implements ITreeRenderer<SettingsTreeElement, never, any> {
/** To override */
templateId = undefined;
public static readonly CONTROL_CLASS = 'setting-control-focus-target';
public static readonly CONTROL_SELECTOR = '.' + SettingsRenderer.CONTROL_CLASS;
public static readonly CONTROL_SELECTOR = '.' + AbstractSettingRenderer.CONTROL_CLASS;
public static readonly SETTING_KEY_ATTR = 'data-key';
private readonly _onDidClickOverrideElement = new Emitter<ISettingOverrideClickEvent>();
public readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent> = this._onDidClickOverrideElement.event;
private readonly _onDidChangeSetting = new Emitter<ISettingChangeEvent>();
protected readonly _onDidChangeSetting = new Emitter<ISettingChangeEvent>();
public readonly onDidChangeSetting: Event<ISettingChangeEvent> = this._onDidChangeSetting.event;
private readonly _onDidOpenSettings = new Emitter<string>();
protected readonly _onDidOpenSettings = new Emitter<string>();
public readonly onDidOpenSettings: Event<string> = this._onDidOpenSettings.event;
private readonly _onDidClickSettingLink = new Emitter<ISettingLinkClickEvent>();
......@@ -365,248 +273,28 @@ export class SettingsRenderer implements ITreeRenderer {
private readonly _onDidFocusSetting = new Emitter<SettingsTreeSettingElement>();
public readonly onDidFocusSetting: Event<SettingsTreeSettingElement> = this._onDidFocusSetting.event;
private descriptionMeasureContainer: HTMLElement;
private longestSingleLineDescription = 0;
private rowHeightCache = new Map<string, number>();
private lastRenderedWidth: number;
private settingActions: IAction[];
// Put common injections back here
constructor(
_measureParent: HTMLElement,
@IThemeService private themeService: IThemeService,
@IContextViewService private contextViewService: IContextViewService,
@IOpenerService private readonly openerService: IOpenerService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICommandService private readonly commandService: ICommandService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IKeybindingService private keybindingService: IKeybindingService,
private readonly settingActions: IAction[],
@IThemeService protected readonly _themeService: IThemeService,
@IContextViewService protected readonly _contextViewService: IContextViewService,
@IOpenerService protected readonly _openerService: IOpenerService,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@ICommandService protected readonly _commandService: ICommandService,
@IContextMenuService protected readonly _contextMenuService: IContextMenuService,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
) {
this.descriptionMeasureContainer = $('.setting-item-description');
DOM.append(_measureParent,
$('.setting-measure-container.monaco-tree.settings-editor-tree', undefined,
$('.monaco-scrollable-element', undefined,
$('.monaco-tree-wrapper', undefined,
$('.monaco-tree-rows', undefined,
$('.monaco-tree-row', undefined,
$('.setting-item', undefined,
this.descriptionMeasureContainer)))))));
this.settingActions = [
new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, (context: SettingsTreeSettingElement) => {
if (context) {
this._onDidChangeSetting.fire({ key: context.setting.key, value: undefined, type: context.setting.type as SettingValueType });
}
return Promise.resolve(null);
}),
new Separator(),
this.instantiationService.createInstance(CopySettingIdAction),
this.instantiationService.createInstance(CopySettingAsJSONAction),
];
}
showContextMenu(element: SettingsTreeSettingElement, settingDOMElement: HTMLElement): void {
const toolbarElement: HTMLElement = settingDOMElement.querySelector('.toolbar-toggle-more');
if (toolbarElement) {
this.contextMenuService.showContextMenu({
getActions: () => this.settingActions,
getAnchor: () => toolbarElement,
getActionsContext: () => element
});
}
}
updateWidth(width: number): void {
if (this.lastRenderedWidth !== width) {
this.rowHeightCache = new Map<string, number>();
}
this.longestSingleLineDescription = 0;
this.lastRenderedWidth = width;
}
getHeight(tree: ITree, element: SettingsTreeElement): number {
if (this.rowHeightCache.has(element.id) && !(element instanceof SettingsTreeSettingElement && isExcludeSetting(element.setting))) {
return this.rowHeightCache.get(element.id);
}
const h = this._getHeight(tree, element);
this.rowHeightCache.set(element.id, h);
return h;
}
_getHeight(tree: ITree, element: SettingsTreeElement): number {
if (element instanceof SettingsTreeGroupElement) {
if (element.isFirstGroup) {
return 31;
}
return 40 + (7 * element.level);
}
if (element instanceof SettingsTreeSettingElement) {
if (isExcludeSetting(element.setting)) {
return this._getExcludeSettingHeight(element);
} else {
return this.measureSettingElementHeight(tree, element);
}
}
if (element instanceof SettingsTreeNewExtensionsElement) {
return 40;
}
return 0;
}
_getExcludeSettingHeight(element: SettingsTreeSettingElement): number {
const displayValue = getExcludeDisplayValue(element);
return (displayValue.length + 1) * 22 + 66 + this.measureSettingDescription(element);
}
private measureSettingElementHeight(tree: ITree, element: SettingsTreeSettingElement): number {
let heightExcludingDescription = 86;
if (element.valueType === 'boolean') {
heightExcludingDescription = 60;
}
return heightExcludingDescription + this.measureSettingDescription(element);
}
private measureSettingDescription(element: SettingsTreeSettingElement): number {
if (element.description.length < this.longestSingleLineDescription * .8) {
// Most setting descriptions are one short line, so try to avoid measuring them.
// If the description is less than 80% of the longest single line description, assume this will also render to be one line.
return 18;
}
const boolMeasureClass = 'measure-bool-description';
if (element.valueType === 'boolean') {
this.descriptionMeasureContainer.classList.add(boolMeasureClass);
} else if (this.descriptionMeasureContainer.classList.contains(boolMeasureClass)) {
this.descriptionMeasureContainer.classList.remove(boolMeasureClass);
}
const shouldRenderMarkdown = element.setting.descriptionIsMarkdown && element.description.indexOf('\n- ') >= 0;
while (this.descriptionMeasureContainer.firstChild) {
this.descriptionMeasureContainer.removeChild(this.descriptionMeasureContainer.firstChild);
}
if (shouldRenderMarkdown) {
const text = fixSettingLinks(element.description);
const rendered = renderMarkdown({ value: text });
rendered.classList.add('setting-item-description-markdown');
this.descriptionMeasureContainer.appendChild(rendered);
return this.descriptionMeasureContainer.offsetHeight;
} else {
// Remove markdown links, setting links, backticks
const measureText = element.setting.descriptionIsMarkdown ?
fixSettingLinks(element.description)
.replace(/\[(.*)\]\(.*\)/g, '$1')
.replace(/`([^`]*)`/g, '$1') :
element.description;
this.descriptionMeasureContainer.innerText = measureText;
const h = this.descriptionMeasureContainer.offsetHeight;
if (h < 20 && measureText.length > this.longestSingleLineDescription) {
this.longestSingleLineDescription = measureText.length;
}
return h;
}
}
getTemplateId(tree: ITree, element: SettingsTreeElement): string {
if (element instanceof SettingsTreeGroupElement) {
return SETTINGS_GROUP_ELEMENT_TEMPLATE_ID;
}
if (element instanceof SettingsTreeSettingElement) {
if (element.valueType === 'boolean') {
return SETTINGS_BOOL_TEMPLATE_ID;
}
if (element.valueType === 'integer' || element.valueType === 'number' || element.valueType === 'nullable-integer' || element.valueType === 'nullable-number') {
return SETTINGS_NUMBER_TEMPLATE_ID;
}
if (element.valueType === 'string') {
return SETTINGS_TEXT_TEMPLATE_ID;
}
if (element.valueType === 'enum') {
return SETTINGS_ENUM_TEMPLATE_ID;
}
if (element.valueType === 'exclude') {
return SETTINGS_EXCLUDE_TEMPLATE_ID;
}
return SETTINGS_COMPLEX_TEMPLATE_ID;
}
if (element instanceof SettingsTreeNewExtensionsElement) {
return SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;
}
return '';
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
if (templateId === SETTINGS_GROUP_ELEMENT_TEMPLATE_ID) {
return this.renderGroupTitleTemplate(container);
}
if (templateId === SETTINGS_TEXT_TEMPLATE_ID) {
return this.renderSettingTextTemplate(tree, container);
}
if (templateId === SETTINGS_NUMBER_TEMPLATE_ID) {
return this.renderSettingNumberTemplate(tree, container);
}
if (templateId === SETTINGS_BOOL_TEMPLATE_ID) {
return this.renderSettingBoolTemplate(tree, container);
}
if (templateId === SETTINGS_ENUM_TEMPLATE_ID) {
return this.renderSettingEnumTemplate(tree, container);
}
if (templateId === SETTINGS_EXCLUDE_TEMPLATE_ID) {
return this.renderSettingExcludeTemplate(tree, container);
}
if (templateId === SETTINGS_COMPLEX_TEMPLATE_ID) {
return this.renderSettingComplexTemplate(tree, container);
}
if (templateId === SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID) {
return this.renderNewExtensionsTemplate(container);
}
return null;
renderTemplate(container: HTMLElement): any {
throw new Error('to override');
}
private renderGroupTitleTemplate(container: HTMLElement): IGroupTitleTemplate {
DOM.addClass(container, 'group-title');
const toDispose: IDisposable[] = [];
const template: IGroupTitleTemplate = {
parent: container,
toDispose
};
return template;
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: any): void {
throw new Error('to override');
}
private renderCommonTemplate(tree: ITree, container: HTMLElement, typeClass: string): ISettingItemTemplate {
protected renderCommonTemplate(tree: any, container: HTMLElement, typeClass: string): ISettingItemTemplate {
DOM.addClass(container, 'setting-item');
DOM.addClass(container, 'setting-item-' + typeClass);
const titleElement = DOM.append(container, $('.setting-item-title'));
......@@ -647,17 +335,10 @@ export class SettingsRenderer implements ITreeRenderer {
toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_ENTER, e => container.classList.add('mouseover')));
toDispose.push(DOM.addDisposableListener(titleElement, DOM.EventType.MOUSE_LEAVE, e => container.classList.remove('mouseover')));
toDispose.push(DOM.addStandardDisposableListener(valueElement, 'keydown', (e: StandardKeyboardEvent) => {
if (e.keyCode === KeyCode.Escape) {
tree.domFocus();
e.browserEvent.stopPropagation();
}
}));
return template;
}
private addSettingElementFocusHandler(template: ISettingItemTemplate): void {
protected addSettingElementFocusHandler(template: ISettingItemTemplate): void {
const focusTracker = DOM.trackFocus(template.containerElement);
template.toDispose.push(focusTracker);
focusTracker.onDidBlur(() => {
......@@ -675,76 +356,14 @@ export class SettingsRenderer implements ITreeRenderer {
});
}
private renderSettingTextTemplate(tree: ITree, container: HTMLElement, type = 'text'): ISettingTextItemTemplate {
const common = this.renderCommonTemplate(tree, container, 'text');
const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message'));
const inputBox = new InputBox(common.controlElement, this.contextViewService);
common.toDispose.push(inputBox);
common.toDispose.push(attachInputBoxStyler(inputBox, this.themeService, {
inputBackground: settingsTextInputBackground,
inputForeground: settingsTextInputForeground,
inputBorder: settingsTextInputBorder
}));
common.toDispose.push(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
}));
common.toDispose.push(inputBox);
inputBox.inputElement.classList.add(SettingsRenderer.CONTROL_CLASS);
const template: ISettingTextItemTemplate = {
...common,
inputBox,
validationErrorMessageElement
};
this.addSettingElementFocusHandler(template);
return template;
}
private renderSettingNumberTemplate(tree: ITree, container: HTMLElement): ISettingNumberItemTemplate {
const common = this.renderCommonTemplate(tree, container, 'number');
const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message'));
const inputBox = new InputBox(common.controlElement, this.contextViewService, { type: 'number' });
common.toDispose.push(inputBox);
common.toDispose.push(attachInputBoxStyler(inputBox, this.themeService, {
inputBackground: settingsNumberInputBackground,
inputForeground: settingsNumberInputForeground,
inputBorder: settingsNumberInputBorder
}));
common.toDispose.push(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
}));
common.toDispose.push(inputBox);
inputBox.inputElement.classList.add(SettingsRenderer.CONTROL_CLASS);
const template: ISettingNumberItemTemplate = {
...common,
inputBox,
validationErrorMessageElement
};
this.addSettingElementFocusHandler(template);
return template;
}
private renderSettingToolbar(container: HTMLElement): ToolBar {
const toggleMenuKeybinding = this.keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU);
protected renderSettingToolbar(container: HTMLElement): ToolBar {
const toggleMenuKeybinding = this._keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU);
let toggleMenuTitle = localize('settingsContextMenuTitle', "More Actions... ");
if (toggleMenuKeybinding) {
toggleMenuTitle += ` (${toggleMenuKeybinding && toggleMenuKeybinding.getLabel()})`;
}
const toolbar = new ToolBar(container, this.contextMenuService, {
const toolbar = new ToolBar(container, this._contextMenuService, {
toggleMenuTitle
});
toolbar.setActions([], this.settingActions)();
......@@ -756,95 +375,418 @@ export class SettingsRenderer implements ITreeRenderer {
return toolbar;
}
private renderSettingBoolTemplate(tree: ITree, container: HTMLElement): ISettingBoolItemTemplate {
DOM.addClass(container, 'setting-item');
DOM.addClass(container, 'setting-item-bool');
protected renderSettingElement(node: ITreeNode<SettingsTreeSettingElement, never>, index: number, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {
const element = node.element;
template.context = element;
template.toolbar.context = element;
const titleElement = DOM.append(container, $('.setting-item-title'));
const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));
const labelElement = DOM.append(titleElement, $('span.setting-item-label'));
const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides'));
const setting = element.setting;
const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description'));
const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control'));
const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description'));
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
modifiedIndicatorElement.title = localize('modified', "Modified");
DOM.toggleClass(template.containerElement, 'is-configured', element.isConfigured);
DOM.toggleClass(template.containerElement, 'is-expanded', true);
template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR, element.setting.key);
const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');
template.categoryElement.textContent = element.displayCategory && (element.displayCategory + ': ');
template.categoryElement.title = titleTooltip;
template.labelElement.textContent = element.displayLabel;
template.labelElement.title = titleTooltip;
const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));
template.descriptionElement.innerHTML = '';
if (element.setting.descriptionIsMarkdown) {
const renderedDescription = this.renderDescriptionMarkdown(element, element.description, template.toDispose);
template.descriptionElement.appendChild(renderedDescription);
} else {
template.descriptionElement.innerText = element.description;
}
const baseId = (element.displayCategory + '_' + element.displayLabel).replace(/ /g, '_').toLowerCase();
template.descriptionElement.id = baseId + '_setting_description';
template.otherOverridesElement.innerHTML = '';
if (element.overriddenScopeList.length) {
const otherOverridesLabel = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
DOM.append(template.otherOverridesElement, $('span', null, `(${otherOverridesLabel}: `));
for (let i = 0; i < element.overriddenScopeList.length; i++) {
let view = DOM.append(template.otherOverridesElement, $('a.modified-scope', null, element.overriddenScopeList[i]));
if (i !== element.overriddenScopeList.length - 1) {
DOM.append(template.otherOverridesElement, $('span', null, ', '));
} else {
DOM.append(template.otherOverridesElement, $('span', null, ')'));
}
DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => {
this._onDidClickOverrideElement.fire({
targetKey: element.setting.key,
scope: element.overriddenScopeList[i]
});
e.preventDefault();
e.stopPropagation();
});
}
}
const onChange = value => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context.valueType });
template.deprecationWarningElement.innerText = element.setting.deprecationMessage || '';
this.renderValue(element, <ISettingItemTemplate>template, onChange);
// Remove tree attributes - sometimes overridden by tree - should be managed there
template.containerElement.parentElement.parentElement.removeAttribute('role');
template.containerElement.parentElement.parentElement.removeAttribute('aria-level');
template.containerElement.parentElement.parentElement.removeAttribute('aria-posinset');
template.containerElement.parentElement.parentElement.removeAttribute('aria-setsize');
}
private renderDescriptionMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: IDisposable[]): HTMLElement {
// Rewrite `#editor.fontSize#` to link format
text = fixSettingLinks(text);
const renderedMarkdown = renderMarkdown({ value: text }, {
actionHandler: {
callback: (content: string) => {
if (startsWith(content, '#')) {
const e: ISettingLinkClickEvent = {
source: element,
targetKey: content.substr(1)
};
this._onDidClickSettingLink.fire(e);
} else {
let uri: URI;
try {
uri = URI.parse(content);
} catch (err) {
// ignore
}
if (uri) {
this._openerService.open(uri).catch(onUnexpectedError);
}
}
},
disposeables
}
});
renderedMarkdown.classList.add('setting-item-description-markdown');
cleanRenderedMarkdown(renderedMarkdown);
return renderedMarkdown;
}
protected abstract renderValue(dataElement: SettingsTreeSettingElement, template: ISettingItemTemplate, onChange: (value: any) => void): void;
protected setElementAriaLabels(dataElement: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate): string {
// Create base Id for element references
const baseId = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_').toLowerCase();
const modifiedText = template.otherOverridesElement.textContent ?
template.otherOverridesElement.textContent : (dataElement.isConfigured ? localize('settings.Modified', ' Modified. ') : '');
let itemElement = null;
// Use '.' as reader pause
let label = dataElement.displayCategory + ' ' + dataElement.displayLabel + '. ';
// Setup and add ARIA attributes
// Create id and label for control/input element - parent is wrapper div
if (templateId === SETTINGS_TEXT_TEMPLATE_ID) {
if (itemElement = (<ISettingTextItemTemplate>template).inputBox.inputElement) {
itemElement.setAttribute('role', 'textbox');
label += modifiedText;
}
} else if (templateId === SETTINGS_NUMBER_TEMPLATE_ID) {
if (itemElement = (<ISettingNumberItemTemplate>template).inputBox.inputElement) {
itemElement.setAttribute('role', 'textbox');
label += ' number. ' + modifiedText;
}
} else if (templateId === SETTINGS_BOOL_TEMPLATE_ID) {
if (itemElement = (<ISettingBoolItemTemplate>template).checkbox.domNode) {
itemElement.setAttribute('role', 'checkbox');
label += modifiedText;
// Add checkbox target to description clickable and able to toggle checkbox
template.descriptionElement.setAttribute('checkbox_label_target_id', baseId + '_setting_item');
}
} else if (templateId === SETTINGS_ENUM_TEMPLATE_ID) {
if (itemElement = template.controlElement.firstElementChild) {
itemElement.setAttribute('role', 'combobox');
label += modifiedText;
}
} else {
// Don't change attributes if we don't know what we areFunctions
return '';
}
// We don't have control element, return empty label
if (!itemElement) {
return '';
}
// Labels will not be read on descendent input elements of the parent treeitem
// unless defined as roles for input items
// voiceover does not seem to use labeledby correctly, set labels directly on input elements
itemElement.id = baseId + '_setting_item';
itemElement.setAttribute('aria-label', label);
itemElement.setAttribute('aria-describedby', baseId + '_setting_description settings_aria_more_actions_shortcut_label');
return label;
}
disposeTemplate(template: IDisposableTemplate): void {
dispose(template.toDispose);
}
}
export class SettingGroupRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, IGroupTitleTemplate> {
templateId = SETTINGS_ELEMENT_TEMPLATE_ID;
renderTemplate(container: HTMLElement): IGroupTitleTemplate {
DOM.addClass(container, 'group-title');
const toDispose: IDisposable[] = [];
const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null });
controlElement.appendChild(checkbox.domNode);
toDispose.push(checkbox);
toDispose.push(checkbox.onChange(() => {
if (template.onChange) {
template.onChange(checkbox.checked);
const template: IGroupTitleTemplate = {
parent: container,
toDispose
};
return template;
}
renderElement(element: ITreeNode<SettingsTreeGroupElement, never>, index: number, templateData: IGroupTitleTemplate): void {
templateData.parent.innerHTML = '';
const labelElement = DOM.append(templateData.parent, $('div.settings-group-title-label'));
labelElement.classList.add(`settings-group-level-${element.element.level}`);
labelElement.textContent = element.element.label;
if (element.element.isFirstGroup) {
labelElement.classList.add('settings-group-first');
}
}
disposeTemplate(templateData: IGroupTitleTemplate): void {
}
}
export class SettingNewExtensionsRenderer implements ITreeRenderer<SettingsTreeNewExtensionsElement, never, ISettingNewExtensionsTemplate> {
templateId = SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;
constructor(
@IThemeService private _themeService: IThemeService,
@ICommandService private _commandService: ICommandService,
) {
}
renderTemplate(container: HTMLElement): ISettingNewExtensionsTemplate {
const toDispose: IDisposable[] = [];
container.classList.add('setting-item-new-extensions');
const button = new Button(container, { title: true, buttonBackground: null, buttonHoverBackground: null });
toDispose.push(button);
toDispose.push(button.onDidClick(() => {
if (template.context) {
this._commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', template.context.extensionIds);
}
}));
button.label = localize('newExtensionsButtonLabel', "Show matching extensions");
button.element.classList.add('settings-new-extensions-button');
toDispose.push(attachButtonStyler(button, this._themeService));
// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety
// Also have to ignore embedded links - too buried to stop propagation
toDispose.push(DOM.addDisposableListener(descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => {
const targetElement = <HTMLElement>e.target;
const targetId = descriptionElement.getAttribute('checkbox_label_target_id');
const template: ISettingNewExtensionsTemplate = {
button,
toDispose
};
// Make sure we are not a link and the target ID matches
// Toggle target checkbox
if (targetElement.tagName.toLowerCase() !== 'a' && targetId === template.checkbox.domNode.id) {
template.checkbox.checked = template.checkbox.checked ? false : true;
template.onChange(checkbox.checked);
}
DOM.EventHelper.stop(e);
return template;
}
renderElement(element: ITreeNode<SettingsTreeNewExtensionsElement, never>, index: number, templateData: ISettingNewExtensionsTemplate): void {
templateData.context = element.element;
}
disposeTemplate(template: IDisposableTemplate): void {
dispose(template.toDispose);
}
}
export class SettingComplexRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingComplexItemTemplate> {
templateId = SETTINGS_COMPLEX_TEMPLATE_ID;
renderTemplate(container: HTMLElement): ISettingComplexItemTemplate {
const common = this.renderCommonTemplate(null, container, 'complex');
const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: null, buttonHoverBackground: null });
common.toDispose.push(openSettingsButton);
common.toDispose.push(openSettingsButton.onDidClick(() => template.onChange(null)));
openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json");
openSettingsButton.element.classList.add('edit-in-settings-button');
common.toDispose.push(attachButtonStyler(openSettingsButton, this._themeService, {
buttonBackground: Color.transparent.toString(),
buttonHoverBackground: Color.transparent.toString(),
buttonForeground: 'foreground'
}));
const template: ISettingComplexItemTemplate = {
...common,
button: openSettingsButton
};
checkbox.domNode.classList.add(SettingsRenderer.CONTROL_CLASS);
const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));
const toolbar = this.renderSettingToolbar(toolbarContainer);
toDispose.push(toolbar);
this.addSettingElementFocusHandler(template);
const template: ISettingBoolItemTemplate = {
toDispose,
return template;
}
containerElement: container,
categoryElement,
labelElement,
controlElement,
checkbox,
descriptionElement,
deprecationWarningElement,
otherOverridesElement,
toolbar
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingComplexItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExcludeItemTemplate, onChange: (value: string) => void): void {
template.onChange = () => this._onDidOpenSettings.fire(dataElement.setting.key);
}
}
export class SettingExcludeRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExcludeItemTemplate> {
templateId = SETTINGS_EXCLUDE_TEMPLATE_ID;
renderTemplate(container: HTMLElement): ISettingExcludeItemTemplate {
const common = this.renderCommonTemplate(null, container, 'exclude');
const excludeWidget = this._instantiationService.createInstance(ExcludeSettingWidget, common.controlElement);
excludeWidget.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
common.toDispose.push(excludeWidget);
const template: ISettingExcludeItemTemplate = {
...common,
excludeWidget
};
this.addSettingElementFocusHandler(template);
// Prevent clicks from being handled by list
toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', e => e.stopPropagation()));
common.toDispose.push(excludeWidget.onDidChangeExclude(e => this.onDidChangeExclude(template, e)));
return template;
}
private onDidChangeExclude(template: ISettingExcludeItemTemplate, e: IExcludeChangeEvent): void {
if (template.context) {
const newValue = { ...template.context.scopeValue };
// first delete the existing entry, if present
if (e.originalPattern) {
if (e.originalPattern in template.context.defaultValue) {
// delete a default by overriding it
newValue[e.originalPattern] = false;
} else {
delete newValue[e.originalPattern];
}
}
// then add the new or updated entry, if present
if (e.pattern) {
if (e.pattern in template.context.defaultValue && !e.sibling) {
// add a default by deleting its override
delete newValue[e.pattern];
} else {
newValue[e.pattern] = e.sibling ? { when: e.sibling } : true;
}
}
const sortKeys = (obj) => {
const keyArray = Object.keys(obj)
.map(key => ({ key, val: obj[key] }))
.sort((a, b) => a.key.localeCompare(b.key));
const retVal = {};
keyArray.forEach(pair => {
retVal[pair.key] = pair.val;
});
return retVal;
};
this._onDidChangeSetting.fire({
key: template.context.setting.key,
value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),
type: template.context.valueType
});
}
}
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingExcludeItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExcludeItemTemplate, onChange: (value: string) => void): void {
const value = getExcludeDisplayValue(dataElement);
template.excludeWidget.setValue(value);
template.context = dataElement;
}
}
export class SettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
templateId = SETTINGS_TEXT_TEMPLATE_ID;
renderTemplate(container: HTMLElement): ISettingTextItemTemplate {
const common = this.renderCommonTemplate(null, container, 'text');
const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message'));
const inputBox = new InputBox(common.controlElement, this._contextViewService);
common.toDispose.push(inputBox);
common.toDispose.push(attachInputBoxStyler(inputBox, this._themeService, {
inputBackground: settingsTextInputBackground,
inputForeground: settingsTextInputForeground,
inputBorder: settingsTextInputBorder
}));
common.toDispose.push(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
}));
common.toDispose.push(inputBox);
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
const template: ISettingTextItemTemplate = {
...common,
inputBox,
validationErrorMessageElement
};
toDispose.push(DOM.addStandardDisposableListener(controlElement, 'keydown', (e: StandardKeyboardEvent) => {
if (e.keyCode === KeyCode.Escape) {
tree.domFocus();
e.browserEvent.stopPropagation();
}
}));
this.addSettingElementFocusHandler(template);
return template;
}
public cancelSuggesters() {
this.contextViewService.hideContextView();
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingTextItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void {
const label = this.setElementAriaLabels(dataElement, SETTINGS_TEXT_TEMPLATE_ID, template);
template.onChange = null;
template.inputBox.value = dataElement.value;
template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(value); };
renderValidations(dataElement, template, true, label);
}
}
export class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {
templateId = SETTINGS_ENUM_TEMPLATE_ID;
private renderSettingEnumTemplate(tree: ITree, container: HTMLElement): ISettingEnumItemTemplate {
const common = this.renderCommonTemplate(tree, container, 'enum');
renderTemplate(container: HTMLElement): ISettingEnumItemTemplate {
const common = this.renderCommonTemplate(null, container, 'enum');
const selectBox = new SelectBox([], undefined, this.contextViewService, undefined, { useCustomDrawn: true });
const selectBox = new SelectBox([], undefined, this._contextViewService, undefined, { useCustomDrawn: true });
common.toDispose.push(selectBox);
common.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService, {
common.toDispose.push(attachSelectBoxStyler(selectBox, this._themeService, {
selectBackground: settingsSelectBackground,
selectForeground: settingsSelectForeground,
selectBorder: settingsSelectBorder,
......@@ -853,7 +795,7 @@ export class SettingsRenderer implements ITreeRenderer {
selectBox.render(common.controlElement);
const selectElement = common.controlElement.querySelector('select');
if (selectElement) {
selectElement.classList.add(SettingsRenderer.CONTROL_CLASS);
selectElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
}
common.toDispose.push(
......@@ -876,85 +818,65 @@ export class SettingsRenderer implements ITreeRenderer {
return template;
}
private renderSettingExcludeTemplate(tree: ITree, container: HTMLElement): ISettingExcludeItemTemplate {
const common = this.renderCommonTemplate(tree, container, 'exclude');
const excludeWidget = this.instantiationService.createInstance(ExcludeSettingWidget, common.controlElement);
excludeWidget.domNode.classList.add(SettingsRenderer.CONTROL_CLASS);
common.toDispose.push(excludeWidget);
const template: ISettingExcludeItemTemplate = {
...common,
excludeWidget
};
this.addSettingElementFocusHandler(template);
common.toDispose.push(excludeWidget.onDidChangeExclude(e => {
if (template.context) {
const newValue = { ...template.context.scopeValue };
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingEnumItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
// first delete the existing entry, if present
if (e.originalPattern) {
if (e.originalPattern in template.context.defaultValue) {
// delete a default by overriding it
newValue[e.originalPattern] = false;
} else {
delete newValue[e.originalPattern];
}
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void {
const enumDescriptions = dataElement.setting.enumDescriptions;
const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown;
// then add the new or updated entry, if present
if (e.pattern) {
if (e.pattern in template.context.defaultValue && !e.sibling) {
// add a default by deleting its override
delete newValue[e.pattern];
} else {
newValue[e.pattern] = e.sibling ? { when: e.sibling } : true;
}
}
let displayOptions = dataElement.setting.enum
.map(String)
.map(escapeInvisibleChars)
.map((data, index) => <ISelectOptionItem>{
text: data,
description: (enumDescriptions && enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index])),
descriptionIsMarkdown: enumDescriptionsAreMarkdown,
decoratorRight: (data === dataElement.defaultValue ? localize('settings.Default', "{0}", 'default') : '')
});
const sortKeys = (obj) => {
const keyArray = Object.keys(obj)
.map(key => ({ key, val: obj[key] }))
.sort((a, b) => a.key.localeCompare(b.key));
template.selectBox.setOptions(displayOptions);
const retVal = {};
keyArray.forEach(pair => {
retVal[pair.key] = pair.val;
});
return retVal;
};
const label = this.setElementAriaLabels(dataElement, SETTINGS_ENUM_TEMPLATE_ID, template);
template.selectBox.setAriaLabel(label);
this._onDidChangeSetting.fire({
key: template.context.setting.key,
value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue),
type: template.context.valueType
});
}
}));
const idx = dataElement.setting.enum.indexOf(dataElement.value);
template.onChange = null;
template.selectBox.select(idx);
template.onChange = idx => onChange(dataElement.setting.enum[idx]);
return template;
template.enumDescriptionElement.innerHTML = '';
}
}
private renderSettingComplexTemplate(tree: ITree, container: HTMLElement): ISettingComplexItemTemplate {
const common = this.renderCommonTemplate(tree, container, 'complex');
export class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingNumberItemTemplate> {
templateId = SETTINGS_NUMBER_TEMPLATE_ID;
const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: null, buttonHoverBackground: null });
common.toDispose.push(openSettingsButton);
common.toDispose.push(openSettingsButton.onDidClick(() => template.onChange(null)));
openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json");
openSettingsButton.element.classList.add('edit-in-settings-button');
renderTemplate(container: HTMLElement): ISettingNumberItemTemplate {
const common = super.renderCommonTemplate(null, container, 'number');
const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message'));
common.toDispose.push(attachButtonStyler(openSettingsButton, this.themeService, {
buttonBackground: Color.transparent.toString(),
buttonHoverBackground: Color.transparent.toString(),
buttonForeground: 'foreground'
const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number' });
common.toDispose.push(inputBox);
common.toDispose.push(attachInputBoxStyler(inputBox, this._themeService, {
inputBackground: settingsNumberInputBackground,
inputForeground: settingsNumberInputForeground,
inputBorder: settingsNumberInputBorder
}));
common.toDispose.push(
inputBox.onDidChange(e => {
if (template.onChange) {
template.onChange(e);
}
}));
common.toDispose.push(inputBox);
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
const template: ISettingComplexItemTemplate = {
const template: ISettingNumberItemTemplate = {
...common,
button: openSettingsButton
inputBox,
validationErrorMessageElement
};
this.addSettingElementFocusHandler(template);
......@@ -962,198 +884,112 @@ export class SettingsRenderer implements ITreeRenderer {
return template;
}
private renderNewExtensionsTemplate(container: HTMLElement): ISettingNewExtensionsTemplate {
const toDispose: IDisposable[] = [];
container.classList.add('setting-item-new-extensions');
const button = new Button(container, { title: true, buttonBackground: null, buttonHoverBackground: null });
toDispose.push(button);
toDispose.push(button.onDidClick(() => {
if (template.context) {
this.commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', template.context.extensionIds);
}
}));
button.label = localize('newExtensionsButtonLabel', "Show matching extensions");
button.element.classList.add('settings-new-extensions-button');
toDispose.push(attachButtonStyler(button, this.themeService));
const template: ISettingNewExtensionsTemplate = {
button,
toDispose
};
// this.addSettingElementFocusHandler(template);
return template;
}
renderElement(tree: ITree, element: SettingsTreeElement, templateId: string, template: any): void {
if (templateId === SETTINGS_GROUP_ELEMENT_TEMPLATE_ID) {
return this.renderGroupElement(<SettingsTreeGroupElement>element, template);
}
if (templateId === SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID) {
return this.renderNewExtensionsElement(<SettingsTreeNewExtensionsElement>element, template);
}
return this.renderSettingElement(tree, <SettingsTreeSettingElement>element, templateId, template);
}
private renderGroupElement(element: SettingsTreeGroupElement, template: IGroupTitleTemplate): void {
template.parent.innerHTML = '';
const labelElement = DOM.append(template.parent, $('div.settings-group-title-label'));
labelElement.classList.add(`settings-group-level-${element.level}`);
labelElement.textContent = (<SettingsTreeGroupElement>element).label;
if (element.isFirstGroup) {
labelElement.classList.add('settings-group-first');
}
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingNumberItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
private renderNewExtensionsElement(element: SettingsTreeNewExtensionsElement, template: ISettingNewExtensionsTemplate): void {
template.context = element;
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingNumberItemTemplate, onChange: (value: number) => void): void {
const numParseFn = (dataElement.valueType === 'integer' || dataElement.valueType === 'nullable-integer')
? parseInt : parseFloat;
public getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement {
const parent = DOM.findParentWithClass(domElement, 'setting-item');
if (parent) {
return parent;
}
const nullNumParseFn = (dataElement.valueType === 'nullable-integer' || dataElement.valueType === 'nullable-number')
? (v => v === '' ? null : numParseFn(v)) : numParseFn;
return null;
}
const label = this.setElementAriaLabels(dataElement, SETTINGS_NUMBER_TEMPLATE_ID, template);
public getDOMElementsForSettingKey(treeContainer: HTMLElement, key: string): NodeListOf<HTMLElement> {
return treeContainer.querySelectorAll(`[${SettingsRenderer.SETTING_KEY_ATTR}="${key}"]`);
}
template.onChange = null;
template.inputBox.value = dataElement.value;
template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(nullNumParseFn(value)); };
public getKeyForDOMElementInSetting(element: HTMLElement): string {
const settingElement = this.getSettingDOMElementForDOMElement(element);
return settingElement && settingElement.getAttribute(SettingsRenderer.SETTING_KEY_ATTR);
renderValidations(dataElement, template, true, label);
}
}
private renderSettingElement(tree: ITree, element: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {
template.context = element;
template.toolbar.context = element;
const setting = element.setting;
DOM.toggleClass(template.containerElement, 'is-configured', element.isConfigured);
DOM.toggleClass(template.containerElement, 'is-expanded', true);
template.containerElement.setAttribute(SettingsRenderer.SETTING_KEY_ATTR, element.setting.key);
const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');
template.categoryElement.textContent = element.displayCategory && (element.displayCategory + ': ');
template.categoryElement.title = titleTooltip;
template.labelElement.textContent = element.displayLabel;
template.labelElement.title = titleTooltip;
export class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingBoolItemTemplate> {
templateId = SETTINGS_BOOL_TEMPLATE_ID;
template.descriptionElement.innerHTML = '';
if (element.setting.descriptionIsMarkdown) {
const renderedDescription = this.renderDescriptionMarkdown(element, element.description, template.toDispose);
template.descriptionElement.appendChild(renderedDescription);
} else {
template.descriptionElement.innerText = element.description;
}
renderTemplate(container: HTMLElement): ISettingBoolItemTemplate {
DOM.addClass(container, 'setting-item');
DOM.addClass(container, 'setting-item-bool');
const baseId = (element.displayCategory + '_' + element.displayLabel).replace(/ /g, '_').toLowerCase();
template.descriptionElement.id = baseId + '_setting_description';
const titleElement = DOM.append(container, $('.setting-item-title'));
const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));
const labelElement = DOM.append(titleElement, $('span.setting-item-label'));
const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides'));
template.otherOverridesElement.innerHTML = '';
const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description'));
const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control'));
const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description'));
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
modifiedIndicatorElement.title = localize('modified', "Modified");
if (element.overriddenScopeList.length) {
const otherOverridesLabel = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
DOM.append(template.otherOverridesElement, $('span', null, `(${otherOverridesLabel}: `));
const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));
for (let i = 0; i < element.overriddenScopeList.length; i++) {
let view = DOM.append(template.otherOverridesElement, $('a.modified-scope', null, element.overriddenScopeList[i]));
const toDispose: IDisposable[] = [];
const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null });
controlElement.appendChild(checkbox.domNode);
toDispose.push(checkbox);
toDispose.push(checkbox.onChange(() => {
if (template.onChange) {
template.onChange(checkbox.checked);
}
}));
if (i !== element.overriddenScopeList.length - 1) {
DOM.append(template.otherOverridesElement, $('span', null, ', '));
} else {
DOM.append(template.otherOverridesElement, $('span', null, ')'));
}
// Need to listen for mouse clicks on description and toggle checkbox - use target ID for safety
// Also have to ignore embedded links - too buried to stop propagation
toDispose.push(DOM.addDisposableListener(descriptionElement, DOM.EventType.MOUSE_DOWN, (e) => {
const targetElement = <HTMLElement>e.target;
const targetId = descriptionElement.getAttribute('checkbox_label_target_id');
DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => {
this._onDidClickOverrideElement.fire({
targetKey: element.setting.key,
scope: element.overriddenScopeList[i]
});
e.preventDefault();
e.stopPropagation();
});
// Make sure we are not a link and the target ID matches
// Toggle target checkbox
if (targetElement.tagName.toLowerCase() !== 'a' && targetId === template.checkbox.domNode.id) {
template.checkbox.checked = template.checkbox.checked ? false : true;
template.onChange(checkbox.checked);
}
DOM.EventHelper.stop(e);
}));
}
this.renderValue(element, templateId, <ISettingItemTemplate>template);
// Remove tree attributes - sometimes overridden by tree - should be managed there
template.containerElement.parentElement.removeAttribute('role');
template.containerElement.parentElement.removeAttribute('aria-level');
template.containerElement.parentElement.removeAttribute('aria-posinset');
template.containerElement.parentElement.removeAttribute('aria-setsize');
}
checkbox.domNode.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
const toolbarContainer = DOM.append(container, $('.setting-toolbar-container'));
const toolbar = this.renderSettingToolbar(toolbarContainer);
toDispose.push(toolbar);
private renderDescriptionMarkdown(element: SettingsTreeSettingElement, text: string, disposeables: IDisposable[]): HTMLElement {
// Rewrite `#editor.fontSize#` to link format
text = fixSettingLinks(text);
const template: ISettingBoolItemTemplate = {
toDispose,
const renderedMarkdown = renderMarkdown({ value: text }, {
actionHandler: {
callback: (content: string) => {
if (startsWith(content, '#')) {
const e: ISettingLinkClickEvent = {
source: element,
targetKey: content.substr(1)
};
this._onDidClickSettingLink.fire(e);
} else {
let uri: URI;
try {
uri = URI.parse(content);
} catch (err) {
// ignore
}
if (uri) {
this.openerService.open(uri).catch(onUnexpectedError);
}
}
},
disposeables
containerElement: container,
categoryElement,
labelElement,
controlElement,
checkbox,
descriptionElement,
deprecationWarningElement,
otherOverridesElement,
toolbar
};
this.addSettingElementFocusHandler(template);
// Prevent clicks from being handled by list
toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation()));
toDispose.push(DOM.addStandardDisposableListener(controlElement, 'keydown', (e: StandardKeyboardEvent) => {
if (e.keyCode === KeyCode.Escape) {
e.browserEvent.stopPropagation();
}
});
}));
renderedMarkdown.classList.add('setting-item-description-markdown');
cleanRenderedMarkdown(renderedMarkdown);
return renderedMarkdown;
return template;
}
private renderValue(element: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate | ISettingBoolItemTemplate): void {
const onChange = value => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context.valueType });
template.deprecationWarningElement.innerText = element.setting.deprecationMessage || '';
if (templateId === SETTINGS_ENUM_TEMPLATE_ID) {
this.renderEnum(element, <ISettingEnumItemTemplate>template, onChange);
} else if (templateId === SETTINGS_TEXT_TEMPLATE_ID) {
this.renderText(element, <ISettingTextItemTemplate>template, onChange);
} else if (templateId === SETTINGS_NUMBER_TEMPLATE_ID) {
this.renderNumber(element, <ISettingTextItemTemplate>template, onChange);
} else if (templateId === SETTINGS_BOOL_TEMPLATE_ID) {
this.renderBool(element, <ISettingBoolItemTemplate>template, onChange);
} else if (templateId === SETTINGS_EXCLUDE_TEMPLATE_ID) {
this.renderExcludeSetting(element, <ISettingExcludeItemTemplate>template);
} else if (templateId === SETTINGS_COMPLEX_TEMPLATE_ID) {
this.renderComplexSetting(element, <ISettingComplexItemTemplate>template);
}
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingBoolItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
private renderBool(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void {
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void {
template.onChange = null;
template.checkbox.checked = dataElement.value;
template.onChange = onChange;
......@@ -1161,132 +997,99 @@ export class SettingsRenderer implements ITreeRenderer {
// Setup and add ARIA attributes
this.setElementAriaLabels(dataElement, SETTINGS_BOOL_TEMPLATE_ID, template);
}
}
private renderEnum(dataElement: SettingsTreeSettingElement, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void {
const enumDescriptions = dataElement.setting.enumDescriptions;
const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown;
let displayOptions = dataElement.setting.enum
.map(String)
.map(escapeInvisibleChars)
.map((data, index) => <ISelectOptionItem>{
text: data,
description: (enumDescriptions && enumDescriptions[index] && (enumDescriptionsAreMarkdown ? fixSettingLinks(enumDescriptions[index], false) : enumDescriptions[index])),
descriptionIsMarkdown: enumDescriptionsAreMarkdown,
decoratorRight: (data === dataElement.defaultValue ? localize('settings.Default', "{0}", 'default') : '')
});
template.selectBox.setOptions(displayOptions);
const label = this.setElementAriaLabels(dataElement, SETTINGS_ENUM_TEMPLATE_ID, template);
template.selectBox.setAriaLabel(label);
const idx = dataElement.setting.enum.indexOf(dataElement.value);
template.onChange = null;
template.selectBox.select(idx);
template.onChange = idx => onChange(dataElement.setting.enum[idx]);
template.enumDescriptionElement.innerHTML = '';
}
export class SettingTreeRenderers {
public readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent>;
private renderText(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void {
private readonly _onDidChangeSetting = new Emitter<ISettingChangeEvent>();
public readonly onDidChangeSetting: Event<ISettingChangeEvent>;
const label = this.setElementAriaLabels(dataElement, SETTINGS_TEXT_TEMPLATE_ID, template);
public readonly onDidOpenSettings: Event<string>;
template.onChange = null;
template.inputBox.value = dataElement.value;
template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(value); };
public readonly onDidClickSettingLink: Event<ISettingLinkClickEvent>;
renderValidations(dataElement, template, true, label);
}
public readonly onDidFocusSetting: Event<SettingsTreeSettingElement>;
private renderNumber(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: number) => void): void {
const numParseFn = (dataElement.valueType === 'integer' || dataElement.valueType === 'nullable-integer')
? parseInt : parseFloat;
public readonly allRenderers: ITreeRenderer<SettingsTreeElement, never, any>[];
const nullNumParseFn = (dataElement.valueType === 'nullable-integer' || dataElement.valueType === 'nullable-number')
? (v => v === '' ? null : numParseFn(v)) : numParseFn;
private readonly settingActions: IAction[];
const label = this.setElementAriaLabels(dataElement, SETTINGS_NUMBER_TEMPLATE_ID, template);
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IContextViewService private readonly _contextViewService: IContextViewService
) {
this.settingActions = [
new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, (context: SettingsTreeSettingElement) => {
if (context) {
this._onDidChangeSetting.fire({ key: context.setting.key, value: undefined, type: context.setting.type as SettingValueType });
}
template.onChange = null;
template.inputBox.value = dataElement.value;
template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(nullNumParseFn(value)); };
return Promise.resolve(null);
}),
new Separator(),
this._instantiationService.createInstance(CopySettingIdAction),
this._instantiationService.createInstance(CopySettingAsJSONAction),
];
renderValidations(dataElement, template, true, label);
}
const settingRenderers = [
this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions),
this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions),
this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions),
this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions),
this._instantiationService.createInstance(SettingTextRenderer, this.settingActions),
this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions),
this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions),
];
private renderExcludeSetting(dataElement: SettingsTreeSettingElement, template: ISettingExcludeItemTemplate): void {
const value = getExcludeDisplayValue(dataElement);
template.excludeWidget.setValue(value);
template.context = dataElement;
this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement));
this.onDidChangeSetting = Event.any(
...settingRenderers.map(r => r.onDidChangeSetting),
this._onDidChangeSetting.event
);
this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings));
this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink));
this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting));
this.allRenderers = [
...settingRenderers,
this._instantiationService.createInstance(SettingGroupRenderer),
this._instantiationService.createInstance(SettingNewExtensionsRenderer),
];
}
private renderComplexSetting(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate): void {
template.onChange = () => this._onDidOpenSettings.fire(dataElement.setting.key);
public cancelSuggesters() {
this._contextViewService.hideContextView();
}
private setElementAriaLabels(dataElement: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate): string {
// Create base Id for element references
const baseId = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_').toLowerCase();
const modifiedText = template.otherOverridesElement.textContent ?
template.otherOverridesElement.textContent : (dataElement.isConfigured ? localize('settings.Modified', ' Modified. ') : '');
let itemElement = null;
// Use '.' as reader pause
let label = dataElement.displayCategory + ' ' + dataElement.displayLabel + '. ';
// Setup and add ARIA attributes
// Create id and label for control/input element - parent is wrapper div
if (templateId === SETTINGS_TEXT_TEMPLATE_ID) {
if (itemElement = (<ISettingTextItemTemplate>template).inputBox.inputElement) {
itemElement.setAttribute('role', 'textbox');
label += modifiedText;
}
} else if (templateId === SETTINGS_NUMBER_TEMPLATE_ID) {
if (itemElement = (<ISettingNumberItemTemplate>template).inputBox.inputElement) {
itemElement.setAttribute('role', 'textbox');
label += ' number. ' + modifiedText;
}
} else if (templateId === SETTINGS_BOOL_TEMPLATE_ID) {
if (itemElement = (<ISettingBoolItemTemplate>template).checkbox.domNode) {
itemElement.setAttribute('role', 'checkbox');
label += modifiedText;
// Add checkbox target to description clickable and able to toggle checkbox
template.descriptionElement.setAttribute('checkbox_label_target_id', baseId + '_setting_item');
}
} else if (templateId === SETTINGS_ENUM_TEMPLATE_ID) {
if (itemElement = template.controlElement.firstElementChild) {
itemElement.setAttribute('role', 'combobox');
label += modifiedText;
}
} else {
// Don't change attributes if we don't know what we areFunctions
return '';
showContextMenu(element: SettingsTreeSettingElement, settingDOMElement: HTMLElement): void {
const toolbarElement: HTMLElement = settingDOMElement.querySelector('.toolbar-toggle-more');
if (toolbarElement) {
this._contextMenuService.showContextMenu({
getActions: () => this.settingActions,
getAnchor: () => toolbarElement,
getActionsContext: () => element
});
}
}
// We don't have control element, return empty label
if (!itemElement) {
return '';
getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement {
const parent = DOM.findParentWithClass(domElement, 'setting-item');
if (parent) {
return parent;
}
// Labels will not be read on descendent input elements of the parent treeitem
// unless defined as roles for input items
// voiceover does not seem to use labeledby correctly, set labels directly on input elements
itemElement.id = baseId + '_setting_item';
itemElement.setAttribute('aria-label', label);
itemElement.setAttribute('aria-describedby', baseId + '_setting_description settings_aria_more_actions_shortcut_label');
return null;
}
return label;
getDOMElementsForSettingKey(treeContainer: HTMLElement, key: string): NodeListOf<HTMLElement> {
return treeContainer.querySelectorAll(`[${AbstractSettingRenderer.SETTING_KEY_ATTR}="${key}"]`);
}
disposeTemplate(tree: ITree, templateId: string, template: IDisposableTemplate): void {
dispose(template.toDispose);
getKeyForDOMElementInSetting(element: HTMLElement): string {
const settingElement = this.getSettingDOMElementForDOMElement(element);
return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR);
}
}
......@@ -1395,22 +1198,62 @@ export class SettingsTreeFilter implements IFilter {
}
}
export class SettingsTreeController extends WorkbenchTreeController {
export class SettingsTreeFilter2 implements ITreeFilter<SettingsTreeElement> {
constructor(
@IConfigurationService configurationService: IConfigurationService
) {
super({}, configurationService);
}
private viewState: ISettingsEditorViewState,
) { }
filter(element: SettingsTreeElement, parentVisibility: TreeVisibility): TreeFilterResult<void> {
// Filter during search
if (this.viewState.filterToCategory && element instanceof SettingsTreeSettingElement) {
if (!this.settingContainedInGroup(element.setting, this.viewState.filterToCategory)) {
return false;
}
}
// Non-user scope selected
if (element instanceof SettingsTreeSettingElement && this.viewState.settingsTarget !== ConfigurationTarget.USER) {
if (!element.matchesScope(this.viewState.settingsTarget)) {
return false;
}
}
// @modified or tag
if (element instanceof SettingsTreeSettingElement && this.viewState.tagFilters) {
if (!element.matchesAllTags(this.viewState.tagFilters)) {
return false;
}
}
// Group with no visible children
if (element instanceof SettingsTreeGroupElement) {
if (typeof element.count === 'number') {
return element.count > 0;
}
protected onLeftClick(tree: ITree, element: any, eventish: IMouseEvent, origin?: string): boolean {
const isLink = eventish.target.tagName.toLowerCase() === 'a' ||
eventish.target.parentElement.tagName.toLowerCase() === 'a'; // <code> inside <a>
return TreeVisibility.Recurse;
}
if (isLink && (DOM.findParentWithClass(eventish.target, 'setting-item-description-markdown', tree.getHTMLElement()) || DOM.findParentWithClass(eventish.target, 'select-box-description-markdown'))) {
return true;
// Filtered "new extensions" button
if (element instanceof SettingsTreeNewExtensionsElement) {
if ((this.viewState.tagFilters && this.viewState.tagFilters.size) || this.viewState.filterToCategory) {
return false;
}
}
return false;
return true;
}
private settingContainedInGroup(setting: ISetting, group: SettingsTreeGroupElement): boolean {
return group.children.some(child => {
if (child instanceof SettingsTreeGroupElement) {
return this.settingContainedInGroup(setting, child);
} else if (child instanceof SettingsTreeSettingElement) {
return child.setting.key === setting.key;
} else {
return false;
}
});
}
}
......@@ -1435,112 +1278,96 @@ export class SettingsAccessibilityProvider implements IAccessibilityProvider {
}
}
class NonExpandableOrSelectableTree extends Tree {
expand(): Promise<any> {
return Promise.resolve(null);
}
collapse(): Promise<any> {
return Promise.resolve(null);
}
public setFocus(element?: any, eventPayload?: any): void {
return;
}
class SettingsTreeDelegate implements IListVirtualDelegate<SettingsTreeGroupChild> {
getHeight(element: SettingsTreeElement): number {
if (element instanceof SettingsTreeGroupElement) {
if (element.isFirstGroup) {
return 31;
}
public focusNext(count?: number, eventPayload?: any): void {
return;
}
return 40 + (7 * element.level);
}
public focusPrevious(count?: number, eventPayload?: any): void {
return;
return 78;
}
public focusParent(eventPayload?: any): void {
return;
}
getTemplateId(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): string {
if (element instanceof SettingsTreeGroupElement) {
return SETTINGS_ELEMENT_TEMPLATE_ID;
}
public focusFirstChild(eventPayload?: any): void {
return;
}
if (element instanceof SettingsTreeSettingElement) {
if (element.valueType === 'boolean') {
return SETTINGS_BOOL_TEMPLATE_ID;
}
public focusFirst(eventPayload?: any, from?: any): void {
return;
}
if (element.valueType === 'integer' || element.valueType === 'number' || element.valueType === 'nullable-integer' || element.valueType === 'nullable-number') {
return SETTINGS_NUMBER_TEMPLATE_ID;
}
public focusNth(index: number, eventPayload?: any): void {
return;
}
if (element.valueType === 'string') {
return SETTINGS_TEXT_TEMPLATE_ID;
}
public focusLast(eventPayload?: any, from?: any): void {
return;
}
if (element.valueType === 'enum') {
return SETTINGS_ENUM_TEMPLATE_ID;
}
public focusNextPage(eventPayload?: any): void {
return;
}
if (element.valueType === 'exclude') {
return SETTINGS_EXCLUDE_TEMPLATE_ID;
}
public focusPreviousPage(eventPayload?: any): void {
return;
}
return SETTINGS_COMPLEX_TEMPLATE_ID;
}
public select(element: any, eventPayload?: any): void {
return;
}
if (element instanceof SettingsTreeNewExtensionsElement) {
return SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID;
}
public selectRange(fromElement: any, toElement: any, eventPayload?: any): void {
return;
throw new Error('unknown element type: ' + element);
}
public selectAll(elements: any[], eventPayload?: any): void {
return;
hasDynamicHeight(element: SettingsTreeGroupElement | SettingsTreeSettingElement | SettingsTreeNewExtensionsElement): boolean {
return !(element instanceof SettingsTreeGroupElement);
}
}
public setSelection(elements: any[], eventPayload?: any): void {
return;
class NonCollapsibleObjectTreeModel<T> extends ObjectTreeModel<T> {
isCollapsible(element: T): boolean {
return false;
}
public toggleSelection(element: any, eventPayload?: any): void {
return;
setCollapsed(element: T, collapsed?: boolean, recursive?: boolean): boolean {
return false;
}
}
export class SettingsTree extends NonExpandableOrSelectableTree {
export class SettingsTree extends ObjectTree<SettingsTreeElement> {
protected disposables: IDisposable[];
constructor(
container: HTMLElement,
viewState: ISettingsEditorViewState,
configuration: Partial<ITreeConfiguration>,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService
renderers: ITreeRenderer<any, void, any>[],
@IThemeService themeService: IThemeService
) {
const treeClass = 'settings-editor-tree';
const controller = instantiationService.createInstance(SettingsTreeController);
const fullConfiguration = <ITreeConfiguration>{
controller,
accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider),
filter: instantiationService.createInstance(SettingsTreeFilter, viewState),
styler: new DefaultTreestyler(DOM.createStyleSheet(container), treeClass),
...configuration
};
const options = {
ariaLabel: localize('treeAriaLabel', "Settings"),
showLoading: false,
indentPixels: 0,
twistiePixels: 20, // Actually for gear button
};
super(container,
fullConfiguration,
options);
new SettingsTreeDelegate(),
renderers,
{
supportDynamicHeights: true,
ariaLabel: localize('treeAriaLabel', "Settings"),
identityProvider: {
getId(e) {
return e.id;
}
},
filter: new SettingsTreeFilter2(viewState)
});
this.disposables = [];
this.disposables.push(controller);
this.disposables.push(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const activeBorderColor = theme.getColor(focusBorder);
if (activeBorderColor) {
......@@ -1609,6 +1436,10 @@ export class SettingsTree extends NonExpandableOrSelectableTree {
}));
}
protected createModel(view: ISpliceable<ITreeNode<SettingsTreeGroupChild>>, options: IObjectTreeOptions<SettingsTreeGroupChild>): ITreeModel<SettingsTreeGroupChild | null, void, SettingsTreeGroupChild | null> {
return new NonCollapsibleObjectTreeModel<SettingsTreeGroupChild>(view, options);
}
public dispose(): void {
this.disposables = dispose(this.disposables);
}
......
......@@ -191,7 +191,7 @@ export class ExcludeSettingListModel {
}
}
interface IExcludeChangeEvent {
export interface IExcludeChangeEvent {
originalPattern: string;
pattern?: string;
sibling?: string;
......
......@@ -113,30 +113,35 @@
text-decoration: underline;
}
.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-tree-wrapper,
.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-tree-wrapper {
.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-rows,
.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-rows {
margin-left: 0px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-wrapper {
.settings-editor > .settings-body .settings-tree-container .monaco-list-rows {
max-width: 1000px;
margin: auto;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-wrapper .monaco-tree-rows {
.settings-editor > .settings-body .settings-tree-container .monaco-list-row {
line-height: 1.4em !important; /* TODO */
padding-left: 208px;
padding-right: 24px;
box-sizing: border-box;
/* box-sizing: border-box; */
}
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-row {
position: relative;
}
.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-tree-wrapper .monaco-tree-rows,
.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-tree-wrapper .monaco-tree-rows {
.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-row,
.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-row {
/* 3 margin + 20 padding + 2 border */
width: calc(100% - 25px);
padding-left: 25px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-row > .content::before {
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-twistie {
/* Hide twisties */
display: none !important;
}
......@@ -162,10 +167,10 @@
width: 26px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-row .mouseover .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-tree-row .setting-item.focused .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-tree-row .setting-toolbar-container:hover > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-tree-row .setting-toolbar-container > .monaco-toolbar .active .toolbar-toggle-more {
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item.focused .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .toolbar-toggle-more,
.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .toolbar-toggle-more {
opacity: 1;
}
......@@ -261,7 +266,7 @@
padding-left: 31px;
}
.settings-editor > .settings-body .settings-tree-container .monaco-tree-wrapper,
.settings-editor > .settings-body .settings-tree-container .monaco-list-rows,
.settings-editor > .settings-body .settings-toc-wrapper {
height: 100%;
max-width: 1000px;
......@@ -273,7 +278,7 @@
margin-left: 0px;
}
.settings-editor > .settings-body > .settings-tree-container .monaco-tree-row {
.settings-editor > .settings-body > .settings-tree-container .monaco-list-row {
overflow: visible;
/* so validation messages dont get clipped */
cursor: default;
......@@ -408,7 +413,7 @@
visibility: hidden;
}
.settings-editor > .settings-body .settings-tree-container .setting-measure-container .monaco-tree-row {
.settings-editor > .settings-body .settings-tree-container .setting-measure-container .monaco-list-row {
padding-left: 20px;
}
......@@ -426,6 +431,10 @@
display: block;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-bool {
padding-bottom: 26px;
}
.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-value-description {
display: flex;
cursor: pointer;
......
......@@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
import * as arrays from 'vs/base/common/arrays';
import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import * as collections from 'vs/base/common/collections';
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { Iterator } from 'vs/base/common/iterator';
import { isArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { collapseAll, expandAll } from 'vs/base/parts/tree/browser/treeUtils';
import 'vs/css!./media/settingsEditor2';
import { localize } from 'vs/nls';
......@@ -32,8 +33,8 @@ import { IEditor, IEditorMemento } from 'vs/workbench/common/editor';
import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput';
import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout';
import { ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveExtensionsSettings, resolveSettingsTree, SettingsDataSource, SettingsRenderer, SettingsTree, SimplePagedDataSource } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels';
import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveExtensionsSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel } from 'vs/workbench/parts/preferences/browser/settingsTreeModels';
import { settingsTextInputBorder } from 'vs/workbench/parts/preferences/browser/settingsWidgets';
import { TOCRenderer, TOCTree, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/parts/preferences/common/preferences';
......@@ -42,6 +43,19 @@ import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEdit
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
function createGroupIterator(group: SettingsTreeGroupElement): Iterator<ITreeElement<SettingsTreeGroupChild>> {
const groupsIt = Iterator.fromArray(group.children);
return Iterator.map(groupsIt, g => {
return {
element: g,
children: g instanceof SettingsTreeGroupElement ?
createGroupIterator(g) :
undefined
};
});
}
const $ = DOM.$;
const SETTINGS_EDITOR_STATE_KEY = 'settingsEditorState';
......@@ -76,9 +90,8 @@ export class SettingsEditor2 extends BaseEditor {
private settingsTargetsWidget: SettingsTargetsWidget;
private settingsTreeContainer: HTMLElement;
private settingsTree: Tree;
private settingsTreeRenderer: SettingsRenderer;
private settingsTreeDataSource: SimplePagedDataSource;
private settingsTree: SettingsTree;
private settingRenderers: SettingTreeRenderers;
private tocTreeModel: TOCTreeModel;
private settingsTreeModel: SettingsTreeModel;
private noResultsMessage: HTMLElement;
......@@ -259,8 +272,8 @@ export class SettingsEditor2 extends BaseEditor {
}
layout(dimension: DOM.Dimension): void {
const firstEl = this.settingsTree.getFirstVisibleElement();
const firstElTop = this.settingsTree.getRelativeTop(firstEl);
// const firstEl = this.settingsTree.getFirstVisibleElement();
// const firstElTop = this.settingsTree.getRelativeTop(firstEl);
this.layoutTrees(dimension);
......@@ -276,7 +289,7 @@ export class SettingsEditor2 extends BaseEditor {
this.lastLayedoutWidth = dimension.width;
this.delayRefreshOnLayout.trigger(() => {
this.renderTree(undefined, true).then(() => {
this.settingsTree.reveal(firstEl, firstElTop);
// this.settingsTree.reveal(firstEl, firstElTop);
});
});
}
......@@ -284,9 +297,9 @@ export class SettingsEditor2 extends BaseEditor {
focus(): void {
if (this.lastFocusedSettingElement) {
const elements = this.settingsTreeRenderer.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), this.lastFocusedSettingElement);
const elements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), this.lastFocusedSettingElement);
if (elements.length) {
const control = elements[0].querySelector(SettingsRenderer.CONTROL_SELECTOR);
const control = elements[0].querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
if (control) {
(<HTMLElement>control).focus();
return;
......@@ -307,26 +320,26 @@ export class SettingsEditor2 extends BaseEditor {
}
}
const firstFocusable = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR);
const firstFocusable = this.settingsTree.getHTMLElement().querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
if (firstFocusable) {
(<HTMLElement>firstFocusable).focus();
}
}
showContextMenu(): void {
const settingDOMElement = this.settingsTreeRenderer.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree());
const settingDOMElement = this.settingRenderers.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree());
if (!settingDOMElement) {
return;
}
const focusedKey = this.settingsTreeRenderer.getKeyForDOMElementInSetting(settingDOMElement);
const focusedKey = this.settingRenderers.getKeyForDOMElementInSetting(settingDOMElement);
if (!focusedKey) {
return;
}
const elements = this.currentSettingsModel.getElementsByName(focusedKey);
if (elements && elements[0]) {
this.settingsTreeRenderer.showContextMenu(elements[0], settingDOMElement);
this.settingRenderers.showContextMenu(elements[0], settingDOMElement);
}
}
......@@ -414,9 +427,9 @@ export class SettingsEditor2 extends BaseEditor {
this.settingsTree.reveal(elements[0], sourceTop);
const domElements = this.settingsTreeRenderer.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), evt.targetKey);
const domElements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), evt.targetKey);
if (domElements && domElements[0]) {
const control = domElements[0].querySelector(SettingsRenderer.CONTROL_SELECTOR);
const control = domElements[0].querySelector(AbstractSettingRenderer.CONTROL_SELECTOR);
if (control) {
(<HTMLElement>control).focus();
}
......@@ -483,57 +496,57 @@ export class SettingsEditor2 extends BaseEditor {
this.createTOC(bodyContainer);
this.createFocusSink(
bodyContainer,
e => {
if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
if (this.settingsTree.getScrollPosition() > 0) {
const firstElement = this.settingsTree.getFirstVisibleElement();
this.settingsTree.reveal(firstElement, 0.1);
return true;
}
} else {
const firstControl = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR);
if (firstControl) {
(<HTMLElement>firstControl).focus();
}
}
return false;
},
'settings list focus helper');
// this.createFocusSink(
// bodyContainer,
// e => {
// if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
// if (this.settingsTree.getScrollPosition() > 0) {
// const firstElement = this.settingsTree.getFirstVisibleElement();
// this.settingsTree.reveal(firstElement, 0.1);
// return true;
// }
// } else {
// const firstControl = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR);
// if (firstControl) {
// (<HTMLElement>firstControl).focus();
// }
// }
// return false;
// },
// 'settings list focus helper');
this.createSettingsTree(bodyContainer);
this.createFocusSink(
bodyContainer,
e => {
if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
if (this.settingsTree.getScrollPosition() < 1) {
const lastElement = this.settingsTree.getLastVisibleElement();
this.settingsTree.reveal(lastElement, 0.9);
return true;
}
}
return false;
},
'settings list focus helper'
);
// this.createFocusSink(
// bodyContainer,
// e => {
// if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) {
// if (this.settingsTree.getScrollPosition() < 1) {
// const lastElement = this.settingsTree.getLastVisibleElement();
// this.settingsTree.reveal(lastElement, 0.9);
// return true;
// }
// }
// return false;
// },
// 'settings list focus helper'
// );
}
private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement {
const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink'));
listFocusSink.setAttribute('aria-label', label);
listFocusSink.tabIndex = 0;
this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => {
if (e.relatedTarget && callback(e)) {
e.relatedTarget.focus();
}
}));
// private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement {
// const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink'));
// listFocusSink.setAttribute('aria-label', label);
// listFocusSink.tabIndex = 0;
// this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => {
// if (e.relatedTarget && callback(e)) {
// e.relatedTarget.focus();
// }
// }));
return listFocusSink;
}
// return listFocusSink;
// }
private createTOC(parent: HTMLElement): void {
this.tocTreeModel = new TOCTreeModel(this.viewState);
......@@ -553,15 +566,9 @@ export class SettingsEditor2 extends BaseEditor {
if (this.searchResultModel) {
this.viewState.filterToCategory = element;
this.renderTree();
}
if (element && (!e.payload || !e.payload.fromScroll)) {
let refreshP: Promise<void> = Promise.resolve(null);
if (this.settingsTreeDataSource.pageTo(element.index, true)) {
refreshP = this.renderTree();
}
refreshP.then(() => this.settingsTree.reveal(element, 0));
this.settingsTree.scrollTop = 0;
} else if (element && (!e.payload || !e.payload.fromScroll)) {
this.settingsTree.reveal(element, 0);
}
}));
......@@ -585,17 +592,17 @@ export class SettingsEditor2 extends BaseEditor {
labelDiv.id = 'settings_aria_more_actions_shortcut_label';
labelDiv.setAttribute('aria-label', '');
this.settingsTreeRenderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer);
this._register(this.settingsTreeRenderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type)));
this._register(this.settingsTreeRenderer.onDidOpenSettings(settingKey => {
this.settingRenderers = this.instantiationService.createInstance(SettingTreeRenderers);
this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type)));
this._register(this.settingRenderers.onDidOpenSettings(settingKey => {
this.openSettingsFile(settingKey);
}));
this._register(this.settingsTreeRenderer.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName)));
this._register(this.settingsTreeRenderer.onDidFocusSetting(element => {
this._register(this.settingRenderers.onDidClickSettingLink(settingName => this.onDidClickSetting(settingName)));
this._register(this.settingRenderers.onDidFocusSetting(element => {
this.lastFocusedSettingElement = element.setting.key;
this.settingsTree.reveal(element);
}));
this._register(this.settingsTreeRenderer.onDidClickOverrideElement((element: ISettingOverrideClickEvent) => {
this._register(this.settingRenderers.onDidClickOverrideElement((element: ISettingOverrideClickEvent) => {
if (ConfigurationTargetToString(ConfigurationTarget.WORKSPACE) === element.scope.toUpperCase()) {
this.settingsTargetsWidget.updateTarget(ConfigurationTarget.WORKSPACE);
} else if (ConfigurationTargetToString(ConfigurationTarget.USER) === element.scope.toUpperCase()) {
......@@ -605,25 +612,19 @@ export class SettingsEditor2 extends BaseEditor {
this.searchWidget.setValue(element.targetKey);
}));
this.settingsTreeDataSource = this.instantiationService.createInstance(SimplePagedDataSource,
this.instantiationService.createInstance(SettingsDataSource, this.viewState));
this.settingsTree = this._register(this.instantiationService.createInstance(SettingsTree,
this.settingsTreeContainer,
this.viewState,
{
renderer: this.settingsTreeRenderer,
dataSource: this.settingsTreeDataSource
}));
this.settingRenderers.allRenderers));
this.settingsTree.getHTMLElement().attributes.removeNamedItem('tabindex');
// Have to redefine role of the tree widget to form for input elements
// TODO:CDL make this an option for tree
this.settingsTree.getHTMLElement().setAttribute('role', 'form');
this._register(this.settingsTree.onDidScroll(() => {
this.updateTreeScrollSync();
}));
// this._register(this.settingsTree.onDidScroll(() => {
// this.updateTreeScrollSync();
// }));
}
private notifyNoSaveNeeded() {
......@@ -649,7 +650,7 @@ export class SettingsEditor2 extends BaseEditor {
}
private updateTreeScrollSync(): void {
this.settingsTreeRenderer.cancelSuggesters();
this.settingRenderers.cancelSuggesters();
if (this.searchResultModel) {
return;
}
......@@ -657,36 +658,38 @@ export class SettingsEditor2 extends BaseEditor {
if (!this.tocTree.getInput()) {
return;
}
this.updateTreePagingByScroll();
const elementToSync = this.settingsTree.getFirstVisibleElement();
const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent :
elementToSync instanceof SettingsTreeGroupElement ? elementToSync :
null;
// this.updateTreePagingByScroll();
const element = this.tocTreeModel.children[0];
// const elementToSync = this.settingsTree.getFirstVisibleElement();
// const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent :
// elementToSync instanceof SettingsTreeGroupElement ? elementToSync :
// null;
if (element && this.tocTree.getSelection()[0] !== element) {
this.tocTree.reveal(element);
const elementTop = this.tocTree.getRelativeTop(element);
collapseAll(this.tocTree, element);
if (elementTop < 0 || elementTop > 1) {
this.tocTree.reveal(element);
} else {
this.tocTree.reveal(element, elementTop);
}
// this.tocTree.reveal(element);
// const elementTop = this.tocTree.getRelativeTop(element);
// collapseAll(this.tocTree, element);
// if (elementTop < 0 || elementTop > 1) {
// this.tocTree.reveal(element);
// } else {
// this.tocTree.reveal(element, elementTop);
// }
this.tocTree.expand(element);
// this.tocTree.expand(element);
this.tocTree.setSelection([element]);
this.tocTree.setFocus(element, { fromScroll: true });
}
}
private updateTreePagingByScroll(): void {
const lastVisibleElement = this.settingsTree.getLastVisibleElement();
if (lastVisibleElement && this.settingsTreeDataSource.pageTo(lastVisibleElement.index)) {
this.renderTree();
}
}
// private updateTreePagingByScroll(): void {
// const lastVisibleElement = this.settingsTree.getLastVisibleElement();
// if (lastVisibleElement && this.settingsTreeDataSource.pageTo(lastVisibleElement.index)) {
// this.renderTree();
// }
// }
private updateChangedSetting(key: string, value: any): Promise<void> {
// ConfigurationService displays the error if this fails.
......@@ -800,12 +803,6 @@ export class SettingsEditor2 extends BaseEditor {
if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') {
DOM.toggleClass(this.rootElement, 'no-toc-search', !!this.searchResultModel);
}
if (this.searchResultModel) {
this.settingsTreeDataSource.pageTo(Number.MAX_VALUE);
} else {
this.settingsTreeDataSource.reset();
}
}
private scheduleRefresh(element: HTMLElement, key = ''): void {
......@@ -870,7 +867,7 @@ export class SettingsEditor2 extends BaseEditor {
} else {
this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState);
this.settingsTreeModel.update(resolvedSettingsRoot);
this.settingsTree.setInput(this.settingsTreeModel.root);
this.refreshTree();
this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement;
if (this.tocTree.getInput()) {
......@@ -914,11 +911,11 @@ export class SettingsEditor2 extends BaseEditor {
}
// If a setting control is currently focused, schedule a refresh for later
const focusedSetting = this.settingsTreeRenderer.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree());
const focusedSetting = this.settingRenderers.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree());
if (focusedSetting && !force) {
// If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting
if (key) {
const focusedKey = focusedSetting.getAttribute(SettingsRenderer.SETTING_KEY_ATTR);
const focusedKey = focusedSetting.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR);
if (focusedKey === key &&
!DOM.hasClass(focusedSetting, 'setting-item-exclude')) { // update `exclude`s live, as they have a separate "submit edit" step built in before this
......@@ -932,31 +929,31 @@ export class SettingsEditor2 extends BaseEditor {
}
}
let refreshP: Promise<any>;
if (key) {
const elements = this.currentSettingsModel.getElementsByName(key);
if (elements && elements.length) {
// TODO https://github.com/Microsoft/vscode/issues/57360
// refreshP = Promise.all(elements.map(e => this.settingsTree.refresh(e)));
refreshP = this.settingsTree.refresh();
this.refreshTree();
} else {
// Refresh requested for a key that we don't know about
return Promise.resolve(null);
}
} else {
refreshP = this.settingsTree.refresh();
this.refreshTree();
}
return refreshP.then(() => {
this.tocTreeModel.update();
return this.tocTree.refresh();
}).then(() => { });
this.tocTreeModel.update();
return this.tocTree.refresh();
}
private refreshTree(): void {
this.settingsTree.setChildren(null, createGroupIterator(this.currentSettingsModel.root));
}
private updateModifiedLabelForKey(key: string): void {
const dataElements = this.currentSettingsModel.getElementsByName(key);
const isModified = dataElements && dataElements[0] && dataElements[0].isConfigured; // all elements are either configured or not
const elements = this.settingsTreeRenderer.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), key);
const elements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), key);
if (elements && elements[0]) {
DOM.toggleClass(elements[0], 'is-configured', isModified);
}
......@@ -1012,15 +1009,17 @@ export class SettingsEditor2 extends BaseEditor {
// Added a filter model
this.tocTree.setSelection([]);
expandAll(this.tocTree);
return this.settingsTree.setInput(this.searchResultModel.root).then(() => {
this.renderResultCountMessages();
});
this.refreshTree();
this.renderResultCountMessages();
} else {
// Leaving search mode
collapseAll(this.tocTree);
return this.settingsTree.setInput(this.settingsTreeModel.root).then(() => this.renderResultCountMessages());
this.refreshTree();
this.renderResultCountMessages();
}
}
return Promise.resolve(null);
}
/**
......@@ -1138,7 +1137,7 @@ export class SettingsEditor2 extends BaseEditor {
this.searchResultModel.setResult(type, result);
this.tocTreeModel.currentSearchModel = this.searchResultModel;
this.onSearchModeToggled();
this.settingsTree.setInput(this.searchResultModel.root);
this.refreshTree();
} else {
this.searchResultModel.setResult(type, result);
this.tocTreeModel.update();
......@@ -1153,7 +1152,7 @@ export class SettingsEditor2 extends BaseEditor {
}
private renderResultCountMessages() {
if (!this.settingsTree.getInput()) {
if (!this.currentSettingsModel) {
return;
}
......@@ -1201,13 +1200,12 @@ export class SettingsEditor2 extends BaseEditor {
const listHeight = dimension.height - (76 + 11 /* header height + padding*/);
const settingsTreeHeight = listHeight - 14;
this.settingsTreeContainer.style.height = `${settingsTreeHeight}px`;
this.settingsTree.layout(settingsTreeHeight, 800);
this.settingsTree.layout(settingsTreeHeight);
this.settingsTree.layoutWidth(dimension.width);
const tocTreeHeight = listHeight - 16;
this.tocTreeContainer.style.height = `${tocTreeHeight}px`;
this.tocTree.layout(tocTreeHeight, 175);
this.settingsTreeRenderer.updateWidth(dimension.width);
}
protected saveState(): void {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册