diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 8b750c0924f979..78255e02110251 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -334,7 +334,7 @@ export function isEmptyPattern(pattern: ParsedPattern | ParsedExpression): patte return false; } -function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): ParsedStringPattern { +function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions, cacheKey?: string): ParsedStringPattern { if (!arg1) { return NULL; } @@ -359,7 +359,7 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P }; // Check cache - const patternKey = `${ignoreCase ? pattern.toLowerCase() : pattern}_${!!options.trimForExclusions}_${ignoreCase}`; + const patternKey = `${cacheKey === undefined ? `default:${ignoreCase ? pattern.toLowerCase() : pattern}` : `custom:${cacheKey}`}_${!!options.trimForExclusions}_${ignoreCase}`; let parsedPattern = CACHE.get(patternKey); if (parsedPattern) { return wrapRelativePattern(parsedPattern, arg1, internalOptions); @@ -462,7 +462,7 @@ function trivia3(pattern: string, options: IGlobOptionsInternal): ParsedStringPa const parsedPatterns = aggregateBasenameMatches(pattern.slice(1, -1) .split(',') .map(pattern => parsePattern(pattern, options)) - .filter(pattern => pattern !== NULL), pattern); + .filter(pattern => pattern !== NULL), pattern, options.ignoreCase); const patternsLength = parsedPatterns.length; if (!patternsLength) { @@ -617,7 +617,7 @@ export function getPathTerms(patternOrExpression: ParsedPattern | ParsedExpressi function parsedExpression(expression: IExpression, options: IGlobOptions): ParsedExpression { const parsedPatterns = aggregateBasenameMatches(Object.getOwnPropertyNames(expression) .map(pattern => parseExpressionPattern(pattern, expression[pattern], options)) - .filter(pattern => pattern !== NULL)); + .filter(pattern => pattern !== NULL), undefined, options.ignoreCase); const patternsLength = parsedPatterns.length; if (!patternsLength) { @@ -750,7 +750,7 @@ function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, return NULL; // pattern is disabled } - const parsedPattern = parsePattern(pattern, options); + const parsedPattern = parsePattern(pattern, options, pattern); if (parsedPattern === NULL) { return NULL; } @@ -786,7 +786,7 @@ function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, return parsedPattern; } -function aggregateBasenameMatches(parsedPatterns: Array, result?: string): Array { +function aggregateBasenameMatches(parsedPatterns: Array, result?: string, ignoreCase?: boolean): Array { const basenamePatterns = parsedPatterns.filter(parsedPattern => !!(parsedPattern).basenames); if (basenamePatterns.length < 2) { return parsedPatterns; @@ -830,7 +830,7 @@ function aggregateBasenameMatches(parsedPatterns: Array equalsIgnoreCase(candidate, basename)) : basenames.indexOf(basename); return index !== -1 ? patterns[index] : null; }; diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 3021b924988f05..7f0bd234845fb8 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -779,6 +779,40 @@ suite('Glob', () => { assert.strictEqual(glob.match(expr, 'foo/foo'), null); }); + test('expression with two basename globs ignores case', function () { + const expr = { + '**/BAR': true, + '**/BAZ': true + }; + + assert.strictEqual(glob.match(expr, 'bar', { ignoreCase: true }), '**/BAR'); + assert.strictEqual(glob.match(expr, 'baz', { ignoreCase: true }), '**/BAZ'); + assert.strictEqual(glob.match(expr, 'src/bar', { ignoreCase: true }), '**/BAR'); + assert.strictEqual(glob.match(expr, 'bar'), null); + }); + + test('expression with cached basename globs ignores case', function () { + glob.parse('**/bar', { ignoreCase: true }); + + const expr = { + '**/BAR': true, + '**/BAZ': true + }; + + assert.strictEqual(glob.match(expr, 'BaR', { ignoreCase: true }), '**/BAR'); + }); + + test('expression cache does not collide with string pattern cache', function () { + glob.parse('**/BAR', { ignoreCase: true }); + + const expr = { + '**/bar': true, + '**/baz': true + }; + + assert.strictEqual(glob.match(expr, 'bar', { ignoreCase: true }), '**/bar'); + }); + test('expression with two basename globs and a siblings expression', function () { const expr = { '**/bar': true, @@ -1185,6 +1219,8 @@ suite('Glob', () => { assertNoGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js'); assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.ts', true); assertGlobMatch('{**/*.JS,**/*.TS}', 'bar/foo.js', true); + assertNoGlobMatch('{**/BAR,**/BAZ}', 'bar'); + assertGlobMatch('{**/BAR,**/BAZ}', 'bar', true); // T4 assertNoGlobMatch('**/FOO/Bar', 'bar/foo/bar'); assertGlobMatch('**/FOO/Bar', 'bar/foo/bar', true); diff --git a/src/vs/sessions/SESSIONS_LIST.md b/src/vs/sessions/SESSIONS_LIST.md index 76bf6c9a3ebd7f..4ffaf85d97226d 100644 --- a/src/vs/sessions/SESSIONS_LIST.md +++ b/src/vs/sessions/SESSIONS_LIST.md @@ -195,7 +195,7 @@ The Open Pull Request action shared by the session context menu and header uses | Menu | Constant | Where it appears | Use for | |------|----------|------------------|---------| -| `SessionSectionToolbar` | `SessionSectionToolbarMenuId` | Toolbar on section headers (Pinned, workspace groups, Done) | Section-scoped actions like the workspace `DropdownWithPrimaryActionViewItem` whose fixed primary action is "New Session" and whose dropdown contains actions contributed to `Menus.SessionSectionNewSession`, including the GitHub-backed "Create Session from Pull Request" action. When that menu is empty, the toolbar renders the ordinary "New Session" action. The toolbar also contains the selected "Archive All"/"Mark All as Done" action. The Done section restores/unarchives sessions individually (or via multi-selection) rather than with a section-wide action. Section headers also show a collapsible chevron on hover/focus; while a section action dropdown is open, both the toolbar and chevron remain visible. The chevron uses the same ghost icon hover background token as toolbar icon buttons. | +| `SessionSectionToolbar` | `SessionSectionToolbarMenuId` | Toolbar on section headers (Pinned, workspace groups, Done) | Section-scoped actions like the workspace `DropdownWithPrimaryActionViewItem` whose fixed primary action is "New Session" and whose dropdown contains actions contributed to `Menus.SessionSectionNewSession`, including the GitHub-backed "Create Session from Pull Request" action. When that menu is empty, the toolbar renders the ordinary "New Session" action. The toolbar also contains the selected "Archive All"/"Mark All as Done" action. The Done section restores/unarchives sessions individually (or via multi-selection) rather than with a section-wide action. Section headers also show a collapsible chevron on hover or keyboard focus; while a section action dropdown is open, both the toolbar and chevron remain visible. The chevron uses the same ghost icon hover background token as toolbar icon buttons. | ### Group Header Menu diff --git a/src/vs/sessions/contrib/chat/browser/media/newSessionPromptOptions.css b/src/vs/sessions/contrib/chat/browser/media/newSessionPromptOptions.css index 9b6f8ea08b45a0..a8c8d007fc1ec5 100644 --- a/src/vs/sessions/contrib/chat/browser/media/newSessionPromptOptions.css +++ b/src/vs/sessions/contrib/chat/browser/media/newSessionPromptOptions.css @@ -101,7 +101,6 @@ } .new-session-prompt-option-title { - color: var(--vscode-descriptionForeground); display: flex; font-size: var(--vscode-agents-fontSize-body1); font-weight: var(--vscode-agents-fontWeight-semiBold); @@ -147,7 +146,7 @@ } .new-session-prompt-option-description { - color: var(--vscode-agentsChatInput-placeholderForeground, var(--vscode-descriptionForeground)); + color: var(--vscode-descriptionForeground); font-size: var(--vscode-agents-fontSize-label1); font-weight: var(--vscode-agents-fontWeight-regular); grid-column: 1 / -1; diff --git a/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css b/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css index 9dfb5575735ff6..a276f88703beab 100644 --- a/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css +++ b/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css @@ -67,6 +67,10 @@ right: 0; top: 0; } + + .actions-container { + gap: var(--vscode-spacing-size40); + } } .monaco-list-row .session-pending-voice-indicator { @@ -522,6 +526,10 @@ display: none; } + .session-section-toolbar .actions-container { + gap: var(--vscode-spacing-size40); + } + .session-section-toolbar .monaco-action-bar .action-label { border-radius: var(--vscode-cornerRadius-small); } @@ -531,8 +539,8 @@ display: none; color: var(--vscode-descriptionForeground); font-size: var(--vscode-codiconFontSize-compact, 12px); - width: 22px; height: 22px; + margin-right: 6px; border-radius: var(--vscode-cornerRadius-small); justify-content: center; } @@ -563,13 +571,19 @@ } .monaco-list-row:hover .session-section .session-section-chevron.collapsible, -.monaco-list-row.focused .session-section .session-section-chevron.collapsible, +.sessions-list-control:not(.session-section-focus-from-pointer) .monaco-list:focus-within .monaco-list-row.focused .session-section .session-section-chevron.collapsible, .monaco-list-row .session-section.dropdown-active .session-section-chevron.collapsible { display: flex; align-items: center; } -.monaco-list-row .session-section .session-section-chevron.collapsible:hover { +.monaco-list-row:hover .session-section .session-section-chevron.collapsible + .session-section-icon, +.sessions-list-control:not(.session-section-focus-from-pointer) .monaco-list:focus-within .monaco-list-row.focused .session-section .session-section-chevron.collapsible + .session-section-icon, +.monaco-list-row .session-section.dropdown-active .session-section-chevron.collapsible + .session-section-icon { + display: none; +} + +.session-section.dropdown-active .session-section-toolbar .monaco-dropdown-with-primary { background-color: var(--vscode-toolbar-hoverBackground); } diff --git a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts index 8ee3001aa5760c..23ec2e66308ee2 100644 --- a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts +++ b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts @@ -181,6 +181,27 @@ function isSessionSection(item: SessionListItem): item is ISessionSection { return !isSessionGroupItem(item) && 'sessions' in item && Array.isArray((item as ISessionSection).sessions); } +function getSessionSectionIcon(sectionId: string): ThemeIcon | undefined { + switch (sectionId) { + case QUICK_CHATS_SECTION_ID: + return Codicon.commentDiscussion; + case 'pinned': + return Codicon.pinned; + case AUTOMATIONS_SECTION_ID: + return Codicon.watch; + case 'archived': + return Codicon.archive; + case 'recent': + return Codicon.history; + case 'older': + return Codicon.calendar; + default: + return sectionId.startsWith('workspace:') + ? Codicon.folder + : undefined; + } +} + function isSessionShowMore(item: SessionListItem): item is ISessionShowMore { return 'showMore' in item && (item as ISessionShowMore).showMore === true; } @@ -970,6 +991,8 @@ export class SessionSectionRenderer implements ITreeRenderer { if (element === null) { - this.commandService.executeCommand(ACTION_ID_NEW_CHAT); + this.options.createNewChat(); } })); diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts index 510b77c94bd198..23b6d39d4c789d 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagementEditor.ts @@ -1357,7 +1357,7 @@ export class AICustomizationManagementEditor extends EditorPane { isWorkspaceFile, ); }; - const renderSelectionCheckbox = (row: HTMLElement, customization: IPromptPath): void => { + const renderSelectionCheckbox = (row: HTMLElement, customization: IPromptPath, onSelectionChange?: () => void): void => { const checkboxContainer = DOM.append(row, $('.item-sync-checkbox.prompt-migration-checkbox')); const checkboxTitle = localize('customizationMigrationSelectAriaLabel', "Select {0}", customization.name ?? basename(customization.uri)); const checkbox = this.migrationPageDisposables.add(new Checkbox(checkboxTitle, this.isCustomizationSelectedForMigration(customization), defaultCheckboxStyles)); @@ -1365,12 +1365,13 @@ export class AICustomizationManagementEditor extends EditorPane { this.migrationPageDisposables.add(checkbox.onChange(() => { this.setCustomizationSelectedForMigration(customization, checkbox.checked); this.updateCustomizationMigrationActionState(); + onSelectionChange?.(); })); }; - const renderItem = (container: HTMLElement, customization: IPromptPath): void => { + const renderItem = (container: HTMLElement, customization: IPromptPath, onSelectionChange?: () => void): void => { const row = DOM.append(container, $('div.ai-customization-list-item.prompt-migration-item')); - renderSelectionCheckbox(row, customization); + renderSelectionCheckbox(row, customization, onSelectionChange); const itemLeft = DOM.append(row, $('span.item-left')); const displayName = customization.name ?? basename(customization.uri); @@ -1421,6 +1422,9 @@ export class AICustomizationManagementEditor extends EditorPane { } this.renderCustomizationMigrationPage(); })); + const updateGroupCheckboxState = (): void => { + groupCheckbox.checked = customizations.every(customization => this.isCustomizationSelectedForMigration(customization)); + }; const groupToggle = DOM.append(groupHeader, $('button.prompt-migration-group-toggle')) as HTMLButtonElement; groupToggle.type = 'button'; const groupId = `prompt-migration-group-${category.id}-${groupKey}`; @@ -1455,7 +1459,7 @@ export class AICustomizationManagementEditor extends EditorPane { })); for (const customization of customizations) { - renderItem(groupItems, customization); + renderItem(groupItems, customization, updateGroupCheckboxState); } }; diff --git a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts index 20bf2261de97aa..c1ee439b3e1ade 100644 --- a/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/widgetHosts/viewPane/chatViewPane.ts @@ -916,7 +916,8 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { const newSessionButtonContainer = this.sessionsNewButtonContainer = append(sessionsContainer, $('.agent-sessions-new-button-container')); const newSessionButton = this._register(new Button(newSessionButtonContainer, { ...defaultButtonStyles, secondary: true })); newSessionButton.label = localize('newSession', "New Session"); - this._register(newSessionButton.onDidClick(() => this.commandService.executeCommand(ACTION_ID_NEW_CHAT, this.getActionsContext()))); + const createNewChat = () => this.commandService.executeCommand(ACTION_ID_NEW_CHAT, this.getActionsContext()); + this._register(newSessionButton.onDidClick(createNewChat)); // Sessions Control this.sessionsControlContainer = append(sessionsContainer, $('.agent-sessions-control-container')); @@ -924,6 +925,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate { source: 'chatViewPane', filter: sessionsFilter, overrideStyles: this.getLocationBasedColors().listOverrideStyles, + createNewChat, getHoverPosition: () => this.getSessionHoverPosition(), trackActiveEditorSession: () => { return !this._widget || this._widget.isEmpty(); // only track and reveal if chat widget is empty diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsControl.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsControl.test.ts new file mode 100644 index 00000000000000..e15a07bc51ec5d --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsControl.test.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { EventType } from '../../../../../../base/browser/dom.js'; +import { Event } from '../../../../../../base/common/event.js'; +import { mock } from '../../../../../../base/test/common/mock.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js'; +import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js'; +import { AgentSessionsControl } from '../../../browser/agentSessions/agentSessionsControl.js'; +import { IAgentSessionsModel } from '../../../browser/agentSessions/agentSessionsModel.js'; +import { IAgentSessionsService } from '../../../browser/agentSessions/agentSessionsService.js'; +import { IAgentSessionsFilter } from '../../../browser/agentSessions/agentSessionsViewer.js'; +import { IChatSessionsService } from '../../../common/chatSessionsService.js'; +import { IVoicePlaybackService } from '../../../common/voicePlaybackService.js'; +import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js'; + +suite('AgentSessionsControl', () => { + + const store = ensureNoDisposablesAreLeakedInTestSuite(); + + test('creates a new chat when double-clicking empty list space', () => { + const model: IAgentSessionsModel = { + sessions: [], + resolved: true, + getSession: () => undefined, + observeSession: () => { throw new Error('Not implemented'); }, + onWillResolve: Event.None, + onDidResolve: Event.None, + onDidChangeSessions: Event.None, + onDidChangeSessionArchivedState: Event.None, + resolve: async () => { }, + }; + const filter: IAgentSessionsFilter = { + onDidChange: Event.None, + exclude: () => false, + getExcludes: () => ({ providers: [], states: [], archived: false, read: false, repositoryGroupCapped: true }), + isDefault: () => true, + reset: () => { }, + }; + const instantiationService = workbenchInstantiationService(undefined, store); + instantiationService.stub(IChatSessionsService, new class extends mock() { }); + instantiationService.stub(IVoicePlaybackService, new class extends mock() { }); + instantiationService.stub(IAgentSessionsService, new class extends mock() { + override readonly model = model; + override readonly onDidChangeSessionArchivedState = Event.None; + override getSession = () => undefined; + }); + + const container = document.createElement('div'); + let newChatCount = 0; + const control = store.add(instantiationService.createInstance(AgentSessionsControl, container, { + overrideStyles: {}, + filter, + source: 'test', + createNewChat: () => newChatCount++, + getHoverPosition: () => HoverPosition.BELOW, + trackActiveEditorSession: () => false, + })); + + control.element?.querySelector('.monaco-list')?.dispatchEvent(new MouseEvent(EventType.DBLCLICK, { + bubbles: true, + button: 0, + detail: 2, + })); + + assert.strictEqual(newChatCount, 1); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/browser/aiCustomization/aiCustomizationManagementEditor.test.ts b/src/vs/workbench/contrib/chat/test/browser/aiCustomization/aiCustomizationManagementEditor.test.ts index ebc8f3ae0b6e30..b6b8d2561a3e9d 100644 --- a/src/vs/workbench/contrib/chat/test/browser/aiCustomization/aiCustomizationManagementEditor.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/aiCustomization/aiCustomizationManagementEditor.test.ts @@ -510,4 +510,75 @@ suite('aiCustomizationManagementEditor', () => { editor.editorPreviewDisposables.dispose(); } }); + + test('unchecking every item in a migration group unchecks the group checkbox', () => { + const editor = createTestEditor(undefined, createConfigurationServiceStub({ + [ChatConfiguration.ChatCustomizationsPromptMigrationEnabled]: true, + })); + const promptFiles = [ + { + uri: URI.file('/workspace/.github/prompts/workspace-a.prompt.md'), + name: 'workspace-a.prompt.md', + storage: PromptsStorage.local, + type: PromptsType.prompt, + source: PromptFileSource.GitHubWorkspace, + } as IPromptPath, + { + uri: URI.file('/workspace/.github/prompts/workspace-b.prompt.md'), + name: 'workspace-b.prompt.md', + storage: PromptsStorage.local, + type: PromptsType.prompt, + source: PromptFileSource.GitHubWorkspace, + } as IPromptPath, + ]; + editor.customizationsByMigrationCategory = new Map([[CustomizationMigrationCategoryId.PromptFiles, promptFiles]]); + editor.activeMigrationCategoryId = CustomizationMigrationCategoryId.PromptFiles; + for (const promptFile of promptFiles) { + editor.setCustomizationSelectedForMigration(promptFile, true); + } + editor.migrationListContainer = document.createElement('div'); + editor.migrationTitleElement = document.createElement('h2'); + editor.migrationDescriptionElement = document.createElement('p'); + editor.migrationLinkElement = document.createElement('a'); + editor.migrationMigrateButton = { enabled: false, label: '' }; + document.body.appendChild(editor.migrationListContainer); + + try { + editor.renderCustomizationMigrationPage(); + + const groupCheckbox = editor.migrationListContainer.querySelector('.prompt-migration-group-checkbox [role="checkbox"]'); + const itemCheckboxes = [...editor.migrationListContainer.querySelectorAll('.prompt-migration-group-items .prompt-migration-checkbox [role="checkbox"]')]; + const readGroupChecked = () => groupCheckbox?.getAttribute('aria-checked'); + + const initiallyChecked = readGroupChecked(); + // Unchecking only one item already breaks "all selected", so the group checkbox should clear. + itemCheckboxes[0].click(); + const afterFirstUncheck = readGroupChecked(); + // Unchecking the last remaining item must keep the group checkbox cleared (issue #331330). + itemCheckboxes[1].click(); + const afterLastUncheck = readGroupChecked(); + // Re-checking every item should re-select the group checkbox. + itemCheckboxes[0].click(); + itemCheckboxes[1].click(); + const afterRecheckingAll = readGroupChecked(); + + assert.deepStrictEqual({ + itemCount: itemCheckboxes.length, + initiallyChecked, + afterFirstUncheck, + afterLastUncheck, + afterRecheckingAll, + }, { + itemCount: 2, + initiallyChecked: 'true', + afterFirstUncheck: 'false', + afterLastUncheck: 'false', + afterRecheckingAll: 'true', + }); + } finally { + editor.migrationListContainer.remove(); + editor.migrationPageDisposables.dispose(); + editor.editorPreviewDisposables.dispose(); + } + }); }); diff --git a/src/vs/workbench/contrib/modernUI/browser/media/statusBar.css b/src/vs/workbench/contrib/modernUI/browser/media/statusBar.css index 9784fab6002731..11acf295d7d9af 100644 --- a/src/vs/workbench/contrib/modernUI/browser/media/statusBar.css +++ b/src/vs/workbench/contrib/modernUI/browser/media/statusBar.css @@ -60,10 +60,12 @@ background-image: linear-gradient(var(--vscode-statusBarItem-compactHoverBackground), var(--vscode-statusBarItem-compactHoverBackground)); } +/* + * The items at either end of the status bar attach to the corner in the base + * styles, which pads them asymmetrically. As pills they need even padding on + * both sides, so give both end items the same symmetric inset. + */ +.modern-ui .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label, .modern-ui .part.statusbar > .items-container > .statusbar-item.right.last-visible-item > .statusbar-item-label { padding: 0 var(--vscode-spacing-size60); } - -.modern-ui .part.statusbar > .items-container > .statusbar-item.left.first-visible-item > .statusbar-item-label { - padding-right: var(--vscode-spacing-size40); -} diff --git a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts index ac3207098565ea..c54d5111764e0d 100644 --- a/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts +++ b/src/vs/workbench/contrib/welcomeAgentSessions/browser/agentSessionsWelcome.ts @@ -11,6 +11,7 @@ import { Toggle } from '../../../../base/browser/ui/toggle/toggle.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { DisposableStore, IReference, toDisposable } from '../../../../base/common/lifecycle.js'; +import { MarshalledId } from '../../../../base/common/marshallingIds.js'; import { Emitter } from '../../../../base/common/event.js'; import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; import { basename } from '../../../../base/common/resources.js'; @@ -35,7 +36,9 @@ import { IEditorService } from '../../../services/editor/common/editorService.js import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../chat/common/constants.js'; import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js'; +import { IChatViewTitleActionContext } from '../../chat/common/actions/chatActions.js'; import { ChatWidget } from '../../chat/browser/widget/chatWidget.js'; +import { ACTION_ID_NEW_CHAT } from '../../chat/browser/actions/chatActions.js'; import { IAgentSessionsService } from '../../chat/browser/agentSessions/agentSessionsService.js'; import { AgentSessionProviders, AgentSessionTarget } from '../../chat/browser/agentSessions/agentSessions.js'; import { IAgentSession } from '../../chat/browser/agentSessions/agentSessionsModel.js'; @@ -563,6 +566,10 @@ export class AgentSessionsWelcomePage extends EditorPane { limitResults: () => MAX_SESSIONS, overrideExclude: (session) => session.isArchived() ? true : undefined, })), + createNewChat: () => this.commandService.executeCommand(ACTION_ID_NEW_CHAT, this.chatWidget?.viewModel ? { + $mid: MarshalledId.ChatViewContext, + sessionResource: this.chatWidget.viewModel.sessionResource, + } satisfies IChatViewTitleActionContext : undefined), getHoverPosition: () => HoverPosition.BELOW, trackActiveEditorSession: () => false, source: 'welcomeView',