Skip to content

Commit 73a5961

Browse files
authored
Merge pull request microsoft#185614 from microsoft/merogge/accessible-view-chat
accessible view adoption/ improvements
2 parents c1897a7 + 686f1ce commit 73a5961

File tree

13 files changed

+202
-55
lines changed

13 files changed

+202
-55
lines changed

src/vs/editor/common/standaloneStrings.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
77

88
export namespace AccessibilityHelpNLS {
99
export const accessibilityHelpTitle = nls.localize('accessibilityHelpTitle', "Accessibility Help");
10-
export const openingDocs = nls.localize("openingDocs", "Now opening the Editor Accessibility documentation page.");
10+
export const openingDocs = nls.localize("openingDocs", "Now opening the Accessibility documentation page.");
1111
export const readonlyDiffEditor = nls.localize("readonlyDiffEditor", "You are in a read-only pane of a diff editor.");
1212
export const editableDiffEditor = nls.localize("editableDiffEditor", "You are in a pane of a diff editor.");
1313
export const readonlyEditor = nls.localize("readonlyEditor", "You are in a read-only code editor");
@@ -20,7 +20,6 @@ export namespace AccessibilityHelpNLS {
2020
export const tabFocusModeOnMsgNoKb = nls.localize("tabFocusModeOnMsgNoKb", "Pressing Tab in the current editor will move focus to the next focusable element. The command {0} is currently not triggerable by a keybinding.");
2121
export const tabFocusModeOffMsg = nls.localize("tabFocusModeOffMsg", "Pressing Tab in the current editor will insert the tab character. Toggle this behavior by pressing {0}.");
2222
export const tabFocusModeOffMsgNoKb = nls.localize("tabFocusModeOffMsgNoKb", "Pressing Tab in the current editor will insert the tab character. The command {0} is currently not triggerable by a keybinding.");
23-
export const openDoc = nls.localize("openDoc", "Press H now to open a browser window with more information related to editor accessibility.");
2423
export const showAccessibilityHelpAction = nls.localize("showAccessibilityHelpAction", "Show Accessibility Help");
2524
}
2625

src/vs/editor/contrib/hover/browser/contentHover.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,24 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
2727

2828
const $ = dom.$;
2929

30+
export interface IHoverElementInfo {
31+
dimensions?: { width: number; height: number }; content?: string;
32+
}
33+
3034
export class ContentHoverController extends Disposable {
3135

3236
private readonly _participants: IEditorHoverParticipant[];
37+
3338
private readonly _widget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor));
39+
40+
getWidgetInfo(): IHoverElementInfo | undefined {
41+
const node = this._widget.getDomNode();
42+
if (!node.textContent) {
43+
return undefined;
44+
}
45+
return { content: node.textContent, dimensions: { width: node.clientWidth, height: node.clientHeight } };
46+
}
47+
3448
private readonly _computer: ContentHoverComputer;
3549
private readonly _hoverOperation: HoverOperation<IHoverPart>;
3650

