From b6d045912be82102f66e8627ddd78ebe23230d4d Mon Sep 17 00:00:00 2001 From: Sebastian Thomschke Date: Wed, 3 Dec 2025 08:52:45 +0100 Subject: [PATCH 1/2] fix: reduce LSPTextHover resize flicker --- .../FocusableBrowserInformationControl.java | 3 +- .../lsp4e/operations/hover/LSPTextHover.java | 38 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java index 50c2b767e..314e0b40d 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java @@ -103,7 +103,7 @@ private void updateBrowserSize(final Browser browser) { setSize(hint.x, hint.y); if (!"complete".equals(safeEvaluate(browser, "return document.readyState"))) { //$NON-NLS-1$ //$NON-NLS-2$ - UI.getDisplay().timerExec(200, () -> updateBrowserSize(browser)); + UI.getDisplay().timerExec(50, () -> updateBrowserSize(browser)); return; } @@ -191,6 +191,7 @@ public void setInput(@Nullable Object input) { })); return; } + this.currentAsyncToken = null; if (input instanceof String html) { input = styleHtml(html); } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/LSPTextHover.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/LSPTextHover.java index 6b3567e3e..afa0209c4 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/LSPTextHover.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/LSPTextHover.java @@ -66,6 +66,8 @@ @SuppressWarnings("restriction") public class LSPTextHover implements ITextHover, ITextHoverExtension, ITextHoverExtension2 { + private static final int GET_LEGACY_HOVER_INFO_TIMEOUT_MS = 1000; + private static final int GET_ASYNC_HOVER_INFO_TIMEOUT_MS = 100; private static final int GET_HOVER_REGION_TIMEOUT_MS = 100; private @Nullable IRegion lastRegion; @@ -74,17 +76,17 @@ public class LSPTextHover implements ITextHover, ITextHoverExtension, ITextHover private @Nullable CompletableFuture<@Nullable String> hoverInfoFuture; @Override + @Deprecated public @Nullable String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) { - // Non-blocking: only return immediately available content. final var hoverInfoRequest_ = this.hoverInfoFuture = getHoverInfoFuture(textViewer, hoverRegion); - if (hoverInfoRequest_.isDone()) { - try { - return hoverInfoRequest_.getNow(null); - } catch (final Exception ex) { - if (CancellationUtil.isRequestCancelledException(ex)) { - // Hover was cancelled; ignore as this is an expected fast-mouse-move scenario. - return null; - } + + try { + return hoverInfoRequest_.get(GET_LEGACY_HOVER_INFO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + LanguageServerPlugin.logError(ex); + Thread.currentThread().interrupt(); + } catch (ExecutionException | TimeoutException ex) { + if (!CancellationUtil.isRequestCancelledException(ex)) { LanguageServerPlugin.logError(ex); } } @@ -94,8 +96,22 @@ public class LSPTextHover implements ITextHover, ITextHoverExtension, ITextHover @Override public @Nullable Object getHoverInfo2(ITextViewer textViewer, IRegion hoverRegion) { final var hoverInfoRequest_ = this.hoverInfoFuture = getHoverInfoFuture(textViewer, hoverRegion); - final String placeholder = "Loading…"; //$NON-NLS-1$ - return new AsyncHtmlHoverInput(hoverInfoRequest_, placeholder); + + try { + return hoverInfoRequest_.get(GET_ASYNC_HOVER_INFO_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + LanguageServerPlugin.logError(ex); + Thread.currentThread().interrupt(); + return null; + } catch (ExecutionException ex) { + if (!CancellationUtil.isRequestCancelledException(ex)) { + LanguageServerPlugin.logError(ex); + } + return null; + } catch (TimeoutException ex) { + final String placeholder = "Loading…"; //$NON-NLS-1$ + return new AsyncHtmlHoverInput(hoverInfoRequest_, placeholder); + } } public CompletableFuture<@Nullable String> getHoverInfoFuture(ITextViewer textViewer, IRegion hoverRegion) { From 94e637f2d4ce533b1b61b90280f82f498e3e15d1 Mon Sep 17 00:00:00 2001 From: Sebastian Thomschke Date: Wed, 3 Dec 2025 12:18:14 +0100 Subject: [PATCH 2/2] fix: harden FocusableBrowserInformationControl.updateBrowserSize --- .../FocusableBrowserInformationControl.java | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java index 314e0b40d..6dbc3df65 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/hover/FocusableBrowserInformationControl.java @@ -94,57 +94,79 @@ protected void createContent(Composite parent) { } private void updateBrowserSize(final Browser browser) { + updateBrowserSize(browser, 1); + } + + private void updateBrowserSize(final Browser browser, final int attempt) { + if (attempt > 100) // ~1s total + return; + if (getShell().isDisposed() || browser.isDisposed() || getInput() == null) return; - @Nullable - Point constraints = getSizeConstraints(); - Point hint = computeSizeHint(); + final @Nullable Point constraints = getSizeConstraints(); + final Point hint = computeSizeHint(); setSize(hint.x, hint.y); - if (!"complete".equals(safeEvaluate(browser, "return document.readyState"))) { //$NON-NLS-1$ //$NON-NLS-2$ - UI.getDisplay().timerExec(50, () -> updateBrowserSize(browser)); + final var docReadyState = safeEvaluate(browser, "return document.readyState"); //$NON-NLS-1$ + if (!"complete".equals(docReadyState)) { //$NON-NLS-1$ + retryUpdateBrowserSize(browser, attempt); + return; + } + + safeExecute(browser, "document.getElementsByTagName('html')[0].style.whiteSpace = 'nowrap'"); //$NON-NLS-1$ + final Object widthObj = safeEvaluate(browser, "var b = document.body; return b && b.scrollWidth"); //$NON-NLS-1$ + if (!(widthObj instanceof final Number widthValue)) { + retryUpdateBrowserSize(browser, attempt); + return; + } + double width = 20 + widthValue.doubleValue(); + setSize((int) width, hint.y); + + safeExecute(browser, "document.getElementsByTagName('html')[0].style.whiteSpace = 'normal'"); //$NON-NLS-1$ + final Object heightObj = safeEvaluate(browser, "var b = document.body; return b && b.scrollHeight"); //$NON-NLS-1$ + if (!(heightObj instanceof final Number heightValue)) { + retryUpdateBrowserSize(browser, attempt); return; } - safeExecute(browser, "document.getElementsByTagName(\"html\")[0].style.whiteSpace = \"nowrap\""); //$NON-NLS-1$ - Double width = 20 - + (safeEvaluate(browser, "return document.body.scrollWidth;") instanceof Double evaluated ? evaluated //$NON-NLS-1$ - : 0); - setSize(width.intValue(), hint.y); - - safeExecute(browser, "document.getElementsByTagName(\"html\")[0].style.whiteSpace = \"normal\""); //$NON-NLS-1$ - Double height = safeEvaluate(browser, "return document.body.scrollHeight;") instanceof Double evaluated //$NON-NLS-1$ - ? evaluated - : 0d; - Object marginTop = safeEvaluate(browser, "return window.getComputedStyle(document.body).marginTop;"); //$NON-NLS-1$ - Object marginBottom = safeEvaluate(browser, "return window.getComputedStyle(document.body).marginBottom;"); //$NON-NLS-1$ + double height = heightValue.doubleValue(); if (Platform.getPreferencesService().getBoolean(EditorsUI.PLUGIN_ID, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TEXT_HOVER_AFFORDANCE, true, null)) { - FontData[] fontDatas = JFaceResources.getDialogFont().getFontData(); + final FontData[] fontDatas = JFaceResources.getDialogFont().getFontData(); height += fontDatas[0].getHeight(); } width = width * 1.5; if (Util.isWin32()) { + Object marginTop = safeEvaluate(browser, + "var b = document.body; return b && window.getComputedStyle(b).marginTop"); //$NON-NLS-1$ + Object marginBottom = safeEvaluate(browser, + "var b = document.body; return b && window.getComputedStyle(b).marginBottom"); //$NON-NLS-1$ height = adjust(height, marginTop); height = adjust(height, marginBottom); } if (constraints != null && constraints.x < width) { - width = (double) constraints.x; + width = constraints.x; } if (constraints != null && constraints.y < height) { - height = (double) constraints.y; + height = constraints.y; } - setSize(width.intValue(), height.intValue()); + setSize((int) width, (int) height); + } + + private void retryUpdateBrowserSize(final Browser browser, final int currentAttempt) { + UI.getDisplay().timerExec(10, () -> updateBrowserSize(browser, currentAttempt + 1)); } private static @Nullable Object safeEvaluate(Browser browser, String expression) { try { return browser.evaluate(expression); } catch (Exception ex) { - LanguageServerPlugin.logError(ex); + if (LanguageServerPlugin.DEBUG || LanguageServerPlugin.isLogTraceEnabled()) { + LanguageServerPlugin.logError("Failed to evaluate browser expression: " + expression, ex); //$NON-NLS-1$ + } } return null; } @@ -153,7 +175,9 @@ private static boolean safeExecute(Browser browser, String expression) { try { return browser.execute(expression); } catch (Exception ex) { - LanguageServerPlugin.logError(ex); + if (LanguageServerPlugin.DEBUG || LanguageServerPlugin.isLogTraceEnabled()) { + LanguageServerPlugin.logError("Failed to execute browser expression: " + expression, ex); //$NON-NLS-1$ + } } return false; }