Path: blob/main/src/vs/workbench/contrib/chat/browser/attachments/chatContextPickService.ts
4780 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*--------------------------------------------------------------------------------------------*/4import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js';5import { IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js';6import { derived, IObservable, ObservablePromise } from '../../../../../base/common/observable.js';7import { compare } from '../../../../../base/common/strings.js';8import { ThemeIcon } from '../../../../../base/common/themables.js';9import { isObject } from '../../../../../base/common/types.js';10import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';11import { IQuickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js';12import { IChatRequestVariableEntry } from '../../common/attachments/chatVariableEntries.js';13import { IChatWidget } from '../chat.js';141516export interface IChatContextPickerPickItem extends Partial<IQuickItem> {17label: string;18iconClass?: string;19iconClasses?: readonly string[];20description?: string;21detail?: string;22disabled?: boolean;23asAttachment(): ChatContextPickAttachment | Promise<ChatContextPickAttachment>;24}2526export type ChatContextPickAttachment = IChatRequestVariableEntry | IChatRequestVariableEntry[] | 'noop';2728export function isChatContextPickerPickItem(item: unknown): item is IChatContextPickerPickItem {29return isObject(item) && typeof (item as IChatContextPickerPickItem).asAttachment === 'function';30}3132interface IChatContextItem {33readonly label: string;34readonly icon: ThemeIcon;35readonly commandId?: string;36readonly ordinal?: number;37isEnabled?(widget: IChatWidget): Promise<boolean> | boolean;38}3940export interface IChatContextValueItem extends IChatContextItem {41readonly type: 'valuePick';4243asAttachment(widget: IChatWidget): Promise<IChatRequestVariableEntry | IChatRequestVariableEntry[] | undefined>;44}4546export type ChatContextPick = IChatContextPickerPickItem | IQuickPickSeparator;4748export interface IChatContextPicker {49readonly placeholder: string;50/**51* Picks that should either be:52* - A promise that resolves to the picked items53* - A function that maps input query into items to display.54*/55readonly picks: Promise<ChatContextPick[]> | ((query: IObservable<string>, token: CancellationToken) => IObservable<{ busy: boolean; picks: ChatContextPick[] }>);5657/** Return true to cancel the default behavior */58readonly goBack?: () => boolean;5960readonly configure?: {61label: string;62commandId: string;63};6465readonly dispose?: () => void;66}6768export interface IChatContextPickerItem extends IChatContextItem {69readonly type: 'pickerPick';7071asPicker(widget: IChatWidget): IChatContextPicker;72}7374/**75* Helper for use in {@IChatContextPickerItem} that wraps a simple query->promise76* function into the requisite observable.77*/78export function picksWithPromiseFn(fn: (query: string, token: CancellationToken) => Promise<ChatContextPick[]>): (query: IObservable<string>, token: CancellationToken) => IObservable<{ busy: boolean; picks: ChatContextPick[] }> {79return (query, token) => {80const promise = derived(reader => {81const queryValue = query.read(reader);82const cts = new CancellationTokenSource(token);83reader.store.add(toDisposable(() => cts.dispose(true)));84return new ObservablePromise(fn(queryValue, cts.token));85});8687return promise.map((value, reader) => {88const result = value.promiseResult.read(reader);89return { picks: result?.data || [], busy: result === undefined };90});91};92}9394export interface IChatContextPickService {95_serviceBrand: undefined;9697items: Iterable<IChatContextValueItem | IChatContextPickerItem>;9899/**100* Register a value or picker to the "Add Context" flow. A value directly resolved to a101* chat attachment and a picker first shows a list of items to pick from and then102* resolves the selected item to a chat attachment.103*/104registerChatContextItem(item: IChatContextValueItem | IChatContextPickerItem): IDisposable;105}106107export const IChatContextPickService = createDecorator<IChatContextPickService>('IContextPickService');108109export class ChatContextPickService implements IChatContextPickService {110111declare _serviceBrand: undefined;112113private readonly _picks: IChatContextValueItem[] = [];114115readonly items: Iterable<IChatContextValueItem> = this._picks;116117registerChatContextItem(pick: IChatContextValueItem): IDisposable {118this._picks.push(pick);119120this._picks.sort((a, b) => {121const valueA = a.ordinal ?? 0;122const valueB = b.ordinal ?? 0;123if (valueA === valueB) {124return compare(a.label, b.label);125} else if (valueA < valueB) {126return 1;127} else {128return -1;129}130});131132return toDisposable(() => {133const index = this._picks.indexOf(pick);134if (index >= 0) {135this._picks.splice(index, 1);136}137});138}139}140141142