@@ -14,6 +14,7 @@ import { IVSCodeExtensionContext } from '../../../platform/extContext/common/ext
1414import { OutputChannelName } from '../../../platform/log/vscode/outputChannelLogTarget' ;
1515import { ChatRequestScheme , ILoggedElementInfo , ILoggedRequestInfo , ILoggedToolCall , IRequestLogger , LoggedInfo , LoggedInfoKind , LoggedRequestKind } from '../../../platform/requestLogger/node/requestLogger' ;
1616import { assertNever } from '../../../util/vs/base/common/assert' ;
17+ import { RunOnceScheduler } from '../../../util/vs/base/common/async' ;
1718import { Disposable , toDisposable } from '../../../util/vs/base/common/lifecycle' ;
1819import { LRUCache } from '../../../util/vs/base/common/map' ;
1920import { isDefined } from '../../../util/vs/base/common/types' ;
@@ -32,14 +33,17 @@ const showRawRequestBodyCommand = 'github.copilot.chat.debug.showRawRequestBody'
3233export class RequestLogTree extends Disposable implements IExtensionContribution {
3334 readonly id = 'requestLogTree' ;
3435 private readonly chatRequestProvider : ChatRequestProvider ;
36+ private readonly treeView : vscode . TreeView < TreeItem > ;
3537
3638 constructor (
3739 @IInstantiationService instantiationService : IInstantiationService ,
3840 @IRequestLogger requestLogger : IRequestLogger ,
3941 ) {
4042 super ( ) ;
4143 this . chatRequestProvider = this . _register ( instantiationService . createInstance ( ChatRequestProvider ) ) ;
42- this . _register ( vscode . window . registerTreeDataProvider ( 'copilot-chat' , this . chatRequestProvider ) ) ;
44+ this . treeView = this . _register ( vscode . window . createTreeView ( 'copilot-chat' , { treeDataProvider : this . chatRequestProvider } ) ) ;
45+ this . chatRequestProvider . setVisible ( this . treeView . visible ) ;
46+ this . _register ( this . treeView . onDidChangeVisibility ( e => this . chatRequestProvider . setVisible ( e . visible ) ) ) ;
4347
4448 let server : RequestServer | undefined ;
4549
@@ -559,6 +563,16 @@ type TreeItem = ChatPromptItem | ChatRequestItem | ChatElementItem | ToolCallIte
559563
560564class ChatRequestProvider extends Disposable implements vscode . TreeDataProvider < TreeItem > {
561565 private readonly filters : LogTreeFilters ;
566+ private rootItems : ( ChatPromptItem | TreeChildItem ) [ ] = [ ] ;
567+ private seenChatRequests = new WeakSet < ChatRequest > ( ) ;
568+ private processedCount = 0 ;
569+ private currentPrompt : ChatPromptItem | undefined ;
570+ private readonly refreshScheduler : RunOnceScheduler ;
571+ private pendingRootRefresh = false ;
572+ private readonly pendingPromptRefresh = new Set < ChatPromptItem > ( ) ;
573+ private readonly refreshDelay = 50 ;
574+ private isVisible = false ;
575+ private needsRefreshWhenVisible = false ;
562576
563577 constructor (
564578 @IRequestLogger private readonly requestLogger : IRequestLogger ,
@@ -567,8 +581,10 @@ class ChatRequestProvider extends Disposable implements vscode.TreeDataProvider<
567581 super ( ) ;
568582 this . filters = this . _register ( instantiationService . createInstance ( LogTreeFilters ) ) ;
569583 this . _register ( new LogTreeFilterCommands ( this . filters ) ) ;
570- this . _register ( this . requestLogger . onDidChangeRequests ( ( ) => this . _onDidChangeTreeData . fire ( ) ) ) ;
571- this . _register ( this . filters . onDidChangeFilters ( ( ) => this . _onDidChangeTreeData . fire ( ) ) ) ;
584+ this . _register ( this . requestLogger . onDidChangeRequests ( ( ) => this . handleRequestChange ( ) ) ) ;
585+ this . _register ( this . filters . onDidChangeFilters ( ( ) => this . handleFilterChange ( ) ) ) ;
586+ this . refreshScheduler = this . _register ( new RunOnceScheduler ( ( ) => this . flushRefreshQueue ( ) , 0 ) ) ;
587+ this . rebuildFromScratch ( ) ;
572588 }
573589
574590 private readonly _onDidChangeTreeData = new vscode . EventEmitter < TreeItem | undefined | void > ( ) ;
@@ -580,48 +596,104 @@ class ChatRequestProvider extends Disposable implements vscode.TreeDataProvider<
580596
581597 getChildren ( element ?: TreeItem | undefined ) : vscode . ProviderResult < TreeItem [ ] > {
582598 if ( element instanceof ChatPromptItem ) {
583- return element . children ;
584- } else if ( element ) {
599+ return element . children . filter ( child => this . filters . itemIncluded ( child ) ) ;
600+ }
601+
602+ if ( element ) {
585603 return [ ] ;
604+ }
605+
606+ return this . rootItems . filter ( item => this . filters . itemIncluded ( item ) ) ;
607+ }
608+
609+ private rebuildFromScratch ( ) : void {
610+ this . rootItems = [ ] ;
611+ this . seenChatRequests = new WeakSet < ChatRequest > ( ) ;
612+ this . processedCount = 0 ;
613+ this . currentPrompt = undefined ;
614+ this . appendNewEntries ( ) ;
615+ }
616+
617+ private handleRequestChange ( ) : void {
618+ this . appendNewEntries ( ) ;
619+ }
620+
621+ private handleFilterChange ( ) : void {
622+ if ( this . isVisible ) {
623+ this . _onDidChangeTreeData . fire ( undefined ) ;
586624 } else {
587- let lastPrompt : ChatPromptItem | undefined ;
588- const result : ( ChatPromptItem | TreeChildItem ) [ ] = [ ] ;
589- const seen = new Set < ChatRequest | undefined > ( ) ;
590-
591- for ( const r of this . requestLogger . getRequests ( ) ) {
592- const item = this . logToTreeItem ( r ) ;
593- if ( r . chatRequest !== lastPrompt ?. request ) {
594- if ( lastPrompt ) {
595- result . push ( lastPrompt ) ;
596- }
597- lastPrompt = r . chatRequest ? ChatPromptItem . create ( r , r . chatRequest , seen . has ( r . chatRequest ) ) : undefined ;
598- seen . add ( r . chatRequest ) ;
625+ this . pendingRootRefresh = true ;
626+ this . needsRefreshWhenVisible = true ;
627+ }
628+ }
629+
630+ setVisible ( visible : boolean ) : void {
631+ this . isVisible = visible ;
632+ if ( visible ) {
633+ if ( this . pendingRootRefresh || this . pendingPromptRefresh . size || this . needsRefreshWhenVisible ) {
634+ this . refreshScheduler . schedule ( 0 ) ;
635+ }
636+ }
637+ }
638+
639+ private appendNewEntries ( ) : void {
640+ const requests = this . requestLogger . getRequests ( ) ;
641+ let rootChanged = false ;
642+ const promptsToRefresh = new Set < ChatPromptItem > ( ) ;
643+ const newPrompts = new Set < ChatPromptItem > ( ) ;
644+
645+ if ( requests . length < this . processedCount ) {
646+ this . rootItems = [ ] ;
647+ this . seenChatRequests = new WeakSet < ChatRequest > ( ) ;
648+ this . processedCount = 0 ;
649+ this . currentPrompt = undefined ;
650+ rootChanged = true ;
651+ }
652+
653+ for ( let i = this . processedCount ; i < requests . length ; i ++ ) {
654+ const info = requests [ i ] ;
655+ const child = this . logToTreeItem ( info ) ;
656+ const request = info . chatRequest ;
657+
658+ if ( request ) {
659+ const hasSeen = this . seenChatRequests . has ( request ) ;
660+ if ( ! this . currentPrompt || this . currentPrompt . request !== request ) {
661+ this . currentPrompt = ChatPromptItem . create ( info , request , hasSeen ) ;
662+ this . currentPrompt . children . length = 0 ;
663+ this . rootItems . push ( this . currentPrompt ) ;
664+ this . seenChatRequests . add ( request ) ;
665+ rootChanged = true ;
666+ newPrompts . add ( this . currentPrompt ) ;
599667 }
600668
601- if ( lastPrompt ) {
602- if ( ! lastPrompt . children . find ( c => c . id === item . id ) ) {
603- lastPrompt . children . push ( item ) ;
604- }
605- if ( ! lastPrompt . children . find ( c => c . id === item . id ) ) {
606- lastPrompt . children . push ( item ) ;
607- }
608- } else {
609- result . push ( item ) ;
669+ if ( ! this . currentPrompt . children . some ( c => c . id === child . id ) ) {
670+ this . currentPrompt . children . push ( child ) ;
671+ promptsToRefresh . add ( this . currentPrompt ) ;
610672 }
673+ } else {
674+ this . currentPrompt = undefined ;
675+ this . rootItems . push ( child ) ;
676+ rootChanged = true ;
611677 }
678+ }
612679
613- if ( lastPrompt ) {
614- result . push ( lastPrompt ) ;
615- }
680+ this . processedCount = requests . length ;
616681
617- return result . map ( r => {
618- if ( r instanceof ChatPromptItem ) {
619- return r . withFilteredChildren ( child => this . filters . itemIncluded ( child ) ) ;
620- }
682+ if ( rootChanged ) {
683+ this . pendingRootRefresh = true ;
684+ }
685+
686+ for ( const prompt of promptsToRefresh ) {
687+ if ( rootChanged && newPrompts . has ( prompt ) ) {
688+ continue ;
689+ }
690+ this . pendingPromptRefresh . add ( prompt ) ;
691+ }
621692
622- return r ;
623- } )
624- . filter ( r => this . filters . itemIncluded ( r ) ) ;
693+ if ( this . isVisible ) {
694+ this . refreshScheduler . schedule ( this . refreshDelay ) ;
695+ } else {
696+ this . needsRefreshWhenVisible = true ;
625697 }
626698 }
627699
@@ -637,6 +709,24 @@ class ChatRequestProvider extends Disposable implements vscode.TreeDataProvider<
637709 assertNever ( r ) ;
638710 }
639711 }
712+
713+ private flushRefreshQueue ( ) : void {
714+ if ( ! this . isVisible ) {
715+ return ;
716+ }
717+
718+ if ( this . pendingRootRefresh ) {
719+ this . _onDidChangeTreeData . fire ( undefined ) ;
720+ } else {
721+ for ( const prompt of this . pendingPromptRefresh ) {
722+ this . _onDidChangeTreeData . fire ( prompt ) ;
723+ }
724+ }
725+
726+ this . pendingRootRefresh = false ;
727+ this . pendingPromptRefresh . clear ( ) ;
728+ this . needsRefreshWhenVisible = false ;
729+ }
640730}
641731
642732type TreeChildItem = ChatRequestItem | ChatElementItem | ToolCallItem ;
@@ -824,4 +914,4 @@ class LogTreeFilterCommands extends Disposable {
824914 this . _register ( vscode . commands . registerCommand ( 'github.copilot.chat.debug.showNesRequests' , ( ) => filters . setNesRequestsShown ( true ) ) ) ;
825915 this . _register ( vscode . commands . registerCommand ( 'github.copilot.chat.debug.hideNesRequests' , ( ) => filters . setNesRequestsShown ( false ) ) ) ;
826916 }
827- }
917+ }
0 commit comments