Skip to content

Commit c770ca6

Browse files
authored
Merge pull request microsoft#188318 from microsoft/merogge/accessible-view-hint
add accessible view hint, next/previous iterations through chat responses, hint about navigating through notifications/chat responses
2 parents cb0f0ef + 3a5743c commit c770ca6

File tree

12 files changed

+162
-62
lines changed

12 files changed

+162
-62
lines changed

src/vs/workbench/browser/parts/notifications/notificationsToasts.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
2525
import { IntervalCounter } from 'vs/base/common/async';
2626
import { assertIsDefined } from 'vs/base/common/types';
2727
import { NotificationsToastsVisibleContext } from 'vs/workbench/common/contextkeys';
28+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
29+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2830

2931
interface INotificationToast {
3032
readonly item: INotificationViewItem;
@@ -83,7 +85,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast
8385
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
8486
@IContextKeyService private readonly contextKeyService: IContextKeyService,
8587
@ILifecycleService private readonly lifecycleService: ILifecycleService,
86-
@IHostService private readonly hostService: IHostService
88+
@IHostService private readonly hostService: IHostService,
89+
@IKeybindingService private readonly _keybindingService: IKeybindingService,
90+
@IConfigurationService private readonly _configurationService: IConfigurationService
8791
) {
8892
super(themeService);
8993

@@ -187,11 +191,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast
187191
const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, {
188192
verticalScrollMode: ScrollbarVisibility.Hidden,
189193
widgetAriaLabel: (() => {
190-
if (!item.source) {
191-
return localize('notificationAriaLabel', "{0}, notification", item.message.raw);
194+
let accessibleViewHint: string | undefined;
195+
const keybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel();
196+
if (this._configurationService.getValue('accessibility.verbosity.notification')) {
197+
accessibleViewHint = keybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding");
192198
}
193199

194-
return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source);
200+
if (!item.source) {
201+
if (accessibleViewHint) {
202+
return localize('notificationAriaLabelViewHint', "{0}, notification {1}", item.message.raw, accessibleViewHint);
203+
} else {
204+
return localize('notificationAriaLabel', "{0}, notification", item.message.raw);
205+
}
206+
}
207+
if (accessibleViewHint) {
208+
return localize('notificationWithSourceAriaLabelViewHint', "{0}, source: {1}, notification {2}", item.message.raw, item.source, accessibleViewHint);
209+
} else {
210+
return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source);
211+
}
195212
})()
196213
});
197214
itemDisposables.add(notificationList);

