Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
121 changes: 6 additions & 115 deletions src/vs/sessions/contrib/changesView/browser/changesViewActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import './media/changesViewActions.css';
import { $, reset } from '../../../../base/browser/dom.js';
import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
import { autorun, observableFromEvent } from '../../../../base/common/observable.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { localize, localize2 } from '../../../../nls.js';
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { observableFromEvent } from '../../../../base/common/observable.js';
import { localize2 } from '../../../../nls.js';
import { Action2, IAction2Options, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
import { getAgentChangesSummary, hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
import { Menus } from '../../../browser/menus.js';
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
import { CHANGES_VIEW_ID } from './changesView.js';
import { IAction } from '../../../../base/common/actions.js';
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';

import { activeSessionHasChangesContextKey } from '../common/changes.js';
Expand All @@ -34,12 +25,6 @@ const openChangesViewActionOptions: IAction2Options = {
title: localize2('openChangesView', "Changes"),
icon: Codicon.diffMultiple,
f1: false,
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenChangesViewAction is still registered but it’s no longer contributed to any menu and has f1: false, so it becomes effectively unreachable from the UI. Either re-add a menu contribution (e.g. Menus.TitleBarSessionMenu or the new title bar widget) and gating when clause, or make it discoverable via the Command Palette (f1: true).

Suggested change
f1: false,
f1: true,

Copilot uses AI. Check for mistakes.
menu: {
id: Menus.TitleBarSessionMenu,
group: 'navigation',
order: 1,
when: ContextKeyExpr.equals(activeSessionHasChangesContextKey.key, true),
},
};

class OpenChangesViewAction extends Action2 {
Expand All @@ -58,111 +43,17 @@ class OpenChangesViewAction extends Action2 {

registerAction2(OpenChangesViewAction);

/**
* Custom action view item that renders the changes summary as:
* [diff-icon] +insertions -deletions
*/
class ChangesActionViewItem extends BaseActionViewItem {

private _container: HTMLElement | undefined;
private readonly _renderDisposables = this._register(new DisposableStore());

constructor(
action: IAction,
options: IBaseActionViewItemOptions | undefined,
@ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService,
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
@IHoverService private readonly hoverService: IHoverService,
) {
super(undefined, action, options);

this._register(autorun(reader => {
this.sessionManagementService.activeSession.read(reader);
this._updateLabel();
}));

this._register(this.agentSessionsService.model.onDidChangeSessions(() => {
this._updateLabel();
}));
}

override render(container: HTMLElement): void {
super.render(container);
this._container = container;
container.classList.add('changes-action-view-item');
this._updateLabel();
}

private _updateLabel(): void {
if (!this._container) {
return;
}

this._renderDisposables.clear();
reset(this._container);

const activeSession = this.sessionManagementService.getActiveSession();
if (!activeSession) {
this._container.style.display = 'none';
return;
}

const agentSession = this.agentSessionsService.getSession(activeSession.resource);
const changes = agentSession?.changes;

if (!changes || !hasValidDiff(changes)) {
this._container.style.display = 'none';
return;
}

const summary = getAgentChangesSummary(changes);
if (!summary) {
this._container.style.display = 'none';
return;
}

this._container.style.display = '';

// Diff icon
const iconEl = $('span.changes-action-icon' + ThemeIcon.asCSSSelector(Codicon.diffMultiple));
this._container.appendChild(iconEl);

// Insertions
const addedEl = $('span.changes-action-added');
addedEl.textContent = `+${summary.insertions}`;
this._container.appendChild(addedEl);

// Deletions
const removedEl = $('span.changes-action-removed');
removedEl.textContent = `-${summary.deletions}`;
this._container.appendChild(removedEl);

// Hover
this._renderDisposables.add(this.hoverService.setupManagedHover(
getDefaultHoverDelegate('mouse'),
this._container,
localize('agentSessions.viewChanges', "View All Changes")
));
}
}

class ChangesViewActionsContribution extends Disposable implements IWorkbenchContribution {

static readonly ID = 'workbench.contrib.changesViewActions';

constructor(
@IActionViewItemService actionViewItemService: IActionViewItemService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@ISessionsManagementService sessionManagementService: ISessionsManagementService,
@IAgentSessionsService agentSessionsService: IAgentSessionsService,
) {
super();

this._register(actionViewItemService.register(Menus.TitleBarSessionMenu, OpenChangesViewAction.ID, (action, options) => {
return instantiationService.createInstance(ChangesActionViewItem, action, options);
}));

// Bind context key: true when the active session has changes
const sessionsChanged = observableFromEvent(this, agentSessionsService.model.onDidChangeSessions, () => { });
this._register(bindContextKey(activeSessionHasChangesContextKey, contextKeyService, reader => {
Expand Down
15 changes: 13 additions & 2 deletions src/vs/sessions/contrib/chat/browser/branchPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface IBranchItem {
export class BranchPicker extends Disposable {

private _selectedBranch: string | undefined;
private _preferredBranch: string | undefined;
private _newSession: INewSession | undefined;
private _branches: string[] = [];

Expand All @@ -48,6 +49,13 @@ export class BranchPicker extends Disposable {
return this._selectedBranch;
}

/**
* Sets a preferred branch to select when branches are loaded.
*/
setPreferredBranch(branch: string | undefined): void {
this._preferredBranch = branch;
}

constructor(
@IActionWidgetService private readonly actionWidgetService: IActionWidgetService,
) {
Expand Down Expand Up @@ -85,8 +93,11 @@ export class BranchPicker extends Disposable {
.filter((name): name is string => !!name)
.filter(name => !name.includes(COPILOT_WORKTREE_PATTERN));

// Select active branch, main, master, or the first branch by default
const defaultBranch = this._branches.find(b => b === repository.state.get().HEAD?.name)
// Select preferred branch (from draft), active branch, main, master, or the first branch
const preferred = this._preferredBranch;
this._preferredBranch = undefined;
const defaultBranch = (preferred ? this._branches.find(b => b === preferred) : undefined)
?? this._branches.find(b => b === repository.state.get().HEAD?.name)
?? this._branches.find(b => b === 'main')
?? this._branches.find(b => b === 'master')
?? this._branches[0];
Expand Down
Loading