Skip to content

Commit 63b1303

Browse files
raghucssitjdneoCopilot
authored
In Agent mode: Auto scroll to prompts like 'Continue'. (#26)
* 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 * Fix Checkstyle violations in BaseTurnWidget - Wrap the WarnWidget construction line that exceeded 120 characters - Fix indentation of the return statement to match the enclosing block These were causing the CI build (Checkstyle) to fail on PR #26. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Height adjustment review fix. We can use the target control height directly. No need to calculate by hint. --------- Co-authored-by: Sheng Chen <sheche@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent de828cc commit 63b1303

3 files changed

Lines changed: 102 additions & 5 deletions

File tree

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/BaseTurnWidget.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.ui.PlatformUI;
2424
import org.osgi.service.event.EventHandler;
2525

26+
import com.microsoft.copilot.eclipse.core.Constants;
2627
import com.microsoft.copilot.eclipse.core.CopilotCore;
2728
import com.microsoft.copilot.eclipse.core.chat.ConfirmationContent;
2829
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
@@ -36,6 +37,7 @@
3637
import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.EditAgentRoundData;
3738
import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.ReplyData;
3839
import com.microsoft.copilot.eclipse.core.persistence.CopilotTurnData.ToolCallData;
40+
import com.microsoft.copilot.eclipse.ui.CopilotUi;
3941
import com.microsoft.copilot.eclipse.ui.chat.services.AvatarService;
4042
import com.microsoft.copilot.eclipse.ui.chat.services.ChatServiceManager;
4143
import com.microsoft.copilot.eclipse.ui.utils.SwtUtils;
@@ -604,16 +606,16 @@ protected void ensureFooterAtBottom() {
604606
* @param code the server error code
605607
* @param modelProviderName the BYOK model-provider name, or {@code null} for built-in models
606608
*/
607-
protected void createWarnDialog(String message, int code, String modelProviderName) {
609+
protected Composite createWarnDialog(String message, int code, String modelProviderName) {
608610
// TODO: Remove this legacy fallback after TBB is officially released.
609611
// When the language server has not enabled token-based billing yet, restore the original
610612
// main-branch warning behavior (no plan-driven actions; single upgrade button on the legacy
611613
// 30-day free trial message).
612614
if (!this.serviceManager.getAuthStatusManager().getQuotaStatus().tokenBasedBillingEnabled()) {
613-
new WarnWidget(this, SWT.BOTTOM, message, code);
615+
WarnWidget warnWidget = new WarnWidget(this, SWT.BOTTOM, message, code);
614616
ensureFooterAtBottom();
615617
requestLayout();
616-
return;
618+
return warnWidget;
617619
}
618620
boolean byokQuotaExceeded = QuotaActions.isByokQuotaExceeded(code, modelProviderName);
619621
String displayMessage = byokQuotaExceeded ? Messages.chat_warnWidget_byokQuotaUsageMessage : message;
@@ -627,9 +629,11 @@ protected void createWarnDialog(String message, int code, String modelProviderNa
627629
&& quotaStatus.premiumInteractions().overagePermitted();
628630
canUpgradePlan = quotaStatus.canUpgradePlan();
629631
}
630-
new WarnWidget(this, SWT.NONE, displayMessage, planForActions, overageEnabled, canUpgradePlan);
632+
WarnWidget warnWidget =
633+
new WarnWidget(this, SWT.NONE, displayMessage, planForActions, overageEnabled, canUpgradePlan);
631634
ensureFooterAtBottom();
632635
requestLayout();
636+
return warnWidget;
633637
}
634638

635639
/**
@@ -666,6 +670,19 @@ public CompletableFuture<LanguageModelToolConfirmationResult> requestToolExecuti
666670

667671
this.getParent().requestLayout();
668672

673+
// Ensure the chat content viewer scrolls to show the newly created confirmation
674+
// dialog. Walk up the composite hierarchy to find a ChatContentViewer
675+
// and request scrolling. Use async exec because layout needs to complete first.
676+
SwtUtils.invokeOnDisplayThreadAsync(() -> {
677+
ChatContentViewer viewer = SwtUtils.findParentOfType(this.getParent(), ChatContentViewer.class);
678+
if (viewer != null) {
679+
if (this.confirmDialog != null && !this.confirmDialog.isDisposed()) {
680+
viewer.showControl(this.confirmDialog);
681+
}
682+
}
683+
684+
}, this.getParent());
685+
669686
return toolConfirmationFuture;
670687
}
671688

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/ChatContentViewer.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -468,9 +468,17 @@ public BaseTurnWidget getTurnWidget(String turnId) {
468468
}
469469

470470
private void renderWarnMessageWithUpgradePlanButton(String errorMessage, int code, String modelProviderName) {
471-
latestTurnWidget.createWarnDialog(errorMessage, code, modelProviderName);
471+
Composite warnWidget = latestTurnWidget.createWarnDialog(errorMessage, code, modelProviderName);
472472
refreshLayoutFull();
473473
scrollToLatestUserTurn();
474+
// Ensure the chat content viewer scrolls to show the newly created warning banner. Walk up the composite hierarchy
475+
// to find a ChatContentViewer and request scrolling. Use async exec because layout needs to complete first.
476+
SwtUtils.invokeOnDisplayThreadAsync(() -> {
477+
if (warnWidget != null && !warnWidget.isDisposed()) {
478+
showControl(warnWidget);
479+
}
480+
481+
}, this.getParent());
474482
}
475483

476484
/**
@@ -483,6 +491,12 @@ public void renderErrorMessage(String errorMessage) {
483491
this.errorWidget = new ErrorWidget(cmpContent, SWT.BOTTOM, errorMessage);
484492
refreshLayoutFull();
485493
scrollToLatestUserTurn();
494+
// Ensure the chat content viewer scrolls to show the newly created error banner.
495+
SwtUtils.invokeOnDisplayThreadAsync(() -> {
496+
if (this.errorWidget != null && !this.errorWidget.isDisposed()) {
497+
this.showControl(this.errorWidget);
498+
}
499+
}, this.getParent());
486500
}
487501

488502
/**
@@ -770,6 +784,50 @@ private int topOf(Control target) {
770784
return running;
771785
}
772786

787+
/**
788+
* Scrolls the viewport to make {@code target} visible, equivalent to
789+
* {@link org.eclipse.swt.custom.ScrolledComposite#showControl(Control)}.
790+
*
791+
* <p>Walks up the widget tree to find the direct child of {@code cmpContent} that contains
792+
* {@code target}, computes the content-coordinate position of {@code target} by summing
793+
* the turn's {@link #topOf} value with the local y offsets down to {@code target}, then
794+
* adjusts {@link #scrollOffset} by the minimum amount needed to bring {@code target} fully
795+
* into the viewport.</p>
796+
*/
797+
public void showControl(Composite target) {
798+
if (target == null || target.isDisposed()) {
799+
return;
800+
}
801+
// Walk up to find the direct child of cmpContent that is the ancestor of target.
802+
Control ancestor = target;
803+
while (ancestor != null && ancestor.getParent() != cmpContent) {
804+
ancestor = ancestor.getParent();
805+
}
806+
if (ancestor == null || ancestor.getParent() != cmpContent) {
807+
return;
808+
}
809+
// Content-coordinate top of the enclosing turn widget.
810+
int ancestorTop = topOf(ancestor);
811+
// Accumulate the local y offset by walking from target up to (but not including) ancestor.
812+
int localY = 0;
813+
for (Control c = target; c != ancestor; c = c.getParent()) {
814+
localY += c.getLocation().y;
815+
}
816+
int targetTop = ancestorTop + localY;
817+
int targetBottom = targetTop + target.getSize().y;
818+
int viewport = getClientArea().height;
819+
// Scroll the minimum amount: down if target is below the visible area, up if above.
820+
int newOffset = scrollOffset;
821+
if (targetBottom > scrollOffset + viewport) {
822+
newOffset = targetBottom - viewport;
823+
}
824+
if (targetTop < newOffset) {
825+
newOffset = targetTop;
826+
}
827+
scrollOffset = clampOffset(newOffset);
828+
relayoutWindow();
829+
}
830+
773831
@Override
774832
public void dispose() {
775833
pendingEvents.clear();

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/utils/SwtUtils.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,30 @@ private SwtUtils() {
4848
}
4949

5050
private static final String INLINE_ANNOTATION_COLOR_KEY = "org.eclipse.ui.editors.inlineAnnotationColor";
51+
5152
private static final int DEFAULT_GHOST_TEXT_SCALE = 128;
5253

54+
/**
55+
* Walks up the parent chain of the given control and returns the first ancestor that is an instance of the specified
56+
* type, or {@code null} if none is found.
57+
*
58+
* @param <T> the target type
59+
* @param control the starting control (may be {@code null})
60+
* @param type the class to search for
61+
* @return the first matching ancestor, or {@code null}
62+
*/
63+
@Nullable
64+
public static <T> T findParentOfType(Control control, Class<T> type) {
65+
Control current = control;
66+
while (current != null) {
67+
if (type.isInstance(current)) {
68+
return type.cast(current);
69+
}
70+
current = current.getParent();
71+
}
72+
return null;
73+
}
74+
5375
/**
5476
* Invokes the given runnable on the display thread.
5577
*/

0 commit comments

Comments
 (0)