Skip to content

Commit e02a094

Browse files
authored
Merge pull request microsoft#260698 from microsoft/copilot/fix-47b991ed-f5bb-490c-b9f1-6564cef4fbe3
Add command contribution support for chat session view items
2 parents d6b3b49 + 63eee6f commit e02a094

File tree

5 files changed

+87
-8
lines changed

5 files changed

+87
-8
lines changed

src/vs/workbench/api/common/extHostChatSessions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class ExtHostChatSessions extends Disposable implements ExtHostChatSessio
7575
commands.registerArgumentProcessor({
7676
processArgument: (arg) => {
7777
if (arg && arg.$mid === MarshalledId.ChatSessionContext) {
78-
const id = arg.id;
78+
const id = arg.session.id;
7979
const sessionContent = this._sessionMap.get(id);
8080
if (sessionContent) {
8181
return sessionContent;

src/vs/workbench/contrib/chat/browser/actions/chatActions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ export function registerChatActions() {
580580

581581
const ckey = contextKeyService.createKey('chatSessionType', session.providerType);
582582
const actions = menuService.getMenuActions(MenuId.ChatSessionsMenu, contextKeyService);
583-
const menuActions = getContextMenuActions(actions, 'navigation');
583+
const menuActions = getContextMenuActions(actions, 'inline');
584584
ckey.reset();
585585

586586
// Use primary actions if available, otherwise fall back to secondary actions
@@ -756,7 +756,7 @@ export function registerChatActions() {
756756
const contextItem = context.item as ICodingAgentPickerItem;
757757
commandService.executeCommand(buttonItem.id, {
758758
uri: contextItem.uri,
759-
session: contextItem.session,
759+
session: contextItem.session?.session,
760760
$mid: MarshalledId.ChatSessionContext
761761
});
762762

src/vs/workbench/contrib/chat/browser/chatSessions.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,24 @@ import { IContextKeyService, ContextKeyExpr } from '../../../../platform/context
2020
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
2121
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
2222
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
23+
import { IMenuService, MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js';
24+
import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
25+
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
2326
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
27+
import { MarshalledId } from '../../../../base/common/marshallingIds.js';
2428
import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js';
2529
import { Extensions, IViewContainersRegistry, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewDescriptor } from '../../../common/views.js';
2630
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
2731
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
2832
import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';
2933
import { IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js';
34+
import { ChatContextKeys } from '../common/chatContextKeys.js';
3035
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
3136
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
3237
import { CancellationToken } from '../../../../base/common/cancellation.js';
3338
import { FuzzyScore } from '../../../../base/common/filters.js';
3439
import { ResourceLabels, IResourceLabel } from '../../../browser/labels.js';
3540
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
36-
import { Disposable } from '../../../../base/common/lifecycle.js';
3741
import { append, $, getActiveWindow, clearNode } from '../../../../base/browser/dom.js';
3842
import { URI } from '../../../../base/common/uri.js';
3943
import { IEditorGroupsService, IEditorGroup } from '../../../services/editor/common/editorGroupsService.js';
@@ -45,7 +49,6 @@ import { EditorInput } from '../../../common/editor/editorInput.js';
4549
import { ChatEditorInput } from './chatEditorInput.js';
4650
import { IChatWidgetService, IChatWidget } from './chat.js';
4751
import { ChatAgentLocation, ChatConfiguration } from '../common/constants.js';
48-
import { MenuId, MenuRegistry } from '../../../../platform/actions/common/actions.js';
4952
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
5053
import { IWorkbenchContribution } from '../../../common/contributions.js';
5154
import { IViewsService } from '../../../services/views/common/viewsService.js';
@@ -56,6 +59,16 @@ import { coalesce } from '../../../../base/common/arrays.js';
5659

5760
export const VIEWLET_ID = 'workbench.view.chat.sessions';
5861

62+
// Helper function to create context overlay for session items
63+
function getSessionItemContextOverlay(session: IChatSessionItem, provider?: IChatSessionItemProvider): [string, any][] {
64+
const overlay: [string, any][] = [];
65+
if (provider) {
66+
overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]);
67+
}
68+
69+
return overlay;
70+
}
71+
5972
// Extended interface for local chat session items that includes editor information or widget information
6073
interface ILocalChatSessionItem extends IChatSessionItem {
6174
editor?: EditorInput;
@@ -546,6 +559,7 @@ interface ISessionTemplateData {
546559
container: HTMLElement;
547560
resourceLabel: IResourceLabel;
548561
actionBar: ActionBar;
562+
elementDisposable: DisposableStore;
549563
}
550564

551565
// Renderer for session items in the tree
@@ -557,6 +571,8 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
557571
private readonly labels: ResourceLabels,
558572
@IThemeService private readonly themeService: IThemeService,
559573
@ILogService private readonly logService: ILogService,
574+
@IMenuService private readonly menuService: IMenuService,
575+
@IContextKeyService private readonly contextKeyService: IContextKeyService,
560576
) {
561577
super();
562578

@@ -615,17 +631,24 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
615631
renderTemplate(container: HTMLElement): ISessionTemplateData {
616632
const element = append(container, $('.chat-session-item'));
617633
const resourceLabel = this.labels.create(element, { supportHighlights: true });
618-
const actionBar = new ActionBar(container);
634+
const actionsContainer = append(resourceLabel.element, $('.actions'));
635+
const actionBar = new ActionBar(actionsContainer);
636+
const elementDisposable = new DisposableStore();
619637

620638
return {
621639
container: element,
622640
resourceLabel,
623-
actionBar
641+
actionBar,
642+
elementDisposable
624643
};
625644
}
626645

627646
renderElement(element: ITreeNode<IChatSessionItem, FuzzyScore>, index: number, templateData: ISessionTemplateData): void {
628647
const session = element.element;
648+
const sessionWithProvider = session as IChatSessionItem & { provider: IChatSessionItemProvider };
649+
650+
// Clear previous element disposables
651+
templateData.elementDisposable.clear();
629652

630653
// Handle different icon types
631654
let iconResource: URI | undefined;
@@ -661,9 +684,51 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
661684
fileKind: undefined,
662685
icon: iconTheme || iconUri
663686
});
687+
688+
// Create context overlay for this specific session item
689+
const contextOverlay = getSessionItemContextOverlay(session, sessionWithProvider.provider);
690+
691+
const contextKeyService = this.contextKeyService.createOverlay(contextOverlay);
692+
693+
// Create menu for this session item
694+
const menu = templateData.elementDisposable.add(
695+
this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService)
696+
);
697+
698+
// Setup action bar with contributed actions
699+
const setupActionBar = () => {
700+
templateData.actionBar.clear();
701+
702+
// Create marshalled context for command execution
703+
const marshalledSession = {
704+
session: session,
705+
$mid: MarshalledId.ChatSessionContext
706+
};
707+
708+
const actions = menu.getActions({ arg: marshalledSession, shouldForwardArgs: true });
709+
710+
const { primary } = getActionBarActions(
711+
actions,
712+
'inline',
713+
);
714+
715+
templateData.actionBar.push(primary, { icon: true, label: false });
716+
717+
// Set context for the action bar
718+
templateData.actionBar.context = session;
719+
};
720+
721+
// Setup initial action bar and listen for menu changes
722+
templateData.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
723+
setupActionBar();
724+
}
725+
726+
disposeElement(_element: ITreeNode<IChatSessionItem, FuzzyScore>, _index: number, templateData: ISessionTemplateData): void {
727+
templateData.elementDisposable.clear();
664728
}
665729

666730
disposeTemplate(templateData: ISessionTemplateData): void {
731+
templateData.elementDisposable.dispose();
667732
templateData.resourceLabel.dispose();
668733
templateData.actionBar.dispose();
669734
}
@@ -867,7 +932,7 @@ class SessionsViewPane extends ViewPane {
867932
this.dataSource = new SessionsDataSource(this.provider);
868933

869934
const delegate = new SessionsDelegate();
870-
const renderer = new SessionsRenderer(this.labels, this.themeService, this.logService);
935+
const renderer = this.instantiationService.createInstance(SessionsRenderer, this.labels);
871936
this._register(renderer);
872937

873938
this.tree = this.instantiationService.createInstance(

src/vs/workbench/contrib/chat/browser/media/chatSessions.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,15 @@
4040
.chat-sessions-message .no-sessions-message .codicon {
4141
opacity: 0.7;
4242
}
43+
44+
/* Hide action bar actions by default */
45+
.chat-sessions-tree .chat-session-item .actions .action-label {
46+
display: none;
47+
}
48+
49+
/* Show action bar actions on hover and focus */
50+
.chat-sessions-tree .chat-session-item:hover .actions .action-label,
51+
.chat-sessions-tree .monaco-list-row.focused .chat-session-item .actions .action-label,
52+
.chat-sessions-tree .monaco-list-row.selected .chat-session-item .actions .action-label {
53+
display: block;
54+
}

src/vs/workbench/contrib/chat/common/chatContextKeys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export namespace ChatContextKeys {
9999
};
100100

101101
export const panelLocation = new RawContextKey<ViewContainerLocation>('chatPanelLocation', undefined, { type: 'number', description: localize('chatPanelLocation', "The location of the chat panel.") });
102+
103+
export const sessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('chatSessionType', "The type of the current chat session item.") });
102104
}
103105

104106
export namespace ChatContextKeyExprs {

0 commit comments

Comments
 (0)