From 82643d2fb48f48de42855fda9325fecc2b268a8f Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Sat, 23 May 2026 19:54:29 +0545 Subject: [PATCH 1/2] fix: refresh scroller layout when tool confirmation dialog is cancelled When the user cancels a subagent tool confirmation dialog, the subagent panel stays expanded at its pre-cancel height. This happens because cancelConfirmation() only called parent.layout() (relaying the turn widget's children), but never updated the ScrolledComposite's minimum size via refreshScrollerLayout(). On the Continue path this goes unnoticed because subsequent subagent messages trigger a layout refresh; on the Cancel path no further messages arrive, so the panel is stuck. Fix by passing a Runnable callback from BaseTurnWidget into the dialog at construction time. BaseTurnWidget walks up the ancestor chain once to find ChatContentViewer and captures refreshScrollerLayout() as the callback. cancelConfirmation() calls it after disposal, which recomputes setMinHeight() in a single layout pass and collapses the panel correctly. This also removes the redundant parent.layout() call that preceded the deep layout(true,true) inside refreshScrollerLayout(). Fixes #169 --- .../copilot/eclipse/ui/chat/BaseTurnWidget.java | 12 +++++++++++- .../ui/chat/InvokeToolConfirmationDialog.java | 9 ++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) 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 d8a5221a..e17b3cea 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 @@ -649,7 +649,17 @@ public CompletableFuture requestToolExecuti ConfirmationContent content, Object input) { reset(); - this.confirmDialog = new InvokeToolConfirmationDialog(this, content, input); + Runnable refreshLayout = null; + Composite ancestor = this.getParent(); + while (ancestor != null && !ancestor.isDisposed()) { + if (ancestor instanceof ChatContentViewer) { + final ChatContentViewer viewer = (ChatContentViewer) ancestor; + refreshLayout = viewer::refreshScrollerLayout; + break; + } + ancestor = ancestor.getParent(); + } + this.confirmDialog = new InvokeToolConfirmationDialog(this, content, input, refreshLayout); CompletableFuture toolConfirmationFuture = this.confirmDialog .getConfirmationFuture(); diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java index ebc307a0..e27c8e1e 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java @@ -68,6 +68,7 @@ public class InvokeToolConfirmationDialog extends Composite { private Runnable titleFontChangeCallback; private ConfirmationContent confirmationContent; private ConfirmationAction selectedAction; + private final Runnable onCancelCallback; /** * Create a new confirmation dialog driven by {@link ConfirmationContent}. @@ -75,12 +76,14 @@ public class InvokeToolConfirmationDialog extends Composite { * @param parent the parent composite * @param content confirmation content with title, message, and action buttons * @param input the input object to pass to the tool + * @param onCancelCallback invoked after the dialog is disposed on cancellation, for layout refresh */ public InvokeToolConfirmationDialog(Composite parent, - ConfirmationContent content, Object input) { + ConfirmationContent content, Object input, Runnable onCancelCallback) { super(parent, SWT.BORDER | SWT.WRAP); this.setLayout(new GridLayout(1, false)); this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + this.onCancelCallback = onCancelCallback; this.confirmationContent = content; createDialogContent(content.getTitle(), content.getMessage(), input); @@ -120,8 +123,8 @@ public void cancelConfirmation() { new AgentToolCancelLabel(parent, SWT.NONE, this.cancelMessage); } this.dispose(); - if (parent != null && !parent.isDisposed()) { - parent.requestLayout(); + if (onCancelCallback != null) { + onCancelCallback.run(); } }, this); } From f2c2a90ae54079d00f6e5c3cf2e6e9f8b62c824d Mon Sep 17 00:00:00 2001 From: rsd-darshan Date: Mon, 25 May 2026 17:20:31 +0545 Subject: [PATCH 2/2] fix: refresh scroller layout when tool confirmation dialog is cancelled When the user cancels a subagent tool confirmation dialog, the subagent panel stays expanded at its pre-cancel height. This happens because cancelConfirmation() only called parent.layout() (relaying the turn widget's children), but never updated the ScrolledComposite's minimum size via refreshScrollerLayout(). On the Continue path this goes unnoticed because subsequent subagent messages trigger a layout refresh; on the Cancel path no further messages arrive, so the panel is stuck. Fix by adding a dispose listener on the dialog in BaseTurnWidget that walks up to the nearest ChatContentViewer and calls requestRefreshScrollerLayout(), a new coalesced async helper that schedules a single refreshScrollerLayout() after pending SWT mutations settle. InvokeToolConfirmationDialog remains self-contained: both the accept and cancel paths use a private disposeAndRequestParentLayout() helper, keeping scroller-specific behaviour out of the dialog. Fixes #169 --- .../eclipse/ui/chat/BaseTurnWidget.java | 20 +++++++++---------- .../eclipse/ui/chat/ChatContentViewer.java | 8 ++++++++ .../ui/chat/InvokeToolConfirmationDialog.java | 14 ++++++------- 3 files changed, 24 insertions(+), 18 deletions(-) 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 e17b3cea..8890a95b 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 @@ -649,17 +649,17 @@ public CompletableFuture requestToolExecuti ConfirmationContent content, Object input) { reset(); - Runnable refreshLayout = null; - Composite ancestor = this.getParent(); - while (ancestor != null && !ancestor.isDisposed()) { - if (ancestor instanceof ChatContentViewer) { - final ChatContentViewer viewer = (ChatContentViewer) ancestor; - refreshLayout = viewer::refreshScrollerLayout; - break; + this.confirmDialog = new InvokeToolConfirmationDialog(this, content, input); + this.confirmDialog.addDisposeListener(e -> { + Composite ancestor = this.getParent(); + while (ancestor != null && !ancestor.isDisposed()) { + if (ancestor instanceof ChatContentViewer) { + ((ChatContentViewer) ancestor).requestRefreshScrollerLayout(); + break; + } + ancestor = ancestor.getParent(); } - ancestor = ancestor.getParent(); - } - this.confirmDialog = new InvokeToolConfirmationDialog(this, content, input, refreshLayout); + }); CompletableFuture toolConfirmationFuture = this.confirmDialog .getConfirmationFuture(); 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 58ee3f5d..7ef32077 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 @@ -404,6 +404,14 @@ public void renderErrorMessage(String errorMessage) { scrollToLatestUserTurn(); } + /** + * Schedules a single async {@link #refreshScrollerLayout()} call so that multiple dispose/layout + * events that arrive in the same event-loop tick are coalesced into one pass. + */ + public void requestRefreshScrollerLayout() { + SwtUtils.invokeOnDisplayThreadAsync(() -> refreshScrollerLayout(), this); + } + /** * Update the size of scrolled composite when there are content updates. */ diff --git a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java index e27c8e1e..1f65aead 100644 --- a/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java +++ b/com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/InvokeToolConfirmationDialog.java @@ -68,7 +68,6 @@ public class InvokeToolConfirmationDialog extends Composite { private Runnable titleFontChangeCallback; private ConfirmationContent confirmationContent; private ConfirmationAction selectedAction; - private final Runnable onCancelCallback; /** * Create a new confirmation dialog driven by {@link ConfirmationContent}. @@ -76,14 +75,12 @@ public class InvokeToolConfirmationDialog extends Composite { * @param parent the parent composite * @param content confirmation content with title, message, and action buttons * @param input the input object to pass to the tool - * @param onCancelCallback invoked after the dialog is disposed on cancellation, for layout refresh */ public InvokeToolConfirmationDialog(Composite parent, - ConfirmationContent content, Object input, Runnable onCancelCallback) { + ConfirmationContent content, Object input) { super(parent, SWT.BORDER | SWT.WRAP); this.setLayout(new GridLayout(1, false)); this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - this.onCancelCallback = onCancelCallback; this.confirmationContent = content; createDialogContent(content.getTitle(), content.getMessage(), input); @@ -122,10 +119,7 @@ public void cancelConfirmation() { && StringUtils.isNotEmpty(this.cancelMessage)) { new AgentToolCancelLabel(parent, SWT.NONE, this.cancelMessage); } - this.dispose(); - if (onCancelCallback != null) { - onCancelCallback.run(); - } + disposeAndRequestParentLayout(); }, this); } } @@ -321,6 +315,10 @@ private void acceptAndDispose(ConfirmationAction action) { new LanguageModelToolConfirmationResult( ToolConfirmationResult.ACCEPT)); + disposeAndRequestParentLayout(); + } + + private void disposeAndRequestParentLayout() { Composite parent = this.getParent(); this.dispose(); if (parent != null && !parent.isDisposed()) {