Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding accessibility help for verbose hover #212783

Merged
merged 33 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
36a5ff6
adding code in order to provide accessibility help for the hover
aiday-mar May 15, 2024
34513de
adding the description of the possible commands that can be used
aiday-mar May 15, 2024
04cb337
reusing the method
aiday-mar May 15, 2024
a1851bd
joining the content and changing the text in the help view
aiday-mar May 15, 2024
cf2cfe0
Merge branch 'main' into whispering-gorilla
aiday-mar May 15, 2024
9a358bf
polishing the code
aiday-mar May 15, 2024
f07cd63
removing the question mark
aiday-mar May 15, 2024
31ea768
changing the import
aiday-mar May 15, 2024
24062b1
removing the setting ID from imports
aiday-mar May 16, 2024
cb92438
adding code in order to update when the hover updates
aiday-mar May 16, 2024
fa6bc6c
adding methods to service
aiday-mar May 17, 2024
a0c67a5
adding code in order to dispose the accessible hover view
aiday-mar May 21, 2024
4b77920
fixing bug
aiday-mar May 21, 2024
42a7702
polishing the code
aiday-mar May 21, 2024
32120f1
checking that action not supported for the early return
aiday-mar May 21, 2024
676ce75
using disposable store instead
aiday-mar May 21, 2024
c037269
using the appropriate string
aiday-mar May 21, 2024
9c71082
polishing the code
aiday-mar May 22, 2024
052aa39
Merge branch 'main' into whispering-gorilla
aiday-mar May 23, 2024
1f450dd
using instead the type help and the resolved keybindings
aiday-mar May 23, 2024
1b60028
hiding also on the `onDidBlurEditorWidget` firing
aiday-mar May 23, 2024
12f0cf6
Revert "using instead the type help and the resolved keybindings"
meganrogge May 23, 2024
592fca0
use hover accessible view, provide custom help
meganrogge May 23, 2024
bcd5d95
Revert "Revert "using instead the type help and the resolved keybindi…
meganrogge May 23, 2024
992e464
add HoverAccessibilityHelp, BaseHoverAccessibleViewProvider
meganrogge May 23, 2024
82b9f49
polishing the code
aiday-mar May 24, 2024
039459e
Merge branch 'main' into whispering-gorilla
aiday-mar May 24, 2024
107b1ab
polishing the code
aiday-mar May 24, 2024
80870b3
provide content at a specific index from the hover accessibility help…
aiday-mar May 24, 2024
c2b2b78
introducing method _initializeOptions
aiday-mar May 24, 2024
56f21d5
using readonly where possible
aiday-mar May 24, 2024
b861568
using public everywhere
aiday-mar May 24, 2024
09f5d26
using a getter for the actions
aiday-mar May 24, 2024
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
27 changes: 24 additions & 3 deletions src/vs/editor/contrib/hover/browser/contentHoverController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ContentHoverWidget } from 'vs/editor/contrib/hover/browser/contentHover
import { ContentHoverComputer } from 'vs/editor/contrib/hover/browser/contentHoverComputer';
import { ContentHoverVisibleData, HoverResult } from 'vs/editor/contrib/hover/browser/contentHoverTypes';
import { EditorHoverStatusBar } from 'vs/editor/contrib/hover/browser/contentHoverStatusBar';
import { Emitter } from 'vs/base/common/event';

export class ContentHoverController extends Disposable implements IHoverWidget {

Expand All @@ -35,6 +36,9 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
private readonly _markdownHoverParticipant: MarkdownHoverParticipant | undefined;
private readonly _hoverOperation: HoverOperation<IHoverPart>;

private readonly _onContentsChanged = this._register(new Emitter<void>());
public readonly onContentsChanged = this._onContentsChanged.event;

constructor(
private readonly _editor: ICodeEditor,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
Expand Down Expand Up @@ -214,7 +218,7 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
fragment,
statusBar,
setColorPicker: (widget) => colorPicker = widget,
onContentsChanged: () => this._widget.onContentsChanged(),
onContentsChanged: () => this._doOnContentsChanged(),
setMinimumDimensions: (dimensions: dom.Dimension) => this._widget.setMinimumDimensions(dimensions),
hide: () => this.hide()
};
Expand Down Expand Up @@ -261,6 +265,11 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
}
}

