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..d70435af271 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,7 @@ private OverlayPreferenceStore createDialogOverlayStore() { ArrayList overlayKeys= new ArrayList<>(); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_SPACES)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, "usefindreplaceoverlay")); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_SPACES)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_SPACES)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_IDEOGRAPHIC_SPACES)); @@ -729,6 +730,7 @@ private OverlayPreferenceStore createOverlayStore() { overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractTextEditor.PREFERENCE_COLOR_FIND_SCOPE)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR)); + overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, "usefindreplaceoverlay")); //$NON-NLS-1$ overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE)); overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH)); @@ -859,6 +861,10 @@ public void widgetSelected(SelectionEvent e) { IntegerDomain lineSpaceDomain= new IntegerDomain(0, 1000); addTextField(appearanceComposite, lineSpacing, lineSpaceDomain, 15, 0); + label= "Use modern Find/Replace-Overlay"; //$NON-NLS-1$ + Preference useFindReplaceOverlay= new Preference("usefindreplaceoverlay", label, null); //$NON-NLS-1$ + addCheckBox(appearanceComposite, useFindReplaceOverlay, new BooleanDomain(), 0); + label= TextEditorMessages.TextEditorPreferencePage_enableWordWrap; Preference enableWordWrap= new Preference(AbstractTextEditor.PREFERENCE_WORD_WRAP_ENABLED, label, null); addCheckBox(appearanceComposite, enableWordWrap, new BooleanDomain(), 0); diff --git a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java index 0bb4b3347c8..57ce2a0a744 100644 --- a/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java +++ b/bundles/org.eclipse.ui.editors/src/org/eclipse/ui/texteditor/AbstractDecoratedTextEditorPreferenceConstants.java @@ -723,6 +723,7 @@ private AbstractDecoratedTextEditorPreferenceConstants() { * @param store the preference store to be initialized */ public static void initializeDefaultValues(IPreferenceStore store) { + store.setDefault("usefindreplaceoverlay", false); //$NON-NLS-1$ store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.USE_ANNOTATIONS_PREFERENCE_PAGE, false); store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.USE_QUICK_DIFF_PREFERENCE_PAGE, false); @@ -736,6 +737,7 @@ public static void initializeDefaultValues(IPreferenceStore store) { store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE, 200); + store.setDefault("usefindreplaceoverlay", false); store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN, false); store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN, 80); store.setDefault(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_ALLOW_OVERRIDE, false); diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png new file mode 100644 index 00000000000..1a71a7923a9 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png new file mode 100644 index 00000000000..7eaaff97987 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png new file mode 100644 index 00000000000..9fcc646d92b Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png new file mode 100644 index 00000000000..f2e6a039c5d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png new file mode 100644 index 00000000000..7be33cd83c8 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png new file mode 100644 index 00000000000..018dc33f6fe Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png new file mode 100644 index 00000000000..c6aeae4d462 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif new file mode 100644 index 00000000000..81fb7b4a39d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png new file mode 100644 index 00000000000..d9a07e503ef Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png new file mode 100644 index 00000000000..b6af9dbf57b Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png new file mode 100644 index 00000000000..997b0545b89 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png new file mode 100644 index 00000000000..bd2eacec42d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java new file mode 100644 index 00000000000..379d1c6c954 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +public class FindReplaceLogicFindAllStatus implements IFindReplaceLogicStatus { + private int selectCount; + + public FindReplaceLogicFindAllStatus(int selectCount) { + if (selectCount <= 0) { + // invalid value - what to do? Throw an exception?! @HeikoKlare + } + this.selectCount = selectCount; + } + + public int getSelectCount() { + return selectCount; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java new file mode 100644 index 00000000000..2ad8406eb87 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +/** + * Avoid using this class. It is used as a glue to correctly map to the + * error-messages generated by RegEx-Errors which are directly displayed in + * plain text. + * + * Prefer using and extending {@link FindReplaceLogicStatus} + */ +public class FindReplaceLogicMessage implements IFindReplaceLogicStatus { + + private String message; + + public FindReplaceLogicMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java new file mode 100644 index 00000000000..7ef140b2b46 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +public class FindReplaceLogicReplaceAllStatus implements IFindReplaceLogicStatus { + private int replaceCount; + + public FindReplaceLogicReplaceAllStatus(int replaceCount) { + this.replaceCount = replaceCount; + } + + public int getReplaceCount() { + return replaceCount; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java new file mode 100644 index 00000000000..0d3bf628bbc --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +public class FindReplaceLogicStatus implements IFindReplaceLogicStatus { + + public enum MessageCode { + NO_MATCH, + NONE, WRAPPED, READONLY, + } + + private MessageCode messageCode; + + public FindReplaceLogicStatus(MessageCode errorCode) { + this.messageCode = errorCode; + } + + public MessageCode getMessageCode() { + return messageCode; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java new file mode 100644 index 00000000000..7d367a059e6 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +/** + * Marker interface for all Status-objects that a Find/Replace-Operation could + * return + */ +public interface IFindReplaceLogicStatus { + public T visit(IFindReplaceLogicStatusVisitor visitor); +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java new file mode 100644 index 00000000000..bdfdb87a740 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2023 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.internal.findandreplace; + +public interface IFindReplaceLogicStatusVisitor { + public T visit(IFindReplaceLogicStatus status); + + public T visit(FindReplaceLogicReplaceAllStatus status); + + public T visit(FindReplaceLogicStatus status); + + public T visit(FindReplaceLogicMessage status); + + public T visit(FindReplaceLogicFindAllStatus status); + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java index 5839928d3c8..50fec2f6a03 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java @@ -21,6 +21,8 @@ import org.eclipse.swt.widgets.Shell; import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.IPageChangedListener; import org.eclipse.jface.dialogs.PageChangedEvent; @@ -34,16 +36,15 @@ import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchWindow; - /** - * An action which opens a Find/Replace dialog. - * The dialog while open, tracks the active workbench part - * and retargets itself to the active find/replace target. + * An action which opens a Find/Replace dialog. The dialog while open, tracks + * the active workbench part and retargets itself to the active find/replace + * target. *

