Skip to content

Commit 7f8c7c3

Browse files
authored
Merge pull request microsoft#182330 from microsoft/merogge/acc-interactive
Add accessibility help menu to chat input
2 parents 149db50 + 2d6c2a2 commit 7f8c7c3

File tree

5 files changed

+110
-1
lines changed

5 files changed

+110
-1
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ const configuration: IConfigurationNode = {
2323
type: 'boolean',
2424
default: true,
2525
tags: ['accessibility']
26+
},
27+
'accessibility.verbosity.chatInput': {
28+
description: localize('verbosity.chatInput.description', 'Provide information about how to access the interactive session accessibility help menu when the interactive input is focused'),
29+
type: 'boolean',
30+
default: true,
31+
tags: ['accessibility']
2632
}
2733
}
2834
};
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { localize } from 'vs/nls';
7+
import { format } from 'vs/base/common/strings';
8+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
9+
10+
export function getAccessibilityHelpText(keybindingService: IKeybindingService): string {
11+
const content = [];
12+
content.push(localize('chat.accessibilityHelp', "Chat Accessibility Help"));
13+
content.push(localize('interactiveSession.helpMenuExit', "Exit this menu and return to the interactive editor input via the Escape key."));
14+
content.push(descriptionForCommand('interactiveSession.action.focus', localize('interactiveSession.action.focus', 'The Focus Interactive Session ({0}) command focuses the chat request/response list, which can be navigated with UpArrow/DownArrow.',), localize('interactiveSession.action.focusNoKb', 'The Focus Interactive Session command focuses the chat request/response list, which can be navigated with UpArrow/DownArrow and is currently not triggerable by a keybinding.'), keybindingService));
15+
content.push(descriptionForCommand('workbench.action.interactiveSession.focusInput', localize('workbench.action.interactiveSession.focusInput', 'The Focus Interactive Session Input ({0}) command focuses the input box for chat requests.'), localize('workbench.action.interactiveSession.focusInputNoKb', 'Focus Interactive Session Input command focuses the input box for chat requests and is currently not triggerable by a keybinding.'), keybindingService));
16+
return content.join('\n');
17+
}
18+
19+
function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string {
20+
const kb = keybindingService.lookupKeybinding(commandId);
21+
if (kb) {
22+
return format(msg, kb.getAriaLabel());
23+
}
24+
return format(noKbMsg, commandId);
25+
}

src/vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { addStandardDisposableListener } from 'vs/base/browser/dom';
67
import { Codicon } from 'vs/base/common/codicons';
78
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
9+
import { withNullAsUndefined } from 'vs/base/common/types';
810
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
911
import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions';
1012
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
1113
import { localize } from 'vs/nls';
1214
import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
1315
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
16+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1417
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
1518
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
1619
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
1720
import { ActiveEditorContext } from 'vs/workbench/common/contextkeys';
21+
import { getAccessibilityHelpText } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionAccessibilityHelp';
1822
import { clearChatEditor, clearChatSession } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionClear';
1923
import { IInteractiveSessionWidgetService } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
2024
import { IInteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionEditor';
@@ -124,6 +128,58 @@ export function registerInteractiveSessionActions() {
124128
}
125129
});
126130

