Skip to content

Commit

Permalink
Fix preference tree for plugins (#14036)
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew authored Aug 21, 2024
1 parent 6f36201 commit 5d51320
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 42 deletions.
2 changes: 2 additions & 0 deletions packages/core/src/common/json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export interface IJSONSchema {
$id?: string;
$schema?: string;
type?: JsonType | JsonType[];
owner?: string;
group?: string;
title?: string;
default?: JSONValue;
definitions?: IJSONSchemaMap;
Expand Down
13 changes: 12 additions & 1 deletion packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,22 @@ export class TheiaPluginScanner extends AbstractPluginScanner {
try {
if (rawPlugin.contributes.configuration) {
const configurations = Array.isArray(rawPlugin.contributes.configuration) ? rawPlugin.contributes.configuration : [rawPlugin.contributes.configuration];
const hasMultipleConfigs = configurations.length > 1;
contributions.configuration = [];
for (const c of configurations) {
const config = this.readConfiguration(c, rawPlugin.packagePath);
if (config) {
Object.values(config.properties).forEach(property => property.title = config.title);
Object.values(config.properties).forEach(property => {
if (hasMultipleConfigs) {
// If there are multiple configuration contributions, we need to distinguish them by their title in the settings UI.
// They are placed directly under the plugin's name in the settings UI.
property.owner = rawPlugin.displayName;
property.group = config.title;
} else {
// If there's only one configuration contribution, we display the title in the settings UI.
property.owner = config.title;
}
});
contributions.configuration.push(config);
}
}
Expand Down
126 changes: 98 additions & 28 deletions packages/preferences/src/browser/util/preference-tree-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ import debounce = require('@theia/core/shared/lodash.debounce');
import { Preference } from './preference-types';
import { COMMONLY_USED_SECTION_PREFIX, PreferenceLayoutProvider } from './preference-layout';

export interface CreatePreferencesGroupOptions {
id: string,
group: string,
root: CompositeTreeNode,
expanded?: boolean,
depth?: number,
label?: string
}

@injectable()
export class PreferenceTreeGenerator {

Expand Down Expand Up @@ -57,10 +66,22 @@ export class PreferenceTreeGenerator {
const root = this.createRootNode();

const commonlyUsedLayout = this.layoutProvider.getCommonlyUsedLayout();
const commonlyUsed = this.getOrCreatePreferencesGroup(commonlyUsedLayout.id, commonlyUsedLayout.id, root, groups, commonlyUsedLayout.label);
const commonlyUsed = this.getOrCreatePreferencesGroup({
id: commonlyUsedLayout.id,
group: commonlyUsedLayout.id,
root,
groups,
label: commonlyUsedLayout.label
});

for (const layout of this.layoutProvider.getLayout()) {
this.getOrCreatePreferencesGroup(layout.id, layout.id, root, groups, layout.label);
this.getOrCreatePreferencesGroup({
id: layout.id,
group: layout.id,
root,
groups,
label: layout.label
});
}
for (const preference of commonlyUsedLayout.settings ?? []) {
if (preference in preferencesSchema.properties) {
Expand All @@ -70,16 +91,11 @@ export class PreferenceTreeGenerator {
for (const propertyName of propertyNames) {
const property = preferencesSchema.properties[propertyName];
if (!this.preferenceConfigs.isSectionName(propertyName) && !OVERRIDE_PROPERTY_PATTERN.test(propertyName) && !property.deprecationMessage) {
const layoutItem = this.layoutProvider.getLayoutForPreference(propertyName);
const labels = layoutItem ? layoutItem.id.split('.') : propertyName.split('.');
// If a title is set, this property belongs to the 'extensions' category
const groupID = property.title ? this.defaultTopLevelCategory : this.getGroupName(labels);
// Automatically assign all properties with the same title to the same subgroup
const subgroupName = property.title ?? this.getSubgroupName(labels, groupID);
const subgroupID = [groupID, subgroupName].join('.');
const toplevelParent = this.getOrCreatePreferencesGroup(groupID, groupID, root, groups);
const immediateParent = subgroupName && this.getOrCreatePreferencesGroup(subgroupID, groupID, toplevelParent, groups, property.title ?? layoutItem?.label);
this.createLeafNode(propertyName, immediateParent || toplevelParent, property);
if (property.owner) {
this.createPluginLeafNode(propertyName, property, root, groups);
} else {
this.createBuiltinLeafNode(propertyName, property, root, groups);
}
}
}

Expand All @@ -103,6 +119,63 @@ export class PreferenceTreeGenerator {
return root;
};

protected createBuiltinLeafNode(name: string, property: PreferenceDataProperty, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>): void {
const layoutItem = this.layoutProvider.getLayoutForPreference(name);
const labels = layoutItem ? layoutItem.id.split('.') : name.split('.');
const groupID = this.getGroupName(labels);
const subgroupName = this.getSubgroupName(labels, groupID);
const subgroupID = [groupID, subgroupName].join('.');
const toplevelParent = this.getOrCreatePreferencesGroup({
id: groupID,
group: groupID,
root,
groups
});
const immediateParent = subgroupName ? this.getOrCreatePreferencesGroup({
id: subgroupID,
group: groupID,
root: toplevelParent,
groups,
label: layoutItem?.label
}) : undefined;
this.createLeafNode(name, immediateParent || toplevelParent, property);
}

protected createPluginLeafNode(name: string, property: PreferenceDataProperty, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>): void {
if (!property.owner) {
return;
}
const groupID = this.defaultTopLevelCategory;
const subgroupName = property.owner;
const subsubgroupName = property.group;
const hasGroup = Boolean(subsubgroupName);
const toplevelParent = this.getOrCreatePreferencesGroup({
id: groupID,
group: groupID,
root,
groups
});
const subgroupID = [groupID, subgroupName].join('.');
const subgroupParent = this.getOrCreatePreferencesGroup({
id: subgroupID,
group: groupID,
root: toplevelParent,
groups,
expanded: hasGroup,
label: subgroupName
});
const subsubgroupID = [groupID, subgroupName, subsubgroupName].join('.');
const subsubgroupParent = hasGroup ? this.getOrCreatePreferencesGroup({
id: subsubgroupID,
group: subgroupID,
root: subgroupParent,
groups,
depth: 2,
label: subsubgroupName
}) : undefined;
this.createLeafNode(name, subsubgroupParent || subgroupParent, property);
}

getNodeId(preferenceId: string): string {
const expectedGroup = this.getGroupName(preferenceId.split('.'));
const expectedId = `${expectedGroup}@${preferenceId}`;
Expand Down Expand Up @@ -151,40 +224,37 @@ export class PreferenceTreeGenerator {
preferenceId: property,
parent: preferencesGroup,
preference: { data },
depth: Preference.TreeNode.isTopLevel(preferencesGroup) ? 1 : 2,
depth: Preference.TreeNode.isTopLevel(preferencesGroup) ? 1 : 2
};
CompositeTreeNode.addChild(preferencesGroup, newNode);
return newNode;
}

protected createPreferencesGroup(id: string, group: string, root: CompositeTreeNode, label?: string): Preference.CompositeTreeNode {
protected createPreferencesGroup(options: CreatePreferencesGroupOptions): Preference.CompositeTreeNode {
const newNode: Preference.CompositeTreeNode = {
id: `${group}@${id}`,
id: `${options.group}@${options.id}`,
visible: true,
parent: root,
parent: options.root,
children: [],
expanded: false,
selected: false,
depth: 0,
label
label: options.label
};
const isTopLevel = Preference.TreeNode.isTopLevel(newNode);
if (!isTopLevel) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (newNode as any).expanded;
if (!(options.expanded ?? isTopLevel)) {
delete newNode.expanded;
}
newNode.depth = isTopLevel ? 0 : 1;
CompositeTreeNode.addChild(root, newNode);
newNode.depth = options.depth ?? (isTopLevel ? 0 : 1);
CompositeTreeNode.addChild(options.root, newNode);
return newNode;
}

protected getOrCreatePreferencesGroup(
id: string, group: string, root: CompositeTreeNode, groups: Map<string, Preference.CompositeTreeNode>, label?: string
): Preference.CompositeTreeNode {
const existingGroup = groups.get(id);
protected getOrCreatePreferencesGroup(options: CreatePreferencesGroupOptions & { groups: Map<string, Preference.CompositeTreeNode> }): Preference.CompositeTreeNode {
const existingGroup = options.groups.get(options.id);
if (existingGroup) { return existingGroup; }
const newNode = this.createPreferencesGroup(id, group, root, label);
groups.set(id, newNode);
const newNode = this.createPreferencesGroup(options);
options.groups.set(options.id, newNode);
return newNode;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,13 @@ export class PreferenceTreeLabelProvider implements LabelProviderContribution {
}

getName(node: Preference.TreeNode): string {
if (Preference.CompositeTreeNode.is(node) && node.label) {
if (Preference.TreeNode.is(node) && node.label) {
return node.label;
}
const { id } = Preference.TreeNode.getGroupAndIdFromNodeId(node.id);
const layouts = this.layoutProvider.getLayout();
const layout = layouts.find(e => e.id === id);
if (layout) {
return layout.label;
} else {
const labels = id.split('.');
const groupName = labels[labels.length - 1];
return this.formatString(groupName);
}
const labels = id.split('.');
const groupName = labels[labels.length - 1];
return this.formatString(groupName);
}

getPrefix(node: Preference.TreeNode, fullPath = false): string | undefined {
Expand Down
6 changes: 4 additions & 2 deletions packages/preferences/src/browser/util/preference-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
PreferenceDataProperty,
PreferenceScope,
TreeNode as BaseTreeNode,
ExpandableTreeNode,
CompositeTreeNode as BaseCompositeTreeNode,
SelectableTreeNode,
PreferenceInspection,
CommonCommands,
Expand Down Expand Up @@ -59,7 +59,8 @@ export namespace Preference {
};
}

export interface CompositeTreeNode extends ExpandableTreeNode, SelectableTreeNode {
export interface CompositeTreeNode extends BaseCompositeTreeNode, SelectableTreeNode {
expanded?: boolean;
depth: number;
label?: string;
}
Expand All @@ -69,6 +70,7 @@ export namespace Preference {
}

export interface LeafNode extends BaseTreeNode {
label?: string;
depth: number;
preference: { data: PreferenceDataProperty };
preferenceId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@theia/core/lib/browser';
import React = require('@theia/core/shared/react');
import { PreferenceTreeModel, PreferenceTreeNodeRow, PreferenceTreeNodeProps } from '../preference-tree-model';
import { Preference } from '../util/preference-types';

@injectable()
export class PreferencesTreeWidget extends TreeWidget {
Expand All @@ -50,13 +51,21 @@ export class PreferencesTreeWidget extends TreeWidget {
this.rows = new Map();
let index = 0;
for (const [id, nodeRow] of this.model.currentRows.entries()) {
if (nodeRow.visibleChildren > 0 && (ExpandableTreeNode.is(nodeRow.node) || ExpandableTreeNode.isExpanded(nodeRow.node.parent))) {
if (nodeRow.visibleChildren > 0 && this.isVisibleNode(nodeRow.node)) {
this.rows.set(id, { ...nodeRow, index: index++ });
}
}
this.updateScrollToRow();
}

protected isVisibleNode(node: Preference.TreeNode): boolean {
if (Preference.TreeNode.isTopLevel(node)) {
return true;
} else {
return ExpandableTreeNode.isExpanded(node.parent) && Preference.TreeNode.is(node.parent) && this.isVisibleNode(node.parent);
}
}

protected override doRenderNodeRow({ depth, visibleChildren, node, isExpansible }: PreferenceTreeNodeRow): React.ReactNode {
return this.renderNode(node, { depth, visibleChildren, isExpansible });
}
Expand Down

0 comments on commit 5d51320

Please sign in to comment.