Skip to content

Commit 645f29c

Browse files
vs-code-engineering[bot]houghj16Copilot
authored
[cherry-pick] chat: Keep customization sections browsable (#335128)
Co-authored-by: Jessie Houghton <jehoughton@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 206853c commit 645f29c

8 files changed

Lines changed: 64 additions & 56 deletions

File tree

‎src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationListWidget.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import { ICustomizationHarnessService } from '../../common/customizationHarnessS
4949
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
5050
import { IAICustomizationListItem } from './aiCustomizationItemSource.js';
5151
import { IAICustomizationItemsModel, ItemsModelSection } from './aiCustomizationItemsModel.js';
52-
import { createCustomizationCardPrimaryAction, CustomizationCardListController, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setupCollapsibleSection } from './customizationCardList.js';
52+
import { createCustomizationCardPrimaryAction, CustomizationCardListController, getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setupCollapsibleSection } from './customizationCardList.js';
5353
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
5454
import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js';
5555

@@ -1834,7 +1834,7 @@ export class AICustomizationListWidget extends Disposable {
18341834
const heights = layoutVirtualizedSections(content, this.cardSectionLists.map(section => ({
18351835
container: section.container,
18361836
contentHeight: section.items.length * ITEM_HEIGHT,
1837-
minimumHeight: ITEM_HEIGHT,
1837+
minimumHeight: getVirtualizedSectionMinimumHeight(section.items, () => ITEM_HEIGHT),
18381838
})));
18391839
for (let index = 0; index < this.cardSectionLists.length; index++) {
18401840
const section = this.cardSectionLists[index];

‎src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ import { IAgentPluginItem } from '../agentPluginEditor/agentPluginItems.js';
101101
import { IExtension } from '../../../extensions/common/extensions.js';
102102
import { EmbeddedMcpServerDetail, IMcpServerDetailInput } from './embeddedMcpServerDetail.js';
103103
import { EmbeddedAgentPluginDetail } from './embeddedAgentPluginDetail.js';
104-
import { layoutVirtualizedSectionList, layoutVirtualizedSections, setupCollapsibleSection } from './customizationCardList.js';
104+
import { getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, setupCollapsibleSection } from './customizationCardList.js';
105105
import { EmbeddedExtensionToolsDetail } from './embeddedExtensionToolsDetail.js';
106106
import { ICustomizationHarnessService, type ICustomizationSourceFolder } from '../../common/customizationHarnessService.js';
107107
import { ChatConfiguration } from '../../common/constants.js';
@@ -1943,7 +1943,7 @@ export class AICustomizationManagementEditor extends EditorPane {
19431943
const heights = layoutVirtualizedSections(this.migrationListContainer, this.migrationSectionLists.map(section => ({
19441944
container: section.container,
19451945
contentHeight: section.items.length * MIGRATION_ITEM_HEIGHT,
1946-
minimumHeight: MIGRATION_ITEM_HEIGHT,
1946+
minimumHeight: getVirtualizedSectionMinimumHeight(section.items, () => MIGRATION_ITEM_HEIGHT),
19471947
})));
19481948
for (let index = 0; index < this.migrationSectionLists.length; index++) {
19491949
const section = this.migrationSectionLists[index];

‎src/vs/workbench/contrib/chat/browser/aiCustomization/customizationCardList.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js';
1111
import { localize } from '../../../../../nls.js';
1212

1313
const $ = DOM.$;
14+
const DEFAULT_VIRTUALIZED_SECTION_ROWS = 4;
1415
let collapsibleSectionIdPool = 0;
1516

1617
export interface ICustomizationCardListItem {
@@ -77,6 +78,10 @@ export interface IVirtualizedSectionLayout {
7778
readonly minimumHeight: number;
7879
}
7980

81+
export function getVirtualizedSectionMinimumHeight<T>(items: readonly T[], getHeight: (item: T) => number): number {
82+
return items.slice(0, DEFAULT_VIRTUALIZED_SECTION_ROWS).reduce((height, item) => height + getHeight(item), 0);
83+
}
84+
8085
export function renderVirtualizedSectionLoadingPlaceholder(container: HTMLElement, label: string, height: number): HTMLElement {
8186
const placeholder = DOM.append(container, $('.virtualized-section-loading'));
8287
placeholder.style.height = `${height}px`;

‎src/vs/workbench/contrib/chat/browser/aiCustomization/mcpListWidget.ts‎

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import { status } from '../../../../../base/browser/ui/aria/aria.js';
5656
import { Range } from '../../../../../editor/common/core/range.js';
5757
import { IMcpServerConfiguration, McpServerType } from '../../../../../platform/mcp/common/mcpPlatformTypes.js';
5858
import { createWorkbenchMcpServerDetailInput, IMcpServerDetailInput } from './embeddedMcpServerDetail.js';
59-
import { createCustomizationCardPrimaryAction, CustomizationCardListController, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from './customizationCardList.js';
59+
import { createCustomizationCardPrimaryAction, CustomizationCardListController, getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from './customizationCardList.js';
6060
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
6161
import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js';
6262
import { WorkbenchList } from '../../../../../platform/list/browser/listService.js';
@@ -1757,27 +1757,22 @@ export class McpListWidget extends Disposable {
17571757
private renderMcpListActions(entry: IMcpInstalledEntry, actions: HTMLElement, disposables: DisposableStore, updateTabbability: () => void): void {
17581758
const label = getMcpEntryLabel(entry);
17591759
let enabled = this.isInstalledEntryEnabled(entry);
1760-
const switchElement = DOM.append(actions, $('button.plugin-enable-switch')) as HTMLButtonElement;
1761-
switchElement.type = 'button';
1762-
switchElement.setAttribute('role', 'switch');
1763-
DOM.append(switchElement, $('.plugin-enable-switch-thumb'));
1760+
const toggle = disposables.add(new Switch({ ariaLabel: label, checked: enabled }));
1761+
DOM.append(actions, toggle.domNode);
17641762
const update = () => {
17651763
enabled = this.isInstalledEntryEnabled(entry);
17661764
const blockedByPlugin = getMcpDisabledReason(entry)?.source === 'plugin';
17671765
const toggleLabel = enabled ? localize('disableMcpServerAria', "Disable {0}", label) : localize('enableMcpServerAria', "Enable {0}", label);
17681766
const accessibleLabel = blockedByPlugin ? localize('mcpServerManagedByPluginAria', "{0} is disabled by its plugin", label) : toggleLabel;
1769-
switchElement.disabled = blockedByPlugin;
1770-
switchElement.classList.toggle('checked', enabled);
1771-
switchElement.setAttribute('aria-checked', String(enabled));
1772-
switchElement.setAttribute('aria-label', accessibleLabel);
1773-
switchElement.title = accessibleLabel;
1767+
toggle.disabled = blockedByPlugin;
1768+
toggle.checked = enabled;
1769+
toggle.setAriaLabel(accessibleLabel);
17741770
updateTabbability();
17751771
};
17761772
update();
1777-
disposables.add(DOM.addDisposableGenericMouseDownListener(switchElement, event => DOM.EventHelper.stop(event, true)));
1778-
disposables.add(DOM.addDisposableListener(switchElement, 'click', event => {
1779-
DOM.EventHelper.stop(event, true);
1780-
enabled = !enabled;
1773+
disposables.add(DOM.addDisposableGenericMouseDownListener(toggle.domNode, event => DOM.EventHelper.stop(event, true)));
1774+
disposables.add(toggle.onChange(checked => {
1775+
enabled = checked;
17811776
this.setInstalledEntryEnabled(entry, enabled);
17821777
update();
17831778
status(enabled ? localize('mcpServerEnabledStatus', "{0} enabled.", label) : localize('mcpServerDisabledStatus', "{0} disabled.", label));
@@ -1809,7 +1804,7 @@ export class McpListWidget extends Disposable {
18091804
const heights = layoutVirtualizedSections(content, this.sectionLists.map(section => ({
18101805
container: section.container,
18111806
contentHeight: section.entries.length * MCP_SECTION_ITEM_HEIGHT,
1812-
minimumHeight: MCP_SECTION_ITEM_HEIGHT,
1807+
minimumHeight: getVirtualizedSectionMinimumHeight(section.entries, () => MCP_SECTION_ITEM_HEIGHT),
18131808
})));
18141809
for (let index = 0; index < this.sectionLists.length; index++) {
18151810
const section = this.sectionLists[index];

‎src/vs/workbench/contrib/chat/browser/aiCustomization/pluginListWidget.ts‎

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { INotificationService } from '../../../../../platform/notification/commo
4747
import { getErrorMessage } from '../../../../../base/common/errors.js';
4848
import { getPluginInclusionLabel } from './aiCustomizationPresentation.js';
4949
import { status } from '../../../../../base/browser/ui/aria/aria.js';
50-
import { createCustomizationCardPrimaryAction, CustomizationCardListController, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from './customizationCardList.js';
50+
import { createCustomizationCardPrimaryAction, CustomizationCardListController, getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from './customizationCardList.js';
5151
import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js';
5252
import { ScrollbarVisibility } from '../../../../../base/common/scrollable.js';
5353

@@ -1464,28 +1464,26 @@ export class PluginListWidget extends Disposable {
14641464

14651465
private renderInstalledListActions(item: IInstalledPluginItem, row: HTMLElement, actions: HTMLElement, disposables: DisposableStore): void {
14661466
let renderedState = item.plugin.enablement.get();
1467-
const switchElement = DOM.append(actions, $('button.plugin-enable-switch')) as HTMLButtonElement;
1468-
switchElement.type = 'button';
1469-
switchElement.setAttribute('role', 'switch');
1470-
DOM.append(switchElement, $('.plugin-enable-switch-thumb'));
1471-
disposables.add(DOM.addDisposableGenericMouseDownListener(switchElement, event => DOM.EventHelper.stop(event, true)));
1467+
const toggle = disposables.add(new Switch({ ariaLabel: item.name, checked: isContributionEnabled(renderedState) }));
1468+
DOM.append(actions, toggle.domNode);
1469+
disposables.add(DOM.addDisposableGenericMouseDownListener(toggle.domNode, event => DOM.EventHelper.stop(event, true)));
14721470
const update = (state: ContributionEnablementState, blocked: boolean) => {
14731471
renderedState = state;
14741472
const checked = isContributionEnabled(state);
14751473
const workspaceScope = state === ContributionEnablementState.EnabledWorkspace || state === ContributionEnablementState.DisabledWorkspace;
14761474
const toggleLabel = checked
14771475
? (workspaceScope ? localize('excludePluginWorkspaceAria', "Exclude {0} from Workspace", item.name) : localize('excludePluginProfileAria', "Exclude {0} from Profile", item.name))
14781476
: (workspaceScope ? localize('includePluginWorkspaceAria', "Include {0} in Workspace", item.name) : localize('includePluginProfileAria', "Include {0} for Profile", item.name));
1479-
switchElement.disabled = blocked;
1480-
switchElement.setAttribute('aria-checked', String(checked));
1481-
switchElement.setAttribute('aria-label', blocked ? localize('pluginManagedByOrganizationAria', "{0} is managed by your organization", item.name) : toggleLabel);
1482-
switchElement.classList.toggle('checked', checked);
1483-
switchElement.title = blocked ? localize('pluginPolicyBlockedSwitch', "This plugin is managed by your organization.") : toggleLabel;
1477+
toggle.disabled = blocked;
1478+
toggle.checked = checked;
1479+
toggle.setAriaLabel(
1480+
blocked ? localize('pluginManagedByOrganizationAria', "{0} is managed by your organization", item.name) : toggleLabel,
1481+
blocked ? localize('pluginPolicyBlockedSwitch', "This plugin is managed by your organization.") : toggleLabel,
1482+
);
14841483
row.classList.toggle('disabled', !checked || blocked);
14851484
};
14861485
disposables.add(autorun(reader => update(item.plugin.enablement.read(reader), item.plugin.policyBlocked?.read(reader) === true)));
1487-
disposables.add(DOM.addDisposableListener(switchElement, 'click', event => {
1488-
DOM.EventHelper.stop(event, true);
1486+
disposables.add(toggle.onChange(() => {
14891487
const nextState = getToggledPluginEnablementState(renderedState);
14901488
update(nextState, isPluginPolicyBlocked(item.plugin));
14911489
this.agentPluginService.enablementModel.setEnabled(item.plugin.uri.toString(), nextState);
@@ -1537,7 +1535,7 @@ export class PluginListWidget extends Disposable {
15371535
const heights = layoutVirtualizedSections(content, this.sectionLists.map(section => ({
15381536
container: section.container,
15391537
contentHeight: section.entries.reduce((height, entry) => height + delegate.getHeight(entry), 0),
1540-
minimumHeight: section.entries.length > 0 ? delegate.getHeight(section.entries[0]) : 0,
1538+
minimumHeight: getVirtualizedSectionMinimumHeight(section.entries, entry => delegate.getHeight(entry)),
15411539
})));
15421540
for (let index = 0; index < this.sectionLists.length; index++) {
15431541
const section = this.sectionLists[index];

‎src/vs/workbench/contrib/chat/browser/aiCustomization/toolsListWidget.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { IContextMenuService, IContextViewService } from '../../../../../platfor
3131
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
3232
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
3333
import { WorkbenchList } from '../../../../../platform/list/browser/listService.js';
34-
import { layoutVirtualizedSectionList, layoutVirtualizedSections, setupCollapsibleSection } from './customizationCardList.js';
34+
import { getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, setupCollapsibleSection } from './customizationCardList.js';
3535
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
3636
import { defaultButtonStyles, defaultCheckboxStyles, defaultInputBoxStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
3737
import { IExtensionManifestPropertiesService } from '../../../../services/extensions/common/extensionManifestPropertiesService.js';
@@ -1061,7 +1061,7 @@ export class ToolsListWidget extends Disposable {
10611061
const heights = layoutVirtualizedSections(this._treeContainer, this._sectionLists.map(section => ({
10621062
container: section.container,
10631063
contentHeight: section.entries.reduce((sum, entry) => sum + computeToolsRowHeight(entry), 0),
1064-
minimumHeight: section.entries.length > 0 ? computeToolsRowHeight(section.entries[0]) : 0,
1064+
minimumHeight: getVirtualizedSectionMinimumHeight(section.entries, computeToolsRowHeight),
10651065
})));
10661066
for (let index = 0; index < this._sectionLists.length; index++) {
10671067
this._layoutSection(this._sectionLists[index], heights[index]);

‎src/vs/workbench/contrib/chat/test/browser/aiCustomization/aiCustomizationListWidget.test.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { IPromptsService, PromptsStorage } from '../../../common/promptSyntax/se
2626
import { PromptsType } from '../../../common/promptSyntax/promptTypes.js';
2727
import { Codicon } from '../../../../../../base/common/codicons.js';
2828
import { ResourceSet } from '../../../../../../base/common/map.js';
29-
import { createCustomizationCardPrimaryAction, CustomizationCardListController, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from '../../../browser/aiCustomization/customizationCardList.js';
29+
import { createCustomizationCardPrimaryAction, CustomizationCardListController, getVirtualizedSectionMinimumHeight, layoutVirtualizedSectionList, layoutVirtualizedSections, renderVirtualizedSectionLoadingPlaceholder, setVirtualizedRowActionsTabbable, setupCollapsibleSection } from '../../../browser/aiCustomization/customizationCardList.js';
3030

3131
suite('aiCustomizationListWidget', () => {
3232
ensureNoDisposablesAreLeakedInTestSuite();
@@ -251,6 +251,16 @@ suite('aiCustomizationListWidget', () => {
251251
});
252252
});
253253

254+
test('virtualized sections show up to four rows by default', () => {
255+
assert.deepStrictEqual({
256+
fourOfFive: getVirtualizedSectionMinimumHeight([44, 44, 44, 44, 44], height => height),
257+
allOfThree: getVirtualizedSectionMinimumHeight([44, 66, 44], height => height),
258+
}, {
259+
fourOfFive: 176,
260+
allOfThree: 154,
261+
});
262+
});
263+
254264
test('virtualized sections keep one complete row when the initial height is constrained', () => {
255265
const root = document.createElement('div');
256266
const sections = Array.from({ length: 3 }, () => {

0 commit comments

Comments
 (0)