improve sessions workspace picker (#304907)

* improve sessions workspace picker

* feedback
This commit is contained in:
Sandeep Somavarapu
2026-03-26 08:29:55 +01:00
committed by GitHub
parent ae7b6654c6
commit f6218ecb33
12 changed files with 319 additions and 101 deletions

View File

@@ -191,7 +191,8 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
constructor(
private readonly _supportsPreview: boolean,
private readonly _onRemoveItem: ((item: IActionListItem<T>) => void) | undefined,
private _hasAnySubmenuActions: boolean,
private readonly _onShowSubmenu: ((item: IActionListItem<T>) => void) | undefined,
private readonly _hasAnySubmenuActions: boolean,
private readonly _linkHandler: ((uri: URI, item: IActionListItem<T>) => void) | undefined,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IOpenerService private readonly _openerService: IOpenerService,
@@ -342,17 +343,22 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
actionBar.push(toolbarActions, { icon: true, label: false });
}
// Show submenu indicator for items with submenu actions
const hasSubmenu = !!element.submenuActions?.length;
if (hasSubmenu) {
// Show submenu indicator only for items with submenu actions
if (element.submenuActions?.length) {
data.submenuIndicator.className = 'action-list-submenu-indicator has-submenu ' + ThemeIcon.asClassName(Codicon.chevronRight);
data.submenuIndicator.style.display = '';
data.submenuIndicator.style.visibility = '';
data.elementDisposables.add(dom.addDisposableListener(data.submenuIndicator, dom.EventType.CLICK, (e) => {
e.stopPropagation();
this._onShowSubmenu?.(element);
}));
} else if (this._hasAnySubmenuActions) {
// Reserve space for alignment when other items have submenus
data.submenuIndicator.className = 'action-list-submenu-indicator';
data.submenuIndicator.style.display = '';
data.submenuIndicator.style.visibility = 'hidden';
} else {
// No items have submenu actions — hide completely
data.submenuIndicator.className = 'action-list-submenu-indicator';
data.submenuIndicator.style.display = 'none';
}
}
@@ -431,6 +437,12 @@ export interface IActionListOptions {
* When true and filtering is enabled, focuses the filter input when the list opens.
*/
readonly focusFilterOnOpen?: boolean;
/**
* When false, non-submenu items do not reserve space for the submenu chevron.
* Defaults to true for alignment consistency.
*/
readonly reserveSubmenuSpace?: boolean;
}
/**
@@ -528,10 +540,11 @@ export class ActionListWidget<T> extends Disposable {
};
const hasAnySubmenuActions = items.some(item => !!item.submenuActions?.length);
const reserveSubmenuSpace = this._options?.reserveSubmenuSpace ?? true;
const hasAnySubmenuActions = reserveSubmenuSpace && items.some(item => !!item.submenuActions?.length);
this._list = this._register(new List(user, this.domNode, virtualDelegate, [
new ActionItemRenderer<T>(preview, (item) => this._removeItem(item), hasAnySubmenuActions, this._options?.linkHandler, this._keybindingService, this._openerService),
new ActionItemRenderer<T>(preview, (item) => this._removeItem(item), (item) => this._showSubmenuForItem(item), hasAnySubmenuActions, this._options?.linkHandler, this._keybindingService, this._openerService),
new HeaderRenderer(),
new SeparatorRenderer(),
], {
@@ -1100,10 +1113,10 @@ export class ActionListWidget<T> extends Disposable {
this._list.setSelection([]);
return;
}
// Don't select when clicking the submenu indicator
if (element.submenuActions?.length && dom.isMouseEvent(e.browserEvent)) {
// Don't select when clicking the toolbar or submenu indicator
if (dom.isMouseEvent(e.browserEvent)) {
const target = e.browserEvent.target;
if (dom.isHTMLElement(target) && target.closest('.action-list-submenu-indicator')) {
if (dom.isHTMLElement(target) && (target.closest('.action-list-item-toolbar') || target.closest('.action-list-submenu-indicator'))) {
this._list.setSelection([]);
return;
}
@@ -1195,6 +1208,16 @@ export class ActionListWidget<T> extends Disposable {
}, { groupId: `actionListHover` });
}
private _showSubmenuForItem(item: IActionListItem<T>): void {
const index = this._list.indexOf(item);
if (index >= 0) {
const rowElement = this._getRowElement(index);
if (rowElement) {
this._showSubmenuForElement(item, rowElement);
}
}
}
private _showSubmenuForElement(element: IActionListItem<T>, anchor: HTMLElement): void {
this._submenuDisposables.clear();
this._hover.clear();
@@ -1203,26 +1226,38 @@ export class ActionListWidget<T> extends Disposable {
// Convert submenu actions into ActionListWidget items
const submenuItems: IActionListItem<IAction>[] = [];
for (const action of element.submenuActions!) {
if (action instanceof SubmenuAction) {
// Add header for the group
const submenuGroups = element.submenuActions!.filter((a): a is SubmenuAction => a instanceof SubmenuAction);
const groupsWithActions = submenuGroups.filter(g => g.actions.length > 0);
for (let gi = 0; gi < groupsWithActions.length; gi++) {
const group = groupsWithActions[gi];
for (let ci = 0; ci < group.actions.length; ci++) {
const child = group.actions[ci];
submenuItems.push({
kind: ActionListItemKind.Header,
group: { title: action.label },
label: action.label,
item: child,
kind: ActionListItemKind.Action,
label: child.label,
description: ci === 0 && group.label ? group.label : (child.tooltip || undefined),
group: { title: '', icon: ThemeIcon.fromId(child.checked ? Codicon.check.id : Codicon.blank.id) },
hideIcon: false,
hover: {},
});
}
if (gi < groupsWithActions.length - 1) {
submenuItems.push({ kind: ActionListItemKind.Separator, label: '' });
}
}
// Also include non-SubmenuAction items directly
for (const action of element.submenuActions!) {
if (!(action instanceof SubmenuAction)) {
submenuItems.push({
item: action,
kind: ActionListItemKind.Action,
label: action.label,
description: action.tooltip || undefined,
group: { title: '' },
hideIcon: false,
hover: {},
});
// Add each child action as a selectable item
for (const child of action.actions) {
submenuItems.push({
item: child,
kind: ActionListItemKind.Action,
label: child.label,
description: child.tooltip || undefined,
group: { title: '', icon: ThemeIcon.fromId(child.checked ? Codicon.check.id : Codicon.blank.id) },
hideIcon: false,
hover: {},
});
}
}
}
@@ -1359,10 +1394,13 @@ export class ActionListWidget<T> extends Disposable {
if (element && element.item && this.focusCondition(element)) {
// Check if the hover target is inside a toolbar - if so, skip the splice
// to avoid re-rendering which would destroy the element mid-hover
// to avoid re-rendering which would destroy the element mid-hover.
// But still maintain submenu state for items with submenu actions.
const isHoveringToolbar = dom.isHTMLElement(e.browserEvent.target) && e.browserEvent.target.closest('.action-list-item-toolbar') !== null;
if (isHoveringToolbar) {
this._cancelSubmenuShow();
if (!element.submenuActions?.length) {
this._cancelSubmenuShow();
}
this._list.setFocus([]);
return;
}

View File

@@ -11,7 +11,7 @@ This design allows new compute environments (remote agent hosts, cloud backends,
```
┌─────────────────────────────────────────────────────────────────┐
│ UI Components │
│ (SessionsView, TitleBar, NewSession, ChatWidget)
│ (SessionsView, TitleBar, NewSession, Changes | Terminal)
└───────────────────────────┬─────────────────────────────────────┘
┌───────────▼────────────┐

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from '../../../../base/browser/dom.js';
import { SubmenuAction, toAction } from '../../../../base/common/actions.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Emitter, Event } from '../../../../base/common/event.js';
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
@@ -88,22 +89,26 @@ export class WorkspacePicker extends Disposable {
// Restore selected workspace from storage
this._selectedWorkspace = this._restoreSelectedWorkspace();
// If restore failed (providers not yet registered), retry when providers appear
if (!this._selectedWorkspace && this._hasStoredWorkspace()) {
const providerListener = this._register(this.sessionsProvidersService.onDidChangeProviders(() => {
if (!this._selectedWorkspace) {
const restored = this._restoreSelectedWorkspace();
if (restored) {
this._selectedWorkspace = restored;
this._updateTriggerLabel();
this._onDidSelectWorkspace.fire(restored);
}
// React to provider registrations/removals: re-validate the current
// selection and attempt to restore a stored workspace when none is active.
this._register(this.sessionsProvidersService.onDidChangeProviders(() => {
if (this._selectedWorkspace) {
// Validate that the selected workspace's provider is still registered
const providers = this.sessionsProvidersService.getProviders();
if (!providers.some(p => p.id === this._selectedWorkspace!.providerId)) {
this._selectedWorkspace = undefined;
this._updateTriggerLabel();
}
if (this._selectedWorkspace) {
providerListener.dispose();
}
if (!this._selectedWorkspace) {
const restored = this._restoreSelectedWorkspace();
if (restored) {
this._selectedWorkspace = restored;
this._updateTriggerLabel();
this._onDidSelectWorkspace.fire(restored);
}
}));
}
}
}));
}
/**
@@ -162,7 +167,7 @@ export class WorkspacePicker extends Disposable {
onHide: () => { triggerElement.focus(); },
};
const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces...") } : undefined;
const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces..."), reserveSubmenuSpace: false } : { reserveSubmenuSpace: false };
this.actionWidgetService.show<IWorkspacePickerItem>(
'workspacePicker',
@@ -267,28 +272,27 @@ export class WorkspacePicker extends Disposable {
const hasMultipleProviders = allProviders.length > 1;
if (hasMultipleProviders) {
// Group workspaces by provider
for (const provider of allProviders) {
// Group workspaces by provider, showing provider name as description on the first entry
const providersWithWorkspaces = allProviders.filter(p => recentWorkspaces.some(w => w.providerId === p.id));
for (let pi = 0; pi < providersWithWorkspaces.length; pi++) {
const provider = providersWithWorkspaces[pi];
const providerWorkspaces = recentWorkspaces.filter(w => w.providerId === provider.id);
if (providerWorkspaces.length === 0) {
continue;
}
items.push({
kind: ActionListItemKind.Header,
label: provider.label,
group: { title: provider.label, icon: provider.icon },
item: {},
});
for (const { workspace, providerId } of providerWorkspaces) {
for (let i = 0; i < providerWorkspaces.length; i++) {
const { workspace, providerId } = providerWorkspaces[i];
const selection: IWorkspaceSelection = { providerId, workspace };
const selected = this._isSelectedWorkspace(selection);
items.push({
kind: ActionListItemKind.Action,
label: workspace.label,
description: i === 0 ? provider.label : undefined,
group: { title: '', icon: workspace.icon },
item: { selection, checked: selected || undefined },
onRemove: () => this._removeRecentWorkspace(selection),
});
}
if (pi < providersWithWorkspaces.length - 1) {
items.push({ kind: ActionListItemKind.Separator, label: '' });
}
}
} else {
for (const { workspace, providerId } of recentWorkspaces) {
@@ -299,6 +303,7 @@ export class WorkspacePicker extends Disposable {
label: workspace.label,
group: { title: '', icon: workspace.icon },
item: { selection, checked: selected || undefined },
onRemove: () => this._removeRecentWorkspace(selection),
});
}
}
@@ -308,14 +313,48 @@ export class WorkspacePicker extends Disposable {
if (items.length > 0 && allBrowseActions.length > 0) {
items.push({ kind: ActionListItemKind.Separator, label: '' });
}
for (let i = 0; i < allBrowseActions.length; i++) {
const action = allBrowseActions[i];
if (hasMultipleProviders && allBrowseActions.length > 1) {
// Show a single "Browse..." entry with provider-grouped submenu actions
const providerMap = new Map<string, { provider: typeof allProviders[0]; actions: { action: ISessionsBrowseAction; index: number }[] }>();
allBrowseActions.forEach((action, i) => {
let entry = providerMap.get(action.providerId);
if (!entry) {
const provider = allProviders.find(p => p.id === action.providerId);
if (!provider) { return; }
entry = { provider, actions: [] };
providerMap.set(action.providerId, entry);
}
entry.actions.push({ action, index: i });
});
const submenuActions = [...providerMap.values()].map(({ provider, actions }) =>
new SubmenuAction(
`workspacePicker.browse.${provider.id}`,
provider.label,
actions.map(({ action, index }) => toAction({
id: `workspacePicker.browse.${index}`,
label: localize(`workspacePicker.browse`, "{0}...", action.label),
tooltip: '',
run: () => this._executeBrowseAction(index),
})),
)
);
items.push({
kind: ActionListItemKind.Action,
label: action.label,
group: { title: '', icon: action.icon },
item: { browseActionIndex: i },
label: localize('workspacePicker.browse', "Select..."),
group: { title: '', icon: Codicon.folderOpened },
item: {},
submenuActions,
});
} else {
for (let i = 0; i < allBrowseActions.length; i++) {
const action = allBrowseActions[i];
items.push({
kind: ActionListItemKind.Action,
label: localize(`workspacePicker.browse`, "Select {0}...", action.label),
group: { title: '', icon: action.icon },
item: { browseActionIndex: i },
});
}
}
return items;
@@ -341,8 +380,12 @@ export class WorkspacePicker extends Disposable {
if (!this._selectedWorkspace) {
return false;
}
return this._selectedWorkspace.providerId === selection.providerId
&& this._selectedWorkspace.workspace.label === selection.workspace.label;
if (this._selectedWorkspace.providerId !== selection.providerId) {
return false;
}
const selectedUri = this._selectedWorkspace.workspace.repositories[0]?.uri;
const candidateUri = selection.workspace.repositories[0]?.uri;
return this.uriIdentityService.extUri.isEqual(selectedUri, candidateUri);
}
private _persistSelectedWorkspace(selection: IWorkspaceSelection): void {
@@ -353,10 +396,6 @@ export class WorkspacePicker extends Disposable {
this._addRecentWorkspace(selection.providerId, selection.workspace, true);
}
private _hasStoredWorkspace(): boolean {
return this._getStoredRecentWorkspaces().length > 0;
}
private _restoreSelectedWorkspace(): IWorkspaceSelection | undefined {
try {
const providers = this._getActiveProviders();
@@ -469,6 +508,24 @@ export class WorkspacePicker extends Disposable {
});
}
private _removeRecentWorkspace(selection: IWorkspaceSelection): void {
const uri = selection.workspace.repositories[0]?.uri;
if (!uri) {
return;
}
const recents = this._getStoredRecentWorkspaces();
const updated = recents.filter(p =>
!(p.providerId === selection.providerId && this.uriIdentityService.extUri.isEqual(URI.revive(p.uri), uri))
);
this.storageService.store(STORAGE_KEY_RECENT_WORKSPACES, JSON.stringify(updated), StorageScope.PROFILE, StorageTarget.MACHINE);
// Clear current selection if it was the removed workspace
if (this._isSelectedWorkspace(selection)) {
this._selectedWorkspace = undefined;
this._updateTriggerLabel();
}
}
private _getStoredRecentWorkspaces(): IStoredRecentWorkspace[] {
const raw = this.storageService.get(STORAGE_KEY_RECENT_WORKSPACES, StorageScope.PROFILE);
if (!raw) {

View File

@@ -781,13 +781,13 @@ export class CopilotChatSessionsProvider extends Disposable implements ISessions
this.browseActions = [
{
label: 'Browse Folders...',
label: localize('folders', "Folders"),
icon: Codicon.folderOpened,
providerId: this.id,
execute: () => this._browseForFolder(),
},
{
label: 'Browse Repositories...',
label: localize('repositories', "Repositories"),
icon: Codicon.repo,
providerId: this.id,
execute: () => this._browseForRepo(),

View File

@@ -59,7 +59,7 @@ export class RemoteAgentHostSessionsProvider extends Disposable implements ISess
this.sessionTypes = [CopilotCLISessionType];
this.browseActions = [{
label: localize('browseRemote', "Browse Remote Folders..."),
label: localize('folders', "Folders"),
icon: Codicon.remote,
providerId: this.id,
execute: () => this._browseForFolder(),

View File

@@ -1,4 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

View File

@@ -319,6 +319,16 @@
opacity: 0.7;
margin-right: 4px;
}
.session-section-toolbar {
margin-left: auto;
display: none;
}
}
.monaco-list-row:hover .session-section .session-section-toolbar,
.monaco-list-row.focused .session-section .session-section-toolbar {
display: block;
}
.sessions-list-control {

View File

@@ -1,4 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

View File

@@ -1,4 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

View File

@@ -42,9 +42,11 @@ const $ = DOM.$;
export const SessionItemToolbarMenuId = new MenuId('SessionItemToolbar');
export const SessionItemContextMenuId = new MenuId('SessionItemContextMenu');
export const SessionSectionToolbarMenuId = new MenuId('SessionSectionToolbar');
export const IsSessionPinnedContext = new RawContextKey<boolean>('sessionItem.isPinned', false);
export const IsSessionArchivedContext = new RawContextKey<boolean>('sessionItem.isArchived', false);
export const IsSessionReadContext = new RawContextKey<boolean>('sessionItem.isRead', true);
export const SessionSectionTypeContext = new RawContextKey<string>('sessionSection.type', '');
//#region Types
@@ -439,19 +441,36 @@ interface ISessionSectionTemplate {
readonly container: HTMLElement;
readonly label: HTMLElement;
readonly count: HTMLElement;
readonly toolbar: MenuWorkbenchToolBar;
readonly contextKeyService: IContextKeyService;
readonly disposables: DisposableStore;
}
class SessionSectionRenderer implements ITreeRenderer<SessionListItem, FuzzyScore, ISessionSectionTemplate> {
static readonly TEMPLATE_ID = 'session-section';
readonly templateId = SessionSectionRenderer.TEMPLATE_ID;
constructor(private readonly hideSectionCount: boolean) { }
constructor(
private readonly hideSectionCount: boolean,
private readonly instantiationService: IInstantiationService,
private readonly contextKeyService: IContextKeyService,
) { }
renderTemplate(container: HTMLElement): ISessionSectionTemplate {
const disposables = new DisposableStore();
container.classList.add('session-section');
const label = DOM.append(container, $('span.session-section-label'));
const count = DOM.append(container, $('span.session-section-count'));
return { container, label, count };
const toolbarContainer = DOM.append(container, $('.session-section-toolbar'));
const contextKeyService = disposables.add(this.contextKeyService.createScoped(container));
const scopedInstantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])));
const toolbar = disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, toolbarContainer, SessionSectionToolbarMenuId, {
menuOptions: { shouldForwardArgs: true },
}));
return { container, label, count, toolbar, contextKeyService, disposables };
}
renderElement(node: ITreeNode<SessionListItem, FuzzyScore>, _index: number, template: ISessionSectionTemplate): void {
@@ -467,9 +486,16 @@ class SessionSectionRenderer implements ITreeRenderer<SessionListItem, FuzzyScor
template.count.textContent = String(element.sessions.length);
template.count.style.display = '';
}
// Set context key for section type so toolbar actions can use when clauses
const sectionType = element.id.startsWith('repo:') ? 'repository' : element.id;
SessionSectionTypeContext.bindTo(template.contextKeyService).set(sectionType);
template.toolbar.context = element;
}
disposeTemplate(_template: ISessionSectionTemplate): void { }
disposeTemplate(template: ISessionSectionTemplate): void {
template.disposables.dispose();
}
}
//#endregion
@@ -635,7 +661,7 @@ export class SessionsList extends Disposable implements ISessionsList {
new SessionsTreeDelegate(approvalModel),
[
sessionRenderer,
new SessionSectionRenderer(true /* hideSectionCount */),
new SessionSectionRenderer(true /* hideSectionCount */, instantiationService, contextKeyService),
showMoreRenderer,
],
{

View File

@@ -5,19 +5,19 @@
import { Codicon } from '../../../../../base/common/codicons.js';
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
import { localize2 } from '../../../../../nls.js';
import { localize, localize2 } from '../../../../../nls.js';
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { KeybindingsRegistry, KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IViewsService } from '../../../../../workbench/services/views/common/viewsService.js';
import { EditorsVisibleContext, IsAuxiliaryWindowContext } from '../../../../../workbench/common/contextkeys.js';
import { AgentSessionSection, IAgentSessionSection, isAgentSessionSection } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { ChatContextKeys } from '../../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
import { IChatWidgetService } from '../../../../../workbench/contrib/chat/browser/chat.js';
import { AUX_WINDOW_GROUP } from '../../../../../workbench/services/editor/common/editorService.js';
import { SessionsCategories } from '../../../../common/categories.js';
import { SessionItemToolbarMenuId, SessionItemContextMenuId, IsSessionPinnedContext, IsSessionArchivedContext, IsSessionReadContext, SessionsGrouping, SessionsSorting } from './sessionsList.js';
import { SessionItemToolbarMenuId, SessionItemContextMenuId, SessionSectionToolbarMenuId, SessionSectionTypeContext, IsSessionPinnedContext, IsSessionArchivedContext, IsSessionReadContext, SessionsGrouping, SessionsSorting, ISessionSection } from './sessionsList.js';
import { ISessionsManagementService, IsNewChatSessionContext } from '../sessionsManagementService.js';
import { ISessionData, SessionStatus } from '../../common/sessionData.js';
import { IsRepositoryGroupCappedContext, SessionsViewFilterOptionsSubMenu, SessionsViewFilterSubMenu, SessionsViewGroupingContext, SessionsViewId, SessionsView, SessionsViewSortingContext } from './sessionsView.js';
@@ -243,19 +243,19 @@ registerAction2(class FindSessionAction extends Action2 {
registerAction2(class NewSessionForRepositoryAction extends Action2 {
constructor() {
super({
id: 'agentSessionSection.newSession',
id: 'sessionsView.sectionNewSession',
title: localize2('newSessionForRepo', "New Session"),
icon: Codicon.newSession,
menu: [{
id: MenuId.AgentSessionSectionToolbar,
id: SessionSectionToolbarMenuId,
group: 'navigation',
order: 0,
when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Repository),
when: ContextKeyExpr.equals(SessionSectionTypeContext.key, 'repository'),
}]
});
}
async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {
if (!context || !isAgentSessionSection(context) || context.sessions.length === 0) {
async run(accessor: ServicesAccessor, context?: ISessionSection): Promise<void> {
if (!context || !context.sessions || context.sessions.length === 0) {
return;
}
const sessionsManagementService = accessor.get(ISessionsManagementService);
@@ -265,6 +265,109 @@ registerAction2(class NewSessionForRepositoryAction extends Action2 {
}
});
const ConfirmArchiveStorageKey = 'sessions.confirmArchive';
registerAction2(class ArchiveSectionAction extends Action2 {
constructor() {
super({
id: 'sessionsView.sectionArchive',
title: localize2('archiveSection', "Archive All"),
icon: Codicon.archive,
menu: [{
id: SessionSectionToolbarMenuId,
group: 'navigation',
order: 1,
when: ContextKeyExpr.notEquals(SessionSectionTypeContext.key, 'archived'),
}]
});
}
async run(accessor: ServicesAccessor, context?: ISessionSection): Promise<void> {
if (!context || !context.sessions || context.sessions.length === 0) {
return;
}
const sessionsManagementService = accessor.get(ISessionsManagementService);
const dialogService = accessor.get(IDialogService);
const storageService = accessor.get(IStorageService);
const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);
if (!skipConfirmation) {
const confirmed = await dialogService.confirm({
message: context.sessions.length === 1
? localize('archiveSectionSessions.confirmSingle', "Are you sure you want to archive 1 session from '{0}'?", context.label)
: localize('archiveSectionSessions.confirm', "Are you sure you want to archive {0} sessions from '{1}'?", context.sessions.length, context.label),
detail: localize('archiveSectionSessions.detail', "You can unarchive sessions later if needed from the sessions view."),
primaryButton: localize('archiveSectionSessions.archive', "Archive All"),
checkbox: {
label: localize('doNotAskAgain', "Do not ask me again")
}
});
if (!confirmed.confirmed) {
return;
}
if (confirmed.checkboxChecked) {
storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);
}
}
for (const session of context.sessions) {
await sessionsManagementService.archiveSession(session);
}
}
});
registerAction2(class UnarchiveSectionAction extends Action2 {
constructor() {
super({
id: 'sessionsView.sectionUnarchive',
title: localize2('unarchiveSection', "Unarchive All"),
icon: Codicon.unarchive,
menu: [{
id: SessionSectionToolbarMenuId,
group: 'navigation',
order: 1,
when: ContextKeyExpr.equals(SessionSectionTypeContext.key, 'archived'),
}]
});
}
async run(accessor: ServicesAccessor, context?: ISessionSection): Promise<void> {
if (!context || !context.sessions || context.sessions.length === 0) {
return;
}
const sessionsManagementService = accessor.get(ISessionsManagementService);
const dialogService = accessor.get(IDialogService);
const storageService = accessor.get(IStorageService);
if (context.sessions.length > 1) {
const skipConfirmation = storageService.getBoolean(ConfirmArchiveStorageKey, StorageScope.PROFILE, false);
if (!skipConfirmation) {
const confirmed = await dialogService.confirm({
message: localize('unarchiveSectionSessions.confirm', "Are you sure you want to unarchive {0} sessions?", context.sessions.length),
primaryButton: localize('unarchiveSectionSessions.unarchive', "Unarchive All"),
checkbox: {
label: localize('doNotAskAgain2', "Do not ask me again")
}
});
if (!confirmed.confirmed) {
return;
}
if (confirmed.checkboxChecked) {
storageService.store(ConfirmArchiveStorageKey, true, StorageScope.PROFILE, StorageTarget.USER);
}
}
}
for (const session of context.sessions) {
await sessionsManagementService.unarchiveSession(session);
}
}
});
// Session Item Actions
registerAction2(class PinSessionAction extends Action2 {

View File

@@ -1,4 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/