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
38 changes: 28 additions & 10 deletions src/vs/sessions/browser/parts/chatCompositeBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ import { applySessionBarThemeColors } from './sessionBarStyles.js';
import { ISessionsProvidersService } from '../../services/sessions/browser/sessionsProvidersService.js';
import { isAgentHostProvider } from '../../common/agentHostSessionsProvider.js';
import { ICommandService } from '../../../platform/commands/common/commands.js';
import { CLOSE_CHAT_COMMAND_ID, COPY_AGENT_HOST_CHAT_LINK_COMMAND_ID } from '../../common/sessionCommands.js';
import { CLOSE_CHAT_COMMAND_ID, COPY_AGENT_HOST_CHAT_LINK_COMMAND_ID, RENAME_CHAT_COMMAND_ID } from '../../common/sessionCommands.js';
import { getSessionConversationStatusAriaLabel } from '../sessionConversationGroups.js';
import { IEditorGroupsService } from '../../../workbench/services/editor/common/editorGroupsService.js';
import { IKeybindingService } from '../../../platform/keybinding/common/keybinding.js';

interface IChatTab {
readonly chat: IChat;
Expand Down Expand Up @@ -145,6 +146,7 @@ export class ChatCompositeBar extends Disposable {
@ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService,
@ICommandService private readonly _commandService: ICommandService,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
) {
super();

Expand Down Expand Up @@ -504,8 +506,10 @@ export class ChatCompositeBar extends Disposable {
this._delegate?.onTabDragEnd?.();
}));

const renameAction = this._tabDisposables.add(new Action('sessionCompositeBar.renameChat', localize('renameChat', "Rename"), undefined, true, async () => {
this._startTabEditing(chatTab);
const renameAction = this._tabDisposables.add(new Action(RENAME_CHAT_COMMAND_ID, localize('renameChat', "Rename..."), undefined, true, async () => {
if (session) {
await this._commandService.executeCommand(RENAME_CHAT_COMMAND_ID, { session, chat, inline: true });
}
}));

const copyLinkAction = this._tabDisposables.add(new Action(COPY_AGENT_HOST_CHAT_LINK_COMMAND_ID, localize('copyChatLink', "Copy Link"), undefined, true, async () => {
Expand Down Expand Up @@ -551,7 +555,8 @@ export class ChatCompositeBar extends Disposable {
provider && isAgentHostProvider(provider) ? [copyLinkAction] : [],
capabilities.canDelete ? [deleteAction] : [],
);
}
},
getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id) ?? undefined,
});
}));

Expand All @@ -578,17 +583,29 @@ export class ChatCompositeBar extends Disposable {
return provider && isAgentHostProvider(provider) ? provider.getBackendChatResource(chat.resource) : undefined;
}

