Path: blob/main/src/vs/workbench/contrib/chat/browser/chatSessions/chatSessionPickerActionItem.ts
5292 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/chatSessionPickerActionItem.css';6import { IAction } from '../../../../../base/common/actions.js';7import { Event } from '../../../../../base/common/event.js';8import * as dom from '../../../../../base/browser/dom.js';9import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';10import { IActionWidgetDropdownAction, IActionWidgetDropdownOptions } from '../../../../../platform/actionWidget/browser/actionWidgetDropdown.js';11import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';12import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';13import { ActionWidgetDropdownActionViewItem } from '../../../../../platform/actions/browser/actionWidgetDropdownActionViewItem.js';14import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem } from '../../common/chatSessionsService.js';15import { ICommandService } from '../../../../../platform/commands/common/commands.js';16import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';17import { IDisposable } from '../../../../../base/common/lifecycle.js';18import { renderLabelWithIcons, renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js';19import { localize } from '../../../../../nls.js';20import { URI } from '../../../../../base/common/uri.js';212223export interface IChatSessionPickerDelegate {24readonly onDidChangeOption: Event<IChatSessionProviderOptionItem>;25getCurrentOption(): IChatSessionProviderOptionItem | undefined;26setOption(option: IChatSessionProviderOptionItem): void;27getOptionGroup(): IChatSessionProviderOptionGroup | undefined;28getSessionResource: () => URI | undefined;29}3031/**32* Action view item for making an option selection for a contributed chat session33* These options are provided by the relevant ChatSession Provider34*/35export class ChatSessionPickerActionItem extends ActionWidgetDropdownActionViewItem {36protected currentOption: IChatSessionProviderOptionItem | undefined;37protected container: HTMLElement | undefined;3839constructor(40action: IAction,41initialState: { group: IChatSessionProviderOptionGroup; item: IChatSessionProviderOptionItem | undefined },42protected readonly delegate: IChatSessionPickerDelegate,43@IActionWidgetService actionWidgetService: IActionWidgetService,44@IContextKeyService contextKeyService: IContextKeyService,45@IKeybindingService keybindingService: IKeybindingService,46@ICommandService protected readonly commandService: ICommandService,47@ITelemetryService telemetryService: ITelemetryService,48) {49const { group, item } = initialState;50const actionWithLabel: IAction = {51...action,52label: item?.name || group.name,53tooltip: item?.description ?? group.description ?? group.name,54run: () => { }55};5657const sessionPickerActionWidgetOptions: Omit<IActionWidgetDropdownOptions, 'label' | 'labelRenderer'> = {58actionProvider: {59getActions: () => this.getDropdownActions()60},61actionBarActionProvider: undefined,62reporter: { id: group.id, name: `ChatSession:${group.name}`, includeOptions: false },63};6465super(actionWithLabel, sessionPickerActionWidgetOptions, actionWidgetService, keybindingService, contextKeyService, telemetryService);66this.currentOption = item;6768this._register(this.delegate.onDidChangeOption(newOption => {69this.currentOption = newOption;70if (this.element) {71this.renderLabel(this.element);72}73this.updateEnabled();74}));75}7677/**78* Returns the actions to show in the dropdown. Can be overridden by subclasses.79*/80protected getDropdownActions(): IActionWidgetDropdownAction[] {81// if locked, show the current option only82const currentOption = this.delegate.getCurrentOption();83if (currentOption?.locked) {84return [this.createLockedOptionAction(currentOption)];85}8687const group = this.delegate.getOptionGroup();88if (!group) {89return [];90}9192const actions: IActionWidgetDropdownAction[] = group.items.map(optionItem => {93const isCurrent = optionItem.id === currentOption?.id;94return {95id: optionItem.id,96enabled: !optionItem.locked,97icon: optionItem.icon,98checked: isCurrent,99class: undefined,100description: optionItem.description,101tooltip: optionItem.description ?? optionItem.name,102label: optionItem.name,103run: () => {104this.delegate.setOption(optionItem);105}106} satisfies IActionWidgetDropdownAction;107});108109// Add commands at the end in a separate section (only if there are options)110if (group.commands?.length) {111const addSeparator = actions.length > 0;112for (const command of group.commands) {113const args = command.arguments ? [...command.arguments] : [];114const sessionResource = this.delegate.getSessionResource();115if (sessionResource) {116args.unshift(sessionResource);117}118actions.push({119id: command.command,120enabled: true,121checked: false,122class: undefined,123description: undefined,124tooltip: command.tooltip ?? command.title,125label: command.title,126// Use category to create a separator before commands (only if there are options)127category: addSeparator ? { label: '', order: Number.MAX_SAFE_INTEGER } : undefined,128run: () => {129this.commandService.executeCommand(command.command, ...args);130}131} satisfies IActionWidgetDropdownAction);132}133}134135return actions;136}137138/**139* Creates a disabled action for a locked option.140*/141protected createLockedOptionAction(option: IChatSessionProviderOptionItem): IActionWidgetDropdownAction {142return {143id: option.id,144enabled: false,145icon: option.icon,146checked: true,147class: undefined,148description: option.description,149tooltip: option.description ?? option.name,150label: option.name,151run: () => { }152};153}154155protected override renderLabel(element: HTMLElement): IDisposable | null {156const domChildren = [];157element.classList.add('chat-session-option-picker');158const group = this.delegate.getOptionGroup();159// If the current option is the default and has an icon, collapse the text and show only the icon160const isDefaultWithIcon = this.currentOption?.default && this.currentOption?.icon;161162if (this.currentOption?.icon) {163domChildren.push(renderIcon(this.currentOption.icon));164}165166if (!isDefaultWithIcon) {167domChildren.push(dom.$('span.chat-session-option-label', undefined, this.currentOption?.name ?? group?.description ?? localize('chat.sessionPicker.label', "Pick Option")));168}169170domChildren.push(...renderLabelWithIcons(`$(chevron-down)`));171172dom.reset(element, ...domChildren);173this.setAriaLabelAttributes(element);174return null;175}176177override render(container: HTMLElement): void {178this.container = container;179super.render(container);180container.classList.add(this.getContainerClass());181182// Set initial locked state on container183if (this.currentOption?.locked) {184container.classList.add('locked');185}186}187188/**189* Returns the CSS class to add to the container. Can be overridden by subclasses.190*/191protected getContainerClass(): string {192return 'chat-sessionPicker-item';193}194195protected override updateEnabled(): void {196const originalEnabled = this.action.enabled;197if (this.currentOption?.locked) {198this.action.enabled = false;199}200super.updateEnabled();201this.action.enabled = originalEnabled;202if (this.container) {203this.container.classList.toggle('locked', !!this.currentOption?.locked);204}205}206}207208209