Skip to content
Merged
Show file tree
Hide file tree
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
36 changes: 0 additions & 36 deletions src/vs/sessions/browser/parts/chatCompositeBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,7 @@ import { ISessionsProvidersService } from '../../services/sessions/browser/sessi
import { isAgentHostProvider } from '../../common/agentHostSessionsProvider.js';
import { ICommandService } from '../../../platform/commands/common/commands.js';
import { CLOSE_CHAT_COMMAND_ID } from '../../common/sessionCommands.js';
import { MenuItemAction } from '../../../platform/actions/common/actions.js';
import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js';
import { SessionActivatingActionRunner } from '../sessionActionRunner.js';
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
import { getSessionConversationStatusAriaLabel } from '../sessionConversationGroups.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';

interface IChatTab {
readonly chat: IChat;
Expand Down Expand Up @@ -115,9 +108,6 @@ export class ChatCompositeBar extends Disposable {
private readonly _newChatContainer: HTMLElement;
private readonly _sessionActionsContainer: HTMLElement;
private readonly _sessionToolbar: MenuWorkbenchToolBar;
private readonly _metaRow: HTMLElement;
private readonly _metaToolbar: MenuWorkbenchToolBar;
private readonly _showMetadataInChatInput: IObservable<boolean>;
private readonly _tabs: IChatTab[] = [];
private readonly _tabDisposables = this._register(new DisposableStore());

Expand All @@ -126,7 +116,6 @@ export class ChatCompositeBar extends Disposable {
private _editingTab: IChatTab | undefined;
private _delegate: IChatCompositeBarDelegate | undefined;
private _showSessionActions = false;
private _metadataInInput = false;

private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
Expand Down Expand Up @@ -157,12 +146,9 @@ export class ChatCompositeBar extends Disposable {
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService,
@ICommandService private readonly _commandService: ICommandService,
@ISessionsService sessionsService: ISessionsService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();

this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService);
this._container = $('.chat-composite-bar.session-chat-tabs-bar');

// Tabs row — only shown when the group has multiple chats or is split out.
Expand Down Expand Up @@ -202,21 +188,6 @@ export class ChatCompositeBar extends Disposable {
highlightToggledItems: true,
}));

this._metaRow = $('.chat-composite-bar-meta-row');
this._container.appendChild(this._metaRow);
const metaToolbarContainer = $('.chat-composite-bar-meta-toolbar');
this._metaRow.appendChild(metaToolbarContainer);
const metaActionRunner = this._register(new SessionActivatingActionRunner(() => this._delegate?.session, sessionsService));
this._metaToolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, metaToolbarContainer, Menus.SessionHeaderMeta, {
hiddenItemStrategy: HiddenItemStrategy.Ignore,
menuOptions: { shouldForwardArgs: true },
actionRunner: metaActionRunner,
actionViewItemProvider: (action, options) => action instanceof MenuItemAction
? this._instantiationService.createInstance(ChatPillActionViewItem, undefined, action, options)
: undefined,
}));
this._register(this._metaToolbar.onDidChangeMenuItems(() => this._updateMetaRowVisibility()));

const preventMiddleButtonDefault = (e: MouseEvent) => {
if (e.button === 1 && !this._isInTabInput(e)) {
e.preventDefault();
Expand Down Expand Up @@ -269,7 +240,6 @@ export class ChatCompositeBar extends Disposable {

this._delegate = delegate;
this._sessionToolbar.context = delegate?.session;
this._metaToolbar.context = delegate?.session;

const store = new DisposableStore();
this._groupDisposables.value = store;
Expand All @@ -292,18 +262,12 @@ export class ChatCompositeBar extends Disposable {
this._newChatContainer.classList.toggle('hidden', !supportsMultipleChats || isQuickChat);
this._newChatAction.enabled = supportsMultipleChats && !isQuickChat && !delegate.session.isArchived.read(reader);
this._showSessionActions = delegate.showSessionActions.read(reader);
this._metadataInInput = this._showMetadataInChatInput.read(reader);
this._sessionActionsContainer.classList.toggle('hidden', !this._showSessionActions);
this._updateMetaRowVisibility();

this._setVisible(delegate.visible.read(reader));
}));
}

private _updateMetaRowVisibility(): void {
this._metaRow.style.display = this._showSessionActions && !this._metadataInInput && !this._metaToolbar.isEmpty() ? '' : 'none';
}

