From 12c4eba09b6d7a8383a7797fd2f3f04d3bd07359 Mon Sep 17 00:00:00 2001 From: Elsa Zacharia Date: Thu, 26 Jun 2025 01:13:51 +0530 Subject: [PATCH 01/21] This PR removes the only remaining WS_CARBON check from the codebase. The Carbon backend is no longer used or supported in SWT, as all modern macOS environments use the Cocoa backend (WS_COCOA). This check is now obsolete and can be removed. --- .../src/org/eclipse/jface/util/Util.java | 2 +- .../org/eclipse/ui/internal/keys/BindingPersistence.java | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/util/Util.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/util/Util.java index 86f6c008c25..55d49b689e9 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/util/Util.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/util/Util.java @@ -560,7 +560,7 @@ public static boolean isWindows() { */ public static boolean isMac() { final String ws = SWT.getPlatform(); - return WS_CARBON.equals(ws) || WS_COCOA.equals(ws); + return WS_COCOA.equals(ws); } /** diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/keys/BindingPersistence.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/keys/BindingPersistence.java index f7afca45bcb..55675dcbbd1 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/keys/BindingPersistence.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/keys/BindingPersistence.java @@ -519,7 +519,7 @@ private static final void readBindingsFromPreferences(final IMemento preferences * @param commandService The command service for the workbench; must * not be null. */ - @SuppressWarnings("removal") + // @SuppressWarnings("removal") private static final void readBindingsFromRegistry(final IConfigurationElement[] configurationElements, final int configurationElementCount, final BindingManager bindingManager, final CommandManager commandService) { @@ -608,12 +608,6 @@ private static final void readBindingsFromRegistry(final IConfigurationElement[] if (Util.WS_COCOA.equals(platform)) { cocoaTempList.add(binding); - } else if (Util.WS_CARBON.equals(platform)) { - bindings.add(binding); - // temp work around ... simply honour the carbon - // bindings for cocoa. - cocoaTempList.add(new KeyBinding(keySequence, parameterizedCommand, schemeId, contextId, locale, - Util.WS_COCOA, null, Binding.SYSTEM)); } else { bindings.add(binding); } From d56188fe8a251bf21f4d2b21e1cf5f8b8c929caa Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Tue, 1 Jul 2025 17:25:05 +0200 Subject: [PATCH 02/21] Paint Whole FormText to Avoid Rendering Artifacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit updates the painting logic of FormText to render the entire content every time, rather than in segments. Painting in parts—particularly during scrolling—can introduce precision loss when using fractional (e.g., 1.25x, 1.75x) scaling factors. This may result in skipped pixels and visual glitches in the UI. Fixes: https://github.com/eclipse-platform/eclipse.platform.ui/issues/2997 --- .../src/org/eclipse/ui/forms/widgets/FormText.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java index 154344d859b..48edf36d91c 100644 --- a/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java +++ b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java @@ -1554,7 +1554,8 @@ private void paint(PaintEvent e) { ensureBoldFontPresent(getFont()); gc.setForeground(getForeground()); gc.setBackground(getBackground()); - repaint(gc, e.x, e.y, e.width, e.height); + Point size = getSize(); + repaint(gc, 0, 0, size.x, size.y); } private void repaint(GC gc, int x, int y, int width, int height) { From d3faeb4d29979e087a32f7567523cbca32aa8ba2 Mon Sep 17 00:00:00 2001 From: Amartya Parijat Date: Wed, 2 Jul 2025 11:18:31 +0200 Subject: [PATCH 03/21] Version bump(s) for 4.37 stream --- bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF index 0f0fa404973..49b3629e275 100644 --- a/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.forms/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %name Bundle-SymbolicName: org.eclipse.ui.forms;singleton:=true -Bundle-Version: 3.13.500.qualifier +Bundle-Version: 3.13.600.qualifier Bundle-Vendor: %provider-name Bundle-Localization: plugin Export-Package: org.eclipse.ui.forms, From 55a6cf168eaa745f6d026fe94b6296c9d5e12873 Mon Sep 17 00:00:00 2001 From: Shahzaib Ibrahim Date: Wed, 28 May 2025 17:50:56 +0200 Subject: [PATCH 04/21] Refactor Image(Display, int, int) in Tests Replacing Image(Display, int, int) with Image(Display, ImageGcDrawer, int, int) in Tests --- .../jface/tests/internal/databinding/swt/Screenshots.java | 2 +- .../eclipse/jface/tests/labelProviders/LabelProviderTest.java | 4 ++-- .../org/eclipse/jface/tests/widgets/AbstractFactoryTest.java | 4 +++- .../eclipse/jface/text/tests/codemining/CodeMiningTest.java | 4 ++-- .../tests/source/inlined/LineContentBoundsDrawingTest.java | 2 +- .../eclipse/jface/text/tests/codemining/CodeMiningTest.java | 4 ++-- .../forms/org/eclipse/ui/tests/forms/util/FormImagesTest.java | 2 +- .../eclipse/ui/workbench/texteditor/tests/ScreenshotTest.java | 2 +- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/internal/databinding/swt/Screenshots.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/internal/databinding/swt/Screenshots.java index d08947505a9..90ebb6b7f48 100644 --- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/internal/databinding/swt/Screenshots.java +++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/internal/databinding/swt/Screenshots.java @@ -89,7 +89,7 @@ public static String takeScreenshot(Class testClass, String name, PrintStream GC gc = new GC(display); Rectangle displayBounds= display.getBounds(); out.println("Display @ " + displayBounds); - final Image image = new Image(display, displayBounds.width, displayBounds.height); + final Image image = new Image(display, (iGc, width, height) -> {}, displayBounds.width, displayBounds.height); gc.copyArea(image, 0, 0); gc.dispose(); diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/labelProviders/LabelProviderTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/labelProviders/LabelProviderTest.java index dc2e74db1e1..49b6ce965b9 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/labelProviders/LabelProviderTest.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/labelProviders/LabelProviderTest.java @@ -15,8 +15,8 @@ public class LabelProviderTest { private static final Car HORCH = new Car("Horch"); - private static Image horchImage = new Image(Display.getDefault(), 50, 10); - private static Image defaultImage = new Image(Display.getDefault(), 1, 1); + private static Image horchImage = new Image(Display.getDefault(), (gc, width, height) -> {}, 50, 10); + private static Image defaultImage = new Image(Display.getDefault(), (gc, width, height) -> {}, 1, 1); private final Function textFunction = o -> o instanceof Car ? ((Car) o).getMake() : "unknown"; private final Function imageFunction = o -> o instanceof Car ? horchImage : defaultImage; diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/widgets/AbstractFactoryTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/widgets/AbstractFactoryTest.java index 6217a391e87..e8f873c12e1 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/widgets/AbstractFactoryTest.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/widgets/AbstractFactoryTest.java @@ -14,6 +14,7 @@ package org.eclipse.jface.tests.widgets; import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageGcDrawer; import org.eclipse.swt.widgets.Shell; import org.junit.After; import org.junit.AfterClass; @@ -26,7 +27,8 @@ public class AbstractFactoryTest { @BeforeClass public static void classSetup() { - image = new Image(null, 1, 1); + final ImageGcDrawer noOp = (gc, width, height) -> {}; + image = new Image(null, noOp, 1, 1); } @Before diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java index 2d79f9a176c..e512180a3b3 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java @@ -515,7 +515,7 @@ private static boolean hasCodeMiningPrintedBelowLine(ITextViewer viewer, int lin starty= lineBounds.y; } - Image image= new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y); + Image image= new Image(widget.getDisplay(), (gc, width, height) -> {}, widget.getSize().x, widget.getSize().y); try { GC gc= new GC(widget); gc.copyArea(image, 0, 0); @@ -555,7 +555,7 @@ private static boolean hasCodeMiningPrintedAfterTextOnLine(ITextViewer viewer, i } else { secondLineBounds= widget.getTextBounds(lineOffset, lineOffset + lineLength); } - Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y); + Image image = new Image(widget.getDisplay(), (gc, width, height) -> {}, widget.getSize().x, widget.getSize().y); GC gc = new GC(widget); gc.copyArea(image, 0, 0); gc.dispose(); diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java index 46708f74109..818deb75cfd 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/source/inlined/LineContentBoundsDrawingTest.java @@ -148,7 +148,7 @@ protected boolean condition() { } public int getMostRightPaintedPixel(StyledText widget) { - Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y); + Image image = new Image(widget.getDisplay(), (gc, width, height) -> {}, widget.getSize().x, widget.getSize().y); GC gc = new GC(widget); gc.copyArea(image, 0, 0); gc.dispose(); diff --git a/tests/org.eclipse.ui.editors.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java b/tests/org.eclipse.ui.editors.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java index f20ccbb2d38..9abe1e3a6b5 100644 --- a/tests/org.eclipse.ui.editors.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java +++ b/tests/org.eclipse.ui.editors.tests/src/org/eclipse/jface/text/tests/codemining/CodeMiningTest.java @@ -273,7 +273,7 @@ private static boolean existsPixelWithNonBackgroundColorAtLine(ITextViewer viewe int lineOffset = document.getLineOffset(line); Rectangle lineBounds = widget.getTextBounds(lineOffset, lineOffset + lineLength); String lineStr = document.get(lineOffset, lineLength); - Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y); + Image image = new Image(widget.getDisplay(), (gc, width, height) -> {}, widget.getSize().x, widget.getSize().y); try { GC gc = new GC(widget); gc.copyArea(image, 0, 0); @@ -306,7 +306,7 @@ private static boolean existsPixelWithNonBackgroundColorAtEndOfLine(ITextViewer String lineStr = document.get(lineOffset, document.getLineLength(line) - document.getLineDelimiter(line).length()); Rectangle lineBounds = widget.getTextBounds(lineOffset, lineOffset); - Image image = new Image(widget.getDisplay(), widget.getSize().x, widget.getSize().y); + Image image = new Image(widget.getDisplay(), (gc, width, height) -> {}, widget.getSize().x, widget.getSize().y); try { GC gc = new GC(widget); gc.copyArea(image, 0, 0); diff --git a/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FormImagesTest.java b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FormImagesTest.java index d9b1184ee14..0bae04287fe 100755 --- a/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FormImagesTest.java +++ b/tests/org.eclipse.ui.tests.forms/forms/org/eclipse/ui/tests/forms/util/FormImagesTest.java @@ -281,7 +281,7 @@ public void testToolkitColors() throws Exception { @Test public void testDisposeUnknown() throws Exception { Display display = Display.getCurrent(); - Image image = new Image(display, 10, 10); + Image image = new Image(display, (gc, width, height) -> {}, 10, 10); getFormImagesInstance().markFinished(image, display); assertTrue("markFinished(...) did not dispose of an unknown image", image.isDisposed()); assertNull("descriptors map", getDescriptors(getFormImagesInstance())); diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/ScreenshotTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/ScreenshotTest.java index baecc53cbf8..793ed327921 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/ScreenshotTest.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/ScreenshotTest.java @@ -117,7 +117,7 @@ public static String takeScreenshot(Class testClass, String name, PrintStream GC gc = new GC(display); Rectangle displayBounds= display.getBounds(); out.println("Display @ " + displayBounds); - final Image image= new Image(display, displayBounds.width, displayBounds.height); + final Image image= new Image(display, (iGc, width, height) -> {}, displayBounds.width, displayBounds.height); gc.copyArea(image, 0, 0); gc.dispose(); From 300360ebac04b2deeb8492f9db93c628c2d13fe4 Mon Sep 17 00:00:00 2001 From: Eclipse Platform Bot Date: Thu, 5 Jun 2025 12:36:00 +0000 Subject: [PATCH 05/21] Version bump(s) for 4.37 stream --- tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF | 2 +- tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF | 2 +- tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF index 15d33aa01f3..fffe968e1e8 100644 --- a/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.jface.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.jface.tests -Bundle-Version: 1.4.900.qualifier +Bundle-Version: 1.4.1000.qualifier Automatic-Module-Name: org.eclipse.jface.tests Bundle-RequiredExecutionEnvironment: JavaSE-17 Require-Bundle: org.junit;bundle-version="4.12.0", diff --git a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF index 741ef7fdb93..f5bed800eee 100644 --- a/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.jface.text.tests -Bundle-Version: 3.13.900.qualifier +Bundle-Version: 3.13.1000.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: diff --git a/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF index 38972be7663..944d1b2b124 100755 --- a/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.tests.forms/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Forms Test Bundle-SymbolicName: org.eclipse.ui.tests.forms;singleton:=true -Bundle-Version: 3.10.400.qualifier +Bundle-Version: 3.10.500.qualifier Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.test.performance, diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF index a485e9e52b0..1af7fc573ed 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Plugin.name Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor.tests -Bundle-Version: 3.14.800.qualifier +Bundle-Version: 3.14.900.qualifier Bundle-Vendor: %Plugin.providerName Bundle-Localization: plugin Export-Package: From a56fbc4f25df5811a9f124f37a2b35b0265b8b7a Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Thu, 3 Jul 2025 14:35:35 +0200 Subject: [PATCH 06/21] Avoid IllegalArgumentException if the line number mapping fails See https://github.com/eclipse-platform/eclipse.platform.ui/issues/3080 See also https://github.com/eclipse-jdt/eclipse.jdt.ui/pull/2316 --- .../ui/texteditor/stickyscroll/StickyLine.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java index 6d0ebdbf4e7..5100417fafd 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java @@ -57,16 +57,19 @@ public StyleRange[] getStyleRanges() { StyledText textWidget = sourceViewer.getTextWidget(); int widgetLineNumber = getWidgetLineNumber(); - if (widgetLineNumber >= textWidget.getLineCount()) { + if (widgetLineNumber < 0 || widgetLineNumber >= textWidget.getLineCount()) { return null; } - - int offsetAtLine = textWidget.getOffsetAtLine(getWidgetLineNumber()); - StyleRange[] styleRanges = textWidget.getStyleRanges(offsetAtLine, getText().length()); - for (StyleRange styleRange : styleRanges) { - styleRange.start = styleRange.start - offsetAtLine; + try { + int offsetAtLine = textWidget.getOffsetAtLine(widgetLineNumber); + StyleRange[] styleRanges = textWidget.getStyleRanges(offsetAtLine, getText().length()); + for (StyleRange styleRange : styleRanges) { + styleRange.start = styleRange.start - offsetAtLine; + } + return styleRanges; + } catch (IllegalArgumentException e) { + return null; // in case of an invalid line number, return null } - return styleRanges; } private int getWidgetLineNumber() { From 9c3250bea2720362045ca5ae48d9fe4481cb2a49 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Thu, 3 Jul 2025 15:42:07 +0200 Subject: [PATCH 07/21] Use I-Builds to resolve platform CDT dependencies --- releng/org.eclipse.ui.releng/platformUiTools.p2f | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releng/org.eclipse.ui.releng/platformUiTools.p2f b/releng/org.eclipse.ui.releng/platformUiTools.p2f index c64d35de192..1a637cd75f0 100644 --- a/releng/org.eclipse.ui.releng/platformUiTools.p2f +++ b/releng/org.eclipse.ui.releng/platformUiTools.p2f @@ -129,7 +129,7 @@ - + From cd58a4d0d6e5d4cbdb7eae6cd3f98b4b422a1b1a Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Thu, 3 Jul 2025 17:00:27 +0200 Subject: [PATCH 08/21] StickyLine.getText() should not fail on not mapped widget lines Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/3085 --- .../texteditor/stickyscroll/StickyScrollingControl.java | 3 ++- .../org/eclipse/ui/texteditor/stickyscroll/StickyLine.java | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/texteditor/stickyscroll/StickyScrollingControl.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/texteditor/stickyscroll/StickyScrollingControl.java index ce44dfc2504..5d0d04beb2a 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/texteditor/stickyscroll/StickyScrollingControl.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/texteditor/stickyscroll/StickyScrollingControl.java @@ -236,7 +236,8 @@ private void styleStickyLines() { List stickyLinesStyleRanges= new ArrayList<>(); int stickyLineTextOffset= 0; - for (int i= 0; i < getNumberStickyLines(); i++) { + int stickyLinesCount = getNumberStickyLines(); + for (int i = 0; i < stickyLinesCount; i++) { IStickyLine stickyLine= stickyLines.get(i); StyleRange[] ranges= stickyLine.getStyleRanges(); if (ranges != null) { diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java index 5100417fafd..fa00944998b 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/stickyscroll/StickyLine.java @@ -47,7 +47,11 @@ public int getLineNumber() { public String getText() { if (text == null) { StyledText textWidget = sourceViewer.getTextWidget(); - text = textWidget.getLine(getWidgetLineNumber()); + int widgetLineNumber = getWidgetLineNumber(); + if (widgetLineNumber < 0 || widgetLineNumber >= textWidget.getLineCount()) { + return ""; // return empty string if line number is invalid //$NON-NLS-1$ + } + text = textWidget.getLine(widgetLineNumber); } return text; } From df4eca1c0cea404aa52a94bb57a1a7466f41f331 Mon Sep 17 00:00:00 2001 From: Andrey Loskutov Date: Mon, 7 Jul 2025 09:41:36 +0200 Subject: [PATCH 09/21] FormText.paint(): don't pass (0,0) to Image constructor Fixes https://github.com/eclipse-platform/eclipse.platform.ui/issues/3087 --- .../src/org/eclipse/ui/forms/widgets/FormText.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java index 48edf36d91c..0e2bde803f5 100644 --- a/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java +++ b/bundles/org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.java @@ -1555,7 +1555,14 @@ private void paint(PaintEvent e) { gc.setForeground(getForeground()); gc.setBackground(getBackground()); Point size = getSize(); - repaint(gc, 0, 0, size.x, size.y); + Rectangle paintBounds; + if (size.x == 0 && size.y == 0) { + // avoids crash on image creation with (0,0) image size + paintBounds = new Rectangle(e.x, e.y, e.width, e.height); + } else { + paintBounds = new Rectangle(0, 0, size.x, size.y); + } + repaint(gc, paintBounds.x, paintBounds.y, paintBounds.width, paintBounds.height); } private void repaint(GC gc, int x, int y, int width, int height) { From 312d606f51eba76df542eef57f0962a095fc9629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sat, 5 Jul 2025 07:24:10 +0200 Subject: [PATCH 10/21] Extract Reconciler Job Into an own class Currently the BackgroundThread is an inner class of the AbstractReconciler and is tightly coupled with it what makes it quite hard to understand the interaction between both and to properly update a reconciler on document changes or refactor the code. This is a first attempt to decouple the things by extracting the inner class in an own (package private) type to allow further refactoring operations and proper encapsulation. --- .../text/reconciler/AbstractReconciler.java | 212 +---------------- .../jface/text/reconciler/ReconcilerJob.java | 220 ++++++++++++++++++ 2 files changed, 228 insertions(+), 204 deletions(-) create mode 100644 bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java index 3895aed0df5..511dd77305c 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java @@ -15,9 +15,7 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.DocumentEvent; @@ -52,201 +50,6 @@ */ abstract public class AbstractReconciler implements IReconciler { - - /** - * Background thread for the reconciling activity. - */ - private class BackgroundThread extends Job { - - /** Has the reconciler been canceled. */ - private boolean fCanceled= false; - /** Has the reconciler been reset. */ - private boolean fReset= false; - /** Some changes need to be processed. */ - private boolean fIsDirty= false; - /** Is a reconciling strategy active. */ - private boolean fIsActive= false; - - private volatile boolean fIsAlive; - - private boolean started; - - /** - * Creates a new background thread. The thread - * runs with minimal priority. - * - * @param name the thread's name - */ - public BackgroundThread(String name) { - super(name); - setPriority(Job.DECORATE); - setSystem(true); - } - - /** - * Returns whether a reconciling strategy is active right now. - * - * @return true if a activity is active - */ - public boolean isActive() { - return fIsActive; - } - - /** - * Returns whether some changes need to be processed. - * - * @return true if changes wait to be processed - * @since 3.0 - */ - public synchronized boolean isDirty() { - return fIsDirty; - } - - /** - * Cancels the background thread. - */ - public void doCancel() { - fCanceled= true; - IProgressMonitor pm= fProgressMonitor; - if (pm != null) - pm.setCanceled(true); - synchronized (fDirtyRegionQueue) { - fDirtyRegionQueue.notifyAll(); - } - } - - /** - * Suspends the caller of this method until this background thread has - * emptied the dirty region queue. - */ - public void suspendCallerWhileDirty() { - AbstractReconciler.this.signalWaitForFinish(); - boolean isDirty; - do { - synchronized (fDirtyRegionQueue) { - isDirty= fDirtyRegionQueue.getSize() > 0; - if (isDirty) { - try { - fDirtyRegionQueue.wait(); - } catch (InterruptedException x) { - } - } - } - } while (isDirty); - } - - /** - * Reset the background thread as the text viewer has been changed, - */ - public void reset() { - - if (fDelay > 0) { - - synchronized (this) { - fIsDirty= true; - fReset= true; - } - synchronized (fDirtyRegionQueue) { - fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay); - } - - } else { - - synchronized (this) { - fIsDirty= true; - } - - synchronized (fDirtyRegionQueue) { - fDirtyRegionQueue.notifyAll(); - } - } - synchronized (this) { - started= false; - } - informNotFinished(); - reconcilerReset(); - } - - /** - * The background activity. Waits until there is something in the - * queue managing the changes that have been applied to the text viewer. - * Removes the first change from the queue and process it. - *

- * Calls {@link AbstractReconciler#initialProcess()} on entrance. - *

- */ - @Override - public IStatus run(IProgressMonitor monitor) { - fIsAlive= true; - delay(); - - if (fCanceled) - return Status.CANCEL_STATUS; - - initialProcess(); - - while (!fCanceled) { - - delay(); - - if (fCanceled) - break; - - if (!isDirty()) { - waitFinish= false; //signalWaitForFinish() was called but nothing todo - continue; - } - - synchronized (this) { - if (fReset) { - fReset= false; - continue; - } - } - - DirtyRegion r= null; - synchronized (fDirtyRegionQueue) { - r= fDirtyRegionQueue.removeNextDirtyRegion(); - } - - fIsActive= true; - - fProgressMonitor.setCanceled(false); - - process(r); - - synchronized (fDirtyRegionQueue) { - if (0 == fDirtyRegionQueue.getSize()) { - synchronized (this) { - fIsDirty= fProgressMonitor.isCanceled(); - } - fDirtyRegionQueue.notifyAll(); - } - } - - fIsActive= false; - } - fIsAlive= false; - return Status.OK_STATUS; - } - - public boolean isAlive() { - return fIsAlive; - } - - public synchronized void start() { - if (!started) { - started= true; - schedule(); - } - } - - @Override - public boolean belongsTo(Object family) { - return family == fViewer || AbstractReconciler.class == family; - } - } - /** * Internal document listener and text input listener. */ @@ -323,13 +126,14 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { } /** Queue to manage the changes applied to the text viewer. */ - private DirtyRegionQueue fDirtyRegionQueue; + DirtyRegionQueue fDirtyRegionQueue; /** The background thread. */ - private BackgroundThread fThread; + private ReconcilerJob fThread; /** Internal document and text input listener. */ private Listener fListener; + /** The background thread delay. */ - private int fDelay= 500; + int fDelay= 500; /** Signal that the the background thread should not delay. */ volatile boolean waitFinish; /** Are there incremental reconciling strategies? */ @@ -476,7 +280,7 @@ public void install(ITextViewer textViewer) { synchronized (this) { if (fThread != null) return; - fThread= new BackgroundThread(getClass().getName()); + fThread= new ReconcilerJob(getClass().getName(), this); } fDirtyRegionQueue= new DirtyRegionQueue(); @@ -510,7 +314,7 @@ public void uninstall() { synchronized (this) { // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 - BackgroundThread bt= fThread; + ReconcilerJob bt= fThread; fThread= null; bt.doCancel(); } @@ -579,7 +383,7 @@ public void signalWaitForFinish() { } } - private void informNotFinished() { + void informNotFinished() { waitFinish= false; aboutToWork(); } @@ -590,7 +394,7 @@ private void aboutToBeReconciledInternal() { } - private void delay() { + void delay() { synchronized (fDirtyRegionQueue) { if (waitFinish) { return; // do not delay when waiting; diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java new file mode 100644 index 00000000000..f9c7db1e4bd --- /dev/null +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java @@ -0,0 +1,220 @@ +/******************************************************************************* + * Copyright (c) 2000, 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Christoph Läubrich - extract into own class + *******************************************************************************/ +package org.eclipse.jface.text.reconciler; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Background thread for the reconciling activity. + */ +class ReconcilerJob extends Job { + + /** Has the reconciler been canceled. */ + private boolean fCanceled= false; + + /** Has the reconciler been reset. */ + private boolean fReset= false; + + /** Some changes need to be processed. */ + private boolean fIsDirty= false; + + /** Is a reconciling strategy active. */ + private boolean fIsActive= false; + + private volatile boolean fIsAlive; + + private boolean started; + + private AbstractReconciler fReconciler; + + /** + * Creates a new background thread. The thread runs with minimal priority. + * + * @param name the thread's name + */ + public ReconcilerJob(String name, AbstractReconciler reconciler) { + super(name); + fReconciler= reconciler; + setPriority(Job.DECORATE); + setSystem(true); + } + + /** + * Returns whether a reconciling strategy is active right now. + * + * @return true if a activity is active + */ + public boolean isActive() { + return fIsActive; + } + + /** + * Returns whether some changes need to be processed. + * + * @return true if changes wait to be processed + * @since 3.0 + */ + public synchronized boolean isDirty() { + return fIsDirty; + } + + /** + * Cancels the background thread. + */ + public void doCancel() { + fCanceled= true; + IProgressMonitor pm= fReconciler.getProgressMonitor(); + if (pm != null) + pm.setCanceled(true); + synchronized (fReconciler.fDirtyRegionQueue) { + fReconciler.fDirtyRegionQueue.notifyAll(); + } + } + + /** + * Suspends the caller of this method until this background thread has emptied the dirty region + * queue. + */ + public void suspendCallerWhileDirty() { + fReconciler.signalWaitForFinish(); + boolean isDirty; + do { + synchronized (fReconciler.fDirtyRegionQueue) { + isDirty= fReconciler.fDirtyRegionQueue.getSize() > 0; + if (isDirty) { + try { + fReconciler.fDirtyRegionQueue.wait(); + } catch (InterruptedException x) { + } + } + } + } while (isDirty); + } + + /** + * Reset the background thread as the text viewer has been changed, + */ + public void reset() { + + if (fReconciler.fDelay > 0) { + + synchronized (this) { + fIsDirty= true; + fReset= true; + } + synchronized (fReconciler.fDirtyRegionQueue) { + fReconciler.fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay); + } + + } else { + + synchronized (this) { + fIsDirty= true; + } + + synchronized (fReconciler.fDirtyRegionQueue) { + fReconciler.fDirtyRegionQueue.notifyAll(); + } + } + synchronized (this) { + started= false; + } + fReconciler.informNotFinished(); + fReconciler.reconcilerReset(); + } + + /** + * The background activity. Waits until there is something in the queue managing the changes + * that have been applied to the text viewer. Removes the first change from the queue and + * process it. + *

+ * Calls {@link AbstractReconciler#initialProcess()} on entrance. + *

+ */ + @Override + public IStatus run(IProgressMonitor monitor) { + fIsAlive= true; + fReconciler.delay(); + + if (fCanceled) + return Status.CANCEL_STATUS; + + fReconciler.initialProcess(); + + while (!fCanceled) { + + fReconciler.delay(); + + if (fCanceled) + break; + + if (!isDirty()) { + fReconciler.waitFinish= false; //signalWaitForFinish() was called but nothing todo + continue; + } + + synchronized (this) { + if (fReset) { + fReset= false; + continue; + } + } + + DirtyRegion r= null; + synchronized (fReconciler.fDirtyRegionQueue) { + r= fReconciler.fDirtyRegionQueue.removeNextDirtyRegion(); + } + + fIsActive= true; + + fReconciler.getProgressMonitor().setCanceled(false); + + fReconciler.process(r); + + synchronized (fReconciler.fDirtyRegionQueue) { + if (0 == fReconciler.fDirtyRegionQueue.getSize()) { + synchronized (this) { + fIsDirty= fReconciler.getProgressMonitor().isCanceled(); + } + fReconciler.fDirtyRegionQueue.notifyAll(); + } + } + + fIsActive= false; + } + fIsAlive= false; + return Status.OK_STATUS; + } + + public boolean isAlive() { + return fIsAlive; + } + + public synchronized void start() { + if (!started) { + started= true; + schedule(); + } + } + + @Override + public boolean belongsTo(Object family) { + return family == fReconciler.getTextViewer() || AbstractReconciler.class == family; + } + +} From 6359bc293fc8d78a2755ae58a87bc3cc13ed029e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Tue, 8 Jul 2025 06:43:58 +0200 Subject: [PATCH 11/21] Only start the reconciler thread with a job Currently the Job Framework is not designed for handling more than a few running jobs and certain actions are considerably delayed once a job is active for a longer time. Because of this, now only start the reconciler thread inside a job but perform the actual work in an own thread. Fix https://github.com/eclipse-jdt/eclipse.jdt.ui/issues/2323 --- .../text/reconciler/AbstractReconciler.java | 232 ++++++++++++++++-- .../jface/text/reconciler/ReconcilerJob.java | 220 ----------------- 2 files changed, 211 insertions(+), 241 deletions(-) delete mode 100644 bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java index 511dd77305c..e4ad7855bf6 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java @@ -16,6 +16,7 @@ import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.DocumentEvent; @@ -50,6 +51,203 @@ */ abstract public class AbstractReconciler implements IReconciler { + + /** + * Background thread for the reconciling activity. + */ + class BackgroundThread extends Thread { + + /** Has the reconciler been canceled. */ + private boolean fCanceled= false; + /** Has the reconciler been reset. */ + private boolean fReset= false; + /** Some changes need to be processed. */ + private boolean fIsDirty= false; + /** Is a reconciling strategy active. */ + private boolean fIsActive= false; + + private boolean fStarted; + + /** + * Creates a new background thread. The thread + * runs with minimal priority. + * + * @param name the thread's name + */ + public BackgroundThread(String name) { + super(name); + setPriority(Thread.MIN_PRIORITY); + setDaemon(true); + } + + /** + * Returns whether a reconciling strategy is active right now. + * + * @return true if a activity is active + */ + public boolean isActive() { + return fIsActive; + } + + /** + * Returns whether some changes need to be processed. + * + * @return true if changes wait to be processed + * @since 3.0 + */ + public synchronized boolean isDirty() { + return fIsDirty; + } + + /** + * Cancels the background thread. + */ + public void cancel() { + fCanceled= true; + IProgressMonitor pm= fProgressMonitor; + if (pm != null) + pm.setCanceled(true); + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); + } + } + + /** + * Suspends the caller of this method until this background thread has + * emptied the dirty region queue. + */ + public void suspendCallerWhileDirty() { + AbstractReconciler.this.signalWaitForFinish(); + boolean isDirty; + do { + synchronized (fDirtyRegionQueue) { + isDirty= fDirtyRegionQueue.getSize() > 0; + if (isDirty) { + try { + fDirtyRegionQueue.wait(); + } catch (InterruptedException x) { + } + } + } + } while (isDirty); + } + + /** + * Reset the background thread as the text viewer has been changed, + */ + public void reset() { + + if (fDelay > 0) { + + synchronized (this) { + fIsDirty= true; + fReset= true; + } + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay); + } + + } else { + + synchronized (this) { + fIsDirty= true; + } + + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); + } + } + + informNotFinished(); + reconcilerReset(); + } + + /** + * The background activity. Waits until there is something in the + * queue managing the changes that have been applied to the text viewer. + * Removes the first change from the queue and process it. + *

+ * Calls {@link AbstractReconciler#initialProcess()} on entrance. + *

+ */ + @Override + public void run() { + + delay(); + + if (fCanceled) + return; + + initialProcess(); + + while (!fCanceled) { + + delay(); + + if (fCanceled) + break; + + if (!isDirty()) { + waitFinish= false; //signalWaitForFinish() was called but nothing todo + continue; + } + + synchronized (this) { + if (fReset) { + fReset= false; + continue; + } + } + + DirtyRegion r= null; + synchronized (fDirtyRegionQueue) { + r= fDirtyRegionQueue.removeNextDirtyRegion(); + } + + fIsActive= true; + + fProgressMonitor.setCanceled(false); + + process(r); + + synchronized (fDirtyRegionQueue) { + if (0 == fDirtyRegionQueue.getSize()) { + synchronized (this) { + fIsDirty= fProgressMonitor.isCanceled(); + } + fDirtyRegionQueue.notifyAll(); + } + } + + fIsActive= false; + } + } + + public void startReconciling() { + if (!isAlive()) { + if (fStarted) { + return; + } + fStarted= true; + Job.createSystem("Delayed Reconciler startup", m -> { //$NON-NLS-1$ + try { + start(); + return Status.OK_STATUS; + } catch (IllegalThreadStateException e) { + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549 + // This is the only instance where the thread is started; since + // we checked that it is not alive, it must be dead already due + // to a run-time exception or error. Exit. + return Status.CANCEL_STATUS; + } + }).schedule(); + } else { + reset(); + } + + } + } + /** * Internal document listener and text input listener. */ @@ -63,7 +261,7 @@ public void documentAboutToBeChanged(DocumentEvent e) { public void documentChanged(DocumentEvent e) { if (fThread.isActive() || !fThread.isDirty() && fThread.isAlive()) { - if (!fIsAllowedToModifyDocument && isRunningInReconcilerThread()) + if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread) throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$ aboutToBeReconciledInternal(); } @@ -126,14 +324,13 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { } /** Queue to manage the changes applied to the text viewer. */ - DirtyRegionQueue fDirtyRegionQueue; + private DirtyRegionQueue fDirtyRegionQueue; /** The background thread. */ - private ReconcilerJob fThread; + private BackgroundThread fThread; /** Internal document and text input listener. */ private Listener fListener; - /** The background thread delay. */ - int fDelay= 500; + private int fDelay= 500; /** Signal that the the background thread should not delay. */ volatile boolean waitFinish; /** Are there incremental reconciling strategies? */ @@ -280,7 +477,7 @@ public void install(ITextViewer textViewer) { synchronized (this) { if (fThread != null) return; - fThread= new ReconcilerJob(getClass().getName(), this); + fThread= new BackgroundThread(getClass().getName()); } fDirtyRegionQueue= new DirtyRegionQueue(); @@ -314,9 +511,9 @@ public void uninstall() { synchronized (this) { // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 - ReconcilerJob bt= fThread; + BackgroundThread bt= fThread; fThread= null; - bt.doCancel(); + bt.cancel(); } } } @@ -383,7 +580,7 @@ public void signalWaitForFinish() { } } - void informNotFinished() { + private void informNotFinished() { waitFinish= false; aboutToWork(); } @@ -394,7 +591,7 @@ private void aboutToBeReconciledInternal() { } - void delay() { + private void delay() { synchronized (fDirtyRegionQueue) { if (waitFinish) { return; // do not delay when waiting; @@ -444,11 +641,7 @@ protected synchronized void startReconciling() { if (fThread == null) return; - if (!fThread.isAlive()) { - fThread.start(); - } else { - fThread.reset(); - } + fThread.startReconciling(); } /** @@ -464,10 +657,7 @@ protected void reconcilerReset() { * @return true if running in this reconciler's background thread * @since 3.4 */ - protected synchronized boolean isRunningInReconcilerThread() { - if (fThread == null) { - return false; - } - return Job.getJobManager().currentJob() == fThread; + protected boolean isRunningInReconcilerThread() { + return Thread.currentThread() == fThread; } -} \ No newline at end of file +} diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java deleted file mode 100644 index f9c7db1e4bd..00000000000 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/ReconcilerJob.java +++ /dev/null @@ -1,220 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2025 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - * Christoph Läubrich - extract into own class - *******************************************************************************/ -package org.eclipse.jface.text.reconciler; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; - -/** - * Background thread for the reconciling activity. - */ -class ReconcilerJob extends Job { - - /** Has the reconciler been canceled. */ - private boolean fCanceled= false; - - /** Has the reconciler been reset. */ - private boolean fReset= false; - - /** Some changes need to be processed. */ - private boolean fIsDirty= false; - - /** Is a reconciling strategy active. */ - private boolean fIsActive= false; - - private volatile boolean fIsAlive; - - private boolean started; - - private AbstractReconciler fReconciler; - - /** - * Creates a new background thread. The thread runs with minimal priority. - * - * @param name the thread's name - */ - public ReconcilerJob(String name, AbstractReconciler reconciler) { - super(name); - fReconciler= reconciler; - setPriority(Job.DECORATE); - setSystem(true); - } - - /** - * Returns whether a reconciling strategy is active right now. - * - * @return true if a activity is active - */ - public boolean isActive() { - return fIsActive; - } - - /** - * Returns whether some changes need to be processed. - * - * @return true if changes wait to be processed - * @since 3.0 - */ - public synchronized boolean isDirty() { - return fIsDirty; - } - - /** - * Cancels the background thread. - */ - public void doCancel() { - fCanceled= true; - IProgressMonitor pm= fReconciler.getProgressMonitor(); - if (pm != null) - pm.setCanceled(true); - synchronized (fReconciler.fDirtyRegionQueue) { - fReconciler.fDirtyRegionQueue.notifyAll(); - } - } - - /** - * Suspends the caller of this method until this background thread has emptied the dirty region - * queue. - */ - public void suspendCallerWhileDirty() { - fReconciler.signalWaitForFinish(); - boolean isDirty; - do { - synchronized (fReconciler.fDirtyRegionQueue) { - isDirty= fReconciler.fDirtyRegionQueue.getSize() > 0; - if (isDirty) { - try { - fReconciler.fDirtyRegionQueue.wait(); - } catch (InterruptedException x) { - } - } - } - } while (isDirty); - } - - /** - * Reset the background thread as the text viewer has been changed, - */ - public void reset() { - - if (fReconciler.fDelay > 0) { - - synchronized (this) { - fIsDirty= true; - fReset= true; - } - synchronized (fReconciler.fDirtyRegionQueue) { - fReconciler.fDirtyRegionQueue.notifyAll(); // wake up wait(fDelay); - } - - } else { - - synchronized (this) { - fIsDirty= true; - } - - synchronized (fReconciler.fDirtyRegionQueue) { - fReconciler.fDirtyRegionQueue.notifyAll(); - } - } - synchronized (this) { - started= false; - } - fReconciler.informNotFinished(); - fReconciler.reconcilerReset(); - } - - /** - * The background activity. Waits until there is something in the queue managing the changes - * that have been applied to the text viewer. Removes the first change from the queue and - * process it. - *

- * Calls {@link AbstractReconciler#initialProcess()} on entrance. - *

- */ - @Override - public IStatus run(IProgressMonitor monitor) { - fIsAlive= true; - fReconciler.delay(); - - if (fCanceled) - return Status.CANCEL_STATUS; - - fReconciler.initialProcess(); - - while (!fCanceled) { - - fReconciler.delay(); - - if (fCanceled) - break; - - if (!isDirty()) { - fReconciler.waitFinish= false; //signalWaitForFinish() was called but nothing todo - continue; - } - - synchronized (this) { - if (fReset) { - fReset= false; - continue; - } - } - - DirtyRegion r= null; - synchronized (fReconciler.fDirtyRegionQueue) { - r= fReconciler.fDirtyRegionQueue.removeNextDirtyRegion(); - } - - fIsActive= true; - - fReconciler.getProgressMonitor().setCanceled(false); - - fReconciler.process(r); - - synchronized (fReconciler.fDirtyRegionQueue) { - if (0 == fReconciler.fDirtyRegionQueue.getSize()) { - synchronized (this) { - fIsDirty= fReconciler.getProgressMonitor().isCanceled(); - } - fReconciler.fDirtyRegionQueue.notifyAll(); - } - } - - fIsActive= false; - } - fIsAlive= false; - return Status.OK_STATUS; - } - - public boolean isAlive() { - return fIsAlive; - } - - public synchronized void start() { - if (!started) { - started= true; - schedule(); - } - } - - @Override - public boolean belongsTo(Object family) { - return family == fReconciler.getTextViewer() || AbstractReconciler.class == family; - } - -} From eeaf7b05f0fe936899c0c8dc5156afd2927bb245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Tue, 8 Jul 2025 09:38:00 +0200 Subject: [PATCH 12/21] Perform initial work in the job Currently the first thing a reconciler after startup does is to sleep for a while then call initialProcess then start the processing loop (that sleeps again...). This now moves this initial work into the start job so we have a plain processing loop in the thread to make further improvements easier. --- .../text/reconciler/AbstractReconciler.java | 176 +++++++++--------- .../text/reconciler/DirtyRegionQueue.java | 4 + .../reconciler/AbstractReconcilerTest.java | 2 +- 3 files changed, 96 insertions(+), 86 deletions(-) diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java index e4ad7855bf6..19c8ab9ed26 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java @@ -55,29 +55,27 @@ abstract public class AbstractReconciler implements IReconciler { /** * Background thread for the reconciling activity. */ - class BackgroundThread extends Thread { + class BackgroundWorker implements Runnable { /** Has the reconciler been canceled. */ - private boolean fCanceled= false; + private boolean fCanceled; /** Has the reconciler been reset. */ - private boolean fReset= false; + private boolean fReset; /** Some changes need to be processed. */ - private boolean fIsDirty= false; + private boolean fIsDirty; /** Is a reconciling strategy active. */ - private boolean fIsActive= false; + private boolean fIsActive; private boolean fStarted; - /** - * Creates a new background thread. The thread - * runs with minimal priority. - * - * @param name the thread's name - */ - public BackgroundThread(String name) { - super(name); - setPriority(Thread.MIN_PRIORITY); - setDaemon(true); + private String fName; + + private boolean fIsAlive; + + private volatile Thread fThread; + + public BackgroundWorker(String name) { + fName= name; } /** @@ -166,80 +164,86 @@ public void reset() { * The background activity. Waits until there is something in the * queue managing the changes that have been applied to the text viewer. * Removes the first change from the queue and process it. - *

- * Calls {@link AbstractReconciler#initialProcess()} on entrance. - *

*/ @Override public void run() { + try { + while (!fCanceled) { - delay(); - - if (fCanceled) - return; - - initialProcess(); - - while (!fCanceled) { - - delay(); - - if (fCanceled) - break; + delay(); - if (!isDirty()) { - waitFinish= false; //signalWaitForFinish() was called but nothing todo - continue; - } + if (fCanceled) + break; - synchronized (this) { - if (fReset) { - fReset= false; + if (!isDirty()) { + waitFinish= false; //signalWaitForFinish() was called but nothing todo continue; } - } - DirtyRegion r= null; - synchronized (fDirtyRegionQueue) { - r= fDirtyRegionQueue.removeNextDirtyRegion(); - } + synchronized (this) { + if (fReset) { + fReset= false; + continue; + } + } + + DirtyRegion r= null; + synchronized (fDirtyRegionQueue) { + r= fDirtyRegionQueue.removeNextDirtyRegion(); + } - fIsActive= true; + fIsActive= true; - fProgressMonitor.setCanceled(false); + fProgressMonitor.setCanceled(false); - process(r); + process(r); - synchronized (fDirtyRegionQueue) { - if (0 == fDirtyRegionQueue.getSize()) { - synchronized (this) { - fIsDirty= fProgressMonitor.isCanceled(); + synchronized (fDirtyRegionQueue) { + if (fDirtyRegionQueue.isEmpty()) { + synchronized (this) { + fIsDirty= fProgressMonitor.isCanceled(); + } + fDirtyRegionQueue.notifyAll(); } - fDirtyRegionQueue.notifyAll(); } + fIsActive= false; } - - fIsActive= false; + } finally { + fIsAlive= false; } } + boolean isAlive() { + return fIsAlive; + } + + /** + * Star the reconciling if not running (and calls + * {@link AbstractReconciler#initialProcess()}) or {@link #reset()} otherwise. + */ public void startReconciling() { - if (!isAlive()) { - if (fStarted) { - return; - } + if (!fStarted) { + fIsAlive= true; fStarted= true; - Job.createSystem("Delayed Reconciler startup", m -> { //$NON-NLS-1$ - try { - start(); - return Status.OK_STATUS; - } catch (IllegalThreadStateException e) { - // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=40549 - // This is the only instance where the thread is started; since - // we checked that it is not alive, it must be dead already due - // to a run-time exception or error. Exit. + Job.createSystem("Delayed Reconciler startup for " + fName, m -> { //$NON-NLS-1$ + //Until we process some code from the job, the reconciler thread is the current thread + fThread= Thread.currentThread(); + delay(); + if (fCanceled) { + return Status.CANCEL_STATUS; + } + initialProcess(); + if (fCanceled) { return Status.CANCEL_STATUS; } + Thread thread= new Thread(this); + thread.setName(fName); + thread.setPriority(Thread.MIN_PRIORITY); + thread.setDaemon(true); + //we will no longer process any code here, so hand over to the worker thread. + fThread= thread; + thread.start(); + return Status.OK_STATUS; }).schedule(); } else { reset(); @@ -260,8 +264,8 @@ public void documentAboutToBeChanged(DocumentEvent e) { @Override public void documentChanged(DocumentEvent e) { - if (fThread.isActive() || !fThread.isDirty() && fThread.isAlive()) { - if (!fIsAllowedToModifyDocument && Thread.currentThread() == fThread) + if (fWorker.isActive() || !fWorker.isDirty() && fWorker.isAlive()) { + if (!fIsAllowedToModifyDocument && isRunningInReconcilerThread()) throw new UnsupportedOperationException("The reconciler thread is not allowed to modify the document"); //$NON-NLS-1$ aboutToBeReconciledInternal(); } @@ -270,13 +274,13 @@ public void documentChanged(DocumentEvent e) { * The second OR condition handles the case when the document * gets changed while still inside initialProcess(). */ - if (fThread.isActive() || fThread.isDirty() && fThread.isAlive()) + if (fWorker.isActive() || fWorker.isDirty() && fWorker.isAlive()) fProgressMonitor.setCanceled(true); if (fIsIncrementalReconciler) createDirtyRegion(e); - fThread.reset(); + fWorker.reset(); } @@ -292,11 +296,11 @@ public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput synchronized (fDirtyRegionQueue) { fDirtyRegionQueue.purgeQueue(); } - if (fDocument != null && fDocument.getLength() > 0 && fThread.isDirty() && fThread.isAlive()) { + if (fDocument != null && fDocument.getLength() > 0 && fWorker.isDirty() && fWorker.isAlive()) { DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), ""); //$NON-NLS-1$ createDirtyRegion(e); - fThread.reset(); - fThread.suspendCallerWhileDirty(); + fWorker.reset(); + fWorker.suspendCallerWhileDirty(); } } @@ -316,7 +320,7 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { fDocument.addDocumentListener(this); - if (!fThread.isDirty()) + if (!fWorker.isDirty()) aboutToBeReconciledInternal(); startReconciling(); @@ -326,7 +330,7 @@ public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { /** Queue to manage the changes applied to the text viewer. */ private DirtyRegionQueue fDirtyRegionQueue; /** The background thread. */ - private BackgroundThread fThread; + private BackgroundWorker fWorker; /** Internal document and text input listener. */ private Listener fListener; /** The background thread delay. */ @@ -475,9 +479,9 @@ public void install(ITextViewer textViewer) { fViewer= textViewer; synchronized (this) { - if (fThread != null) + if (fWorker != null) return; - fThread= new BackgroundThread(getClass().getName()); + fWorker= new BackgroundWorker(getClass().getName()); } fDirtyRegionQueue= new DirtyRegionQueue(); @@ -511,8 +515,8 @@ public void uninstall() { synchronized (this) { // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 - BackgroundThread bt= fThread; - fThread= null; + BackgroundWorker bt= fWorker; + fWorker= null; bt.cancel(); } } @@ -618,10 +622,10 @@ protected void forceReconciling() { if (fDocument != null) { - if (!fThread.isDirty()&& fThread.isAlive()) + if (!fWorker.isDirty()&& fWorker.isAlive()) aboutToBeReconciledInternal(); - if (fThread.isActive()) + if (fWorker.isActive()) fProgressMonitor.setCanceled(true); if (fIsIncrementalReconciler) { @@ -638,10 +642,10 @@ protected void forceReconciling() { * Clients may extend this method. */ protected synchronized void startReconciling() { - if (fThread == null) + if (fWorker == null) return; - fThread.startReconciling(); + fWorker.startReconciling(); } /** @@ -657,7 +661,9 @@ protected void reconcilerReset() { * @return true if running in this reconciler's background thread * @since 3.4 */ - protected boolean isRunningInReconcilerThread() { - return Thread.currentThread() == fThread; + protected synchronized boolean isRunningInReconcilerThread() { + if (fWorker == null) + return false; + return Thread.currentThread() == fWorker.fThread; } } diff --git a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java index c342fc77627..e8a53dfd266 100644 --- a/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java +++ b/bundles/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java @@ -85,6 +85,10 @@ public int getSize() { return fDirtyRegions.size(); } + public boolean isEmpty() { + return fDirtyRegions.isEmpty(); + } + /** * Throws away all entries in the queue. */ diff --git a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java index 9778c251510..7975edbfa6d 100644 --- a/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java +++ b/tests/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/reconciler/AbstractReconcilerTest.java @@ -190,7 +190,7 @@ public IReconcilingStrategy getReconcilingStrategy(String contentType) { fReconciler.install(fViewer); fAccessor= new Accessor(fReconciler, AbstractReconciler.class); - Object object= fAccessor.get("fThread"); + Object object= fAccessor.get("fWorker"); fAccessor= new Accessor(object, object.getClass()); } From 61f02f1c55fc5809ce46fa4834087347052c83cb Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Tue, 8 Jul 2025 14:13:54 -0400 Subject: [PATCH 13/21] Add scope changed notification to SearchDialog - add new IScopeChangedListener, IScopeChangeProvider, ScopeChangedEvent classes - have SearchDialog implement new IScopeChangeProvider - bump up minor version with new public interfaces - fixes #3096 --- .../org.eclipse.jface/META-INF/MANIFEST.MF | 2 +- .../jface/dialogs/IScopeChangeProvider.java | 38 +++++++++++ .../jface/dialogs/IScopeChangedListener.java | 31 +++++++++ .../jface/dialogs/ScopeChangedEvent.java | 68 +++++++++++++++++++ .../search/internal/ui/SearchDialog.java | 34 +++++++++- 5 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangeProvider.java create mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java create mode 100644 bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java diff --git a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF index d3502adcfbc..756eac4697e 100644 --- a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface;singleton:=true -Bundle-Version: 3.37.100.qualifier +Bundle-Version: 3.38.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: org.eclipse.jface, diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangeProvider.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangeProvider.java new file mode 100644 index 00000000000..cfd615ca36c --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangeProvider.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.dialogs; + +/** + * @since 3.38 + * + */ +public interface IScopeChangeProvider { + + /** + * Adds a listener for scope changes in this scope change provider. Has no + * effect if an identical listener is already registered. + * + * @param listener a scope changed listener + */ + void addScopeChangedListener(IScopeChangedListener listener); + + /** + * Removes the given scope change listener from this page change provider. Has + * no effect if an identical listener is not registered. + * + * @param listener a scope changed listener + */ + void removeScopeChangedListener(IScopeChangedListener listener); + +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java new file mode 100644 index 00000000000..83b4ffff162 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jface.dialogs; + +/** + * A listener which is notified when the scope for the search page is changed. + * + * @see IScopeChangeProvider + * @see ScopeChangedEvent + * + * @since 3.38 + */ +public interface IScopeChangedListener { + /** + * Notifies that the selected scope has changed. + * + * @param event event object describing the change + */ + public void scopeChanged(ScopeChangedEvent event); +} diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java new file mode 100644 index 00000000000..4a124689333 --- /dev/null +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + * Red Hat Inc. - copied and modified from PageChangedEvent.java + *******************************************************************************/ +package org.eclipse.jface.dialogs; + +import java.util.EventObject; + +/** + * Event object describing a page selection change. The source of these events + * is a page change provider. + * + * @see IPageChangeProvider + * @see IPageChangedListener + * + * @since 3.38 + */ +public class ScopeChangedEvent extends EventObject { + + private static final long serialVersionUID = -2652600407410991930L; + + /** + * The changed scope. + */ + protected int scope; + + /** + * Creates a new event for the given source and new scope. + * + * @param source the page change provider + * @param scope the new scope. In the JFace provided dialogs this will be an + * ISearchPageContainer constant. + */ + public ScopeChangedEvent(IPageChangeProvider source, + int scope) { + super(source); + this.scope = scope; + } + + /** + * Returns the new scope. + * + * @return the new scope. In dialogs implemented by JFace, this will be an + * ISearchPageContainer constant. + */ + public int getScope() { + return scope; + } + + /** + * Returns the scope change provider that is the source of this event. + * + * @return the originating scope change provider + */ + public IScopeChangeProvider getPageChangeProvider() { + return (IScopeChangeProvider) getSource(); + } +} diff --git a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java index f00afb61d4b..024399c0326 100644 --- a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java +++ b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -60,7 +60,10 @@ import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.dialogs.IPageChangeProvider; import org.eclipse.jface.dialogs.IPageChangedListener; +import org.eclipse.jface.dialogs.IScopeChangeProvider; +import org.eclipse.jface.dialogs.IScopeChangedListener; import org.eclipse.jface.dialogs.PageChangedEvent; +import org.eclipse.jface.dialogs.ScopeChangedEvent; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.SafeRunnable; @@ -91,7 +94,8 @@ import org.eclipse.search.ui.ISearchPageScoreComputer; -public class SearchDialog extends ExtendedDialogWindow implements ISearchPageContainer, IPageChangeProvider { +public class SearchDialog extends ExtendedDialogWindow + implements ISearchPageContainer, IPageChangeProvider, IScopeChangeProvider { // Dialog store id constants private static final String DIALOG_NAME= "SearchDialog"; //$NON-NLS-1$ @@ -150,6 +154,7 @@ protected void layout(Composite composite, boolean flushCache) { private Button fCustomizeButton; private Button fReplaceButton; private ListenerList fPageChangeListeners; + private ListenerList fScopeChangeListeners; private final IWorkbenchWindow fWorkbenchWindow; private final ISelection fCurrentSelection; @@ -706,6 +711,18 @@ public void setPerformActionEnabled(boolean state) { */ public void notifyScopeSelectionChanged() { setPerformActionEnabled(fLastEnableState); + if (fScopeChangeListeners != null && !fScopeChangeListeners.isEmpty()) { + // Fires the scope change event + final ScopeChangedEvent event = new ScopeChangedEvent(this, getSelectedScope()); + for (IScopeChangedListener l : fScopeChangeListeners) { + SafeRunner.run(new SafeRunnable() { + @Override + public void run() { + l.scopeChanged(event); + } + }); + } + } } private Control createPageControl(Composite parent, final SearchPageDescriptor descriptor) { @@ -828,4 +845,17 @@ public void run() { } } } + + @Override + public void addScopeChangedListener(IScopeChangedListener listener) { + if (fScopeChangeListeners == null) { + fScopeChangeListeners = new ListenerList<>(); + } + fScopeChangeListeners.add(listener); + } + + @Override + public void removeScopeChangedListener(IScopeChangedListener listener) { + fScopeChangeListeners.remove(listener); + } } From f330f3fd2fb8bfff0628bc2d197bf1a51ab05dc9 Mon Sep 17 00:00:00 2001 From: Eclipse Platform Bot Date: Tue, 8 Jul 2025 18:22:52 +0000 Subject: [PATCH 14/21] Version bump(s) for 4.37 stream --- bundles/org.eclipse.search/META-INF/MANIFEST.MF | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.search/META-INF/MANIFEST.MF b/bundles/org.eclipse.search/META-INF/MANIFEST.MF index fcfd9d845d8..001a2b22bfb 100644 --- a/bundles/org.eclipse.search/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.search/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.search; singleton:=true -Bundle-Version: 3.17.200.qualifier +Bundle-Version: 3.17.300.qualifier Bundle-Activator: org.eclipse.search.internal.ui.SearchPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName From da1671b4869fcc6b6246a7d20ead23bf5c631d95 Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Wed, 9 Jul 2025 16:29:52 -0400 Subject: [PATCH 15/21] Update bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java Co-authored-by: Federico Jeanne --- .../src/org/eclipse/jface/dialogs/IScopeChangedListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java index 83b4ffff162..d6748a32ca9 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/IScopeChangedListener.java @@ -27,5 +27,5 @@ public interface IScopeChangedListener { * * @param event event object describing the change */ - public void scopeChanged(ScopeChangedEvent event); + void scopeChanged(ScopeChangedEvent event); } From a2a5c0fe0d0d862d384bdfb7127d9645f828e900 Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Wed, 9 Jul 2025 16:30:10 -0400 Subject: [PATCH 16/21] Update bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java Co-authored-by: Patrick Ziegler --- .../search/org/eclipse/search/internal/ui/SearchDialog.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java index 024399c0326..ea90f35e8d3 100644 --- a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java +++ b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java @@ -856,6 +856,8 @@ public void addScopeChangedListener(IScopeChangedListener listener) { @Override public void removeScopeChangedListener(IScopeChangedListener listener) { - fScopeChangeListeners.remove(listener); + if (fScopeChangeListeners != null) { + fScopeChangeListeners.remove(listener); + } } } From c04e7718e99dd3cec6f1d24ac95e7cc2ee52756d Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Wed, 9 Jul 2025 16:33:36 -0400 Subject: [PATCH 17/21] Update bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java Co-authored-by: Federico Jeanne --- .../src/org/eclipse/jface/dialogs/ScopeChangedEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java index 4a124689333..c4326c18cdd 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/dialogs/ScopeChangedEvent.java @@ -32,7 +32,7 @@ public class ScopeChangedEvent extends EventObject { /** * The changed scope. */ - protected int scope; + private final int scope; /** * Creates a new event for the given source and new scope. From 51c70364320732bb6575810b437cfe66d60f2797 Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Wed, 9 Jul 2025 16:42:35 -0400 Subject: [PATCH 18/21] Use lambda to invoide scopeChanged method for listeners --- .../org/eclipse/search/internal/ui/SearchDialog.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java index ea90f35e8d3..3d2b82912dd 100644 --- a/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java +++ b/bundles/org.eclipse.search/search/org/eclipse/search/internal/ui/SearchDialog.java @@ -715,12 +715,7 @@ public void notifyScopeSelectionChanged() { // Fires the scope change event final ScopeChangedEvent event = new ScopeChangedEvent(this, getSelectedScope()); for (IScopeChangedListener l : fScopeChangeListeners) { - SafeRunner.run(new SafeRunnable() { - @Override - public void run() { - l.scopeChanged(event); - } - }); + SafeRunner.run(() -> l.scopeChanged(event)); } } } From 8ce465bc409ad5447f4b6daf3c29acac95e43b71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Thu, 17 Jul 2025 06:45:22 +0200 Subject: [PATCH 19/21] Do not fetch input eagerly if not required Currently CompatibilityEditor.createPart(WorkbenchPartReference) eagerly fetches the input just to then check if it is actually needed. This now pulls out the check for MultiEditor out to only fetch the input if there is a demand for it. --- .../ui/internal/e4/compatibility/CompatibilityEditor.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/e4/compatibility/CompatibilityEditor.java b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/e4/compatibility/CompatibilityEditor.java index b46d072e9b4..bc22035b669 100644 --- a/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/e4/compatibility/CompatibilityEditor.java +++ b/bundles/org.eclipse.ui.workbench/eclipseui/org/eclipse/ui/internal/e4/compatibility/CompatibilityEditor.java @@ -59,9 +59,11 @@ public class CompatibilityEditor extends CompatibilityPart { @Override IWorkbenchPart createPart(WorkbenchPartReference reference) throws PartInitException { IWorkbenchPart part = super.createPart(reference); - IEditorInput input = ((EditorReference) reference).getEditorInput(); - if (input instanceof MultiEditorInput && part instanceof MultiEditor) { - createMultiEditorChildren(part, input); + if (part instanceof MultiEditor) { + IEditorInput input = ((EditorReference) reference).getEditorInput(); + if (input instanceof MultiEditorInput) { + createMultiEditorChildren(part, input); + } } return part; } From f5f04e7e1dbeb14481cf0f76986373d973fe1df1 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Fri, 11 Jul 2025 17:16:15 +0200 Subject: [PATCH 20/21] Extend existence check in URLImageDescriptor when running without OSGi This extends the check added in cff785d2b2b9d514a25901f105a5a125c2c644aa to also consider the case when OSGi isn't running. Otherwise a path to a non-existent file is returned, thus breaking the contract specified in the JavaDoc. --- .../org.eclipse.jface/META-INF/MANIFEST.MF | 2 +- .../jface/resource/URLImageDescriptor.java | 30 ++++++------- .../tests/images/UrlImageDescriptorTest.java | 42 +++++++++++++------ 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF index 756eac4697e..13debb7c1bc 100644 --- a/bundles/org.eclipse.jface/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface/META-INF/MANIFEST.MF @@ -18,7 +18,7 @@ Export-Package: org.eclipse.jface, org.eclipse.jface.fieldassist, org.eclipse.jface.fieldassist.images, org.eclipse.jface.images, - org.eclipse.jface.internal;x-friends:="org.eclipse.ui.workbench,org.eclipse.e4.ui.workbench.renderers.swt", + org.eclipse.jface.internal;x-friends:="org.eclipse.ui.workbench,org.eclipse.e4.ui.workbench.renderers.swt,org.eclipse.jface.tests", org.eclipse.jface.internal.provisional.action;x-friends:="org.eclipse.ui.workbench,org.eclipse.ui.ide", org.eclipse.jface.layout, org.eclipse.jface.menus, diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java index 5b0368be105..d57d14844a1 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -31,7 +32,6 @@ import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Status; import org.eclipse.jface.internal.InternalPolicy; import org.eclipse.jface.util.Policy; @@ -246,24 +246,16 @@ private static String getxPath(String name, int zoom) { * * @return {@link String} or null if the file cannot be found */ - private static String getFilePath(URL url, boolean logIOException) { + private static String getFilePath(URL url, boolean logException) { try { if (!InternalPolicy.OSGI_AVAILABLE) { - if (FILE_PROTOCOL.equalsIgnoreCase(url.getProtocol())) - return IPath.fromOSString(url.getFile()).toOSString(); - return null; + return getFilePath(url); } url = resolvePathVariables(url); URL locatedURL = FileLocator.toFileURL(url); - if (FILE_PROTOCOL.equalsIgnoreCase(locatedURL.getProtocol())) { - String filePath = IPath.fromOSString(locatedURL.getPath()).toOSString(); - if (Files.exists(Path.of(filePath))) { - return filePath; - } - } - return null; - } catch (IOException e) { - if (logIOException) { + return getFilePath(locatedURL); + } catch (IOException | URISyntaxException e) { + if (logException) { Policy.logException(e); } else if (InternalPolicy.DEBUG_LOG_URL_IMAGE_DESCRIPTOR_MISSING_2x) { String path = url.getPath(); @@ -275,6 +267,16 @@ private static String getFilePath(URL url, boolean logIOException) { } } + private static String getFilePath(URL url) throws URISyntaxException { + if (FILE_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { + Path filePath = Path.of(url.toURI()); + if (Files.exists(filePath)) { + return filePath.toString(); + } + } + return null; + } + private static URL resolvePathVariables(URL url) { URL platformURL = FileLocator.find(url); // Resolve variables within URL's path if (platformURL != null) { diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java index c86db5d3a34..1e6cfb8a437 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java @@ -26,6 +26,7 @@ import org.eclipse.core.runtime.Adapters; import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.internal.InternalPolicy; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; @@ -122,20 +123,35 @@ public void testImageFileNameProviderGetxName() { @Test public void testImageFileNameProviderGetxName_forFileURL() throws IOException { - URL imageFileURL = tempFolder.newFile("image.png").toURI().toURL(); - tempFolder.newFile("image@2x.png"); - ImageDescriptor descriptor = ImageDescriptor.createFromURL(imageFileURL); + testImageFileNameProviderGetxName_forFileURL(true); + } - ImageFileNameProvider fileNameProvider = Adapters.adapt(descriptor, ImageFileNameProvider.class); - assertNotNull("URLImageDescriptor does not adapt to ImageFileNameProvider", fileNameProvider); - String imagePath100 = fileNameProvider.getImagePath(100); - assertNotNull("URLImageDescriptor ImageFileNameProvider does not return the 100% path", imagePath100); - assertEquals(IPath.fromOSString(imagePath100).lastSegment(), "image.png"); - String imagePath200 = fileNameProvider.getImagePath(200); - assertNotNull("URLImageDescriptor ImageFileNameProvider does not return the @2x path", imagePath200); - assertEquals(IPath.fromOSString(imagePath200).lastSegment(), "image@2x.png"); - String imagePath150 = fileNameProvider.getImagePath(150); - assertNull("URLImageDescriptor's ImageFileNameProvider does return a @1.5x path", imagePath150); + @Test + public void testImageFileNameProviderGetxName_forFileURL_noOSGi() throws IOException { + testImageFileNameProviderGetxName_forFileURL(false); + } + + private void testImageFileNameProviderGetxName_forFileURL(boolean osgiAvailable) throws IOException { + boolean oldOsgiAvailable = InternalPolicy.OSGI_AVAILABLE; + InternalPolicy.OSGI_AVAILABLE = osgiAvailable; + try { + URL imageFileURL = tempFolder.newFile("image.png").toURI().toURL(); + tempFolder.newFile("image@2x.png"); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(imageFileURL); + + ImageFileNameProvider fileNameProvider = Adapters.adapt(descriptor, ImageFileNameProvider.class); + assertNotNull("URLImageDescriptor does not adapt to ImageFileNameProvider", fileNameProvider); + String imagePath100 = fileNameProvider.getImagePath(100); + assertNotNull("URLImageDescriptor ImageFileNameProvider does not return the 100% path", imagePath100); + assertEquals(IPath.fromOSString(imagePath100).lastSegment(), "image.png"); + String imagePath200 = fileNameProvider.getImagePath(200); + assertNotNull("URLImageDescriptor ImageFileNameProvider does not return the @2x path", imagePath200); + assertEquals(IPath.fromOSString(imagePath200).lastSegment(), "image@2x.png"); + String imagePath150 = fileNameProvider.getImagePath(150); + assertNull("URLImageDescriptor's ImageFileNameProvider does return a @1.5x path", imagePath150); + } finally { + InternalPolicy.OSGI_AVAILABLE = oldOsgiAvailable; + } } @Test From 2252e99dab52a7c8ee6e13016005ffac092180e7 Mon Sep 17 00:00:00 2001 From: "Patrick.Ziegler" Date: Thu, 17 Jul 2025 14:11:00 +0200 Subject: [PATCH 21/21] Restore correct URL/URI handling in URLImageDescriptor The changes done with cee46311e84ba41e8a421b47099ad709e23761f1 cause an exception when an URL is passed as argument, which contains improperly escaped characters (most noticeably whitespaces) and therefore causes an URISyntaxException when calling URL.toURI(). This does not occur when using IPath.fromOSString(), which is the approach that was done previously. But instead of converting this path back to an OS-dependent string, it is instead converted to a file, to check whether it exists and to only then convert it back to its String representation. A test case with an ill-formed URL has been added to avoid a similar problem in the future. --- .../jface/resource/URLImageDescriptor.java | 15 +++++++-------- .../tests/images/UrlImageDescriptorTest.java | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java index d57d14844a1..646ca5cb90f 100644 --- a/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java +++ b/bundles/org.eclipse.jface/src/org/eclipse/jface/resource/URLImageDescriptor.java @@ -19,19 +19,18 @@ import java.io.BufferedInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Status; import org.eclipse.jface.internal.InternalPolicy; import org.eclipse.jface.util.Policy; @@ -254,7 +253,7 @@ private static String getFilePath(URL url, boolean logException) { url = resolvePathVariables(url); URL locatedURL = FileLocator.toFileURL(url); return getFilePath(locatedURL); - } catch (IOException | URISyntaxException e) { + } catch (IOException e) { if (logException) { Policy.logException(e); } else if (InternalPolicy.DEBUG_LOG_URL_IMAGE_DESCRIPTOR_MISSING_2x) { @@ -267,11 +266,11 @@ private static String getFilePath(URL url, boolean logException) { } } - private static String getFilePath(URL url) throws URISyntaxException { + private static String getFilePath(URL url) { if (FILE_PROTOCOL.equalsIgnoreCase(url.getProtocol())) { - Path filePath = Path.of(url.toURI()); - if (Files.exists(filePath)) { - return filePath.toString(); + File file = IPath.fromOSString(url.getPath()).toFile(); + if (file.exists()) { + return file.getPath(); } } return null; diff --git a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java index 1e6cfb8a437..ead4939cca0 100644 --- a/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java +++ b/tests/org.eclipse.jface.tests/src/org/eclipse/jface/tests/images/UrlImageDescriptorTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import java.io.File; import java.io.IOException; import java.net.URL; @@ -154,6 +155,23 @@ private void testImageFileNameProviderGetxName_forFileURL(boolean osgiAvailable) } } + @Test + public void testImageFileNameProviderGetxName_forFileURL_WhiteSpace() throws IOException { + File imageFolder = tempFolder.newFolder("folder with spaces"); + File imageFile = new File(imageFolder, "image with spaces.png"); + imageFile.createNewFile(); + + // This is an invalid URL because the whitespace characters are not properly encoded + URL imageFileURL = new URL("file", null, imageFile.getPath()); + ImageDescriptor descriptor = ImageDescriptor.createFromURL(imageFileURL); + + ImageFileNameProvider fileNameProvider = Adapters.adapt(descriptor, ImageFileNameProvider.class); + assertNotNull("URLImageDescriptor does not adapt to ImageFileNameProvider", fileNameProvider); + + String imagePath100 = fileNameProvider.getImagePath(100); + assertNotNull("URLImageDescriptor ImageFileNameProvider does not return the 100% path", imagePath100); + } + @Test public void testAdaptToURL() { ImageDescriptor descriptor = ImageDescriptor