From a2565d400d383c62b893d3eecb1febec0050b255 Mon Sep 17 00:00:00 2001 From: raghucssit Date: Mon, 27 Apr 2026 17:12:00 +0200 Subject: [PATCH] In Agent mode: Auto scroll to prompts like 'Continue'. User must be made aware some action is needed from them to continue the work by agent. Usually this happens when "Too many requests" or "Command line run prompt" etc. see https://github.com/microsoft/copilot-eclipse-feedback/issues/184 --- .../copilot/eclipse/core/Constants.java | 1 + .../eclipse/ui/chat/BaseTurnWidget.java | 40 ++++++++++++++++++- .../eclipse/ui/chat/ChatContentViewer.java | 18 ++++++++- .../CopilotPreferenceInitializer.java | 1 + .../preferences/GeneralPreferencesPage.java | 14 +++++++ .../eclipse/ui/preferences/Messages.java | 4 ++ .../ui/preferences/messages.properties | 3 ++ .../copilot/eclipse/ui/utils/SwtUtils.java | 22 ++++++++++ 8 files changed, 100 insertions(+), 3 deletions(-) diff --git a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java index 176bc245..ce3b0757 100644 --- a/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java +++ b/com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/Constants.java @@ -51,6 +51,7 @@ private Constants() { public static final String CHAT_CHANNEL = "chatProgress"; public static final String AUTO_SHOW_WHAT_IS_NEW = "autoShowWhatsNew"; public static final String AUTO_BREAKPOINT_RESPONSE = "autoBreakpointResponse"; + public static final String AUTO_SCROLL_TO_NEW_DIALOG = "autoScrollToNewDialog"; public static final String GITHUB_JOBS_VIEW_ID = "com.microsoft.copilot.eclipse.ui.jobs.JobsView"; public static final String SUPPRESS_TERMINAL_DEPENDENCY_DIALOG = "suppressTerminalDependencyDialog"; 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 45557687..286db93d 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 @@ -23,6 +23,7 @@ import org.eclipse.ui.PlatformUI; import org.osgi.service.event.EventHandler; +import com.microsoft.copilot.eclipse.core.Constants; import com.microsoft.copilot.eclipse.core.CopilotCore; import com.microsoft.copilot.eclipse.core.chat.ConfirmationContent; import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants; @@ -36,6 +37,7 @@ import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.EditAgentRoundData; import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.ReplyData; import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.ToolCallData; +import com.microsoft.copilot.eclipse.ui.CopilotUi; 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.SwtUtils; @@ -627,9 +629,24 @@ protected void createWarnDialog(String message, int code, String modelProviderNa && quotaStatus.premiumInteractions().overagePermitted(); canUpgradePlan = quotaStatus.canUpgradePlan(); } - new WarnWidget(this, SWT.NONE, displayMessage, planForActions, overageEnabled, canUpgradePlan); + WarnWidget warnWidget = new WarnWidget(this, SWT.NONE, displayMessage, planForActions, overageEnabled, canUpgradePlan); ensureFooterAtBottom(); requestLayout(); + // Ensure the chat content viewer scrolls to show the newly created warning banner. Walk up the composite hierarchy + // to find a ChatContentViewer and request scrolling. Use async exec because layout needs to complete first. + SwtUtils.invokeOnDisplayThreadAsync(() -> { + if (!isAutoScrollPromptsEnabled()) { + return; + } + ChatContentViewer viewer = SwtUtils.findParentOfType(this.getParent(), ChatContentViewer.class); + if (viewer != null) { + viewer.refreshScrollerLayout(); + if (warnWidget != null && !warnWidget.isDisposed()) { + viewer.showControl(warnWidget); + } + } + + }, this.getParent()); } /** @@ -666,6 +683,23 @@ public CompletableFuture requestToolExecuti this.getParent().requestLayout(); + // Ensure the chat content viewer scrolls to show the newly created confirmation + // dialog. Walk up the composite hierarchy to find a ChatContentViewer + // and request scrolling. Use async exec because layout needs to complete first. + SwtUtils.invokeOnDisplayThreadAsync(() -> { + if (!isAutoScrollPromptsEnabled()) { + return; + } + ChatContentViewer viewer = SwtUtils.findParentOfType(this.getParent(), ChatContentViewer.class); + if (viewer != null) { + viewer.refreshScrollerLayout(); + if (this.confirmDialog != null && !this.confirmDialog.isDisposed()) { + viewer.showControl(this.confirmDialog); + } + } + + }, this.getParent()); + return toolConfirmationFuture; } @@ -710,4 +744,8 @@ public void dispose() { this.cancelMsgEventHandler = null; } } + + public static boolean isAutoScrollPromptsEnabled() { + return CopilotUi.getPlugin().getPreferenceStore().getBoolean(Constants.AUTO_SCROLL_TO_NEW_DIALOG); + } } diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatContentViewer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatContentViewer.java index 86a0bbf7..749a2f40 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatContentViewer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatContentViewer.java @@ -421,7 +421,9 @@ public BaseTurnWidget getTurnWidget(String turnId) { private void renderWarnMessageWithUpgradePlanButton(String errorMessage, int code, String modelProviderName) { latestTurnWidget.createWarnDialog(errorMessage, code, modelProviderName); refreshScrollerLayout(); - scrollToLatestUserTurn(); + if (!BaseTurnWidget.isAutoScrollPromptsEnabled()) { + scrollToLatestUserTurn(); + } } /** @@ -433,7 +435,19 @@ public void renderErrorMessage(String errorMessage) { } this.errorWidget = new ErrorWidget(cmpContent, SWT.BOTTOM, errorMessage); refreshScrollerLayout(); - scrollToLatestUserTurn(); + // Ensure the chat content viewer scrolls to show the newly created error banner. + SwtUtils.invokeOnDisplayThreadAsync(() -> { + if (!BaseTurnWidget.isAutoScrollPromptsEnabled()) { + return; + } + if (this.errorWidget != null && !this.errorWidget.isDisposed()) { + this.showControl(this.errorWidget); + } + }, this.getParent()); + + if (!BaseTurnWidget.isAutoScrollPromptsEnabled()) { + scrollToLatestUserTurn(); + } } /** diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java index 6b43717c..68a40a55 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java @@ -39,6 +39,7 @@ public void initializeDefaultPreferences() { pref.setDefault(Constants.CUSTOM_INSTRUCTIONS_CHAT_LOAD_SCOPE, CustomInstructionsChatLoadScope.DEFAULT_VALUE.getValue()); pref.setDefault(Constants.AUTO_BREAKPOINT_RESPONSE, false); + pref.setDefault(Constants.AUTO_SCROLL_TO_NEW_DIALOG, true); pref.setDefault(Constants.MCP, """ { "servers": { diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GeneralPreferencesPage.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GeneralPreferencesPage.java index 6b7da3e3..90c8ab31 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GeneralPreferencesPage.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/GeneralPreferencesPage.java @@ -101,6 +101,20 @@ public void createFieldEditors() { showWhatsNewField.getDescriptionControl(whatsNewComposite) .setToolTipText(Messages.preferences_page_enable_whats_new_tooltip); addField(showWhatsNewField); + + // Chat group + Group grpChat = new Group(parent, SWT.NONE); + grpChat.setLayout(gl); + gdf.applyTo(grpChat); + grpChat.setText(Messages.preferences_page_auto_scroll_group); + + Composite ctnAutoScroll = new Composite(grpChat, SWT.NONE); + ctnAutoScroll.setLayout(gl); + BooleanFieldEditor bfeAutoScroll = new BooleanFieldEditor(Constants.AUTO_SCROLL_TO_NEW_DIALOG, + Messages.preferences_page_auto_scroll_to_new_dialog, ctnAutoScroll); + bfeAutoScroll.getDescriptionControl(ctnAutoScroll) + .setToolTipText(Messages.preferences_page_auto_scroll_to_new_dialog_tooltip); + addField(bfeAutoScroll); } @Override diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java index 7582fd7a..450246d7 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java @@ -54,6 +54,10 @@ public class Messages extends NLS { public static String preferences_page_whats_new_settings; public static String preferences_page_enable_whats_new; public static String preferences_page_enable_whats_new_tooltip; + + public static String preferences_page_auto_scroll_group; + public static String preferences_page_auto_scroll_to_new_dialog; + public static String preferences_page_auto_scroll_to_new_dialog_tooltip; public static String preferences_page_github_enterprise; public static String preferences_page_watched_files; public static String preferences_page_watched_files_note_content; diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties index f041db59..ba090826 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties @@ -43,6 +43,9 @@ preferences_page_enable_strict_ssl= Enable Strict SSL preferences_page_whats_new_settings= What's New preferences_page_enable_whats_new= Always display the 'What's New' dialog after GitHub Copilot updates. preferences_page_enable_whats_new_tooltip= Enable this to automatically display the "What's New" page whenever GitHub Copilot is updated. +preferences_page_auto_scroll_group= Chat +preferences_page_auto_scroll_to_new_dialog= Automatically scroll to new confirmation dialogs/prompts in Chat View. +preferences_page_auto_scroll_to_new_dialog_tooltip= When enabled, the chat view will automatically scroll to show any confirmation dialogs or prompts. preferences_page_github_enterprise= GitHub Enterprise Authentication Endpoint preferences_page_mcp= Server Configurations preferences_page_proxy_config_link= Configure Proxy diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/SwtUtils.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/SwtUtils.java index 0a054490..73180b8c 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/SwtUtils.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/SwtUtils.java @@ -48,8 +48,30 @@ private SwtUtils() { } private static final String INLINE_ANNOTATION_COLOR_KEY = "org.eclipse.ui.editors.inlineAnnotationColor"; + private static final int DEFAULT_GHOST_TEXT_SCALE = 128; + /** + * Walks up the parent chain of the given control and returns the first ancestor that is an instance of the specified + * type, or {@code null} if none is found. + * + * @param the target type + * @param control the starting control (may be {@code null}) + * @param type the class to search for + * @return the first matching ancestor, or {@code null} + */ + @Nullable + public static T findParentOfType(Control control, Class type) { + Control current = control; + while (current != null) { + if (type.isInstance(current)) { + return type.cast(current); + } + current = current.getParent(); + } + return null; + } + /** * Invokes the given runnable on the display thread. */