Skip to content

Commit 45c32dc

Browse files
committed
Settings editor as tree - expand/collapse settings with long descriptions
1 parent 7299d52 commit 45c32dc

3 files changed

Lines changed: 94 additions & 89 deletions

File tree

src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@
109109
.settings-editor > .settings-body > .settings-tree-container .setting-item {
110110
cursor: default;
111111
white-space: normal;
112-
padding: 3px 0px;
113112
display: flex;
114113
height: 100%;
115114
}
@@ -164,14 +163,18 @@
164163
white-space: pre-wrap;
165164
}
166165

167-
/* .settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description,
168-
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-measure-helper .setting-item-description {
166+
.settings-editor > .settings-body > .settings-tree-container .setting-measure-container.monaco-tree-row {
167+
padding-left: 15px;
168+
}
169+
170+
.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description,
171+
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description {
169172
height: initial;
170173
}
171174

172175
.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expandable .setting-item-description {
173176
cursor: pointer;
174-
} */
177+
}
175178

176179
.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value {
177180
display: flex;
@@ -219,6 +222,19 @@
219222
visibility: hidden;
220223
}
221224

225+
.settings-editor > .settings-body > .settings-tree-container .setting-item .expand-indicator {
226+
visibility: hidden;
227+
position: absolute;
228+
bottom: 3px;
229+
width: calc(100% - 190px);
230+
text-align: center;
231+
opacity: .5;
232+
}
233+
234+
.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expandable .expand-indicator {
235+
visibility: visible;
236+
}
237+
222238
.vs-dark .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value > .setting-reset-button.monaco-button {
223239
background: url("clean-dark.svg") center center no-repeat;
224240
}

src/vs/workbench/parts/preferences/browser/settingsEditor2.ts

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
2424
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
2525
import { EditorOptions } from 'vs/workbench/common/editor';
2626
import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
27-
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeFilter, TreeElement, TreeItemType } from 'vs/workbench/parts/preferences/browser/settingsTree';
27+
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeFilter, TreeElement } from 'vs/workbench/parts/preferences/browser/settingsTree';
2828
import { IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences';
2929
import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
3030
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
@@ -55,7 +55,7 @@ export class SettingsEditor2 extends BaseEditor {
5555

5656
private pendingSettingModifiedReport: { key: string, value: any };
5757

58-
private focusedElement: TreeElement;
58+
private selectedElement: TreeElement;
5959

6060
private viewState: ISettingsEditorViewState;
6161
private searchResultModel: SearchResultModel;
@@ -185,8 +185,8 @@ export class SettingsEditor2 extends BaseEditor {
185185
private createList(parent: HTMLElement): void {
186186
this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container'));
187187

188-
this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, { settingsTarget: ConfigurationTarget.USER });
189-
const renderer = this.instantiationService.createInstance(SettingsRenderer, {});
188+
this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState);
189+
const renderer = this.instantiationService.createInstance(SettingsRenderer, this.viewState, this.settingsTreeContainer);
190190
this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value)));
191191
this._register(renderer.onDidClickButton(e => this.onDidClickShowAllSettings()));
192192

@@ -206,17 +206,15 @@ export class SettingsEditor2 extends BaseEditor {
206206
});
207207

208208
this.settingsTree.onDidChangeFocus(e => {
209-
if (this.focusedElement && this.focusedElement.type === TreeItemType.setting) {
210-
const row = document.getElementById(this.focusedElement.id);
211-
setTabindexes(row, -1);
209+
if (this.selectedElement) {
210+
this.settingsTree.refresh(this.selectedElement);
212211
}
213212

214-
this.focusedElement = e.focus;
215-
216-
if (this.focusedElement && this.focusedElement.type === TreeItemType.setting) {
217-
const row = document.getElementById(this.focusedElement.id);
218-
setTabindexes(row, 0);
213+
if (e.focus) {
214+
this.settingsTree.refresh(e.focus);
219215
}
216+
217+
this.selectedElement = e.focus;
220218
});
221219
}
222220

@@ -467,57 +465,3 @@ export class SettingsEditor2 extends BaseEditor {
467465
this.settingsTree.layout(listHeight, 800);
468466
}
469467
}
470-
471-
function setTabindexes(element: HTMLElement, tabIndex: number): void {
472-
const focusableElements = element.querySelectorAll('input, button, select, a');
473-
for (let i = 0; focusableElements && i < focusableElements.length; i++) {
474-
const element = focusableElements[i];
475-
(<HTMLElement>element).tabIndex = tabIndex;
476-
}
477-
}
478-
479-
// class SettingItemDelegate implements IDelegate<ListEntry> {
480-
481-
// constructor(private measureContainer: HTMLElement) {
482-
483-
// }
484-
485-
// getHeight(entry: ListEntry) {
486-
// if (entry.templateId === SETTINGS_GROUP_ENTRY_TEMPLATE_ID) {
487-
// return 30;
488-
// }
489-
490-
// if (entry.templateId === SETTINGS_ENTRY_TEMPLATE_ID) {
491-
// if (entry.isExpanded) {
492-
// return this.getDynamicHeight(entry);
493-
// } else {
494-
// return 68;
495-
// }
496-
// }
497-
498-
// if (entry.templateId === BUTTON_ROW_ENTRY_TEMPLATE) {
499-
// return 60;
500-
// }
501-
502-
// return 0;
503-
// }
504-
505-
// getTemplateId(element: ListEntry) {
506-
// return element.templateId;
507-
// }
508-
509-
// private getDynamicHeight(entry: ISettingItemEntry): number {
510-
// return measureSettingItemEntry(entry, this.measureContainer);
511-
// }
512-
// }
513-
514-
// function measureSettingItemEntry(entry: ISettingItemEntry, measureContainer: HTMLElement): number {
515-
// const measureHelper = DOM.append(measureContainer, $('.setting-item-measure-helper.monaco-list-row'));
516-
517-
// const template = SettingItemRenderer.renderTemplate(measureHelper);
518-
// SettingItemRenderer.renderElement(entry, 0, template);
519-
520-
// const height = measureHelper.offsetHeight;
521-
// measureContainer.removeChild(measureHelper);
522-
// return height;
523-
// }