src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/b
1212
import { localize } from 'vs/nls';
1313
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
1414
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
15-
import { AccessibilityHelpAction, AccessibleViewAction, AccessibleViewNextAction, AccessibleViewPreviousAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
15+
import { AccessibilityHelpAction, AccessibilityVerbositySettingId, AccessibleViewAction, AccessibleViewNextAction, AccessibleViewPreviousAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
1616
import * as strings from 'vs/base/common/strings';
1717
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1818
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
@@ -36,7 +36,7 @@ class AccessibilityHelpProvider implements IAccessibleContentProvider {
3636
this._editor.focus();
3737
}
3838
options: IAccessibleViewOptions = { type: AccessibleViewType.HelpMenu, ariaLabel: localize('editor-help', "editor accessibility help"), readMoreUrl: 'https://go.microsoft.com/fwlink/?linkid=851010' };
39-
verbositySettingKey: string = 'editor';
39+
verbositySettingKey = AccessibilityVerbositySettingId.Editor;
4040
constructor(
4141
private readonly _editor: ICodeEditor,
4242
@IKeybindingService private readonly _keybindingService: IKeybindingService
@@ -118,7 +118,7 @@ class HoverAccessibleViewContribution extends Disposable {
118118
return false;
119119
}
120120
accessibleViewService.show({
121-
verbositySettingKey: 'hover',
121+
verbositySettingKey: AccessibilityVerbositySettingId.Hover,
122122
provideContent() { return editorHoverContent; },
123123
onClose() { },
124124
options: this._options
@@ -135,7 +135,7 @@ class HoverAccessibleViewContribution extends Disposable {
135135
return false;
136136
}
137137
accessibleViewService.show({
138-
verbositySettingKey: 'hover',
138+
verbositySettingKey: AccessibilityVerbositySettingId.Hover,
139139
provideContent() { return extensionHoverContent; },
140140
onClose() { },
141141
options: this._options
@@ -208,7 +208,7 @@ class NotificationAccessibleViewContribution extends Disposable {
208208
list.focusPrevious();
209209
renderAccessibleView();
210210
},
211-
verbositySettingKey: 'notifications',
211+
verbositySettingKey: AccessibilityVerbositySettingId.Notification,
212212
options: {
213213
ariaLabel: localize('notification', "Notification Accessible View"),
214214
type: AccessibleViewType.View

src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ export const enum AccessibilityVerbositySettingId {
1717
Chat = 'accessibility.verbosity.panelChat',
1818
InlineChat = 'accessibility.verbosity.inlineChat',
1919
KeybindingsEditor = 'accessibility.verbosity.keybindingsEditor',
20-
Notebook = 'accessibility.verbosity.notebook'
20+
Notebook = 'accessibility.verbosity.notebook',
21+
Editor = 'accessibility.verbosity.editor',
22+
Hover = 'accessibility.verbosity.hover',
23+
Notification = 'accessibility.verbosity.notification'
2124
}
2225

2326
const baseProperty: object = {
@@ -54,6 +57,14 @@ const configuration: IConfigurationNode = {
5457
[AccessibilityVerbositySettingId.Notebook]: {
5558
description: localize('verbosity.notebook', 'Provide information about how to focus the cell container or inner editor when a notebook cell is focused.'),
5659
...baseProperty
60+
},
61+
[AccessibilityVerbositySettingId.Hover]: {
62+
description: localize('verbosity.hover', 'Provide information about how to open the hover in an accessible view.'),
63+
...baseProperty
64+
},
65+
[AccessibilityVerbositySettingId.Notification]: {
66+
description: localize('verbosity.notification', 'Provide information about how to open the notification in an accessible view.'),
67+
...baseProperty
5768
}
5869
}
5970
};
@@ -118,7 +129,7 @@ export const AccessibleViewNextAction = registerCommand(new MultiCommand({
118129
menuOpts: [{
119130
menuId: MenuId.CommandPalette,
120131
group: '',
121-
title: localize('editor.action.accessibleViewNext', "Next Accessible View"),
132+
title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View"),
122133
order: 1
123134
}],
124135
}));
@@ -133,7 +144,7 @@ export const AccessibleViewPreviousAction = registerCommand(new MultiCommand({
133144
menuOpts: [{
134145
menuId: MenuId.CommandPalette,
135146
group: '',
136-
title: localize('editor.action.accessibleViewPrevious', "Previous Accessible View"),
147+
title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View"),
137148
order: 1
138149
}],
139150
}));

src/vs/workbench/contrib/accessibility/browser/accessibleView.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
2525
import { alert } from 'vs/base/browser/ui/aria/aria';
2626
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
2727
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
28+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
29+
import { AccessibilityVerbositySettingId, AccessibleViewAction, AccessibleViewNextAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
2830

2931
const enum DEFAULT {
3032
WIDTH = 800,
3133
TOP = 3
3234
}
3335

3436
export interface IAccessibleContentProvider {
35-
verbositySettingKey: string;
37+
verbositySettingKey: AccessibilityVerbositySettingId;
3638
provideContent(): string;
3739
onClose(): void;
3840
onKeyDown?(e: IKeyboardEvent): void;
@@ -48,6 +50,7 @@ export interface IAccessibleViewService {
4850
show(provider: IAccessibleContentProvider): void;
4951
next(): void;
5052
previous(): void;
53+
getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string;
5154
}
5255

5356
export const enum AccessibleViewType {
@@ -79,7 +82,8 @@ class AccessibleView extends Disposable {
7982
@IModelService private readonly _modelService: IModelService,
8083
@IContextViewService private readonly _contextViewService: IContextViewService,
8184
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
82-
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
85+
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
86+
@IKeybindingService private readonly _keybindingService: IKeybindingService
8387
) {
8488
super();
8589
this._accessiblityHelpIsShown = accessibilityHelpIsShown.bindTo(this._contextKeyService);
@@ -169,7 +173,7 @@ class AccessibleView extends Disposable {
169173
? AccessibilityHelpNLS.changeConfigToOnMac
170174
: AccessibilityHelpNLS.changeConfigToOnWinLinux
171175
);
172-
if (accessibilitySupport && provider.verbositySettingKey === 'editor') {
176+
if (accessibilitySupport && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) {
173177
message = AccessibilityHelpNLS.auto_on;
174178
message += '\n';
175179
} else if (!accessibilitySupport) {
@@ -193,7 +197,7 @@ class AccessibleView extends Disposable {
193197
model.setLanguage(provider.options.language);
194198
}
195199
container.appendChild(this._editorContainer);
196-
this._editorWidget.updateOptions({ ariaLabel: provider.options.ariaLabel });
200+
this._editorWidget.updateOptions({ ariaLabel: provider.next && provider.previous ? localize('accessibleViewAriaLabelWithNav', "{0} {1}", provider.options.ariaLabel, this._getNavigationAriaHint(provider.verbositySettingKey)) : localize('accessibleViewAriaLabel', "{0}", provider.options.ariaLabel) });
197201
this._editorWidget.focus();
198202
});
199203
const disposableStore = new DisposableStore();
@@ -235,14 +239,26 @@ class AccessibleView extends Disposable {
235239
}
236240
return this._modelService.createModel(resource.fragment, null, resource, false);
237241
}
242+
243+
private _getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string {
244+
let hint = '';
245+
const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel();
246+
const previousKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel();
247+
if (this._configurationService.getValue(verbositySettingKey)) {
248+
hint = (nextKeybinding && previousKeybinding) ? localize('chatAccessibleViewNextPreviousHint', "Show the next {0} or previous {1} item in the accessible view", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View");
249+
}
250+
return hint;
251+
}
238252
}
239253

240254
export class AccessibleViewService extends Disposable implements IAccessibleViewService {
241255
declare readonly _serviceBrand: undefined;
242256
private _accessibleView: AccessibleView | undefined;
243257

244258
constructor(
245-
@IInstantiationService private readonly _instantiationService: IInstantiationService
259+
@IInstantiationService private readonly _instantiationService: IInstantiationService,
260+
@IConfigurationService private readonly _configurationService: IConfigurationService,
261+
@IKeybindingService private readonly _keybindingService: IKeybindingService
246262
) {
247263
super();
248264
}
@@ -259,4 +275,12 @@ export class AccessibleViewService extends Disposable implements IAccessibleView
259275
previous(): void {
260276
this._accessibleView?.previous();
261277
}
278+
getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string {
279+
let hint = '';
280+
const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel();
281+
if (this._configurationService.getValue(verbositySettingKey)) {
282+
hint = keybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding");
283+
}
284+
return hint;
285+
}
262286
}

src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
1212
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
1313
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
1414
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
15+
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
1516

1617
export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string {
1718
const keybindingService = accessor.get(IKeybindingService);
@@ -70,7 +71,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi
7071
inputEditor.getSupportedActions();
7172
const helpText = getAccessibilityHelpText(accessor, type);
7273
accessibleViewService.show({
73-
verbositySettingKey: type,
74+
verbositySettingKey: type as AccessibilityVerbositySettingId,
7475
provideContent: () => helpText,
7576
onClose: () => {
7677
if (type === 'panelChat' && cachedPosition) {

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
3737
import '../common/chatColors';
3838
import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
3939
import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions';
40-
import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
40+
import { AccessibilityVerbositySettingId, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
4141
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
4242
import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
4343
import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
@@ -135,48 +135,61 @@ class ChatAccessibleViewContribution extends Disposable {
135135
const accessibleViewService = accessor.get(IAccessibleViewService);
136136
const widgetService = accessor.get(IChatWidgetService);
137137
const codeEditorService = accessor.get(ICodeEditorService);
138+
return renderAccessibleView(accessibleViewService, widgetService, codeEditorService, true);
139+
function renderAccessibleView(accessibleViewService: IAccessibleViewService, widgetService: IChatWidgetService, codeEditorService: ICodeEditorService, initialRender?: boolean): boolean {
140+
let widget = widgetService.lastFocusedWidget;
141+
if (!widget) {
142+
return false;
143+
}
138144

139-
let widget = widgetService.lastFocusedWidget;
140-
if (!widget) {
141-
return false;
142-
}
145+
const chatInputFocused = initialRender && !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor());
143146

144-
const chatInputFocused = !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor());
147+
if (chatInputFocused) {
148+
widget.focusLastMessage();
149+
widget = widgetService.lastFocusedWidget;
150+
}
145151

146-
if (chatInputFocused) {
147-
widget.focusLastMessage();
148-
widget = widgetService.lastFocusedWidget;
149-
}
152+
if (!widget) {
153+
return false;
154+
}
150155

151-
if (!widget) {
152-
return false;
153-
}
156+
const verifiedWidget: IChatWidget = widget;
157+
const focusedItem = verifiedWidget.getFocus();
154158

155-
const verifiedWidget: IChatWidget = widget;
156-
const focusedItem = verifiedWidget.getFocus();
159+
if (!focusedItem) {
160+
return false;
161+
}
157162

158-
if (!focusedItem) {
159-
return false;
160-
}
163+
widget.focus(focusedItem);
161164

162-
const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined;
163-
if (!responseContent) {
164-
return false;
165-
}
165+
const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined;
166+
if (!responseContent) {
167+
return false;
168+
}
166169

167-
accessibleViewService.show({
168-
verbositySettingKey: 'panelChat',
169-
provideContent(): string { return responseContent; },
170-
onClose() {
171-
if (chatInputFocused) {
172-
verifiedWidget.focusInput();
173-
} else {
174-
verifiedWidget.focus(focusedItem);
175-
}
176-
},
177-
options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View }
178-
});
179-
return true;
170+
accessibleViewService.show({
171+
verbositySettingKey: AccessibilityVerbositySettingId.Chat,
172+
provideContent(): string { return responseContent; },
173+
onClose() {
174+
if (chatInputFocused) {
175+
verifiedWidget.focusInput();
176+
} else {
177+
verifiedWidget.reveal(focusedItem);
178+
verifiedWidget.focus(focusedItem);
179+
}
180+
},
181+
next() {
182+
verifiedWidget.moveFocus(focusedItem, 'next');
183+
renderAccessibleView(accessibleViewService, widgetService, codeEditorService);
184+
},
185+
previous() {
186+
verifiedWidget.moveFocus(focusedItem, 'previous');
187+
renderAccessibleView(accessibleViewService, widgetService, codeEditorService);
188+
},
189+
options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View }
190+
});
191+
return true;
192+
}
180193
}, CONTEXT_IN_CHAT_SESSION));
181194
}
182195
}

src/vs/workbench/contrib/chat/browser/chat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface IChatWidget {
5656

5757
reveal(item: ChatTreeItem): void;
5858
focus(item: ChatTreeItem): void;
59+
moveFocus(item: ChatTreeItem, type: 'next' | 'previous'): void;
5960
getFocus(): ChatTreeItem | undefined;
6061
acceptInput(query?: string): void;
6162
focusLastMessage(): void;

0 commit comments

Comments
 (0)