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
8 changes: 7 additions & 1 deletion src/vs/sessions/browser/parts/chatGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { getChatSessionArchiveActionPresentation, getChatSessionArchiveActionWor
import { ChatInteractivity, IChat, isSideChatOf, SessionStatus } from '../../services/sessions/common/session.js';
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
import { UNARCHIVE_SESSION_COMMAND_ID } from '../../common/sessionCommands.js';
import { SessionActiveChatHasSideChatsContext, SessionActiveChatIsClosableContext, SessionActiveChatResourceContext, SessionFocusedChatIsRenameTargetContext, SessionHeaderActiveChatIsPinnedContext, SessionHeaderShowsChatContext } from '../../common/contextkeys.js';
import { SessionActiveChatHasSideChatsContext, SessionActiveChatIsClosableContext, SessionActiveChatResourceContext, SessionFocusedChatIsRenameTargetContext, SessionHeaderActiveChatIsPinnedContext, SessionHeaderShowsChatContext, SessionToolbarShowsSessionContext } from '../../common/contextkeys.js';
import { IChatViewFactory } from '../../services/chatView/browser/chatViewFactory.js';
import { ChatCompositeBar, IChatCompositeBarDelegate } from './chatCompositeBar.js';
import { type IRemoteHostUnavailableEmptyStateContent, RemoteHostUnavailableEmptyState } from './remoteHostUnavailableEmptyState.js';
Expand Down Expand Up @@ -117,6 +117,7 @@ export class ChatGroupView extends Disposable implements ISerializableView {
private readonly _activeChatResourceKey: IContextKey<string>;
private readonly _activeChatHasSideChatsKey: IContextKey<boolean>;
private readonly _headerShowsChatKey: IContextKey<boolean>;
private readonly _toolbarShowsSessionKey: IContextKey<boolean>;
private readonly _focusedChatIsRenameTargetKey: IContextKey<boolean>;
private readonly _connection: SessionRemoteConnection;

Expand Down Expand Up @@ -153,6 +154,7 @@ export class ChatGroupView extends Disposable implements ISerializableView {
this._activeChatResourceKey = SessionActiveChatResourceContext.bindTo(scopedContextKeyService);
this._activeChatHasSideChatsKey = SessionActiveChatHasSideChatsContext.bindTo(scopedContextKeyService);
this._headerShowsChatKey = SessionHeaderShowsChatContext.bindTo(scopedContextKeyService);
this._toolbarShowsSessionKey = SessionToolbarShowsSessionContext.bindTo(scopedContextKeyService);
this._focusedChatIsRenameTargetKey = SessionFocusedChatIsRenameTargetContext.bindTo(scopedContextKeyService);

// Assigned here rather than as a field initializer: `_instantiationService`
Expand Down Expand Up @@ -233,6 +235,7 @@ export class ChatGroupView extends Disposable implements ISerializableView {
this._activeChatResourceKey.reset();
this._activeChatHasSideChatsKey.reset();
this._headerShowsChatKey.reset();
this._toolbarShowsSessionKey.reset();
this._focusedChatIsRenameTargetKey.reset();
this._chatHeader.setChat(undefined);
this._compositeBar.setGroup(undefined);
Expand All @@ -256,6 +259,9 @@ export class ChatGroupView extends Disposable implements ISerializableView {
this._chatHeader.setVisible(visible);
this._headerShowsChatKey.set(visible);
}));
this._contextDisposables.add(autorun(reader => {
this._toolbarShowsSessionKey.set(context.showSessionActions.read(reader));
}));

const delegate: IChatCompositeBarDelegate = {
session: context.session,
Expand Down
6 changes: 6 additions & 0 deletions src/vs/sessions/browser/parts/sessionHeader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import { Event } from '../../../base/common/event.js';
import { Disposable } from '../../../base/common/lifecycle.js';
import { IContextKey, IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
import { SessionToolbarShowsSessionContext } from '../../common/contextkeys.js';
import { IActiveSession } from '../../services/sessions/common/sessionsManagement.js';
import { SessionHeaderBar } from './sessionHeaderBar.js';

Expand All @@ -15,6 +17,7 @@ import { SessionHeaderBar } from './sessionHeaderBar.js';
export class SessionHeader extends Disposable {

private readonly _bar: SessionHeaderBar;
private readonly _toolbarShowsSessionKey: IContextKey<boolean>;

get element(): HTMLElement { return this._bar.element; }
get visible(): boolean { return this._bar.visible; }
Expand All @@ -24,12 +27,15 @@ export class SessionHeader extends Disposable {

constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super();
this._toolbarShowsSessionKey = SessionToolbarShowsSessionContext.bindTo(contextKeyService);
this._bar = this._register(instantiationService.createInstance(SessionHeaderBar));
}

setSession(session: IActiveSession | undefined): void {
this._toolbarShowsSessionKey.set(!!session);
this._bar.setContext(session ? { session, chat: session.activeChat } : undefined);
}

Expand Down
1 change: 1 addition & 0 deletions src/vs/sessions/common/contextkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const SessionActiveChatHasSideChatsContext = new RawContextKey<boolean>('
export const SessionShouldShowChatTabsContext = new RawContextKey<boolean>('sessionShouldShowChatTabs', false, localize('sessionShouldShowChatTabs', "Whether the session view's chat tab strip is shown, i.e. the session has more than one chat actually showing as a tab. A single visible tab always hides the strip"));
export const SessionHasMultipleOpenChatsContext = new RawContextKey<boolean>('sessionHasMultipleOpenChats', false, localize('sessionHasMultipleOpenChats', "Whether the session view's session has more than one open chat (the tabs shown in the strip, including in-composer drafts). Used to scope chat-to-chat navigation (next/previous chat, the Ctrl+Tab chat switcher)"));
export const SessionActiveChatIsClosableContext = new RawContextKey<boolean>('sessionActiveChatIsClosable', false, localize('sessionActiveChatIsClosable', "Whether the active chat surface can be closed, either by hiding a non-main chat or by removing a side-by-side chat group"));
export const SessionToolbarShowsSessionContext = new RawContextKey<boolean>('sessionToolbarShowsSession', false, localize('sessionToolbarShowsSession', "Whether the session toolbar represents the whole session rather than a side-by-side chat group"));
export const SessionHeaderShowsChatContext = new RawContextKey<boolean>('sessionHeaderShowsChat', false, localize('sessionHeaderShowsChat', "Whether the session-style header represents one chat in a side-by-side chat group rather than the whole session"));
export const SessionHeaderActiveChatIsPinnedContext = new RawContextKey<boolean>('sessionHeaderActiveChatIsPinned', false, localize('sessionHeaderActiveChatIsPinned', "Whether the chat represented by a side-by-side chat group header is pinned"));
export const SessionFocusedChatIsRenameTargetContext = new RawContextKey<boolean>('sessionFocusedChatIsRenameTarget', false, localize('sessionFocusedChatIsRenameTarget', "Whether the focused chat group's visible chat is a non-main chat that should receive the chat-specific rename command instead of the session rename command"));
Expand Down
23 changes: 22 additions & 1 deletion src/vs/sessions/contrib/sessions/browser/sessionsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { IViewsService } from '../../../../workbench/services/views/common/views
import { getQuickNavigateHandler, inQuickPickContext } from '../../../../workbench/browser/quickaccess.js';
import { Menus } from '../../../browser/menus.js';
import { SessionsCategories } from '../../../common/categories.js';
import { CanGoBackContext, CanGoForwardContext, SessionProviderIdContext, MultipleSessionsVisibleContext, SessionIsArchivedContext, SessionIsCreatedContext, SessionIsMaximizedContext, SessionIsStickyContext, SessionsFocusContext, SessionSupportsMultipleChatsContext, SessionSupportsRenameContext, SessionsWelcomeVisibleContext, SessionIdContext, SessionHasMultipleCommittedChatsContext, SessionHasMultipleOpenChatsContext, SessionsPickerVisibleContext, SessionActiveChatIsClosableContext, SessionFocusedChatIsRenameTargetContext, SessionActiveChatIsDeletableContext, SessionChatsPickerVisibleContext, SessionActiveChatHasSideChatsContext, SessionActiveChatResourceContext, SessionsTitleBarNewSessionEnabledContext, SessionsEditorScopeContext, SessionsHasClosedItemContext, IsNewChatSessionContext, IsQuickChatSessionContext, SessionsListPromoteNewChatActionContext, SessionHeaderActiveChatIsPinnedContext, SessionHeaderShowsChatContext, SessionItemIsMultiSelectionContext, IsPhoneLayoutContext } from '../../../common/contextkeys.js';
import { CanGoBackContext, CanGoForwardContext, SessionProviderIdContext, MultipleSessionsVisibleContext, SessionIsArchivedContext, SessionIsCreatedContext, SessionIsMaximizedContext, SessionIsStickyContext, SessionsFocusContext, SessionSupportsMultipleChatsContext, SessionSupportsRenameContext, SessionsWelcomeVisibleContext, SessionIdContext, SessionHasMultipleCommittedChatsContext, SessionHasMultipleOpenChatsContext, SessionsPickerVisibleContext, SessionActiveChatIsClosableContext, SessionFocusedChatIsRenameTargetContext, SessionActiveChatIsDeletableContext, SessionChatsPickerVisibleContext, SessionActiveChatHasSideChatsContext, SessionActiveChatResourceContext, SessionsTitleBarNewSessionEnabledContext, SessionsEditorScopeContext, SessionsHasClosedItemContext, IsNewChatSessionContext, IsQuickChatSessionContext, SessionsListPromoteNewChatActionContext, SessionHeaderActiveChatIsPinnedContext, SessionHeaderShowsChatContext, SessionItemIsMultiSelectionContext, IsPhoneLayoutContext, SessionToolbarShowsSessionContext } from '../../../common/contextkeys.js';
import { ANY_AGENT_HOST_PROVIDER_RE } from '../../../common/agentHostSessionsProvider.js';
import { ARRANGE_SESSIONS_COMMAND_ID, CLOSE_CHAT_COMMAND_ID, CLOSE_SESSION_COMMAND_ID, FOCUS_ACTIVE_SESSION_COMMAND_ID, FOCUS_NEXT_CHAT_GROUP_COMMAND_ID, FOCUS_PREVIOUS_CHAT_GROUP_COMMAND_ID, MOVE_CHAT_TO_NEXT_GROUP_COMMAND_ID, MOVE_CHAT_TO_PREVIOUS_GROUP_COMMAND_ID, RENAME_CHAT_COMMAND_ID, RENAME_SESSION_COMMAND_ID, SESSION_GRID_FOCUS_COMMANDS, SPLIT_CHAT_GROUP_DOWN_COMMAND_ID, SPLIT_CHAT_GROUP_RIGHT_COMMAND_ID, TOGGLE_PIN_CHAT_COMMAND_ID, TOGGLE_PIN_SESSION_COMMAND_ID } from '../../../common/sessionCommands.js';
import { IActiveSession, ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
Expand Down Expand Up @@ -919,6 +919,18 @@ registerAction2(class CloseChatAction extends Action2 {
}
});

MenuRegistry.appendMenuItem(Menus.SessionBarToolbar, {
command: {
id: CLOSE_CHAT_COMMAND_ID,
title: localize('closeChatGroup', "Close"),
tooltip: localize('closeChatGroupTooltip', "Close Chat Group"),
icon: Codicon.close,
},
Comment thread
sandy081 marked this conversation as resolved.
when: ContextKeyExpr.and(SessionHeaderShowsChatContext, SessionActiveChatIsClosableContext),
group: 'navigation',
order: 30,
});

registerAction2(class CloseAllChatsAction extends Action2 {
constructor() {
super({
Expand Down Expand Up @@ -1894,6 +1906,7 @@ registerAction2(class CloseSessionAction extends Action2 {
super({
id: CLOSE_SESSION_COMMAND_ID,
title: localize2('chatCompositeBar.close', "Close"),
tooltip: localize2('chatCompositeBar.closeTooltip', "Close Session"),
icon: Codicon.close,
keybinding: {
weight: KeybindingWeight.SessionsContrib,
Expand All @@ -1907,6 +1920,14 @@ registerAction2(class CloseSessionAction extends Action2 {
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KeyW] },
},
menu: [{
id: Menus.SessionBarToolbar,
when: ContextKeyExpr.and(
MultipleSessionsVisibleContext,
SessionToolbarShowsSessionContext,
),
group: 'navigation',
order: 30,
}, {
id: Menus.SessionBarToolbar,
when: ContextKeyExpr.and(
ContextKeyExpr.or(SessionIsCreatedContext, MultipleSessionsVisibleContext),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ suite('Sessions - Actions', () => {

assert.deepStrictEqual(actions, [
{ id: 'sessions.chatCompositeBar.togglePin', group: 'navigation' },
{ id: 'sessions.chatCompositeBar.close', group: 'navigation' },
{ id: 'sessions.sessionHeader.rename', group: 'secondary/1_session' },
{ id: 'sessions.chatCompositeBar.addChat', group: 'secondary/3_newChat' },
{ id: 'sessions.chatCompositeBar.togglePin', group: 'secondary/4_pin' },
Expand Down Expand Up @@ -573,7 +574,7 @@ suite('Sessions - Actions', () => {
const actions = MenuRegistry.getMenuItems(Menus.SessionBarToolbar)
.filter(isIMenuItem)
.filter(item => [TOGGLE_PIN_SESSION_COMMAND_ID, TOGGLE_PIN_CHAT_COMMAND_ID, 'sessions.chatCompositeBar.toggleMaximize', CLOSE_SESSION_COMMAND_ID, CLOSE_CHAT_COMMAND_ID].includes(item.command.id))
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.command.id.localeCompare(b.command.id))
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.command.id.localeCompare(b.command.id) || (a.group ?? '').localeCompare(b.group ?? ''))
.map(item => ({
id: item.command.id,
title: typeof item.command.title === 'string' ? item.command.title : item.command.title.value,
Expand All @@ -586,50 +587,77 @@ suite('Sessions - Actions', () => {
{ id: TOGGLE_PIN_CHAT_COMMAND_ID, title: 'Pin', group: 'navigation' },
{ id: TOGGLE_PIN_CHAT_COMMAND_ID, title: 'Pin', group: 'secondary/4_pin' },
{ id: 'sessions.chatCompositeBar.toggleMaximize', title: 'Maximize', group: 'secondary/4_pin' },
{ id: CLOSE_SESSION_COMMAND_ID, title: 'Close', group: 'navigation' },
{ id: CLOSE_SESSION_COMMAND_ID, title: 'Close', group: 'secondary/4_pin' },
{ id: CLOSE_CHAT_COMMAND_ID, title: 'Close', group: 'navigation' },
{ id: CLOSE_CHAT_COMMAND_ID, title: 'Close', group: 'secondary/4_pin' },
]);
});

test('uses the same small close icon for chat and side-panel tabs', () => {
const chatClose = MenuRegistry.getMenuItems(Menus.SessionChatTab)
test('uses a compact close icon for tabs and a regular close icon for headers', () => {
const chatTabClose = MenuRegistry.getMenuItems(Menus.SessionChatTab)
.filter(isIMenuItem)
.find(item => item.command.id === CLOSE_CHAT_COMMAND_ID);
const chatHeaderClose = MenuRegistry.getMenuItems(Menus.SessionBarToolbar)
.filter(isIMenuItem)
.find(item => item.command.id === CLOSE_CHAT_COMMAND_ID && item.group === 'navigation');
const sessionHeaderClose = MenuRegistry.getMenuItems(Menus.SessionBarToolbar)
.filter(isIMenuItem)
.find(item => item.command.id === CLOSE_SESSION_COMMAND_ID && item.group === 'navigation');
const instantiationService = workbenchInstantiationService(undefined, disposables);
const editorClose = disposables.add(instantiationService.createInstance(CloseEditorTabAction, CloseEditorTabAction.ID, CloseEditorTabAction.LABEL));

assert.deepStrictEqual({
chatIcon: chatClose?.command.icon,
chatTabIcon: chatTabClose?.command.icon,
chatHeaderIcon: chatHeaderClose?.command.icon,
chatHeaderTooltip: chatHeaderClose?.command.tooltip,
sessionHeaderIcon: sessionHeaderClose?.command.icon,
sessionHeaderTooltip: typeof sessionHeaderClose?.command.tooltip === 'string' ? sessionHeaderClose.command.tooltip : sessionHeaderClose?.command.tooltip?.value,
editorClass: editorClose.class,
}, {
chatIcon: Codicon.closeSmall,
chatTabIcon: Codicon.closeSmall,
chatHeaderIcon: Codicon.close,
chatHeaderTooltip: 'Close Chat Group',
sessionHeaderIcon: Codicon.close,
sessionHeaderTooltip: 'Close Session',
editorClass: 'codicon codicon-close-small',
});
});

test('uses mutually exclusive close actions for session and chat group headers', () => {
const getCloseWhen = (menu: MenuId, commandId: string) => MenuRegistry.getMenuItems(menu)
const getCloseItems = (menu: MenuId, commandId: string) => MenuRegistry.getMenuItems(menu)
.filter(isIMenuItem)
.find(item => item.command.id === commandId)
?.when?.serialize();
.filter(item => item.command.id === commandId)
.map(item => ({ group: item.group, when: item.when?.serialize() }))
.sort((a, b) => (a.group ?? '').localeCompare(b.group ?? ''));

assert.deepStrictEqual({
toolbar: {
chat: getCloseWhen(Menus.SessionBarToolbar, CLOSE_CHAT_COMMAND_ID),
session: getCloseWhen(Menus.SessionBarToolbar, CLOSE_SESSION_COMMAND_ID),
chat: getCloseItems(Menus.SessionBarToolbar, CLOSE_CHAT_COMMAND_ID),
session: getCloseItems(Menus.SessionBarToolbar, CLOSE_SESSION_COMMAND_ID),
},
contextMenu: {
chat: getCloseWhen(Menus.SessionHeaderContext, CLOSE_CHAT_COMMAND_ID),
session: getCloseWhen(Menus.SessionHeaderContext, CLOSE_SESSION_COMMAND_ID),
chat: getCloseItems(Menus.SessionHeaderContext, CLOSE_CHAT_COMMAND_ID),
session: getCloseItems(Menus.SessionHeaderContext, CLOSE_SESSION_COMMAND_ID),
},
}, {
toolbar: {
chat: 'sessionActiveChatIsClosable && sessionHeaderShowsChat',
session: 'multipleSessionsVisible && !sessionHeaderShowsChat || sessionIsCreated && !sessionHeaderShowsChat',
chat: [
{ group: 'navigation', when: 'sessionActiveChatIsClosable && sessionHeaderShowsChat' },
{ group: 'secondary/4_pin', when: 'sessionActiveChatIsClosable && sessionHeaderShowsChat' },
],
session: [
{ group: 'navigation', when: 'multipleSessionsVisible && sessionToolbarShowsSession' },
{ group: 'secondary/4_pin', when: 'multipleSessionsVisible && !sessionHeaderShowsChat || sessionIsCreated && !sessionHeaderShowsChat' },
],
},
contextMenu: {
chat: 'sessionActiveChatIsClosable && sessionHeaderShowsChat',
session: 'multipleSessionsVisible && !sessionHeaderShowsChat || sessionIsCreated && !sessionHeaderShowsChat',
chat: [
{ group: '1_view', when: 'sessionActiveChatIsClosable && sessionHeaderShowsChat' },
],
session: [
{ group: '1_view', when: 'multipleSessionsVisible && !sessionHeaderShowsChat || sessionIsCreated && !sessionHeaderShowsChat' },
],
},
});

Expand Down
Loading
Loading