private _doOnContentsChanged(): void {
this._onContentsChanged.fire();
this._widget.onContentsChanged();
}

private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({
description: 'content-hover-highlight',
className: 'hoverHighlight'
Expand Down Expand Up @@ -351,8 +360,20 @@ export class ContentHoverController extends Disposable implements IHoverWidget {
this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null);
}

public async updateFocusedMarkdownHoverVerbosityLevel(action: HoverVerbosityAction): Promise<void> {
this._markdownHoverParticipant?.updateFocusedMarkdownHoverPartVerbosityLevel(action);
public async updateMarkdownHoverVerbosityLevel(action: HoverVerbosityAction, index?: number, focus?: boolean): Promise<void> {
this._markdownHoverParticipant?.updateMarkdownHoverVerbosityLevel(action, index, focus);
}

public focusedMarkdownHoverIndex(): number {
return this._markdownHoverParticipant?.focusedMarkdownHoverIndex() ?? -1;
}

public markdownHoverContentAtIndex(index: number): string {
return this._markdownHoverParticipant?.markdownHoverContentAtIndex(index) ?? '';
}

public doesMarkdownHoverAtIndexSupportVerbosityAction(index: number, action: HoverVerbosityAction): boolean {
return this._markdownHoverParticipant?.doesMarkdownHoverAtIndexSupportVerbosityAction(index, action) ?? false;
}

public getWidgetContent(): string | undefined {
Expand Down
201 changes: 180 additions & 21 deletions src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,205 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { localize } from 'vs/nls';
import { format } from 'vs/base/common/strings';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController';
import { AccessibleViewType, AccessibleViewProviderId } from 'vs/platform/accessibility/browser/accessibleView';
import { AccessibleViewType, AccessibleViewProviderId, AdvancedContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from 'vs/platform/accessibility/browser/accessibleView';
import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { HoverVerbosityAction } from 'vs/editor/common/languages';
import { DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, DECREASE_HOVER_VERBOSITY_ACTION_ID, DECREASE_HOVER_VERBOSITY_ACTION_LABEL, INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_LABEL } from 'vs/editor/contrib/hover/browser/hoverActionIds';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Action, IAction } from 'vs/base/common/actions';
import { ThemeIcon } from 'vs/base/common/themables';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';

namespace HoverAccessibilityHelpNLS {
export const intro = localize('intro', "The hover widget is focused. Press the Tab key to cycle through the hover parts.");
export const increaseVerbosity = localize('increaseVerbosity', "- The focused hover part verbosity level can be increased ({0}).");
export const increaseVerbosityNoKb = localize('increaseVerbosityNoKb', "- The focused hover part verbosity level can be increased, which is currently not triggerable via keybinding.");
export const decreaseVerbosity = localize('decreaseVerbosity', "- The focused hover part verbosity level can be decreased ({0}).");
export const decreaseVerbosityNoKb = localize('decreaseVerbosityNoKb', "- The focused hover part verbosity level can be decreased, which is currently not triggerable via keybinding.");
export const hoverContent = localize('contentHover', "The last focused hover content is the following.");
}

export class HoverAccessibleView implements IAccessibleViewImplentation {

readonly type = AccessibleViewType.View;
readonly priority = 95;
readonly name = 'hover';
readonly when = EditorContextKeys.hoverFocused;
getProvider(accessor: ServicesAccessor) {

private _provider: HoverAccessibilityHelpProvider | undefined;

getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined {
const codeEditorService = accessor.get(ICodeEditorService);
const editor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
const editorHoverContent = editor ? HoverController.get(editor)?.getWidgetContent() ?? undefined : undefined;
if (!editor || !editorHoverContent) {
return;
const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();
if (!codeEditor) {
return undefined;
}
return {
id: AccessibleViewProviderId.Hover,
verbositySettingKey: 'accessibility.verbosity.hover',
provideContent() { return editorHoverContent; },
onClose() {
HoverController.get(editor)?.focus();
},
options: {
language: editor?.getModel()?.getLanguageId() ?? 'typescript',
type: AccessibleViewType.View
}
this._provider = accessor.get(IInstantiationService).createInstance(HoverAccessibilityHelpProvider, codeEditor);
return this._provider;
}

dispose(): void {
this._provider?.dispose();
}
}

export class HoverAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider {

public readonly options: IAccessibleViewOptions;
public readonly id = AccessibleViewProviderId.Hover;
public readonly verbositySettingKey = 'accessibility.verbosity.hover';
public readonly actions: IAction[] = [];

private readonly _hoverController: HoverController | null = null;
private _onHoverContentsChanged: IDisposable | undefined;
private _markdownHoverFocusedIndex: number = -1;

private _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());
public onDidChangeContent: Event<void> = this._onDidChangeContent.event;

constructor(
private readonly _editor: ICodeEditor,
@IKeybindingService private readonly _keybindingService: IKeybindingService
) {
super();
this.options = {
language: this._editor.getModel()?.getLanguageId(),
type: AccessibleViewType.View
};
this._hoverController = HoverController.get(this._editor);
this._initializeActions();
}

public provideContent(): string {
const content: string[] = [HoverAccessibilityHelpNLS.intro];
if (!this._hoverController) {
return content.join('\n');
}
content.push(...this._descriptionsOfVerbosityActions(this._hoverController));
content.push(...this._descriptionOfFocusedMarkdownHover(this._hoverController));
return content.join('\n');
}

public onOpen(): void {
if (!this._hoverController) {
return;
}
this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = true;
this._markdownHoverFocusedIndex = this._hoverController.focusedMarkdownHoverIndex();
this._onHoverContentsChanged = this._register(this._hoverController.onHoverContentsChanged(() => {
this._onDidChangeContent.fire();
}));
}

public onClose(): void {
if (!this._hoverController) {
return;
}
this._markdownHoverFocusedIndex = -1;
this._hoverController.focus();
this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = false;
this._onHoverContentsChanged?.dispose();
}

private _initializeActions(): void {
this.actions.push(this._getActionFor(HoverVerbosityAction.Increase));
this.actions.push(this._getActionFor(HoverVerbosityAction.Decrease));
}

private _getActionFor(action: HoverVerbosityAction): IAction {
let actionId: string;
let accessibleActionId: string;
let actionLabel: string;
let actionCodicon: ThemeIcon;
switch (action) {
case HoverVerbosityAction.Increase:
actionId = INCREASE_HOVER_VERBOSITY_ACTION_ID;
accessibleActionId = INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;
actionLabel = INCREASE_HOVER_VERBOSITY_ACTION_LABEL;
actionCodicon = Codicon.add;
break;
case HoverVerbosityAction.Decrease:
actionId = DECREASE_HOVER_VERBOSITY_ACTION_ID;
accessibleActionId = DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;
actionLabel = DECREASE_HOVER_VERBOSITY_ACTION_LABEL;
actionCodicon = Codicon.remove;
break;
}
return new Action(accessibleActionId, actionLabel, ThemeIcon.asClassName(actionCodicon), true, () => {
this._editor.getAction(actionId)?.run({ index: this._markdownHoverFocusedIndex, focus: false });
});
}

private _descriptionsOfVerbosityActions(hoverController: HoverController): string[] {
const content: string[] = [];
const descriptionForIncreaseAction = this._descriptionOfVerbosityAction(hoverController, HoverVerbosityAction.Increase);
if (descriptionForIncreaseAction !== undefined) {
content.push(descriptionForIncreaseAction);
}
const descriptionForDecreaseAction = this._descriptionOfVerbosityAction(hoverController, HoverVerbosityAction.Decrease);
if (descriptionForDecreaseAction !== undefined) {
content.push(descriptionForDecreaseAction);
}
return content;
}

private _descriptionOfVerbosityAction(hoverController: HoverController, action: HoverVerbosityAction): string | undefined {
const isActionSupported = hoverController.doesMarkdownHoverAtIndexSupportVerbosityAction(this._markdownHoverFocusedIndex, action);
if (!isActionSupported) {
return;
}
let actionId: string;
let descriptionWithKb: string;
let descriptionWithoutKb: string;
switch (action) {
case HoverVerbosityAction.Increase:
aiday-mar marked this conversation as resolved.
Show resolved Hide resolved
actionId = INCREASE_HOVER_VERBOSITY_ACTION_ID;
descriptionWithKb = HoverAccessibilityHelpNLS.increaseVerbosity;
descriptionWithoutKb = HoverAccessibilityHelpNLS.increaseVerbosityNoKb;
break;
case HoverVerbosityAction.Decrease:
actionId = DECREASE_HOVER_VERBOSITY_ACTION_ID;
descriptionWithKb = HoverAccessibilityHelpNLS.decreaseVerbosity;
descriptionWithoutKb = HoverAccessibilityHelpNLS.decreaseVerbosityNoKb;
break;
}
return this._descriptionForCommand(actionId, descriptionWithKb, descriptionWithoutKb);
}

private _descriptionForCommand(commandId: string, msg: string, noKbMsg: string): string {
const kb = this._keybindingService.lookupKeybinding(commandId);
return kb ? format(msg, kb.getAriaLabel()) : format(noKbMsg, commandId);
}

private _descriptionOfFocusedMarkdownHover(hoverController: HoverController): string[] {
const content: string[] = [];
const hoverContent = hoverController.markdownHoverContentAtIndex(this._markdownHoverFocusedIndex);
if (hoverContent) {
content.push('\n' + HoverAccessibilityHelpNLS.hoverContent);
content.push('\n' + hoverContent);
}
return content;
}
}

export class ExtHoverAccessibleView implements IAccessibleViewImplentation {

readonly type = AccessibleViewType.View;
readonly priority = 90;
readonly name = 'extension-hover';
getProvider(accessor: ServicesAccessor) {

getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined {
const contextViewService = accessor.get(IContextViewService);
const contextViewElement = contextViewService.getContextViewElement();
const extensionHoverContent = contextViewElement?.textContent ?? undefined;
Expand All @@ -63,4 +220,6 @@ export class ExtHoverAccessibleView implements IAccessibleViewImplentation {
options: { language: 'typescript', type: AccessibleViewType.View }
};
}

dispose() { }
}
5 changes: 5 additions & 0 deletions src/vs/editor/contrib/hover/browser/hoverActionIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';

export const SHOW_OR_FOCUS_HOVER_ACTION_ID = 'editor.action.showHover';
export const SHOW_DEFINITION_PREVIEW_HOVER_ACTION_ID = 'editor.action.showDefinitionPreviewHover';
Expand All @@ -14,4 +15,8 @@ export const PAGE_DOWN_HOVER_ACTION_ID = 'editor.action.pageDownHover';
export const GO_TO_TOP_HOVER_ACTION_ID = 'editor.action.goToTopHover';
export const GO_TO_BOTTOM_HOVER_ACTION_ID = 'editor.action.goToBottomHover';
export const INCREASE_HOVER_VERBOSITY_ACTION_ID = 'editor.action.increaseHoverVerbosityLevel';
export const INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID = 'editor.action.increaseHoverVerbosityLevelFromAccessibleView';
export const INCREASE_HOVER_VERBOSITY_ACTION_LABEL = nls.localize({ key: 'increaseHoverVerbosityLevel', comment: ['Label for action that will increase the hover verbosity level.'] }, "Increase Hover Verbosity Level");
export const DECREASE_HOVER_VERBOSITY_ACTION_ID = 'editor.action.decreaseHoverVerbosityLevel';
export const DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID = 'editor.action.decreaseHoverVerbosityLevelFromAccessibleView';
export const DECREASE_HOVER_VERBOSITY_ACTION_LABEL = nls.localize({ key: 'decreaseHoverVerbosityLevel', comment: ['Label for action that will decrease the hover verbosity level.'] }, "Decrease Hover Verbosity Level");
24 changes: 9 additions & 15 deletions src/vs/editor/contrib/hover/browser/hoverActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DECREASE_HOVER_VERBOSITY_ACTION_ID, GO_TO_BOTTOM_HOVER_ACTION_ID, GO_TO_TOP_HOVER_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, PAGE_DOWN_HOVER_ACTION_ID, PAGE_UP_HOVER_ACTION_ID, SCROLL_DOWN_HOVER_ACTION_ID, SCROLL_LEFT_HOVER_ACTION_ID, SCROLL_RIGHT_HOVER_ACTION_ID, SCROLL_UP_HOVER_ACTION_ID, SHOW_DEFINITION_PREVIEW_HOVER_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from 'vs/editor/contrib/hover/browser/hoverActionIds';
import { DECREASE_HOVER_VERBOSITY_ACTION_ID, DECREASE_HOVER_VERBOSITY_ACTION_LABEL, GO_TO_BOTTOM_HOVER_ACTION_ID, GO_TO_TOP_HOVER_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_LABEL, PAGE_DOWN_HOVER_ACTION_ID, PAGE_UP_HOVER_ACTION_ID, SCROLL_DOWN_HOVER_ACTION_ID, SCROLL_LEFT_HOVER_ACTION_ID, SCROLL_RIGHT_HOVER_ACTION_ID, SCROLL_UP_HOVER_ACTION_ID, SHOW_DEFINITION_PREVIEW_HOVER_ACTION_ID, SHOW_OR_FOCUS_HOVER_ACTION_ID } from 'vs/editor/contrib/hover/browser/hoverActionIds';
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
Expand Down Expand Up @@ -425,17 +425,14 @@ export class IncreaseHoverVerbosityLevel extends EditorAction {
constructor() {
super({
id: INCREASE_HOVER_VERBOSITY_ACTION_ID,
label: nls.localize({
key: 'increaseHoverVerbosityLevel',
comment: ['Label for action that will increase the hover verbosity level.']
}, "Increase Hover Verbosity Level"),
label: INCREASE_HOVER_VERBOSITY_ACTION_LABEL,
alias: 'Increase Hover Verbosity Level',
precondition: EditorContextKeys.hoverFocused
precondition: EditorContextKeys.hoverVisible
});
}

public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
HoverController.get(editor)?.updateFocusedMarkdownHoverVerbosityLevel(HoverVerbosityAction.Increase);
public run(accessor: ServicesAccessor, editor: ICodeEditor, args?: { index: number; focus: boolean }): void {
HoverController.get(editor)?.updateMarkdownHoverVerbosityLevel(HoverVerbosityAction.Increase, args?.index, args?.focus);
}
}

Expand All @@ -444,16 +441,13 @@ export class DecreaseHoverVerbosityLevel extends EditorAction {
constructor() {
super({
id: DECREASE_HOVER_VERBOSITY_ACTION_ID,
label: nls.localize({
key: 'decreaseHoverVerbosityLevel',
comment: ['Label for action that will decrease the hover verbosity level.']
}, "Decrease Hover Verbosity Level"),
label: DECREASE_HOVER_VERBOSITY_ACTION_LABEL,
alias: 'Decrease Hover Verbosity Level',
precondition: EditorContextKeys.hoverFocused
precondition: EditorContextKeys.hoverVisible
});
}

public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
HoverController.get(editor)?.updateFocusedMarkdownHoverVerbosityLevel(HoverVerbosityAction.Decrease);
public run(accessor: ServicesAccessor, editor: ICodeEditor, args?: { index: number; focus: boolean }): void {
HoverController.get(editor)?.updateMarkdownHoverVerbosityLevel(HoverVerbosityAction.Decrease, args?.index, args?.focus);
}
}
Loading
Loading