add accessibility help extension contributions pt (#210116)

This commit is contained in:
Megan Rogge 2024-04-18 09:13:56 -07:00 committed by GitHub
parent ad2c8accac
commit 48bbfa7161
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 258 additions and 78 deletions

View file

@ -48,3 +48,4 @@ export function isAccessibilityInformation(obj: any): obj is IAccessibilityInfor
&& (typeof obj.role === 'undefined' || typeof obj.role === 'string');
}
export const ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX = 'ACCESSIBLE_VIEW_SHOWN_';

View file

@ -106,7 +106,7 @@ interface IUserFriendlyViewDescriptor {
remoteName?: string | string[];
virtualWorkspace?: string;
accessibilityHelpContent: string;
accessibilityHelpContent?: string;
}
enum InitialVisibility {
@ -173,7 +173,7 @@ const viewDescriptor: IJSONSchema = {
},
accessibilityHelpContent: {
type: 'string',
description: localize('vscode.extension.contributes.view.accessibilityHelpContent', "When the accessibility help dialog is invoked in this view, this content will be presented to the user as a markdown string.")
markdownDescription: localize('vscode.extension.contributes.view.accessibilityHelpContent', "When the accessibility help dialog is invoked in this view, this content will be presented to the user as a markdown string. Keybindings will be resolved when provided in the format of (keybinding:commandId). If there is no keybinding, that will be indicated with a link to configure one.")
}
}
};

View file

@ -75,6 +75,7 @@ import type { IUpdatableHoverTooltipMarkdownString } from 'vs/base/browser/ui/ho
import { parseLinkedText } from 'vs/base/common/linkedText';
import { Button } from 'vs/base/browser/ui/button/button';
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
export class TreeViewPane extends ViewPane {
@ -94,9 +95,10 @@ export class TreeViewPane extends ViewPane {
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService notificationService: INotificationService,
@IHoverService hoverService: IHoverService
@IHoverService hoverService: IHoverService,
@IAccessibleViewInformationService accessibleViewService: IAccessibleViewInformationService,
) {
super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle, donotForwardArgs: false }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService);
super({ ...(options as IViewPaneOptions), titleMenuId: MenuId.ViewTitle, donotForwardArgs: false }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService, accessibleViewService);
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
this.treeView = treeView;
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));

View file

@ -52,6 +52,7 @@ import type { IUpdatableHover } from 'vs/base/browser/ui/hover/hover';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { PANEL_BACKGROUND, PANEL_STICKY_SCROLL_BACKGROUND, PANEL_STICKY_SCROLL_BORDER, PANEL_STICKY_SCROLL_SHADOW, SIDE_BAR_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BACKGROUND, SIDE_BAR_STICKY_SCROLL_BORDER, SIDE_BAR_STICKY_SCROLL_SHADOW } from 'vs/workbench/common/theme';
import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
export enum ViewPaneShowActions {
/** Show the actions when the view is hovered. This is the default behavior. */
@ -374,7 +375,8 @@ export abstract class ViewPane extends Pane implements IView {
@IOpenerService protected openerService: IOpenerService,
@IThemeService protected themeService: IThemeService,
@ITelemetryService protected telemetryService: ITelemetryService,
@IHoverService protected readonly hoverService: IHoverService
@IHoverService protected readonly hoverService: IHoverService,
protected readonly accessibleViewService?: IAccessibleViewInformationService
) {
super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } });
@ -545,7 +547,17 @@ export abstract class ViewPane extends Pane implements IView {
}
this.iconContainerHover = this._register(this.hoverService.setupUpdatableHover(getDefaultHoverDelegate('mouse'), this.iconContainer, calculatedTitle));
this.iconContainer.setAttribute('aria-label', calculatedTitle);
this.iconContainer.setAttribute('aria-label', this._getAriaLabel(calculatedTitle));
}
private _getAriaLabel(title: string): string {
const viewHasAccessibilityHelpContent = this.viewDescriptorService.getViewDescriptorById(this.id)?.accessibilityHelpContent;
const accessibleViewHasShownForView = this.accessibleViewService?.hasShownAccessibleView(this.id);
if (!viewHasAccessibilityHelpContent || accessibleViewHasShownForView) {
return title;
}
return nls.localize('viewAccessibilityHelp', 'Use Alt+F1 for accessibility help {0}', title);
}
protected updateTitle(title: string): void {
@ -557,7 +569,7 @@ export abstract class ViewPane extends Pane implements IView {
if (this.iconContainer) {
this.iconContainerHover?.update(calculatedTitle);
this.iconContainer.setAttribute('aria-label', calculatedTitle);
this.iconContainer.setAttribute('aria-label', this._getAriaLabel(calculatedTitle));
}
this._title = title;
@ -730,8 +742,9 @@ export abstract class FilterViewPane extends ViewPane {
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IHoverService hoverService: IHoverService,
accessibleViewService?: IAccessibleViewInformationService
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService);
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, hoverService, accessibleViewService);
this.filterWidget = this._register(instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])).createInstance(FilterWidget, options.filterOptions));
}