131+
registerEditorAction(class AccessibilityHelpInteractiveSessionAction extends EditorAction {
132+
constructor() {
133+
super({
134+
id: 'interactiveSession.action.accessibilityHelp',
135+
label: localize('actions.interactiveSession.accessibiltyHelp', "Chat View Accessibility Help"),
136+
alias: 'Chat View Accessibility Help',
137+
precondition: CONTEXT_IN_INTERACTIVE_INPUT,
138+
kbOpts: {
139+
primary: KeyMod.Alt | KeyCode.F1,
140+
weight: KeybindingWeight.EditorContrib
141+
}
142+
});
143+
}
144+
145+
async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
146+
const widgetService = accessor.get(IInteractiveSessionWidgetService);
147+
const keybindingService = accessor.get(IKeybindingService);
148+
const inputEditor = widgetService.lastFocusedWidget?.inputEditor;
149+
const editorUri = editor.getModel()?.uri;
150+
151+
if (!inputEditor || !editorUri) {
152+
return;
153+
}
154+
155+
const widget = widgetService.getWidgetByInputUri(editorUri);
156+
const domNode = withNullAsUndefined(inputEditor.getDomNode());
157+
158+
if (!domNode || !widget) {
159+
return;
160+
}
161+
162+
const cachedInput = inputEditor.getValue();
163+
const cachedPosition = inputEditor.getPosition();
164+
165+
const helpText = getAccessibilityHelpText(keybindingService);
166+
inputEditor.setValue(helpText);
167+
inputEditor.updateOptions({ readOnly: true });
168+
inputEditor.focus();
169+
const disposable = addStandardDisposableListener(domNode, 'keydown', e => {
170+
if (e.keyCode === KeyCode.Escape && inputEditor.getValue() === helpText) {
171+
inputEditor.updateOptions({ readOnly: false });
172+
inputEditor.setValue(cachedInput);
173+
if (cachedPosition) {
174+
inputEditor.setPosition(cachedPosition);
175+
}
176+
widget.focusInput();
177+
disposable.dispose();
178+
}
179+
});
180+
}
181+
});
182+
127183
registerAction2(class FocusInteractiveSessionInputAction extends Action2 {
128184
constructor() {
129185
super({

src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionInputPart.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import { IModelService } from 'vs/editor/common/services/model';
1717
import { localize } from 'vs/nls';
1818
import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
1919
import { MenuId } from 'vs/platform/actions/common/actions';
20+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2021
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2122
import { registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget';
2223
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2324
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
25+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2426
import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style';
2527
import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
2628
import { IInteractiveSessionExecuteActionContext } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionExecuteActions';
@@ -75,12 +77,28 @@ export class InteractiveSessionInputPart extends Disposable implements IHistoryN
7577
@IModelService private readonly modelService: IModelService,
7678
@IInstantiationService private readonly instantiationService: IInstantiationService,
7779
@IContextKeyService private readonly contextKeyService: IContextKeyService,
80+
@IConfigurationService private readonly configurationService: IConfigurationService,
81+
@IKeybindingService private readonly keybindingService: IKeybindingService
7882
) {
7983
super();
8084

8185
this.inputEditorHasText = CONTEXT_INTERACTIVE_INPUT_HAS_TEXT.bindTo(contextKeyService);
8286
this.history = new HistoryNavigator([], 5);
8387
this._register(this.historyService.onDidClearHistory(() => this.history.clear()));
88+
this._register(this.configurationService.onDidChangeConfiguration(e => {
89+
if (e.affectsConfiguration('accessibility.verbosity.chatInput')) {
90+
this.inputEditor.updateOptions({ ariaLabel: this._getAriaLabel() });
91+
}
92+
}));
93+
}
94+
95+
private _getAriaLabel(): string {
96+
const verbose = this.configurationService.getValue<boolean>('accessibility.verbosity.chatInput');
97+
if (verbose) {
98+
const kbLabel = this.keybindingService.lookupKeybinding('interactiveSession.action.accessibilityHelp')?.getLabel();
99+
return kbLabel ? localize('interactiveSessionInput.accessibilityHelp', "Chat Input, Type code here and press Enter to run. Use {0} for Interactive Session Accessibility Help.", kbLabel) : localize('interactiveSessionInput.accessibilityHelpNoKb', "Chat Input, Type code here and press Enter to run. Use the Interactive Session Accessibility Help command for more information.");
100+
}
101+
return localize('interactiveSessionInput', "Chat Input");
84102
}
85103

86104
setState(providerId: string, inputValue: string): void {
@@ -152,7 +170,7 @@ export class InteractiveSessionInputPart extends Disposable implements IHistoryN
152170

153171
const options = getSimpleEditorOptions();
154172
options.readOnly = false;
155-
options.ariaLabel = localize('chatInput', "Chat Input");
173+
options.ariaLabel = this._getAriaLabel();
156174
options.fontFamily = DEFAULT_FONT_FAMILY;
157175
options.fontSize = 13;
158176
options.lineHeight = 20;

src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionWidget.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { isEqual } from 'vs/base/common/resources';
1212
import { URI } from 'vs/base/common/uri';
1313
import 'vs/css!./media/interactiveSession';
1414
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
15+
import { EditorOption } from 'vs/editor/common/config/editorOptions';
1516
import { localize } from 'vs/nls';
1617
import { MenuId } from 'vs/platform/actions/common/actions';
1718
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -423,6 +424,9 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
423424
}
424425

425426
getViewState(): IViewState {
427+
if (this.inputEditor.getOption(EditorOption.readOnly)) {
428+
return { inputValue: undefined };
429+
}
426430
this.inputPart.saveState();
427431
return { inputValue: this.inputPart.inputEditor.getValue() };
428432
}

0 commit comments

Comments
 (0)