@@ -10,9 +10,9 @@ import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
1010import { IObjectTreeElement } from '../../../../base/browser/ui/tree/tree.js' ;
1111import { ActionRunner , IAction } from '../../../../base/common/actions.js' ;
1212import { Codicon } from '../../../../base/common/codicons.js' ;
13- import { DisposableStore , IDisposable } from '../../../../base/common/lifecycle.js' ;
13+ import { Disposable , DisposableStore , IDisposable } from '../../../../base/common/lifecycle.js' ;
1414import { Event } from '../../../../base/common/event.js' ;
15- import { autorun , derived , IObservable } from '../../../../base/common/observable.js' ;
15+ import { autorun , derived , derivedOpts , IObservable } from '../../../../base/common/observable.js' ;
1616import { basename } from '../../../../base/common/path.js' ;
1717import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js' ;
1818import { isEqual } from '../../../../base/common/resources.js' ;
@@ -69,6 +69,7 @@ import { ActiveSessionContextKeys, CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID, C
6969import { buildTreeChildren , ChangesTreeElement , ChangesTreeRenderer , IChangesFileItem , IChangesTreeRootInfo , isChangesFileItem , toIChangesFileItem } from './changesViewRenderer.js' ;
7070import { ChangesViewModel } from './changesViewModel.js' ;
7171import { ResourceTree } from '../../../../base/common/resourceTree.js' ;
72+ import { structuralEquals } from '../../../../base/common/equals.js' ;
7273
7374const $ = dom . $ ;
7475
@@ -78,52 +79,100 @@ const RUN_SESSION_CODE_REVIEW_ACTION_ID = 'sessions.codeReview.run';
7879
7980// --- ButtonBar widget
8081
81- class ChangesButtonBarWidget extends MenuWorkbenchButtonBar {
82+ class ChangesButtonBarWidget extends Disposable {
8283 constructor (
8384 container : HTMLElement ,
84- sessionResource : URI | undefined ,
85- private readonly outgoingChanges : number ,
86- private readonly codeReviewLoading : boolean ,
87- private readonly reviewCommentCount : number | undefined ,
85+ viewModel : ChangesViewModel ,
8886 @IAgentSessionsService agentSessionsService : IAgentSessionsService ,
8987 @IMenuService menuService : IMenuService ,
88+ @ICodeReviewService codeReviewService : ICodeReviewService ,
9089 @IContextKeyService contextKeyService : IContextKeyService ,
9190 @IContextMenuService contextMenuService : IContextMenuService ,
9291 @IKeybindingService keybindingService : IKeybindingService ,
9392 @ITelemetryService telemetryService : ITelemetryService ,
94- @IHoverService hoverService : IHoverService ,
93+ @IHoverService hoverService : IHoverService
9594 ) {
96- super (
97- container ,
98- MenuId . ChatEditingSessionChangesToolbar ,
99- {
100- telemetrySource : 'changesView' ,
101- disableWhileRunning : true ,
102- menuOptions : sessionResource
103- ? { args : [ sessionResource , agentSessionsService . getSession ( sessionResource ) ?. metadata ] }
104- : { shouldForwardArgs : true } ,
105- buttonConfigProvider : ( action ) => this . _getButtonConfiguration ( action )
106- } ,
107- menuService , contextKeyService , contextMenuService , keybindingService , telemetryService , hoverService
108- ) ;
95+ super ( ) ;
96+
97+ const outgoingChangesObs = derived ( reader => {
98+ const activeSessionState = viewModel . activeSessionStateObs . read ( reader ) ;
99+ return activeSessionState ?. outgoingChanges ?? 0 ;
100+ } ) ;
101+
102+ const reviewStateObs = derivedOpts < { isLoading : boolean ; commentCount : number | undefined } > ( { equalsFn : structuralEquals } , reader => {
103+ const sessionResource = viewModel . activeSessionResourceObs . read ( reader ) ;
104+ if ( ! sessionResource ) {
105+ return { isLoading : false , commentCount : undefined } ;
106+ }
107+
108+ const sessionChanges = viewModel . activeSessionChangesObs . read ( reader ) ;
109+ const prReviewState = codeReviewService . getPRReviewState ( sessionResource ) . read ( reader ) ;
110+ const prReviewCommentCount = prReviewState . kind === PRReviewStateKind . Loaded
111+ ? prReviewState . comments . length
112+ : 0 ;
113+
114+ let isLoading = false ;
115+ let commentCount : number | undefined ;
116+ if ( sessionChanges && sessionChanges . length > 0 ) {
117+ const reviewFiles = getCodeReviewFilesFromSessionChanges ( sessionChanges ) ;
118+ const reviewVersion = getCodeReviewVersion ( reviewFiles ) ;
119+ const reviewState = codeReviewService . getReviewState ( sessionResource ) . read ( reader ) ;
120+
121+ if ( reviewState . kind === CodeReviewStateKind . Loading && reviewState . version === reviewVersion ) {
122+ isLoading = true ;
123+ } else {
124+ const codeReviewCommentCount = reviewState . kind === CodeReviewStateKind . Result && reviewState . version === reviewVersion
125+ ? reviewState . comments . length
126+ : 0 ;
127+ const totalReviewCommentCount = codeReviewCommentCount + prReviewCommentCount ;
128+ if ( totalReviewCommentCount > 0 ) {
129+ commentCount = totalReviewCommentCount ;
130+ }
131+ }
132+ } else if ( prReviewCommentCount > 0 ) {
133+ commentCount = prReviewCommentCount ;
134+ }
135+
136+ return { isLoading, commentCount } ;
137+ } ) ;
138+
139+ this . _register ( autorun ( reader => {
140+ const sessionResource = viewModel . activeSessionResourceObs . read ( reader ) ;
141+ const outgoingChanges = outgoingChangesObs . read ( reader ) ;
142+ const reviewState = reviewStateObs . read ( reader ) ;
143+
144+ reader . store . add ( new MenuWorkbenchButtonBar (
145+ container ,
146+ MenuId . ChatEditingSessionChangesToolbar ,
147+ {
148+ telemetrySource : 'changesView' ,
149+ disableWhileRunning : true ,
150+ menuOptions : sessionResource
151+ ? { args : [ sessionResource , agentSessionsService . getSession ( sessionResource ) ?. metadata ] }
152+ : { shouldForwardArgs : true } ,
153+ buttonConfigProvider : ( action ) => this . _getButtonConfiguration ( action , outgoingChanges , reviewState )
154+ } ,
155+ menuService , contextKeyService , contextMenuService , keybindingService , telemetryService , hoverService
156+ ) ) ;
157+ } ) ) ;
109158 }
110159
111- private _getButtonConfiguration ( action : IAction ) : { showIcon : boolean ; showLabel : boolean ; isSecondary ?: boolean ; customLabel ?: string ; customClass ?: string } | undefined {
160+ private _getButtonConfiguration ( action : IAction , outgoingChanges : number , reviewState : { isLoading : boolean ; commentCount : number | undefined } ) : { showIcon : boolean ; showLabel : boolean ; isSecondary ?: boolean ; customLabel ?: string ; customClass ?: string } | undefined {
112161 if (
113162 action . id === 'github.copilot.sessions.sync' ||
114163 action . id === 'github.copilot.chat.createPullRequestCopilotCLIAgentSession.updatePR'
115164 ) {
116- const customLabel = this . outgoingChanges > 0
117- ? `${ action . label } ${ this . outgoingChanges } ↑`
165+ const customLabel = outgoingChanges > 0
166+ ? `${ action . label } ${ outgoingChanges } ↑`
118167 : action . label ;
119168 return { customLabel, showIcon : true , showLabel : true , isSecondary : false } ;
120169 }
121170 if ( action . id === RUN_SESSION_CODE_REVIEW_ACTION_ID ) {
122- if ( this . codeReviewLoading ) {
171+ if ( reviewState . isLoading ) {
123172 return { showIcon : true , showLabel : true , isSecondary : true , customLabel : '$(loading~spin)' , customClass : 'code-review-loading' } ;
124173 }
125- if ( this . reviewCommentCount !== undefined ) {
126- return { showIcon : true , showLabel : true , isSecondary : true , customLabel : String ( this . reviewCommentCount ) , customClass : 'code-review-comments' } ;
174+ if ( reviewState . commentCount !== undefined ) {
175+ return { showIcon : true , showLabel : true , isSecondary : true , customLabel : String ( reviewState . commentCount ) , customClass : 'code-review-comments' } ;
127176 }
128177 return { showIcon : true , showLabel : false , isSecondary : true } ;
129178 }
@@ -214,7 +263,6 @@ export class ChangesViewPane extends ViewPane {
214263 @IEditorService private readonly editorService : IEditorService ,
215264 @ISessionsManagementService private readonly sessionManagementService : ISessionsManagementService ,
216265 @ILabelService private readonly labelService : ILabelService ,
217- @ICodeReviewService private readonly codeReviewService : ICodeReviewService ,
218266 @ITelemetryService private readonly telemetryService : ITelemetryService ,
219267 ) {
220268 super ( { ...options , titleMenuId : MenuId . ChatEditingSessionTitleToolbar } , keybindingService , contextMenuService , configurationService , contextKeyService , viewDescriptorService , instantiationService , openerService , themeService , hoverService ) ;
@@ -463,50 +511,8 @@ export class ChangesViewPane extends ViewPane {
463511 const scopedInstantiationService = this . instantiationService . createChild ( scopedServiceCollection ) ;
464512 this . renderDisposables . add ( scopedInstantiationService ) ;
465513
466- const outgoingChangesObs = derived ( reader => {
467- const activeSessionState = this . viewModel . activeSessionStateObs . read ( reader ) ;
468- return activeSessionState ?. outgoingChanges ?? 0 ;
469- } ) ;
470-
471- this . renderDisposables . add ( autorun ( reader => {
472- const sessionResource = this . viewModel . activeSessionResourceObs . read ( reader ) ;
473- const outgoingChanges = outgoingChangesObs . read ( reader ) ;
474-
475- // Read code review state to update the button label dynamically
476- let reviewCommentCount : number | undefined ;
477- let codeReviewLoading = false ;
478- if ( sessionResource ) {
479- const prReviewState = this . codeReviewService . getPRReviewState ( sessionResource ) . read ( reader ) ;
480- const prReviewCommentCount = prReviewState . kind === PRReviewStateKind . Loaded ? prReviewState . comments . length : 0 ;
481- const activeSession = this . sessionManagementService . activeSession . read ( reader ) ;
482- const sessionChanges = activeSession ?. changes . read ( reader ) ;
483- if ( sessionChanges && sessionChanges . length > 0 ) {
484- const reviewFiles = getCodeReviewFilesFromSessionChanges ( sessionChanges ) ;
485- const reviewVersion = getCodeReviewVersion ( reviewFiles ) ;
486- const reviewState = this . codeReviewService . getReviewState ( sessionResource ) . read ( reader ) ;
487- if ( reviewState . kind === CodeReviewStateKind . Loading && reviewState . version === reviewVersion ) {
488- codeReviewLoading = true ;
489- } else {
490- const codeReviewCommentCount = reviewState . kind === CodeReviewStateKind . Result && reviewState . version === reviewVersion ? reviewState . comments . length : 0 ;
491- const totalReviewCommentCount = codeReviewCommentCount + prReviewCommentCount ;
492- if ( totalReviewCommentCount > 0 ) {
493- reviewCommentCount = totalReviewCommentCount ;
494- }
495- }
496- } else if ( prReviewCommentCount > 0 ) {
497- reviewCommentCount = prReviewCommentCount ;
498- }
499- }
500-
501- reader . store . add ( scopedInstantiationService . createInstance (
502- ChangesButtonBarWidget ,
503- this . actionsContainer ! ,
504- sessionResource ,
505- outgoingChanges ,
506- codeReviewLoading ,
507- reviewCommentCount
508- ) ) ;
509- } ) ) ;
514+ this . renderDisposables . add ( scopedInstantiationService . createInstance (
515+ ChangesButtonBarWidget , this . actionsContainer , this . viewModel ) ) ;
510516 }
511517
512518 // Update visibility and file count badge based on entries
0 commit comments