Skip to content

Commit 5e85d57

Browse files
Copilotosortega
andcommitted
Add command contribution support for chat session view items
Co-authored-by: osortega <[email protected]>
1 parent 1e56d4b commit 5e85d57

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

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

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ 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 } from '../../../../platform/actions/common/actions.js';
24+
import { getActionBarActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
25+
import { DisposableStore } from '../../../../base/common/lifecycle.js';
2326
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js';
2427
import { ViewPane, IViewPaneOptions } from '../../../browser/parts/views/viewPane.js';
2528
import { Extensions, IViewContainersRegistry, IViewDescriptorService, ViewContainerLocation, IViewsRegistry, IViewDescriptor } from '../../../common/views.js';
2629
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
2730
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js';
2831
import { WorkbenchAsyncDataTree } from '../../../../platform/list/browser/listService.js';
2932
import { IChatSessionItem, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService } from '../common/chatSessionsService.js';
33+
import { ChatContextKeys } from '../common/chatContextKeys.js';
3034
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
3135
import { IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
3236
import { CancellationToken } from '../../../../base/common/cancellation.js';
@@ -53,9 +57,26 @@ import { ThemeIcon } from '../../../../base/common/themables.js';
5357
import { IChatEditorOptions } from './chatEditor.js';
5458
import { ChatSessionUri } from '../common/chatUri.js';
5559
import { coalesce } from '../../../../base/common/arrays.js';
60+
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
61+
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
62+
import { INotificationService } from '../../../../platform/notification/common/notification.js';
5663

5764
export const VIEWLET_ID = 'workbench.view.chat.sessions';
5865

66+
// Helper function to create context overlay for session items
67+
function getSessionItemContextOverlay(session: IChatSessionItem, provider?: IChatSessionItemProvider): [string, any][] {
68+
const overlay: [string, any][] = [
69+
[ChatContextKeys.sessionId.key, session.id],
70+
[ChatContextKeys.sessionLabel.key, session.label],
71+
];
72+
73+
if (provider) {
74+
overlay.push([ChatContextKeys.sessionType.key, provider.chatSessionType]);
75+
}
76+
77+
return overlay;
78+
}
79+
5980
// Extended interface for local chat session items that includes editor information or widget information
6081
interface ILocalChatSessionItem extends IChatSessionItem {
6182
editor?: EditorInput;
@@ -546,6 +567,7 @@ interface ISessionTemplateData {
546567
container: HTMLElement;
547568
resourceLabel: IResourceLabel;
548569
actionBar: ActionBar;
570+
elementDisposable: DisposableStore;
549571
}
550572

551573
// Renderer for session items in the tree
@@ -557,6 +579,8 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
557579
private readonly labels: ResourceLabels,
558580
@IThemeService private readonly themeService: IThemeService,
559581
@ILogService private readonly logService: ILogService,
582+
@IMenuService private readonly menuService: IMenuService,
583+
@IContextKeyService private readonly contextKeyService: IContextKeyService,
560584
) {
561585
super();
562586

@@ -616,16 +640,22 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
616640
const element = append(container, $('.chat-session-item'));
617641
const resourceLabel = this.labels.create(element, { supportHighlights: true });
618642
const actionBar = new ActionBar(container);
643+
const elementDisposable = new DisposableStore();
619644

620645
return {
621646
container: element,
622647
resourceLabel,
623-
actionBar
648+
actionBar,
649+
elementDisposable
624650
};
625651
}
626652

627653
renderElement(element: ITreeNode<IChatSessionItem, FuzzyScore>, index: number, templateData: ISessionTemplateData): void {
628654
const session = element.element;
655+
const sessionWithProvider = session as IChatSessionItem & { provider: IChatSessionItemProvider };
656+
657+
// Clear previous element disposables
658+
templateData.elementDisposable.clear();
629659

630660
// Handle different icon types
631661
let iconResource: URI | undefined;
@@ -661,9 +691,42 @@ class SessionsRenderer extends Disposable implements ITreeRenderer<IChatSessionI
661691
fileKind: undefined,
662692
icon: iconTheme || iconUri
663693
});
694+
695+
// Create context overlay for this specific session item
696+
const contextOverlay = getSessionItemContextOverlay(session, sessionWithProvider.provider);
697+
698+
const contextKeyService = this.contextKeyService.createOverlay(contextOverlay);
699+
700+
// Create menu for this session item
701+
const menu = templateData.elementDisposable.add(
702+
this.menuService.createMenu(MenuId.ChatSessionsMenu, contextKeyService)
703+
);
704+
705+
// Setup action bar with contributed actions
706+
const setupActionBar = () => {
707+
templateData.actionBar.clear();
708+
709+
const { primary } = getActionBarActions(
710+
menu.getActions({ arg: session, shouldForwardArgs: true }),
711+
'inline'
712+
);
713+
templateData.actionBar.push(primary, { icon: true, label: false });
714+
715+
// Set context for the action bar
716+
templateData.actionBar.context = session;
717+
};
718+
719+
// Setup initial action bar and listen for menu changes
720+
templateData.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
721+
setupActionBar();
722+
}
723+
724+
disposeElement(_element: ITreeNode<IChatSessionItem, FuzzyScore>, _index: number, templateData: ISessionTemplateData): void {
725+
templateData.elementDisposable.clear();
664726
}
665727

