Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have chat command center entry #229070

Merged
merged 4 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
80 changes: 80 additions & 0 deletions src/vs/platform/actions/browser/actionViewItemService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IActionViewItemProvider } from '../../../base/browser/ui/actionbar/actionbar.js';
import { Emitter, Event } from '../../../base/common/event.js';
import { IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { MenuId } from '../common/actions.js';


export const IActionViewItemService = createDecorator<IActionViewItemService>('IActionViewItemService');

export interface IActionViewItemService {

_serviceBrand: undefined;

onDidChange: Event<MenuId>;

register(menu: MenuId, commandId: string, provider: IActionViewItemProvider, event?: Event<unknown>): IDisposable;

lookUp(menu: MenuId, commandId: string): IActionViewItemProvider | undefined;
}

export class NullActionViewItemService implements IActionViewItemService {
_serviceBrand: undefined;

onDidChange: Event<MenuId> = Event.None;

register(menu: MenuId, commandId: string, provider: IActionViewItemProvider, event?: Event<unknown>): IDisposable {
return toDisposable(() => { });
}

lookUp(menu: MenuId, commandId: string): IActionViewItemProvider | undefined {
return undefined;
}
}

class ActionViewItemService implements IActionViewItemService {

declare _serviceBrand: undefined;

private readonly _providers = new Map<string, IActionViewItemProvider>();

private readonly _onDidChange = new Emitter<MenuId>();
readonly onDidChange: Event<MenuId> = this._onDidChange.event;

dispose(): void {
this._onDidChange.dispose();
}

register(menu: MenuId, commandId: string, provider: IActionViewItemProvider, event?: Event<unknown>): IDisposable {
const id = this._makeKey(menu, commandId);
if (this._providers.has(id)) {
throw new Error(`A provider for the command ${commandId} and menu ${menu} is already registered.`);
}
this._providers.set(id, provider);

const listener = event?.(() => {
this._onDidChange.fire(menu);
});

return toDisposable(() => {
listener?.dispose();
this._providers.delete(id);
});
}

lookUp(menu: MenuId, commandId: string): IActionViewItemProvider | undefined {
return this._providers.get(this._makeKey(menu, commandId));
}

private _makeKey(menu: MenuId, commandId: string) {
return menu.id + commandId;
}
}

registerSingleton(IActionViewItemService, ActionViewItemService, InstantiationType.Delayed);
28 changes: 26 additions & 2 deletions src/vs/platform/actions/browser/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import { Emitter, Event } from '../../../base/common/event.js';
import { Iterable } from '../../../base/common/iterator.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { localize } from '../../../nls.js';
import { createAndFillInActionBarActions } from './menuEntryActionViewItem.js';
import { createActionViewItem, createAndFillInActionBarActions } from './menuEntryActionViewItem.js';
import { IMenuActionOptions, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from '../common/actions.js';
import { createConfigureKeybindingAction } from '../common/menuService.js';
import { ICommandService } from '../../commands/common/commands.js';
import { IContextKeyService } from '../../contextkey/common/contextkey.js';
import { IContextMenuService } from '../../contextview/browser/contextView.js';
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
import { ITelemetryService } from '../../telemetry/common/telemetry.js';
import { IActionViewItemService } from './actionViewItemService.js';
import { IInstantiationService } from '../../instantiation/common/instantiation.js';

export const enum HiddenItemStrategy {
/** This toolbar doesn't support hiding*/
Expand Down Expand Up @@ -335,8 +337,24 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar {
@IKeybindingService keybindingService: IKeybindingService,
@ICommandService commandService: ICommandService,
@ITelemetryService telemetryService: ITelemetryService,
@IActionViewItemService actionViewService: IActionViewItemService,
@IInstantiationService instaService: IInstantiationService,
) {
super(container, { resetMenu: menuId, ...options }, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService);
super(container, {
resetMenu: menuId,
...options,
actionViewItemProvider: (action, opts) => {
let provider = actionViewService.lookUp(menuId, action.id);
if (!provider) {
provider = options?.actionViewItemProvider;
}
const viewItem = provider?.(action, opts);
if (viewItem) {
return viewItem;
}
return createActionViewItem(instaService, action, options);
}
}, menuService, contextKeyService, contextMenuService, keybindingService, commandService, telemetryService);

// update logic
const menu = this._store.add(menuService.createMenu(menuId, contextKeyService, { emitEventsForSubmenuChanges: true }));
Expand All @@ -357,6 +375,12 @@ export class MenuWorkbenchToolBar extends WorkbenchToolBar {
updateToolbar();
this._onDidChangeMenuItems.fire(this);
}));

this._store.add(actionViewService.onDidChange(e => {
if (e === menuId) {
updateToolbar();
}
}));
updateToolbar();
}

Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export class MenuId {
static readonly ChatInputSide = new MenuId('ChatInputSide');
static readonly ChatInlineResourceAnchorContext = new MenuId('ChatInlineResourceAnchorContext');
static readonly ChatInlineSymbolAnchorContext = new MenuId('ChatInlineSymbolAnchorContext');
static readonly ChatCommandCenter = new MenuId('ChatCommandCenter');
static readonly AccessibleView = new MenuId('AccessibleView');
static readonly MultiDiffEditorFileToolbar = new MenuId('MultiDiffEditorFileToolbar');
static readonly DiffEditorHunkToolbar = new MenuId('DiffEditorHunkToolbar');
Expand Down
72 changes: 70 additions & 2 deletions src/vs/workbench/contrib/chat/browser/actions/chatActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ThemeIcon } from '../../../../../base/common/themables.js';
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
import { EditorAction2, ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize, localize2 } from '../../../../../nls.js';
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { Action2, MenuId, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { IsLinuxContext, IsWindowsContext } from '../../../../../platform/contextkey/common/contextkeys.js';
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
Expand All @@ -29,6 +29,12 @@ import { IChatWidgetHistoryService } from '../../common/chatWidgetHistoryService
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
import { IWorkbenchContribution } from '../../../../common/contributions.js';
import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { SubmenuEntryActionViewItem } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js';
import { assertType } from '../../../../../base/common/types.js';

export interface IChatViewTitleActionContext {
chatView: ChatViewPane;
Expand Down Expand Up @@ -67,14 +73,19 @@ class OpenChatGlobalAction extends Action2 {
id: CHAT_OPEN_ACTION_ID,
title: localize2('openChat', "Open Chat"),
icon: Codicon.commentDiscussion,
f1: false,
f1: true,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyI,
mac: {
primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KeyI
}
},
menu: {
id: MenuId.ChatCommandCenter,
group: 'navigation',
order: 1
}
});
}
Expand Down Expand Up @@ -348,3 +359,60 @@ export function stringifyItem(item: IChatRequestViewModel | IChatResponseViewMod
return (includeName ? `${item.username}: ` : '') + item.response.toString();
}
}


