diff --git a/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts b/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts index 41a7fbdfce9..8404497154c 100644 --- a/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts +++ b/src/vs/editor/contrib/hover/browser/hoverAccessibleViews.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { HoverController } from 'vs/editor/contrib/hover/browser/hoverController'; -import { AccessibleViewType, AccessibleViewProviderId, AdvancedContentProvider, IAccessibleViewContentProvider, IAccessibleViewOptions } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleViewProviderId, AccessibleContentProvider, 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'; @@ -36,9 +36,7 @@ export class HoverAccessibleView implements IAccessibleViewImplentation { public readonly name = 'hover'; public readonly when = EditorContextKeys.hoverFocused; - private _provider: HoverAccessibleViewProvider | undefined; - - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const codeEditorService = accessor.get(ICodeEditorService); const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); if (!codeEditor) { @@ -49,12 +47,7 @@ export class HoverAccessibleView implements IAccessibleViewImplentation { return; } const keybindingService = accessor.get(IKeybindingService); - this._provider = accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController); - return this._provider; - } - - dispose(): void { - this._provider?.dispose(); + return accessor.get(IInstantiationService).createInstance(HoverAccessibleViewProvider, keybindingService, codeEditor, hoverController); } } @@ -65,9 +58,7 @@ export class HoverAccessibilityHelp implements IAccessibleViewImplentation { public readonly type = AccessibleViewType.Help; public readonly when = EditorContextKeys.hoverVisible; - private _provider: HoverAccessibleViewProvider | undefined; - - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const codeEditorService = accessor.get(ICodeEditorService); const codeEditor = codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor(); if (!codeEditor) { @@ -79,10 +70,6 @@ export class HoverAccessibilityHelp implements IAccessibleViewImplentation { } return accessor.get(IInstantiationService).createInstance(HoverAccessibilityHelpProvider, hoverController); } - - dispose(): void { - this._provider?.dispose(); - } } abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider { @@ -124,7 +111,6 @@ abstract class BaseHoverAccessibleViewProvider extends Disposable implements IAc } this._focusedHoverPartIndex = -1; this._hoverController.shouldKeepOpenOnEditorMouseMoveOrLeave = false; - this.dispose(); } provideContentAtIndex(focusedHoverIndex: number, includeVerbosityActions: boolean): string { @@ -247,12 +233,11 @@ export class HoverAccessibleViewProvider extends BaseHoverAccessibleViewProvider } export class ExtHoverAccessibleView implements IAccessibleViewImplentation { - public readonly type = AccessibleViewType.View; public readonly priority = 90; public readonly name = 'extension-hover'; - getProvider(accessor: ServicesAccessor): AdvancedContentProvider | undefined { + getProvider(accessor: ServicesAccessor): AccessibleContentProvider | undefined { const contextViewService = accessor.get(IContextViewService); const contextViewElement = contextViewService.getContextViewElement(); const extensionHoverContent = contextViewElement?.textContent ?? undefined; @@ -262,16 +247,14 @@ export class ExtHoverAccessibleView implements IAccessibleViewImplentation { // The accessible view, itself, uses the context view service to display the text. We don't want to read that. return; } - return { - id: AccessibleViewProviderId.Hover, - verbositySettingKey: 'accessibility.verbosity.hover', - provideContent() { return extensionHoverContent; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.Hover, + { language: 'typescript', type: AccessibleViewType.View }, + () => { return extensionHoverContent; }, + () => { hoverService.showAndFocusLastHover(); }, - options: { language: 'typescript', type: AccessibleViewType.View } - }; + 'accessibility.verbosity.hover', + ); } - - dispose() { } } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts index 6182681a3b5..93514b9135c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsAccessibleView.ts @@ -3,16 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { InlineCompletionContextKeys } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys'; import { InlineCompletionsController } from 'vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController'; -import { AccessibleViewType, AccessibleViewProviderId } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleViewProviderId, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -export class InlineCompletionsAccessibleView extends Disposable implements IAccessibleViewImplentation { +export class InlineCompletionsAccessibleView implements IAccessibleViewImplentation { readonly type = AccessibleViewType.View; readonly priority = 95; readonly name = 'inline-completions'; @@ -35,28 +34,28 @@ export class InlineCompletionsAccessibleView extends Disposable implements IAcce return; } const language = editor.getModel()?.getLanguageId() ?? undefined; - return { - id: AccessibleViewProviderId.InlineCompletions, - verbositySettingKey: 'accessibility.verbosity.inlineCompletions', - provideContent() { return lineText + ghostText; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.InlineCompletions, + { language, type: AccessibleViewType.View }, + () => lineText + ghostText, + + () => { model.stop(); editor.focus(); }, - next() { + 'accessibility.verbosity.inlineCompletions', + undefined, + undefined, + () => { model.next(); setTimeout(() => resolveProvider(), 50); }, - previous() { + () => { model.previous(); setTimeout(() => resolveProvider(), 50); }, - options: { language, type: AccessibleViewType.View } - }; + ); } return resolveProvider(); } - constructor() { - super(); - } } diff --git a/src/vs/platform/accessibility/browser/accessibleView.ts b/src/vs/platform/accessibility/browser/accessibleView.ts index d71a5871195..d6bc55b8900 100644 --- a/src/vs/platform/accessibility/browser/accessibleView.ts +++ b/src/vs/platform/accessibility/browser/accessibleView.ts @@ -9,6 +9,7 @@ import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQui import { Event } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IAccessibleViewService = createDecorator('accessibleViewService'); @@ -26,7 +27,8 @@ export const enum AccessibleViewProviderId { Hover = 'hover', Notification = 'notification', EmptyEditorHint = 'emptyEditorHint', - Comments = 'comments' + Comments = 'comments', + DebugConsole = 'debugConsole', } export const enum AccessibleViewType { @@ -69,7 +71,7 @@ export interface IAccessibleViewOptions { } -export interface IAccessibleViewContentProvider extends IBasicContentProvider { +export interface IAccessibleViewContentProvider extends IBasicContentProvider, IDisposable { id: AccessibleViewProviderId; verbositySettingKey: string; /** @@ -102,6 +104,7 @@ export interface IPosition { export interface IAccessibleViewService { readonly _serviceBrand: undefined; + // The provider will be disposed when the view is closed show(provider: AccesibleViewContentProvider, position?: IPosition): void; showLastProvider(id: AccessibleViewProviderId): void; showAccessibleViewHelp(): void; @@ -131,9 +134,9 @@ export interface ICodeBlockActionContext { element: unknown; } -export type AccesibleViewContentProvider = AdvancedContentProvider | ExtensionContentProvider; +export type AccesibleViewContentProvider = AccessibleContentProvider | ExtensionContentProvider; -export class AdvancedContentProvider implements IAccessibleViewContentProvider { +export class AccessibleContentProvider extends Disposable implements IAccessibleViewContentProvider { constructor( public id: AccessibleViewProviderId, @@ -149,10 +152,12 @@ export class AdvancedContentProvider implements IAccessibleViewContentProvider { public onKeyDown?: (e: IKeyboardEvent) => void, public getSymbols?: () => IAccessibleViewSymbol[], public onDidRequestClearLastProvider?: Event, - ) { } + ) { + super(); + } } -export class ExtensionContentProvider implements IBasicContentProvider { +export class ExtensionContentProvider extends Disposable implements IBasicContentProvider { constructor( public readonly id: string, @@ -164,10 +169,12 @@ export class ExtensionContentProvider implements IBasicContentProvider { public previous?: () => void, public actions?: IAction[], public onDidChangeContent?: Event, - ) { } + ) { + super(); + } } -export interface IBasicContentProvider { +export interface IBasicContentProvider extends IDisposable { id: string; options: IAccessibleViewOptions; onClose(): void; diff --git a/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts b/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts index 0a6369565bb..8b298d92a21 100644 --- a/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts +++ b/src/vs/platform/accessibility/browser/accessibleViewRegistry.ts @@ -4,19 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { alert } from 'vs/base/browser/ui/aria/aria'; -export interface IAccessibleViewImplentation extends IDisposable { +export interface IAccessibleViewImplentation { type: AccessibleViewType; priority: number; name: string; /** * @returns the provider or undefined if the view should not be shown */ - getProvider: (accessor: ServicesAccessor) => AdvancedContentProvider | ExtensionContentProvider | undefined; + getProvider: (accessor: ServicesAccessor) => AccessibleContentProvider | ExtensionContentProvider | undefined; when?: ContextKeyExpression | undefined; } @@ -31,7 +31,6 @@ export const AccessibleViewRegistry = new class AccessibleViewRegistry { if (idx !== -1) { this._implementations.splice(idx, 1); } - implementation.dispose(); } }; } diff --git a/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts b/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts index 15a240f9150..bed675ca22e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationAccessibleView.ts @@ -7,8 +7,8 @@ import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; -import { IAccessibleViewService, AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; -import { IAccessibleViewImplentation, alertAccessibleViewFocusChange } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { IAccessibleViewService, AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; +import { alertAccessibleViewFocusChange, IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { IAccessibilitySignalService, AccessibilitySignal } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -59,15 +59,17 @@ export class NotificationAccessibleView implements IAccessibleViewImplentation { return; } notification.onDidClose(() => accessibleViewService.next()); - return { - id: AccessibleViewProviderId.Notification, - provideContent: () => { + return new AccessibleContentProvider( + AccessibleViewProviderId.Notification, + { type: AccessibleViewType.View }, + () => { return notification.source ? localize('notification.accessibleViewSrc', '{0} Source: {1}', message, notification.source) : localize('notification.accessibleView', '{0}', message); }, - onClose(): void { - focusList(); - }, - next(): void { + () => focusList(), + 'accessibility.verbosity.notification', + undefined, + getActionsFromNotification(notification, accessibilitySignalService), + () => { if (!list) { return; } @@ -76,7 +78,7 @@ export class NotificationAccessibleView implements IAccessibleViewImplentation { alertAccessibleViewFocusChange(notificationIndex, length, 'next'); getProvider(); }, - previous(): void { + () => { if (!list) { return; } @@ -85,14 +87,10 @@ export class NotificationAccessibleView implements IAccessibleViewImplentation { alertAccessibleViewFocusChange(notificationIndex, length, 'previous'); getProvider(); }, - verbositySettingKey: 'accessibility.verbosity.notification', - options: { type: AccessibleViewType.View }, - actions: getActionsFromNotification(notification, accessibilitySignalService) - }; + ); } return getProvider(); } - dispose() { } } diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 28a82a629df..ea8047a243d 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -376,7 +376,7 @@ export abstract class ViewPane extends Pane implements IView { @IThemeService protected themeService: IThemeService, @ITelemetryService protected telemetryService: ITelemetryService, @IHoverService protected readonly hoverService: IHoverService, - protected readonly accessibleViewService?: IAccessibleViewInformationService + protected readonly accessibleViewInformationService?: IAccessibleViewInformationService ) { super({ ...options, ...{ orientation: viewDescriptorService.getViewLocationById(options.id) === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL } }); @@ -553,7 +553,7 @@ export abstract class ViewPane extends Pane implements IView { private _getAriaLabel(title: string): string { const viewHasAccessibilityHelpContent = this.viewDescriptorService.getViewDescriptorById(this.id)?.accessibilityHelpContent; - const accessibleViewHasShownForView = this.accessibleViewService?.hasShownAccessibleView(this.id); + const accessibleViewHasShownForView = this.accessibleViewInformationService?.hasShownAccessibleView(this.id); if (!viewHasAccessibilityHelpContent || accessibleViewHasShownForView) { return title; } diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index fa4e47467d2..076b4b7def3 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -58,7 +58,8 @@ export const enum AccessibilityVerbositySettingId { EmptyEditorHint = 'accessibility.verbosity.emptyEditorHint', ReplInputHint = 'accessibility.verbosity.replInputHint', Comments = 'accessibility.verbosity.comments', - DiffEditorActive = 'accessibility.verbosity.diffEditorActive' + DiffEditorActive = 'accessibility.verbosity.diffEditorActive', + DebugConsole = 'accessibility.verbosity.debugConsole', } const baseVerbosityProperty: IConfigurationPropertySchema = { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index d4bd1f893e8..abdcee0b421 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -24,7 +24,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 { AccessibleViewProviderId, AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService, IAccessibleViewSymbol } from 'vs/platform/accessibility/browser/accessibleView'; 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'; @@ -51,7 +51,7 @@ const enum DIMENSIONS { MAX_WIDTH = 600 } -export type AccesibleViewContentProvider = AdvancedContentProvider | ExtensionContentProvider; +export type AccesibleViewContentProvider = AccessibleContentProvider | ExtensionContentProvider; interface ICodeBlock { startLine: number; @@ -140,6 +140,7 @@ export class AccessibleView extends Disposable { lineDecorationsWidth: 6, dragAndDrop: false, cursorWidth: 1, + wordWrap: 'off', wrappingStrategy: 'advanced', wrappingIndent: 'none', padding: { top: 2, bottom: 2 }, @@ -156,7 +157,7 @@ export class AccessibleView extends Disposable { } })); this._register(this._configurationService.onDidChangeConfiguration(e => { - if (this._currentProvider instanceof AdvancedContentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) { + if (this._currentProvider instanceof AccessibleContentProvider && e.affectsConfiguration(this._currentProvider.verbositySettingKey)) { if (this._accessiblityHelpIsShown.get()) { this.show(this._currentProvider); } @@ -258,6 +259,7 @@ export class AccessibleView extends Disposable { onHide: () => { if (!showAccessibleViewHelp) { this._updateLastProvider(); + this._currentProvider?.dispose(); this._currentProvider = undefined; this._resetContextKeys(); } @@ -276,7 +278,7 @@ export class AccessibleView extends Disposable { if (symbol && this._currentProvider) { this.showSymbol(this._currentProvider, symbol); } - if (provider instanceof AdvancedContentProvider && provider.onDidRequestClearLastProvider) { + if (provider instanceof AccessibleContentProvider && provider.onDidRequestClearLastProvider) { this._register(provider.onDidRequestClearLastProvider((id: string) => { if (this._lastProvider?.options.id === id) { this._lastProvider = undefined; @@ -312,7 +314,7 @@ export class AccessibleView extends Disposable { if (!this._currentProvider) { return false; } - 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); + return this._currentProvider instanceof AccessibleContentProvider ? this._configurationService.getValue(this._currentProvider.verbositySettingKey) === true : this._storageService.getBoolean(`${ACCESSIBLE_VIEW_SHOWN_STORAGE_PREFIX}${this._currentProvider.id}`, StorageScope.APPLICATION, false); } goToSymbol(): void { @@ -352,7 +354,7 @@ export class AccessibleView extends Disposable { } getSymbols(): IAccessibleViewSymbol[] | undefined { - const provider = this._currentProvider instanceof AdvancedContentProvider ? this._currentProvider : undefined; + const provider = this._currentProvider instanceof AccessibleContentProvider ? this._currentProvider : undefined; if (!this._currentContent || !provider) { return; } @@ -464,7 +466,7 @@ export class AccessibleView extends Disposable { } disableHint(): void { - if (!(this._currentProvider instanceof AdvancedContentProvider)) { + if (!(this._currentProvider instanceof AccessibleContentProvider)) { return; } this._configurationService.updateValue(this._currentProvider?.verbositySettingKey, false); @@ -490,7 +492,7 @@ export class AccessibleView extends Disposable { const verbose = this._verbosityEnabled(); const readMoreLink = provider.options.readMoreUrl ? localize("openDoc", "\n\nOpen a browser window with more information related to accessibility.", AccessibilityCommandId.AccessibilityHelpOpenHelpLink) : ''; let disableHelpHint = ''; - if (provider instanceof AdvancedContentProvider && provider.options.type === AccessibleViewType.Help && verbose) { + if (provider instanceof AccessibleContentProvider && provider.options.type === AccessibleViewType.Help && verbose) { disableHelpHint = this._getDisableVerbosityHint(); } const accessibilitySupport = this._accessibilityService.isScreenReaderOptimized(); @@ -501,7 +503,7 @@ export class AccessibleView extends Disposable { ? AccessibilityHelpNLS.changeConfigToOnMac : AccessibilityHelpNLS.changeConfigToOnWinLinux ); - if (accessibilitySupport && provider instanceof AdvancedContentProvider && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) { + if (accessibilitySupport && provider instanceof AccessibleContentProvider && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) { message = AccessibilityHelpNLS.auto_on; message += '\n'; } else if (!accessibilitySupport) { @@ -579,6 +581,8 @@ export class AccessibleView extends Disposable { this._updateContextKeys(provider, false); this._lastProvider = undefined; this._currentContent = undefined; + this._currentProvider?.dispose(); + this._currentProvider = undefined; }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { @@ -593,7 +597,7 @@ export class AccessibleView extends Disposable { e.preventDefault(); e.stopPropagation(); } - if (provider instanceof AdvancedContentProvider) { + if (provider instanceof AccessibleContentProvider) { provider.onKeyDown?.(e); } })); @@ -649,7 +653,7 @@ 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 instanceof AdvancedContentProvider && !!this._currentProvider.getSymbols?.()); + return this._currentProvider.options.type === AccessibleViewType.Help || this._currentProvider.options.language === 'markdown' || this._currentProvider.options.language === undefined || (this._currentProvider instanceof AccessibleContentProvider && !!this._currentProvider.getSymbols?.()); } private _updateLastProvider(): AccesibleViewContentProvider | undefined { @@ -657,7 +661,7 @@ export class AccessibleView extends Disposable { if (!provider) { return; } - const lastProvider = provider instanceof AdvancedContentProvider ? new AdvancedContentProvider( + const lastProvider = provider instanceof AccessibleContentProvider ? new AccessibleContentProvider( provider.id, provider.options, provider.provideContent.bind(provider), @@ -689,21 +693,36 @@ export class AccessibleView extends Disposable { if (!lastProvider) { return; } - - const accessibleViewHelpProvider = { - id: lastProvider.id, - provideContent: () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), - onClose: () => { - this._contextViewService.hideContextView(); - // HACK: Delay to allow the context view to hide #207638 - queueMicrotask(() => this.show(lastProvider)); - }, - options: { type: AccessibleViewType.Help }, - verbositySettingKey: lastProvider instanceof AdvancedContentProvider ? lastProvider.verbositySettingKey : undefined - }; + let accessibleViewHelpProvider; + if (lastProvider instanceof AccessibleContentProvider) { + accessibleViewHelpProvider = new AccessibleContentProvider( + lastProvider.id as AccessibleViewProviderId, + { type: AccessibleViewType.Help }, + () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), + () => { + this._contextViewService.hideContextView(); + // HACK: Delay to allow the context view to hide #207638 + queueMicrotask(() => this.show(lastProvider)); + }, + lastProvider.verbositySettingKey as AccessibilityVerbositySettingId + ); + } else { + accessibleViewHelpProvider = new ExtensionContentProvider( + lastProvider.id as AccessibleViewProviderId, + { type: AccessibleViewType.Help }, + () => lastProvider.options.customHelp ? lastProvider?.options.customHelp() : this._getAccessibleViewHelpDialogContent(this._goToSymbolsSupported()), + () => { + this._contextViewService.hideContextView(); + // HACK: Delay to allow the context view to hide #207638 + queueMicrotask(() => this.show(lastProvider)); + }, + ); + } this._contextViewService.hideContextView(); // HACK: Delay to allow the context view to hide #186514 - queueMicrotask(() => this.show(accessibleViewHelpProvider, undefined, true)); + if (accessibleViewHelpProvider) { + queueMicrotask(() => this.show(accessibleViewHelpProvider, undefined, true)); + } } private _getAccessibleViewHelpDialogContent(providerHasSymbols?: boolean): string { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts index d8d07767246..bc026ec30af 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewContributions.ts @@ -6,7 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { accessibleViewIsShown } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; -import { AccessibleViewType, AdvancedContentProvider, ExtensionContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewType, AccessibleContentProvider, ExtensionContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -27,12 +27,17 @@ export class AccesibleViewContributions extends Disposable { super(); AccessibleViewRegistry.getImplementations().forEach(impl => { const implementation = (accessor: ServicesAccessor) => { - const provider: AdvancedContentProvider | ExtensionContentProvider | undefined = impl.getProvider(accessor); - if (provider) { + const provider: AccessibleContentProvider | ExtensionContentProvider | undefined = impl.getProvider(accessor); + if (!provider) { + return false; + } + try { accessor.get(IAccessibleViewService).show(provider); return true; + } catch { + provider.dispose(); + return false; } - return false; }; if (impl.type === AccessibleViewType.View) { this._register(AccessibleViewAction.addImplementation(impl.priority, impl.name, implementation, impl.when)); diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 0e47093d20b..0cce331a43b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -39,7 +39,7 @@ export class EditorAccessibilityHelpContribution extends Disposable { } } -class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider { +class EditorAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { id = AccessibleViewProviderId.Editor; onClose() { this._editor.focus(); @@ -51,6 +51,7 @@ class EditorAccessibilityHelpProvider implements IAccessibleViewContentProvider @IKeybindingService private readonly _keybindingService: IKeybindingService, @IContextKeyService private readonly _contextKeyService: IContextKeyService ) { + super(); } provideContent(): string { diff --git a/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts index f8381417bd8..7c18982222f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/extensionAccesibilityHelp.contribution.ts @@ -57,7 +57,6 @@ function registerAccessibilityHelpAction(keybindingService: IKeybindingService, () => viewsService.openView(viewDescriptor.id, true), ); }, - dispose: () => { }, })); disposableStore.add(keybindingService.onDidUpdateKeybindings(() => { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index e7374ecb565..b8e70095ab3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor/commands'; import { INLINE_CHAT_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -25,7 +25,6 @@ export class ChatAccessibilityHelp implements IAccessibleViewImplentation { const codeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor() || accessor.get(ICodeEditorService).getFocusedCodeEditor(); return getChatAccessibilityHelpProvider(accessor, codeEditor ?? undefined, 'panelChat'); } - dispose() { } } export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat'): string { @@ -70,11 +69,11 @@ export function getChatAccessibilityHelpProvider(accessor: ServicesAccessor, edi const cachedPosition = inputEditor.getPosition(); inputEditor.getSupportedActions(); const helpText = getAccessibilityHelpText(type); - return { - id: type === 'panelChat' ? AccessibleViewProviderId.Chat : AccessibleViewProviderId.InlineChat, - verbositySettingKey: type === 'panelChat' ? AccessibilityVerbositySettingId.Chat : AccessibilityVerbositySettingId.InlineChat, - provideContent: () => helpText, - onClose: () => { + return new AccessibleContentProvider( + type === 'panelChat' ? AccessibleViewProviderId.Chat : AccessibleViewProviderId.InlineChat, + { type: AccessibleViewType.Help }, + () => helpText, + () => { if (type === 'panelChat' && cachedPosition) { inputEditor.setPosition(cachedPosition); inputEditor.focus(); @@ -86,6 +85,6 @@ export function getChatAccessibilityHelpProvider(accessor: ServicesAccessor, edi } }, - options: { type: AccessibleViewType.Help } - }; + type === 'panelChat' ? AccessibilityVerbositySettingId.Chat : AccessibilityVerbositySettingId.InlineChat, + ); } diff --git a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts index 6d682b5d72e..21a2158f723 100644 --- a/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts +++ b/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts @@ -6,7 +6,7 @@ import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { alertAccessibleViewFocusChange, IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; @@ -69,11 +69,11 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplentation { const length = responses?.length; const responseIndex = responses?.findIndex(i => i === focusedItem); - return { - id: AccessibleViewProviderId.Chat, - verbositySettingKey: AccessibilityVerbositySettingId.Chat, - provideContent(): string { return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true); }, - onClose() { + const provider = new AccessibleContentProvider( + AccessibleViewProviderId.Chat, + { type: AccessibleViewType.View }, + () => { return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true); }, + () => { verifiedWidget.reveal(focusedItem); if (chatInputFocused) { verifiedWidget.focusInput(); @@ -81,19 +81,21 @@ export class ChatResponseAccessibleView implements IAccessibleViewImplentation { verifiedWidget.focus(focusedItem); } }, - next() { + AccessibilityVerbositySettingId.Chat, + undefined, + undefined, + () => { verifiedWidget.moveFocus(focusedItem, 'next'); alertAccessibleViewFocusChange(responseIndex, length, 'next'); resolveProvider(widgetService, codeEditorService); }, - previous() { + () => { verifiedWidget.moveFocus(focusedItem, 'previous'); alertAccessibleViewFocusChange(responseIndex, length, 'previous'); resolveProvider(widgetService, codeEditorService); }, - options: { type: AccessibleViewType.View } - }; + ); + return provider; } } - dispose() { } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts index ef4ce7693a1..0e32031fc61 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorAccessibilityHelp.ts @@ -7,7 +7,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor/commands'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { localize } from 'vs/nls'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyEqualsExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -51,15 +51,12 @@ export class DiffEditorAccessibilityHelp implements IAccessibleViewImplentation if (commentCommandInfo) { content.push(commentCommandInfo); } - return { - id: AccessibleViewProviderId.DiffEditor, - verbositySettingKey: AccessibilityVerbositySettingId.DiffEditor, - provideContent: () => content.join('\n\n'), - onClose: () => { - codeEditor.focus(); - }, - options: { type: AccessibleViewType.Help } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.DiffEditor, + { type: AccessibleViewType.Help }, + () => content.join('\n\n'), + () => codeEditor.focus(), + AccessibilityVerbositySettingId.DiffEditor, + ); } - dispose() { } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts b/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts index 1c247dec349..cd77c78e485 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsAccessibility.ts @@ -13,6 +13,7 @@ import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCo import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode'; import { IAccessibleViewContentProvider, AccessibleViewProviderId, IAccessibleViewOptions, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { Disposable } from 'vs/base/common/lifecycle'; export namespace CommentAccessibilityHelpNLS { export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:"); @@ -27,7 +28,7 @@ export namespace CommentAccessibilityHelpNLS { export const submitComment = nls.localize('submitComment', "- Submit Comment{0}", ``); } -export class CommentsAccessibilityHelpProvider implements IAccessibleViewContentProvider { +export class CommentsAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider { id = AccessibleViewProviderId.Comments; verbositySettingKey: AccessibilityVerbositySettingId = AccessibilityVerbositySettingId.Comments; options: IAccessibleViewOptions = { type: AccessibleViewType.Help }; @@ -48,5 +49,4 @@ export class CommentsAccessibilityHelp implements IAccessibleViewImplentation { getProvider(accessor: ServicesAccessor) { return accessor.get(IInstantiationService).createInstance(CommentsAccessibilityHelpProvider); } - dispose() { } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts b/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts index 2fdac52458d..92ff53fa8d6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsAccessibleView.ts @@ -6,7 +6,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -56,28 +56,25 @@ export class CommentsAccessibleView extends Disposable implements IAccessibleVie } }; }); - return { - id: AccessibleViewProviderId.Notification, - provideContent: () => { - return content; - }, - onClose(): void { - commentsView.focus(); - }, - next(): void { + return new AccessibleContentProvider( + AccessibleViewProviderId.Notification, + { type: AccessibleViewType.View }, + () => content, + () => commentsView.focus(), + AccessibilityVerbositySettingId.Comments, + undefined, + actions, + () => { commentsView.focus(); commentsView.focusNextNode(); resolveProvider(); }, - previous(): void { + () => { commentsView.focus(); commentsView.focusPreviousNode(); resolveProvider(); - }, - verbositySettingKey: AccessibilityVerbositySettingId.Comments, - options: { type: AccessibleViewType.View }, - actions - }; + } + ); } return resolveProvider(); } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index b85da3db794..eca1f04f309 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -11,6 +11,7 @@ import 'vs/css!./media/debug.contribution'; import 'vs/css!./media/debugHover'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; +import { AccessibleViewRegistry } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; @@ -28,6 +29,7 @@ import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; +import { DebugAccessibleView } from 'vs/workbench/contrib/debug/browser/debugAccessibleView'; import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { ADD_CONFIGURATION_ID, ADD_TO_WATCH_ID, ADD_TO_WATCH_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTINUE_ID, CONTINUE_LABEL, COPY_EVALUATE_PATH_ID, COPY_EVALUATE_PATH_LABEL, COPY_STACK_TRACE_ID, COPY_VALUE_ID, COPY_VALUE_LABEL, DEBUG_COMMAND_CATEGORY, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, EDIT_EXPRESSION_COMMAND_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, PAUSE_ID, PAUSE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, REMOVE_EXPRESSION_COMMAND_ID, RESTART_FRAME_ID, RESTART_LABEL, RESTART_SESSION_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, SELECT_DEBUG_SESSION_ID, SELECT_DEBUG_SESSION_LABEL, SET_EXPRESSION_COMMAND_ID, SHOW_LOADED_SCRIPTS_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL, TERMINATE_THREAD_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess'; @@ -632,3 +634,5 @@ configurationRegistry.registerConfiguration({ } } }); + +AccessibleViewRegistry.register(new DebugAccessibleView()); diff --git a/src/vs/workbench/contrib/debug/browser/debugAccessibleView.ts b/src/vs/workbench/contrib/debug/browser/debugAccessibleView.ts new file mode 100644 index 00000000000..1172f9b1e62 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugAccessibleView.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider, IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IDebugService, IReplElement } from 'vs/workbench/contrib/debug/common/debug'; +import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getReplView, Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { IViewsService } from 'vs/workbench/services/views/common/viewsService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Position } from 'vs/editor/common/core/position'; + +export class DebugAccessibleView implements IAccessibleViewImplentation { + priority = 70; + name = 'debugConsole'; + when = ContextKeyExpr.equals('focusedView', 'workbench.panel.repl.view'); + type: AccessibleViewType = AccessibleViewType.View; + getProvider(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const debugService = accessor.get(IDebugService); + const accessibleViewService = accessor.get(IAccessibleViewService); + const replView = getReplView(viewsService); + if (!replView) { + return undefined; + } + const focusedElement = replView.getFocusedElement(); + return new DebugAccessibleViewProvider(replView, focusedElement, debugService, accessibleViewService); + } +} + +class DebugAccessibleViewProvider extends Disposable implements IAccessibleViewContentProvider { + public readonly id = AccessibleViewProviderId.DebugConsole; + private _content: string | undefined; + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + public readonly onDidChangeContent: Event = this._onDidChangeContent.event; + private readonly _onDidResolveChildren: Emitter = this._register(new Emitter()); + public readonly onDidResolveChildren: Event = this._onDidResolveChildren.event; + + public readonly verbositySettingKey = AccessibilityVerbositySettingId.DebugConsole; + public readonly options = { + type: AccessibleViewType.View + }; + + private _elementPositionMap: Map = new Map(); + + constructor( + private readonly _replView: Repl, + private readonly _focusedElement: IReplElement | undefined, + @IDebugService private readonly _debugService: IDebugService, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService) { + super(); + + } + public provideContent(): string { + const viewModel = this._debugService.getViewModel(); + const focusedDebugSession = viewModel?.focusedSession; + if (!focusedDebugSession) { + return 'No debug session is active.'; + } + const elements = focusedDebugSession.getReplElements(); + if (!elements.length) { + return 'No output in the debug console.'; + } + if (!this._content) { + this._updateContent(elements); + } + // Content is loaded asynchronously, so we need to check if it's available or fallback to the elements that are already available. + return this._content ?? elements.map(e => e.toString(true)).join('\n'); + } + + public onClose(): void { + this._content = undefined; + this._replView.focusTree(); + this._elementPositionMap.clear(); + } + + public onOpen(): void { + // Children are resolved async, so we need to update the content when they are resolved. + this._register(this.onDidResolveChildren(() => { + this._onDidChangeContent.fire(); + queueMicrotask(() => { + if (this._focusedElement) { + const position = this._elementPositionMap.get(this._focusedElement.getId()); + if (position) { + this._accessibleViewService.setPosition(position, true); + } + } + }); + })); + } + + private async _updateContent(elements: IReplElement[]) { + const dataSource = this._replView.getReplDataSource(); + if (!dataSource) { + return; + } + let line = 0; + const content: string[] = []; + for (const e of elements) { + content.push(e.toString().replace(/\n/g, '')); + this._elementPositionMap.set(e.getId(), new Position(line, 1)); + line++; + if (e.sourceData) { + line++; + } + if (dataSource.hasChildren(e)) { + const childContent: string[] = []; + const children = await dataSource.getChildren(e); + for (const child of children) { + const id = child.getId(); + if (!this._elementPositionMap.has(id)) { + // don't overwrite parent position + this._elementPositionMap.set(id, new Position(line, 1)); + } + childContent.push(child.toString()); + line++; + } + content.push(childContent.join('\n')); + } + } + + this._content = content.join('\n'); + this._onDidResolveChildren.fire(); + } +} diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index c282aa3fab9..e5b0ec13dd7 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -72,6 +72,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; import { AccessibilitySignal, IAccessibilitySignalService } from 'vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IAccessibleViewService } from 'vs/platform/accessibility/browser/accessibleView'; const $ = dom.$; @@ -116,6 +118,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { private filter: ReplFilter; private multiSessionRepl: IContextKey; private menu: IMenu; + private replDataSource: IAsyncDataSource | undefined; constructor( options: IViewPaneOptions, @@ -138,6 +141,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { @IMenuService menuService: IMenuService, @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @ILogService private readonly logService: ILogService, + @IAccessibleViewService private readonly accessibleViewService: IAccessibleViewService ) { const filterText = storageService.get(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE, ''); super({ @@ -526,6 +530,18 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { return this.replInput; } + getReplDataSource(): IAsyncDataSource | undefined { + return this.replDataSource; + } + + getFocusedElement(): IReplElement | undefined { + return this.tree?.getFocus()?.[0]; + } + + focusTree(): void { + this.tree?.domFocus(); + } + override focus(): void { super.focus(); setTimeout(() => this.replInput.focus(), 0); @@ -624,6 +640,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { const wordWrap = this.configurationService.getValue('debug').console.wordWrap; this.treeContainer.classList.toggle('word-wrap', wordWrap); const linkDetector = this.instantiationService.createInstance(LinkDetector); + this.replDataSource = new ReplDataSource(); const tree = this.tree = >this.instantiationService.createInstance( WorkbenchAsyncDataTree, @@ -639,7 +656,7 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { new ReplRawObjectsRenderer(linkDetector, this.hoverService), ], // https://github.com/microsoft/TypeScript/issues/32526 - new ReplDataSource() satisfies IAsyncDataSource, + this.replDataSource, { filter: this.filter, accessibilityProvider: new ReplAccessibilityProvider(), @@ -702,7 +719,8 @@ export class Repl extends FilterViewPane implements IHistoryNavigationWidget { options.suggest = { showStatusBar: true }; const config = this.configurationService.getValue('debug'); options.acceptSuggestionOnEnter = config.console.acceptSuggestionOnEnter === 'on' ? 'on' : 'off'; - options.ariaLabel = localize('debugConsole', "Debug Console"); + const accessibleViewHint = this.accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.DebugConsole); + options.ariaLabel = accessibleViewHint ? localize('debugConsoleHint', "Debug Console, {0}", accessibleViewHint) : localize('debugConsole', "Debug Console"); this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions()); @@ -939,7 +957,7 @@ class SelectReplActionViewItem extends FocusSessionActionViewItem { } } -function getReplView(viewsService: IViewsService): Repl | undefined { +export function getReplView(viewsService: IViewsService): Repl | undefined { return viewsService.getActiveViewWithId(REPL_VIEW_ID) as Repl ?? undefined; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts index a2348928fb6..d168f54f836 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibilityHelp.ts @@ -23,5 +23,4 @@ export class InlineChatAccessibilityHelp implements IAccessibleViewImplentation } return getChatAccessibilityHelpProvider(accessor, codeEditor, 'inlineChat'); } - dispose() { } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts index 797dae2825d..964741b6455 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatAccessibleView.ts @@ -7,12 +7,12 @@ import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/in import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; -import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; export class InlineChatAccessibleView implements IAccessibleViewImplentation { readonly priority = 100; @@ -34,15 +34,12 @@ export class InlineChatAccessibleView implements IAccessibleViewImplentation { if (!responseContent) { return; } - return { - id: AccessibleViewProviderId.InlineChat, - verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, - provideContent(): string { return renderMarkdownAsPlaintext(new MarkdownString(responseContent), true); }, - onClose() { - controller.focus(); - }, - options: { type: AccessibleViewType.View } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.InlineChat, + { type: AccessibleViewType.View }, + () => renderMarkdownAsPlaintext(new MarkdownString(responseContent), true), + () => controller.focus(), + AccessibilityVerbositySettingId.InlineChat + ); } - dispose() { } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index b146d0f71d2..3ea6a915bed 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -7,7 +7,7 @@ import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/a import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { localize } from 'vs/nls'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IVisibleEditorPane } from 'vs/workbench/common/editor'; @@ -23,16 +23,13 @@ export class NotebookAccessibilityHelp implements IAccessibleViewImplentation { || accessor.get(ICodeEditorService).getFocusedCodeEditor() || accessor.get(IEditorService).activeEditorPane; - if (activeEditor) { - return runAccessibilityHelpAction(accessor, activeEditor); + if (!activeEditor) { + return; } - return; + return getAccessibilityHelpProvider(accessor, activeEditor); } - dispose() { } } - - export function getAccessibilityHelpText(): string { return [ localize('notebook.overview', 'The notebook view is a collection of code and markdown cells. Code cells can be executed and will produce output directly below the cell.'), @@ -48,15 +45,13 @@ export function getAccessibilityHelpText(): string { ].join('\n\n'); } -export function runAccessibilityHelpAction(accessor: ServicesAccessor, editor: ICodeEditor | IVisibleEditorPane) { +export function getAccessibilityHelpProvider(accessor: ServicesAccessor, editor: ICodeEditor | IVisibleEditorPane) { const helpText = getAccessibilityHelpText(); - return { - id: AccessibleViewProviderId.Notebook, - verbositySettingKey: AccessibilityVerbositySettingId.Notebook, - provideContent: () => helpText, - onClose: () => { - editor.focus(); - }, - options: { type: AccessibleViewType.Help } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.Notebook, + { type: AccessibleViewType.Help }, + () => helpText, + () => editor.focus(), + AccessibilityVerbositySettingId.Notebook, + ); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts index daa9bae804d..8771a492e2f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibleView.ts @@ -2,7 +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 { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -18,13 +18,12 @@ export class NotebookAccessibleView implements IAccessibleViewImplentation { readonly when = ContextKeyExpr.and(NOTEBOOK_OUTPUT_FOCUSED, ContextKeyExpr.equals('resourceExtname', '.ipynb')); getProvider(accessor: ServicesAccessor) { const editorService = accessor.get(IEditorService); - return showAccessibleOutput(editorService); + return getAccessibleOutputProvider(editorService); } - dispose() { } } -export function showAccessibleOutput(editorService: IEditorService) { +export function getAccessibleOutputProvider(editorService: IEditorService) { const activePane = editorService.activeEditorPane; const notebookEditor = getNotebookEditorFromEditorPane(activePane); const notebookViewModel = notebookEditor?.getViewModel(); @@ -73,15 +72,15 @@ export function showAccessibleOutput(editorService: IEditorService) { return; } - return { - id: AccessibleViewProviderId.Notebook, - verbositySettingKey: AccessibilityVerbositySettingId.Notebook, - provideContent(): string { return outputContent; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.Notebook, + { type: AccessibleViewType.View }, + () => { return outputContent; }, + () => { notebookEditor?.setFocus(selections[0]); activePane?.focus(); }, - options: { type: AccessibleViewType.View } - }; + AccessibilityVerbositySettingId.Notebook, + ); } diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts index 329b73e2a31..f685191817f 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/model'; import { IAccessibleViewContentProvider, AccessibleViewProviderId, IAccessibleViewOptions, AccessibleViewType, IAccessibleViewSymbol } from 'vs/platform/accessibility/browser/accessibleView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -16,7 +16,7 @@ import { ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/termin import { BufferContentTracker } from 'vs/workbench/contrib/terminalContrib/accessibility/browser/bufferContentTracker'; import { TerminalAccessibilitySettingId } from 'vs/workbench/contrib/terminalContrib/accessibility/common/terminalAccessibilityConfiguration'; -export class TerminalAccessibleBufferProvider extends DisposableStore implements IAccessibleViewContentProvider { +export class TerminalAccessibleBufferProvider extends Disposable implements IAccessibleViewContentProvider { id = AccessibleViewProviderId.Terminal; options: IAccessibleViewOptions = { type: AccessibleViewType.View, language: 'terminal', id: AccessibleViewProviderId.Terminal }; verbositySettingKey = AccessibilityVerbositySettingId.Terminal; @@ -35,14 +35,14 @@ export class TerminalAccessibleBufferProvider extends DisposableStore implements super(); this.options.customHelp = customHelp; this.options.position = configurationService.getValue(TerminalAccessibilitySettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; - this.add(this._instance.onDisposed(() => this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal))); - this.add(configurationService.onDidChangeConfiguration(e => { + this._register(this._instance.onDisposed(() => this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal))); + this._register(configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalAccessibilitySettingId.AccessibleViewPreserveCursorPosition)) { this.options.position = configurationService.getValue(TerminalAccessibilitySettingId.AccessibleViewPreserveCursorPosition) ? 'initial-bottom' : 'bottom'; } })); this._focusedInstance = _terminalService.activeInstance; - this.add(_terminalService.onDidChangeActiveInstance(() => { + this._register(_terminalService.onDidChangeActiveInstance(() => { if (_terminalService.activeInstance && this._focusedInstance?.instanceId !== _terminalService.activeInstance?.instanceId) { this._onDidRequestClearProvider.fire(AccessibleViewProviderId.Terminal); this._focusedInstance = _terminalService.activeInstance; diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts index 35fcdd0410c..dd0c9dc5470 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibilityHelp.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { IAccessibleViewImplentation } from 'vs/platform/accessibility/browser/accessibleViewRegistry'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -27,15 +27,14 @@ export class TerminalChatAccessibilityHelp implements IAccessibleViewImplentatio } const helpText = getAccessibilityHelpText(accessor); - return { - id: AccessibleViewProviderId.TerminalChat, - verbositySettingKey: AccessibilityVerbositySettingId.TerminalChat, - provideContent: () => helpText, - onClose: () => TerminalChatController.get(instance)?.focus(), - options: { type: AccessibleViewType.Help } - }; + return new AccessibleContentProvider( + AccessibleViewProviderId.TerminalChat, + { type: AccessibleViewType.Help }, + () => helpText, + () => TerminalChatController.get(instance)?.focus(), + AccessibilityVerbositySettingId.TerminalChat, + ); } - dispose() { } } export function getAccessibilityHelpText(accessor: ServicesAccessor): string { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts index 2898dfde41c..a4bd399abf2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatAccessibleView.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AccessibleViewProviderId, AccessibleViewType } from 'vs/platform/accessibility/browser/accessibleView'; +import { AccessibleViewProviderId, AccessibleViewType, AccessibleContentProvider } from 'vs/platform/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController'; @@ -23,15 +23,14 @@ export class TerminalInlineChatAccessibleView implements IAccessibleViewImplenta return; } const responseContent = controller.lastResponseContent; - return { - id: AccessibleViewProviderId.TerminalChat, - verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, - provideContent(): string { return responseContent; }, - onClose() { + return new AccessibleContentProvider( + AccessibleViewProviderId.TerminalChat, + { type: AccessibleViewType.View }, + () => { return responseContent; }, + () => { controller.focus(); }, - options: { type: AccessibleViewType.View } - }; + AccessibilityVerbositySettingId.InlineChat, + ); } - dispose() { } }