22 * Copyright (c) Microsoft Corporation. All rights reserved.
33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
5+ /* eslint-disable local/code-no-native-private */
56
67import type * as vscode from 'vscode' ;
78import { coalesce } from '../../../base/common/arrays.js' ;
89import { CancellationToken , CancellationTokenSource } from '../../../base/common/cancellation.js' ;
910import { CancellationError } from '../../../base/common/errors.js' ;
10- import { Disposable , DisposableStore } from '../../../base/common/lifecycle.js' ;
11+ import { Emitter } from '../../../base/common/event.js' ;
12+ import { Disposable , DisposableStore , toDisposable } from '../../../base/common/lifecycle.js' ;
1113import { ResourceMap } from '../../../base/common/map.js' ;
1214import { MarshalledId } from '../../../base/common/marshallingIds.js' ;
1315import { URI , UriComponents } from '../../../base/common/uri.js' ;
@@ -29,6 +31,175 @@ import { basename } from '../../../base/common/resources.js';
2931import { Diagnostic } from './extHostTypeConverters.js' ;
3032import { SymbolKind , SymbolKinds } from '../../../editor/common/languages.js' ;
3133
34+ // #region Chat Session Item Controller
35+
36+ class ChatSessionItemImpl implements vscode . ChatSessionItem {
37+ #label: string ;
38+ #iconPath?: vscode . IconPath ;
39+ #description?: string | vscode . MarkdownString ;
40+ #badge?: string | vscode . MarkdownString ;
41+ #status?: vscode . ChatSessionStatus ;
42+ #archived?: boolean ;
43+ #tooltip?: string | vscode . MarkdownString ;
44+ #timing?: { startTime : number ; endTime ?: number } ;
45+ #changes?: readonly vscode . ChatSessionChangedFile [ ] | { files : number ; insertions : number ; deletions : number } ;
46+ #onChanged: ( ) => void ;
47+
48+ readonly resource : vscode . Uri ;
49+
50+ constructor ( resource : vscode . Uri , label : string , onChanged : ( ) => void ) {
51+ this . resource = resource ;
52+ this . #label = label ;
53+ this . #onChanged = onChanged ;
54+ }
55+
56+ get label ( ) : string {
57+ return this . #label;
58+ }
59+
60+ set label ( value : string ) {
61+ if ( this . #label !== value ) {
62+ this . #label = value ;
63+ this . #onChanged( ) ;
64+ }
65+ }
66+
67+ get iconPath ( ) : vscode . IconPath | undefined {
68+ return this . #iconPath;
69+ }
70+
71+ set iconPath ( value : vscode . IconPath | undefined ) {
72+ if ( this . #iconPath !== value ) {
73+ this . #iconPath = value ;
74+ this . #onChanged( ) ;
75+ }
76+ }
77+
78+ get description ( ) : string | vscode . MarkdownString | undefined {
79+ return this . #description;
80+ }
81+
82+ set description ( value : string | vscode . MarkdownString | undefined ) {
83+ if ( this . #description !== value ) {
84+ this . #description = value ;
85+ this . #onChanged( ) ;
86+ }
87+ }
88+
89+ get badge ( ) : string | vscode . MarkdownString | undefined {
90+ return this . #badge;
91+ }
92+
93+ set badge ( value : string | vscode . MarkdownString | undefined ) {
94+ if ( this . #badge !== value ) {
95+ this . #badge = value ;
96+ this . #onChanged( ) ;
97+ }
98+ }
99+
100+ get status ( ) : vscode . ChatSessionStatus | undefined {
101+ return this . #status;
102+ }
103+
104+ set status ( value : vscode . ChatSessionStatus | undefined ) {
105+ if ( this . #status !== value ) {
106+ this . #status = value ;
107+ this . #onChanged( ) ;
108+ }
109+ }
110+
111+ get archived ( ) : boolean | undefined {
112+ return this . #archived;
113+ }
114+
115+ set archived ( value : boolean | undefined ) {
116+ if ( this . #archived !== value ) {
117+ this . #archived = value ;
118+ this . #onChanged( ) ;
119+ }
120+ }
121+
122+ get tooltip ( ) : string | vscode . MarkdownString | undefined {
123+ return this . #tooltip;
124+ }
125+
126+ set tooltip ( value : string | vscode . MarkdownString | undefined ) {
127+ if ( this . #tooltip !== value ) {
128+ this . #tooltip = value ;
129+ this . #onChanged( ) ;
130+ }
131+ }
132+
133+ get timing ( ) : { startTime : number ; endTime ?: number } | undefined {
134+ return this . #timing;
135+ }
136+
137+ set timing ( value : { startTime : number ; endTime ?: number } | undefined ) {
138+ if ( this . #timing !== value ) {
139+ this . #timing = value ;
140+ this . #onChanged( ) ;
141+ }
142+ }
143+
144+ get changes ( ) : readonly vscode . ChatSessionChangedFile [ ] | { files : number ; insertions : number ; deletions : number } | undefined {
145+ return this . #changes;
146+ }
147+
148+ set changes ( value : readonly vscode . ChatSessionChangedFile [ ] | { files : number ; insertions : number ; deletions : number } | undefined ) {
149+ if ( this . #changes !== value ) {
150+ this . #changes = value ;
151+ this . #onChanged( ) ;
152+ }
153+ }
154+ }
155+
156+ class ChatSessionItemCollectionImpl implements vscode . ChatSessionItemCollection {
157+ readonly #items = new ResourceMap < vscode . ChatSessionItem > ( ) ;
158+ #onItemsChanged: ( ) => void ;
159+
160+ constructor ( onItemsChanged : ( ) => void ) {
161+ this . #onItemsChanged = onItemsChanged ;
162+ }
163+
164+ get size ( ) : number {
165+ return this . #items. size ;
166+ }
167+
168+ replace ( items : readonly vscode . ChatSessionItem [ ] ) : void {
169+ this . #items. clear ( ) ;
170+ for ( const item of items ) {
171+ this . #items. set ( item . resource , item ) ;
172+ }
173+ this . #onItemsChanged( ) ;
174+ }
175+
176+ forEach ( callback : ( item : vscode . ChatSessionItem , collection : vscode . ChatSessionItemCollection ) => unknown , thisArg ?: any ) : void {
177+ for ( const [ _ , item ] of this . #items) {
178+ callback . call ( thisArg , item , this ) ;
179+ }
180+ }
181+
182+ add ( item : vscode . ChatSessionItem ) : void {
183+ this . #items. set ( item . resource , item ) ;
184+ this . #onItemsChanged( ) ;
185+ }
186+
187+ delete ( resource : vscode . Uri ) : void {
188+ this . #items. delete ( resource ) ;
189+ this . #onItemsChanged( ) ;
190+ }
191+
192+ get ( resource : vscode . Uri ) : vscode . ChatSessionItem | undefined {
193+ return this . #items. get ( resource ) ;
194+ }
195+
196+ [ Symbol . iterator ] ( ) : Iterator < readonly [ id : URI , chatSessionItem : vscode . ChatSessionItem ] > {
197+ return this . #items. entries ( ) ;
198+ }
199+ }
200+
201+ // #endregion
202+
32203class ExtHostChatSession {
33204 private _stream : ChatAgentResponseStream ;
34205
@@ -62,13 +233,20 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
62233 readonly extension : IExtensionDescription ;
63234 readonly disposable : DisposableStore ;
64235 } > ( ) ;
236+ private readonly _chatSessionItemControllers = new Map < number , {
237+ readonly sessionType : string ;
238+ readonly controller : vscode . ChatSessionItemController ;
239+ readonly extension : IExtensionDescription ;
240+ readonly disposable : DisposableStore ;
241+ } > ( ) ;
242+ private _nextChatSessionItemProviderHandle = 0 ;
65243 private readonly _chatSessionContentProviders = new Map < number , {
66244 readonly provider : vscode . ChatSessionContentProvider ;
67245 readonly extension : IExtensionDescription ;
68246 readonly capabilities ?: vscode . ChatSessionCapabilities ;
69247 readonly disposable : DisposableStore ;
70248 } > ( ) ;
71- private _nextChatSessionItemProviderHandle = 0 ;
249+ private _nextChatSessionItemControllerHandle = 0 ;
72250 private _nextChatSessionContentProviderHandle = 0 ;
73251
74252 /**
@@ -140,6 +318,52 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
140318 } ;
141319 }
142320
321+
322+ createChatSessionItemController ( extension : IExtensionDescription , id : string , refreshHandler : ( ) => Thenable < void > ) : vscode . ChatSessionItemController {
323+ const controllerHandle = this . _nextChatSessionItemControllerHandle ++ ;
324+ const disposables = new DisposableStore ( ) ;
325+
326+ // TODO: Currently not hooked up
327+ const onDidArchiveChatSessionItem = disposables . add ( new Emitter < vscode . ChatSessionItem > ( ) ) ;
328+
329+ const collection = new ChatSessionItemCollectionImpl ( ( ) => {
330+ this . _proxy . $onDidChangeChatSessionItems ( controllerHandle ) ;
331+ } ) ;
332+
333+ let isDisposed = false ;
334+
335+ const controller : vscode . ChatSessionItemController = {
336+ id,
337+ refreshHandler,
338+ items : collection ,
339+ onDidArchiveChatSessionItem : onDidArchiveChatSessionItem . event ,
340+ createChatSessionItem : ( resource : vscode . Uri , label : string ) => {
341+ if ( isDisposed ) {
342+ throw new Error ( 'ChatSessionItemController has been disposed' ) ;
343+ }
344+
345+ return new ChatSessionItemImpl ( resource , label , ( ) => {
346+ // TODO: Optimize to only update the specific item
347+ this . _proxy . $onDidChangeChatSessionItems ( controllerHandle ) ;
348+ } ) ;
349+ } ,
350+ dispose : ( ) => {
351+ isDisposed = true ;
352+ disposables . dispose ( ) ;
353+ } ,
354+ } ;
355+
356+ this . _chatSessionItemControllers . set ( controllerHandle , { controller, extension, disposable : disposables , sessionType : id } ) ;
357+ this . _proxy . $registerChatSessionItemProvider ( controllerHandle , id ) ;
358+
359+ disposables . add ( toDisposable ( ( ) => {
360+ this . _chatSessionItemControllers . delete ( controllerHandle ) ;
361+ this . _proxy . $unregisterChatSessionItemProvider ( controllerHandle ) ;
362+ } ) ) ;
363+
364+ return controller ;
365+ }
366+
143367 registerChatSessionContentProvider ( extension : IExtensionDescription , chatSessionScheme : string , chatParticipant : vscode . ChatParticipant , provider : vscode . ChatSessionContentProvider , capabilities ?: vscode . ChatSessionCapabilities ) : vscode . Disposable {
144368 const handle = this . _nextChatSessionContentProviderHandle ++ ;
145369 const disposables = new DisposableStore ( ) ;
@@ -184,13 +408,14 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
184408 }
185409 }
186410
187- private convertChatSessionItem ( sessionType : string , sessionContent : vscode . ChatSessionItem ) : IChatSessionItem {
411+ private convertChatSessionItem ( sessionContent : vscode . ChatSessionItem ) : IChatSessionItem {
188412 return {
189413 resource : sessionContent . resource ,
190414 label : sessionContent . label ,
191415 description : sessionContent . description ? typeConvert . MarkdownString . from ( sessionContent . description ) : undefined ,
192416 badge : sessionContent . badge ? typeConvert . MarkdownString . from ( sessionContent . badge ) : undefined ,
193417 status : this . convertChatSessionStatus ( sessionContent . status ) ,
418+ archived : sessionContent . archived ,
194419 tooltip : typeConvert . MarkdownString . fromStrict ( sessionContent . tooltip ) ,
195420 timing : {
196421 startTime : sessionContent . timing ?. startTime ?? 0 ,
@@ -207,21 +432,35 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
207432 }
208433
209434 async $provideChatSessionItems ( handle : number , token : vscode . CancellationToken ) : Promise < IChatSessionItem [ ] > {
210- const entry = this . _chatSessionItemProviders . get ( handle ) ;
211- if ( ! entry ) {
212- this . _logService . error ( `No provider registered for handle ${ handle } ` ) ;
213- return [ ] ;
214- }
435+ let items : vscode . ChatSessionItem [ ] ;
436+
437+ const controller = this . _chatSessionItemControllers . get ( handle ) ;
438+ if ( controller ) {
439+ // Call the refresh handler to populate items
440+ await controller . controller . refreshHandler ( ) ;
441+ if ( token . isCancellationRequested ) {
442+ return [ ] ;
443+ }
215444
216- const sessions = await entry . provider . provideChatSessionItems ( token ) ;
217- if ( ! sessions ) {
218- return [ ] ;
445+ items = Array . from ( controller . controller . items , x => x [ 1 ] ) ;
446+ } else {
447+
448+ const itemProvider = this . _chatSessionItemProviders . get ( handle ) ;
449+ if ( ! itemProvider ) {
450+ this . _logService . error ( `No provider registered for handle ${ handle } ` ) ;
451+ return [ ] ;
452+ }
453+
454+ items = await itemProvider . provider . provideChatSessionItems ( token ) ?? [ ] ;
455+ if ( token . isCancellationRequested ) {
456+ return [ ] ;
457+ }
219458 }
220459
221460 const response : IChatSessionItem [ ] = [ ] ;
222- for ( const sessionContent of sessions ) {
461+ for ( const sessionContent of items ) {
223462 this . _sessionItems . set ( sessionContent . resource , sessionContent ) ;
224- response . push ( this . convertChatSessionItem ( entry . sessionType , sessionContent ) ) ;
463+ response . push ( this . convertChatSessionItem ( sessionContent ) ) ;
225464 }
226465 return response ;
227466 }
0 commit comments