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