From 95c98c9aced0e2a37da3fb20a560b272f2d4ea20 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Fri, 12 Jun 2026 17:08:50 +0200 Subject: [PATCH] Coalesce Quick Access resize updates and skip no-op resizes QuickAccessContents armed a recompute on every shell resize during the popup's initial layout, and each recompute cancels the in-flight compute job. On Windows the native search field triggers a burst of initial layout resizes, so the compute job scheduled for freshly typed text was repeatedly cancelled and the table was never refreshed. This is the intermittent failure in QuickAccessDialogTest.testPreviousChoicesAvailableForExtension. A resize only changes the results by changing how many rows fit, so track the row count used for the last compute and skip the resize-triggered update when it is unchanged. Remaining resizes are coalesced with a trailing-edge debounce so a burst (for example scrollbar oscillation during initial layout) collapses into a single update once the layout settles, instead of cancelling the in-flight compute job repeatedly. Also fix an accidental ~83 minute timeout (TIMEOUT * 1000) in that test's dialog-init wait. Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/4009 --- .../quickaccess/QuickAccessContents.java | 46 +++++++++++++------ .../quickaccess/QuickAccessDialogTest.java | 2 +- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/quickaccess/QuickAccessContents.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/quickaccess/QuickAccessContents.java index 3886f6a4257..c7590d06739 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/quickaccess/QuickAccessContents.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/quickaccess/QuickAccessContents.java @@ -102,6 +102,9 @@ public abstract class QuickAccessContents { */ private static final String QUICK_ACCESS_COMMAND_ID = "org.eclipse.ui.window.quickAccess"; //$NON-NLS-1$ + /** Trailing-edge debounce window collapsing a burst of shell resizes. */ + private static final int RESIZE_DEBOUNCE_MS = 100; + protected Text filterText; private final QuickAccessProvider[] providers; @@ -123,7 +126,7 @@ public abstract class QuickAccessContents { private Color grayColor; private TextLayout textLayout; private boolean showAllMatches = false; - protected boolean resized = false; + private int lastComputedItemCount = -1; private TriggerSequence keySequence; private Job computeProposalsJob; @@ -164,6 +167,7 @@ public void updateProposals(String filter) { String computingMessage = NLS.bind(QuickAccessMessages.QuickaAcessContents_computeMatchingEntries, filter); int maxNumberOfItemsInTable = computeNumberOfItems(); + lastComputedItemCount = maxNumberOfItemsInTable; AtomicReference[]> entries = new AtomicReference<>(); final Job currentComputeEntriesJob = Job.create(computingMessage, theMonitor -> { entries.set( @@ -766,24 +770,36 @@ public Table createTable(Composite composite) { tableComposite = new Composite(composite, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite); table = new Table(tableComposite, SWT.SINGLE | SWT.FULL_SELECTION); + final Runnable resizeUpdate = () -> { + if (showAllMatches || table == null || table.isDisposed() || filterText == null + || filterText.isDisposed()) { + return; + } + // Skip when the layout settled back to the row count we already computed for, + // so an oscillating burst does not cancel the in-flight compute job. + if (computeNumberOfItems() == lastComputedItemCount) { + return; + } + if (Policy.DEBUG_QUICK_ACCESS) { + trace("Resize listener triggering proposals update"); //$NON-NLS-1$ + } + updateProposals(filterText.getText().toLowerCase()); + }; table.getShell().addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { - if (!showAllMatches) { - if (!resized) { - resized = true; - e.display.timerExec(100, () -> { - if (table != null && !table.isDisposed() && filterText != null - && !filterText.isDisposed()) { - if (Policy.DEBUG_QUICK_ACCESS) { - trace("Resize listener triggering proposals update"); //$NON-NLS-1$ - } - updateProposals(filterText.getText().toLowerCase()); - } - resized = false; - }); - } + if (showAllMatches || table == null || table.isDisposed() || filterText == null + || filterText.isDisposed()) { + return; + } + // A resize only matters when the number of visible rows changes. Coalesce a + // burst of resizes (e.g. scrollbar oscillation during initial layout) into a + // single trailing update, and skip it when the row count is unchanged. + if (computeNumberOfItems() == lastComputedItemCount) { + return; } + e.display.timerExec(-1, resizeUpdate); + e.display.timerExec(RESIZE_DEBOUNCE_MS, resizeUpdate); } }); diff --git a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/quickaccess/QuickAccessDialogTest.java b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/quickaccess/QuickAccessDialogTest.java index c9dd5d7b920..d51e07ab510 100644 --- a/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/quickaccess/QuickAccessDialogTest.java +++ b/tests/org.eclipse.ui.tests/Eclipse UI Tests/org/eclipse/ui/tests/quickaccess/QuickAccessDialogTest.java @@ -321,7 +321,7 @@ public void testPreviousChoicesAvailableForExtension() { * wait for the dialog initialization, to avoid race conditions later on in the test, see: * https://github.com/eclipse-platform/eclipse.platform.ui/issues/4009 */ - assertTrue(DisplayHelper.waitForCondition(text.getDisplay(), TIMEOUT * 1000, + assertTrue(DisplayHelper.waitForCondition(text.getDisplay(), TIMEOUT, () -> dialog.infoText != null), "Unexpected dialog info: " + dialog.infoText); text.setText(TestQuickAccessComputer.TEST_QUICK_ACCESS_PROPOSAL_LABEL);