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. */