diff --git a/src/vs/sessions/contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.ts b/src/vs/sessions/contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.ts index c3d198e1d16..f3794216126 100644 --- a/src/vs/sessions/contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.ts +++ b/src/vs/sessions/contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.ts @@ -19,7 +19,6 @@ import { IProductService } from '../../../../platform/product/common/productServ import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js'; import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js'; -import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { URI } from '../../../../base/common/uri.js'; @@ -170,5 +169,5 @@ MenuRegistry.appendMenuItem(MenuId.ChatEditingSessionChangesToolbar, { title: localize2('applyActions', 'Apply Actions'), group: 'navigation', order: 1, - when: ContextKeyExpr.and(IsSessionsWindowContext, ChatContextKeys.hasAgentSessionChanges), + when: IsSessionsWindowContext, }); diff --git a/src/vs/sessions/contrib/changes/browser/changesView.ts b/src/vs/sessions/contrib/changes/browser/changesView.ts index a968289e7a5..4cfd91351d2 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.ts @@ -93,6 +93,8 @@ const changesViewModeContextKey = new RawContextKey('changesVie // --- Versions Mode const enum ChangesVersionMode { + BranchChanges = 'branchChanges', + OutgoingChanges = 'outgoingChanges', AllChanges = 'allChanges', LastTurn = 'lastTurn' } @@ -102,7 +104,7 @@ const enum IsolationMode { Worktree = 'worktree' } -const changesVersionModeContextKey = new RawContextKey('sessions.changesVersionMode', ChangesVersionMode.AllChanges); +const changesVersionModeContextKey = new RawContextKey('sessions.changesVersionMode', ChangesVersionMode.BranchChanges); const isMergeBaseBranchProtectedContextKey = new RawContextKey('sessions.isMergeBaseBranchProtected', false); const isolationModeContextKey = new RawContextKey('sessions.isolationMode', IsolationMode.Workspace); const hasOpenPullRequestContextKey = new RawContextKey('sessions.hasOpenPullRequest', false); @@ -225,9 +227,14 @@ function buildTreeChildren(items: IChangesFileItem[]): IObjectTreeElement; readonly activeSessionResourceObs: IObservable; + readonly activeSessionBranchNameObs: IObservable; + readonly activeSessionBaseBranchNameObs: IObservable; + readonly activeSessionUpstreamBranchNameObs: IObservable; readonly activeSessionIsolationModeObs: IObservable; readonly activeSessionRepositoryObs: IObservableWithChange; readonly activeSessionChangesObs: IObservable; + readonly activeSessionFirstCheckpointRefObs: IObservable; + readonly activeSessionLastCheckpointRefObs: IObservable; readonly versionModeObs: ISettableObservable; setVersionMode(mode: ChangesVersionMode): void { @@ -264,14 +271,6 @@ class ChangesViewModel extends Disposable { return activeSession?.resource; }); - // Active session isolation mode - this.activeSessionIsolationModeObs = derived(reader => { - const activeSession = this.sessionManagementService.activeSession.read(reader); - return activeSession?.workspace.read(reader)?.repositories[0]?.workingDirectory === undefined - ? IsolationMode.Workspace - : IsolationMode.Worktree; - }); - // Active session changes this.activeSessionChangesObs = derivedOpts({ equalsFn: arrayEqualsC() @@ -283,6 +282,18 @@ class ChangesViewModel extends Disposable { return activeSession.changes.read(reader) as readonly (IChatSessionFileChange | IChatSessionFileChange2)[]; }); + const activeSessionRepositoryObs = derived(reader => { + const activeSession = this.sessionManagementService.activeSession.read(reader); + return activeSession?.workspace.read(reader)?.repositories[0]; + }); + + // Active session isolation mode + this.activeSessionIsolationModeObs = derived(reader => { + return activeSessionRepositoryObs.read(reader)?.workingDirectory === undefined + ? IsolationMode.Workspace + : IsolationMode.Worktree; + }); + // Active session repository const activeSessionRepositoryPromiseObs = derived(reader => { const activeSessionResource = this.activeSessionResourceObs.read(reader); @@ -290,13 +301,12 @@ class ChangesViewModel extends Disposable { return constObservable(undefined); } - const activeSession = this.sessionManagementService.activeSession.read(reader); - const worktree = activeSession?.workspace.read(reader)?.repositories[0]?.workingDirectory; - if (!worktree) { + const workingDirectory = activeSessionRepositoryObs.read(reader)?.workingDirectory; + if (!workingDirectory) { return constObservable(undefined); } - return new ObservablePromise(this.gitService.openRepository(worktree)).resolvedValue; + return new ObservablePromise(this.gitService.openRepository(workingDirectory)).resolvedValue; }); this.activeSessionRepositoryObs = derived(reader => { @@ -308,11 +318,57 @@ class ChangesViewModel extends Disposable { return activeSessionRepositoryPromise.read(reader); }); + // Active session branch name + this.activeSessionBranchNameObs = derived(reader => { + const repository = activeSessionRepositoryObs.read(reader); + const repositoryState = this.activeSessionRepositoryObs.read(reader)?.state.read(reader); + + return repository?.detail ?? repositoryState?.HEAD?.name; + }); + + // Active session base branch name + this.activeSessionBaseBranchNameObs = derived(reader => { + return activeSessionRepositoryObs.read(reader)?.baseBranchName; + }); + + // Active session upstream branch name + this.activeSessionUpstreamBranchNameObs = derived(reader => { + const repositoryState = this.activeSessionRepositoryObs.read(reader)?.state.read(reader); + return repositoryState?.HEAD?.upstream + ? `${repositoryState.HEAD.upstream.remote}/${repositoryState.HEAD.upstream.name}` + : undefined; + }); + + // Active session first checkpoint ref + this.activeSessionFirstCheckpointRefObs = derived(reader => { + const sessionResource = this.activeSessionResourceObs.read(reader); + if (!sessionResource) { + return undefined; + } + + this.sessionsChangedSignal.read(reader); + const model = this.agentSessionsService.getSession(sessionResource); + + return model?.metadata?.firstCheckpointRef as string | undefined; + }); + + // Active session last checkpoint ref + this.activeSessionLastCheckpointRefObs = derived(reader => { + const sessionResource = this.activeSessionResourceObs.read(reader); + if (!sessionResource) { + return undefined; + } + + this.sessionsChangedSignal.read(reader); + const model = this.agentSessionsService.getSession(sessionResource); + return model?.metadata?.lastCheckpointRef as string | undefined; + }); + // Version mode - this.versionModeObs = observableValue(this, ChangesVersionMode.AllChanges); + this.versionModeObs = observableValue(this, ChangesVersionMode.BranchChanges); this._register(runOnChange(this.activeSessionResourceObs, () => { - this.setVersionMode(ChangesVersionMode.AllChanges); + this.setVersionMode(ChangesVersionMode.BranchChanges); })); // View mode @@ -405,13 +461,6 @@ export class ChangesViewPane extends ViewPane { this.bodyContainer = dom.append(container, $('.changes-view-body')); - // Welcome message for empty state - this.welcomeContainer = dom.append(this.bodyContainer, $('.changes-welcome')); - const welcomeIcon = dom.append(this.welcomeContainer, $('.changes-welcome-icon')); - welcomeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.diffMultiple)); - const welcomeMessage = dom.append(this.welcomeContainer, $('.changes-welcome-message')); - welcomeMessage.textContent = localize('changesView.noChanges', "Changed files and other session artifacts will appear here."); - // Actions container - positioned outside and above the card this.actionsContainer = dom.append(this.bodyContainer, $('.chat-editing-session-actions.outside-card')); @@ -457,6 +506,13 @@ export class ChangesViewPane extends ViewPane { // List container this.listContainer = dom.append(this.contentContainer, $('.chat-editing-session-list')); + // Welcome message for empty state + this.welcomeContainer = dom.append(this.contentContainer, $('.changes-welcome')); + const welcomeIcon = dom.append(this.welcomeContainer, $('.changes-welcome-icon')); + welcomeIcon.classList.add(...ThemeIcon.asClassNameArray(Codicon.diffMultiple)); + const welcomeMessage = dom.append(this.welcomeContainer, $('.changes-welcome-message')); + welcomeMessage.textContent = localize('changesView.noChanges', "Changed files and other session artifacts will appear here."); + // CI Status widget — bottom pane this.ciStatusWidget = this._register(this.instantiationService.createInstance(CIStatusWidget, this.splitViewContainer)); @@ -632,18 +688,6 @@ export class ChangesViewPane extends ViewPane { return repository?.state.read(reader)?.HEAD?.commit; }); - const lastCheckpointRefObs = derived(reader => { - const sessionResource = this.viewModel.activeSessionResourceObs.read(reader); - if (!sessionResource) { - return undefined; - } - - this.viewModel.sessionsChangedSignal.read(reader); - const model = this.agentSessionsService.getSession(sessionResource); - - return model?.metadata?.lastCheckpointRef as string | undefined; - }); - const lastTurnChangesObs = derived(reader => { const repository = this.viewModel.activeSessionRepositoryObs.read(reader); const headCommit = headCommitObs.read(reader); @@ -652,7 +696,7 @@ export class ChangesViewPane extends ViewPane { return constObservable(undefined); } - const lastCheckpointRef = lastCheckpointRefObs.read(reader); + const lastCheckpointRef = this.viewModel.activeSessionLastCheckpointRefObs.read(reader); const diffPromise = lastCheckpointRef ? repository.diffBetweenWithStats(`${lastCheckpointRef}^`, lastCheckpointRef) @@ -694,8 +738,8 @@ export class ChangesViewPane extends ViewPane { let sourceEntries: IChangesFileItem[]; if (versionMode === ChangesVersionMode.LastTurn) { - const lastCheckpointRef = lastCheckpointRefObs.read(reader); const lastTurnDiffChanges = lastTurnChangesObs.read(reader).read(reader); + const lastCheckpointRef = this.viewModel.activeSessionLastCheckpointRefObs.read(reader); const diffChanges = lastTurnDiffChanges ?? []; @@ -915,9 +959,7 @@ export class ChangesViewPane extends ViewPane { const { files } = topLevelStats.read(reader); const hasEntries = files > 0; - dom.setVisibility(hasEntries, this.contentContainer!); - dom.setVisibility(hasEntries, this.actionsContainer!); - dom.setVisibility(hasEntries, this.splitViewContainer!); + dom.setVisibility(hasEntries, this.listContainer!); dom.setVisibility(!hasEntries, this.welcomeContainer!); if (this.filesCountBadge) { @@ -1539,31 +1581,33 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem { ) { const actionProvider: IActionWidgetDropdownActionProvider = { getActions: () => { - const activeSession = sessionManagementService.activeSession.get(); - const activeSessionIsolationMode = this.viewModel.activeSessionIsolationModeObs.get(); - const activeSessionRepositoryState = this.viewModel.activeSessionRepositoryObs.get()?.state.get(); - const activeSessionRepository = activeSession?.workspace.get()?.repositories[0]; - - const baseBranchName = activeSessionIsolationMode === IsolationMode.Worktree - ? activeSessionRepository?.baseBranchName ?? '' - : activeSessionRepositoryState?.HEAD?.upstream - ? `${activeSessionRepositoryState.HEAD.upstream.remote}/${activeSessionRepositoryState.HEAD.upstream.name}` - : activeSessionRepositoryState?.HEAD?.name ?? ''; - - const branchName = activeSessionRepository?.detail - ?? activeSessionRepositoryState?.HEAD?.name ?? ''; - - const allChangesDescription = baseBranchName && branchName - ? `${branchName} → ${baseBranchName}` - : branchName ?? localize('chatEditing.versionsAllChanges.description', 'Show all changes made in this session'); + const branchName = viewModel.activeSessionBranchNameObs.get(); + const baseBranchName = viewModel.activeSessionBaseBranchNameObs.get(); return [ + { + ...action, + id: 'chatEditing.versionsBranchChanges', + label: localize('chatEditing.versionsBranchChanges', 'Branch Changes'), + description: `${branchName} → ${baseBranchName}`, + checked: viewModel.versionModeObs.get() === ChangesVersionMode.BranchChanges, + category: { label: 'changes', order: 1, showHeader: false }, + run: async () => { + viewModel.setVersionMode(ChangesVersionMode.BranchChanges); + if (this.element) { + this.renderLabel(this.element); + } + }, + }, { ...action, id: 'chatEditing.versionsAllChanges', label: localize('chatEditing.versionsAllChanges', 'All Changes'), - description: allChangesDescription, + description: localize('chatEditing.versionsAllChanges.description', 'Show all changes made in this session'), checked: viewModel.versionModeObs.get() === ChangesVersionMode.AllChanges, + category: { label: 'checkpoints', order: 2, showHeader: false }, + enabled: viewModel.activeSessionFirstCheckpointRefObs.get() !== undefined && + viewModel.activeSessionLastCheckpointRefObs.get() !== undefined, run: async () => { viewModel.setVersionMode(ChangesVersionMode.AllChanges); if (this.element) { @@ -1577,6 +1621,9 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem { label: localize('chatEditing.versionsLastTurnChanges', "Last Turn's Changes"), description: localize('chatEditing.versionsLastTurnChanges.description', 'Show only changes from the last turn'), checked: viewModel.versionModeObs.get() === ChangesVersionMode.LastTurn, + category: { label: 'checkpoints', order: 3, showHeader: false }, + enabled: viewModel.activeSessionFirstCheckpointRefObs.get() !== undefined && + viewModel.activeSessionLastCheckpointRefObs.get() !== undefined, run: async () => { viewModel.setVersionMode(ChangesVersionMode.LastTurn); if (this.element) { @@ -1600,9 +1647,11 @@ class ChangesPickerActionItem extends ActionWidgetDropdownActionViewItem { protected override renderLabel(element: HTMLElement): null { const mode = this.viewModel.versionModeObs.get(); - const label = mode === ChangesVersionMode.LastTurn - ? localize('sessionsChanges.versionsLastTurn', "Last Turn's Changes") - : localize('sessionsChanges.versionsAllChanges', "All Changes"); + const label = mode === ChangesVersionMode.BranchChanges + ? localize('sessionsChanges.versionsBranchChanges', "Branch Changes") + : mode === ChangesVersionMode.AllChanges + ? localize('sessionsChanges.versionsAllChanges', "All Changes") + : localize('sessionsChanges.versionsLastTurn', "Last Turn's Changes"); dom.reset(element, dom.$('span', undefined, label), ...renderLabelWithIcons('$(chevron-down)')); this.updateAriaLabel(); diff --git a/src/vs/sessions/contrib/changes/browser/media/changesView.css b/src/vs/sessions/contrib/changes/browser/media/changesView.css index 7281fcd1a11..13cfef8a6cb 100644 --- a/src/vs/sessions/contrib/changes/browser/media/changesView.css +++ b/src/vs/sessions/contrib/changes/browser/media/changesView.css @@ -74,6 +74,10 @@ font-size: 12px; align-items: center; + > span { + margin-left: 2px; + } + > .codicon { font-size: 10px !important; padding-left: 4px; diff --git a/src/vs/sessions/contrib/codeReview/browser/codeReview.contributions.ts b/src/vs/sessions/contrib/codeReview/browser/codeReview.contributions.ts index 509dc56c0e5..b0a16fd6161 100644 --- a/src/vs/sessions/contrib/codeReview/browser/codeReview.contributions.ts +++ b/src/vs/sessions/contrib/codeReview/browser/codeReview.contributions.ts @@ -41,7 +41,9 @@ function registerSessionCodeReviewAction(tooltip: string, icon: ThemeIcon): Disp tooltip, category: CHAT_CATEGORY, icon, - precondition: canRunSessionCodeReviewContextKey, + precondition: ContextKeyExpr.and( + ChatContextKeys.hasAgentSessionChanges, + canRunSessionCodeReviewContextKey), menu: [ { id: MenuId.ChatEditingSessionChangesToolbar, @@ -49,7 +51,6 @@ function registerSessionCodeReviewAction(tooltip: string, icon: ThemeIcon): Disp order: 7, when: ContextKeyExpr.and( IsSessionsWindowContext, - ChatContextKeys.hasAgentSessionChanges, ChatContextKeys.agentSessionType.notEqualsTo(AgentSessionProviders.Cloud), ), }, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index a9ef3840595..2d3cf87cfc4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -357,7 +357,6 @@ export class ViewAllSessionChangesAction extends Action2 { id: MenuId.ChatEditingSessionChangesToolbar, group: 'navigation', order: 10, - when: ChatContextKeys.hasAgentSessionChanges } ], });