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
10 changes: 9 additions & 1 deletion src/vs/sessions/browser/parts/chatCompositeBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js'
import { SessionActivatingActionRunner } from '../sessionActionRunner.js';
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
import { getSessionConversationStatusAriaLabel } from '../sessionConversationGroups.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';

interface IChatTab {
readonly chat: IChat;
Expand Down Expand Up @@ -114,6 +117,7 @@ export class ChatCompositeBar extends Disposable {
private readonly _sessionToolbar: MenuWorkbenchToolBar;
private readonly _metaRow: HTMLElement;
private readonly _metaToolbar: MenuWorkbenchToolBar;
private readonly _showMetadataInChatInput: IObservable<boolean>;
private readonly _tabs: IChatTab[] = [];
private readonly _tabDisposables = this._register(new DisposableStore());

Expand All @@ -122,6 +126,7 @@ export class ChatCompositeBar extends Disposable {
private _editingTab: IChatTab | undefined;
private _delegate: IChatCompositeBarDelegate | undefined;
private _showSessionActions = false;
private _metadataInInput = false;

private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
Expand Down Expand Up @@ -153,9 +158,11 @@ export class ChatCompositeBar extends Disposable {
@ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService,
@ICommandService private readonly _commandService: ICommandService,
@ISessionsService sessionsService: ISessionsService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();

this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService);
this._container = $('.chat-composite-bar.session-chat-tabs-bar');

// Tabs row — only shown when the group has multiple chats or is split out.
Expand Down Expand Up @@ -285,6 +292,7 @@ export class ChatCompositeBar extends Disposable {
this._newChatContainer.classList.toggle('hidden', !supportsMultipleChats || isQuickChat);
this._newChatAction.enabled = supportsMultipleChats && !isQuickChat && !delegate.session.isArchived.read(reader);
this._showSessionActions = delegate.showSessionActions.read(reader);
this._metadataInInput = this._showMetadataInChatInput.read(reader);
this._sessionActionsContainer.classList.toggle('hidden', !this._showSessionActions);
this._updateMetaRowVisibility();

Expand All @@ -293,7 +301,7 @@ export class ChatCompositeBar extends Disposable {
}

private _updateMetaRowVisibility(): void {
this._metaRow.style.display = this._showSessionActions && !this._metaToolbar.isEmpty() ? '' : 'none';
this._metaRow.style.display = this._showSessionActions && !this._metadataInInput && !this._metaToolbar.isEmpty() ? '' : 'none';
}

setAriaLabel(label: string): void {
Expand Down
7 changes: 6 additions & 1 deletion src/vs/sessions/browser/parts/chatGroupsView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class ChatGroupsView extends Themable {
private _mainChatResource: IObservable<string> | undefined;
private _sessionActive = true;
private _sessionVisible = true;
private readonly _singleGroupTabsReplaceHeader = observableValue(this, false);

/** While restoring a persisted layout: routes (late-loading) chats back to their saved groups. */
private _restoreAssignment: Map<string, number> | undefined;
Expand All @@ -113,6 +114,10 @@ export class ChatGroupsView extends Themable {
super(themeService);
}

setSingleGroupTabsReplaceHeader(enabled: boolean): void {
this._singleGroupTabsReplaceHeader.set(enabled, undefined);
}

/** Sets (or clears) the session whose chats this view partitions into groups. */
setSession(session: IActiveSession | undefined, options: IChatViewOptions): void {
this._options = options;
Expand Down Expand Up @@ -280,7 +285,7 @@ export class ChatGroupsView extends Themable {
}
return session.shouldShowChatTabs.read(reader);
});
const showSessionActions = derived(reader => this._groupCount.read(reader) === 1 && tabsVisible.read(reader));
const showSessionActions = derived(reader => this._singleGroupTabsReplaceHeader.read(reader) && this._groupCount.read(reader) === 1 && tabsVisible.read(reader));

const view = store.add(this._instantiationService.createInstance(ChatGroupView));
const entry: IGroupEntry = { id, view, resourceIds, activeResourceId, chats, tabsVisible };
Expand Down
4 changes: 2 additions & 2 deletions src/vs/sessions/browser/parts/media/chatCompositeBar.css
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,11 @@
overflow: hidden;
}

.chat-groups-view.single-group .chat-composite-bar-tabs-row {
.session-view.tabs-replace-header .chat-groups-view.single-group .chat-composite-bar-tabs-row {
border-bottom: var(--vscode-strokeThickness) solid color-mix(in srgb, var(--session-view-foreground, var(--chat-tab-active-foreground)) 12%, transparent);
}

:is(.hc-black, .hc-light) .chat-groups-view.single-group .chat-composite-bar-tabs-row {
:is(.hc-black, .hc-light) .session-view.tabs-replace-header .chat-groups-view.single-group .chat-composite-bar-tabs-row {
border-bottom-color: var(--vscode-contrastBorder);
}

Expand Down
13 changes: 11 additions & 2 deletions src/vs/sessions/browser/parts/sessionView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ import { AbstractChatView, IChatViewOptions } from './chatView.js';
import { ChatGroupsView } from './chatGroupsView.js';
import { SessionHeader, SessionViewFloatingToolbar } from './sessionHeader.js';
import { ISessionContext, SessionContext } from '../../services/sessions/browser/sessionContext.js';
import { autorun, observableValue } from '../../../base/common/observable.js';
import { autorun, IObservable, observableValue } from '../../../base/common/observable.js';
import { SessionIsMaximizedContext } from '../../common/contextkeys.js';
import { AGENTS_CENTERED_CONTENT_MAX_WIDTH } from '../../common/layoutConstants.js';
import { setActiveSessionContextKeys } from '../../services/sessions/common/sessionContextKeys.js';
import { applySessionViewThemeColors } from './sessionBarStyles.js';
import { IChatViewFactory } from '../../services/chatView/browser/chatViewFactory.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';

/**
* Options passed to {@link SessionView.openSession}. Extends the chat view
Expand Down Expand Up @@ -83,15 +86,18 @@ export class SessionView extends Disposable implements ISerializableView {
private _isLeafVisible = true;

private readonly _sessionObs = observableValue<IActiveSession | undefined>(this, undefined);
private readonly _showMetadataInChatInput: IObservable<boolean>;

constructor(
@IChatViewFactory private readonly _chatViewFactory: IChatViewFactory,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService private readonly themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
) {
super();

this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService);
// Scoped context key service so toolbars hosted within can react to
// session-specific context keys (e.g. sessionIsCreated, sessionIsSticky).
const scopedContextKeyService = this._scopedContextKeyService = this._register(contextKeyService.createScoped(this.element));
Expand Down Expand Up @@ -151,10 +157,13 @@ export class SessionView extends Disposable implements ISerializableView {

this._register(autorun(reader => {
const session = this._sessionObs.read(reader);
const tabsReplaceHeader = this._groupsView.groupCount.read(reader) === 1
const tabsReplaceHeader = this._showMetadataInChatInput.read(reader)
&& this._groupsView.groupCount.read(reader) === 1
&& (session?.isCreated.read(reader) ?? false)
&& (session?.shouldShowChatTabs.read(reader) ?? false);
this._header.setVisible(!tabsReplaceHeader);
this._groupsView.setSingleGroupTabsReplaceHeader(tabsReplaceHeader);
this.element.classList.toggle('tabs-replace-header', tabsReplaceHeader);
}));
}

Expand Down
24 changes: 21 additions & 3 deletions src/vs/sessions/test/browser/chatCompositeBar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import { URI } from '../../../base/common/uri.js';
import { mock } from '../../../base/test/common/mock.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../base/test/common/utils.js';
import { ICommandService } from '../../../platform/commands/common/commands.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { TestConfigurationService } from '../../../platform/configuration/test/common/testConfigurationService.js';
import { MenuRegistry } from '../../../platform/actions/common/actions.js';
import { TestInstantiationService } from '../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { workbenchInstantiationService } from '../../../workbench/test/browser/workbenchTestServices.js';
import { ChatCompositeBar, IChatCompositeBarDelegate } from '../../browser/parts/chatCompositeBar.js';
import { getSessionChatDragData, isSessionChatDrag } from '../../browser/dnd.js';
import { CLOSE_CHAT_COMMAND_ID } from '../../common/sessionCommands.js';
import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js';
import { Menus } from '../../browser/menus.js';
import { ISessionsProvidersService } from '../../services/sessions/browser/sessionsProvidersService.js';
import { ISessionsPartService } from '../../services/sessions/browser/sessionsPartService.js';
import { ISessionsService } from '../../services/sessions/browser/sessionsService.js';
Expand Down Expand Up @@ -83,16 +88,19 @@ interface IChatCompositeBarHarness {
readonly tabs: readonly HTMLElement[];
}

function createHarness(disposables: Pick<DisposableStore, 'add'>, isQuickChat = false): IChatCompositeBarHarness {
function createHarness(disposables: Pick<DisposableStore, 'add'>, options?: { readonly isQuickChat?: boolean; readonly showMetadataInInput?: boolean }): IChatCompositeBarHarness {
const store = disposables.add(new DisposableStore());
const instantiationService = workbenchInstantiationService(undefined, store);
const commandService = new TestCommandService();
const sessionsService = new TestSessionsService();
const mainChat = createChat('main', 'Main Chat');
const secondaryChat = createChat('secondary', 'Secondary Chat');
const session = createSession([mainChat, secondaryChat], mainChat, isQuickChat);
const session = createSession([mainChat, secondaryChat], mainChat, options?.isQuickChat);

instantiationService.stub(ICommandService, commandService);
instantiationService.stub(IConfigurationService, new TestConfigurationService({
[SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING]: options?.showMetadataInInput ?? false,
}));
instantiationService.stub(ISessionsService, sessionsService);
instantiationService.stub(ISessionsManagementService, new class extends mock<ISessionsManagementService>() {
override readonly onDidChangeSessions = Event.None;
Expand Down Expand Up @@ -145,11 +153,21 @@ suite('Sessions - ChatCompositeBar', () => {
});

test('hides New Chat for workspace-less sessions', () => {
const { bar } = createHarness(disposables, true);
const { bar } = createHarness(disposables, { isQuickChat: true });

assert.strictEqual(bar.element.querySelector('.chat-composite-bar-new-chat')?.classList.contains('hidden'), true);
});

test('hides header metadata pills when they are configured in the chat input', () => {
disposables.add(MenuRegistry.appendMenuItem(Menus.SessionHeaderMeta, {
command: { id: 'test.sessionMetadata', title: 'Changes' },
group: 'navigation',
}));
const { bar } = createHarness(disposables, { showMetadataInInput: true });

assert.strictEqual(bar.element.querySelector<HTMLElement>('.chat-composite-bar-meta-row')?.style.display, 'none');
});

test('middle-click closes the targeted inactive non-main chat', () => {
const { store, commandService, sessionsService, bar, session, tabs } = createHarness(disposables);
let bubbled = 0;
Expand Down
12 changes: 11 additions & 1 deletion src/vs/sessions/test/browser/chatGroupsView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ interface IChatGroupsHarness {
readonly view: ChatGroupsView;
}

function createHarness(disposables: Pick<DisposableStore, 'add'>): IChatGroupsHarness {
function createHarness(disposables: Pick<DisposableStore, 'add'>, tabsReplaceHeader = true): IChatGroupsHarness {
const store = disposables.add(new DisposableStore());
const instantiationService = workbenchInstantiationService(undefined, store);
const sessionsService = new TestSessionsService();
Expand All @@ -169,6 +169,7 @@ function createHarness(disposables: Pick<DisposableStore, 'add'>): IChatGroupsHa
}());

const view = store.add(instantiationService.createInstance(ChatGroupsView));
view.setSingleGroupTabsReplaceHeader(tabsReplaceHeader);
mainWindow.document.body.appendChild(view.element);
store.add(toDisposable(() => view.element.remove()));
return { instantiationService, sessionsService, chatViewFactory, view };
Expand Down Expand Up @@ -455,4 +456,13 @@ suite('Sessions - ChatGroupsView', () => {
});
});

test('hides session actions when tabs do not replace the header', () => {
const { view } = createHarness(disposables, false);
const main = createChat('main');
const secondary = createChat('secondary');
view.setSession(new TestActiveSession([main, secondary]), options);

assert.strictEqual(view.element.querySelector<HTMLElement>('.session-chat-tabs-actions')?.classList.contains('hidden'), true);
});

});
Loading