- * It can also be used without having an IWorkbenchPart e.g. for - * dialogs or wizards by just providing a {@link Shell} and an {@link IFindReplaceTarget}. - * In this case the dialog won't be shared with the one - * used for the active workbench part. + * It can also be used without having an IWorkbenchPart e.g. for dialogs or + * wizards by just providing a {@link Shell} and an {@link IFindReplaceTarget}. + * In this case the dialog won't be shared with the one used for the active + * workbench part. *

*

* This class may be instantiated; it is not intended to be subclassed. @@ -54,16 +55,23 @@ */ public class FindReplaceAction extends ResourceAction implements IUpdate { + private static boolean shouldShowModernOverlay() { + IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode("org.eclipse.ui.editors"); //$NON-NLS-1$ + return preferences.getBoolean("usefindreplaceoverlay", false); //$NON-NLS-1$ + } + /** - * Represents the "global" find/replace dialog. It tracks the active - * part and retargets the find/replace dialog accordingly. The find/replace - * target is retrieved from the active part using + * Represents the "global" find/replace dialog. It tracks the active part and + * retargets the find/replace dialog accordingly. The find/replace target is + * retrieved from the active part using * getAdapter(IFindReplaceTarget.class). *

- * The stub has the same life cycle as the find/replace dialog.

+ * The stub has the same life cycle as the find/replace dialog. + *

*

- * If no IWorkbenchPart is available a Shell must be provided - * In this case the IFindReplaceTarget will never change.

+ * If no IWorkbenchPart is available a Shell must be provided In this case the + * IFindReplaceTarget will never change. + *

*/ static class FindReplaceDialogStub implements IPartListener2, IPageChangedListener, DisposeListener { @@ -86,8 +94,8 @@ static class FindReplaceDialogStub implements IPartListener2, IPageChangedListen */ public FindReplaceDialogStub(IWorkbenchPartSite site) { this(site.getShell()); - fWindow= site.getWorkbenchWindow(); - IPartService service= fWindow.getPartService(); + fWindow = site.getWorkbenchWindow(); + IPartService service = fWindow.getPartService(); service.addPartListener(this); partActivated(service.getActivePart()); } @@ -99,7 +107,7 @@ public FindReplaceDialogStub(IWorkbenchPartSite site) { * @since 3.3 */ public FindReplaceDialogStub(Shell shell) { - fDialog= new FindReplaceDialog(shell); + fDialog = new FindReplaceDialog(shell); fDialog.create(); fDialog.getShell().addDisposeListener(this); } @@ -114,19 +122,19 @@ public FindReplaceDialog getDialog() { } private void partActivated(IWorkbenchPart part) { - IFindReplaceTarget target= part == null ? null : part.getAdapter(IFindReplaceTarget.class); - fPreviousPart= fPart; - fPart= target == null ? null : part; + IFindReplaceTarget target = part == null ? null : part.getAdapter(IFindReplaceTarget.class); + fPreviousPart = fPart; + fPart = target == null ? null : part; if (fPreviousTarget != target) { - fPreviousTarget= target; + fPreviousTarget = target; if (fDialog != null) { - boolean isEditable= false; + boolean isEditable = false; if (fPart instanceof ITextEditorExtension2) { - ITextEditorExtension2 extension= (ITextEditorExtension2) fPart; - isEditable= extension.isEditorInputModifiable(); + ITextEditorExtension2 extension = (ITextEditorExtension2) fPart; + isEditable = extension.isEditorInputModifiable(); } else if (target != null) - isEditable= target.isEditable(); + isEditable = target.isEditable(); fDialog.updateTarget(target, isEditable, false); } } @@ -140,38 +148,38 @@ public void partActivated(IWorkbenchPartReference partRef) { @Override public void pageChanged(PageChangedEvent event) { if (event.getSource() instanceof IWorkbenchPart) - partActivated((IWorkbenchPart)event.getSource()); + partActivated((IWorkbenchPart) event.getSource()); } @Override public void partClosed(IWorkbenchPartReference partRef) { - IWorkbenchPart part= partRef.getPart(true); + IWorkbenchPart part = partRef.getPart(true); if (part == fPreviousPart) { - fPreviousPart= null; - fPreviousTarget= null; + fPreviousPart = null; + fPreviousTarget = null; } if (part == fPart) - partActivated((IWorkbenchPart)null); + partActivated((IWorkbenchPart) null); } @Override public void widgetDisposed(DisposeEvent event) { if (fgFindReplaceDialogStub == this) - fgFindReplaceDialogStub= null; + fgFindReplaceDialogStub = null; - if(fgFindReplaceDialogStubShell == this) - fgFindReplaceDialogStubShell= null; + if (fgFindReplaceDialogStubShell == this) + fgFindReplaceDialogStubShell = null; if (fWindow != null) { fWindow.getPartService().removePartListener(this); - fWindow= null; + fWindow = null; } - fDialog= null; - fPart= null; - fPreviousPart= null; - fPreviousTarget= null; + fDialog = null; + fPart = null; + fPreviousPart = null; + fPreviousTarget = null; } @Override @@ -199,8 +207,8 @@ public void partVisible(IWorkbenchPartReference partRef) { } /** - * Checks if the dialogs shell is the same as the given shell and if not clears - * the stub and closes the dialog. + * Checks if the dialogs shell is the same as the given shell and + * if not clears the stub and closes the dialog. * * @param shell the shell check * @since 3.3 @@ -208,10 +216,10 @@ public void partVisible(IWorkbenchPartReference partRef) { public void checkShell(Shell shell) { if (fDialog != null && shell != fDialog.getParentShell()) { if (fgFindReplaceDialogStub == this) - fgFindReplaceDialogStub= null; + fgFindReplaceDialogStub = null; if (fgFindReplaceDialogStubShell == this) - fgFindReplaceDialogStubShell= null; + fgFindReplaceDialogStubShell = null; fDialog.close(); } @@ -219,17 +227,20 @@ public void checkShell(Shell shell) { } - /** * Listener for disabling the dialog on shell close. *

- * This stub is shared amongst IWorkbenchParts.

+ * This stub is shared amongst IWorkbenchParts. + *

*/ private static FindReplaceDialogStub fgFindReplaceDialogStub; - /** Listener for disabling the dialog on shell close. + /** + * Listener for disabling the dialog on shell close. *

- * This stub is shared amongst Shells.

+ * This stub is shared amongst Shells. + *

+ * * @since 3.3 */ private static FindReplaceDialogStub fgFindReplaceDialogStubShell; @@ -242,45 +253,51 @@ public void checkShell(Shell shell) { private IWorkbenchWindow fWorkbenchWindow; /** * The shell to use if the action is created with a shell. + * * @since 3.3 */ private Shell fShell; + private FindReplaceOverlay overlay; + /** * Creates a new find/replace action for the given workbench part. *

- * The action configures its visual representation from the given - * resource bundle.

+ * The action configures its visual representation from the given resource + * bundle. + *

* - * @param bundle the resource bundle - * @param prefix a prefix to be prepended to the various resource keys - * (described in ResourceAction constructor), or - * null if none - * @param workbenchPart the workbench part + * @param bundle the resource bundle + * @param prefix a prefix to be prepended to the various resource keys + * (described in ResourceAction constructor), + * or null if none + * @param workbenchPart the workbench part * @see ResourceAction#ResourceAction(ResourceBundle, String) */ public FindReplaceAction(ResourceBundle bundle, String prefix, IWorkbenchPart workbenchPart) { super(bundle, prefix); Assert.isLegal(workbenchPart != null); - fWorkbenchPart= workbenchPart; + fWorkbenchPart = workbenchPart; update(); } /** * Creates a new find/replace action for the given target and shell. *

- * This can be used without having an IWorkbenchPart e.g. for - * dialogs or wizards.

+ * This can be used without having an IWorkbenchPart e.g. for dialogs or + * wizards. + *

*

- * The action configures its visual representation from the given - * resource bundle.

+ * The action configures its visual representation from the given resource + * bundle. + *

* * @param bundle the resource bundle * @param prefix a prefix to be prepended to the various resource keys - * (described in ResourceAction constructor), or - * null if none + * (described in ResourceAction constructor), or + * null if none * @param target the IFindReplaceTarget to use - * @param shell the shell + * @param shell the shell * @see ResourceAction#ResourceAction(ResourceBundle, String) * * @since 3.3 @@ -288,81 +305,117 @@ public FindReplaceAction(ResourceBundle bundle, String prefix, IWorkbenchPart wo public FindReplaceAction(ResourceBundle bundle, String prefix, Shell shell, IFindReplaceTarget target) { super(bundle, prefix); Assert.isLegal(target != null && shell != null); - fTarget= target; - fShell= shell; + fTarget = target; + fShell = shell; update(); } /** - * Creates a new find/replace action for the given workbench window. - * The action configures its visual representation from the given - * resource bundle. + * Creates a new find/replace action for the given workbench window. The action + * configures its visual representation from the given resource bundle. * - * @param bundle the resource bundle - * @param prefix a prefix to be prepended to the various resource keys - * (described in ResourceAction constructor), or - * null if none + * @param bundle the resource bundle + * @param prefix a prefix to be prepended to the various resource keys + * (described in ResourceAction + * constructor), or null if none * @param workbenchWindow the workbench window * @see ResourceAction#ResourceAction(ResourceBundle, String) * - * @deprecated use FindReplaceAction(ResourceBundle, String, IWorkbenchPart) instead + * @deprecated use FindReplaceAction(ResourceBundle, String, IWorkbenchPart) + * instead */ @Deprecated public FindReplaceAction(ResourceBundle bundle, String prefix, IWorkbenchWindow workbenchWindow) { super(bundle, prefix); - fWorkbenchWindow= workbenchWindow; + fWorkbenchWindow = workbenchWindow; update(); } @Override public void run() { - if (fTarget == null) - return; + if (shouldShowModernOverlay()) { + showModernOverlay(); + } else { + if (fTarget == null) + return; + showClassicDialog(); + } + } + + /** + * @since 3.17 + */ + public void showClassicDialog() { final FindReplaceDialog dialog; final boolean isEditable; - if(fShell == null) { + if (fShell == null) { if (fgFindReplaceDialogStub != null) { - Shell shell= fWorkbenchPart.getSite().getShell(); + Shell shell = fWorkbenchPart.getSite().getShell(); fgFindReplaceDialogStub.checkShell(shell); } if (fgFindReplaceDialogStub == null) - fgFindReplaceDialogStub= new FindReplaceDialogStub(fWorkbenchPart.getSite()); + fgFindReplaceDialogStub = new FindReplaceDialogStub(fWorkbenchPart.getSite()); if (fWorkbenchPart instanceof ITextEditorExtension2) - isEditable= ((ITextEditorExtension2) fWorkbenchPart).isEditorInputModifiable(); + isEditable = ((ITextEditorExtension2) fWorkbenchPart).isEditorInputModifiable(); else - isEditable= fTarget.isEditable(); + isEditable = fTarget.isEditable(); - dialog= fgFindReplaceDialogStub.getDialog(); + dialog = fgFindReplaceDialogStub.getDialog(); } else { if (fgFindReplaceDialogStubShell != null) { fgFindReplaceDialogStubShell.checkShell(fShell); } if (fgFindReplaceDialogStubShell == null) - fgFindReplaceDialogStubShell= new FindReplaceDialogStub(fShell); + fgFindReplaceDialogStubShell = new FindReplaceDialogStub(fShell); - isEditable= fTarget.isEditable(); - dialog= fgFindReplaceDialogStubShell.getDialog(); + isEditable = fTarget.isEditable(); + dialog = fgFindReplaceDialogStubShell.getDialog(); } dialog.updateTarget(fTarget, isEditable, true); dialog.open(); } + private void showModernOverlay() { + if (overlay == null) { + Shell shellToUse; + if (fShell == null) { + shellToUse = fWorkbenchPart.getSite().getShell(); + } else { + shellToUse = fShell; + } + overlay = new FindReplaceOverlay(shellToUse, fWorkbenchPart, fTarget, this); + } + overlay.create(); + overlay.open(); + } + + /** + * Closes the "modern" overlay. Typically called by the overlay itself. + * + * @since 3.17 + */ + public void closeModernOverlay() { + if (overlay != null) { + overlay.close(); + } + } + @Override public void update() { - if(fShell == null){ + if (fShell == null) { if (fWorkbenchPart == null && fWorkbenchWindow != null) - fWorkbenchPart= fWorkbenchWindow.getPartService().getActivePart(); + fWorkbenchPart = fWorkbenchWindow.getPartService().getActivePart(); if (fWorkbenchPart != null) - fTarget= fWorkbenchPart.getAdapter(IFindReplaceTarget.class); + fTarget = fWorkbenchPart.getAdapter(IFindReplaceTarget.class); else - fTarget= null; + fTarget = null; } setEnabled(fTarget != null && fTarget.canPerformFind()); } 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..8354552532c --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java @@ -0,0 +1,756 @@ +/******************************************************************************* + * Copyright (c) 2023 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.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +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.Link; +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.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.window.Window; + +import org.eclipse.jface.text.IFindReplaceTarget; + +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.FindReplaceLogicMessageGenerator; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.SearchOptions; +import org.eclipse.ui.internal.findandreplace.status.IFindReplaceStatus; + +/** + * @since 3.17 + */ +public class FindReplaceOverlay extends Dialog { + + FindReplaceAction parentAction; + FindReplaceLogic findReplacer; + IWorkbenchPart targetPart; + boolean overlayOpen; + boolean replaceBarOpen; + + Composite container; + Button replaceToggle; + + Composite contentGroup; + + Composite searchContainer; + Composite searchBarContainer; + Text searchBar; + ToolBar searchTools; + ToolItem wholeWordSearchButton; + ToolItem caseSensitiveSearchButton; + ToolItem regexSearchButton; + ToolItem searchUpButton; + ToolItem searchDownButton; + ToolItem searchAllButton; + + Composite replaceContainer; + Composite replaceBarContainer; + Text replaceBar; + ToolBar replaceTools; + ToolItem replaceButton; + ToolItem replaceAllButton; + + Link openOldDialog; + + public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target, + FindReplaceAction parentAction) { + super(parent); + this.parentAction = parentAction; + createFindReplacer(target); + + setShellStyle(SWT.MODELESS); + setBlockOnOpen(false); + targetPart = part; + } + + @Override + protected boolean isResizable() { + return false; + } + + private void createFindReplacer(IFindReplaceTarget target) { + findReplacer = new FindReplaceLogic(); + boolean isTargetEditable = false; + if (target != null) { + isTargetEditable = target.isEditable(); + } + findReplacer.updateTarget(target, isTargetEditable); + findReplacer.activate(SearchOptions.INCREMENTAL); + findReplacer.activate(SearchOptions.GLOBAL); + findReplacer.activate(SearchOptions.WRAP); + findReplacer.activate(SearchOptions.FORWARD); + } + + KeyListener shortcuts = new KeyListener() { + + private void performEnterAction(KeyEvent e) { + // probably not the right way to implement this. + // What do you think, @HeikoKlare? + boolean isShiftPressed = (e.stateMask & SWT.SHIFT) != 0; + boolean isCtrlPressed = (e.stateMask & SWT.CTRL) != 0; + if (okayToUse(replaceBar) && replaceBar.isFocusControl()) { + if (isCtrlPressed) { + findReplacer.performReplaceAll(getFindString(), getReplaceString(), getShell().getDisplay()); + } else { + performSingleReplace(); + } + } else { + if (isCtrlPressed) { + findReplacer.performSelectAll(getFindString(), getShell().getDisplay()); + } else { + boolean oldForwardSearchSetting = findReplacer.isActive(SearchOptions.FORWARD); + activateInFindReplacerIf(SearchOptions.FORWARD, !isShiftPressed); + findReplacer.deactivate(SearchOptions.INCREMENTAL); + findReplacer.performSearch(getFindString()); + activateInFindReplacerIf(SearchOptions.FORWARD, oldForwardSearchSetting); + findReplacer.activate(SearchOptions.INCREMENTAL); + } + } + } + + @Override + public void keyPressed(KeyEvent e) { + if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'F' || e.keyCode == 'f')) { + parentAction.closeModernOverlay(); + } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'R' || e.keyCode == 'r')) { + replaceToggle.setSelection(!replaceToggle.getSelection()); + replaceToggle.notifyListeners(SWT.Selection, null); + } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'W' || e.keyCode == 'w')) { + wholeWordSearchButton.setSelection(!wholeWordSearchButton.getSelection()); + wholeWordSearchButton.notifyListeners(SWT.Selection, null); + } else if (e.keyCode == SWT.CR) { + performEnterAction(e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + // Do nothing + } + + }; + ControlListener shellMovementListener = new ControlListener() { + @Override + public void controlMoved(ControlEvent e) { + positionToPart(); + } + + @Override + public void controlResized(ControlEvent e) { + positionToPart(); + } + }; + PaintListener widgetMovementListener = new PaintListener() { + + @Override + public void paintControl(PaintEvent e) { + positionToPart(); + } + + }; + 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 + } + }; + + public 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; + } + + @Override + public void create() { + if (overlayOpen) { + return; + } + super.create(); + } + + @Override + public boolean close() { + if (!overlayOpen) { + return true; + } + overlayOpen = false; + replaceBarOpen = false; + unbindListeners(); + container.dispose(); + return super.close(); + } + + @Override + public int open() { + int returnCode; + positionToPart(); + if (overlayOpen) { + searchBar.forceFocus(); + findReplacer.performIncrementalSearch(getFindString()); + returnCode = Window.CANCEL; + } else { + bindListeners(); + returnCode = super.open(); + } + overlayOpen = true; + return returnCode; + } + + private void unbindListeners() { + 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() { + 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) { + Control ret = createDialog(parent); + initFindStringFromSelection(); + positionToPart(); + return ret; + } + + public Control createDialog(final Composite parent) { + createMainContainer(parent); + + // createLinks(); + createFindContainer(); + createSearchBar(); + createSearchTools(); + + container.layout(); + + applyDialogFont(container); + return container; + } + + @SuppressWarnings("unused") + private void createLinks() { + openOldDialog = new Link(contentGroup, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(openOldDialog); + openOldDialog.setText("classic Find/Replace"); //$NON-NLS-1$ + openOldDialog.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + parentAction.showClassicDialog(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + + }); + } + + private void createSearchTools() { + searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.CENTER).applyTo(searchTools); + wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK); + wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_WHOLE_WORD)); + wholeWordSearchButton.setToolTipText("Only find in whole words"); //$NON-NLS-1$ + wholeWordSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK); + caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_CASE_SENSITIVE)); + caseSensitiveSearchButton.setToolTipText("Match case"); //$NON-NLS-1$ + caseSensitiveSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + regexSearchButton = new ToolItem(searchTools, SWT.CHECK); + regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_FIND_REGEX)); + regexSearchButton.setToolTipText("Search for a regular expression"); //$NON-NLS-1$ + regexSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.REGEX, regexSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + + regexSearchButton = new ToolItem(searchTools, SWT.SEPARATOR); + + searchAllButton = new ToolItem(searchTools, SWT.PUSH); + searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_SEARCH_ALL)); + searchAllButton.setToolTipText("Search all (Ctrl + Enter)"); //$NON-NLS-1$ + searchAllButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.performSelectAll(getFindString(), getShell().getDisplay()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + + }); + searchUpButton = new ToolItem(searchTools, SWT.PUSH); + searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.ELCL_FIND_PREV)); + searchUpButton.setToolTipText("Search backward (Shift + Enter)"); //$NON-NLS-1$ + searchUpButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.deactivate(SearchOptions.FORWARD); + findReplacer.performSearch(getFindString()); + findReplacer.activate(SearchOptions.FORWARD); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + searchDownButton = new ToolItem(searchTools, SWT.PUSH); + searchDownButton.setSelection(true); // by default, search down + searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.ELCL_FIND_NEXT)); + searchDownButton.setToolTipText("Search forward (Enter)"); //$NON-NLS-1$ + searchDownButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + // TODO extract into transaction + findReplacer.activate(SearchOptions.FORWARD); + findReplacer.activate(SearchOptions.INCREMENTAL); + findReplacer.deactivate(SearchOptions.FORWARD); + findReplacer.performSearch(getFindString()); + findReplacer.deactivate(SearchOptions.INCREMENTAL); + findReplacer.activate(SearchOptions.FORWARD); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + + } + + private void createReplaceTools() { + replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.CENTER).applyTo(replaceTools); + replaceButton = new ToolItem(replaceTools, SWT.PUSH); + replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE)); + replaceButton.setToolTipText("Replace (Enter)"); //$NON-NLS-1$ + replaceButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + performSingleReplace(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + replaceAllButton = new ToolItem(replaceTools, SWT.PUSH); + replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE_ALL)); + replaceAllButton.setToolTipText("Replace All (Ctrl + Enter)"); //$NON-NLS-1$ + replaceAllButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.performReplaceAll(getFindString(), getReplaceString(), getShell().getDisplay()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + } + + private void createSearchBar() { + searchBar = new Text(searchBarContainer, SWT.SINGLE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBar); + searchBar.forceFocus(); + searchBar.selectAll(); + searchBar.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + findReplacer.performIncrementalSearch(getFindString()); + } + }); + + searchBar.addKeyListener(shortcuts); + + searchBar.setMessage("Find"); //$NON-NLS-1$ + } + + private void createReplaceBar() { + replaceBar = new Text(replaceBarContainer, SWT.SINGLE); + GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(replaceBar); + replaceBar.setMessage("Replace"); //$NON-NLS-1$ + 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).equalWidth(false).applyTo(searchContainer); + searchBarContainer = new Composite(searchContainer, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBarContainer); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).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).equalWidth(false) + .applyTo(replaceContainer); + replaceBarContainer = new Composite(replaceContainer, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL) + .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 (findReplacer.getTarget().isEditable()) { + createReplaceToggle(); + } + + contentGroup = new Composite(container, SWT.NULL); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).spacing(2, 2).applyTo(contentGroup); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup); + } + + private void createReplaceToggle() { + replaceToggle = new Button(container, SWT.PUSH); // https://stackoverflow.com/questions/33161797/how-to-remove-border-of-swt-button-so-that-it-seems-like-a-label + GridDataFactory.fillDefaults().grab(false, true).align(GridData.BEGINNING, GridData.FILL) + .applyTo(replaceToggle); + replaceToggle.setToolTipText("Toggle input for replace (Ctrl+R)"); //$NON-NLS-1$ + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_OPEN_REPLACE)); + replaceToggle.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + if (!replaceBarOpen) { + createReplaceDialog(); + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_CLOSE_REPLACE)); + } else { + hideReplace(); + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_OPEN_REPLACE)); + } + replaceToggle.setSelection(false); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + } + + public void hideReplace() { + if (!replaceBarOpen) { + return; + } + replaceBarOpen = false; + replaceContainer.dispose(); + replaceTools.dispose(); + replaceBar.dispose(); + positionToPart(); + searchBar.forceFocus(); + } + + public void createReplaceDialog() { + if (replaceBarOpen) { + return; + } + replaceBarOpen = true; + createReplaceContainer(); + createReplaceBar(); + createReplaceTools(); + positionToPart(); + 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) { + enableSearchTools(true); + enableReplaceTools(true); + enableReplaceToggle(true); + Point toolBarWidth = searchTools.getSize(); + GC gc = new GC(searchBar); + gc.setFont(searchBar.getFont()); + Point idealWidth = gc.stringExtent("THIS TEXT HAS A REASONABLE LENGTH FOR SEARCHING"); //$NON-NLS-1$ + Point idealCompromiseWidth = gc.stringExtent("THIS TEXT HAS A REASONABLE"); //$NON-NLS-1$ + Point worstCompromiseWidth = gc.stringExtent("THIS TEXT "); //$NON-NLS-1$ + + int newWidth = idealWidth.x + toolBarWidth.x; + if (newWidth > targetBounds.width * 0.7) { + newWidth = (int) (targetBounds.width * 0.7); + } + if (newWidth < idealCompromiseWidth.x + toolBarWidth.x) { + enableSearchTools(false); + enableReplaceTools(false); + } + if (newWidth < worstCompromiseWidth.x + toolBarWidth.x) { + newWidth = (int) (targetBounds.width * 0.95); + enableReplaceToggle(false); + } + return newWidth; + } + + private Point getNewPosition(Widget targetTextWidget, Point targetOrigin, Rectangle targetBounds) { + Point scrollBarSize = ((Scrollable) targetTextWidget).getVerticalBar().getSize(); + + int newX = targetOrigin.x + targetBounds.width - container.getBounds().width - scrollBarSize.x + - ((StyledText) targetTextWidget).getRightMargin(); + int newY = targetOrigin.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); + } + } + + public void positionToPart() { + getShell().layout(true); + container.layout(true); + if (targetPart instanceof StatusTextEditor textEditor) { + Control targetWidget = textEditor.getSourceViewer().getTextWidget(); + if (targetWidget == null || targetWidget.isDisposed()) { + this.close(); + return; + } + StyledText targetTextWidget = textEditor.getSourceViewer().getTextWidget(); + Point targetOrigin = targetTextWidget.toDisplay(0, 0); + Rectangle targetBounds = targetTextWidget.getBounds(); + + int newWidth = getIdealDialogWidth(targetBounds); + int newHeight = container.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + getShell().setSize(new Point(newWidth, newHeight)); + + Point newPosition = getNewPosition(targetTextWidget, targetOrigin, targetBounds); + getShell().setLocation(newPosition); + + repositionTextSelection(); + } + container.layout(true); + getShell().layout(true); + } + + private String getFindString() { + return searchBar.getText(); + } + + private String getReplaceString() { + if (replaceBar.isDisposed()) + return ""; //$NON-NLS-1$ + return replaceBar.getText(); + + } + + private void performSingleReplace() { + findReplacer.performSelectAndReplace(getFindString(), getReplaceString()); + findReplacer.performIncrementalSearch(getFindString()); + evaluateFindReplacerStatus(); + } + + private void initFindStringFromSelection() { + searchBar.setText(findReplacer.getTarget().getSelectionText()); + } + + private void evaluateFindReplacerStatus() { + IFindReplaceStatus status = findReplacer.getStatus(); + + String dialogMessage = status.accept(new FindReplaceLogicMessageGenerator()); + + if (status instanceof FindReplaceLogicStatus statusMessage) { + switch (statusMessage.getMessageCode()) { + case NO_MATCH: + case READONLY: + case WRAPPED: + tryToBeep(); + break; + case NONE: + default: + break; + } + } + + } + + private void tryToBeep() { + Shell dialogShell = getShell(); + if (dialogShell != null && !dialogShell.isDisposed()) { + getShell().getDisplay().beep(); + } + } + + private void activateInFindReplacerIf(SearchOptions option, boolean shouldActivate) { + if (shouldActivate) { + findReplacer.activate(option); + } else { + findReplacer.deactivate(option); + } + } + + private boolean okayToUse(Widget widget) { + return widget != null && !widget.isDisposed(); + } +} \ No newline at end of file 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..5430426e1f8 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2023 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; + +/** + * @HeikoKlare Modeled after TemplatePageImages.java - I'm not sure if this + * approach is good or bad. I copy-pasted methods, increasing + * code-duplication. We should consider implementing a unified + * method for inserting icons (.. or does it already exist?) + */ +class FindReplaceOverlayImages { + + static final String PREFIX_ELCL = TextEditorPlugin.PLUGIN_ID + ".elcl."; //$NON-NLS-1$ + + static final String PREFIX_DLCL = TextEditorPlugin.PLUGIN_ID + ".dlcl."; //$NON-NLS-1$ + + static final String PREFIX_OBJ = TextEditorPlugin.PLUGIN_ID + ".obj."; //$NON-NLS-1$ + + static final String ELCL_FIND_NEXT = PREFIX_ELCL + "select_next.png"; //$NON-NLS-1$ + + static final String ELCL_FIND_PREV = PREFIX_ELCL + "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_OPEN_REPLACE = PREFIX_OBJ + "open_replace.png"; //$NON-NLS-1$ + + static final String OBJ_CLOSE_REPLACE = PREFIX_OBJ + "close_replace.png"; //$NON-NLS-1$ + + static final String OBJ_SEARCH_ALL = PREFIX_OBJ + "search_all.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$ + + // Use IPath and toOSString to build the names to ensure they have the + // slashes correct + private final static String ELCL = ICONS_PATH + "elcl16/"; //$NON-NLS-1$ + + private final static String DLCL = ICONS_PATH + "dlcl16/"; //$NON-NLS-1$ + + private final static String OBJ = ICONS_PATH + "obj16/"; //$NON-NLS-1$ + + /** + * Declare all images + */ + private static void declareImages() { + declareRegistryImage(ELCL_FIND_NEXT, ELCL + "select_next.png"); //$NON-NLS-1$ + declareRegistryImage(ELCL_FIND_PREV, ELCL + "select_prev.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_FIND_REGEX, OBJ + "regex_gear.gif"); //$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_OPEN_REPLACE, OBJ + "open_replace.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_CLOSE_REPLACE, OBJ + "close_replace.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_SEARCH_ALL, OBJ + "search_all.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/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java index d6f5cf79438..d42427a7f8e 100644 --- a/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java +++ b/bundles/org.eclipse.ui.workbench/Eclipse UI/org/eclipse/ui/internal/dialogs/ViewsPreferencePage.java @@ -98,6 +98,7 @@ public class ViewsPreferencePage extends PreferencePage implements IWorkbenchPre private ITheme currentTheme; private String defaultTheme; private Button useRoundTabs; + private Button useFindReplaceOverlay; private Button enableMru; private Button useColoredLabels; @@ -185,6 +186,7 @@ protected Control createContents(Composite parent) { } private void createThemeIndependentComposits(Composite comp) { + createUseFindReplaceOverlay(comp); createUseRoundTabs(comp); createColoredLabelsPref(comp); createEnableMruPref(comp); @@ -289,6 +291,12 @@ protected void createUseRoundTabs(Composite composite) { useRoundTabs = createCheckButton(composite, WorkbenchMessages.ViewsPreference_useRoundTabs, enabled); } + protected void createUseFindReplaceOverlay(Composite composite) { + IEclipsePreferences prefs = getSwtRendererPreferences(); + boolean enabled = prefs.getBoolean("USE_FIND_REPLACE_OVERLAY", true); //$NON-NLS-1$ + useFindReplaceOverlay = createCheckButton(composite, "use modern Find/Replace-Overlay", enabled); //$NON-NLS-1$ + } + protected void createEnableMruPref(Composite composite) { createLabel(composite, ""); //$NON-NLS-1$ createLabel(composite, WorkbenchMessages.ViewsPreference_visibleTabs_description); @@ -332,6 +340,7 @@ public boolean performOk() { prefs.putBoolean(PartRenderingEngine.ENABLED_THEME_KEY, themingEnabled.getSelection()); prefs.putBoolean(CTabRendering.USE_ROUND_TABS, useRoundTabs.getSelection()); + prefs.putBoolean("USE_FIND_REPLACE_OVERLAY", useFindReplaceOverlay.getSelection()); //$NON-NLS-1$ try { prefs.flush(); } catch (BackingStoreException e) { @@ -407,6 +416,7 @@ protected void performDefaults() { useColoredLabels.setSelection(apiStore.getDefaultBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS)); useRoundTabs.setSelection(CTabRendering.USE_ROUND_TABS_DEFAULT); + useFindReplaceOverlay.setSelection(true); enableMru.setSelection(getDefaultMRUValue()); super.performDefaults(); } diff --git a/releng/org.eclipse.text.releng/.project b/releng/org.eclipse.text.releng/.project new file mode 100644 index 00000000000..c79a93b4658 --- /dev/null +++ b/releng/org.eclipse.text.releng/.project @@ -0,0 +1,11 @@ + + + org.eclipse.text.releng + + + + + + + + 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 3ac8f358be7..8ce61723ee7 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 @@ -10,7 +10,8 @@ Export-Package: org.eclipse.ui.workbench.texteditor.tests, org.eclipse.ui.workbench.texteditor.tests.minimap, org.eclipse.ui.workbench.texteditor.tests.revisions, - org.eclipse.ui.workbench.texteditor.tests.rulers + org.eclipse.ui.workbench.texteditor.tests.rulers, + org.eclipse.ui.findandreplace Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.jface.text;bundle-version="[3.5.0,4.0.0)", diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java new file mode 100644 index 00000000000..211d59d945e --- /dev/null +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright (c) 2023 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.findandreplace; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.text.TextViewer; + +import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicFindAllStatus; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicMessage; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicReplaceAllStatus; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.IFindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.SearchOptions; + + +public class FindReplaceLogicTest { + Shell parentShell; + + private IFindReplaceLogic setupFindReplaceLogicObject(TextViewer target) { + IFindReplaceLogic findReplaceLogic= new FindReplaceLogic(); + if (target != null) { + findReplaceLogic.updateTarget(target.getFindReplaceTarget(), true); + } + + return findReplaceLogic; + } + + private TextViewer setupTextViewer(String contentText) { + TextViewer textViewer= new TextViewer(parentShell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + textViewer.setDocument(new Document(contentText)); + textViewer.getControl().setFocus(); + return textViewer; + } + + @After + public void disposeShell() { + if (parentShell != null) { + parentShell.dispose(); + } + } + + @Before + public void setupShell() { + parentShell= new Shell(); + } + + @Test + @Ignore("https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203") + public void testPerformReplaceAllBackwards() { + TextViewer textViewer= setupTextViewer(""); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + + performReplaceAllBaseTestcases(findReplaceLogic, textViewer); + } + + @Test + public void testPerformReplaceAllForwards() { + TextViewer textViewer= setupTextViewer(""); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + performReplaceAllBaseTestcases(findReplaceLogic, textViewer); + } + + /** + * Expects the TextViewer to contain a document with "aaaa" + * + * @param findReplaceLogic logic-object that will be tested + * @param textViewer textviewer-object that contains the contents on which findReplaceLogic + * operates + */ + @SuppressWarnings("boxing") + private void performReplaceAllBaseTestcases(IFindReplaceLogic findReplaceLogic, TextViewer textViewer) { + Display display= parentShell.getDisplay(); + textViewer.setDocument(new Document("aaaa")); + + findReplaceLogic.performReplaceAll("a", "b", display); + assertThat(textViewer.getDocument().get(), equalTo("bbbb")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 4); + + findReplaceLogic.performReplaceAll("b", "aa", display); + assertThat(textViewer.getDocument().get(), equalTo("aaaaaaaa")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 4); + + findReplaceLogic.performReplaceAll("b", "c", display); + assertThat(textViewer.getDocument().get(), equalTo("aaaaaaaa")); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + + findReplaceLogic.performReplaceAll("aaaaaaaa", "d", display); // https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203 + assertThat(textViewer.getDocument().get(), equalTo("d")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("d", null, display); + assertThat(textViewer.getDocument().get(), equalTo("")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + textViewer.getDocument().set("f"); + findReplaceLogic.performReplaceAll("f", "", display); + assertThat(textViewer.getDocument().get(), equalTo("")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + + IFindReplaceTarget mockFindReplaceTarget= Mockito.mock(IFindReplaceTarget.class); + Mockito.when(mockFindReplaceTarget.isEditable()).thenReturn(false); + + findReplaceLogic.updateTarget(mockFindReplaceTarget, false); + findReplaceLogic.performReplaceAll("a", "b", display); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + @Test + public void testPerformReplaceAllForwardRegEx() { + TextViewer textViewer= setupTextViewer("hello@eclipse.com looks.almost@like_an_email"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.REGEX); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performReplaceAll(".+\\@.+\\.com", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo(" looks.almost@like_an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("( looks.)|(like_)", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 2); + + findReplaceLogic.performReplaceAll("[", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsMessageWithString(findReplaceLogic, "Unclosed character class near index 0\r\n" + + "[\r\n" + + "^"); + + } + + @Test + public void testPerformReplaceAllForward() { + TextViewer textViewer= setupTextViewer("hello@eclipse.com looks.almost@like_an_email"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.REGEX); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performReplaceAll(".+\\@.+\\.com", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo(" looks.almost@like_an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("( looks.)|(like_)", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 2); + + findReplaceLogic.performReplaceAll("[", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsMessageWithString(findReplaceLogic, "Unclosed character class near index 0\r\n" + + "[\r\n" + + "^"); + } + + @Test + public void testPerformSelectAndReplace() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performSearch(""); // select first, then replace. We don't need to perform a second search + findReplaceLogic.performSelectAndReplace("", " "); + assertThat(textViewer.getDocument().get(), equalTo("Hello World!")); + expectStatusEmpty(findReplaceLogic); + + findReplaceLogic.performSelectAndReplace("", " "); // perform the search yourself and replace that automatically + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusEmpty(findReplaceLogic); + } + + @Test + public void testPerformSelectAndReplaceBackward() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.deactivate(SearchOptions.FORWARD); + findReplaceLogic.activate(SearchOptions.WRAP); // this only works if the search was wrapped + + findReplaceLogic.performSearch(""); // select first, then replace. We don't need to perform a second search + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.WRAPPED); + findReplaceLogic.performSelectAndReplace("", " "); + assertThat(textViewer.getDocument().get(), equalTo("HelloWorld !")); + + findReplaceLogic.performSelectAndReplace("", " "); // perform the search yourself and replace that automatically + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusEmpty(findReplaceLogic); + } + + + @SuppressWarnings("boxing") + @Test + public void testPerformReplaceAndFind() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + boolean status = findReplaceLogic.performReplaceAndFind("", " "); + assertThat(status, is(true)); + assertThat(textViewer.getDocument().get(), equalTo("Hello World!")); + assertThat(findReplaceLogic.getTarget().getSelectionText(), equalTo("")); + expectStatusEmpty(findReplaceLogic); + + status= findReplaceLogic.performReplaceAndFind("", " "); + assertThat(status, is(true)); + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + + status= findReplaceLogic.performReplaceAndFind("", " "); + assertEquals("Status wasn't correctly returned", false, status); + assertEquals("Text shouldn't have been changed", "Hello World !", textViewer.getDocument().get()); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + @Test + public void testPerformSelectAllForward() { + TextViewer textViewer= setupTextViewer("AbAbAbAb"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performSelectAll("b", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 4); + // I don't have access to getAllSelectionPoints or similar (not yet implemented), so I cannot really test for correct behavior + // related to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1047 + + findReplaceLogic.performSelectAll("AbAbAbAb", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 1); + } + + + @Test + @Ignore("https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203") + public void testPerformSelectAllBackward() { + TextViewer textViewer= setupTextViewer("AbAbAbAb"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.deactivate(SearchOptions.FORWARD); + + findReplaceLogic.performSelectAll("b", parentShell.getDisplay()); // https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203 maybe related? + expectStatusIsFindAllWithCount(findReplaceLogic, 4); + // I don't have access to getAllSelectionPoints or similar (not yet implemented), so I cannot really test for correct behavior + // related to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1047 + + findReplaceLogic.performSelectAll("AbAbAbAb", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 1); + } + + @Test + public void testSelectWholeWords() { + TextViewer textViewer= setupTextViewer("Hello World of get and getters, set and setters"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + findReplaceLogic.activate(SearchOptions.WHOLE_WORD); + findReplaceLogic.deactivate(SearchOptions.WRAP); + + findReplaceLogic.performSearch("get"); + findReplaceLogic.performSearch("get"); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + private void expectStatusEmpty(IFindReplaceLogic findReplaceLogic) { + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NONE); + } + + private void expectStatusIsCode(IFindReplaceLogic findReplaceLogic, FindReplaceLogicStatus.MessageCode code) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicStatus.class)); + assertThat(((FindReplaceLogicStatus) findReplaceLogic.getStatus()).getMessageCode(), equalTo(code)); + } + + @SuppressWarnings("boxing") + private void expectStatusIsReplaceAllWithCount(IFindReplaceLogic findReplaceLogic, int count) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicReplaceAllStatus.class)); + assertThat(((FindReplaceLogicReplaceAllStatus) findReplaceLogic.getStatus()).getReplaceCount(), equalTo(count)); + } + + @SuppressWarnings("boxing") + private void expectStatusIsFindAllWithCount(IFindReplaceLogic findReplaceLogic, int count) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicFindAllStatus.class)); + assertThat(((FindReplaceLogicFindAllStatus) findReplaceLogic.getStatus()).getSelectCount(), equalTo(count)); + } + + private void expectStatusIsMessageWithString(IFindReplaceLogic findReplaceLogic, String message) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicMessage.class)); + assertThat(((FindReplaceLogicMessage) findReplaceLogic.getStatus()).getMessage(), equalTo(message)); + } + +} diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java index 2a4c0738cfa..b5099262f89 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java @@ -54,7 +54,6 @@ public class FindReplaceDialogTest { @Rule public TestName testName= new TestName(); - private TextViewer fTextViewer; private static void runEventQueue() { diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java index c1b2c55bd49..9d34cf24f25 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java @@ -32,7 +32,6 @@ */ @RunWith(Suite.class) @SuiteClasses({ - FindReplaceDialogTest.class, HippieCompletionTest.class, RangeTest.class, ChangeRegionTest.class,