View file

@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
import { Registry } from 'vs/platform/registry/common/platform';
import { IAccessibleViewService, AccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { UnfocusedViewDimmingContribution } from 'vs/workbench/contrib/accessibility/browser/unfocusedViewDimmingContribution';
import { CommentAccessibleViewContribution, HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions';
import { ExtensionAccessibilityHelpDialogContribution, CommentAccessibleViewContribution, HoverAccessibleViewContribution, InlineCompletionsAccessibleViewContribution, NotificationAccessibleViewContribution } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions';
import { AccessibilityStatus } from 'vs/workbench/contrib/accessibility/browser/accessibilityStatus';
import { EditorAccessibilityHelpContribution } from 'vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp';
import { SaveAccessibilitySignalContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/saveAccessibilitySignal';
@ -18,10 +18,12 @@ import { CommentsAccessibilityHelpContribution } from 'vs/workbench/contrib/comm
import { DiffEditorActiveAnnouncementContribution } from 'vs/workbench/contrib/accessibilitySignals/browser/openDiffEditorAnnouncement';
import { SpeechAccessibilitySignalContribution } from 'vs/workbench/contrib/speech/browser/speechAccessibilitySignal';
import { registerAudioCueConfiguration } from 'vs/workbench/contrib/accessibility/browser/audioCueConfiguration';
import { AccessibleViewInformationService, IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
registerAccessibilityConfiguration();
registerAudioCueConfiguration();
registerSingleton(IAccessibleViewService, AccessibleViewService, InstantiationType.Delayed);
registerSingleton(IAccessibleViewInformationService, AccessibleViewInformationService, InstantiationType.Delayed);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(EditorAccessibilityHelpContribution, LifecyclePhase.Eventually);
@ -34,6 +36,7 @@ workbenchRegistry.registerWorkbenchContribution(CommentAccessibleViewContributio
workbenchRegistry.registerWorkbenchContribution(InlineCompletionsAccessibleViewContribution, LifecyclePhase.Eventually);
registerWorkbenchContribution2(AccessibilityStatus.ID, AccessibilityStatus, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(ExtensionAccessibilityHelpDialogContribution.ID, ExtensionAccessibilityHelpDialogContribution, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(SaveAccessibilitySignalContribution.ID, SaveAccessibilitySignalContribution, WorkbenchPhase.AfterRestored);
registerWorkbenchContribution2(SpeechAccessibilitySignalContribution.ID, SpeechAccessibilitySignalContribution, WorkbenchPhase.AfterRestored);
registerWorkbenchContribution2(DiffEditorActiveAnnouncementContribution.ID, DiffEditorActiveAnnouncementContribution, WorkbenchPhase.AfterRestored);

View file

@ -25,7 +25,7 @@ import { IModelService } from 'vs/editor/common/services/model';
import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings';
import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController';
import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
@ -40,6 +40,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { AccessibilityVerbositySettingId, AccessibilityWorkbenchSettingId, AccessibleViewProviderId, accessibilityHelpIsShown, accessibleViewContainsCodeBlocks, accessibleViewCurrentProviderId, accessibleViewGoToSymbolSupported, accessibleViewInCodeBlock, accessibleViewIsShown, accessibleViewOnLastLine, accessibleViewSupportsNavigation, accessibleViewVerbosityEnabled } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands';
import { IChatCodeBlockContextProviderService } from 'vs/workbench/contrib/chat/browser/chat';
@ -50,20 +51,56 @@ const enum DIMENSIONS {
MAX_WIDTH = 600
}
export interface IAccessibleContentProvider {
type ContentProvider = AdvancedContentProvider | ExtensionContentProvider;
export class AdvancedContentProvider implements IAccessibleViewContentProvider {
constructor(
public id: AccessibleViewProviderId,
public options: IAccessibleViewOptions,
public provideContent: () => string,
public onClose: () => void,
public verbositySettingKey: AccessibilityVerbositySettingId,
public actions?: IAction[],
public next?: () => void,
public previous?: () => void,
public onKeyDown?: (e: IKeyboardEvent) => void,
public getSymbols?: () => IAccessibleViewSymbol[],
public onDidRequestClearLastProvider?: Event<AccessibleViewProviderId>,
) { }
}
export class ExtensionContentProvider implements IBasicContentProvider {
constructor(
public readonly id: string,
public options: IAccessibleViewOptions,
public provideContent: () => string,
public onClose: () => void,
public next?: () => void,
public previous?: () => void,
public actions?: IAction[],
) { }
}
export interface IBasicContentProvider {
id: string;
options: IAccessibleViewOptions;
onClose(): void;
provideContent(): string;
actions?: IAction[];
previous?(): void;
next?(): void;
}
export interface IAccessibleViewContentProvider extends IBasicContentProvider {
id: AccessibleViewProviderId;
verbositySettingKey: AccessibilityVerbositySettingId;
options: IAccessibleViewOptions;
/**
* Note that a Codicon class should be provided for each action.
* If not, a default will be used.
*/
actions?: IAction[];
provideContent(): string;
onClose(): void;
onKeyDown?(e: IKeyboardEvent): void;
previous?(): void;
next?(): void;
/**
* When the language is markdown, this is provided by default.
*/
@ -78,7 +115,7 @@ export const IAccessibleViewService = createDecorator<IAccessibleViewService>('a
export interface IAccessibleViewService {
readonly _serviceBrand: undefined;
show(provider: IAccessibleContentProvider, position?: Position): void;
show(provider: ContentProvider, position?: Position): void;
showLastProvider(id: AccessibleViewProviderId): void;
showAccessibleViewHelp(): void;
next(): void;
@ -157,10 +194,10 @@ export class AccessibleView extends Disposable {
private _title: HTMLElement;
private readonly _toolbar: WorkbenchToolBar;
private _currentProvider: IAccessibleContentProvider | undefined;
private _currentProvider: ContentProvider | undefined;
private _currentContent: string | undefined;
private _lastProvider: IAccessibleContentProvider | undefined;
private _lastProvider: ContentProvider | undefined;
constructor(
@IOpenerService private readonly _openerService: IOpenerService,
@ -174,7 +211,8 @@ export class AccessibleView extends Disposable {
@ILayoutService private readonly _layoutService: ILayoutService,
@IMenuService private readonly _menuService: IMenuService,
@ICommandService private readonly _commandService: ICommandService,
@IChatCodeBlockContextProviderService private readonly _codeBlockContextProviderService: IChatCodeBlockContextProviderService
@IChatCodeBlockContextProviderService private readonly _codeBlockContextProviderService: IChatCodeBlockContextProviderService,
@IStorageService private readonly _storageService: IStorageService
) {
super();
@ -231,7 +269,7 @@ export class AccessibleView extends Disposable {
}
}));
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (this._currentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) {
if (this._currentProvider instanceof AdvancedContentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) {
if (this._accessiblityHelpIsShown.get()) {
this.show(this._currentProvider);
}
@ -316,7 +354,7 @@ export class AccessibleView extends Disposable {
this.show(this._lastProvider);
}
show(provider?: IAccessibleContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean, position?: Position): void {
show(provider?: ContentProvider, symbol?: IAccessibleViewSymbol, showAccessibleViewHelp?: boolean, position?: Position): void {
provider = provider ?? this._currentProvider;
if (!provider) {
return;
@ -348,8 +386,8 @@ export class AccessibleView extends Disposable {
if (symbol && this._currentProvider) {
this.showSymbol(this._currentProvider, symbol);
}
if (provider.onDidRequestClearLastProvider) {
this._register(provider.onDidRequestClearLastProvider((id) => {
if (provider instanceof AdvancedContentProvider && provider.onDidRequestClearLastProvider) {
this._register(provider.onDidRequestClearLastProvider((id: string) => {
if (this._lastProvider?.options.id === id) {
this._lastProvider = undefined;
}
@ -362,20 +400,24 @@ export class AccessibleView extends Disposable {
if (provider.id === AccessibleViewProviderId.Chat) {
this._register(this._codeBlockContextProviderService.registerProvider({ getCodeBlockContext: () => this.getCodeBlockContext() }, 'accessibleView'));
}
if (provider instanceof ExtensionContentProvider) {
this._storageService.store(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${provider.id}`, true, StorageScope.APPLICATION, StorageTarget.USER);
}
}
previous(): void {
if (!this._currentProvider) {
return;
}
this._currentProvider.previous?.();
this._currentProvider?.previous?.();
}
next(): void {
this._currentProvider?.next?.();
}
private _verbosityEnabled(): boolean {
if (!this._currentProvider) {
return;
return false;
}
this._currentProvider.next?.();
return this._currentProvider instanceof AdvancedContentProvider ? this._configurationService.getValue(this._currentProvider.verbositySettingKey) === true : this._storageService.getBoolean(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${this._currentProvider.id}`, StorageScope.APPLICATION, false);
}
goToSymbol(): void {
@ -415,14 +457,15 @@ export class AccessibleView extends Disposable {
}
getSymbols(): IAccessibleViewSymbol[] | undefined {
if (!this._currentProvider || !this._currentContent) {
const provider = this._currentProvider instanceof AdvancedContentProvider ? this._currentProvider : undefined;
if (!this._currentContent || !provider) {
return;
}
const symbols: IAccessibleViewSymbol[] = this._currentProvider.getSymbols?.() || [];
const symbols: IAccessibleViewSymbol[] = provider.getSymbols?.() || [];
if (symbols?.length) {
return symbols;
}
if (this._currentProvider.options.language && this._currentProvider.options.language !== 'markdown') {
if (provider.options.language && provider.options.language !== 'markdown') {
// Symbols haven't been provided and we cannot parse this language
return;
}
@ -463,7 +506,7 @@ export class AccessibleView extends Disposable {
}
}
showSymbol(provider: IAccessibleContentProvider, symbol: IAccessibleViewSymbol): void {
showSymbol(provider: ContentProvider, symbol: IAccessibleViewSymbol): void {
if (!this._currentContent) {
return;
}
@ -490,14 +533,14 @@ export class AccessibleView extends Disposable {
}
disableHint(): void {
if (!this._currentProvider) {
if (!(this._currentProvider instanceof AdvancedContentProvider)) {
return;
}
this._configurationService.updateValue(this._currentProvider?.verbositySettingKey, false);
alert(localize('disableAccessibilityHelp', '{0} accessibility verbosity is now disabled', this._currentProvider.verbositySettingKey));
}
private _updateContextKeys(provider: IAccessibleContentProvider, shown: boolean): void {
private _updateContextKeys(provider: ContentProvider, shown: boolean): void {
if (provider.options.type === AccessibleViewType.Help) {
this._accessiblityHelpIsShown.set(shown);
this._accessibleViewIsShown.reset();
@ -505,23 +548,18 @@ export class AccessibleView extends Disposable {
this._accessibleViewIsShown.set(shown);
this._accessiblityHelpIsShown.reset();
}
if (provider.next && provider.previous) {
this._accessibleViewSupportsNavigation.set(true);
} else {
this._accessibleViewSupportsNavigation.reset();
}
const verbosityEnabled: boolean = this._configurationService.getValue(provider.verbositySettingKey);
this._accessibleViewVerbosityEnabled.set(verbosityEnabled);
this._accessibleViewSupportsNavigation.set(provider.next !== undefined || provider.previous !== undefined);
this._accessibleViewVerbosityEnabled.set(this._verbosityEnabled());
this._accessibleViewGoToSymbolSupported.set(this._goToSymbolsSupported() ? this.getSymbols()?.length! > 0 : false);
}
private _render(provider: IAccessibleContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean): IDisposable {
private _render(provider: ContentProvider, container: HTMLElement, showAccessibleViewHelp?: boolean): IDisposable {
this._currentProvider = provider;
this._accessibleViewCurrentProviderId.set(provider.id);
const value = this._configurationService.getValue(provider.verbositySettingKey);
const verbose = this._verbosityEnabled();
const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility (H).") : '';
let disableHelpHint = '';
if (provider.options.type === AccessibleViewType.Help && !!value) {
if (provider instanceof AdvancedContentProvider && provider.options.type === AccessibleViewType.Help && verbose) {
disableHelpHint = this._getDisableVerbosityHint(provider.verbositySettingKey);
}
const accessibilitySupport = this._accessibilityService.isScreenReaderOptimized();
@ -532,7 +570,7 @@ export class AccessibleView extends Disposable {
? AccessibilityHelpNLS.changeConfigToOnMac
: AccessibilityHelpNLS.changeConfigToOnWinLinux
);
if (accessibilitySupport && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) {
if (accessibilitySupport && provider instanceof AdvancedContentProvider && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) {
message = AccessibilityHelpNLS.auto_on;
message += '\n';
} else if (!accessibilitySupport) {
@ -540,14 +578,13 @@ export class AccessibleView extends Disposable {
message += '\n';
}
}
const verbose = this._configurationService.getValue(provider.verbositySettingKey);
const exitThisDialogHint = verbose && !provider.options.position ? localize('exit', '\n\nExit this dialog (Escape).') : '';
const newContent = message + provider.provideContent() + readMoreLink + disableHelpHint + exitThisDialogHint;
this.calculateCodeBlocks(newContent);
this._currentContent = newContent;
this._updateContextKeys(provider, true);
const widgetIsFocused = this._editorWidget.hasTextFocus() || this._editorWidget.hasWidgetFocus();
this._getTextModel(URI.from({ path: `accessible-view-${provider.verbositySettingKey}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => {
this._getTextModel(URI.from({ path: `accessible-view-${provider.id}`, scheme: 'accessible-view', fragment: this._currentContent })).then((model) => {
if (!model) {
return;
}
@ -559,8 +596,7 @@ export class AccessibleView extends Disposable {
model.setLanguage(provider.options.language ?? 'markdown');
container.appendChild(this._container);
let actionsHint = '';
const verbose = this._configurationService.getValue(provider.verbositySettingKey);
const hasActions = this._accessibleViewSupportsNavigation.get() || this._accessibleViewVerbosityEnabled.get() || this._accessibleViewGoToSymbolSupported.get() || this._currentProvider?.actions;
const hasActions = this._accessibleViewSupportsNavigation.get() || this._accessibleViewVerbosityEnabled.get() || this._accessibleViewGoToSymbolSupported.get() || provider.actions?.length;
if (verbose && !showAccessibleViewHelp && hasActions) {
actionsHint = provider.options.position ? localize('ariaAccessibleViewActionsBottom', 'Explore actions such as disabling this hint (Shift+Tab), use Escape to exit this dialog.') : localize('ariaAccessibleViewActions', 'Explore actions such as disabling this hint (Shift+Tab).');
}
@ -591,7 +627,7 @@ export class AccessibleView extends Disposable {
}
}
});
this._updateToolbar(provider.actions, provider.options.type);
this._updateToolbar(this._currentProvider.actions, provider.options.type);
const hide = (e: KeyboardEvent | IKeyboardEvent): void => {
provider.onClose();
@ -614,7 +650,9 @@ export class AccessibleView extends Disposable {
e.preventDefault();
e.stopPropagation();
}
provider.onKeyDown?.(e);
if (provider instanceof AdvancedContentProvider) {
provider.onKeyDown?.(e);
}
}));
disposableStore.add(addDisposableListener(this._toolbar.getElement(), EventType.KEY_DOWN, (e: KeyboardEvent) => {
const keyboardEvent = new StandardKeyboardEvent(e);
@ -668,17 +706,34 @@ export class AccessibleView extends Disposable {
if (!this._currentProvider) {
return false;
}
return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || !!this._currentProvider.getSymbols?.();
return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || (this._currentProvider instanceof AdvancedContentProvider && !!this._currentProvider.getSymbols?.());
}
private _updateLastProvider(): IAccessibleContentProvider | undefined {
if (!this._currentProvider) {
private _updateLastProvider(): ContentProvider | undefined {
const provider = this._currentProvider;
if (!provider) {
return;
}
const lastProvider = Object.assign({}, this._currentProvider);
lastProvider.provideContent = this._currentProvider.provideContent.bind(lastProvider);
lastProvider.options = Object.assign({}, this._currentProvider.options);
lastProvider.verbositySettingKey = this._currentProvider.verbositySettingKey;
const lastProvider = provider instanceof AdvancedContentProvider ? new AdvancedContentProvider(
provider.id,
provider.options,
provider.provideContent.bind(provider),
provider.onClose,
provider.verbositySettingKey,
provider.actions,
provider.next,
provider.previous,
provider.onKeyDown,
provider.getSymbols,
) : new ExtensionContentProvider(
provider.id,
provider.options,
provider.provideContent.bind(provider),
provider.onClose,
provider.next,
provider.previous,
provider.actions
);
return lastProvider;
}
@ -688,7 +743,7 @@ export class AccessibleView extends Disposable {
return;
}
const accessibleViewHelpProvider: IAccessibleContentProvider = {
const accessibleViewHelpProvider = {
id: lastProvider.id,
provideContent: () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()),
onClose: () => {
@ -697,7 +752,7 @@ export class AccessibleView extends Disposable {
queueMicrotask(() => this.show(lastProvider));
},
options: { type: AccessibleViewType.Help },
verbositySettingKey: lastProvider.verbositySettingKey
verbositySettingKey: lastProvider instanceof AdvancedContentProvider ? lastProvider.verbositySettingKey : undefined
};
this._contextViewService.hideContextView();
// HACK: Delay to allow the context view to hide #186514
@ -805,7 +860,7 @@ export class AccessibleViewService extends Disposable implements IAccessibleView
super();
}
show(provider: IAccessibleContentProvider, position?: Position): void {
show(provider: ContentProvider, position?: Position): void {
if (!this._accessibleView) {
this._accessibleView = this._register(this._instantiationService.createInstance(AccessibleView));
}
@ -868,7 +923,7 @@ class AccessibleViewSymbolQuickPick {
constructor(private _accessibleView: AccessibleView, @IQuickInputService private readonly _quickInputService: IQuickInputService) {
}
show(provider: IAccessibleContentProvider): void {
show(provider: ContentProvider): void {
const quickPick = this._quickInputService.createQuickPick<IAccessibleViewSymbol>();
quickPick.placeholder = localize('accessibleViewSymbolQuickPickPlaceholder', "Type to search symbols");
quickPick.title = localize('accessibleViewSymbolQuickPickTitle', "Go to Symbol Accessible View");

View file

@ -8,7 +8,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableMap, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { localize } from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@ -19,8 +19,8 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { getNotificationFromContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService';
import { NotificationFocusedContext } from 'vs/workbench/common/contextkeys';
import { IAccessibleViewService, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { FocusedViewContext, NotificationFocusedContext } from 'vs/workbench/common/contextkeys';
import { IAccessibleViewService, IAccessibleViewOptions, AccessibleViewType, ExtensionContentProvider } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
@ -32,12 +32,16 @@ import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions
import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService';
import { Extensions, IViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views';
import { Registry } from 'vs/platform/registry/common/platform';
import { COMMENTS_VIEW_ID, CommentsMenus } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
import { CommentsPanel, CONTEXT_KEY_HAS_COMMENTS } from 'vs/workbench/contrib/comments/browser/commentsView';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { MarshalledId } from 'vs/base/common/marshallingIds';
import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { URI } from 'vs/base/common/uri';
export function descriptionForCommand(commandId: string, msg: string, noKbMsg: string, keybindingService: IKeybindingService): string {
const kb = keybindingService.lookupKeybinding(commandId);
@ -349,3 +353,75 @@ export class InlineCompletionsAccessibleViewContribution extends Disposable {
}
}
export class ExtensionAccessibilityHelpDialogContribution extends Disposable {
static ID = 'extensionAccessibilityHelpDialogContribution';
private _viewHelpDialogMap = this._register(new DisposableMap<string, IDisposable>());
constructor(@IKeybindingService keybindingService: IKeybindingService) {
super();
this._register(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).onViewsRegistered(e => {
for (const view of e) {
for (const viewDescriptor of view.views) {
if (viewDescriptor.accessibilityHelpContent) {
this._viewHelpDialogMap.set(viewDescriptor.id, registerAccessibilityHelpAction(keybindingService, viewDescriptor));
}
}
}
}));
this._register(Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).onViewsDeregistered(e => {
for (const viewDescriptor of e.views) {
if (viewDescriptor.accessibilityHelpContent) {
this._viewHelpDialogMap.get(viewDescriptor.id)?.dispose();
}
}
}));
}
}
function registerAccessibilityHelpAction(keybindingService: IKeybindingService, viewDescriptor: IViewDescriptor): IDisposable {
const disposableStore = new DisposableStore();
const helpContent = resolveExtensionHelpContent(keybindingService, viewDescriptor.accessibilityHelpContent);
if (!helpContent) {
throw new Error('No help content for view');
}
disposableStore.add(AccessibilityHelpAction.addImplementation(95, viewDescriptor.id, accessor => {
const accessibleViewService = accessor.get(IAccessibleViewService);
const viewsService = accessor.get(IViewsService);
accessibleViewService.show(new ExtensionContentProvider(
viewDescriptor.id,
{ type: AccessibleViewType.Help },
() => helpContent.value,
() => viewsService.openView(viewDescriptor.id, true)
));
return true;
}, FocusedViewContext.isEqualTo(viewDescriptor.id)));
disposableStore.add(keybindingService.onDidUpdateKeybindings(() => {
disposableStore.clear();
disposableStore.add(registerAccessibilityHelpAction(keybindingService, viewDescriptor));
}));
return disposableStore;
}
function resolveExtensionHelpContent(keybindingService: IKeybindingService, content?: MarkdownString): MarkdownString | undefined {
if (!content) {
return;
}
let resolvedContent = typeof content === 'string' ? content : content.value;
const matches = resolvedContent.matchAll(/\(keybinding:(?<commandId>.*)\)/gm);
for (const match of [...matches]) {
const commandId = match?.groups?.commandId;
if (match?.length && commandId) {
const keybinding = keybindingService.lookupKeybinding(commandId)?.getAriaLabel();
let kbLabel = keybinding;
if (!kbLabel) {
const args = URI.parse(`command:workbench.action.openGlobalKeybindings?${encodeURIComponent(JSON.stringify(commandId))}`);
kbLabel = ` [Configure a keybinding](${args})`;
} else {
kbLabel = ' (' + keybinding + ')';
}
resolvedContent = resolvedContent.replace(match[0], kbLabel);
}
}
const result = new MarkdownString(resolvedContent);
result.isTrusted = true;
return result;
}

View file

@ -16,7 +16,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { AccessibleViewProviderId, AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { descriptionForCommand } from 'vs/workbench/contrib/accessibility/browser/accessibleViewContributions';
import { IAccessibleViewService, IAccessibleContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { IAccessibleViewService, IAccessibleViewContentProvider, IAccessibleViewOptions, AccessibleViewType } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { CommentAccessibilityHelpNLS } from 'vs/workbench/contrib/comments/browser/commentsAccessibility';
@ -43,7 +43,7 @@ export class EditorAccessibilityHelpContribution extends Disposable {
}
}
class EditorAccessibilityHelpProvider implements IAccessibleContentProvider {
class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider {
id = AccessibleViewProviderId.Editor;
onClose() {
this._editor.focus();

View file

@ -6,7 +6,7 @@
import { Disposable } from 'vs/base/common/lifecycle';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibleViewType, IAccessibleViewContentProvider, IAccessibleViewOptions, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
import { ctxCommentEditorFocused } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
@ -38,7 +38,7 @@ export namespace CommentAccessibilityHelpNLS {
export const submitCommentNoKb = nls.localize('submitCommentNoKb', "- Submit Comment, accessible via tabbing, as it's currently not triggerable with a keybinding.");
}
export class CommentsAccessibilityHelpProvider implements IAccessibleContentProvider {
export class CommentsAccessibilityHelpProvider implements IAccessibleViewContentProvider {
id = AccessibleViewProviderId.Comments;
verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments;
options: IAccessibleViewOptions = { type: AccessibleViewType.Help };

View file

@ -13,7 +13,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ShellIntegrationStatus, TerminalSettingId, WindowsShellType } from 'vs/platform/terminal/common/terminal';
import { AccessibilityVerbositySettingId, AccessibleViewProviderId, accessibleViewCurrentProviderId, accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibleViewType, IAccessibleViewContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibilityCommandId } from 'vs/workbench/contrib/accessibility/common/accessibilityCommands';
import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
@ -25,7 +25,7 @@ export const enum ClassName {
EditorTextArea = 'textarea'
}
export class TerminalAccessibilityHelpProvider extends Disposable implements IAccessibleContentProvider {
export class TerminalAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider {
id = AccessibleViewProviderId.TerminalHelp;
private readonly _hasShellIntegration: boolean = false;
onClose() {

View file

@ -12,11 +12,11 @@ import { TerminalCapability, ITerminalCommand } from 'vs/platform/terminal/commo
import { ICurrentPartialCommand } from 'vs/platform/terminal/common/capabilities/commandDetection/terminalCommand';
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { AccessibleViewType, IAccessibleViewContentProvider, IAccessibleViewOptions, IAccessibleViewSymbol } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
import { ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker';
export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleContentProvider {
export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleViewContentProvider {
id = AccessibleViewProviderId.Terminal;
options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', id: AccessibleViewProviderId.Terminal };
verbositySettingKey = AccessibilityVerbositySettingId.Terminal;

View file

@ -27,6 +27,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
import { IHoverService } from 'vs/platform/hover/browser/hover';
import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
type UserDataSyncConflictResource = IUserDataSyncResource & IResourcePreview;
@ -50,8 +51,9 @@ export class UserDataSyncConflictsViewPane extends TreeViewPane implements IUser
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
@IAccessibleViewInformationService accessibleViewVisibilityService: IAccessibleViewInformationService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService, hoverService);
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService, hoverService, accessibleViewVisibilityService);
this._register(this.userDataSyncService.onDidChangeConflicts(() => this.treeView.refresh()));
this.registerActions();
}

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX } from 'vs/platform/accessibility/common/accessibility';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
export interface IAccessibleViewInformationService {
_serviceBrand: undefined;
hasShownAccessibleView(viewId: string): boolean;
}
export const IAccessibleViewInformationService = createDecorator<IAccessibleViewInformationService>('accessibleViewInformationService');
export class AccessibleViewInformationService extends Disposable implements IAccessibleViewInformationService {
declare readonly _serviceBrand: undefined;
constructor(@IStorageService private readonly _storageService: IStorageService) {
super();
}
hasShownAccessibleView(viewId: string): boolean {
return this._storageService.getBoolean(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${viewId}`, StorageScope.APPLICATION, false) === true;
}
}

View file

@ -80,6 +80,7 @@ import { WorkbenchIconSelectBox } from 'vs/workbench/services/userDataProfile/br
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import type { IHoverWidget } from 'vs/base/browser/ui/hover/hover';
import { IAccessibleViewInformationService } from 'vs/workbench/services/accessibility/common/accessibleViewInformationService';
interface IUserDataProfileTemplate {
readonly name: string;
@ -1126,8 +1127,9 @@ class UserDataProfilePreviewViewPane extends TreeViewPane {
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService notificationService: INotificationService,
@IHoverService hoverService: IHoverService,
@IAccessibleViewInformationService accessibleViewService: IAccessibleViewInformationService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService, hoverService);
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService, hoverService, accessibleViewService);
}
protected override renderTreeView(container: HTMLElement): void {