setAriaLabel(label: string): void {
this._tabsContainer.setAttribute('aria-label', label);
}
Expand Down
34 changes: 9 additions & 25 deletions src/vs/sessions/browser/parts/media/chatCompositeBar.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
overflow: hidden;
}

/* Header host: title row + meta row. */
/* Header host. */
.chat-composite-bar.session-header-bar {
padding: 0 var(--vscode-spacing-size100);
box-sizing: border-box;
Expand All @@ -29,8 +29,7 @@
--chat-tab-max-width: min(200px, 40cqi);
}

/* Header: a status icon column next to a main column (title row + meta row).
Mirrors the sessions list so the meta row aligns under the title. */
/* Header: a status icon column next to the title row. */
.chat-composite-bar-header {
display: flex;
flex-direction: row;
Expand All @@ -39,7 +38,7 @@
border-bottom: var(--vscode-strokeThickness) solid color-mix(in srgb, var(--session-view-foreground, var(--chat-tab-active-foreground)) 12%, transparent);
}

/* Main column stacks the title row and the meta row */
/* Main column hosts the title row. */
.chat-composite-bar-header-main {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -146,34 +145,19 @@
margin-left: auto;
}

/* Meta row: workspace + contributed changes / pull request buttons.
No `overflow: hidden` here — it would clip the meta buttons' focus ring at the
row's 22px height. The workspace label self-truncates via its own containers. */
.chat-composite-bar-meta-row {
display: flex;
align-items: center;
gap: 6px;
height: 22px;
font-size: var(--vscode-agents-fontSize-label1, 12px);
font-weight: var(--vscode-agents-fontWeight-regular, 400);
line-height: 18px;
color: var(--chat-tab-inactive-foreground, var(--session-view-foreground));
white-space: nowrap;
}

