Path: blob/main/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts
4779 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import { localize } from '../../../../nls.js';5import { EditorContextKeys } from '../../../common/editorContextKeys.js';6import { ContentHoverController } from './contentHoverController.js';7import { AccessibleViewType, AccessibleViewProviderId, AccessibleContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from '../../../../platform/accessibility/browser/accessibleView.js';8import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';9import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';10import { IHoverService } from '../../../../platform/hover/browser/hover.js';11import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';12import { HoverVerbosityAction } from '../../../common/languages.js';13import { DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, DECREASE_HOVER_VERBOSITY_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID, INCREASE_HOVER_VERBOSITY_ACTION_ID } from './hoverActionIds.js';14import { ICodeEditor } from '../../../browser/editorBrowser.js';15import { ICodeEditorService } from '../../../browser/services/codeEditorService.js';16import { Action, IAction } from '../../../../base/common/actions.js';17import { ThemeIcon } from '../../../../base/common/themables.js';18import { Codicon } from '../../../../base/common/codicons.js';19import { Emitter, Event } from '../../../../base/common/event.js';20import { Disposable } from '../../../../base/common/lifecycle.js';21import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';22import { labelForHoverVerbosityAction } from './markdownHoverParticipant.js';2324namespace HoverAccessibilityHelpNLS {25export const increaseVerbosity = localize('increaseVerbosity', '- The focused hover part verbosity level can be increased with the Increase Hover Verbosity command.', `<keybinding:${INCREASE_HOVER_VERBOSITY_ACTION_ID}>`);26export const decreaseVerbosity = localize('decreaseVerbosity', '- The focused hover part verbosity level can be decreased with the Decrease Hover Verbosity command.', `<keybinding:${DECREASE_HOVER_VERBOSITY_ACTION_ID}>`);27}2829export class HoverAccessibleView implements IAccessibleViewImplementation {3031public readonly type = AccessibleViewType.View;32public readonly priority = 95;33public readonly name = 'hover';34public readonly when = EditorContextKeys.hoverFocused;3536getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {37const codeEditorService = accessor.get(ICodeEditorService);38const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();39if (!codeEditor) {40throw new Error('No active or focused code editor');41}42const hoverController = ContentHoverController.get(codeEditor);43if (!hoverController) {44return;45}46const keybindingService = accessor.get(IKeybindingService);47return accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController);48}49}5051export class HoverAccessibilityHelp implements IAccessibleViewImplementation {5253public readonly priority = 100;54public readonly name = 'hover';55public readonly type = AccessibleViewType.Help;56public readonly when = EditorContextKeys.hoverVisible;5758getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {59const codeEditorService = accessor.get(ICodeEditorService);60const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor();61if (!codeEditor) {62throw new Error('No active or focused code editor');63}64const hoverController = ContentHoverController.get(codeEditor);65if (!hoverController) {66return;67}68return accessor.get(IInstantiationService).createInstance(HoverAccessibilityHelpProvider, hoverController);69}70}7172abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider {7374abstract provideContent(): string;75abstract options: IAccessibleViewOptions;7677public readonly id = AccessibleViewProviderId.Hover;78public readonly verbositySettingKey = 'accessibility.verbosity.hover';7980private readonly _onDidChangeContent: Emitter<void> = this._register(new Emitter<void>());81public readonly onDidChangeContent: Event<void> = this._onDidChangeContent.event;8283protected _focusedHoverPartIndex: number = -1;8485constructor(protected readonly _hoverController: ContentHoverController) {86super();87}8889public onOpen(): void {90if (!this._hoverController) {91return;92}93this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = true;94this._focusedHoverPartIndex = this._hoverController.focusedHoverPartIndex();95this._register(this._hoverController.onHoverContentsChanged(() => {96this._onDidChangeContent.fire();97}));98}99100public onClose(): void {101if (!this._hoverController) {102return;103}104if (this._focusedHoverPartIndex === -1) {105this._hoverController.focus();106} else {107this._hoverController.focusHoverPartWithIndex(this._focusedHoverPartIndex);108}109this._focusedHoverPartIndex = -1;110this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = false;111}112113provideContentAtIndex(focusedHoverIndex: number, includeVerbosityActions: boolean): string {114if (focusedHoverIndex !== -1) {115const accessibleContent = this._hoverController.getAccessibleWidgetContentAtIndex(focusedHoverIndex);116if (accessibleContent === undefined) {117return '';118}119const contents: string[] = [];120if (includeVerbosityActions) {121contents.push(...this._descriptionsOfVerbosityActionsForIndex(focusedHoverIndex));122}123contents.push(accessibleContent);124return contents.join('\n');125} else {126const accessibleContent = this._hoverController.getAccessibleWidgetContent();127if (accessibleContent === undefined) {128return '';129}130const contents: string[] = [];131contents.push(accessibleContent);132return contents.join('\n');133}134}135136private _descriptionsOfVerbosityActionsForIndex(index: number): string[] {137const content: string[] = [];138const descriptionForIncreaseAction = this._descriptionOfVerbosityActionForIndex(HoverVerbosityAction.Increase, index);139if (descriptionForIncreaseAction !== undefined) {140content.push(descriptionForIncreaseAction);141}142const descriptionForDecreaseAction = this._descriptionOfVerbosityActionForIndex(HoverVerbosityAction.Decrease, index);143if (descriptionForDecreaseAction !== undefined) {144content.push(descriptionForDecreaseAction);145}146return content;147}148149private _descriptionOfVerbosityActionForIndex(action: HoverVerbosityAction, index: number): string | undefined {150const isActionSupported = this._hoverController.doesHoverAtIndexSupportVerbosityAction(index, action);151if (!isActionSupported) {152return;153}154switch (action) {155case HoverVerbosityAction.Increase:156return HoverAccessibilityHelpNLS.increaseVerbosity;157case HoverVerbosityAction.Decrease:158return HoverAccessibilityHelpNLS.decreaseVerbosity;159}160}161}162163export class HoverAccessibilityHelpProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider {164165public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help };166167constructor(hoverController: ContentHoverController) {168super(hoverController);169}170171provideContent(): string {172return this.provideContentAtIndex(this._focusedHoverPartIndex, true);173}174}175176export class HoverAccessibleViewProvider extends BaseHoverAccessibleViewProvider implements IAccessibleViewContentProvider {177178public readonly options: IAccessibleViewOptions = { type: AccessibleViewType.View };179180constructor(181private readonly _keybindingService: IKeybindingService,182private readonly _editor: ICodeEditor,183hoverController: ContentHoverController,184) {185super(hoverController);186this._initializeOptions(this._editor, hoverController);187}188189public provideContent(): string {190return this.provideContentAtIndex(this._focusedHoverPartIndex, false);191}192193public get actions(): IAction[] {194const actions: IAction[] = [];195actions.push(this._getActionFor(this._editor, HoverVerbosityAction.Increase));196actions.push(this._getActionFor(this._editor, HoverVerbosityAction.Decrease));197return actions;198}199200private _getActionFor(editor: ICodeEditor, action: HoverVerbosityAction): IAction {201let actionId: string;202let accessibleActionId: string;203let actionCodicon: ThemeIcon;204switch (action) {205case HoverVerbosityAction.Increase:206actionId = INCREASE_HOVER_VERBOSITY_ACTION_ID;207accessibleActionId = INCREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;208actionCodicon = Codicon.add;209break;210case HoverVerbosityAction.Decrease:211actionId = DECREASE_HOVER_VERBOSITY_ACTION_ID;212accessibleActionId = DECREASE_HOVER_VERBOSITY_ACCESSIBLE_ACTION_ID;213actionCodicon = Codicon.remove;214break;215}216const actionLabel = labelForHoverVerbosityAction(this._keybindingService, action);217const actionEnabled = this._hoverController.doesHoverAtIndexSupportVerbosityAction(this._focusedHoverPartIndex, action);218return new Action(accessibleActionId, actionLabel, ThemeIcon.asClassName(actionCodicon), actionEnabled, () => {219editor.getAction(actionId)?.run({ index: this._focusedHoverPartIndex, focus: false });220});221}222223private _initializeOptions(editor: ICodeEditor, hoverController: ContentHoverController): void {224const helpProvider = this._register(new HoverAccessibilityHelpProvider(hoverController));225this.options.language = editor.getModel()?.getLanguageId();226this.options.customHelp = () => { return helpProvider.provideContentAtIndex(this._focusedHoverPartIndex, true); };227}228}229230export class ExtHoverAccessibleView implements IAccessibleViewImplementation {231public readonly type = AccessibleViewType.View;232public readonly priority = 90;233public readonly name = 'extension-hover';234235getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined {236const contextViewService = accessor.get(IContextViewService);237const contextViewElement = contextViewService.getContextViewElement();238const extensionHoverContent = contextViewElement?.textContent ?? undefined;239const hoverService = accessor.get(IHoverService);240241if (contextViewElement.classList.contains('accessible-view-container') || !extensionHoverContent) {242// The accessible view, itself, uses the context view service to display the text. We don't want to read that.243return;244}245return new AccessibleContentProvider(246AccessibleViewProviderId.Hover,247{ language: 'typescript', type: AccessibleViewType.View },248() => { return extensionHoverContent; },249() => {250hoverService.showAndFocusLastHover();251},252'accessibility.verbosity.hover',253);254}255}256257258