/**
* Start an inline rename for the given tab. Enter commits via
* {@link ISessionsManagementService.renameChat}; Escape or blur cancels.
*/
private _startTabEditing(chatTab: IChatTab): void {
startFocusedTabEditing(): boolean {
const chatTab = this._tabs.find(tab => tab.element === tab.element.ownerDocument.activeElement);
if (!chatTab) {
return false;
}
return this._startTabEditing(chatTab);
}

startTabEditing(chatResource: URI): boolean {
const chatTab = this._tabs.find(tab => tab.chat.resource.toString() === chatResource.toString());
return chatTab ? this._startTabEditing(chatTab) : false;
}

private _startTabEditing(chatTab: IChatTab): boolean {
const delegate = this._delegate;
if (!delegate || this._editingTab) {
return;
return false;
}

const { chat, element: tab, inputContainer } = chatTab;
if (chat.resource.toString() === delegate.mainChatResource.get() || chat.status.get() === SessionStatus.Untitled || !getChatCapabilities(chat, delegate.session, undefined).canRename) {
return false;
}
const initialTitle = chat.title.get();

this._editingTab = chatTab;
Expand Down Expand Up @@ -640,6 +657,7 @@ export class ChatCompositeBar extends Disposable {

store.add(addDisposableListener(inputBox.element, EventType.CLICK, e => e.stopPropagation()));
store.add(addDisposableListener(inputBox.element, EventType.DBLCLICK, e => e.stopPropagation()));
return true;
}

private _cancelTabEditing(): void {
Expand Down
8 changes: 8 additions & 0 deletions src/vs/sessions/browser/parts/chatGroupView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ export class ChatGroupView extends Disposable implements ISerializableView {
this._compositeBar.setAriaLabel(localize('chatGroupTabsAriaLabel', "Chats, Group {0} of {1}", index + 1, count));
}

startFocusedChatTitleEditing(): boolean {
return this._compositeBar.startFocusedTabEditing();
}

startChatTitleEditing(chatResource: URI): boolean {
return this._compositeBar.startTabEditing(chatResource);
}

/** Sets (or clears) the group this view renders. */
setContext(context: IChatGroupContext | undefined): void {
this._contextDisposables.clear();
Expand Down
9 changes: 9 additions & 0 deletions src/vs/sessions/browser/parts/chatGroupsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,15 @@ export class ChatGroupsView extends Themable {
return group.chats.get().find(chat => chat.resource.toString() === activeResource);
}

startFocusedChatTitleEditing(): boolean {
return this._getFocusedGroup()?.view.startFocusedChatTitleEditing() ?? false;
}

startChatTitleEditing(chatResource: URI): boolean {
const group = this._groups.find(group => group.chats.get().some(chat => chat.resource.toString() === chatResource.toString()));
return group?.view.startChatTitleEditing(chatResource) ?? false;
}

private _getFocusedGroup(): IGroupEntry | undefined {
return this._groups.find(group => isAncestorOfActiveElement(group.view.element));
}
Expand Down
8 changes: 8 additions & 0 deletions src/vs/sessions/browser/parts/sessionView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ export class SessionView extends Disposable implements ISerializableView {
return this._isVisible && this._header.startTitleEditing();
}

startFocusedChatTitleEditing(): boolean {
return this._isVisible && this._groupsView.startFocusedChatTitleEditing();
}

startChatTitleEditing(chatResource: URI): boolean {
return this._isVisible && this._groupsView.startChatTitleEditing(chatResource);
}

getFocusedChat(): IChat | undefined {
return this._groupsView.getFocusedChat();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export class SessionsChatAccessibilityHelp implements IAccessibleViewImplementat
content.push(localize('sessionsChat.goForward', "Go forward through visited sessions{0}.", '<keybinding:sessions.goForward>'));
content.push(localize('sessionsChat.navigatePreviousSession', "Navigate to the previous session in the list{0}.", '<keybinding:sessionsViewPane.navigatePreviousSession>'));
content.push(localize('sessionsChat.navigateNextSession', "Navigate to the next session in the list{0}.", '<keybinding:sessionsViewPane.navigateNextSession>'));
content.push(localize('sessionsChat.renameSession', "To rename a session inline, focus its row in the Sessions list and invoke Rename{0}, double-click its title, or open its context menu and choose Rename. Type the new title, then press Enter to confirm or Escape to cancel. From the main chat transcript or input, invoking Rename opens a prompt instead.", `<keybinding:${RENAME_SESSION_COMMAND_ID}>`));
content.push(localize('sessionsChat.renameChat', "When Rename is available for a non-main chat, focus its nested row in the Sessions list and invoke Rename{0} or double-click its title to rename it inline. Type the new title, then press Enter to confirm or Escape to cancel. From the chat transcript or input, invoking Rename opens a prompt instead.", `<keybinding:${RENAME_CHAT_COMMAND_ID}>`));
content.push(localize('sessionsChat.renameSession', "To rename a session inline, focus its row in the Sessions list and invoke Rename{0}, double-click its title, or open its context menu and choose Rename. Type the new title, then press Enter to confirm or Escape to cancel. From the main chat transcript or input, invoking Rename edits the header title inline when it is visible and opens a prompt otherwise.", `<keybinding:${RENAME_SESSION_COMMAND_ID}>`));
content.push(localize('sessionsChat.renameChat', "When Rename is available for a non-main chat, focus its tab or nested row in the Sessions list and invoke Rename{0}, or double-click its title, to rename it inline. Type the new title, then press Enter to confirm or Escape to cancel. From the chat transcript or input, invoking Rename opens a prompt instead.", `<keybinding:${RENAME_CHAT_COMMAND_ID}>`));
content.push(localize('sessionsChat.archiveSession', "To archive or mark one or more sessions as done, focus them in the Sessions list and invoke Archive or Mark as Done{0}.", `<keybinding:${ARCHIVE_SESSION_COMMAND_ID}>`));
content.push(localize('sessionsChat.deleteSession', "To permanently delete a session, open its context menu and choose Delete. This is destructive and cannot be undone."));
content.push(localize('sessionsChat.changes', "Focus the Changes view{0}.", '<keybinding:workbench.action.agentSessions.focusChangesView>'));
Expand Down
52 changes: 26 additions & 26 deletions src/vs/sessions/contrib/sessions/browser/sessionsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { EditorAreaFocusContext, FocusedViewContext, IsAuxiliaryWindowContext, I
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
import { getQuickNavigateHandler, inQuickPickContext } from '../../../../workbench/browser/quickaccess.js';
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.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, SessionHasSideChatsContext, SessionsTitleBarNewSessionEnabledContext, SessionsEditorScopeContext, SessionsHasClosedItemContext, IsQuickChatSessionContext, SessionsListPromoteNewChatActionContext } from '../../../common/contextkeys.js';
Expand Down Expand Up @@ -64,7 +63,7 @@ import { logSessionsInteraction, SessionsInteractionSource } from '../../../comm
import { NEW_SESSION_ACTION_ID } from '../../chat/common/constants.js';
import { groupSessionsForPicker } from './sessionsPicker.js';
import { getSessionConversationActionId, isSessionConversationSideChat, SESSION_CONVERSATION_SIDE_CHATS_GROUP } from '../../../browser/sessionConversationGroups.js';
import { ISessionChatItem, RENAME_SESSION_LIST_CHAT_ACTION_ID, SessionChatItemCanDeleteContext, SessionChatItemCanRenameContext, SessionChatItemIsUntitledContext, SessionsList, SessionsListFocusedChatItemContext } from './views/sessionsList.js';
import { ISessionChatItem, SessionChatItemCanDeleteContext, SessionChatItemCanRenameContext, SessionChatItemIsUntitledContext, SessionsList, SessionsListFocusedChatItemContext } from './views/sessionsList.js';
import { SessionsView, SessionsViewId } from './views/sessionsView.js';
import './media/newSessionActionViewItem.css';
import { INewSessionComposerService } from '../../chat/browser/newSessionComposerService.js';
Expand Down Expand Up @@ -563,6 +562,7 @@ const CHAT_TAB_KEYBINDING_WEIGHT = KeybindingWeight.SessionsContrib + 10;
interface IChatRenameContext {
readonly session: ISession;
readonly chat: IChat;
readonly inline?: boolean;
}

function getSessionsList(accessor: ServicesAccessor): SessionsList | undefined {
Expand Down Expand Up @@ -632,11 +632,17 @@ registerAction2(class RenameChatAction extends Action2 {
when: ContextKeyExpr.and(
IsSessionsWindowContext,
ContextKeyExpr.or(
ContextKeyExpr.and(ChatContextKeys.inChatSession, SessionFocusedChatIsRenameTargetContext),
SessionFocusedChatIsRenameTargetContext,
ContextKeyExpr.and(FocusedViewContext.isEqualTo(SessionsViewId), WorkbenchListFocusContextKey, SessionsListFocusedChatItemContext),
),
),
},
menu: {
id: Menus.SessionChatItemContext,
group: '1_chat',
order: 1,
when: ContextKeyExpr.and(SessionChatItemCanRenameContext, SessionChatItemIsUntitledContext.negate()),
},
Comment thread
sandy081 marked this conversation as resolved.
});
}

Expand All @@ -648,36 +654,19 @@ registerAction2(class RenameChatAction extends Action2 {
return;
}
}
if (context?.inline && accessor.get(ISessionsPartService).getSessionView(context.session.sessionId)?.startChatTitleEditing(context.chat.resource)) {
return;
}
if (!context && accessor.get(ISessionsPartService).getFocusedSessionView()?.startFocusedChatTitleEditing?.()) {
return;
}
const target = getChatRenameContext(accessor, context);
if (target) {
await renameChatWithQuickInput(accessor, target);
}
}
});

registerAction2(class RenameSessionListChatAction extends Action2 {
constructor() {
super({
id: RENAME_SESSION_LIST_CHAT_ACTION_ID,
title: localize2('renameChat', "Rename..."),
f1: false,
menu: {
id: Menus.SessionChatItemContext,
group: '1_chat',
order: 1,
when: ContextKeyExpr.and(SessionChatItemCanRenameContext, SessionChatItemIsUntitledContext.negate()),
},
});
}

override async run(accessor: ServicesAccessor, context?: ISessionChatItem): Promise<void> {
if (!context) {
return;
}
await renameChatWithQuickInput(accessor, context);
}
});

registerAction2(class OpenSessionListChatToSideAction extends Action2 {
constructor() {
super({
Expand Down Expand Up @@ -1785,6 +1774,16 @@ registerAction2(class RenameSessionHeaderAction extends Action2 {
id: 'sessions.sessionHeader.rename',
title: localize2('renameSessionHeader', "Rename..."),
icon: Codicon.edit,
keybinding: {
primary: KeyCode.F2,
weight: KeybindingWeight.SessionsContrib + 1,
when: ContextKeyExpr.and(
IsSessionsWindowContext,
SessionsFocusContext,
SessionSupportsRenameContext,
SessionFocusedChatIsRenameTargetContext.negate(),
),
},
menu: [{
id: Menus.SessionHeaderContext,
group: '2_edit',
Expand All @@ -1800,6 +1799,7 @@ registerAction2(class RenameSessionHeaderAction extends Action2 {
}

override async run(accessor: ServicesAccessor, session: IActiveSession | undefined): Promise<void> {
session ??= accessor.get(ISessionsService).activeSession.get();
if (!session) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { ICommandService } from '../../../../../platform/commands/common/command
import { IContextKey, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js';
import { MarshalledId } from '../../../../../base/common/marshallingIds.js';
import { SessionProviderIdContext, SessionSupportsDeleteContext, SessionSupportsMultipleChatsContext, SessionSupportsRenameContext, SessionTypeContext, IsPhoneLayoutContext, IsQuickChatSessionContext, SessionIsArchivedContext, SessionIsReadContext, SessionHasPullRequestContext } from '../../../../common/contextkeys.js';
import { ARCHIVE_SESSION_COMMAND_ID, RENAME_SESSION_COMMAND_ID } from '../../../../common/sessionCommands.js';
import { ARCHIVE_SESSION_COMMAND_ID, RENAME_CHAT_COMMAND_ID, RENAME_SESSION_COMMAND_ID } from '../../../../common/sessionCommands.js';
import { IContextMenuService, IContextViewService } from '../../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
Expand Down Expand Up @@ -127,7 +127,6 @@ const EMPTY_GUIDE_SESSION_IDS: ReadonlySet<string> = new Set();
export const SessionItemContextMenuId = MenuId.SessionItemContextMenu;
export const SessionSectionToolbarMenuId = new MenuId('SessionSectionToolbar');
export const SessionGroupToolbarMenuId = new MenuId('SessionGroupToolbar');
export const RENAME_SESSION_LIST_CHAT_ACTION_ID = 'sessions.list.renameChat';
export const NEW_SESSION_FOR_WORKSPACE_ACTION_ID = 'sessionsView.sectionNewSession';

/** Controls whether the empty default Chats group is shown in the sessions list. */
Expand Down Expand Up @@ -4792,7 +4791,7 @@ export class SessionsList extends Disposable implements ISessionsList {
[SessionProviderIdContext.key, element.session.providerId],
]);
const menu = this.menuService.createMenu(Menus.SessionChatItemContext, contextKeyService);
const wrapAction = (action: IAction): IAction => action.id === RENAME_SESSION_LIST_CHAT_ACTION_ID
const wrapAction = (action: IAction): IAction => action.id === RENAME_CHAT_COMMAND_ID
? toAction({
id: action.id,
label: action.label,
Expand Down
Loading
Loading