From 4a103b0eea7de96d12fc2cbada846ca7ef4f1298 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 16 Jul 2024 20:03:46 -0700 Subject: [PATCH] alert screen reader users with changes in watch variables (#221847) --- .../debug/browser/debug.contribution.ts | 2 ++ .../workbench/contrib/debug/common/debug.ts | 7 +++++ .../common/debugAccessibilityAnnouncer.ts | 30 +++++++++++++++++++ .../contrib/debug/common/debugModel.ts | 12 ++++++++ 4 files changed, 51 insertions(+) create mode 100644 src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a6cffaba9b0..582e2fdf902 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -60,6 +60,7 @@ import { launchSchemaId } from 'vs/workbench/services/configuration/common/confi import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import './debugSettingMigration'; import { DebugAccessibilityHelp } from 'vs/workbench/contrib/debug/browser/debugAccessibilityHelp'; +import { DebugWatchAccessibilityAnnouncer } from 'vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer'; import { ReplAccessibilityAnnouncer } from 'vs/workbench/contrib/debug/common/replAccessibilityAnnouncer'; const debugCategory = nls.localize('debugCategory', "Debug"); @@ -646,4 +647,5 @@ configurationRegistry.registerConfiguration({ AccessibleViewRegistry.register(new DebugAccessibleView()); AccessibleViewRegistry.register(new DebugAccessibilityHelp()); +registerWorkbenchContribution2(DebugWatchAccessibilityAnnouncer.ID, DebugWatchAccessibilityAnnouncer, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ReplAccessibilityAnnouncer.ID, ReplAccessibilityAnnouncer, WorkbenchPhase.AfterRestored); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index c5afc868ec0..96785a6403e 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -743,7 +743,14 @@ export interface IDebugModel extends ITreeElement { getBreakpointModes(forBreakpointType: 'source' | 'exception' | 'data' | 'instruction'): DebugProtocol.BreakpointMode[]; onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; + /** + * The expression has been added, removed, or repositioned. + */ onDidChangeWatchExpressions: Event; + /** + * The expression's value has changed. + */ + onDidChangeWatchExpressionValue: Event; fetchCallstack(thread: IThread, levels?: number): Promise; } diff --git a/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts b/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts new file mode 100644 index 00000000000..446e16db168 --- /dev/null +++ b/src/vs/workbench/contrib/debug/common/debugAccessibilityAnnouncer.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; + +export class DebugWatchAccessibilityAnnouncer extends Disposable implements IWorkbenchContribution { + static ID = 'workbench.contrib.debugWatchAccessibilityAnnouncer'; + constructor( + @IDebugService _debugService: IDebugService, + @ILogService _logService: ILogService, + @IAccessibilityService _accessibilityService: IAccessibilityService + ) { + super(); + this._register(_debugService.getModel().onDidChangeWatchExpressionValue((e) => { + if (!e || e.value === 'not available') { + return; + } + + // TODO: get user feedback, perhaps setting to configure verbosity + whether value, name, neither, or both are announced + _accessibilityService.alert(`${e.name} = ${e.value}`); + _logService.trace(`debugAccessibilityAnnouncerValueChanged ${e.name} ${e.value}`); + })); + } +} diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index f9c67ec8958..c2708ae2150 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -298,6 +298,9 @@ export class Expression extends ExpressionContainer implements IExpression { public available: boolean; + private readonly _onDidChangeValue = new Emitter(); + public readonly onDidChangeValue: Event = this._onDidChangeValue.event; + constructor(public name: string, id = generateUuid()) { super(undefined, undefined, 0, id); this.available = false; @@ -310,6 +313,9 @@ export class Expression extends ExpressionContainer implements IExpression { async evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string, keepLazyVars?: boolean, location?: IDebugEvaluatePosition): Promise { this.available = await this.evaluateExpression(this.name, session, stackFrame, context, keepLazyVars, location); + if (this.valueChanged) { + this._onDidChangeValue.fire(this); + } } override toString(): string { @@ -1403,6 +1409,7 @@ export class DebugModel extends Disposable implements IDebugModel { private readonly _onDidChangeBreakpoints = this._register(new Emitter()); private readonly _onDidChangeCallStack = this._register(new Emitter()); private readonly _onDidChangeWatchExpressions = this._register(new Emitter()); + private readonly _onDidChangeWatchExpressionValue = this._register(new Emitter()); private readonly _breakpointModes = new Map(); private breakpoints!: Breakpoint[]; private functionBreakpoints!: FunctionBreakpoint[]; @@ -1497,6 +1504,10 @@ export class DebugModel extends Disposable implements IDebugModel { return this._onDidChangeWatchExpressions.event; } + get onDidChangeWatchExpressionValue(): Event { + return this._onDidChangeWatchExpressionValue.event; + } + rawUpdate(data: IRawModelUpdate): void { const session = this.sessions.find(p => p.getId() === data.sessionId); if (session) { @@ -2003,6 +2014,7 @@ export class DebugModel extends Disposable implements IDebugModel { addWatchExpression(name?: string): IExpression { const we = new Expression(name || ''); + this._register(we.onDidChangeValue((e) => this._onDidChangeWatchExpressionValue.fire(e))); this.watchExpressions.push(we); this._onDidChangeWatchExpressions.fire(we);