666728
disposeTemplate(templateData: ISessionTemplateData): void {
729+
templateData.elementDisposable.dispose();
667730
templateData.resourceLabel.dispose();
668731
templateData.actionBar.dispose();
669732
}
@@ -867,7 +930,7 @@ class SessionsViewPane extends ViewPane {
867930
this.dataSource = new SessionsDataSource(this.provider);
868931

869932
const delegate = new SessionsDelegate();
870-
const renderer = new SessionsRenderer(this.labels, this.themeService, this.logService);
933+
const renderer = this.instantiationService.createInstance(SessionsRenderer, this.labels);
871934
this._register(renderer);
872935

873936
this.tree = this.instantiationService.createInstance(
@@ -962,3 +1025,45 @@ MenuRegistry.appendMenuItem(MenuId.ViewTitle, {
9621025
when: ContextKeyExpr.equals('view', `${VIEWLET_ID}.local`),
9631026
});
9641027

1028+
// Test menu contribution for chat session items
1029+
MenuRegistry.appendMenuItem(MenuId.ChatSessionsMenu, {
1030+
command: {
1031+
id: 'workbench.action.chat.testSessionCommand',
1032+
title: nls.localize2('chatSession.testCommand', "Test Session Command"),
1033+
icon: Codicon.gear
1034+
},
1035+
group: 'inline',
1036+
order: 1,
1037+
when: ContextKeyExpr.and(
1038+
ChatContextKeys.sessionType.isEqualTo('local'),
1039+
ChatContextKeys.sessionId.notEqualsTo('')
1040+
),
1041+
});
1042+
1043+
// Test command implementation for chat session items
1044+
class TestChatSessionCommand extends Action2 {
1045+
constructor() {
1046+
super({
1047+
id: 'workbench.action.chat.testSessionCommand',
1048+
title: nls.localize2('chatSession.testCommand', "Test Session Command"),
1049+
f1: false, // Don't show in command palette
1050+
});
1051+
}
1052+
1053+
async run(accessor: ServicesAccessor, session?: IChatSessionItem): Promise<void> {
1054+
const notificationService = accessor.get(INotificationService);
1055+
1056+
if (session) {
1057+
notificationService.info(
1058+
nls.localize('chatSession.testCommandExecuted', "Test command executed for session: {0} (ID: {1})", session.label, session.id)
1059+
);
1060+
} else {
1061+
notificationService.info(
1062+
nls.localize('chatSession.testCommandNoSession', "Test command executed without session context")
1063+
);
1064+
}
1065+
}
1066+
}
1067+
1068+
registerAction2(TestChatSessionCommand);
1069+

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ 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 sessionId = new RawContextKey<string>('chatSessionId', '', { type: 'string', description: localize('chatSessionId', "The ID of the current chat session item.") });
104+
export const sessionLabel = new RawContextKey<string>('chatSessionLabel', '', { type: 'string', description: localize('chatSessionLabel', "The label of the current chat session item.") });
105+
export const sessionType = new RawContextKey<string>('chatSessionType', '', { type: 'string', description: localize('chatSessionType', "The type of the current chat session item.") });
102106
}
103107

104108
export namespace ChatContextKeyExprs {

0 commit comments

Comments
 (0)