Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 65 additions & 34 deletions src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ interface IFocusEventFromScroll extends KeyboardEvent {

const searchBoxLabel = localize('SearchSettings.AriaLabel', "Search settings");
const SEARCH_TOC_BEHAVIOR_KEY = 'workbench.settings.settingsSearchTocBehavior';
const SCROLL_BEHAVIOR_KEY = 'workbench.settings.scrollBehavior';

const SHOW_AI_RESULTS_ENABLED_LABEL = localize('showAiResultsEnabled', "Show AI-recommended results");
const SHOW_AI_RESULTS_DISABLED_LABEL = localize('showAiResultsDisabled', "No AI results available at this time...");
Expand Down Expand Up @@ -1050,54 +1051,63 @@ export class SettingsEditor2 extends EditorPane {

this.tocFocusedElement = element;
this.tocTree.setSelection(element ? [element] : []);
if (this.searchResultModel) {
const scrollBehavior = this.configurationService.getValue<'paginated' | 'continuous'>(SCROLL_BEHAVIOR_KEY);
if (this.searchResultModel || scrollBehavior === 'paginated') {
// In search mode or paginated mode, filter to show only the selected category
if (this.viewState.filterToCategory !== element) {
this.viewState.filterToCategory = element ?? undefined;
// Force render in this case, because
// onDidClickSetting relies on the updated view.
this.renderTree(undefined, true);
this.settingsTree.scrollTop = 0;
}
} else if (element && (!e.browserEvent || !(<IFocusEventFromScroll>e.browserEvent).fromScroll)) {
let targetElement = element;
// Searches equvalent old Object currently living in the Tree nodes.
if (!this.settingsTree.hasElement(targetElement)) {
if (element instanceof SettingsTreeGroupElement) {
const targetId = element.id;

const findInViewNodes = (nodes: any[]): SettingsTreeGroupElement | undefined => {
for (const node of nodes) {
if (node.element instanceof SettingsTreeGroupElement && node.element.id === targetId) {
return node.element;
}
if (node.children && node.children.length > 0) {
const found = findInViewNodes(node.children);
if (found) {
return found;
} else {
// In continuous mode, clear any category filter that may have been set in paginated mode
if (this.viewState.filterToCategory) {
this.viewState.filterToCategory = undefined;
this.renderTree(undefined, true);
}
if (element && (!e.browserEvent || !(<IFocusEventFromScroll>e.browserEvent).fromScroll)) {
let targetElement = element;
// Searches equivalent old Object currently living in the Tree nodes.
if (!this.settingsTree.hasElement(targetElement)) {
if (element instanceof SettingsTreeGroupElement) {
const targetId = element.id;

const findInViewNodes = (nodes: any[]): SettingsTreeGroupElement | undefined => {
for (const node of nodes) {
if (node.element instanceof SettingsTreeGroupElement && node.element.id === targetId) {
return node.element;
}
if (node.children && node.children.length > 0) {
const found = findInViewNodes(node.children);
if (found) {
return found;
}
}
}
}
return undefined;
};

try {
const rootNode = this.settingsTree.getNode(null);
if (rootNode && rootNode.children) {
const foundOldElement = findInViewNodes(rootNode.children);
if (foundOldElement) {
// Now we don't reveal the New Object, reveal the Old Object"
targetElement = foundOldElement;
return undefined;
};

try {
const rootNode = this.settingsTree.getNode(null);
if (rootNode && rootNode.children) {
const foundOldElement = findInViewNodes(rootNode.children);
if (foundOldElement) {
// Now we don't reveal the New Object, reveal the Old Object"
targetElement = foundOldElement;
}
}
} catch (err) {
// Tree might be in an invalid state, ignore
}
} catch (err) {
// Tree might be in an invalid state, ignore
}
}
}

if (this.settingsTree.hasElement(targetElement)) {
this.settingsTree.reveal(targetElement, 0);
this.settingsTree.setFocus([targetElement]);
if (this.settingsTree.hasElement(targetElement)) {
this.settingsTree.reveal(targetElement, 0);
this.settingsTree.setFocus([targetElement]);
}
}
}
}));
Expand Down Expand Up @@ -1249,6 +1259,12 @@ export class SettingsEditor2 extends EditorPane {
return;
}

// In paginated mode, we don't sync scroll position since categories are filtered
const scrollBehavior = this.configurationService.getValue<'paginated' | 'continuous'>(SCROLL_BEHAVIOR_KEY);
if (scrollBehavior === 'paginated') {
return;
}

if (!this.tocTreeModel) {
return;
}
Expand Down Expand Up @@ -1685,6 +1701,21 @@ export class SettingsEditor2 extends EditorPane {
await this.onSearchInputChanged(true);
} else {
this.refreshTOCTree();

// In paginated mode, set initial category to the first one (Commonly Used)
const scrollBehavior = this.configurationService.getValue<'paginated' | 'continuous'>(SCROLL_BEHAVIOR_KEY);
if (scrollBehavior === 'paginated') {
const rootChildren = this.settingsTreeModel.value.root.children;
if (Array.isArray(rootChildren) && rootChildren.length > 0) {
const firstCategory = rootChildren[0];
if (firstCategory instanceof SettingsTreeGroupElement) {
this.viewState.filterToCategory = firstCategory;
this.tocTree.setFocus([firstCategory]);
this.tocTree.setSelection([firstCategory]);
}
}
}

this.refreshTree();
this.tocTree.collapseAll();
}
Expand Down
67 changes: 56 additions & 11 deletions src/vs/workbench/contrib/preferences/browser/settingsTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2403,13 +2403,14 @@ function escapeInvisibleChars(enumValue: string): string {
export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {
constructor(
private viewState: ISettingsEditorViewState,
private filterGroups: boolean,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
) { }

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)) {
if (!this.settingBelongsToCategory(element, this.viewState.filterToCategory)) {
return false;
}
}
Expand All @@ -2424,6 +2425,16 @@ export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {

// Group with no visible children
if (element instanceof SettingsTreeGroupElement) {
// When filtering to a specific category, only show that category and its descendants
if (this.filterGroups && this.viewState.filterToCategory) {
if (!this.groupIsRelatedToCategory(element, this.viewState.filterToCategory)) {
return false;
}
// For groups related to the category, skip the count check and recurse
// to let child settings be filtered
return TreeVisibility.Recurse;
}

if (typeof element.count === 'number') {
return element.count > 0;
}
Expand All @@ -2441,16 +2452,50 @@ export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {
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;
/**
* Checks if a setting element belongs to the category or any of its subcategories
* by traversing up the setting's parent chain using IDs.
*/
private settingBelongsToCategory(element: SettingsTreeSettingElement, category: SettingsTreeGroupElement): boolean {
let parent = element.parent;
while (parent) {
if (parent.id === category.id) {
return true;
}
});
parent = parent.parent;
}
return false;
}

/**
* Checks if a group is related to the filtered category.
* A group is related if it's the category itself, a descendant of it, or an ancestor of it.
*/
private groupIsRelatedToCategory(group: SettingsTreeGroupElement, category: SettingsTreeGroupElement): boolean {
// Check if this group is the category itself
if (group.id === category.id) {
return true;
}

// Check if this group is a descendant of the category
let parent = group.parent;
while (parent) {
if (parent.id === category.id) {
return true;
}
parent = parent.parent;
}

// Check if this group is an ancestor of the category
let categoryParent = category.parent;
while (categoryParent) {
if (categoryParent.id === group.id) {
return true;
}
categoryParent = categoryParent.parent;
}

return false;
}
}

Expand Down Expand Up @@ -2617,7 +2662,7 @@ export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
},
accessibilityProvider: new SettingsTreeAccessibilityProvider(configurationService, languageService, userDataProfilesService),
styleController: id => new DefaultStyleController(domStylesheetsJs.createStyleSheet(container), id),
filter: instantiationService.createInstance(SettingsTreeFilter, viewState),
filter: instantiationService.createInstance(SettingsTreeFilter, viewState, true),
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: false,
findWidgetEnabled: false,
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/preferences/browser/tocTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class TOCTree extends WorkbenchObjectTree<SettingsTreeGroupElement> {
) {
// test open mode

const filter = instantiationService.createInstance(SettingsTreeFilter, viewState);
const filter = instantiationService.createInstance(SettingsTreeFilter, viewState, false);
const options: IWorkbenchObjectTreeOptions<SettingsTreeGroupElement, void> = {
filter,
multipleSelectionSupport: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ registry.registerConfiguration({
'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the Settings editor Table of Contents while searching. If this setting is being changed in the Settings editor, the setting will take effect after the search query is modified."),
'default': 'filter',
'scope': ConfigurationScope.WINDOW
},
'workbench.settings.scrollBehavior': {
'type': 'string',
'enum': ['paginated', 'continuous'],
'enumDescriptions': [
nls.localize('settingsScrollBehavior.paginated', "Show only settings from the selected category. Clicking a category in the Table of Contents filters the view to that category."),
nls.localize('settingsScrollBehavior.continuous', "Show all settings in a continuous scrolling list. Clicking a category scrolls to that section."),
],
'description': nls.localize('settingsScrollBehavior', "Controls whether the Settings editor shows one category at a time (paginated) or allows continuous scrolling through all settings."),
'default': 'paginated',
'scope': ConfigurationScope.WINDOW
}
}
});
Loading