From ba9920a1889427db57202dc4dd96b02cf3ffd091 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Wed, 29 Jul 2026 15:24:48 +0800 Subject: [PATCH] Add unique accessible names to chat controls Name focusable chat controls and number sibling message fragments so Windows UI Automation can distinguish them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 3 + .../chat/BaseTurnWidgetAccessibilityTest.java | 63 +++++++++++++++++ .../chat/BaseTurnWidgetPartialRenderTest.java | 56 +-------------- .../ui/chat/BaseTurnWidgetTestSupport.java | 68 +++++++++++++++++++ .../copilot/eclipse/ui/chat/ActionBar.java | 4 ++ .../eclipse/ui/chat/AgentMessageWidget.java | 5 ++ .../eclipse/ui/chat/AgentStatusLabel.java | 2 + .../eclipse/ui/chat/BaseTurnWidget.java | 9 +++ .../eclipse/ui/chat/CopilotTurnWidget.java | 2 + .../copilot/eclipse/ui/chat/Messages.java | 9 +++ .../ui/chat/SourceViewerComposite.java | 11 ++- .../eclipse/ui/chat/SubagentTurnWidget.java | 3 + .../eclipse/ui/chat/ThinkingSection.java | 5 ++ .../eclipse/ui/chat/UserTurnWidget.java | 2 + .../copilot/eclipse/ui/chat/WarnWidget.java | 3 + .../eclipse/ui/chat/messages.properties | 9 +++ .../copilot/eclipse/ui/i18n/Messages.java | 4 ++ .../eclipse/ui/i18n/messages.properties | 4 ++ 18 files changed, 204 insertions(+), 58 deletions(-) create mode 100644 com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetAccessibilityTest.java create mode 100644 com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetTestSupport.java diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d33a72031..872bcc089 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -517,6 +517,9 @@ CompletableFuture.runAsync(() -> { **When** adding user-facing text → externalize in `Messages.properties` using Eclipse NLS pattern +**When** naming focusable controls for accessibility → use non-empty names that are unique among sibling controls; +distinguish repeated controls with stable context or an ordinal + **When** implementing context menus → prefer simple, predictable visibility logic **When** opening preference dialogs → show only relevant pages diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetAccessibilityTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetAccessibilityTest.java new file mode 100644 index 000000000..083cf4e9c --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetAccessibilityTest.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui.chat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.Accessible; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.accessibility.AccessibleListener; +import org.eclipse.swt.custom.StyledText; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; + +/** + * Verifies accessible names exposed by turn content controls. + */ +@ExtendWith(MockitoExtension.class) +class BaseTurnWidgetAccessibilityTest extends BaseTurnWidgetTestSupport { + + @Test + void appendMessage_multipleMarkupBlocks_haveUniqueAccessibleNames() { + AtomicReference> accessibleNamesRef = new AtomicReference<>(); + SwtUtils.invokeOnDisplayThread(() -> { + CopilotTurnWidget widget = new CopilotTurnWidget(shell, SWT.NONE, mockChatServiceManager, TURN_ID); + + widget.appendMessage("First explanation\n```java\nint value = 1;\n```\nSecond explanation\n"); + + accessibleNamesRef.set(Arrays.stream(widget.getChildren()) + .filter(StyledText.class::isInstance) + .map(StyledText.class::cast) + .map(BaseTurnWidgetAccessibilityTest::getAccessibleName) + .toList()); + }); + assertEquals(List.of("GitHub Copilot message 1", "GitHub Copilot message 2"), accessibleNamesRef.get()); + } + + @SuppressWarnings("unchecked") + private static String getAccessibleName(StyledText text) { + Accessible accessible = text.getAccessible(); + try { + Field listenersField = Accessible.class.getDeclaredField("accessibleListeners"); + listenersField.setAccessible(true); + List listeners = (List) listenersField.get(accessible); + AccessibleEvent event = new AccessibleEvent(accessible); + for (AccessibleListener listener : listeners) { + listener.getName(event); + } + return event.result; + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } +} diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetPartialRenderTest.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetPartialRenderTest.java index b2e0df9b9..eae590863 100644 --- a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetPartialRenderTest.java +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetPartialRenderTest.java @@ -7,27 +7,14 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; import java.lang.reflect.Field; import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; import org.mockito.junit.jupiter.MockitoExtension; -import com.microsoft.copilot.eclipse.ui.CopilotUi; -import com.microsoft.copilot.eclipse.ui.chat.services.AvatarService; -import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService; -import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; /** @@ -36,48 +23,7 @@ * blocks where fence detection requires a complete line. */ @ExtendWith(MockitoExtension.class) -class BaseTurnWidgetPartialRenderTest { - - private static final String TURN_ID = "turn-1"; - - private Shell shell; - private MockedStatic copilotUiMock; - private CopilotUi mockPlugin; - - @Mock - private ChatServiceManager mockChatServiceManager; - @Mock - private AvatarService mockAvatarService; - @Mock - private ChatFontService mockChatFontService; - - @BeforeEach - void setUp() { - lenient().when(mockChatServiceManager.getAvatarService()).thenReturn(mockAvatarService); - lenient().when(mockChatServiceManager.getChatFontService()).thenReturn(mockChatFontService); - lenient().when(mockAvatarService.getAvatarForCopilot()).thenReturn(null); - - SwtUtils.invokeOnDisplayThread(() -> { - shell = new Shell(Display.getDefault()); - copilotUiMock = mockStatic(CopilotUi.class); - mockPlugin = mock(CopilotUi.class); - copilotUiMock.when(CopilotUi::getPlugin).thenReturn(mockPlugin); - lenient().when(mockPlugin.getChatServiceManager()).thenReturn(mockChatServiceManager); - }); - } - - @AfterEach - void tearDown() { - SwtUtils.invokeOnDisplayThread(() -> { - if (copilotUiMock != null) { - copilotUiMock.close(); - copilotUiMock = null; - } - if (shell != null && !shell.isDisposed()) { - shell.dispose(); - } - }); - } +class BaseTurnWidgetPartialRenderTest extends BaseTurnWidgetTestSupport { @Test void appendMessage_partialLineWithoutNewline_rendersImmediately() { diff --git a/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetTestSupport.java b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetTestSupport.java new file mode 100644 index 000000000..26fe91d0a --- /dev/null +++ b/com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidgetTestSupport.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +package com.microsoft.copilot.eclipse.ui.chat; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; + +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; +import org.mockito.MockedStatic; + +import com.microsoft.copilot.eclipse.ui.CopilotUi; +import com.microsoft.copilot.eclipse.ui.chat.services.AvatarService; +import com.microsoft.copilot.eclipse.ui.chat.services.ChatFontService; +import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; +import com.microsoft.copilot.eclipse.ui.utils.SwtUtils; + +/** + * Shared SWT and service fixture for turn widget tests. + */ +abstract class BaseTurnWidgetTestSupport { + + protected static final String TURN_ID = "turn-1"; + + protected Shell shell; + + @Mock + protected ChatServiceManager mockChatServiceManager; + @Mock + private AvatarService mockAvatarService; + @Mock + private ChatFontService mockChatFontService; + + private MockedStatic copilotUiMock; + + @BeforeEach + void setUpBaseTurnWidget() { + lenient().when(mockChatServiceManager.getAvatarService()).thenReturn(mockAvatarService); + lenient().when(mockChatServiceManager.getChatFontService()).thenReturn(mockChatFontService); + lenient().when(mockAvatarService.getAvatarForCopilot()).thenReturn(null); + + SwtUtils.invokeOnDisplayThread(() -> { + shell = new Shell(Display.getDefault()); + copilotUiMock = mockStatic(CopilotUi.class); + CopilotUi mockPlugin = mock(CopilotUi.class); + copilotUiMock.when(CopilotUi::getPlugin).thenReturn(mockPlugin); + lenient().when(mockPlugin.getChatServiceManager()).thenReturn(mockChatServiceManager); + }); + } + + @AfterEach + void tearDownBaseTurnWidget() { + SwtUtils.invokeOnDisplayThread(() -> { + if (copilotUiMock != null) { + copilotUiMock.close(); + copilotUiMock = null; + } + if (shell != null && !shell.isDisposed()) { + shell.dispose(); + } + }); + } +} diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java index 2c98738f6..9cd8db3bc 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ActionBar.java @@ -450,6 +450,8 @@ public void updateButtonsLayout() { this.sendToJobButton.setEnabled(hasText); this.sendToJobButton.setImage(hasText ? sendToJobImage : sendToJobDisabledImage); this.sendToJobButton.setToolTipText(Messages.chat_actionBar_sendToJobButton_Tooltip); + AccessibilityUtils.addAccessibilityNameForUiComponent(this.sendToJobButton, + Messages.chat_actionBar_sendToJobButton_Tooltip); GridData sendToJobGd = new GridData(SWT.LEFT, SWT.CENTER, false, false); sendToJobGd.widthHint = sendToJobImage.getImageData().width + 2 * UiConstants.BTN_PADDING; sendToJobGd.heightHint = sendToJobImage.getImageData().height + 2 * UiConstants.BTN_PADDING; @@ -550,6 +552,8 @@ private void setUpMcpToolButtonInControlBar(Composite parent) { this.mcpToolButton = UiUtils.createIconButton(parent, SWT.PUSH | SWT.FLAT); this.chatServiceManager.getMcpConfigService().bindWithMcpToolButton(mcpToolButton, mcpToolImage, mcpToolDisabledImage, mcpToolDetectedImage); + AccessibilityUtils.addAccessibilityNameForUiComponent(this.mcpToolButton, + Messages.chat_actionBar_toolButton_accessibilityName); GridData mcpToolGd = new GridData(SWT.LEFT, SWT.CENTER, false, false); mcpToolGd.widthHint = mcpToolImage.getImageData().width + 2 * UiConstants.BTN_PADDING; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java index fee673d96..348508c8d 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentMessageWidget.java @@ -18,6 +18,7 @@ import com.microsoft.copilot.eclipse.core.Constants; import com.microsoft.copilot.eclipse.core.lsp.protocol.codingagent.CodingAgentMessageRequestParams; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; +import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -70,6 +71,8 @@ private void buildMessageLabelWithIcon(CodingAgentMessageRequestParams params) { StyledText titleText = titleLabel.getTextWidget(); titleText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); titleText.setEditable(false); + AccessibilityUtils.addAccessibilityNameForUiComponent(titleText, + Messages.agentMessageWidget_titleAccessibilityName); titleLabel.setMarkup("**" + params.getTitle() + "**"); } @@ -83,6 +86,8 @@ private void buildMessageLabelWithIcon(CodingAgentMessageRequestParams params) { StyledText descText = descLabel.getTextWidget(); descText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); descText.setEditable(false); + AccessibilityUtils.addAccessibilityNameForUiComponent(descText, + Messages.agentMessageWidget_messageAccessibilityName); String reducedDescription = StringUtils.isBlank(params.getDescription()) ? "" : params.getDescription().substring(0, 100) + "..."; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java index 4979bfff3..62adbb022 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/AgentStatusLabel.java @@ -140,6 +140,8 @@ public void setText(String text) { styledText.setEditable(false); styledText.setData(CssConstants.CSS_CLASS_NAME_KEY, "text-secondary"); AccessibilityUtils.addFocusBorderToComposite(styledText); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + Messages.agentStatusLabel_accessibilityName); } textLabel.setMarkup(text); } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidget.java index 9f0992238..fcafeffa4 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidget.java @@ -67,6 +67,7 @@ public abstract class BaseTurnWidget extends Composite { protected int codeBlockIndex; protected boolean inSubagentBlock; protected String overrideRoleName; + private int textBlockIndex; // Resource protected Image icon = null; @@ -106,6 +107,7 @@ protected BaseTurnWidget(Composite parent, int style, ChatServiceManager service this.isCopilot = isCopilot; this.turnId = turnId; this.codeBlockIndex = 1; + this.textBlockIndex = 1; this.statusLabels = new HashMap<>(); this.subagentBlocks = new HashMap<>(); // editor group @@ -577,6 +579,13 @@ private void createCodeBlock(String language) { */ protected abstract void createTextBlock(); + /** + * Returns the next unique accessible name for a text block in this turn. + */ + protected String nextTextBlockAccessibilityName(String pattern) { + return NLS.bind(pattern, textBlockIndex++); + } + /** * Create an optional footer component. Subclasses can override this to define footer structure. * The footer is placed at the bottom of the turn widget. diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CopilotTurnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CopilotTurnWidget.java index f026aa0ef..f0b397058 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CopilotTurnWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/CopilotTurnWidget.java @@ -61,6 +61,8 @@ protected void createTextBlock() { styledText.setEditable(false); AccessibilityUtils.addFocusBorderToComposite(styledText); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + nextTextBlockAccessibilityName(Messages.chat_copilotTurnWidget_message_accessibilityName)); } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/Messages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/Messages.java index f1a06d18e..bb946f7a3 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/Messages.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/Messages.java @@ -25,6 +25,13 @@ public final class Messages extends NLS { public static String agentMessageWidget_openInBrowserTooltip; public static String agentMessageWidget_openJobListButton; public static String agentMessageWidget_openJobListTooltip; + public static String agentMessageWidget_titleAccessibilityName; + public static String agentMessageWidget_messageAccessibilityName; + public static String subagentTurnWidget_messageAccessibilityName; + public static String agentStatusLabel_accessibilityName; + public static String sourceViewerComposite_codeBlockAccessibilityName; + public static String sourceViewerComposite_copyButtonAccessibilityName; + public static String sourceViewerComposite_insertButtonAccessibilityName; public static String handoffContainer_proceedFrom; public static String fileChangeSummary_filesChanged; public static String fileChangeSummary_fileChanged; @@ -40,6 +47,8 @@ public final class Messages extends NLS { public static String thinking_title; public static String thinking_expandTooltip; public static String thinking_collapseTooltip; + public static String thinkingSection_headingAccessibilityName; + public static String thinkingSection_detailsAccessibilityName; // Confirmation dialog action labels public static String confirmation_action_allowOnce; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java index b1e737a84..eb58b39ad 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SourceViewerComposite.java @@ -154,6 +154,8 @@ public void mouseExit(MouseEvent e) { }; serviceManager.getChatFontService().registerCallback(fontChangeCallback); AccessibilityUtils.addFocusBorderToComposite(styledText); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + Messages.sourceViewerComposite_codeBlockAccessibilityName); return viewer; } @@ -172,7 +174,8 @@ private Composite createActionsComposite() { result.setLayout(layout); this.copyIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_COPY); - Button copyButton = createActionButton(result, SWT.PUSH | SWT.FLAT, copyIcon, "Copy", "Copy to clipboard"); + Button copyButton = createActionButton(result, SWT.PUSH | SWT.FLAT, copyIcon, "Copy", "Copy to clipboard", + Messages.sourceViewerComposite_copyButtonAccessibilityName); copyButton.addListener(SWT.Selection, e -> { String content = this.sourceViewer.getDocument().get(); if (StringUtils.isNotEmpty(content)) { @@ -187,7 +190,7 @@ private Composite createActionsComposite() { Image pasteIcon = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_PASTE); Image insertButtonIcon = this.insertIcon != null ? this.insertIcon : pasteIcon; Button insertButton = createActionButton(result, SWT.PUSH | SWT.FLAT, insertButtonIcon, "Insert", - "Insert into editor"); + "Insert into editor", Messages.sourceViewerComposite_insertButtonAccessibilityName); insertButton.addListener(SWT.Selection, this::insert); result.setVisible(false); @@ -195,9 +198,11 @@ private Composite createActionsComposite() { return result; } - private Button createActionButton(Composite parent, int style, Image image, String text, String tooltip) { + private Button createActionButton(Composite parent, int style, Image image, String text, String tooltip, + String accessibilityName) { Button result = new Button(parent, style); result.setToolTipText(tooltip); + AccessibilityUtils.addAccessibilityNameForUiComponent(result, accessibilityName); result.setVisible(true); if (image == null) { result.setText(text); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SubagentTurnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SubagentTurnWidget.java index 99cc1a889..4063b5327 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SubagentTurnWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/SubagentTurnWidget.java @@ -14,6 +14,7 @@ import com.microsoft.copilot.eclipse.core.lsp.protocol.AgentToolCall; import com.microsoft.copilot.eclipse.ui.chat.services.AvatarService; import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager; +import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; /** * A turn widget for displaying subagent messages within a SubagentMessageBlock. @@ -69,5 +70,7 @@ protected void createTextBlock() { StyledText styledText = this.currentTextBlock.getTextWidget(); styledText.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, false)); styledText.setEditable(false); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + nextTextBlockAccessibilityName(Messages.subagentTurnWidget_messageAccessibilityName)); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingSection.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingSection.java index b12607657..592abe908 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingSection.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ThinkingSection.java @@ -15,6 +15,7 @@ import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; +import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -94,6 +95,8 @@ private void createTitleRow(String titleText) { titleWidget.setEditable(false); titleWidget.setMargins(0, 0, 0, 0); UiUtils.applyCssClass(titleWidget, SECONDARY_TEXT_CSS_CLASS, stylingEngine); + AccessibilityUtils.addAccessibilityNameForUiComponent(titleWidget, + Messages.thinkingSection_headingAccessibilityName); titleView.setMarkup("**" + titleText + "**"); } @@ -116,6 +119,8 @@ private ChatMarkupViewer createBodyViewer(String markup) { text.setEditable(false); text.setMargins(0, 0, 0, 0); UiUtils.applyCssClass(text, SECONDARY_TEXT_CSS_CLASS, stylingEngine); + AccessibilityUtils.addAccessibilityNameForUiComponent(text, + Messages.thinkingSection_detailsAccessibilityName); viewer.setMarkup(markup); return viewer; } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/UserTurnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/UserTurnWidget.java index bb5b4c998..81f203c95 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/UserTurnWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/UserTurnWidget.java @@ -115,5 +115,7 @@ protected void createTextBlock() { serviceManager.getChatFontService().registerControl(styledText); AccessibilityUtils.addFocusBorderToComposite(styledText); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + nextTextBlockAccessibilityName(Messages.chat_userTurnWidget_message_accessibilityName)); } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java index 2582320cb..9d7efd2d5 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/WarnWidget.java @@ -22,6 +22,7 @@ import com.microsoft.copilot.eclipse.ui.chat.QuotaActions.QuotaAction; import com.microsoft.copilot.eclipse.ui.i18n.Messages; import com.microsoft.copilot.eclipse.ui.swt.CssConstants; +import com.microsoft.copilot.eclipse.ui.utils.AccessibilityUtils; import com.microsoft.copilot.eclipse.ui.utils.UiUtils; /** @@ -123,6 +124,8 @@ private void buildWarnLabelWithIcon(String message) { StyledText styledText = textLabel.getTextWidget(); styledText.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true)); styledText.setEditable(false); + AccessibilityUtils.addAccessibilityNameForUiComponent(styledText, + Messages.chat_warnWidget_message_accessibilityName); textLabel.setMarkup(message); requestLayout(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/messages.properties b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/messages.properties index d46ae6138..671b22f88 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/messages.properties +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/messages.properties @@ -16,6 +16,13 @@ agentMessageWidget_openInBrowserButton=Open in Browser agentMessageWidget_openInBrowserTooltip=Click to open the pull request in browser agentMessageWidget_openJobListButton=Open Job List agentMessageWidget_openJobListTooltip=Click to open the GitHub Copilot Agent Jobs view +agentMessageWidget_titleAccessibilityName=Coding agent title +agentMessageWidget_messageAccessibilityName=Coding agent message +subagentTurnWidget_messageAccessibilityName=Subagent message {0} +agentStatusLabel_accessibilityName=Tool status +sourceViewerComposite_codeBlockAccessibilityName=Code block +sourceViewerComposite_copyButtonAccessibilityName=Copy code to clipboard +sourceViewerComposite_insertButtonAccessibilityName=Insert code into editor handoffContainer_proceedFrom=PROCEED FROM {0} @@ -35,6 +42,8 @@ todoList_collapseTooltip=Click to collapse the todo list thinking_title=Thinking thinking_expandTooltip=Click to show the thinking details thinking_collapseTooltip=Click to hide the thinking details +thinkingSection_headingAccessibilityName=Thinking heading +thinkingSection_detailsAccessibilityName=Thinking details # Confirmation dialog action labels confirmation_action_allowOnce=Allow Once diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/Messages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/Messages.java index 642b71b1b..e41c9e097 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/Messages.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/Messages.java @@ -94,6 +94,7 @@ public final class Messages extends NLS { public static String chat_actionBar_sendToJob_noProject; public static String chat_actionBar_cancelButton_Tooltip; public static String chat_actionBar_toolButton_toolTip; + public static String chat_actionBar_toolButton_accessibilityName; public static String chat_actionBar_toolButton_disabled_toolTip; public static String chat_actionBar_modelPicker_manageModels; public static String chat_actionBar_toolButton_detected_toolTip; @@ -156,6 +157,9 @@ public final class Messages extends NLS { public static String chat_currentReferencedFile_description; public static String chat_turnWidget_copilot; public static String chat_turnWidget_user; + public static String chat_userTurnWidget_message_accessibilityName; + public static String chat_copilotTurnWidget_message_accessibilityName; + public static String chat_warnWidget_message_accessibilityName; public static String chat_model_unsupported_message; public static String chat_copilotModels; public static String chat_standardModels; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/messages.properties b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/messages.properties index bb6b44003..9878bc09f 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/messages.properties +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/i18n/messages.properties @@ -83,6 +83,7 @@ chat_actionBar_sendToJobButton_Tooltip=Delegate to coding agent chat_actionBar_sendToJob_noProject=No GitHub repository was found in the workspace. Please open a project that contains a Git repository to use the coding agent. chat_actionBar_cancelButton_Tooltip=Cancel chat_actionBar_toolButton_toolTip=Configure Tools... +chat_actionBar_toolButton_accessibilityName=Configure tools chat_actionBar_toolButton_disabled_toolTip=MCP servers are disabled by org policy. Contact your admin. chat_actionBar_modelPicker_manageModels=Manage Models... chat_actionBar_toolButton_detected_toolTip=New MCP server found - approval needed @@ -158,6 +159,9 @@ chat_filePicker_message=Choose files from the list, or search for files: chat_currentReferencedFile_description=Current file chat_turnWidget_copilot=GitHub Copilot chat_turnWidget_user=User +chat_userTurnWidget_message_accessibilityName=User message {0} +chat_copilotTurnWidget_message_accessibilityName=GitHub Copilot message {0} +chat_warnWidget_message_accessibilityName=Warning message chat_model_unsupported_message=Model is not supported, please visit https://github.com/settings/copilot to enable the model and try again. chat_copilotModels=Copilot Models chat_standardModels=Standard Models