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
1 change: 1 addition & 0 deletions src/vs/platform/accessibility/browser/accessibleView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const enum AccessibleViewProviderId {
WebviewFindHelp = 'webviewFindHelp',
OutputFindHelp = 'outputFindHelp',
ProblemsFilterHelp = 'problemsFilterHelp',
SessionsChat = 'sessionsChat',
}

export const enum AccessibleViewType {
Expand Down
7 changes: 7 additions & 0 deletions src/vs/sessions/browser/parts/auxiliaryBarPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import '../../../workbench/browser/parts/auxiliarybar/media/auxiliaryBarPart.css';
import './media/auxiliaryBarPart.css';
import { localize } from '../../../nls.js';
import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js';
import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
Expand Down Expand Up @@ -136,6 +137,12 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart {

}

override create(parent: HTMLElement): void {
super.create(parent);
parent.setAttribute('role', 'complementary');
parent.setAttribute('aria-label', localize('auxiliaryBarAriaLabel', "Session Details"));
}

override updateStyles(): void {
super.updateStyles();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { localize, localize2 } from '../../../../nls.js';
import { Action2, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
import { AI_CUSTOMIZATION_VIEW_ID, AICustomizationItemMenuId } from './aiCustomizationTreeView.js';
import { AI_CUSTOMIZATION_CATEGORY, AI_CUSTOMIZATION_VIEW_ID, AICustomizationItemMenuId, FOCUS_AI_CUSTOMIZATION_VIEW_ID } from './aiCustomizationTreeView.js';
import { AICustomizationItemDisabledContextKey, AICustomizationItemStorageContextKey, AICustomizationItemTypeContextKey, AICustomizationViewPane } from './aiCustomizationTreeViewViews.js';
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
import { Codicon } from '../../../../base/common/codicons.js';
Expand All @@ -20,6 +20,10 @@ import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
import { IPromptsService } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
import { BUILTIN_STORAGE } from '../../chat/common/builtinPromptsStorage.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { SessionsView, SessionsViewId } from '../../sessions/browser/views/sessionsView.js';
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';

//#region Utilities

Expand Down Expand Up @@ -281,3 +285,29 @@ MenuRegistry.appendMenuItem(AICustomizationItemMenuId, {
});

//#endregion

//#region Focus Action

registerAction2(class extends Action2 {
constructor() {
super({
id: FOCUS_AI_CUSTOMIZATION_VIEW_ID,
title: localize2('focusCustomizations', "Focus Chat Customizations"),
category: AI_CUSTOMIZATION_CATEGORY,
precondition: IsSessionsWindowContext,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyU,
when: IsSessionsWindowContext,
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const viewsService = accessor.get(IViewsService);
const sessionsView = await viewsService.openView<SessionsView>(SessionsViewId, false);
sessionsView?.focusCustomizations();
}
});

//#endregion
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ export const AICustomizationItemMenuId = new MenuId('aiCustomization.item');
// Submenu for creating new items
export const AICustomizationNewMenuId = new MenuId('aiCustomization.new');
//#endregion

/**
* Command ID for the Focus Chat Customizations action.
*/
export const FOCUS_AI_CUSTOMIZATION_VIEW_ID = 'aiCustomization.focusView';
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ viewsRegistry.registerViews([{
canMoveView: true,
weight: 100,
order: 1,
windowVisibility: WindowVisibility.Sessions
windowVisibility: WindowVisibility.Sessions,
}], changesViewContainer);

registerWorkbenchContribution2(ChangesTitleBarContribution.ID, ChangesTitleBarContribution, WorkbenchPhase.AfterRestored);
5 changes: 4 additions & 1 deletion src/vs/sessions/contrib/changes/browser/changesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,7 +796,10 @@ export class ChangesViewPane extends ViewPane {

override focus(): void {
super.focus();
this.tree?.domFocus();

if (this.tree && this.tree.getNode(null).visibleChildrenCount > 0) {
this.tree.domFocus();
}
}

private renderSidebarList(
Expand Down
52 changes: 51 additions & 1 deletion src/vs/sessions/contrib/changes/browser/changesViewActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ import { IViewsService } from '../../../../workbench/services/views/common/views
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';
import { ActiveSessionContextKeys, CHANGES_VIEW_ID } from '../common/changes.js';
import { ActiveSessionContextKeys, CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID } from '../common/changes.js';
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';
import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IPaneCompositePartService } from '../../../../workbench/services/panecomposite/browser/panecomposite.js';
import { ViewContainerLocation } from '../../../../workbench/common/views.js';

const openChangesViewActionOptions: IAction2Options = {
id: 'workbench.action.agentSessions.openChangesView',
Expand All @@ -41,6 +46,51 @@ class OpenChangesViewAction extends Action2 {

registerAction2(OpenChangesViewAction);

registerAction2(class FocusChangesViewAction extends Action2 {
constructor() {
super({
id: 'workbench.action.agentSessions.focusChangesView',
title: localize2('focusChangesView', "Focus Changes View"),
category: Categories.View,
precondition: IsSessionsWindowContext,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyH,
when: IsSessionsWindowContext,
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const paneCompositeService = accessor.get(IPaneCompositePartService);
const viewsService = accessor.get(IViewsService);
await paneCompositeService.openPaneComposite(CHANGES_VIEW_CONTAINER_ID, ViewContainerLocation.AuxiliaryBar, true);
const view = await viewsService.openView(CHANGES_VIEW_ID, true);
view?.focus();
}
});

registerAction2(class FocusChangesFileViewAction extends Action2 {
constructor() {
super({
id: 'workbench.action.agentSessions.focusChangesFileView',
title: localize2('focusChangesFileView', "Focus on File Explorer"),
category: Categories.View,
precondition: IsSessionsWindowContext,
f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib + 1,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyE,
when: IsSessionsWindowContext,
},
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const viewsService = accessor.get(IViewsService);
await viewsService.openView('sessions.files.explorer', true);
}
});

class ChangesViewActionsContribution extends Disposable implements IWorkbenchContribution {

static readonly ID = 'workbench.contrib.changesViewActions';
Expand Down
5 changes: 5 additions & 0 deletions src/vs/sessions/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import { ChatViewPane } from '../../../../workbench/contrib/chat/browser/widgetH
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { CopilotCLISessionType } from '../../../services/sessions/common/session.js';
import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { SessionsChatAccessibilityHelp } from './sessionsChatAccessibilityHelp.js';

export class OpenSessionWorktreeInVSCodeAction extends Action2 {
static readonly ID = 'chat.openSessionWorktreeInVSCode';
Expand Down Expand Up @@ -199,3 +201,6 @@ registerSingleton(IPromptsService, AgenticPromptsService, InstantiationType.Dela
registerSingleton(ISessionsConfigurationService, SessionsConfigurationService, InstantiationType.Delayed);
registerSingleton(IAICustomizationWorkspaceService, SessionsAICustomizationWorkspaceService, InstantiationType.Delayed);
registerSingleton(ICustomizationHarnessService, SessionsCustomizationHarnessService, InstantiationType.Delayed);

// register accessibility help
AccessibleViewRegistry.register(new SessionsChatAccessibilityHelp());
23 changes: 22 additions & 1 deletion src/vs/sessions/contrib/chat/browser/newChatViewPane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { ServiceCollection } from '../../../../platform/instantiation/common/ser
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { AccessibilityVerbositySettingId } from '../../../../workbench/contrib/accessibility/browser/accessibilityConfiguration.js';
import { AccessibilityCommandId } from '../../../../workbench/contrib/accessibility/common/accessibilityCommands.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
Expand Down Expand Up @@ -131,6 +133,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
@ISessionsProvidersService private readonly sessionsProvidersService: ISessionsProvidersService,
@IStorageService private readonly storageService: IStorageService,
@IWorkspaceTrustRequestService private readonly workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
) {
super();
this._history = this._register(this.instantiationService.createInstance(ChatHistoryNavigator, ChatAgentLocation.Chat));
Expand Down Expand Up @@ -262,6 +265,17 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {

// --- Editor ---

private _getAriaLabel(): string {
const verbose = this.configurationService.getValue<boolean>(AccessibilityVerbositySettingId.SessionsChat);
if (verbose) {
const kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();
return kbLabel
? localize('chatInput.accessibilityHelp', "Chat input. Press Enter to send out the request. Use {0} for Chat Accessibility Help.", kbLabel)
: localize('chatInput.accessibilityHelpNoKb', "Chat input. Press Enter to send out the request. Use the Chat Accessibility Help command for more information.");
}
return localize('chatInput', "Chat input");
}

private _createEditor(container: HTMLElement, overflowWidgetsDomNode: HTMLElement): void {
const editorContainer = this._editorContainer = dom.append(container, dom.$('.sessions-chat-editor'));
editorContainer.style.height = `${MIN_EDITOR_HEIGHT}px`;
Expand All @@ -281,7 +295,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
const editorOptions: IEditorConstructionOptions = {
...getSimpleEditorOptions(this.configurationService),
readOnly: false,
ariaLabel: localize('chatInput', "Chat input"),
ariaLabel: this._getAriaLabel(),
placeholder: localize('chatPlaceholder', "Run tasks in the background, type '#' for adding context"),
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: 13,
Expand Down Expand Up @@ -318,6 +332,13 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget {
// Ensure suggest widget renders above the input (not clipped by container)
SuggestController.get(this._editor)?.forceRenderingAbove();

// Update aria label when accessibility verbosity setting changes
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(AccessibilityVerbositySettingId.SessionsChat)) {
this._editor.updateOptions({ ariaLabel: this._getAriaLabel() });
}
}));

this._register(this._editor.onDidFocusEditorWidget(() => this._onDidFocus.fire()));
this._register(this._editor.onDidBlurEditorWidget(() => this._onDidBlur.fire()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,10 @@ export class WorkspacePicker extends Disposable {
const label = workspace ? workspace.label : localize('pickWorkspace', "workspace");
const icon = workspace ? workspace.icon : Codicon.project;

this._triggerElement.setAttribute('aria-label', workspace
? localize('workspacePicker.selectedAriaLabel', "New session in {0}", label)
: localize('workspacePicker.pickAriaLabel', "Start by picking a workspace"));

dom.append(this._triggerElement, renderIcon(icon));
const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label'));
labelSpan.textContent = label;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from '../../../../platform/accessibility/browser/accessibleView.js';
import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { AccessibilityVerbositySettingId } from '../../../../workbench/contrib/accessibility/browser/accessibilityConfiguration.js';
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
import { localize } from '../../../../nls.js';
import { FOCUS_AI_CUSTOMIZATION_VIEW_ID } from '../../aiCustomizationTreeView/browser/aiCustomizationTreeView.js';
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
import { NewChatViewPane, SessionsViewId } from './newChatViewPane.js';

export class SessionsChatAccessibilityHelp implements IAccessibleViewImplementation {
readonly priority = 120;
readonly name = 'sessionsChat';
readonly type = AccessibleViewType.Help;
readonly when = IsSessionsWindowContext;

getProvider(accessor: ServicesAccessor) {
const viewsService = accessor.get(IViewsService);

const content: string[] = [];
content.push(localize('sessionsChat.overview', "You are in the Agents app chat input. Type a message and press Enter to send it."));
content.push(localize('sessionsChat.workspace', "Shift+Tab to navigate to the workspace picker and choose a workspace for your session."));
content.push(localize('sessionsChat.history', "Use up and down arrows to navigate your request history in the input box."));
content.push(localize('sessionsChat.changes', "Focus the Changes view{0}.", '<keybinding:workbench.action.agentSessions.focusChangesView>'));
content.push(localize('sessionsChat.filesView', "Focus the Changes files view{0}.", '<keybinding:workbench.action.agentSessions.focusChangesFileView>'));
content.push(localize('sessionsChat.sessionsView', "Focus the Chat Sessions view{0}.", '<keybinding:workbench.action.chat.focusAgentSessionsViewer>'));
content.push(localize('sessionsChat.customizations', "Focus the Chat Customizations view{0}.", `<keybinding:${FOCUS_AI_CUSTOMIZATION_VIEW_ID}>`));

return new AccessibleContentProvider(
AccessibleViewProviderId.SessionsChat,
{ type: AccessibleViewType.Help },
() => content.join('\n'),
() => {
const view = viewsService.getActiveViewWithId<NewChatViewPane>(SessionsViewId);
view?.focus();
},
AccessibilityVerbositySettingId.SessionsChat,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export interface IAICustomizationShortcutsWidgetOptions {

export class AICustomizationShortcutsWidget extends Disposable {

private _headerButton: Button | undefined;

constructor(
container: HTMLElement,
options: IAICustomizationShortcutsWidgetOptions | undefined,
Expand Down Expand Up @@ -77,6 +79,7 @@ export class AICustomizationShortcutsWidget extends Disposable {
headerButton.element.classList.add('customization-link-button', 'sidebar-action-button');
headerButton.element.setAttribute('aria-expanded', String(!isCollapsed));
headerButton.label = localize('customizations', "Customizations");
this._headerButton = headerButton;

const chevronContainer = DOM.append(headerButton.element, $('span.customization-link-counts'));
const chevron = DOM.append(chevronContainer, $('.ai-customization-chevron'));
Expand Down Expand Up @@ -141,4 +144,8 @@ export class AICustomizationShortcutsWidget extends Disposable {

this._register(headerButton.onDidClick(() => toggleCollapse()));
}

focus(): void {
this._headerButton?.element.focus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,9 @@ class SessionsAccessibilityProvider {
if (isSessionShowMore(element)) {
return localize('showMoreAria', "Show {0} more sessions", element.remainingCount);
}
return element.title.get();
const title = element.title.get();
const created = fromNow(element.createdAt, true);
return localize('sessionItemAria', "{0}, created {1}", title, created);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class SessionsView extends ViewPane {
private viewPaneContainer: HTMLElement | undefined;
private sessionsControlContainer: HTMLElement | undefined;
sessionsControl: SessionsList | undefined;
private _customizationsWidget: AICustomizationShortcutsWidget | undefined;
private currentGrouping: SessionsGrouping = SessionsGrouping.Workspace;
private currentSorting: SessionsSorting = SessionsSorting.Created;
private groupingContextKey: IContextKey | undefined;
Expand Down Expand Up @@ -236,7 +237,7 @@ export class SessionsView extends ViewPane {
}));

// AI Customization toolbar (bottom, fixed height)
this._register(this.instantiationService.createInstance(AICustomizationShortcutsWidget, sessionsContainer, {
this._customizationsWidget = this._register(this.instantiationService.createInstance(AICustomizationShortcutsWidget, sessionsContainer, {
onDidChangeLayout: () => {
if (this.viewPaneContainer) {
const { offsetHeight, offsetWidth } = this.viewPaneContainer;
Expand All @@ -246,6 +247,10 @@ export class SessionsView extends ViewPane {
}));
}

focusCustomizations(): void {
this._customizationsWidget?.focus();
}

private restoreLastSelectedSession(): void {
const activeSession = this.sessionsManagementService.activeSession.get();
if (activeSession && this.sessionsControl) {
Expand Down
Loading
Loading