From 4c2ed1a2e77e862959f8d5942e025a8ec3da0642 Mon Sep 17 00:00:00 2001 From: Ben Villalobos Date: Sun, 2 Nov 2025 17:26:25 -0800 Subject: [PATCH] Add screenreader warning on terminal tool notifications (#274228) Enables markdown message parts to receive accessibility information on when to announce statusUpdates (and apply ariaLabel) --- .../chatContentParts/chatMarkdownContentPart.ts | 16 ++++++++++++++++ .../chatTerminalToolProgressPart.ts | 13 +++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index aa5ca13ada0..f871f1d9ec3 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -6,6 +6,7 @@ import '../media/chatCodeBlockPill.css'; import './media/chatMarkdownPart.css'; import * as dom from '../../../../../base/browser/dom.js'; +import { status } from '../../../../../base/browser/ui/aria/aria.js'; import { allowedMarkdownHtmlAttributes, MarkdownRendererMarkedOptions, type MarkdownRenderOptions } from '../../../../../base/browser/markdownRenderer.js'; import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; import { HoverPosition } from '../../../../../base/browser/ui/hover/hoverWidget.js'; @@ -61,11 +62,19 @@ import { ChatExtensionsContentPart } from './chatExtensionsContentPart.js'; import { IOpenEditorOptions, registerOpenEditorListeners } from '../../../../../platform/editor/browser/editor.js'; import { HoverStyle } from '../../../../../base/browser/ui/hover/hover.js'; import { ChatEditingActionContext } from '../chatEditing/chatEditingActions.js'; +import { AccessibilityWorkbenchSettingId } from '../../../accessibility/browser/accessibilityConfiguration.js'; const $ = dom.$; export interface IChatMarkdownContentPartOptions { readonly codeBlockRenderOptions?: ICodeBlockRenderOptions; + readonly accessibilityOptions?: { + /** + * Message to announce to screen readers as a status update if VerboseChatProgressUpdates is enabled. + * Will also be used as the aria-label for the container. + * */ + statusMessage?: string; + }; } export class ChatMarkdownContentPart extends Disposable implements IChatContentPart { @@ -118,6 +127,13 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP this.domNode = $('div.chat-markdown-part'); + if (this.rendererOptions.accessibilityOptions?.statusMessage) { + this.domNode.ariaLabel = this.rendererOptions.accessibilityOptions.statusMessage; + if (configurationService.getValue(AccessibilityWorkbenchSettingId.VerboseChatProgressUpdates)) { + status(this.rendererOptions.accessibilityOptions.statusMessage); + } + } + const enableMath = configurationService.getValue(ChatConfiguration.EnableMath); const doRenderMarkdown = () => { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index 340472b1d20..da8937a82d9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -15,7 +15,7 @@ import { CodeBlockModelCollection } from '../../../common/codeBlockModelCollecti import { IChatCodeBlockInfo } from '../../chat.js'; import { ChatQueryTitlePart } from '../chatConfirmationWidget.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; -import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js'; +import { ChatMarkdownContentPart, EditorPool, type IChatMarkdownContentPartOptions } from '../chatMarkdownContentPart.js'; import { ChatProgressSubPart } from '../chatProgressContentPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; import '../media/chatTerminalToolProgressPart.css'; @@ -38,6 +38,7 @@ import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/m import * as domSanitize from '../../../../../../base/browser/domSanitize.js'; import { DomSanitizerConfig } from '../../../../../../base/browser/domSanitize.js'; import { allowedMarkdownHtmlAttributes } from '../../../../../../base/browser/markdownRenderer.js'; +import { stripIcons } from '../../../../../../base/common/iconLabels.js'; const MAX_TERMINAL_OUTPUT_PREVIEW_HEIGHT = 200; @@ -143,7 +144,15 @@ export class ChatTerminalToolProgressPart extends BaseChatToolInvocationSubPart wordWrap: 'on' } }; - this.markdownPart = this._register(_instantiationService.createInstance(ChatMarkdownContentPart, chatMarkdownContent, context, editorPool, false, codeBlockStartIndex, renderer, {}, currentWidthDelegate(), codeBlockModelCollection, { codeBlockRenderOptions })); + + const markdownOptions: IChatMarkdownContentPartOptions = { + codeBlockRenderOptions, + accessibilityOptions: pastTenseMessage ? { + statusMessage: localize('terminalToolCommand', '{0}', stripIcons(pastTenseMessage)) + } : undefined + }; + + this.markdownPart = this._register(_instantiationService.createInstance(ChatMarkdownContentPart, chatMarkdownContent, context, editorPool, false, codeBlockStartIndex, renderer, {}, currentWidthDelegate(), codeBlockModelCollection, markdownOptions)); this._register(this.markdownPart.onDidChangeHeight(() => this._onDidChangeHeight.fire())); elements.message.append(this.markdownPart.domNode);