mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-11 04:17:01 -05:00
- remember draft state in new chat (#298983)
- move changes action to title
This commit is contained in:
committed by
GitHub
parent
dce8b9367d
commit
1b92648d63
@@ -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';
|
||||
@@ -34,12 +25,6 @@ const openChangesViewActionOptions: IAction2Options = {
|
||||
title: localize2('openChangesView', "Changes"),
|
||||
icon: Codicon.diffMultiple,
|
||||
f1: false,
|
||||
menu: {
|
||||
id: Menus.TitleBarSessionMenu,
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
when: ContextKeyExpr.equals(activeSessionHasChangesContextKey.key, true),
|
||||
},
|
||||
};
|
||||
|
||||
class OpenChangesViewAction extends Action2 {
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
@@ -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];
|
||||
|
||||
@@ -11,7 +11,7 @@ import { toAction } from '../../../../base/common/actions.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { KeyCode } from '../../../../base/common/keyCodes.js';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { observableValue } from '../../../../base/common/observable.js';
|
||||
import { autorun, observableValue } from '../../../../base/common/observable.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
|
||||
import { Button } from '../../../../base/browser/ui/button/button.js';
|
||||
@@ -56,7 +56,7 @@ import { NewChatContextAttachments } from './newChatContextAttachments.js';
|
||||
import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js';
|
||||
import { FolderPicker } from './folderPicker.js';
|
||||
import { IGitService } from '../../../../workbench/contrib/git/common/gitService.js';
|
||||
import { IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js';
|
||||
import { IsolationMode, IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js';
|
||||
import { BranchPicker } from './branchPicker.js';
|
||||
import { SyncIndicator } from './syncIndicator.js';
|
||||
import { INewSession, ISessionOptionGroup, RemoteNewSession } from './newSession.js';
|
||||
@@ -75,6 +75,14 @@ const STORAGE_KEY_DRAFT_STATE = 'sessions.draftState';
|
||||
const MIN_EDITOR_HEIGHT = 50;
|
||||
const MAX_EDITOR_HEIGHT = 200;
|
||||
|
||||
interface IDraftState extends IChatModelInputState {
|
||||
target?: AgentSessionProviders;
|
||||
isolationMode?: IsolationMode;
|
||||
branch?: string;
|
||||
folderUri?: string;
|
||||
repo?: string;
|
||||
}
|
||||
|
||||
// #region --- Chat Welcome Widget ---
|
||||
|
||||
/**
|
||||
@@ -152,6 +160,16 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
// Slash commands
|
||||
private _slashCommandHandler: SlashCommandHandler | undefined;
|
||||
|
||||
// Input state
|
||||
private _draftState: IDraftState | undefined = {
|
||||
inputText: '',
|
||||
attachments: [],
|
||||
mode: { id: ChatModeKind.Agent, kind: ChatModeKind.Agent },
|
||||
selectedModel: undefined,
|
||||
selections: [],
|
||||
contrib: {}
|
||||
};
|
||||
|
||||
// Input history
|
||||
private readonly _history: ChatHistoryNavigator;
|
||||
private _historyNavigationBackwardsEnablement!: IHistoryNavigationContext['historyNavigationBackwardsEnablement'];
|
||||
@@ -190,6 +208,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._isolationModePicker.setVisible(isLocal);
|
||||
this._branchPicker.setVisible(isLocal);
|
||||
this._syncIndicator.setVisible(isLocal);
|
||||
this._updateDraftState();
|
||||
this._focusEditor();
|
||||
}));
|
||||
|
||||
@@ -200,21 +219,37 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
|
||||
this._register(this._branchPicker.onDidChange((branch) => {
|
||||
this._syncIndicator.setBranch(branch);
|
||||
this._updateDraftState();
|
||||
this._focusEditor();
|
||||
}));
|
||||
|
||||
this._register(this._folderPicker.onDidSelectFolder(() => {
|
||||
this._updateDraftState();
|
||||
this._focusEditor();
|
||||
}));
|
||||
|
||||
this._register(this._isolationModePicker.onDidChange(() => {
|
||||
this._register(this._isolationModePicker.onDidChange((mode) => {
|
||||
this._branchPicker.setVisible(mode === 'worktree');
|
||||
this._syncIndicator.setVisible(mode === 'worktree');
|
||||
this._updateDraftState();
|
||||
this._focusEditor();
|
||||
}));
|
||||
|
||||
this._register(this._repoPicker.onDidSelectRepo(() => {
|
||||
this._updateDraftState();
|
||||
}));
|
||||
|
||||
// When language models change (e.g., extension activates), reinitialize if no model selected
|
||||
this._register(this.languageModelsService.onDidChangeLanguageModels(() => {
|
||||
this._initDefaultModel();
|
||||
}));
|
||||
|
||||
// Update input state when attachments or model change
|
||||
this._register(this._contextAttachments.onDidChangeContext(() => this._updateDraftState()));
|
||||
this._register(autorun(reader => {
|
||||
this._currentLanguageModel.read(reader);
|
||||
this._updateDraftState();
|
||||
}));
|
||||
}
|
||||
|
||||
// --- Rendering ---
|
||||
@@ -261,11 +296,12 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._branchPicker.render(branchContainer);
|
||||
this._syncIndicator.render(branchContainer);
|
||||
|
||||
// Set initial visibility based on default target
|
||||
// Set initial visibility based on default target and isolation mode
|
||||
const isLocal = this._targetPicker.selectedTarget === AgentSessionProviders.Background;
|
||||
const isWorktree = this._isolationModePicker.isolationMode === 'worktree';
|
||||
this._isolationModePicker.setVisible(isLocal);
|
||||
this._branchPicker.setVisible(isLocal);
|
||||
this._syncIndicator.setVisible(isLocal);
|
||||
this._branchPicker.setVisible(isLocal && isWorktree);
|
||||
this._syncIndicator.setVisible(isLocal && isWorktree);
|
||||
|
||||
// Render target buttons & extension pickers
|
||||
this._renderOptionGroupPickers();
|
||||
@@ -517,6 +553,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._slashCommandHandler = this._register(this.instantiationService.createInstance(SlashCommandHandler, this._editor));
|
||||
|
||||
this._register(this._editor.onDidChangeModelContent(() => {
|
||||
this._updateDraftState();
|
||||
this._updateSendButtonState();
|
||||
}));
|
||||
}
|
||||
@@ -848,9 +885,8 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
if (this._history.isAtStart()) {
|
||||
return;
|
||||
}
|
||||
const state = this._getInputState();
|
||||
if (state.inputText || state.attachments.length) {
|
||||
this._history.overlay(state);
|
||||
if (this._draftState?.inputText || this._draftState?.attachments.length) {
|
||||
this._history.overlay(this._draftState);
|
||||
}
|
||||
this._navigateHistory(true);
|
||||
}
|
||||
@@ -859,21 +895,26 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
if (this._history.isAtEnd()) {
|
||||
return;
|
||||
}
|
||||
const state = this._getInputState();
|
||||
if (state.inputText || state.attachments.length) {
|
||||
this._history.overlay(state);
|
||||
if (this._draftState?.inputText || this._draftState?.attachments.length) {
|
||||
this._history.overlay(this._draftState);
|
||||
}
|
||||
this._navigateHistory(false);
|
||||
}
|
||||
|
||||
private _getInputState(): IChatModelInputState {
|
||||
return {
|
||||
private _updateDraftState(): void {
|
||||
const attachments = [...this._contextAttachments.attachments];
|
||||
this._draftState = {
|
||||
inputText: this._editor?.getModel()?.getValue() ?? '',
|
||||
attachments: [...this._contextAttachments.attachments],
|
||||
attachments,
|
||||
mode: { id: ChatModeKind.Agent, kind: ChatModeKind.Agent },
|
||||
selectedModel: this._currentLanguageModel.get(),
|
||||
selections: this._editor?.getSelections() ?? [],
|
||||
contrib: {},
|
||||
target: this._targetPicker.selectedTarget,
|
||||
isolationMode: this._isolationModePicker.isolationMode,
|
||||
branch: this._branchPicker.selectedBranch,
|
||||
folderUri: this._folderPicker.selectedFolderUri?.toString(),
|
||||
repo: this._repoPicker.selectedRepo,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -932,7 +973,9 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined
|
||||
);
|
||||
|
||||
this._history.append(this._getInputState());
|
||||
if (this._draftState) {
|
||||
this._history.append(this._draftState);
|
||||
}
|
||||
this._clearDraftState();
|
||||
|
||||
this._sending = true;
|
||||
@@ -940,22 +983,23 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._updateSendButtonState();
|
||||
this._updateInputLoadingState();
|
||||
|
||||
this.sessionsManagementService.sendRequestForNewSession(
|
||||
session.resource,
|
||||
options?.openNewAfterSend ? { openNewSessionView: true } : undefined
|
||||
).then(() => {
|
||||
// Release ref without disposing - the service owns disposal
|
||||
this._newSession.clearAndLeak();
|
||||
|
||||
try {
|
||||
await this.sessionsManagementService.sendRequestForNewSession(
|
||||
session.resource,
|
||||
options?.openNewAfterSend ? { openNewSessionView: true } : undefined
|
||||
);
|
||||
this._newSessionListener.clear();
|
||||
this._contextAttachments.clear();
|
||||
}, e => {
|
||||
} catch (e) {
|
||||
this.logService.error('Failed to send request:', e);
|
||||
}).finally(() => {
|
||||
this._sending = false;
|
||||
this._editor.updateOptions({ readOnly: false });
|
||||
this._updateSendButtonState();
|
||||
this._updateInputLoadingState();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this._sending = false;
|
||||
this._editor.updateOptions({ readOnly: false });
|
||||
this._updateSendButtonState();
|
||||
this._updateInputLoadingState();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1001,10 +1045,23 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
this._currentLanguageModel.set(model, undefined);
|
||||
}
|
||||
}
|
||||
if (draft.isolationMode) {
|
||||
this._isolationModePicker.setPreferredIsolationMode(draft.isolationMode);
|
||||
this._isolationModePicker.setIsolationMode(draft.isolationMode);
|
||||
}
|
||||
if (draft.branch) {
|
||||
this._branchPicker.setPreferredBranch(draft.branch);
|
||||
}
|
||||
if (draft.folderUri) {
|
||||
try { this._folderPicker.setSelectedFolder(URI.parse(draft.folderUri)); } catch { /* ignore */ }
|
||||
}
|
||||
if (draft.repo) {
|
||||
this._repoPicker.setSelectedRepo(draft.repo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getDraftState(): (IChatModelInputState & { target?: AgentSessionProviders }) | undefined {
|
||||
private _getDraftState(): IDraftState | undefined {
|
||||
const raw = this.storageService.get(STORAGE_KEY_DRAFT_STATE, StorageScope.WORKSPACE);
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
@@ -1017,21 +1074,20 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
|
||||
}
|
||||
|
||||
private _clearDraftState(): void {
|
||||
this._draftState = undefined;
|
||||
this.storageService.remove(STORAGE_KEY_DRAFT_STATE, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
saveState(): void {
|
||||
const inputState = this._getInputState();
|
||||
const state = {
|
||||
...inputState,
|
||||
attachments: inputState.attachments.map(IChatRequestVariableEntry.toExport),
|
||||
target: this._targetPicker.selectedTarget,
|
||||
};
|
||||
this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(state), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
if (this._draftState) {
|
||||
const state = {
|
||||
...this._draftState,
|
||||
attachments: this._draftState.attachments.map(IChatRequestVariableEntry.toExport),
|
||||
};
|
||||
this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(state), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Layout ---
|
||||
|
||||
layout(_height: number, _width: number): void {
|
||||
this._editor?.layout();
|
||||
}
|
||||
@@ -1118,6 +1174,11 @@ export class NewChatViewPane extends ViewPane {
|
||||
override saveState(): void {
|
||||
this._widget?.saveState();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._widget?.saveState();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -137,6 +137,7 @@ export type IsolationMode = 'worktree' | 'workspace';
|
||||
export class IsolationModePicker extends Disposable {
|
||||
|
||||
private _isolationMode: IsolationMode = 'worktree';
|
||||
private _preferredIsolationMode: IsolationMode | undefined;
|
||||
private _newSession: INewSession | undefined;
|
||||
private _repository: IGitRepository | undefined;
|
||||
|
||||
@@ -171,7 +172,9 @@ export class IsolationModePicker extends Disposable {
|
||||
setRepository(repository: IGitRepository | undefined): void {
|
||||
this._repository = repository;
|
||||
if (repository) {
|
||||
this._setMode('worktree');
|
||||
const preferred = this._preferredIsolationMode;
|
||||
this._preferredIsolationMode = undefined;
|
||||
this._setMode(preferred ?? 'worktree');
|
||||
} else if (this._isolationMode === 'worktree') {
|
||||
this._setMode('workspace');
|
||||
}
|
||||
@@ -207,6 +210,20 @@ export class IsolationModePicker extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a preferred isolation mode to apply when a repository is set.
|
||||
*/
|
||||
setPreferredIsolationMode(mode: IsolationMode): void {
|
||||
this._preferredIsolationMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatically set the isolation mode.
|
||||
*/
|
||||
setIsolationMode(mode: IsolationMode): void {
|
||||
this._setMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows or hides the picker.
|
||||
*/
|
||||
|
||||
@@ -76,3 +76,19 @@
|
||||
opacity: 0.5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Changes summary */
|
||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes-added {
|
||||
color: var(--vscode-gitDecoration-addedResourceForeground);
|
||||
}
|
||||
|
||||
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes-removed {
|
||||
color: var(--vscode-gitDecoration-deletedResourceForeground);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,8 @@ import { IMenuService, MenuId, MenuRegistry, SubmenuItemAction } from '../../../
|
||||
import { IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
|
||||
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
|
||||
import { IMarshalledAgentSessionContext } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
|
||||
import { IMarshalledAgentSessionContext, getAgentChangesSummary, hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
|
||||
import { IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
|
||||
|
||||
import { Menus } from '../../../browser/menus.js';
|
||||
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
|
||||
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
||||
@@ -128,9 +127,10 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
||||
const label = this._getActiveSessionLabel();
|
||||
const icon = this._getActiveSessionIcon();
|
||||
const repoLabel = this._getRepositoryLabel();
|
||||
const changesSummary = this._getChangesSummary();
|
||||
|
||||
// Build a render-state key from all displayed data
|
||||
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}`;
|
||||
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}|${changesSummary?.insertions ?? ''}|${changesSummary?.deletions ?? ''}`;
|
||||
|
||||
// Skip re-render if state hasn't changed
|
||||
if (this._lastRenderState === renderState) {
|
||||
@@ -175,6 +175,25 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
||||
centerGroup.appendChild(repoEl);
|
||||
}
|
||||
|
||||
// Changes summary shown next to the repo
|
||||
if (changesSummary) {
|
||||
const separator2 = $('span.agent-sessions-titlebar-separator');
|
||||
separator2.textContent = '\u00B7';
|
||||
centerGroup.appendChild(separator2);
|
||||
|
||||
const changesEl = $('span.agent-sessions-titlebar-changes');
|
||||
|
||||
const addedEl = $('span.agent-sessions-titlebar-changes-added');
|
||||
addedEl.textContent = `+${changesSummary.insertions}`;
|
||||
changesEl.appendChild(addedEl);
|
||||
|
||||
const removedEl = $('span.agent-sessions-titlebar-changes-removed');
|
||||
removedEl.textContent = `-${changesSummary.deletions}`;
|
||||
changesEl.appendChild(removedEl);
|
||||
|
||||
centerGroup.appendChild(changesEl);
|
||||
}
|
||||
|
||||
sessionPill.appendChild(centerGroup);
|
||||
|
||||
// Click handler on pill - show sessions picker
|
||||
@@ -336,6 +355,24 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
|
||||
menu.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the changes summary for the active session.
|
||||
*/
|
||||
private _getChangesSummary(): { insertions: number; deletions: number } | undefined {
|
||||
const activeSession = this.activeSessionService.getActiveSession();
|
||||
if (!activeSession) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const agentSession = this.agentSessionsService.getSession(activeSession.resource);
|
||||
const changes = agentSession?.changes;
|
||||
if (!changes || !hasValidDiff(changes)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return getAgentChangesSummary(changes);
|
||||
}
|
||||
|
||||
private _showSessionsPicker(): void {
|
||||
const picker = this.instantiationService.createInstance(AgentSessionsPicker, undefined, {
|
||||
overrideSessionOpen: (session, openOptions) => this.activeSessionService.openSession(session.resource, openOptions)
|
||||
|
||||
Reference in New Issue
Block a user