src/vs/workbench/parts/preferences/browser/settingsTree.ts

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant }
2323
import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
2424
import { ISearchResult, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
2525
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
26+
import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
2627

2728
const $ = DOM.$;
2829

@@ -113,6 +114,8 @@ export class SettingsDataSource implements IDataSource {
113114
id: `${group.id}_${setting.key}`,
114115
setting,
115116

117+
isExpanded: false,
118+
116119
value: displayValue,
117120
isConfigured,
118121
overriddenScopeList,
@@ -210,6 +213,7 @@ export interface ISettingItemTemplate extends IDisposableTemplate {
210213
categoryElement: HTMLElement;
211214
labelElement: HTMLElement;
212215
descriptionElement: HTMLElement;
216+
expandIndicatorElement: HTMLElement;
213217
valueElement: HTMLElement;
214218
overridesElement: HTMLElement;
215219
}
@@ -227,9 +231,9 @@ export interface IButtonRowTemplate extends IDisposableTemplate {
227231
entry?: IButtonElement;
228232
}
229233

230-
const SETTINGS_ENTRY_TEMPLATE_ID = 'settings.entry.template';
231-
const SETTINGS_GROUP_ENTRY_TEMPLATE_ID = 'settings.group.template';
232-
const BUTTON_ROW_ENTRY_TEMPLATE = 'settings.buttonRow.template';
234+
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.entry.template';
235+
const SETTINGS_GROUP_ELEMENT_TEMPLATE_ID = 'settings.group.template';
236+
const BUTTON_ROW_ELEMENT_TEMPLATE = 'settings.buttonRow.template';
233237

234238
export interface ISettingChangeEvent {
235239
key: string;
@@ -247,19 +251,29 @@ export class SettingsRenderer implements IRenderer {
247251
private readonly _onDidOpenSettings: Emitter<void> = new Emitter<void>();
248252
public readonly onDidOpenSettings: Event<void> = this._onDidOpenSettings.event;
249253

254+
private measureContainer: HTMLElement;
255+
250256
constructor(
251257
private viewState: ISettingsEditorViewState,
258+
_measureContainer: HTMLElement,
252259
@IThemeService private themeService: IThemeService,
253260
@IContextViewService private contextViewService: IContextViewService
254-
) { }
261+
) {
262+
this.measureContainer = DOM.append(_measureContainer, $('.setting-measure-container.monaco-tree-row'));
263+
}
255264

256265
getHeight(tree: ITree, element: TreeElement): number {
257266
if (element.type === TreeItemType.groupTitle) {
258267
return 30;
259268
}
260269

261270
if (element.type === TreeItemType.setting) {
262-
return 68;
271+
const isSelected = this.elementIsSelected(tree, element);
272+
if (isSelected) {
273+
return this.measureSettingElementHeight(tree, element);
274+
} else {
275+
return 68;
276+
}
263277
}
264278

265279
if (element.type === TreeItemType.buttonRow) {
@@ -269,32 +283,43 @@ export class SettingsRenderer implements IRenderer {
269283
return 0;
270284
}
271285

286+
private measureSettingElementHeight(tree: ITree, element: ISettingElement): number {
287+
const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper'));
288+
289+
const template = this.renderSettingTemplate(measureHelper);
290+
this.renderSettingElement(tree, element, template, true);
291+
292+
const height = measureHelper.offsetHeight;
293+
this.measureContainer.removeChild(measureHelper);
294+
return height;
295+
}
296+
272297
getTemplateId(tree: ITree, element: TreeElement): string {
273298
if (element.type === TreeItemType.groupTitle) {
274-
return SETTINGS_GROUP_ENTRY_TEMPLATE_ID;
299+
return SETTINGS_GROUP_ELEMENT_TEMPLATE_ID;
275300
}
276301

277302
if (element.type === TreeItemType.buttonRow) {
278-
return BUTTON_ROW_ENTRY_TEMPLATE;
303+
return BUTTON_ROW_ELEMENT_TEMPLATE;
279304
}
280305

281306
if (element.type === TreeItemType.setting) {
282-
return SETTINGS_ENTRY_TEMPLATE_ID;
307+
return SETTINGS_ELEMENT_TEMPLATE_ID;
283308
}
284309

285310
return '';
286311
}
287312

288313
renderTemplate(tree: ITree, templateId: string, container: HTMLElement) {
289-
if (templateId === SETTINGS_GROUP_ENTRY_TEMPLATE_ID) {
314+
if (templateId === SETTINGS_GROUP_ELEMENT_TEMPLATE_ID) {
290315
return this.renderGroupTitleTemplate(container);
291316
}
292317

293-
if (templateId === BUTTON_ROW_ENTRY_TEMPLATE) {
318+
if (templateId === BUTTON_ROW_ELEMENT_TEMPLATE) {
294319
return this.renderButtonRowTemplate(container);
295320
}
296321

297-
if (templateId === SETTINGS_ENTRY_TEMPLATE_ID) {
322+
if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) {
298323
return this.renderSettingTemplate(container);
299324
}
300325

@@ -347,6 +372,7 @@ export class SettingsRenderer implements IRenderer {
347372
const labelElement = DOM.append(titleElement, $('span.setting-item-label'));
348373
const overridesElement = DOM.append(titleElement, $('span.setting-item-overrides'));
349374
const descriptionElement = DOM.append(leftElement, $('.setting-item-description'));
375+
const expandIndicatorElement = DOM.append(leftElement, $('.expand-indicator'));
350376

351377
const valueElement = DOM.append(rightElement, $('.setting-item-value'));
352378

@@ -359,6 +385,7 @@ export class SettingsRenderer implements IRenderer {
359385
categoryElement,
360386
labelElement,
361387
descriptionElement,
388+
expandIndicatorElement,
362389
valueElement,
363390
overridesElement
364391
};
@@ -367,25 +394,33 @@ export class SettingsRenderer implements IRenderer {
367394
}
368395

369396
renderElement(tree: ITree, element: TreeElement, templateId: string, template: any): void {
370-
if (templateId === SETTINGS_ENTRY_TEMPLATE_ID) {
371-
return this.renderSettingElement(<ISettingElement>element, template);
397+
if (templateId === SETTINGS_ELEMENT_TEMPLATE_ID) {
398+
return this.renderSettingElement(tree, <ISettingElement>element, template);
372399
}
373400

374-
if (templateId === SETTINGS_GROUP_ENTRY_TEMPLATE_ID) {
401+
if (templateId === SETTINGS_GROUP_ELEMENT_TEMPLATE_ID) {
375402
(<IGroupTitleTemplate>template).labelElement.textContent = (<IGroupElement>element).group.title;
376403
return;
377404
}
378405

379-
if (templateId === BUTTON_ROW_ENTRY_TEMPLATE) {
406+
if (templateId === BUTTON_ROW_ELEMENT_TEMPLATE) {
380407
return this.renderButtonRowElement(<IButtonElement>element, template);
381408
}
382409
}
383410

384-
private renderSettingElement(element: ISettingElement, template: ISettingItemTemplate): void {
411+
private elementIsSelected(tree: ITree, element: TreeElement): boolean {
412+
const selection = tree.getFocus();
413+
const selectedElement: TreeElement = selection;
414+
return selectedElement && selectedElement.id === element.id;
415+
}
416+
417+
private renderSettingElement(tree: ITree, element: ISettingElement, template: ISettingItemTemplate, measuring?: boolean): void {
418+
const isSelected = this.elementIsSelected(tree, element);
385419
const setting = element.setting;
386420

387421
template.context = element;
388422
DOM.toggleClass(template.parent, 'is-configured', element.isConfigured);
423+
DOM.toggleClass(template.parent, 'is-expanded', isSelected);
389424
template.containerElement.id = element.id;
390425

391426
const titleTooltip = setting.key;
@@ -398,9 +433,19 @@ export class SettingsRenderer implements IRenderer {
398433
template.descriptionElement.textContent = element.description;
399434
template.descriptionElement.title = element.description;
400435

401-
// const expandedHeight = measureSettingItemEntry(entry, that.measureContainer);
402-
// entry.isExpandable = expandedHeight > 68;
403-
// DOM.toggleClass(template.parent, 'is-expandable', entry.isExpandable);
436+
if (!measuring) {
437+
const expandedHeight = this.measureSettingElementHeight(tree, element);
438+
const isExpandable = expandedHeight > 68;
439+
DOM.toggleClass(template.parent, 'is-expandable', isExpandable);
440+
441+
if (isSelected) {
442+
template.expandIndicatorElement.innerHTML = renderOcticons('$(chevron-up)');
443+
} else if (isExpandable) {
444+
template.expandIndicatorElement.innerHTML = renderOcticons('$(chevron-down)');
445+
} else {
446+
template.expandIndicatorElement.innerHTML = '';
447+
}
448+
}
404449

405450
this.renderValue(element, template);
406451

0 commit comments

Comments
 (0)