diff --git a/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF
index 7a34055bd76..a3e278d607e 100644
--- a/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.ui.editors/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.ui.editors; singleton:=true
-Bundle-Version: 3.17.300.qualifier
+Bundle-Version: 3.18.0.qualifier
Bundle-Activator: org.eclipse.ui.internal.editors.text.EditorsPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java
index 95009b7b315..5fea8e14203 100644
--- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java
+++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/internal/editors/text/TextEditorDefaultsPreferencePage.java
@@ -428,6 +428,8 @@ private OverlayPreferenceStore createDialogOverlayStore() {
ArrayList
+ * The preference value is of type Boolean
+ *
+ * The preference value is of type Boolean
+ *
@@ -246,6 +282,8 @@ public void checkShell(Shell shell) { */ private Shell fShell; + private FindReplaceOverlay overlay; + /** * Creates a new find/replace action for the given workbench part. *
@@ -264,6 +302,8 @@ public FindReplaceAction(ResourceBundle bundle, String prefix, IWorkbenchPart wo
Assert.isLegal(workbenchPart != null);
fWorkbenchPart= workbenchPart;
update();
+
+ hookDialogPreferenceListener();
}
/**
@@ -291,6 +331,8 @@ public FindReplaceAction(ResourceBundle bundle, String prefix, Shell shell, IFin
fTarget= target;
fShell= shell;
update();
+
+ hookDialogPreferenceListener();
}
/**
@@ -312,13 +354,29 @@ public FindReplaceAction(ResourceBundle bundle, String prefix, IWorkbenchWindow
super(bundle, prefix);
fWorkbenchWindow= workbenchWindow;
update();
+
+ hookDialogPreferenceListener();
+ }
+
+ private void hookDialogPreferenceListener() {
+ IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(INSTANCE_SCOPE_NODE_NAME);
+ preferences.addPreferenceChangeListener(overlayDialogPreferenceListener);
}
@Override
public void run() {
- if (fTarget == null)
+ if (fTarget == null) {
return;
+ }
+
+ if (shouldUseOverlay()) {
+ showOverlayInEditor();
+ } else {
+ showDialog();
+ }
+ }
+ private void showDialog() {
final FindReplaceDialog dialog;
final boolean isEditable;
@@ -352,6 +410,24 @@ public void run() {
dialog.open();
}
+ private void showOverlayInEditor() {
+ if (overlay == null) {
+ Shell shellToUse = null;
+
+ if (fShell == null) {
+ shellToUse = fWorkbenchPart.getSite().getShell();
+ } else {
+ shellToUse = fShell;
+ }
+ overlay = new FindReplaceOverlay(shellToUse, fWorkbenchPart, fTarget);
+
+ FindReplaceOverlayFirstTimePopup.displayPopupIfNotAlreadyShown(shellToUse);
+ }
+
+ overlay.setPositionToTop(shouldPositionOverlayOnTop());
+ overlay.open();
+ }
+
@Override
public void update() {
diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java
new file mode 100644
index 00000000000..5f465d90b56
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java
@@ -0,0 +1,866 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Vector Informatik GmbH 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:
+ * Vector Informatik GmbH - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.texteditor;
+
+import org.osgi.framework.FrameworkUtil;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.BusyIndicator;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.PaintListener;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGBA;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Scrollable;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.swt.widgets.Widget;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.layout.GridLayoutFactory;
+import org.eclipse.jface.resource.JFaceColors;
+import org.eclipse.jface.window.Window;
+
+import org.eclipse.jface.text.IFindReplaceTarget;
+import org.eclipse.jface.text.IFindReplaceTargetExtension;
+
+import org.eclipse.ui.IPartListener;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchPart;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.findandreplace.FindReplaceLogic;
+import org.eclipse.ui.internal.findandreplace.FindReplaceMessages;
+import org.eclipse.ui.internal.findandreplace.SearchOptions;
+import org.eclipse.ui.internal.findandreplace.status.IFindReplaceStatus;
+
+/**
+ * @since 3.17
+ */
+class FindReplaceOverlay extends Dialog {
+
+ private static final String REPLACE_BAR_OPEN_DIALOG_SETTING = "replaceBarOpen"; //$NON-NLS-1$
+ private static final double WORST_CASE_RATIO_EDITOR_TO_OVERLAY = 0.95;
+ private static final double BIG_WIDTH_RATIO_EDITOR_TO_OVERLAY = 0.7;
+ private static final String MINIMAL_WIDTH_TEXT = "THIS TEXT "; //$NON-NLS-1$
+ private static final String COMPROMISE_WIDTH_TEXT = "THIS TEXT HAS A REASONABLE"; //$NON-NLS-1$
+ private static final String IDEAL_WIDTH_TEXT = "THIS TEXT HAS A REASONABLE LENGTH FOR SEARCHING"; //$NON-NLS-1$
+ private FindReplaceLogic findReplaceLogic;
+ private IWorkbenchPart targetPart;
+ private boolean overlayOpen;
+ private boolean replaceBarOpen;
+
+ private Composite container;
+ private Button replaceToggle;
+
+ private Composite contentGroup;
+
+ private Composite searchContainer;
+ private Composite searchBarContainer;
+ private Text searchBar;
+ private ToolBar searchTools;
+
+ private ToolItem searchInSelectionButton;
+ private ToolItem wholeWordSearchButton;
+ private ToolItem caseSensitiveSearchButton;
+ private ToolItem regexSearchButton;
+ private ToolItem searchUpButton;
+ private ToolItem searchDownButton;
+ private ToolItem searchAllButton;
+
+ private Composite replaceContainer;
+ private Composite replaceBarContainer;
+ private Text replaceBar;
+ private ToolBar replaceTools;
+ private ToolItem replaceButton;
+ private ToolItem replaceAllButton;
+
+ private Color backgroundToUse;
+ private Color normalTextForegroundColor;
+ private boolean positionAtTop = true;
+
+ public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target) {
+ super(parent);
+ createFindReplaceLogic(target);
+
+ setShellStyle(SWT.MODELESS);
+ setBlockOnOpen(false);
+ targetPart = part;
+
+ }
+
+ @Override
+ protected boolean isResizable() {
+ return false;
+ }
+
+ private void createFindReplaceLogic(IFindReplaceTarget target) {
+ findReplaceLogic = new FindReplaceLogic();
+ boolean isTargetEditable = false;
+ if (target != null) {
+ isTargetEditable = target.isEditable();
+ }
+ findReplaceLogic.updateTarget(target, isTargetEditable);
+ findReplaceLogic.activate(SearchOptions.INCREMENTAL);
+ findReplaceLogic.activate(SearchOptions.GLOBAL);
+ findReplaceLogic.activate(SearchOptions.WRAP);
+ findReplaceLogic.activate(SearchOptions.FORWARD);
+ }
+
+ private void performReplaceAll() {
+ BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(),
+ () -> findReplaceLogic.performReplaceAll(getFindString(), getReplaceString()));
+ }
+
+ private void performSelectAll() {
+ BusyIndicator.showWhile(getShell() != null ? getShell().getDisplay() : Display.getCurrent(),
+ () -> findReplaceLogic.performSelectAll(getFindString()));
+ }
+
+ private KeyListener shortcuts = KeyListener.keyPressedAdapter(e -> {
+ e.doit = false;
+ if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'F' || e.keyCode == 'f')) {
+ close();
+ } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'R' || e.keyCode == 'r')) {
+ if (findReplaceLogic.getTarget().isEditable()) {
+ toggleReplace();
+ }
+ } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'W' || e.keyCode == 'w')) {
+ toggleToolItem(wholeWordSearchButton);
+ } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'P' || e.keyCode == 'p')) {
+ toggleToolItem(regexSearchButton);
+ } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'A' || e.keyCode == 'a')) {
+ toggleToolItem(searchInSelectionButton);
+ } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'C' || e.keyCode == 'c')) {
+ toggleToolItem(caseSensitiveSearchButton);
+ } else if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
+ performEnterAction(e);
+ } else if (e.keyCode == SWT.ESC) {
+ close();
+ } else {
+ e.doit = true;
+ }
+ });
+
+ private void performEnterAction(KeyEvent e) {
+ boolean isShiftPressed = (e.stateMask & SWT.SHIFT) != 0;
+ boolean isCtrlPressed = (e.stateMask & SWT.CTRL) != 0;
+ if (okayToUse(replaceBar) && replaceBar.isFocusControl()) {
+ if (isCtrlPressed) {
+ performReplaceAllOnEnter();
+ } else {
+ performReplaceOnEnter();
+ }
+ } else {
+ if (isCtrlPressed) {
+ performSearchAltOnEnter();
+ } else {
+ performSearchOnEnter(isShiftPressed);
+ }
+ }
+ }
+
+ private void performReplaceAllOnEnter() {
+ performReplaceAll();
+ evaluateFindReplaceStatus();
+ }
+
+ private void performReplaceOnEnter() {
+ performSingleReplace();
+ evaluateFindReplaceStatus();
+ }
+
+ private void performSearchAltOnEnter() {
+ performSelectAll();
+ evaluateFindReplaceStatus();
+ }
+
+ private void performSearchOnEnter(boolean isShiftPressed) {
+ performSearch(!isShiftPressed);
+ evaluateFindReplaceStatus();
+ }
+
+ private void toggleToolItem(ToolItem toolItem) {
+ toolItem.setSelection(!toolItem.getSelection());
+ toolItem.notifyListeners(SWT.Selection, null);
+ }
+
+ private ControlListener shellMovementListener = new ControlListener() {
+ @Override
+ public void controlMoved(ControlEvent e) {
+ positionToPart();
+ }
+
+ @Override
+ public void controlResized(ControlEvent e) {
+ positionToPart();
+ }
+ };
+
+ private FocusListener overlayFocusListener = FocusListener.focusLostAdapter(e -> {
+ findReplaceLogic.activate(SearchOptions.GLOBAL);
+ searchInSelectionButton.setSelection(false);
+ });
+
+ private PaintListener widgetMovementListener = __ -> positionToPart();
+
+ private IPartListener partListener = new IPartListener() {
+ @Override
+ public void partActivated(IWorkbenchPart part) {
+ if (getShell() != null) {
+ getShell().setVisible(isPartCurrentlyDisplayedInPartSash());
+ }
+ }
+
+ @Override
+ public void partDeactivated(IWorkbenchPart part) {
+ // Do nothing
+ }
+
+ @Override
+ public void partBroughtToTop(IWorkbenchPart part) {
+ if (getShell() != null) {
+ getShell().setVisible(isPartCurrentlyDisplayedInPartSash());
+ }
+ }
+
+ @Override
+ public void partClosed(IWorkbenchPart part) {
+ close();
+ }
+
+ @Override
+ public void partOpened(IWorkbenchPart part) {
+ // Do nothing
+ }
+ };
+
+ private boolean isPartCurrentlyDisplayedInPartSash() {
+ IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+
+ // Check if the targetPart is currently displayed on the active page
+ boolean isPartDisplayed = false;
+
+ if (activePage != null) {
+ IWorkbenchPart activePart = activePage.getActivePart();
+ if (activePart != null && activePart == targetPart) {
+ isPartDisplayed = true;
+ }
+ }
+
+ return isPartDisplayed;
+ }
+
+ /**
+ * Returns the dialog settings object used to share state between several
+ * find/replace overlays.
+ *
+ * @return the dialog settings to be used
+ */
+ private static IDialogSettings getDialogSettings() {
+ IDialogSettings settings = PlatformUI
+ .getDialogSettingsProvider(FrameworkUtil.getBundle(FindReplaceOverlay.class)).getDialogSettings();
+ return settings;
+ }
+
+ @Override
+ public boolean close() {
+ if (!overlayOpen) {
+ return true;
+ }
+ storeOverlaySettings();
+
+ findReplaceLogic.activate(SearchOptions.GLOBAL);
+ overlayOpen = false;
+ replaceBarOpen = false;
+ unbindListeners();
+ container.dispose();
+ return super.close();
+ }
+
+ @Override
+ public int open() {
+ int returnCode = Window.OK;
+ if (!overlayOpen) {
+ returnCode = super.open();
+ bindListeners();
+ restoreOverlaySettings();
+ }
+ overlayOpen = true;
+ applyOverlayColors(backgroundToUse, true);
+ initFindStringFromSelection();
+
+ getShell().layout();
+ positionToPart();
+
+ searchBar.forceFocus();
+ return returnCode;
+ }
+
+ private void storeOverlaySettings() {
+ getDialogSettings().put(REPLACE_BAR_OPEN_DIALOG_SETTING, replaceBarOpen);
+ }
+
+ private void restoreOverlaySettings() {
+ Boolean shouldOpenReplaceBar = getDialogSettings().getBoolean(REPLACE_BAR_OPEN_DIALOG_SETTING);
+ if (shouldOpenReplaceBar && replaceToggle != null) {
+ toggleReplace();
+ }
+ }
+
+ private void applyOverlayColors(Color color, boolean tryToColorReplaceBar) {
+ searchTools.setBackground(color);
+ searchInSelectionButton.setBackground(color);
+ wholeWordSearchButton.setBackground(color);
+ regexSearchButton.setBackground(color);
+ caseSensitiveSearchButton.setBackground(color);
+ searchAllButton.setBackground(color);
+ searchUpButton.setBackground(color);
+ searchDownButton.setBackground(color);
+
+ searchBarContainer.setBackground(color);
+ searchBar.setBackground(color);
+ searchContainer.setBackground(color);
+
+ if (replaceBarOpen && tryToColorReplaceBar) {
+ replaceContainer.setBackground(color);
+ replaceBar.setBackground(color);
+ replaceBarContainer.setBackground(color);
+ replaceAllButton.setBackground(color);
+ replaceButton.setBackground(color);
+ }
+ }
+
+ private void unbindListeners() {
+ getShell().removeFocusListener(overlayFocusListener);
+ if (targetPart != null && targetPart instanceof StatusTextEditor textEditor) {
+ Control targetWidget = textEditor.getSourceViewer().getTextWidget();
+ if (targetWidget != null) {
+ targetWidget.getShell().removeControlListener(shellMovementListener);
+ targetWidget.removePaintListener(widgetMovementListener);
+ targetPart.getSite().getPage().removePartListener(partListener);
+ }
+ }
+ }
+
+ private void bindListeners() {
+ getShell().addFocusListener(overlayFocusListener);
+ if (targetPart instanceof StatusTextEditor textEditor) {
+ Control targetWidget = textEditor.getSourceViewer().getTextWidget();
+
+ targetWidget.getShell().addControlListener(shellMovementListener);
+ targetWidget.addPaintListener(widgetMovementListener);
+ targetPart.getSite().getPage().addPartListener(partListener);
+ }
+ }
+
+ @Override
+ public Control createContents(Composite parent) {
+ backgroundToUse = new Color(getShell().getDisplay(), new RGBA(0, 0, 0, 0));
+ Control ret = createDialog(parent);
+
+ getShell().layout();
+ positionToPart();
+ return ret;
+ }
+
+ private Control createDialog(final Composite parent) {
+ createMainContainer(parent);
+
+ retrieveBackgroundColor();
+
+ createFindContainer();
+ createSearchBar();
+ createSearchTools();
+
+ container.layout();
+
+ applyDialogFont(container);
+ return container;
+ }
+
+ /**
+ * HACK: In order to not introduce a hard-coded color, we need to retrieve the
+ * color of the "SWT.SEARCH"-Text. Since that search-bar has a border, we don't
+ * want to have it in our own form. Instead, we create such a bar at start-up,
+ * grab it's color and then immediately dispose of that bar.
+ */
+ private void retrieveBackgroundColor() {
+ Text textBarForRetrievingTheRightColor = new Text(container, SWT.SINGLE | SWT.SEARCH);
+ container.layout();
+ backgroundToUse = textBarForRetrievingTheRightColor.getBackground();
+ normalTextForegroundColor = textBarForRetrievingTheRightColor.getForeground();
+ textBarForRetrievingTheRightColor.dispose();
+ }
+
+ private void createSearchTools() {
+ searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL);
+ GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(searchTools);
+
+ createWholeWordsButton();
+ createCaseSensitiveButton();
+ createRegexSearchButton();
+ createAreaSearchButton();
+
+ @SuppressWarnings("unused")
+ ToolItem separator = new ToolItem(searchTools, SWT.SEPARATOR);
+
+ searchUpButton = new ToolItem(searchTools, SWT.PUSH);
+ searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_FIND_PREV));
+ searchUpButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_upSearchButton_toolTip);
+ searchUpButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ performSearch(false);
+ evaluateFindReplaceStatus();
+ }));
+ searchDownButton = new ToolItem(searchTools, SWT.PUSH);
+ searchDownButton.setSelection(true); // by default, search down
+ searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_FIND_NEXT));
+ searchDownButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_downSearchButton_toolTip);
+ searchDownButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ performSearch(true);
+ evaluateFindReplaceStatus();
+ }));
+ searchAllButton = new ToolItem(searchTools, SWT.PUSH);
+ searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_SEARCH_ALL));
+ searchAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchAllButton_toolTip);
+ searchAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ performSelectAll();
+ evaluateFindReplaceStatus();
+ }));
+ }
+
+ private void createAreaSearchButton() {
+ searchInSelectionButton = new ToolItem(searchTools, SWT.CHECK);
+ searchInSelectionButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_SEARCH_IN_AREA));
+ searchInSelectionButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_searchInSelectionButton_toolTip);
+ searchInSelectionButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
+ searchInSelectionButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ activateInFindReplacerIf(SearchOptions.GLOBAL, !searchInSelectionButton.getSelection());
+ updateIncrementalSearch();
+ }));
+ }
+
+ private void createRegexSearchButton() {
+ regexSearchButton = new ToolItem(searchTools, SWT.CHECK);
+ regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_FIND_REGEX));
+ regexSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_regexSearchButton_toolTip);
+ regexSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.REGEX));
+ regexSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ activateInFindReplacerIf(SearchOptions.REGEX, ((ToolItem) e.widget).getSelection());
+ wholeWordSearchButton.setEnabled(!findReplaceLogic.isActive(SearchOptions.REGEX));
+ updateIncrementalSearch();
+ }));
+ }
+
+ private void createCaseSensitiveButton() {
+ caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK);
+ caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_CASE_SENSITIVE));
+ caseSensitiveSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_caseSensitiveButton_toolTip);
+ caseSensitiveSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.CASE_SENSITIVE));
+ caseSensitiveSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection());
+ updateIncrementalSearch();
+ }));
+ }
+
+ private void createWholeWordsButton() {
+ wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK);
+ wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_WHOLE_WORD));
+ wholeWordSearchButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_wholeWordsButton_toolTip);
+ wholeWordSearchButton.setSelection(findReplaceLogic.isActive(SearchOptions.WHOLE_WORD));
+ wholeWordSearchButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection());
+ updateIncrementalSearch();
+ }));
+ }
+
+ private void createReplaceTools() {
+ Color warningColor = JFaceColors.getErrorText(getShell().getDisplay());
+
+ replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL);
+ GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.END).applyTo(replaceTools);
+ replaceButton = new ToolItem(replaceTools, SWT.PUSH);
+ replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE));
+ replaceButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceButton_toolTip);
+ replaceButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (getFindString().isEmpty()) {
+ showUserFeedback(warningColor, true);
+ return;
+ }
+ performSingleReplace();
+ evaluateFindReplaceStatus();
+ }));
+ replaceAllButton = new ToolItem(replaceTools, SWT.PUSH);
+ replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE_ALL));
+ replaceAllButton.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceAllButton_toolTip);
+ replaceAllButton.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ if (getFindString().isEmpty()) {
+ showUserFeedback(warningColor, true);
+ return;
+ }
+ performReplaceAll();
+ evaluateFindReplaceStatus();
+ }));
+ }
+
+ private void createSearchBar() {
+ searchBar = new Text(searchBarContainer, SWT.SINGLE);
+ GridDataFactory.fillDefaults().grab(true, false).align(GridData.FILL, GridData.END).applyTo(searchBar);
+ searchBar.forceFocus();
+ searchBar.selectAll();
+ searchBar.addModifyListener(e -> {
+ wholeWordSearchButton.setEnabled(findReplaceLogic.isWholeWordSearchAvailable(getFindString()));
+
+ showUserFeedback(normalTextForegroundColor, true);
+ // don't perform incremental search if we are already on the word.
+ if (!getFindString().equals(findReplaceLogic.getTarget().getSelectionText())) {
+ updateIncrementalSearch();
+ }
+ });
+ searchBar.addFocusListener(new FocusListener() {
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ // we want to update the base-location of where we start incremental search
+ // to the currently selected position in the target
+ // when coming back into the dialog
+ findReplaceLogic.deactivate(SearchOptions.INCREMENTAL);
+ findReplaceLogic.activate(SearchOptions.INCREMENTAL);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ showUserFeedback(normalTextForegroundColor, false);
+ }
+
+ });
+ searchBar.addKeyListener(shortcuts);
+ searchBar.setMessage(FindReplaceMessages.FindReplaceOverlay_searchBar_message);
+ }
+
+ private void updateIncrementalSearch() {
+ // clear the current incrementally searched selection to avoid having an old
+ // selection left when incrementally searching for an invalid string
+ if (findReplaceLogic.getTarget() instanceof IFindReplaceTargetExtension targetExtension) {
+ targetExtension.setSelection(targetExtension.getLineSelection().x, 0);
+ }
+ findReplaceLogic.performIncrementalSearch(getFindString());
+ evaluateFindReplaceStatus();
+ }
+
+ private void createReplaceBar() {
+ replaceBar = new Text(replaceBarContainer, SWT.SINGLE);
+ GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.END).applyTo(replaceBar);
+ replaceBar.setMessage(FindReplaceMessages.FindReplaceOverlay_replaceBar_message);
+ replaceBar.addFocusListener(FocusListener.focusLostAdapter(e -> {
+ replaceBar.setForeground(normalTextForegroundColor);
+ searchBar.setForeground(normalTextForegroundColor);
+ }));
+ replaceBar.addKeyListener(shortcuts);
+ }
+
+ private void createFindContainer() {
+ searchContainer = new Composite(contentGroup, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchContainer);
+ GridLayoutFactory.fillDefaults().numColumns(2).extendedMargins(4, 4, 2, 8).equalWidth(false)
+ .applyTo(searchContainer);
+ searchContainer.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
+ searchBarContainer = new Composite(searchContainer, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.END).applyTo(searchBarContainer);
+ GridLayoutFactory.fillDefaults().numColumns(1).applyTo(searchBarContainer);
+ }
+
+ private void createReplaceContainer() {
+ replaceContainer = new Composite(contentGroup, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(replaceContainer);
+ GridLayoutFactory.fillDefaults().margins(0, 1).numColumns(2).extendedMargins(4, 4, 2, 8).equalWidth(false)
+ .applyTo(replaceContainer);
+ replaceContainer.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
+ replaceBarContainer = new Composite(replaceContainer, SWT.NONE);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.END).applyTo(replaceBarContainer);
+ GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(replaceBarContainer);
+ }
+
+ private void createMainContainer(final Composite parent) {
+ container = new Composite(parent, SWT.NONE);
+ GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(2, 2).spacing(2, 0).applyTo(container);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(container);
+
+ if (findReplaceLogic.getTarget().isEditable()) {
+ createReplaceToggle();
+ }
+
+ contentGroup = new Composite(container, SWT.NULL);
+ GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).spacing(2, 3).applyTo(contentGroup);
+ GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup);
+ }
+
+ private void createReplaceToggle() {
+ replaceToggle = new Button(container, SWT.PUSH);
+ GridDataFactory.fillDefaults().grab(false, true).align(GridData.BEGINNING, GridData.FILL)
+ .applyTo(replaceToggle);
+ replaceToggle.setToolTipText(FindReplaceMessages.FindReplaceOverlay_replaceToggle_toolTip);
+ replaceToggle.setText("⯈"); //$NON-NLS-1$
+ replaceToggle.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> toggleReplace()));
+ }
+
+ private void toggleReplace() {
+ if (!replaceBarOpen) {
+ createReplaceDialog();
+ replaceToggle.setText("⯅"); //$NON-NLS-1$
+ } else {
+ hideReplace();
+ replaceToggle.setText("⯈"); //$NON-NLS-1$
+ }
+ replaceToggle.setSelection(false); // We don't want the button to look "locked in", so don't
+ // use it's selectionState
+ }
+
+ private void hideReplace() {
+ if (!replaceBarOpen) {
+ return;
+ }
+ searchBar.forceFocus();
+ replaceBarOpen = false;
+ replaceContainer.dispose();
+ positionToPart();
+ }
+
+ private void createReplaceDialog() {
+ if (replaceBarOpen) {
+ return;
+ }
+ replaceBarOpen = true;
+ createReplaceContainer();
+ createReplaceBar();
+ createReplaceTools();
+ positionToPart();
+ applyOverlayColors(backgroundToUse, true);
+ replaceBar.forceFocus();
+ }
+
+ private void enableSearchTools(boolean enable) {
+ ((GridData) searchTools.getLayoutData()).exclude = !enable;
+ searchTools.setVisible(enable);
+
+ if (enable) {
+ ((GridLayout) searchTools.getParent().getLayout()).numColumns = 2;
+ } else {
+ ((GridLayout) searchTools.getParent().getLayout()).numColumns = 1;
+ }
+ }
+
+ private void enableReplaceToggle(boolean enable) {
+ if (!okayToUse(replaceToggle)) {
+ return;
+ }
+ ((GridData) replaceToggle.getLayoutData()).exclude = !enable;
+ replaceToggle.setVisible(enable);
+ }
+
+ private void enableReplaceTools(boolean enable) {
+ if (!okayToUse(replaceTools)) {
+ return;
+ }
+ ((GridData) replaceTools.getLayoutData()).exclude = !enable;
+ replaceTools.setVisible(enable);
+
+ if (enable) {
+ ((GridLayout) replaceTools.getParent().getLayout()).numColumns = 2;
+ } else {
+ ((GridLayout) replaceTools.getParent().getLayout()).numColumns = 1;
+ }
+ }
+
+ private int getIdealDialogWidth(Rectangle targetBounds) {
+ int replaceToggleWidth = 0;
+ if (okayToUse(replaceToggle)) {
+ replaceToggleWidth = replaceToggle.getBounds().width;
+ }
+ int toolBarWidth = searchTools.getSize().x;
+ GC gc = new GC(searchBar);
+ gc.setFont(searchBar.getFont());
+ int idealWidth = gc.stringExtent(IDEAL_WIDTH_TEXT).x; // $NON-NLS-1$
+ int idealCompromiseWidth = gc.stringExtent(COMPROMISE_WIDTH_TEXT).x; // $NON-NLS-1$
+ int worstCompromiseWidth = gc.stringExtent(MINIMAL_WIDTH_TEXT).x; // $NON-NLS-1$
+ gc.dispose();
+
+ int newWidth = idealWidth + toolBarWidth + replaceToggleWidth;
+ if (newWidth > targetBounds.width * BIG_WIDTH_RATIO_EDITOR_TO_OVERLAY) {
+ newWidth = (int) (targetBounds.width * BIG_WIDTH_RATIO_EDITOR_TO_OVERLAY);
+ enableSearchTools(true);
+ enableReplaceTools(true);
+ enableReplaceToggle(true);
+ }
+ if (newWidth < idealCompromiseWidth + toolBarWidth) {
+ enableSearchTools(false);
+ enableReplaceTools(false);
+ enableReplaceToggle(true);
+ }
+ if (newWidth < worstCompromiseWidth + toolBarWidth) {
+ newWidth = (int) (targetBounds.width * WORST_CASE_RATIO_EDITOR_TO_OVERLAY);
+ enableReplaceToggle(false);
+ enableSearchTools(false);
+ enableReplaceTools(false);
+ }
+ return newWidth;
+ }
+
+ private Point getNewPosition(Widget targetTextWidget, Point targetOrigin, Rectangle targetBounds,
+ Point expectedSize) {
+ Point verticalScrollBarSize = ((Scrollable) targetTextWidget).getVerticalBar().getSize();
+ Point horizontalScrollBarSize = ((Scrollable) targetTextWidget).getHorizontalBar().getSize();
+
+ int newX = targetOrigin.x + targetBounds.width - expectedSize.x - verticalScrollBarSize.x
+ - ((StyledText) targetTextWidget).getRightMargin();
+ int newY = targetOrigin.y;
+ if (!positionAtTop) {
+ newY += targetBounds.height - expectedSize.y - horizontalScrollBarSize.y;
+ }
+ return new Point(newX, newY);
+ }
+
+ /**
+ * When making the text-bar 100% small and then regrowing it, we want the text
+ * to start at the first character again.
+ */
+ private void repositionTextSelection() {
+ if (okayToUse(searchBar) && !searchBar.isFocusControl()) {
+ searchBar.setSelection(0, 0);
+ }
+ if (okayToUse(replaceBar) && !replaceBar.isFocusControl()) {
+ replaceBar.setSelection(0, 0);
+ }
+ }
+
+ private void positionToPart() {
+ getShell().requestLayout();
+ if (!(targetPart instanceof StatusTextEditor)) {
+ return;
+ }
+
+ StatusTextEditor textEditor = (StatusTextEditor) targetPart;
+ Control targetWidget = textEditor.getSourceViewer().getTextWidget();
+ if (!okayToUse(targetWidget)) {
+ this.close();
+ return;
+ }
+
+ Point targetOrigin = targetWidget.toDisplay(0, 0);
+ Rectangle targetBounds = targetWidget.getBounds();
+
+ int newWidth = getIdealDialogWidth(targetBounds);
+ int newHeight = container.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
+
+ Point newPosition = getNewPosition(targetWidget, targetOrigin, targetBounds, new Point(newWidth, newHeight));
+
+ getShell().setSize(new Point(newWidth, newHeight));
+ getShell().setLocation(newPosition);
+ getShell().layout(true);
+
+ repositionTextSelection();
+ }
+
+ private String getFindString() {
+ return searchBar.getText();
+ }
+
+ private String getReplaceString() {
+ if (!okayToUse(replaceBar)) {
+ return ""; //$NON-NLS-1$
+ }
+ return replaceBar.getText();
+
+ }
+
+ private void performSingleReplace() {
+ findReplaceLogic.performReplaceAndFind(getFindString(), getReplaceString());
+ }
+
+ private void performSearch(boolean forward) {
+ boolean oldForwardSearchSetting = findReplaceLogic.isActive(SearchOptions.FORWARD);
+ activateInFindReplacerIf(SearchOptions.FORWARD, forward);
+ findReplaceLogic.deactivate(SearchOptions.INCREMENTAL);
+ findReplaceLogic.performSearch(getFindString());
+ activateInFindReplacerIf(SearchOptions.FORWARD, oldForwardSearchSetting);
+ findReplaceLogic.activate(SearchOptions.INCREMENTAL);
+ }
+
+ private void initFindStringFromSelection() {
+ String initText = findReplaceLogic.getTarget().getSelectionText();
+ if (initText.isEmpty()) {
+ return;
+ }
+ if (initText.contains(System.lineSeparator())) { // $NON-NLS-1$
+ findReplaceLogic.deactivate(SearchOptions.GLOBAL);
+ searchInSelectionButton.setSelection(true);
+ } else {
+ searchBar.setText(initText);
+ searchBar.setSelection(0, initText.length());
+ }
+ }
+
+ private void evaluateFindReplaceStatus() {
+ Color warningColor = JFaceColors.getErrorText(getShell().getDisplay());
+ IFindReplaceStatus status = findReplaceLogic.getStatus();
+
+ if (!status.wasSuccessful()) {
+ boolean colorReplaceBar = okayToUse(replaceBar) && replaceBar.isFocusControl();
+ showUserFeedback(warningColor, colorReplaceBar);
+ } else {
+ showUserFeedback(normalTextForegroundColor, false);
+ }
+ }
+
+ private void showUserFeedback(Color feedbackColor, boolean colorReplaceBar) {
+ searchBar.setForeground(feedbackColor);
+ if (colorReplaceBar && okayToUse(replaceBar)) {
+ replaceBar.setForeground(feedbackColor);
+ }
+ }
+
+ private void activateInFindReplacerIf(SearchOptions option, boolean shouldActivate) {
+ if (shouldActivate) {
+ findReplaceLogic.activate(option);
+ } else {
+ findReplaceLogic.deactivate(option);
+ }
+ }
+
+ private static boolean okayToUse(Widget widget) {
+ return widget != null && !widget.isDisposed();
+ }
+
+ public void setPositionToTop(boolean shouldPositionOverlayOnTop) {
+ positionAtTop = shouldPositionOverlayOnTop;
+ }
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayFirstTimePopup.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayFirstTimePopup.java
new file mode 100644
index 00000000000..e274a7db02f
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayFirstTimePopup.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Vector Informatik GmbH 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:
+ * Vector Informatik GmbH - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.texteditor;
+
+import java.time.Duration;
+import java.util.Objects;
+
+import org.osgi.framework.FrameworkUtil;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.core.runtime.preferences.IEclipsePreferences;
+import org.eclipse.core.runtime.preferences.InstanceScope;
+
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.notifications.NotificationPopup;
+
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.internal.findandreplace.FindReplaceMessages;
+
+/**
+ * Utility class to display a popup the first time the FindReplaceOverlay is
+ * shown, informing the user about the new functionality. This class will track
+ * whether the popup was already shown and will only show the Overlay on the
+ * first time the popup was shown.
+ */
+class FindReplaceOverlayFirstTimePopup {
+
+ private FindReplaceOverlayFirstTimePopup() {
+ }
+
+ private static final String PREFERENCE_NODE_NAME = "org.eclipse.ui.editors"; //$NON-NLS-1$
+ private static final String SETTING_POPUP_WAS_SHOWN_BEFORE = "hasShownOverlayPopupBefore"; //$NON-NLS-1$
+ /**
+ * How long to wait until the pop up should vanish in Ms.
+ */
+ private static final Duration POPUP_VANISH_TIME = Duration.ofSeconds(6);
+ private static final String USE_FIND_REPLACE_OVERLAY = "useFindReplaceOverlay"; //$NON-NLS-1$
+
+ /**
+ * Returns the dialog settings object used to remember whether the popup was
+ * already shown or not.
+ *
+ * @return the dialog settings to be used
+ */
+ private static IDialogSettings getDialogSettings() {
+ IDialogSettings settings = PlatformUI
+ .getDialogSettingsProvider(FrameworkUtil.getBundle(FindReplaceOverlayFirstTimePopup.class))
+ .getDialogSettings();
+ return settings;
+ }
+
+ private static void disableUseOverlayPreference() {
+ IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(PREFERENCE_NODE_NAME); // $NON-NLS-1$
+ preferences.putBoolean(USE_FIND_REPLACE_OVERLAY, false);
+ }
+
+ /**
+ * Displays a popup indicating that instead of the FindReplaceDialog, the
+ * FindReplaceOverlay is currently being used. Only displays the popup on the
+ * first time use of FindReplaceOverlay.
+ *
+ * The popup is bound to the bottom right corner of the principal computer
+ * Monitor.
+ *
+ * @param shellToUse the shell to bind the popup to
+ */
+ public static void displayPopupIfNotAlreadyShown(Shell shellToUse) {
+ IDialogSettings settings = getDialogSettings();
+
+ if (!settings.getBoolean(SETTING_POPUP_WAS_SHOWN_BEFORE)) {
+ settings.put(SETTING_POPUP_WAS_SHOWN_BEFORE, true);
+
+ Display displayToUse = Objects.nonNull(shellToUse) ? shellToUse.getDisplay() : Display.getDefault();
+
+ NotificationPopup.forDisplay(displayToUse).content(t -> createFirstTimeNotification(t))
+ .title(FindReplaceMessages.FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_title,
+ true)
+ .delay(POPUP_VANISH_TIME.toMillis()).open();
+ }
+
+ }
+
+ private static Control createFirstTimeNotification(Composite composite) {
+ Link messageBody = new Link(composite, SWT.WRAP);
+
+ messageBody
+ .setText(FindReplaceMessages.FindReplaceOverlayFirstTimePopup_FindReplaceOverlayFirstTimePopup_message);
+ messageBody.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1));
+ messageBody.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ disableUseOverlayPreference();
+ composite.getShell().close();
+ }));
+
+ return messageBody;
+ }
+}
diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java
new file mode 100644
index 00000000000..2017e13f966
--- /dev/null
+++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java
@@ -0,0 +1,156 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Vector Informatik GmbH 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:
+ * Vector Informatik GmbH - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.texteditor;
+
+import java.net.URL;
+
+import org.osgi.framework.Bundle;
+
+import org.eclipse.swt.graphics.Image;
+
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.resource.ImageRegistry;
+
+import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
+
+/**
+ * Provides Icons for the editor overlay used for performing
+ * find/replace-operations.
+ */
+class FindReplaceOverlayImages {
+ static final String PREFIX_OBJ = TextEditorPlugin.PLUGIN_ID + ".obj."; //$NON-NLS-1$
+
+ static final String OBJ_FIND_NEXT = PREFIX_OBJ + "select_next.png"; //$NON-NLS-1$
+
+ static final String OBJ_FIND_PREV = PREFIX_OBJ + "select_prev.png"; //$NON-NLS-1$
+
+ static final String OBJ_FIND_REGEX = PREFIX_OBJ + "regex_gear.gif"; //$NON-NLS-1$
+
+ static final String OBJ_REPLACE = PREFIX_OBJ + "replace.png"; //$NON-NLS-1$
+
+ static final String OBJ_REPLACE_ALL = PREFIX_OBJ + "replace_all.png"; //$NON-NLS-1$
+
+ static final String OBJ_WHOLE_WORD = PREFIX_OBJ + "whole_word.png"; //$NON-NLS-1$
+
+ static final String OBJ_CASE_SENSITIVE = PREFIX_OBJ + "case_sensitive.png"; //$NON-NLS-1$
+
+ static final String OBJ_SEARCH_ALL = PREFIX_OBJ + "search_all.png"; //$NON-NLS-1$
+
+ static final String OBJ_SEARCH_IN_AREA = PREFIX_OBJ + "search_in_selection.png"; //$NON-NLS-1$
+
+ /**
+ * The image registry containing {@link Image images}.
+ */
+ private static ImageRegistry fgImageRegistry;
+
+ private static String ICONS_PATH = "$nl$/icons/full/"; //$NON-NLS-1$
+
+ private final static String OBJ = ICONS_PATH + "obj16/"; //$NON-NLS-1$
+
+ /**
+ * Declare all images
+ */
+ private static void declareImages() {
+ declareRegistryImage(OBJ_FIND_NEXT, OBJ + "select_next.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_FIND_PREV, OBJ + "select_prev.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_FIND_REGEX, OBJ + "regex.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_REPLACE_ALL, OBJ + "replace_all.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_REPLACE, OBJ + "replace.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_WHOLE_WORD, OBJ + "whole_word.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_CASE_SENSITIVE, OBJ + "case_sensitive.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_SEARCH_ALL, OBJ + "search_all.png"); //$NON-NLS-1$
+ declareRegistryImage(OBJ_SEARCH_IN_AREA, OBJ + "search_in_area.png"); //$NON-NLS-1$
+ }
+
+ /**
+ * Declare an Image in the registry table.
+ *
+ * @param key the key to use when registering the image
+ * @param path the path where the image can be found. This path is relative to
+ * where this plugin class is found (i.e. typically the packages
+ * directory)
+ */
+ private final static void declareRegistryImage(String key, String path) {
+ ImageDescriptor desc = ImageDescriptor.getMissingImageDescriptor();
+ Bundle bundle = Platform.getBundle(TextEditorPlugin.PLUGIN_ID);
+ URL url = null;
+ if (bundle != null) {
+ url = FileLocator.find(bundle, IPath.fromOSString(path), null);
+ desc = ImageDescriptor.createFromURL(url);
+ }
+ fgImageRegistry.put(key, desc);
+ }
+
+ /**
+ * Returns the ImageRegistry.
+ *
+ * @return image registry
+ */
+ public static ImageRegistry getImageRegistry() {
+ if (fgImageRegistry == null) {
+ initializeImageRegistry();
+ }
+ return fgImageRegistry;
+ }
+
+ /**
+ * Initialize the image registry by declaring all of the required graphics. This
+ * involves creating JFace image descriptors describing how to create/find the
+ * image should it be needed. The image is not actually allocated until
+ * requested.
+ *
+ * Prefix conventions Wizard Banners WIZBAN_ Preference Banners PREF_BAN_
+ * Property Page Banners PROPBAN_ Color toolbar CTOOL_ Enable toolbar ETOOL_
+ * Disable toolbar DTOOL_ Local enabled toolbar ELCL_ Local Disable toolbar
+ * DLCL_ Object large OBJL_ Object small OBJS_ View VIEW_ Product images PROD_
+ * Misc images MISC_
+ *
+ * Where are the images? The images (typically pngs) are found in the same
+ * location as this plugin class. This may mean the same package directory as
+ * the package holding this class. The images are declared using this.getClass()
+ * to ensure they are looked up via this plugin class.
+ *
+ * @return the image registry
+ * @see org.eclipse.jface.resource.ImageRegistry
+ */
+ public static ImageRegistry initializeImageRegistry() {
+ fgImageRegistry = TextEditorPlugin.getDefault().getImageRegistry();
+ declareImages();
+ return fgImageRegistry;
+ }
+
+ /**
+ * Returns the image managed under the given key in this registry.
+ *
+ * @param key the image's key
+ * @return the image managed under the given key
+ */
+ public static Image get(String key) {
+ return getImageRegistry().get(key);
+ }
+
+ /**
+ * Returns the image descriptor for the given key in this registry.
+ *
+ * @param key the image's key
+ * @return the image descriptor for the given key
+ */
+ public static ImageDescriptor getDescriptor(String key) {
+ return getImageRegistry().getDescriptor(key);
+ }
+}
diff --git a/tests/org.eclipse.text.tests/src/org/eclipse/text/tests/Accessor.java b/tests/org.eclipse.text.tests/src/org/eclipse/text/tests/Accessor.java
index 262da670f00..15887e83d06 100644
--- a/tests/org.eclipse.text.tests/src/org/eclipse/text/tests/Accessor.java
+++ b/tests/org.eclipse.text.tests/src/org/eclipse/text/tests/Accessor.java
@@ -161,9 +161,15 @@ public Object invoke(String methodName, Object[] arguments) {
public Object invoke(String methodName, Class>[] types, Object[] arguments) {
Method method= null;
try {
- method= fClass.getDeclaredMethod(methodName, types);
- } catch (SecurityException | NoSuchMethodException e) {
- throw (AssertionFailedException) new AssertionFailedException(e.getLocalizedMessage()).initCause(e);
+ method = fClass.getDeclaredMethod(methodName, types);
+ Assert.isNotNull(method);
+ method.setAccessible(true);
+ } catch (SecurityException | NoSuchMethodException __) {
+ try {
+ method = fClass.getMethod(methodName, types);
+ } catch (SecurityException | NoSuchMethodException e) {
+ throw (AssertionFailedException) new AssertionFailedException(e.getLocalizedMessage()).initCause(e);
+ }
}
Assert.isNotNull(method);
method.setAccessible(true);
diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceOverlayTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceOverlayTest.java
new file mode 100644
index 00000000000..d59aee0fee4
--- /dev/null
+++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceOverlayTest.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Vector Informatik GmbH 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:
+ * Vector Informatik GmbH - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.workbench.texteditor.tests;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+
+import java.util.ResourceBundle;
+
+import org.junit.Test;
+
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.text.tests.Accessor;
+
+import org.eclipse.jface.text.IFindReplaceTarget;
+import org.eclipse.jface.text.TextViewer;
+
+import org.eclipse.ui.internal.findandreplace.SearchOptions;
+
+public class FindReplaceOverlayTest extends FindReplaceUITest