src/vs/editor/contrib/hover/browser/hover.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
1515
import { ILanguageService } from 'vs/editor/common/languages/language';
1616
import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition';
1717
import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
18-
import { ContentHoverWidget, ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover';
18+
import { ContentHoverWidget, ContentHoverController, IHoverElementInfo } from 'vs/editor/contrib/hover/browser/contentHover';
1919
import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHover';
2020
import * as nls from 'vs/nls';
2121
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
@@ -40,6 +40,9 @@ export class ModesHoverController implements IEditorContribution {
4040
private readonly _didChangeConfigurationHandler: IDisposable;
4141

4242
private _contentWidget: ContentHoverController | null;
43+
44+
getWidgetInfo(): IHoverElementInfo | undefined { return this._contentWidget?.getWidgetInfo(); }
45+
4346
private _glyphWidget: MarginHoverWidget | null;
4447

4548
private _isMouseDown: boolean;

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

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/b
1212
import { localize } from 'vs/nls';
1313
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
1414
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
15-
import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
16-
import { AccessibilityHelpAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
17-
import { AccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
15+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
16+
import { AccessibilityHelpAction, AccessibilityViewAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution';
17+
import { AccessibleViewService, AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
1818
import * as strings from 'vs/base/common/strings';
1919
import * as platform from 'vs/base/common/platform';
2020
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -23,9 +23,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
2323
import { Registry } from 'vs/platform/registry/common/platform';
2424
import { ICommandService } from 'vs/platform/commands/common/commands';
2525
import { NEW_UNTITLED_FILE_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileConstants';
26-
import { KeyCode } from 'vs/base/common/keyCodes';
27-
import { URI } from 'vs/base/common/uri';
28-
import { IOpenerService } from 'vs/platform/opener/common/opener';
26+
import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover';
27+
import { withNullAsUndefined } from 'vs/base/common/types';
2928

3029
registerAccessibilityConfiguration();
3130
registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed);
@@ -35,25 +34,18 @@ class AccessibilityHelpProvider extends Disposable implements IAccessibleContent
3534
this._editor.focus();
3635
this.dispose();
3736
}
38-
options: IAccessibleViewOptions = { ariaLabel: localize('terminal-help-label', "terminal accessibility help") };
37+
options: IAccessibleViewOptions = { type: AccessibleViewType.HelpMenu, ariaLabel: localize('terminal-help-label', "terminal accessibility help") };
3938
id: string = 'editor';
40-
onKeyDown(e: IKeyboardEvent): void {
41-
if (e.keyCode === KeyCode.KeyH) {
42-
alert(AccessibilityHelpNLS.openingDocs);
43-
44-
let url = (this._editor.getRawOptions() as any).accessibilityHelpUrl;
45-
if (typeof url === 'undefined') {
46-
url = 'https://go.microsoft.com/fwlink/?linkid=852450';
47-
}
48-
this._openerService.open(URI.parse(url));
49-
}
50-
}
5139
constructor(
5240
private readonly _editor: ICodeEditor,
53-
@IKeybindingService private readonly _keybindingService: IKeybindingService,
54-
@IOpenerService private readonly _openerService: IOpenerService
41+
@IKeybindingService private readonly _keybindingService: IKeybindingService
5542
) {
5643
super();
44+
let url = (this._editor.getRawOptions() as any).accessibilityHelpUrl;
45+
if (typeof url === 'undefined') {
46+
url = 'https://go.microsoft.com/fwlink/?linkid=852450';
47+
}
48+
this.options.readMoreUrl = url;
5749
}
5850

5951
private _descriptionForCommand(commandId: string, msg: string, noKbMsg: string): string {
@@ -105,7 +97,6 @@ class AccessibilityHelpProvider extends Disposable implements IAccessibleContent
10597
} else {
10698
content.push(this._descriptionForCommand(ToggleTabFocusModeAction.ID, AccessibilityHelpNLS.tabFocusModeOffMsg, AccessibilityHelpNLS.tabFocusModeOffMsgNoKb));
10799
}
108-
content.push(AccessibilityHelpNLS.openDoc);
109100
return content.join('\n');
110101
}
111102
}
@@ -132,3 +123,45 @@ class EditorAccessibilityHelpContribution extends Disposable {
132123

133124
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
134125
workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribution, LifecyclePhase.Eventually);
126+
127+
128+
129+
class HoverAccessibleViewContribution extends Disposable {
130+
static ID: 'hoverAccessibleViewContribution';
131+
constructor() {
132+
super();
133+
this._register(AccessibilityViewAction.addImplementation(90, 'hover', accessor => {
134+
const accessibleViewService = accessor.get(IAccessibleViewService);
135+
const codeEditorService = accessor.get(ICodeEditorService);
136+
const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
137+
if (!editor) {
138+
return false;
139+
}
140+
const controller = ModesHoverController.get(editor);
141+
const hoverInfo = withNullAsUndefined(controller?.getWidgetInfo());
142+
if (!controller || !hoverInfo?.content) {
143+
return false;
144+
}
145+
function provideContent(): string {
146+
return hoverInfo?.content!;
147+
}
148+
const provider = accessibleViewService.registerProvider({
149+
id: 'hover',
150+
provideContent,
151+
onClose() {
152+
provider.dispose();
153+
controller.focus();
154+
},
155+
options: {
156+
ariaLabel: localize('hoverAccessibleView', "Hover Accessible View"), language: 'typescript', type: AccessibleViewType.View, dimensions: controller.getWidgetInfo()!.dimensions
157+
}
158+
});
159+
accessibleViewService.show('hover');
160+
return true;
161+
}));
162+
}
163+
}
164+
165+
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
166+
workbenchContributionsRegistry.registerWorkbenchContribution(HoverAccessibleViewContribution, LifecyclePhase.Eventually);
167+

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
1313
export const enum AccessibilityVerbositySettingId {
1414
Terminal = 'accessibility.verbosity.terminal',
1515
DiffEditor = 'accessibility.verbosity.diffEditor',
16-
Chat = 'accessibility.verbosity.chat',
16+
Chat = 'accessibility.verbosity.panelChat',
1717
InlineChat = 'accessibility.verbosity.inlineChat',
1818
KeybindingsEditor = 'accessibility.verbosity.keybindingsEditor',
1919
Notebook = 'accessibility.verbosity.notebook'
@@ -79,3 +79,17 @@ export const AccessibilityHelpAction = registerCommand(new MultiCommand({
7979
}
8080
}
8181
}));
82+
83+
84+
export const AccessibilityViewAction = registerCommand(new MultiCommand({
85+
id: 'editor.action.accessibilityView',
86+
precondition: undefined,
87+
kbOpts: {
88+
primary: KeyMod.Alt | KeyCode.F2,
89+
weight: KeybindingWeight.WorkbenchContrib,
90+
linux: {
91+
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1,
92+
secondary: [KeyMod.Alt | KeyCode.F1]
93+
}
94+
}
95+
}));

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

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
1212
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
1313
import { ITextModel } from 'vs/editor/common/model';
1414
import { IModelService } from 'vs/editor/common/services/model';
15+
import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings';
1516
import { LinkDetector } from 'vs/editor/contrib/links/browser/links';
1617
import { localize } from 'vs/nls';
1718
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
1819
import { IContextViewDelegate, IContextViewService } from 'vs/platform/contextview/browser/contextView';
1920
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
21+
import { IOpenerService } from 'vs/platform/opener/common/opener';
2022
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
2123
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
2224
import { IDisposable } from 'xterm';
2325

26+
const enum DIMENSION_DEFAULT {
27+
WIDTH = .6,
28+
HEIGHT = 200
29+
}
2430

2531
export interface IAccessibleContentProvider {
2632
id: string;
@@ -38,8 +44,17 @@ export interface IAccessibleViewService {
3844
registerProvider(provider: IAccessibleContentProvider): IDisposable;
3945
}
4046

47+
export const enum AccessibleViewType {
48+
HelpMenu = 'helpMenu',
49+
View = 'view'
50+
}
51+
4152
export interface IAccessibleViewOptions {
4253
ariaLabel: string;
54+
readMoreUrl?: string;
55+
language?: string;
56+
type: AccessibleViewType;
57+
dimensions?: { width: number; height: number };
4358
}
4459

4560
class AccessibleView extends Disposable {
@@ -48,6 +63,7 @@ class AccessibleView extends Disposable {
4863
private _editorContainer: HTMLElement;
4964

5065
constructor(
66+
@IOpenerService private readonly _openerService: IOpenerService,
5167
@IInstantiationService private readonly _instantiationService: IInstantiationService,
5268
@IConfigurationService private readonly _configurationService: IConfigurationService,
5369
@IModelService private readonly _modelService: IModelService,
@@ -89,8 +105,11 @@ class AccessibleView extends Disposable {
89105
}
90106

91107
private _render(provider: IAccessibleContentProvider, container: HTMLElement): IDisposable {
92-
93-
const fragment = localize('exit-tip', 'Exit this menu via the Escape key.\n') + provider.provideContent();
108+
const settingKey = `accessibility.verbosity.${provider.id}`;
109+
const value = this._configurationService.getValue(settingKey);
110+
const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\nPress H now to open a browser window with more information related to accessibility.\n") : '';
111+
const disableHelpHint = provider.options.type && value ? localize('disable-help-hint', '\nTo disable the `accessibility.verbosity` hint for this feature, press D now.\n') : '\n';
112+
const fragment = provider.provideContent() + readMoreLink + disableHelpHint + localize('exit-tip', 'Exit this menu via the Escape key.');
94113

95114
this._getTextModel(URI.from({ path: `accessible-view-${provider.id}`, scheme: 'accessible-view', fragment })).then((model) => {
96115
if (!model) {
@@ -101,32 +120,35 @@ class AccessibleView extends Disposable {
101120
if (!domNode) {
102121
return;
103122
}
123+
if (provider.options.language) {
124+
model.setLanguage(provider.options.language);
125+
}
104126
container.appendChild(this._editorContainer);
105-
this._layout();
106127
this._register(this._editorWidget.onKeyUp((e) => {
107128
if (e.keyCode === KeyCode.Escape) {
108129
this._contextViewService.hideContextView();
130+
} else if (e.keyCode === KeyCode.KeyD) {
131+
this._configurationService.updateValue(settingKey, false);
132+
} else if (e.keyCode === KeyCode.KeyH && provider.options.readMoreUrl) {
133+
const url: string = provider.options.readMoreUrl!;
134+
alert(AccessibilityHelpNLS.openingDocs);
135+
this._openerService.open(URI.parse(url));
109136
}
110137
e.stopPropagation();
111138
provider.onKeyDown?.(e);
112139
}));
113140
this._register(this._editorWidget.onDidBlurEditorText(() => this._contextViewService.hideContextView()));
114-
this._register(this._editorWidget.onDidContentSizeChange(() => this._layout()));
141+
this._register(this._editorWidget.onDidContentSizeChange(() => this._layout(provider)));
115142
this._editorWidget.updateOptions({ ariaLabel: provider.options.ariaLabel });
116143
this._editorWidget.focus();
117144
});
118145
return toDisposable(() => provider.onClose());
119146
}
120147

121-
private _layout(): void {
148+
private _layout(provider: IAccessibleContentProvider): void {
122149
const windowWidth = window.innerWidth;
123-
const windowHeight = window.innerHeight;
124-
125-
const width = windowWidth * .4;
126-
const height = Math.min(.4 * windowHeight, this._editorWidget.getContentHeight());
127-
this._editorWidget.layout({ width, height });
128-
const top = Math.round((windowHeight - height) / 2);
129-
this._editorContainer.style.top = `${top}px`;
150+
const width = provider.options.dimensions?.width || windowWidth * DIMENSION_DEFAULT.WIDTH;
151+
this._editorWidget.layout({ width, height: provider.options.dimensions?.height || this._editorWidget.getContentHeight() });
130152
const left = Math.round((windowWidth - width) / 2);
131153
this._editorContainer.style.left = `${left}px`;
132154
}

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ import { withNullAsUndefined } from 'vs/base/common/types';
1010
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
1111
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
1212
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
13-
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
13+
import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
1414
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
1515

1616

1717
const inputBox = localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.');
1818

19-
export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'chat' | 'inline'): string {
19+
export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string {
2020
const keybindingService = accessor.get(IKeybindingService);
2121
const content = [];
22-
if (type === 'chat') {
22+
if (type === 'panelChat') {
2323
content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.'));
2424
content.push(inputBox);
2525
content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.'));
@@ -50,10 +50,10 @@ function descriptionForCommand(commandId: string, msg: string, noKbMsg: string,
5050
return format(noKbMsg, commandId);
5151
}
5252

53-
export async function runAccessibilityHelpAction(accessor: ServicesAccessor, editor: ICodeEditor, type: 'chat' | 'inline'): Promise<void> {
53+
export async function runAccessibilityHelpAction(accessor: ServicesAccessor, editor: ICodeEditor, type: 'panelChat' | 'inlineChat'): Promise<void> {
5454
const widgetService = accessor.get(IChatWidgetService);
5555
const accessibleViewService = accessor.get(IAccessibleViewService);
56-
const inputEditor: ICodeEditor | undefined = type === 'chat' ? widgetService.lastFocusedWidget?.inputEditor : editor;
56+
const inputEditor: ICodeEditor | undefined = type === 'panelChat' ? widgetService.lastFocusedWidget?.inputEditor : editor;
5757
const editorUri = editor.getModel()?.uri;
5858

5959
if (!inputEditor || !editorUri) {
@@ -71,15 +71,15 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi
7171
id: type,
7272
provideContent: () => helpText,
7373
onClose: () => {
74-
if (type === 'chat' && cachedPosition) {
74+
if (type === 'panelChat' && cachedPosition) {
7575
inputEditor.setPosition(cachedPosition);
7676
inputEditor.focus();
77-
} else if (type === 'inline') {
77+
} else if (type === 'inlineChat') {
7878
InlineChatController.get(editor)?.focus();
7979
}
8080
provider.dispose();
8181
},
82-
options: { ariaLabel: type === 'chat' ? localize('chat-help-label', "Chat accessibility help") : localize('inline-chat-label', "Inline chat accessibility help") }
82+
options: { type: AccessibleViewType.HelpMenu, ariaLabel: type === 'panelChat' ? localize('chat-help-label', "Chat accessibility help") : localize('inline-chat-label', "Inline chat accessibility help") }
8383
});
8484
accessibleViewService.show(type);
8585
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ export function registerChatActions() {
102102
static ID: 'chatAccessibilityHelpContribution';
103103
constructor() {
104104
super();
105-
this._register(AccessibilityHelpAction.addImplementation(105, 'chat', async accessor => {
105+
this._register(AccessibilityHelpAction.addImplementation(105, 'panelChat', async accessor => {
106106
const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor();
107107
if (!codeEditor) {
108108
return;
109109
}
110-
runAccessibilityHelpAction(accessor, codeEditor, 'chat');
110+
runAccessibilityHelpAction(accessor, codeEditor, 'panelChat');
111111
}, CONTEXT_IN_CHAT_INPUT));
112112
}
113113
}

0 commit comments

Comments
 (0)