Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 78 additions & 72 deletions src/vs/sessions/contrib/changes/browser/changesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
import { IObjectTreeElement } from '../../../../base/browser/ui/tree/tree.js';
import { ActionRunner, IAction } from '../../../../base/common/actions.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
import { Disposable, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
import { Event } from '../../../../base/common/event.js';
import { autorun, derived, IObservable } from '../../../../base/common/observable.js';
import { autorun, derived, derivedOpts, IObservable } from '../../../../base/common/observable.js';
import { basename } from '../../../../base/common/path.js';
import { ProgressBar } from '../../../../base/browser/ui/progressbar/progressbar.js';
import { isEqual } from '../../../../base/common/resources.js';
Expand Down Expand Up @@ -69,6 +69,7 @@ import { ActiveSessionContextKeys, CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID, C
import { buildTreeChildren, ChangesTreeElement, ChangesTreeRenderer, IChangesFileItem, IChangesTreeRootInfo, isChangesFileItem, toIChangesFileItem } from './changesViewRenderer.js';
import { ChangesViewModel } from './changesViewModel.js';
import { ResourceTree } from '../../../../base/common/resourceTree.js';
import { structuralEquals } from '../../../../base/common/equals.js';

const $ = dom.$;

Expand All @@ -78,52 +79,100 @@ const RUN_SESSION_CODE_REVIEW_ACTION_ID = 'sessions.codeReview.run';

// --- ButtonBar widget

class ChangesButtonBarWidget extends MenuWorkbenchButtonBar {
class ChangesButtonBarWidget extends Disposable {
constructor(
container: HTMLElement,
sessionResource: URI | undefined,
private readonly outgoingChanges: number,
private readonly codeReviewLoading: boolean,
private readonly reviewCommentCount: number | undefined,
viewModel: ChangesViewModel,
@IAgentSessionsService agentSessionsService: IAgentSessionsService,
@IMenuService menuService: IMenuService,
@ICodeReviewService codeReviewService: ICodeReviewService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextMenuService contextMenuService: IContextMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
@IHoverService hoverService: IHoverService,
@IHoverService hoverService: IHoverService
) {
super(
container,
MenuId.ChatEditingSessionChangesToolbar,
{
telemetrySource: 'changesView',
disableWhileRunning: true,
menuOptions: sessionResource
? { args: [sessionResource, agentSessionsService.getSession(sessionResource)?.metadata] }
: { shouldForwardArgs: true },
buttonConfigProvider: (action) => this._getButtonConfiguration(action)
},
menuService, contextKeyService, contextMenuService, keybindingService, telemetryService, hoverService
);
super();

const outgoingChangesObs = derived(reader => {
const activeSessionState = viewModel.activeSessionStateObs.read(reader);
return activeSessionState?.outgoingChanges ?? 0;
});

const reviewStateObs = derivedOpts<{ isLoading: boolean; commentCount: number | undefined }>({ equalsFn: structuralEquals }, reader => {
const sessionResource = viewModel.activeSessionResourceObs.read(reader);
if (!sessionResource) {
return { isLoading: false, commentCount: undefined };
}

const sessionChanges = viewModel.activeSessionChangesObs.read(reader);
const prReviewState = codeReviewService.getPRReviewState(sessionResource).read(reader);
const prReviewCommentCount = prReviewState.kind === PRReviewStateKind.Loaded
? prReviewState.comments.length
: 0;

let isLoading = false;
let commentCount: number | undefined;
if (sessionChanges && sessionChanges.length > 0) {
const reviewFiles = getCodeReviewFilesFromSessionChanges(sessionChanges);
const reviewVersion = getCodeReviewVersion(reviewFiles);
const reviewState = codeReviewService.getReviewState(sessionResource).read(reader);

if (reviewState.kind === CodeReviewStateKind.Loading && reviewState.version === reviewVersion) {
isLoading = true;
} else {
const codeReviewCommentCount = reviewState.kind === CodeReviewStateKind.Result && reviewState.version === reviewVersion
? reviewState.comments.length
: 0;
const totalReviewCommentCount = codeReviewCommentCount + prReviewCommentCount;
if (totalReviewCommentCount > 0) {
commentCount = totalReviewCommentCount;
}
}
} else if (prReviewCommentCount > 0) {
commentCount = prReviewCommentCount;
}

return { isLoading, commentCount };
});

this._register(autorun(reader => {
const sessionResource = viewModel.activeSessionResourceObs.read(reader);
const outgoingChanges = outgoingChangesObs.read(reader);
const reviewState = reviewStateObs.read(reader);

reader.store.add(new MenuWorkbenchButtonBar(
container,
MenuId.ChatEditingSessionChangesToolbar,
{
telemetrySource: 'changesView',
disableWhileRunning: true,
menuOptions: sessionResource
? { args: [sessionResource, agentSessionsService.getSession(sessionResource)?.metadata] }
: { shouldForwardArgs: true },
buttonConfigProvider: (action) => this._getButtonConfiguration(action, outgoingChanges, reviewState)
},
menuService, contextKeyService, contextMenuService, keybindingService, telemetryService, hoverService
));
}));
}

private _getButtonConfiguration(action: IAction): { showIcon: boolean; showLabel: boolean; isSecondary?: boolean; customLabel?: string; customClass?: string } | undefined {
private _getButtonConfiguration(action: IAction, outgoingChanges: number, reviewState: { isLoading: boolean; commentCount: number | undefined }): { showIcon: boolean; showLabel: boolean; isSecondary?: boolean; customLabel?: string; customClass?: string } | undefined {
if (
action.id === 'github.copilot.sessions.sync' ||
action.id === 'github.copilot.chat.createPullRequestCopilotCLIAgentSession.updatePR'
) {
const customLabel = this.outgoingChanges > 0
? `${action.label} ${this.outgoingChanges}↑`
const customLabel = outgoingChanges > 0
? `${action.label} ${outgoingChanges}↑`
: action.label;
return { customLabel, showIcon: true, showLabel: true, isSecondary: false };
}
if (action.id === RUN_SESSION_CODE_REVIEW_ACTION_ID) {
if (this.codeReviewLoading) {
if (reviewState.isLoading) {
return { showIcon: true, showLabel: true, isSecondary: true, customLabel: '$(loading~spin)', customClass: 'code-review-loading' };
}
if (this.reviewCommentCount !== undefined) {
return { showIcon: true, showLabel: true, isSecondary: true, customLabel: String(this.reviewCommentCount), customClass: 'code-review-comments' };
if (reviewState.commentCount !== undefined) {
return { showIcon: true, showLabel: true, isSecondary: true, customLabel: String(reviewState.commentCount), customClass: 'code-review-comments' };
}
return { showIcon: true, showLabel: false, isSecondary: true };
}
Expand Down Expand Up @@ -214,7 +263,6 @@ export class ChangesViewPane extends ViewPane {
@IEditorService private readonly editorService: IEditorService,
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
@ILabelService private readonly labelService: ILabelService,
@ICodeReviewService private readonly codeReviewService: ICodeReviewService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) {
super({ ...options, titleMenuId: MenuId.ChatEditingSessionTitleToolbar }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
Expand Down Expand Up @@ -463,50 +511,8 @@ export class ChangesViewPane extends ViewPane {
const scopedInstantiationService = this.instantiationService.createChild(scopedServiceCollection);
this.renderDisposables.add(scopedInstantiationService);

const outgoingChangesObs = derived(reader => {
const activeSessionState = this.viewModel.activeSessionStateObs.read(reader);
return activeSessionState?.outgoingChanges ?? 0;
});

this.renderDisposables.add(autorun(reader => {
const sessionResource = this.viewModel.activeSessionResourceObs.read(reader);
const outgoingChanges = outgoingChangesObs.read(reader);

// Read code review state to update the button label dynamically
let reviewCommentCount: number | undefined;
let codeReviewLoading = false;
if (sessionResource) {
const prReviewState = this.codeReviewService.getPRReviewState(sessionResource).read(reader);
const prReviewCommentCount = prReviewState.kind === PRReviewStateKind.Loaded ? prReviewState.comments.length : 0;
const activeSession = this.sessionManagementService.activeSession.read(reader);
const sessionChanges = activeSession?.changes.read(reader);
if (sessionChanges && sessionChanges.length > 0) {
const reviewFiles = getCodeReviewFilesFromSessionChanges(sessionChanges);
const reviewVersion = getCodeReviewVersion(reviewFiles);
const reviewState = this.codeReviewService.getReviewState(sessionResource).read(reader);
if (reviewState.kind === CodeReviewStateKind.Loading && reviewState.version === reviewVersion) {
codeReviewLoading = true;
} else {
const codeReviewCommentCount = reviewState.kind === CodeReviewStateKind.Result && reviewState.version === reviewVersion ? reviewState.comments.length : 0;
const totalReviewCommentCount = codeReviewCommentCount + prReviewCommentCount;
if (totalReviewCommentCount > 0) {
reviewCommentCount = totalReviewCommentCount;
}
}
} else if (prReviewCommentCount > 0) {
reviewCommentCount = prReviewCommentCount;
}
}

reader.store.add(scopedInstantiationService.createInstance(
ChangesButtonBarWidget,
this.actionsContainer!,
sessionResource,
outgoingChanges,
codeReviewLoading,
reviewCommentCount
));
}));
this.renderDisposables.add(scopedInstantiationService.createInstance(
ChangesButtonBarWidget, this.actionsContainer, this.viewModel));
}

// Update visibility and file count badge based on entries
Expand Down
Loading