// --- command center chat

MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
submenu: MenuId.ChatCommandCenter,
title: localize('title4', "Chat"),
icon: Codicon.commentDiscussion,
when: ContextKeyExpr.and(CONTEXT_CHAT_ENABLED, ContextKeyExpr.has('config.chat.commandCenter.enabled')),
order: 10001,
});

export class ChatCommandCenterRendering implements IWorkbenchContribution {

static readonly ID = 'chat.commandCenterRendering';

private readonly _store = new DisposableStore();

constructor(
@IActionViewItemService actionViewItemService: IActionViewItemService,
@IChatAgentService agentService: IChatAgentService,
@IInstantiationService instantiationService: IInstantiationService,
) {

// TODO@jrieken this isn't proper
const key = `submenuitem.${MenuId.ChatCommandCenter.id}`;

this._store.add(actionViewItemService.register(MenuId.CommandCenter, key, (action, options) => {

const agent = agentService.getDefaultAgent(ChatAgentLocation.Panel);
if (!agent?.metadata.themeIcon) {
return undefined;
}

if (!(action instanceof SubmenuItemAction)) {
return undefined;
}

return instantiationService.createInstance(class extends SubmenuEntryActionViewItem {

override render(container: HTMLElement): void {
super.render(container);
assertType(this.element);

const icon = ThemeIcon.asClassNameArray(agent.metadata.themeIcon!);
this.element.classList.add(...icon);
}

}, action, options);

}, agentService.onDidChangeAgents));
}

dispose() {
this._store.dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { ILanguageModelToolsService } from '../../common/languageModelToolsServi
import { AnythingQuickAccessProvider } from '../../../search/browser/anythingQuickAccess.js';
import { ISymbolQuickPickItem, SymbolsQuickAccessProvider } from '../../../search/browser/symbolsQuickAccess.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ActiveEditorContext } from '../../../../common/contextkeys.js';

export function registerChatContextActions() {
registerAction2(AttachContextAction);
Expand Down Expand Up @@ -98,9 +99,15 @@ class AttachFileAction extends Action2 {
constructor() {
super({
id: AttachFileAction.ID,
title: localize2('workbench.action.chat.attachFile.label', "Attach File"),
title: localize2('workbench.action.chat.attachFile.label', "Add File to Chat"),
category: CHAT_CATEGORY,
f1: false
f1: false,
precondition: ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor'),
menu: {
id: MenuId.ChatCommandCenter,
group: 'attach',
order: 1,
}
});
}

Expand All @@ -124,7 +131,13 @@ class AttachSelectionAction extends Action2 {
id: AttachSelectionAction.ID,
title: localize2('workbench.action.chat.attachSelection.label', "Add Selection to Chat"),
category: CHAT_CATEGORY,
f1: false
f1: false,
precondition: ActiveEditorContext.isEqualTo('workbench.editors.files.textFileEditor'),
menu: {
id: MenuId.ChatCommandCenter,
group: 'attach',
order: 2,
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ class QuickChatGlobalAction extends Action2 {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI
}
},
menu: {
id: MenuId.ChatCommandCenter,
group: 'navigation',
order: 5
},
metadata: {
description: localize('toggle.desc', 'Toggle the quick chat'),
args: [{
Expand Down
8 changes: 7 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/edit
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js';
import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js';
import { ChatAccessibilityHelp } from './actions/chatAccessibilityHelp.js';
import { registerChatActions } from './actions/chatActions.js';
import { ChatCommandCenterRendering, registerChatActions } from './actions/chatActions.js';
import { ACTION_ID_NEW_CHAT, registerNewChatActions } from './actions/chatClearActions.js';
import { registerChatCodeBlockActions, registerChatCodeCompareBlockActions } from './actions/chatCodeblockActions.js';
import { registerChatContextActions } from './actions/chatContextActions.js';
Expand Down Expand Up @@ -99,6 +99,11 @@ configurationRegistry.registerConfiguration({
description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."),
default: 0
},
'chat.commandCenter.enabled': {
type: 'boolean',
markdownDescription: nls.localize('chat.commandCenter.enabled', "Controls whether the command center shows a menu for chat actions (requires {0}).", '`#window.commandCenter#`'),
default: true
},
'chat.experimental.implicitContext': {
type: 'boolean',
description: nls.localize('chat.experimental.implicitContext', "Controls whether a checkbox is shown to allow the user to determine which implicit context is included with a chat participant's prompt."),
Expand Down Expand Up @@ -267,6 +272,7 @@ registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointH
registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(ChatCommandCenterRendering.ID, ChatCommandCenterRendering, WorkbenchPhase.AfterRestored);

registerChatActions();
registerChatCopyActions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { IInlineChatSessionService } from '../../browser/inlineChatSessionServic
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl.js';
import { TestWorkerService } from './testWorkerService.js';
import { ILanguageModelsService, LanguageModelsService } from '../../../chat/common/languageModels.js';
import { IActionViewItemService, NullActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';

suite('InteractiveChatController', function () {

Expand Down Expand Up @@ -131,6 +132,7 @@ suite('InteractiveChatController', function () {
const serviceCollection = new ServiceCollection(
[IConfigurationService, new TestConfigurationService()],
[IChatVariablesService, new SyncDescriptor(ChatVariablesService)],
[IActionViewItemService, new SyncDescriptor(NullActionViewItemService)],
[ILogService, new NullLogService()],
[ITelemetryService, NullTelemetryService],
[IHoverService, NullHoverService],
Expand Down
Loading