/* Session header meta toolbar */
.chat-composite-bar-meta-toolbar,
.chat-composite-bar-meta-toolbar .monaco-action-bar,
.chat-composite-bar-meta-toolbar .actions-container {
/* Metadata action fixture host. */
.session-metadata-pill-toolbar,
.session-metadata-pill-toolbar .monaco-action-bar,
.session-metadata-pill-toolbar .actions-container {
display: inline-flex;
align-items: center;
height: 100%;
min-width: 0;
max-width: 100%;
}

/* Spacing between the contributed meta buttons (e.g. changes · pull request). */
.chat-composite-bar-meta-toolbar .actions-container {
/* Spacing between contributed metadata buttons. */
.session-metadata-pill-toolbar .actions-container {
gap: 6px;
}

Expand Down
78 changes: 8 additions & 70 deletions src/vs/sessions/browser/parts/sessionHeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import { $, addDisposableGenericMouseDownListener, addDisposableListener, addSta
import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js';
import { IKeyboardEvent } from '../../../base/browser/keyboardEvent.js';
import { KeyCode } from '../../../base/common/keyCodes.js';
import { autorun, IObservable, IReader, observableSignalFromEvent } from '../../../base/common/observable.js';
import { autorun, IReader } from '../../../base/common/observable.js';
import { IThemeService } from '../../../platform/theme/common/themeService.js';
import { localize } from '../../../nls.js';
import { IActiveSession, ISessionsManagementService } from '../../services/sessions/common/sessionsManagement.js';
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
import { getUntitledSessionTitle } from '../../services/sessions/common/session.js';
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
import { MenuItemAction } from '../../../platform/actions/common/actions.js';
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
import { Menus } from '../menus.js';
import { LocalSelectionTransfer } from '../../../platform/dnd/browser/dnd.js';
Expand All @@ -28,16 +26,10 @@ import { applySessionBarThemeColors } from './sessionBarStyles.js';
import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
import { onUnexpectedError } from '../../../base/common/errors.js';
import { SessionStatusIcon } from '../sessionStatusIcon.js';
import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';
import { SessionActivatingActionRunner } from '../sessionActionRunner.js';

/**
* The session header shown at the top of a session view. It surfaces the session
* identity, optional workspace metadata, contributed metadata pills, and the
* session toolbars.
* identity and session toolbar.
*
* It is intentionally decoupled from the {@link ChatCompositeBar} (the chat tab
* strip) so the two surfaces evolve independently. The hosting view tells the
Expand All @@ -49,9 +41,7 @@ export class SessionHeader extends Disposable {
private readonly _iconEl: HTMLElement;
private readonly _titleEl: HTMLElement;
private readonly _titleTextEl: HTMLElement;
private readonly _metaRow: HTMLElement;
private readonly _toolbar: MenuWorkbenchToolBar;
private readonly _metaToolbar: MenuWorkbenchToolBar;
private readonly _titleActionsEl: HTMLElement;

private readonly _sessionDisposables = this._register(new MutableDisposable<DisposableStore>());
Expand All @@ -75,9 +65,6 @@ export class SessionHeader extends Disposable {

private readonly _sessionTransfer = LocalSelectionTransfer.getInstance<DraggedSessionIdentifier>();

private readonly _metaActionsSignal: IObservable<void>;
private readonly _showMetadataInChatInput: IObservable<boolean>;

private readonly _statusIcon: SessionStatusIcon;

get element(): HTMLElement {
Expand All @@ -98,18 +85,12 @@ export class SessionHeader extends Disposable {
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ISessionsManagementService private readonly _sessionsManagementService: ISessionsManagementService,
@ISessionsService private readonly _sessionsService: ISessionsService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();

this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService);
this._container = $('.chat-composite-bar.session-header-bar');

// Header: a status icon column alongside a main column that stacks the title
// row (title + actions) and the meta row (workspace · diff). This mirrors the
// sessions list so the meta row aligns under the title rather than under the
// status icon.
// Header: a status icon column alongside the title and actions.
const header = $('.chat-composite-bar-header');
this._container.appendChild(header);

Expand Down Expand Up @@ -150,41 +131,7 @@ export class SessionHeader extends Disposable {
highlightToggledItems: true,
}));

this._metaRow = $('.chat-composite-bar-meta-row');
main.appendChild(this._metaRow);

// Session header meta toolbar. Actions are contributed into the generic
// Menus.SessionHeaderMeta menu: the files view contributes the workspace
// folder pill (opens the Files view), the changes view contributes the
// diff-stats action (opens the multi-file diff editor) and the GitHub
// contribution contributes the pull request pill (opens the PR on GitHub),
// each rendered as a compact secondary button pill via
// ChatPillActionViewItem.
const metaToolbarContainer = $('.chat-composite-bar-meta-toolbar');
this._metaRow.appendChild(metaToolbarContainer);
// Commands contributed into the header meta toolbar (e.g. View All Changes)
// operate on this view's session. Promote it to the active session before
// running any of them via a custom action runner, so the command always
// targets the clicked session even when another session is active.
const metaActionRunner = this._register(new SessionActivatingActionRunner(() => this._session, this._sessionsService));
this._metaToolbar = this._register(instantiationService.createInstance(MenuWorkbenchToolBar, metaToolbarContainer, Menus.SessionHeaderMeta, {
hiddenItemStrategy: HiddenItemStrategy.Ignore,
menuOptions: { shouldForwardArgs: true },
actionRunner: metaActionRunner,
// Render every meta action as a consistent `icon title` pill unless it
// registers its own action view item via IActionViewItemService.
actionViewItemProvider: (action, options) => {
if (action instanceof MenuItemAction) {
return instantiationService.createInstance(ChatPillActionViewItem, undefined, action, options);
}
return undefined;
},
}));
// The meta row separator/visibility tracks whether the meta toolbar has any
// contributed actions, so recompute the header whenever they change.
this._metaActionsSignal = observableSignalFromEvent(this, this._metaToolbar.onDidChangeMenuItems);

// Report height changes (e.g. meta row content wrapping) so the host can re-layout
// Report height changes so the host can re-layout.
const heightObserver = this._register(new DisposableResizeObserver('SessionHeader.height', () => {
this._onDidChangeHeight.fire();
}));
Expand Down Expand Up @@ -235,9 +182,9 @@ export class SessionHeader extends Disposable {
return;
}

// Don't swallow a click on the toolbar or meta row pills into a session drag.
// Don't swallow a click on the toolbar into a session drag.
const target = this._lastPointerDownTarget;
if (target && (this._titleActionsEl.contains(target) || this._metaRow.contains(target))) {
if (target && this._titleActionsEl.contains(target)) {
e.preventDefault();
return;
}
Expand Down Expand Up @@ -276,7 +223,6 @@ export class SessionHeader extends Disposable {
this._cancelTitleEditing();
this._session = session;
this._toolbar.context = session;
this._metaToolbar.context = session;
this._statusIcon.reset();

const store = new DisposableStore();
Expand Down Expand Up @@ -313,8 +259,8 @@ export class SessionHeader extends Disposable {
private _updateHeader(session: IActiveSession, reader: IReader): void {
// Session icon — the SessionStatusIcon widget owns the rendering (spinner vs.
// codicon, cross-fade, reduced-motion); here we just feed it the latest state.
// The pull request is surfaced in the meta row, so in terminal/default states the
// title shows the read/unread dot indicator (no session type or PR icon).
// Metadata is surfaced above the chat input, so the title keeps the
// read/unread status indicator.
const status = session.status.read(reader);
const isRead = session.isRead.read(reader);
const isArchived = session.isArchived.read(reader);
Expand All @@ -324,14 +270,6 @@ export class SessionHeader extends Disposable {
const isQuickChat = session.isQuickChat?.read(reader) ?? false;
this._titleTextEl.textContent = session.title.read(reader) || getUntitledSessionTitle(isQuickChat);
this._titleEl.classList.toggle('editable', this._isTitleEditable());
const showMetadataInChatInput = this._showMetadataInChatInput.read(reader);

// Meta row: contributed action pills (workspace folder · diff stats · pull request).
// Reading the signal re-runs this on menu changes.
this._metaActionsSignal.read(reader);
const hasMetaActions = !this._metaToolbar.isEmpty();

this._metaRow.style.display = !showMetadataInChatInput && hasMetaActions ? '' : 'none';
this._onDidChangeHeight.fire();
}

Expand Down
11 changes: 2 additions & 9 deletions src/vs/sessions/browser/parts/sessionView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,12 @@ import { AbstractChatView, IChatViewOptions } from './chatView.js';
import { ChatGroupsView } from './chatGroupsView.js';
import { SessionHeader, SessionViewFloatingToolbar } from './sessionHeader.js';
import { ISessionContext, SessionContext } from '../../services/sessions/browser/sessionContext.js';
import { autorun, IObservable, observableValue } from '../../../base/common/observable.js';
import { autorun, observableValue } from '../../../base/common/observable.js';
import { SessionIsMaximizedContext } from '../../common/contextkeys.js';
import { AGENTS_CENTERED_CONTENT_MAX_WIDTH } from '../../common/layoutConstants.js';
import { setActiveSessionContextKeys } from '../../services/sessions/common/sessionContextKeys.js';
import { applySessionViewThemeColors } from './sessionBarStyles.js';
import { IChatViewFactory } from '../../services/chatView/browser/chatViewFactory.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';

/**
* Options passed to {@link SessionView.openSession}. Extends the chat view
Expand Down Expand Up @@ -86,18 +83,15 @@ export class SessionView extends Disposable implements ISerializableView {
private _isLeafVisible = true;

private readonly _sessionObs = observableValue<IActiveSession | undefined>(this, undefined);
private readonly _showMetadataInChatInput: IObservable<boolean>;

constructor(
@IChatViewFactory private readonly _chatViewFactory: IChatViewFactory,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService private readonly themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();

this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService);
// Scoped context key service so toolbars hosted within can react to
// session-specific context keys (e.g. sessionIsCreated, sessionIsSticky).
const scopedContextKeyService = this._scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));
Expand Down Expand Up @@ -157,8 +151,7 @@ export class SessionView extends Disposable implements ISerializableView {

this._register(autorun(reader => {
const session = this._sessionObs.read(reader);
const tabsReplaceHeader = this._showMetadataInChatInput.read(reader)
&& this._groupsView.groupCount.read(reader) === 1
const tabsReplaceHeader = this._groupsView.groupCount.read(reader) === 1
&& (session?.isCreated.read(reader) ?? false)
&& (session?.shouldShowChatTabs.read(reader) ?? false);
this._header.setVisible(!tabsReplaceHeader);
Expand Down
2 changes: 0 additions & 2 deletions src/vs/sessions/common/sessionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import type { ResolveSessionConfigResult } from '../../platform/agentHost/common
*/
export const DOCK_DETAIL_PANEL_SETTING = 'sessions.layout.singlePaneDetailPanel';

export const SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING = 'chat.agentSessions.showSessionMetadataInInput';

export function isSessionConfigComplete(config: ResolveSessionConfigResult): boolean {
return (config.schema.required ?? []).every(property => config.values[property] !== undefined);
}
Loading