From 0455efe2e0849591bd21ab2034714bc2c385ea9c Mon Sep 17 00:00:00 2001 From: Maximilian Wittmer Date: Tue, 19 Sep 2023 10:02:09 +0200 Subject: [PATCH] Implemented Find-Replace Overlay. In response to issue #1090. Goals for this overlay are a simple and modern UI/UX for finding/replacing in eclipse. !!!!! This commit is only a functional commit meant to showcase a new feature. The code still has several flaws and needs a lot of improvement and redesign before being considered for Merging. !!!!! -- [Modern Find/Replace] Refactor FindReplaceDialog The FindReplaceDialog's business-logic is now handled in the FindReplacer. The FindReplacer communicates using a FindReplaceStatus object which is polled by FindReplaceDialog as needed and allows for displaying correct messages in the Interface. This Refactoring is required so that the business-logic can be reused in the new FindReplace overlay, as proposed in issue eclipse-platform#1090 --- .../META-INF/MANIFEST.MF | 2 +- .../META-INF/MANIFEST.MF | 2 +- .../icons/full/dlcl16/select_next.png | Bin 0 -> 315 bytes .../icons/full/dlcl16/select_prev.png | Bin 0 -> 315 bytes .../icons/full/elcl16/select_next.png | Bin 0 -> 366 bytes .../icons/full/elcl16/select_prev.png | Bin 0 -> 364 bytes .../icons/full/obj16/case_sensitive.png | Bin 0 -> 500 bytes .../icons/full/obj16/close_replace.png | Bin 0 -> 117 bytes .../icons/full/obj16/open_replace.png | Bin 0 -> 366 bytes .../icons/full/obj16/regex_gear.gif | Bin 0 -> 159 bytes .../icons/full/obj16/replace.png | Bin 0 -> 311 bytes .../icons/full/obj16/replace_all.png | Bin 0 -> 631 bytes .../icons/full/obj16/search_all.png | Bin 0 -> 259 bytes .../icons/full/obj16/whole_word.png | Bin 0 -> 394 bytes .../ui/texteditor/FindReplaceAction.java | 225 +++-- .../ui/texteditor/FindReplaceDialog.java | 937 +++--------------- .../ui/texteditor/FindReplaceOverlay.java | 681 +++++++++++++ .../texteditor/FindReplaceOverlayImages.java | 172 ++++ .../ui/texteditor/FindReplaceStatus.java | 59 ++ .../eclipse/ui/texteditor/FindReplacer.java | 793 +++++++++++++++ 20 files changed, 1993 insertions(+), 878 deletions(-) create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceStatus.java create mode 100644 bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplacer.java diff --git a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF index a048d96936e..0ce5366cbf8 100644 --- a/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.jface.text/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.jface.text -Bundle-Version: 3.24.200.qualifier +Bundle-Version: 3.25.0.qualifier Bundle-Vendor: %providerName Bundle-Localization: plugin Export-Package: diff --git a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF index d11f2afbac8..f59fe1a63c2 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor; singleton:=true -Bundle-Version: 3.17.200.qualifier +Bundle-Version: 3.18.0.qualifier Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin Bundle-ActivationPolicy: lazy Bundle-Vendor: %providerName 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 0000000000000000000000000000000000000000..1a71a7923a9e10c4bbfc5863166958ab8c73a665 GIT binary patch literal 315 zcmV-B0mS}^P)JdJNx!sfs9~+fGzlB`nxGA}teqpubfg)zx^B zW%=?6yI~lH-$4+(eBTFxo+%R+P{TNmuq?~UG)=2SSm1dcFvd726Bf9x3-dg4P$n#J z9A_PS3uUJSw1X|D1h#F%Fbo`&9SSVVf-QaDbNv7QCNNDC@;ryG>kg@^3aIu?U>Jsk zW|n0cv~9afS(bDZhIP-~9cY?%MLSE91e&JdD2jqcr{??N5>X=IG%Su|sOx%#no<0N z%{QT2Ac?N)EQ%sX(-cs+;QK@P8VOZZuLuw*yr23T|44(8 z9AOLmE|3 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9fcc646d92bc2e4c9c314f23cb9c400105480ca1 GIT binary patch literal 366 zcmV-!0g?WRP){{8*le~=i_2JC7S`~UyX z6BG;*BiewS^`ig(|GbTYL1IK3u)R+7|G)2-P%uc0Xaly@i2VQeABjbPXccNf)c?<(KmJ9!XezNKR-!~ia;RikJP1VxhH&%!zVfDtkLLrZh<>KF;9Vz|)_xS=8{OU{{(CLyu z&0^7b!(wfru*;KSDn!GHiUp7%YfHqwKRuZ9|K+j5 z|LaP`f2=DOjwdNYt;!SfSeYmIAB0yIh(wc>E0<*n+N{hKcEP6x07>yT%9Jy(>i_@% M07*qoM6N<$g3~Lpr2qf` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f2e6a039c5d7db063b2d49f61f7ec3f82f6297b0 GIT binary patch literal 364 zcmV-y0h9iTP)G%mB{~x zM_T{>{dDR-KK$~+^#35W7zS*v6#kEl|Gqzng3;yihQ-Em;s1Z$?nJ>LF`}b!eW}p@ zKd&~RV2~Km2CORq8t`HT3I>T0ZNQo$paD{ltv_t zTc{|bX3}a;mEL^joFXDJDe)yw&U?Ph`y?lmlk=Y=ql`szN>~54a-*SCuGA+*kU~M{ z;+T72eqvhBJc+uA)ZJPqEGS9{FR6(aW1Me0;9e^rqHl3=kOAN=Y)gUONyMAw;__h z6Aba-m~ZFEuc}tfyt=)2;N!wXpc&Z!X8sVy%r1z;l0U)=0<76Out3i-j5ZcT3BdHx zPN-6ufEEROI1V5C@7z7`u*7y{p0LagAU>Rc-{8OJVZq#+5!fw5hzrJ{d(uge%0Bl5 zTpvuqm``J?P& q-^+NI>A|!?H;S?Wmv`8v&+;4d>q{a%tf2q^0000s0KuLhCE+?CHy}zc-p?O|6y$*s0MhH1pM-=_WumSi4y`o&pI3MAA~!0 z`u_)teSyjOR0ezl((7;rs6&B=f^VJI^VXxD|3SEPgV%qz63^8{Mb%7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..81fb7b4a39dfbf3a218c40f19fc84919cfed1aea GIT binary patch literal 159 zcmZ?wbhEHb6krfw*v!E2`1KF}0RR90|9}7fee>qc>({TZtgK8=Pxtin1PU@>0L7my zj0_B%3_2hgkQodtJ_aW}SFcr@;eCfEY&nSJI{rI!5RRgR5Qr{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d9a07e503ef39a4bdb88e2c1ff5353609ca7dcc6 GIT binary patch literal 311 zcmV-70m%M|P)bG@P44JkAe8V z`J9NytIvf0a9tGn7|31$lE-U+_5}ZS*V#dDm!63Ezx`s||6@1e!T8|S`2Vp>L*AQD z2|TMkHIxUZ0ZNT-A6B1?`FZA6(tl)p{CeX5(AoYUG+W(|YBxCGHbA_<`p?$WvHu-9 zTrSBL+k8+cvwgeiRLpvtl# zmiSvb!!oA|f`I#Hs)LSY0YcK{yza9#R>o?id}002ov JPDHLkV1i>3k`w>{ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b6af9dbf57baca1ceebbb1ef271632bdd2fdd9ab GIT binary patch literal 631 zcmV--0*L*IP)9V#VVwBEV zit~ewiWU+?vv8GLpVEimphPOm*;u)p+l;4o2b5~B+5_T*Invvj;3SPS~=4oC-;<4;TJh8+`$Y;dUp-SAcidL5lHsv7} zUt;9F{{oQ&2CLw1;V&=>`po(Gv5;Dq^d6(2=Nt#oSSo{==seu7eei#HOYZ4CMuF4B zMPxpi!Nix(5MNWH@Ol*n#_u7$$0+D9auNQXTw!3`hw9tsv9G_DMvI=YW03Z0P}+6? zT&FxgLvZ;l4_~86LU(W&MO{kRebwk4)1p79!{|E|T!F*jIOO0uzU9bP1eEoz9H ziy>=oKyK51cmhh&=1_9FN;a_8OLi?~O1H_Xmt?WEoE&wlB3xLGHpmhamtB^&?U`3;R5@4ADk R0Gj{+002ovPDHLkV1lizC-49O literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..997b0545b895c9553dc6c142f38c9ae675190186 GIT binary patch literal 259 zcmV+e0sQ`nP)aE4nUfr?E82V704w(1@nfX=V)u)Si$%yy z=C`4SoG&dcl_1#=kj+5Nrzvs?)R5~SLx6#Ln5$U&?^xoP3@7_E?!LMK3`@d_Z-~WvhoL{c(bdbSm zz^ZPC|F2&@`TzC%9}IkZZw=58|Nm?ITwbo~a*)7kz_L!K|F2#=`9E>#@&BK{{RY#O z{oBCodxzV>hHRPY`+r53%NMK$ENXZD|NQxr{~y2p#)(1lAaxj;Q!AJMCl#X@5M8|J z|HT{6{y%y7fglE{1F1(bz&C5o|2vO9{QvL;;&&L`KIg#y+Q~cr7k6&@A6K>*EPn0& zoBts7C - * 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. @@ -53,17 +52,24 @@ * @noextend This class is not intended to be subclassed by clients. */ public class FindReplaceAction extends ResourceAction implements IUpdate { + private static boolean MW_FIND_REPLACE_OVERLAY_SHOULD_ABSOLUTELY_REPLACE_WITH_A_PROPERTY_LATER = true; + + private static boolean shouldShowModernOverlay() { + return MW_FIND_REPLACE_OVERLAY_SHOULD_ABSOLUTELY_REPLACE_WITH_A_PROPERTY_LATER; + } /** - * 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 +92,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 +105,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 +120,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 +146,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 +205,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 +214,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 +225,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 +251,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 +303,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.18.0 + */ + 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.18 + */ + 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/FindReplaceDialog.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java index fae3682279a..6eaac2a3299 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceDialog.java @@ -21,12 +21,10 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.regex.PatternSyntaxException; import org.osgi.framework.FrameworkUtil; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; @@ -62,19 +60,10 @@ import org.eclipse.jface.text.FindReplaceDocumentAdapter; import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider; import org.eclipse.jface.text.IFindReplaceTarget; -import org.eclipse.jface.text.IFindReplaceTargetExtension; -import org.eclipse.jface.text.IFindReplaceTargetExtension3; -import org.eclipse.jface.text.IFindReplaceTargetExtension4; -import org.eclipse.jface.text.IRegion; -import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; -import org.eclipse.ui.internal.texteditor.NLSUtility; import org.eclipse.ui.internal.texteditor.SWTUtil; @@ -85,6 +74,7 @@ class FindReplaceDialog extends Dialog { private static final int CLOSE_BUTTON_ID = 101; + private FindReplacer findReplacer; /** * Updates the find replace dialog on activation changes. @@ -108,18 +98,17 @@ public void shellDeactivated(ShellEvent e) { fGlobalRadioButton.setSelection(true); fSelectedRangeRadioButton.setSelection(false); - fUseSelectedLines= false; + findReplacer.setGlobalSearch(false); - if (fTarget != null && (fTarget instanceof IFindReplaceTargetExtension)) - ((IFindReplaceTargetExtension) fTarget).setScope(null); - - fOldScope= null; + findReplacer.deactivateScope(); fActiveShell= null; updateButtonState(); } } + private final FindModifyListener fFindModifyListener = new FindModifyListener(); + /** * Modify listener to update the search result in case of incremental search. * @since 2.0 @@ -129,7 +118,7 @@ private class FindModifyListener implements ModifyListener { // XXX: Workaround for Combo bug on Linux (see bug 404202 and bug 410603) private boolean fIgnoreNextEvent; private void ignoreNextEvent() { - fIgnoreNextEvent= true; + fIgnoreNextEvent = true; } @Override @@ -137,57 +126,26 @@ public void modifyText(ModifyEvent e) { // XXX: Workaround for Combo bug on Linux (see bug 404202 and bug 410603) if (fIgnoreNextEvent) { - fIgnoreNextEvent= false; + fIgnoreNextEvent = false; return; } - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) { - if (fFindField.getText().equals("") && fTarget != null) { //$NON-NLS-1$ - // empty selection at base location - int offset= fIncrementalBaseLocation.x; - - if (isForwardSearch() && !fNeedsInitialFindBeforeReplace || !isForwardSearch() && fNeedsInitialFindBeforeReplace) - offset= offset + fIncrementalBaseLocation.y; - - fNeedsInitialFindBeforeReplace= false; - findAndSelect(offset, "", isForwardSearch(), isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); //$NON-NLS-1$ - } else { - performSearch(false, false, isForwardSearch()); - } - } + findReplacer.updateSearchResultAfterTextWasModified(getFindString()); - updateButtonState(!isIncrementalSearch()); + updateButtonState(!findReplacer.isIncrementalSearch()); } } /** The size of the dialogs search history. */ private static final int HISTORY_SIZE= 15; - private Point fIncrementalBaseLocation; - private boolean fWrapInit, fCaseInit, fWholeWordInit, fForwardInit, fGlobalInit, fIncrementalInit; - /** - * Tells whether an initial find operation is needed - * before the replace operation. - * @since 3.0 - */ - private boolean fNeedsInitialFindBeforeReplace; - /** - * Initial value for telling whether the search string is a regular expression. - * @since 3.0 - */ - boolean fIsRegExInit; - private List fFindHistory; private List fReplaceHistory; - private IRegion fOldScope; - private boolean fIsTargetEditable; - private IFindReplaceTarget fTarget; private Shell fParentShell; private Shell fActiveShell; private final ActivationListener fActivationListener= new ActivationListener(); - private final FindModifyListener fFindModifyListener= new FindModifyListener(); private Label fReplaceLabel, fStatusLabel; private Button fForwardRadioButton, fGlobalRadioButton, fSelectedRangeRadioButton; @@ -212,19 +170,9 @@ public void modifyText(ModifyEvent e) { private IDialogSettings fDialogSettings; /** - * Tells whether the target supports regular expressions. - * true if the target supports regular expressions - * @since 3.0 - */ - private boolean fIsTargetSupportingRegEx; - /** - * Tells whether fUseSelectedLines radio is checked. - * @since 3.0 - */ - private boolean fUseSelectedLines; - /** - * true if the find field should receive focus the next time - * the dialog is activated, false otherwise. + * true if the find field should receive focus the next time the + * dialog is activated, false otherwise. + * * @since 3.0 */ private boolean fGiveFocusToFindField= true; @@ -242,23 +190,16 @@ public void modifyText(ModifyEvent e) { */ public FindReplaceDialog(Shell parentShell) { super(parentShell); + findReplacer = new FindReplacer(); fParentShell= null; - fTarget= null; fDialogPositionInit= null; fFindHistory= new ArrayList<>(HISTORY_SIZE); fReplaceHistory= new ArrayList<>(HISTORY_SIZE); - fWrapInit= false; - fCaseInit= false; - fIsRegExInit= false; - fWholeWordInit= false; - fIncrementalInit= false; - fGlobalInit= true; - fForwardInit= true; - readConfiguration(); + updateButtonState(); setShellStyle(getShellStyle() ^ SWT.APPLICATION_MODAL | SWT.MODELESS); setBlockOnOpen(false); @@ -333,12 +274,15 @@ private Composite createButtonSection(Composite parent) { fFindNextButton= makeButton(panel, EditorMessages.FindReplace_FindNextButton_label, 102, true, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) - initIncrementalBaseLocation(); + if (findReplacer.isIncrementalSearch() && !findReplacer.isRegExSearchAvailableAndChecked()) + findReplacer.initIncrementalBaseLocation(); - fNeedsInitialFindBeforeReplace= false; - performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch()); + findReplacer.setNeedsInitialFindBeforeReplace(false); + boolean somethingFound = findReplacer.performSearch(getFindString()); + writeSelection(); + updateButtonState(!somethingFound); updateFindHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fFindNextButton, SWT.FILL, true, SWT.FILL, false); @@ -347,8 +291,11 @@ public void widgetSelected(SelectionEvent e) { new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - performSelectAll(); + findReplacer.performSelectAll(getFindString(), fActiveShell.getDisplay()); + writeSelection(); + updateButtonState(); updateFindAndReplaceHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fSelectAllButton, SWT.FILL, true, SWT.FILL, false); @@ -358,11 +305,12 @@ public void widgetSelected(SelectionEvent e) { fReplaceFindButton= makeButton(panel, EditorMessages.FindReplace_ReplaceFindButton_label, 103, false, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (fNeedsInitialFindBeforeReplace) - performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch()); - if (performReplaceSelection()) - performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch()); + if (findReplacer.performFindFirstThenReplaceInASecondStep(getFindString(), getReplaceString())) { + writeSelection(); + } + updateButtonState(); updateFindAndReplaceHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fReplaceFindButton, SWT.FILL, false, SWT.FILL, false); @@ -370,11 +318,12 @@ public void widgetSelected(SelectionEvent e) { fReplaceSelectionButton= makeButton(panel, EditorMessages.FindReplace_ReplaceSelectionButton_label, 104, false, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (fNeedsInitialFindBeforeReplace) - performSearch(); - performReplaceSelection(); + if (findReplacer.performSelectAndReplace(getFindString(), getReplaceString())) { + writeSelection(); + } updateButtonState(); updateFindAndReplaceHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fReplaceSelectionButton, SWT.FILL, false, SWT.FILL, false); @@ -382,14 +331,17 @@ public void widgetSelected(SelectionEvent e) { fReplaceAllButton= makeButton(panel, EditorMessages.FindReplace_ReplaceAllButton_label, 105, false, new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - performReplaceAll(); + findReplacer.performReplaceAll(getFindString(), getReplaceString(), fActiveShell.getDisplay()); + writeSelection(); + updateButtonState(); updateFindAndReplaceHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fReplaceAllButton, SWT.FILL, true, SWT.FILL, false); // Make the all the buttons the same size as the Remove Selection button. - fReplaceAllButton.setEnabled(isEditable()); + fReplaceAllButton.setEnabled(findReplacer.isEditable()); return panel; } @@ -535,12 +487,15 @@ private Composite createDirectionGroup(Composite parent) { SelectionListener selectionListener= new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) - initIncrementalBaseLocation(); + if (findReplacer.isIncrementalSearch() && !findReplacer.isRegExSearchAvailableAndChecked()) + findReplacer.initIncrementalBaseLocation(); + + findReplacer.setForwardSearch(fForwardRadioButton.getSelection()); } @Override public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing } }; @@ -556,8 +511,9 @@ public void widgetDefaultSelected(SelectionEvent e) { backwardRadioButton.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(backwardRadioButton); - backwardRadioButton.setSelection(!fForwardInit); - fForwardRadioButton.setSelection(fForwardInit); + findReplacer.setForwardSearch(true); // search forward by default + backwardRadioButton.setSelection(!findReplacer.isForwardSearch()); + fForwardRadioButton.setSelection(findReplacer.isForwardSearch()); return panel; } @@ -586,14 +542,14 @@ private Composite createScopeGroup(Composite parent) { fGlobalRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); fGlobalRadioButton.setText(EditorMessages.FindReplace_GlobalRadioButton_label); setGridData(fGlobalRadioButton, SWT.LEFT, false, SWT.CENTER, false); - fGlobalRadioButton.setSelection(fGlobalInit); + fGlobalRadioButton.setSelection(findReplacer.isGlobalSearch()); fGlobalRadioButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (!fGlobalRadioButton.getSelection() || !fUseSelectedLines) + if (!fGlobalRadioButton.getSelection() || findReplacer.isGlobalSearch()) return; - fUseSelectedLines= false; - useSelectedLines(false); + findReplacer.setGlobalSearch(true); + findReplacer.useSelectedLines(false); } @Override @@ -605,15 +561,14 @@ public void widgetDefaultSelected(SelectionEvent e) { fSelectedRangeRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); fSelectedRangeRadioButton.setText(EditorMessages.FindReplace_SelectedRangeRadioButton_label); setGridData(fSelectedRangeRadioButton, SWT.LEFT, false, SWT.CENTER, false); - fSelectedRangeRadioButton.setSelection(!fGlobalInit); - fUseSelectedLines= !fGlobalInit; + fSelectedRangeRadioButton.setSelection(!findReplacer.isGlobalSearch()); fSelectedRangeRadioButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (!fSelectedRangeRadioButton.getSelection() || fUseSelectedLines) + if (!fSelectedRangeRadioButton.getSelection() || !findReplacer.isGlobalSearch()) return; - fUseSelectedLines= true; - useSelectedLines(true); + findReplacer.setGlobalSearch(false); + findReplacer.useSelectedLines(true); } @Override @@ -625,42 +580,6 @@ public void widgetDefaultSelected(SelectionEvent e) { return panel; } - /** - * Tells the dialog to perform searches only in the scope given by the actually selected lines. - * @param selectedLines true if selected lines should be used - * @since 2.0 - */ - private void useSelectedLines(boolean selectedLines) { - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) - initIncrementalBaseLocation(); - - if (fTarget == null || !(fTarget instanceof IFindReplaceTargetExtension)) - return; - - IFindReplaceTargetExtension extensionTarget= (IFindReplaceTargetExtension) fTarget; - - if (selectedLines) { - - IRegion scope; - if (fOldScope == null) { - Point lineSelection= extensionTarget.getLineSelection(); - scope= new Region(lineSelection.x, lineSelection.y); - } else { - scope= fOldScope; - fOldScope= null; - } - - int offset= isForwardSearch() - ? scope.getOffset() - : scope.getOffset() + scope.getLength(); - - extensionTarget.setSelection(offset, 0); - extensionTarget.setScope(scope); - } else { - fOldScope= extensionTarget.getScope(); - extensionTarget.setScope(null); - } - } /** * Creates the panel where the user specifies the text to search @@ -743,6 +662,7 @@ private Composite createOptionsGroup(Composite parent) { SelectionListener selectionListener= new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { + setupFindReplacer(); storeSettings(); } @@ -754,34 +674,35 @@ public void widgetDefaultSelected(SelectionEvent e) { fCaseCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); fCaseCheckBox.setText(EditorMessages.FindReplace_CaseCheckBox_label); setGridData(fCaseCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fCaseCheckBox.setSelection(fCaseInit); + fCaseCheckBox.setSelection(findReplacer.isCaseSensitiveSearch()); fCaseCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fCaseCheckBox); fWrapCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); fWrapCheckBox.setText(EditorMessages.FindReplace_WrapCheckBox_label); setGridData(fWrapCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fWrapCheckBox.setSelection(fWrapInit); + fWrapCheckBox.setSelection(findReplacer.isWrapSearch()); fWrapCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fWrapCheckBox); fWholeWordCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); fWholeWordCheckBox.setText(EditorMessages.FindReplace_WholeWordCheckBox_label); setGridData(fWholeWordCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fWholeWordCheckBox.setSelection(fWholeWordInit); + fWholeWordCheckBox.setSelection(findReplacer.isWholeWordSearchSetting()); fWholeWordCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fWholeWordCheckBox); fIncrementalCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); fIncrementalCheckBox.setText(EditorMessages.FindReplace_IncrementalCheckBox_label); setGridData(fIncrementalCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fIncrementalCheckBox.setSelection(fIncrementalInit); + fIncrementalCheckBox.setSelection(findReplacer.isIncrementalSearch()); fIncrementalCheckBox.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearch()) - initIncrementalBaseLocation(); + if (findReplacer.isIncrementalSearch() && !findReplacer.isRegexSearch()) + findReplacer.initIncrementalBaseLocation(); + setupFindReplacer(); storeSettings(); } @@ -795,26 +716,27 @@ public void widgetDefaultSelected(SelectionEvent e) { fIsRegExCheckBox.setText(EditorMessages.FindReplace_RegExCheckbox_label); setGridData(fIsRegExCheckBox, SWT.LEFT, false, SWT.CENTER, false); ((GridData)fIsRegExCheckBox.getLayoutData()).horizontalSpan= 2; - fIsRegExCheckBox.setSelection(fIsRegExInit); + fIsRegExCheckBox.setSelection(findReplacer.isRegexSearch()); fIsRegExCheckBox.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { boolean newState= fIsRegExCheckBox.getSelection(); fIncrementalCheckBox.setEnabled(!newState); - updateButtonState(); + setupFindReplacer(); storeSettings(); + updateButtonState(); setContentAssistsEnablement(newState); } }); storeButtonWithMnemonicInMap(fIsRegExCheckBox); - fWholeWordCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fWholeWordCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndChecked()); fWholeWordCheckBox.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateButtonState(); } }); - fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fIncrementalCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndChecked()); return panel; } @@ -856,142 +778,6 @@ protected void buttonPressed(int buttonID) { // ------- action invocation --------------------------------------- - /** - * Returns the position of the specified search string, or -1 if the string can not - * be found when searching using the given options. - * - * @param findString the string to search for - * @param startPosition the position at which to start the search - * @param forwardSearch the direction of the search - * @param caseSensitive should the search be case sensitive - * @param wrapSearch should the search wrap to the start/end if arrived at the end/start - * @param wholeWord does the search string represent a complete word - * @param regExSearch if true findString represents a regular expression - * @param beep if true beeps when search does not find a match or needs to wrap - * @return the occurrence of the find string following the options or -1 if nothing - * found - * @since 3.0 - */ - private int findIndex(String findString, int startPosition, boolean forwardSearch, boolean caseSensitive, boolean wrapSearch, boolean wholeWord, boolean regExSearch, boolean beep) { - - if (forwardSearch) { - int index= findAndSelect(startPosition, findString, true, caseSensitive, wholeWord, regExSearch); - if (index == -1) { - - if (beep && okToUse(getShell())) - getShell().getDisplay().beep(); - - if (wrapSearch) { - statusMessage(EditorMessages.FindReplace_Status_wrapped_label); - index= findAndSelect(-1, findString, true, caseSensitive, wholeWord, regExSearch); - } - } - return index; - } - - // backward - int index= startPosition == 0 ? -1 : findAndSelect(startPosition - 1, findString, false, caseSensitive, wholeWord, regExSearch); - if (index == -1) { - - if (beep && okToUse(getShell())) - getShell().getDisplay().beep(); - - if (wrapSearch) { - statusMessage(EditorMessages.FindReplace_Status_wrapped_label); - index= findAndSelect(-1, findString, false, caseSensitive, wholeWord, regExSearch); - } - } - return index; - } - - /** - * Searches for a string starting at the given offset and using the specified search - * directives. If a string has been found it is selected and its start offset is - * returned. - * - * @param offset the offset at which searching starts - * @param findString the string which should be found - * @param forwardSearch the direction of the search - * @param caseSensitive true performs a case sensitive search, false an insensitive search - * @param wholeWord if true only occurrences are reported in which the findString stands as a word by itself - * @param regExSearch if true findString represents a regular expression - * @return the position of the specified string, or -1 if the string has not been found - * @since 3.0 - */ - private int findAndSelect(int offset, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { - if (fTarget instanceof IFindReplaceTargetExtension3) - return ((IFindReplaceTargetExtension3)fTarget).findAndSelect(offset, findString, forwardSearch, caseSensitive, wholeWord, regExSearch); - return fTarget.findAndSelect(offset, findString, forwardSearch, caseSensitive, wholeWord); - } - - /** - * Replaces the selection with replaceString. If - * regExReplace is true, - * replaceString is a regex replace pattern which will get - * expanded if the underlying target supports it. Returns the region of the - * inserted text; note that the returned selection covers the expanded - * pattern in case of regex replace. - * - * @param replaceString the replace string (or a regex pattern) - * @param regExReplace true if replaceString - * is a pattern - * @return the selection after replacing, i.e. the inserted text - * @since 3.0 - */ - Point replaceSelection(String replaceString, boolean regExReplace) { - if (fTarget instanceof IFindReplaceTargetExtension3) - ((IFindReplaceTargetExtension3)fTarget).replaceSelection(replaceString, regExReplace); - else - fTarget.replaceSelection(replaceString); - - return fTarget.getSelection(); - } - - /** - * Returns whether the specified search string can be found using the given options. - * - * @param findString the string to search for - * @param forwardSearch the direction of the search - * @param caseSensitive should the search be case sensitive - * @param wrapSearch should the search wrap to the start/end if arrived at the end/start - * @param wholeWord does the search string represent a complete word - * @param incremental is this an incremental search - * @param regExSearch if true findString represents a regular expression - * @param beep if true beeps when search does not find a match or needs to wrap - * @return true if the search string can be found using the given options - * - * @since 3.0 - */ - private boolean findNext(String findString, boolean forwardSearch, boolean caseSensitive, boolean wrapSearch, boolean wholeWord, boolean incremental, boolean regExSearch, boolean beep) { - - if (fTarget == null) - return false; - - Point r= null; - if (incremental) - r= fIncrementalBaseLocation; - else - r= fTarget.getSelection(); - - int findReplacePosition= r.x; - if (forwardSearch && !fNeedsInitialFindBeforeReplace || !forwardSearch && fNeedsInitialFindBeforeReplace) - findReplacePosition += r.y; - - fNeedsInitialFindBeforeReplace= false; - - int index= findIndex(findString, findReplacePosition, forwardSearch, caseSensitive, wrapSearch, wholeWord, regExSearch, beep); - - if (index == -1) { - String msg= NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, findString); - statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); - return false; - } - - if (forwardSearch && index >= findReplacePosition || !forwardSearch && index <= findReplacePosition) - statusMessage(""); //$NON-NLS-1$ - - return true; - } /** * Returns the dialog's boundaries. @@ -1091,13 +877,10 @@ private void handleDialogClose() { // store current settings in case of re-open storeSettings(); - if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension) - ((IFindReplaceTargetExtension) fTarget).endSession(); + findReplacer.endSession(); // prevent leaks fActiveShell= null; - fTarget= null; - } /** @@ -1105,11 +888,12 @@ private void handleDialogClose() { * @since 3.0 */ private void writeSelection() { - if (fTarget == null) + String selection = findReplacer.getCurrentSelection(); + if (selection == null) return; IDialogSettings s= getDialogSettings(); - s.put("selection", fTarget.getSelectionText()); //$NON-NLS-1$ + s.put("selection", selection); //$NON-NLS-1$ } /** @@ -1118,12 +902,6 @@ private void writeSelection() { */ private void storeSettings() { fDialogPositionInit= getDialogBoundaries(); - fWrapInit= isWrapSearch(); - fWholeWordInit= isWholeWordSetting(); - fCaseInit= isCaseSensitiveSearch(); - fIsRegExInit= isRegExSearch(); - fIncrementalInit= isIncrementalSearch(); - fForwardInit= isForwardSearch(); writeConfiguration(); } @@ -1134,9 +912,9 @@ private void storeSettings() { * action's target. */ private void initFindStringFromSelection() { - if (fTarget != null && okToUse(fFindField)) { - String fullSelection= fTarget.getSelectionText(); - boolean isRegEx= isRegExSearchAvailableAndChecked(); + String fullSelection = findReplacer.getCurrentSelection(); + if (fullSelection != null && okToUse(fFindField)) { + boolean isRegEx = findReplacer.isRegExSearchAvailableAndChecked(); fFindField.removeModifyListener(fFindModifyListener); if (!fullSelection.isEmpty()) { String firstLine= getFirstLine(fullSelection); @@ -1144,10 +922,9 @@ private void initFindStringFromSelection() { fFindField.setText(pattern); if (!firstLine.equals(fullSelection)) { // multiple lines selected - useSelectedLines(true); + findReplacer.useSelectedLines(true); fGlobalRadioButton.setSelection(false); fSelectedRangeRadioButton.setSelection(true); - fUseSelectedLines= true; } } else { if ("".equals(fFindField.getText())) { //$NON-NLS-1$ @@ -1162,114 +939,6 @@ private void initFindStringFromSelection() { } } - /** - * Initializes the anchor used as starting point for incremental searching. - * @since 2.0 - */ - private void initIncrementalBaseLocation() { - if (fTarget != null && isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) { - fIncrementalBaseLocation= fTarget.getSelection(); - } else { - fIncrementalBaseLocation= new Point(0, 0); - } - } - - // ------- history --------------------------------------- - - /** - * Retrieves and returns the option case sensitivity from the appropriate check box. - * @return true if case sensitive - */ - private boolean isCaseSensitiveSearch() { - if (okToUse(fCaseCheckBox)) { - return fCaseCheckBox.getSelection(); - } - return fCaseInit; - } - - /** - * Retrieves and returns the regEx option from the appropriate check box. - * - * @return true if case sensitive - * @since 3.0 - */ - private boolean isRegExSearch() { - if (okToUse(fIsRegExCheckBox)) { - return fIsRegExCheckBox.getSelection(); - } - return fIsRegExInit; - } - - /** - * If the target supports regular expressions search retrieves and returns - * regEx option from appropriate check box. - * - * @return true if regEx is available and checked - * @since 3.0 - */ - private boolean isRegExSearchAvailableAndChecked() { - if (okToUse(fIsRegExCheckBox)) { - return fIsTargetSupportingRegEx && fIsRegExCheckBox.getSelection(); - } - return fIsRegExInit; - } - - /** - * Retrieves and returns the option search direction from the appropriate check box. - * @return true if searching forward - */ - private boolean isForwardSearch() { - if (okToUse(fForwardRadioButton)) { - return fForwardRadioButton.getSelection(); - } - return fForwardInit; - } - - /** - * Retrieves and returns the option search whole words from the appropriate check box. - * @return true if searching for whole words - */ - private boolean isWholeWordSetting() { - if (okToUse(fWholeWordCheckBox)) { - return fWholeWordCheckBox.getSelection(); - } - return fWholeWordInit; - } - - /** - * Returns true if searching should be restricted to entire - * words, false if not. This is the case if the respective - * checkbox is turned on, regex is off, and the checkbox is enabled, i.e. - * the current find string is an entire word. - * - * @return true if the search is restricted to whole words - */ - private boolean isWholeWordSearch() { - return isWholeWordSetting() && !isRegExSearchAvailableAndChecked() && (okToUse(fWholeWordCheckBox) ? fWholeWordCheckBox.isEnabled() : true); - } - - /** - * Retrieves and returns the option wrap search from the appropriate check box. - * @return true if wrapping while searching - */ - private boolean isWrapSearch() { - if (okToUse(fWrapCheckBox)) { - return fWrapCheckBox.getSelection(); - } - return fWrapInit; - } - - /** - * Retrieves and returns the option incremental search from the appropriate check box. - * @return true if incremental search - * @since 2.0 - */ - private boolean isIncrementalSearch() { - if (okToUse(fIncrementalCheckBox)) { - return fIncrementalCheckBox.getSelection(); - } - return fIncrementalInit; - } /** * Creates a button. @@ -1299,331 +968,6 @@ private void storeButtonWithMnemonicInMap(Button button) { fMnemonicButtonMap.put(Character.valueOf(Character.toLowerCase(mnemonic)), button); } - /** - * Returns the status line manager of the active editor or null if there is no such editor. - * @return the status line manager of the active editor - */ - private IEditorStatusLine getStatusLineManager() { - IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (window == null) - return null; - - IWorkbenchPage page= window.getActivePage(); - if (page == null) - return null; - - IEditorPart editor= page.getActiveEditor(); - if (editor == null) - return null; - - return editor.getAdapter(IEditorStatusLine.class); - } - - /** - * Sets the given status message in the status line. - * - * @param error true if it is an error - * @param dialogMessage the message to display in the dialog's status line - * @param editorMessage the message to display in the editor's status line - */ - private void statusMessage(boolean error, String dialogMessage, String editorMessage) { - fStatusLabel.setText(dialogMessage); - - if (error) - fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); - else - fStatusLabel.setForeground(null); - - IEditorStatusLine statusLine= getStatusLineManager(); - if (statusLine != null) - statusLine.setMessage(error, editorMessage, null); - - if (error) - getShell().getDisplay().beep(); - } - - /** - * Sets the given error message in the status line. - * @param message the message - */ - private void statusError(String message) { - statusMessage(true, message, message); - } - - /** - * Sets the given message in the status line. - * @param message the message - */ - private void statusMessage(String message) { - statusMessage(false, message, message); - } - - /** - * Replaces all occurrences of the user's findString with - * the replace string. Indicate to the user the number of replacements - * that occur. - */ - private void performReplaceAll() { - - int replaceCount= 0; - final String replaceString= getReplaceString(); - final String findString= getFindString(); - - if (findString != null && !findString.isEmpty()) { - - class ReplaceAllRunnable implements Runnable { - public int numberOfOccurrences; - @Override - public void run() { - numberOfOccurrences = replaceAll(findString, replaceString == null ? "" : replaceString, //$NON-NLS-1$ - isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); - } - } - - try { - ReplaceAllRunnable runnable= new ReplaceAllRunnable(); - BusyIndicator.showWhile(fActiveShell.getDisplay(), runnable); - replaceCount= runnable.numberOfOccurrences; - - if (replaceCount != 0) { - if (replaceCount == 1) { // not plural - statusMessage(EditorMessages.FindReplace_Status_replacement_label); - } else { - String msg= EditorMessages.FindReplace_Status_replacements_label; - msg= NLSUtility.format(msg, String.valueOf(replaceCount)); - statusMessage(msg); - } - } else { - String msg= NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, findString); - statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); - } - } catch (PatternSyntaxException ex) { - statusError(ex.getLocalizedMessage()); - } catch (IllegalStateException ex) { - // we don't keep state in this dialog - } - } - writeSelection(); - updateButtonState(); - } - - /** - * Replaces all occurrences of the user's findString with the replace string. - * Indicate to the user the number of replacements that occur. - */ - private void performSelectAll() { - - int selectCount = 0; - final String findString = getFindString(); - - if (findString != null && !findString.isEmpty()) { - - class SelectAllRunnable implements Runnable { - public int numberOfOccurrences; - - @Override - public void run() { - numberOfOccurrences = selectAll(findString, isCaseSensitiveSearch(), isWholeWordSearch(), - isRegExSearchAvailableAndChecked()); - } - } - - try { - SelectAllRunnable runnable = new SelectAllRunnable(); - BusyIndicator.showWhile(fActiveShell.getDisplay(), runnable); - selectCount = runnable.numberOfOccurrences; - - if (selectCount != 0) { - if (selectCount == 1) { // not plural - statusMessage(EditorMessages.FindReplace_Status_selection_label); - } else { - String msg = EditorMessages.FindReplace_Status_selections_label; - msg = NLSUtility.format(msg, String.valueOf(selectCount)); - statusMessage(msg); - } - } else { - String msg = NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, - findString); - statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); - } - } catch (PatternSyntaxException ex) { - statusError(ex.getLocalizedMessage()); - } catch (IllegalStateException ex) { - // we don't keep state in this dialog - } - } - writeSelection(); - updateButtonState(); - } - - /** - * Validates the state of the find/replace target. - * - * @return true if target can be changed, false - * otherwise - * @since 2.1 - */ - private boolean validateTargetState() { - - if (fTarget instanceof IFindReplaceTargetExtension2) { - IFindReplaceTargetExtension2 extension= (IFindReplaceTargetExtension2) fTarget; - if (!extension.validateTargetState()) { - statusError(EditorMessages.FindReplaceDialog_read_only); - updateButtonState(); - return false; - } - } - return isEditable(); - } - - /** - * Replaces the current selection of the target with the user's - * replace string. - * - * @return true if the operation was successful - */ - private boolean performReplaceSelection() { - - if (!validateTargetState()) - return false; - - String replaceString= getReplaceString(); - if (replaceString == null) - replaceString= ""; //$NON-NLS-1$ - - boolean replaced; - try { - replaceSelection(replaceString, isRegExSearchAvailableAndChecked()); - replaced= true; - writeSelection(); - } catch (PatternSyntaxException ex) { - statusError(ex.getLocalizedMessage()); - replaced= false; - } catch (IllegalStateException ex) { - replaced= false; - } - - return replaced; - } - - /** - * Locates the user's findString in the text of the target. - */ - private void performSearch() { - performSearch(isForwardSearch()); - } - - /** - * Locates the user's findString in the text of the target. - * - * @param forwardSearch true if searching forwards, false otherwise - * @since 3.7 - */ - private void performSearch(boolean forwardSearch) { - performSearch(isIncrementalSearch() && !isRegExSearchAvailableAndChecked(), true, forwardSearch); - } - - /** - * Locates the user's findString in the text of the target. - * - * @param mustInitIncrementalBaseLocation true if base location must be initialized - * @param beep if true beeps when search does not find a match or needs to wrap - * @param forwardSearch the search direction - * @since 3.0 - */ - private void performSearch(boolean mustInitIncrementalBaseLocation, boolean beep, boolean forwardSearch) { - - if (mustInitIncrementalBaseLocation) - initIncrementalBaseLocation(); - - String findString= getFindString(); - boolean somethingFound= false; - - if (findString != null && !findString.isEmpty()) { - - try { - somethingFound= findNext(findString, forwardSearch, isCaseSensitiveSearch(), isWrapSearch(), isWholeWordSearch(), isIncrementalSearch() && !isRegExSearchAvailableAndChecked(), isRegExSearchAvailableAndChecked(), beep); - } catch (PatternSyntaxException ex) { - statusError(ex.getLocalizedMessage()); - } catch (IllegalStateException ex) { - // we don't keep state in this dialog - } - } - writeSelection(); - updateButtonState(!somethingFound); - } - - /** - * Replaces all occurrences of the user's findString with - * the replace string. Returns the number of replacements - * that occur. - * - * @param findString the string to search for - * @param replaceString the replacement string - * @param caseSensitive should the search be case sensitive - * @param wholeWord does the search string represent a complete word - * @param regExSearch if true findString represents a regular expression - * @return the number of occurrences - * - * @since 3.0 - */ - private int replaceAll(String findString, String replaceString, boolean caseSensitive, boolean wholeWord, - boolean regExSearch) { - - int replaceCount= 0; - int findReplacePosition= 0; - - findReplacePosition= 0; - - if (!validateTargetState()) - return replaceCount; - - if (fTarget instanceof IFindReplaceTargetExtension) - ((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(true); - - try { - int index= 0; - while (index != -1) { - index = findAndSelect(findReplacePosition, findString, true, caseSensitive, wholeWord, regExSearch); - if (index != -1) { // substring not contained from current position - Point selection= replaceSelection(replaceString, regExSearch); - replaceCount++; - findReplacePosition = selection.x + selection.y; - } - } - } finally { - if (fTarget instanceof IFindReplaceTargetExtension) - ((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(false); - } - - return replaceCount; - } - - private int selectAll(String findString, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { - - int replaceCount = 0; - int position = 0; - - if (!validateTargetState()) - return replaceCount; - - List selectedRegions = new ArrayList<>(); - int index = 0; - do { - index = findAndSelect(position, findString, true, caseSensitive, wholeWord, regExSearch); - if (index != -1) { // substring not contained from current position - Point selection = fTarget.getSelection(); - selectedRegions.add(new Region(selection.x, selection.y)); - replaceCount++; - position = selection.x + selection.y; - } - } while (index != -1); - if (fTarget instanceof IFindReplaceTargetExtension4) { - ((IFindReplaceTargetExtension4) fTarget).setSelection(selectedRegions.toArray(IRegion[]::new)); - } - - return replaceCount; - } // ------- UI creation --------------------------------------- @@ -1682,21 +1026,29 @@ private void updateButtonState() { private void updateButtonState(boolean disableReplace) { if (okToUse(getShell()) && okToUse(fFindNextButton)) { - boolean selection= false; - if (fTarget != null) - selection= !fTarget.getSelectionText().isEmpty(); + boolean hasActiveSelection = false; + String selection = findReplacer.getCurrentSelection(); + if (selection != null) + hasActiveSelection = !selection.isEmpty(); - boolean enable= fTarget != null && (fActiveShell == fParentShell || fActiveShell == getShell()); + boolean enable = findReplacer.isTargetAvailable() + && (fActiveShell == fParentShell || fActiveShell == getShell()); String str= getFindString(); boolean findString= str != null && !str.isEmpty(); - fWholeWordCheckBox.setEnabled(isWord(str) && !isRegExSearchAvailableAndChecked()); - + fWholeWordCheckBox.setEnabled(isWord(str) && !findReplacer.isRegExSearchAvailableAndChecked()); fFindNextButton.setEnabled(enable && findString); - fSelectAllButton.setEnabled(enable && findString && fTarget instanceof IFindReplaceTargetExtension4); - fReplaceSelectionButton.setEnabled(!disableReplace && enable && isEditable() && selection && (!fNeedsInitialFindBeforeReplace || !isRegExSearchAvailableAndChecked())); - fReplaceFindButton.setEnabled(!disableReplace && enable && isEditable() && findString && selection && (!fNeedsInitialFindBeforeReplace || !isRegExSearchAvailableAndChecked())); - fReplaceAllButton.setEnabled(enable && isEditable() && findString); + fSelectAllButton.setEnabled(enable && findString && findReplacer.supportsMultiSelection()); + fReplaceSelectionButton + .setEnabled(!disableReplace && enable && findReplacer.isEditable() && hasActiveSelection + && (!findReplacer.needsInitialFindBeforeReplace() + || !findReplacer.isRegExSearchAvailableAndChecked())); + fReplaceFindButton + .setEnabled(!disableReplace && enable && findReplacer.isEditable() && findString + && hasActiveSelection + && (!findReplacer.needsInitialFindBeforeReplace() + || !findReplacer.isRegExSearchAvailableAndChecked())); + fReplaceAllButton.setEnabled(enable && findReplacer.isEditable() && findString); } } @@ -1779,15 +1131,6 @@ private void updateHistory(Combo combo, List history) { } } - /** - * Returns whether the target is editable. - * @return true if target is editable - */ - private boolean isEditable() { - boolean isEditable= (fTarget == null ? false : fTarget.isEditable()); - return fIsTargetEditable && isEditable; - } - /** * Updates this dialog because of a different target. * @param target the new target @@ -1796,49 +1139,35 @@ private boolean isEditable() { * @since 2.0 */ public void updateTarget(IFindReplaceTarget target, boolean isTargetEditable, boolean initializeFindString) { + findReplacer.updateTarget(target, isTargetEditable); - fIsTargetEditable= isTargetEditable; - fNeedsInitialFindBeforeReplace= true; - - if (target != fTarget) { - if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension) - ((IFindReplaceTargetExtension) fTarget).endSession(); - - fTarget= target; - if (fTarget != null) - fIsTargetSupportingRegEx= fTarget instanceof IFindReplaceTargetExtension3; - - if (fTarget instanceof IFindReplaceTargetExtension) { - ((IFindReplaceTargetExtension) fTarget).beginSession(); - - fGlobalInit= true; - fGlobalRadioButton.setSelection(fGlobalInit); - fSelectedRangeRadioButton.setSelection(!fGlobalInit); - fUseSelectedLines= !fGlobalInit; - } - } + boolean globalSearch = findReplacer.isGlobalSearch(); + fGlobalRadioButton.setSelection(globalSearch); + boolean useSelectedLines = !globalSearch; + fSelectedRangeRadioButton.setSelection(useSelectedLines); + findReplacer.useSelectedLines(useSelectedLines); if (okToUse(fIsRegExCheckBox)) - fIsRegExCheckBox.setEnabled(fIsTargetSupportingRegEx); + fIsRegExCheckBox.setEnabled(findReplacer.isTargetSupportingRegEx()); if (okToUse(fWholeWordCheckBox)) - fWholeWordCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fWholeWordCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndChecked()); if (okToUse(fIncrementalCheckBox)) - fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fIncrementalCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndChecked()); if (okToUse(fReplaceLabel)) { - fReplaceLabel.setEnabled(isEditable()); - fReplaceField.setEnabled(isEditable()); + fReplaceLabel.setEnabled(findReplacer.isEditable()); + fReplaceField.setEnabled(findReplacer.isEditable()); if (initializeFindString) { initFindStringFromSelection(); - fGiveFocusToFindField= true; + fGiveFocusToFindField = true; } - initIncrementalBaseLocation(); } + updateButtonState(); - setContentAssistsEnablement(isRegExSearchAvailableAndChecked()); + setContentAssistsEnablement(findReplacer.isRegExSearchAvailableAndChecked()); } /** @@ -1901,11 +1230,11 @@ protected int getDialogBoundsStrategy() { private void readConfiguration() { IDialogSettings s= getDialogSettings(); - fWrapInit= s.get("wrap") == null || s.getBoolean("wrap"); //$NON-NLS-1$ //$NON-NLS-2$ - fCaseInit= s.getBoolean("casesensitive"); //$NON-NLS-1$ - fWholeWordInit= s.getBoolean("wholeword"); //$NON-NLS-1$ - fIncrementalInit= s.getBoolean("incremental"); //$NON-NLS-1$ - fIsRegExInit= s.getBoolean("isRegEx"); //$NON-NLS-1$ + findReplacer.setWrapSearch(s.get("wrap") == null || s.getBoolean("wrap")); //$NON-NLS-1$ //$NON-NLS-2$ + findReplacer.setCaseSensitiveSearch(s.getBoolean("casesensitive")); //$NON-NLS-1$ + findReplacer.setWholeWordSearchSetting(s.getBoolean("wholeword")); //$NON-NLS-1$ + findReplacer.setIncrementalSearch(s.getBoolean("incremental")); //$NON-NLS-1$ + findReplacer.setRegexSearch(s.getBoolean("isRegEx")); //$NON-NLS-1$ String[] findHistory= s.getArray("findhistory"); //$NON-NLS-1$ if (findHistory != null) { @@ -1922,17 +1251,25 @@ private void readConfiguration() { } } + private void setupFindReplacer() { + findReplacer.setWrapSearch(fWrapCheckBox.getSelection()); + findReplacer.setCaseSensitiveSearch(fCaseCheckBox.getSelection()); + findReplacer.setWholeWordSearchSetting(fWholeWordCheckBox.getSelection()); + findReplacer.setIncrementalSearch(fIncrementalCheckBox.getSelection()); + findReplacer.setRegexSearch(fIsRegExCheckBox.getSelection()); + } + /** * Stores its current configuration in the dialog store. */ private void writeConfiguration() { IDialogSettings s= getDialogSettings(); - s.put("wrap", fWrapInit); //$NON-NLS-1$ - s.put("casesensitive", fCaseInit); //$NON-NLS-1$ - s.put("wholeword", fWholeWordInit); //$NON-NLS-1$ - s.put("incremental", fIncrementalInit); //$NON-NLS-1$ - s.put("isRegEx", fIsRegExInit); //$NON-NLS-1$ + s.put("wrap", findReplacer.isWrapSearch()); //$NON-NLS-1$ + s.put("casesensitive", findReplacer.isCaseSensitiveSearch()); //$NON-NLS-1$ + s.put("wholeword", findReplacer.isWholeWordSearchSetting()); //$NON-NLS-1$ + s.put("incremental", findReplacer.isIncrementalSearch()); //$NON-NLS-1$ + s.put("isRegEx", findReplacer.isRegexSearch()); //$NON-NLS-1$ List history= getFindHistory(); String findString= getFindString(); @@ -1976,4 +1313,26 @@ private void writeHistory(List history, IDialogSettings settings, String settings.put(sectionName, names); } + + public void evaluateFindReplacerStatus() { + FindReplaceStatus status = findReplacer.getStatus(); + + if (status.shouldBeep() && okToUse(getShell())) + getShell().getDisplay().beep(); + + String dialogMessage = status.getMessage(); + boolean error = status.isError(); + + fStatusLabel.setText(dialogMessage); + + if (error) + fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); + else + fStatusLabel.setForeground(null); + + if (error) + getShell().getDisplay().beep(); + + findReplacer.resetStatus(); + } } 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..bf7558beca5 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java @@ -0,0 +1,681 @@ +/******************************************************************************* + * 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.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; + +/** + * @since 3.18 + */ +public class FindReplaceOverlay extends Dialog { + + FindReplaceAction parentAction; + FindReplacer findReplacer; + IWorkbenchPart targetPart; + boolean overlayOpen; + boolean replaceBarOpen; + + Composite container; + ToolItem 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 FindReplacer(); + boolean isTargetEditable = false; + if (target != null) { + isTargetEditable = target.isEditable(); + } + findReplacer.getStatus().doBeep(); + findReplacer.updateTarget(target, isTargetEditable); + findReplacer.initIncrementalBaseLocation(); + findReplacer.setIncrementalSearch(true); + findReplacer.setGlobalSearch(true); + findReplacer.setWrapSearch(true); + findReplacer.setForwardSearch(true); + } + + 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 (replaceBar != null && replaceBar.isFocusControl()) { + if (isCtrlPressed) { + findReplacer.performReplaceAll(getFindString(), getReplaceString(), getShell().getDisplay()); + } else { + performSingleReplace(); + } + } else { + if (isCtrlPressed) { + findReplacer.performSelectAll(getFindString(), getShell().getDisplay()); + } else { + boolean oldForwardSearchSetting = findReplacer.isForwardSearch(); + findReplacer.setForwardSearch(!isShiftPressed); + findReplacer.setIncrementalSearch(false); + findReplacer.performSearch(getFindString()); + findReplacer.setForwardSearch(oldForwardSearchSetting); + findReplacer.setIncrementalSearch(true); + } + } + } + + @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.updateSearchResultAfterTextWasModified(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) { + findReplacer.setWholeWordSearchSetting(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) { + findReplacer.setCaseSensitiveSearch(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) { + findReplacer.setRegexSearch(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) { + // TODO extract into transaction + findReplacer.setForwardSearch(true); + findReplacer.setForwardSearch(false); + findReplacer.setNeedsInitialFindBeforeReplace(false); + findReplacer.performSearch(getFindString()); + findReplacer.setForwardSearch(true); + } + + @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.setForwardSearch(true); + findReplacer.setIncrementalSearch(false); + findReplacer.setNeedsInitialFindBeforeReplace(false); + findReplacer.performSearch(getFindString()); + findReplacer.setIncrementalSearch(true); + } + + @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.updateSearchResultAfterTextWasModified(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().margins(1, 1).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(1, 1).numColumns(2).equalWidth(false).spacing(5, 0) + .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).applyTo(container); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(container); + + if (findReplacer.isTargetEditable()) { + 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.BEGINNING).applyTo(contentGroup); + } + + private void createReplaceToggle() { + replaceToggle = new ToolItem(new ToolBar(container, SWT.FLAT), SWT.CHECK); // https://stackoverflow.com/questions/33161797/how-to-remove-border-of-swt-button-so-that-it-seems-like-a-label + 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 int getIdealDialogWidth(Rectangle targetBounds) { + GC gc = new GC(searchBar); + gc.setFont(searchBar.getFont()); + Point idealTextWidth = gc.stringExtent("THIS TEXT HAS A REASONABLE LENGTH FOR SEARCHING"); //$NON-NLS-1$ + Point toolBarWidth = searchTools.getSize(); + toolBarWidth.x += replaceToggle.getParent().getSize().x; + int newWidth = idealTextWidth.x + toolBarWidth.x; + if (newWidth > targetBounds.width * 0.7) { + newWidth = (int) (targetBounds.width * 0.7); + } + 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. (Do we want that ?!) + */ + private void repositionTextSelection() { + if (searchBar != null && !searchBar.isFocusControl()) { + searchBar.setSelection(0, 0); + } + if (replaceBar != null && !replaceBar.isFocusControl()) { + replaceBar.setSelection(0, 0); + } + } + + public void positionToPart() { + getShell().layout(true); + container.layout(true); + System.exit(1); + 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.updateSearchResultAfterTextWasModified(getFindString()); + evaluateFindReplacerStatus(); + } + + private void initFindStringFromSelection() { + searchBar.setText(findReplacer.getCurrentSelection()); + } + + public void evaluateFindReplacerStatus() { + FindReplaceStatus status = findReplacer.getStatus(); + + if (status.shouldBeep()) + getShell().getDisplay().beep(); + + boolean error = status.isError(); + + if (error) + getShell().getDisplay().beep(); + + findReplacer.resetStatus(); + } +} 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.texteditor/src/org/eclipse/ui/texteditor/FindReplaceStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceStatus.java new file mode 100644 index 00000000000..5f4a20489d2 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceStatus.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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; + +/** + * @since 3.17 + */ +class FindReplaceStatus { + private boolean error; + private String message; + private boolean beep; + + public void resetStatus() { + error = false; + beep = false; + setMessage(""); //$NON-NLS-1$ + } + + public boolean shouldBeep() { + return beep; + } + + public void doBeep() { + setBeep(true); + } + + public void setBeep(boolean beep) { + this.beep = beep; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isError() { + return error; + } + + public void setError(boolean error) { + this.error = error; + } + +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplacer.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplacer.java new file mode 100644 index 00000000000..c0c81360bd8 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplacer.java @@ -0,0 +1,793 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.List; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; + +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.text.IFindReplaceTargetExtension; +import org.eclipse.jface.text.IFindReplaceTargetExtension3; +import org.eclipse.jface.text.IFindReplaceTargetExtension4; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; + +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.texteditor.NLSUtility; + +class FindReplacer { + private FindReplaceStatus status = new FindReplaceStatus(); + private IFindReplaceTarget fTarget; + private IRegion fOldScope; + private Point fIncrementalBaseLocation; + + /** + * Wether the search is incremental. (Search-as-you-type) + */ + boolean fIncrementalSearch; + + /** + * Tells whether an initial find operation is needed before the replace + * operation. + * + * @since 3.0 + */ + private boolean fNeedsInitialFindBeforeReplace; + + /** + * Tells whether the target supports regular expressions. true if + * the target supports regular expressions + */ + private boolean fIsTargetSupportingRegEx; + boolean fRegexSearch; + boolean fForwardSearch; + boolean fWholeWordSearch; + boolean fWrapSearch; + boolean fCaseSensitiveSearch; + boolean fGlobalSearch; + private boolean fTargetEditable; + + public Point getIncrementalBaseLocation() { + return fIncrementalBaseLocation; + } + + public void setIncrementalBaseLocation(Point incrementalBaseLocation) { + fIncrementalBaseLocation = incrementalBaseLocation; + } + + public boolean isGlobalSearch() { + return fGlobalSearch; + } + + public void setGlobalSearch(boolean globalSearch) { + fGlobalSearch = globalSearch; + } + + public boolean needsInitialFindBeforeReplace() { + return fNeedsInitialFindBeforeReplace; + } + + public void setNeedsInitialFindBeforeReplace(boolean needsInitialFindBeforeReplace) { + fNeedsInitialFindBeforeReplace = needsInitialFindBeforeReplace; + } + + public boolean isCaseSensitiveSearch() { + return fCaseSensitiveSearch; + } + + public void setCaseSensitiveSearch(boolean caseSensitiveSearch) { + fCaseSensitiveSearch = caseSensitiveSearch; + } + + public boolean isWrapSearch() { + return fWrapSearch; + } + + public void setWrapSearch(boolean wrapSearch) { + fWrapSearch = wrapSearch; + } + + public boolean isWholeWordSearchSetting() { + return fWholeWordSearch; + } + + public void setWholeWordSearchSetting(boolean wholeWordSearch) { + fWholeWordSearch = wholeWordSearch; + } + + public FindReplaceStatus getStatus() { + return status; + } + + public void setStatus(FindReplaceStatus status) { + this.status = status; + } + + public void resetStatus() { + status.resetStatus(); + } + + /** + * Returns true if searching should be restricted to entire words, + * false if not. This is the case if the respective checkbox is + * turned on, regex is off, and the checkbox is enabled, i.e. the current find + * string is an entire word. + * + * @return true if the search is restricted to whole words + */ + private boolean isWholeWordSearch() { + return isWholeWordSearchSetting() && !isRegExSearchAvailableAndChecked(); + } + + public boolean isForwardSearch() { + return fForwardSearch; + } + + public void setForwardSearch(boolean forwardSearch) { + fForwardSearch = forwardSearch; + } + + public boolean isTargetSupportingRegEx() { + return fIsTargetSupportingRegEx; + } + + public void setIsTargetSupportingRegEx(boolean isTargetSupportingRegEx) { + fIsTargetSupportingRegEx = isTargetSupportingRegEx; + } + + public boolean isIncrementalSearch() { + return fIncrementalSearch; + } + + public void setIncrementalSearch(boolean incrementalSearch) { + fIncrementalSearch = incrementalSearch; + } + + public boolean isRegexSearch() { + return fRegexSearch; + } + + public void setRegexSearch(boolean regexSearch) { + fRegexSearch = regexSearch; + } + + + public boolean isRegExSearchAvailableAndChecked() { + return isRegexSearch() && fIsTargetSupportingRegEx; + } + + + /** + * initializes the anchor used as starting point for incremental searching. + * + * @since 2.0 + */ + public void initIncrementalBaseLocation() { + if (fTarget != null && isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) { + fIncrementalBaseLocation = fTarget.getSelection(); + } else { + fIncrementalBaseLocation = new Point(0, 0); + } + } + + /** + * Tells the dialog to perform searches only in the scope given by the actually + * selected lines. + * + * @param selectedLines true if selected lines should be used + * @since 2.0 + */ + public void useSelectedLines(boolean selectedLines) { + if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) + initIncrementalBaseLocation(); + + if (fTarget == null || !(fTarget instanceof IFindReplaceTargetExtension)) + return; + + IFindReplaceTargetExtension extensionTarget = (IFindReplaceTargetExtension) fTarget; + + if (selectedLines) { + + IRegion scope; + if (fOldScope == null) { + Point lineSelection = extensionTarget.getLineSelection(); + scope = new Region(lineSelection.x, lineSelection.y); + } else { + scope = fOldScope; + fOldScope = null; + } + + int offset = isForwardSearch() ? scope.getOffset() : scope.getOffset() + scope.getLength(); + + extensionTarget.setSelection(offset, 0); + extensionTarget.setScope(scope); + } else { + fOldScope = extensionTarget.getScope(); + extensionTarget.setScope(null); + } + } + + /** + * Returns the status line manager of the active editor or null if + * there is no such editor. + * + * @return the status line manager of the active editor + */ + private IEditorStatusLine getStatusLineManager() { + IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (window == null) + return null; + + IWorkbenchPage page = window.getActivePage(); + if (page == null) + return null; + + IEditorPart editor = page.getActiveEditor(); + if (editor == null) + return null; + + return editor.getAdapter(IEditorStatusLine.class); + } + + + /** + * Replaces all occurrences of the user's findString with the replace string. + * Indicate to the user the number of replacements that occur. + * + * @param findString + * @param replaceString + * @param display + */ + public void performReplaceAll(String findString, String replaceString, Display display) { + + int replaceCount = 0; + + if (findString != null && !findString.isEmpty()) { + + class ReplaceAllRunnable implements Runnable { + public int numberOfOccurrences; + + @Override + public void run() { + numberOfOccurrences = replaceAll(findString, replaceString == null ? "" : replaceString, //$NON-NLS-1$ + isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); + } + } + + try { + ReplaceAllRunnable runnable = new ReplaceAllRunnable(); + BusyIndicator.showWhile(display, runnable); + replaceCount = runnable.numberOfOccurrences; + + if (replaceCount != 0) { + if (replaceCount == 1) { // not plural + statusMessage(EditorMessages.FindReplace_Status_replacement_label); + } else { + String msg = EditorMessages.FindReplace_Status_replacements_label; + msg = NLSUtility.format(msg, String.valueOf(replaceCount)); + statusMessage(msg); + } + } else { + String msg = NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, + findString); + statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); + } + } catch (PatternSyntaxException ex) { + statusError(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state in this dialog + } + } + } + + /** + * Replaces all occurrences of the user's findString with the replace string. + * Indicate to the user the number of replacements that occur. + */ + public void performSelectAll(String findString, Display display) { + + int selectCount = 0; + + if (findString != null && !findString.isEmpty()) { + + class SelectAllRunnable implements Runnable { + public int numberOfOccurrences; + + @Override + public void run() { + numberOfOccurrences = selectAll(findString, isCaseSensitiveSearch(), isWholeWordSearch(), + isRegExSearchAvailableAndChecked()); + } + } + + try { + SelectAllRunnable runnable = new SelectAllRunnable(); + BusyIndicator.showWhile(display, runnable); + selectCount = runnable.numberOfOccurrences; + + if (selectCount != 0) { + if (selectCount == 1) { // not plural + statusMessage(EditorMessages.FindReplace_Status_selection_label); + } else { + String msg = EditorMessages.FindReplace_Status_selections_label; + msg = NLSUtility.format(msg, String.valueOf(selectCount)); + statusMessage(msg); + } + } else { + String msg = NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, + findString); + statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); + } + } catch (PatternSyntaxException ex) { + statusError(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state in this dialog + } + } + } + + /** + * Validates the state of the find/replace target. + * + * @return true if target can be changed, false + * otherwise + * @since 2.1 + */ + public boolean validateTargetState() { + + if (fTarget instanceof IFindReplaceTargetExtension2) { + IFindReplaceTargetExtension2 extension = (IFindReplaceTargetExtension2) fTarget; + if (!extension.validateTargetState()) { + statusError(EditorMessages.FindReplaceDialog_read_only); + return false; + } + } + return isEditable(); + } + + /** + * Replaces the current selection of the target with the user's replace string. + * + * @return true if the operation was successful + */ + public boolean performReplaceSelection(String replaceString) { + + if (!validateTargetState()) + return false; + + if (replaceString == null) + replaceString = ""; //$NON-NLS-1$ + + boolean replaced; + try { + replaceSelection(replaceString, isRegExSearchAvailableAndChecked()); + replaced = true; + } catch (PatternSyntaxException ex) { + statusError(ex.getLocalizedMessage()); + replaced = false; + } catch (IllegalStateException ex) { + replaced = false; + } + + return replaced; + } + + /** + * Locates the user's findString in the text of the target. + * + * @param searchString + * @return + * + * @since 3.7 + */ + public boolean performSearch(String searchString) { + return performSearch(isIncrementalSearch() && !isRegExSearchAvailableAndChecked(), searchString); + } + + /** + * Locates the user's findString in the text of the target. + * + * @param mustInitIncrementalBaseLocation true if base location + * must be initialized + * @param findString + * @return + * @since 3.0 + */ + public boolean performSearch(boolean mustInitIncrementalBaseLocation, String findString) { + + if (mustInitIncrementalBaseLocation) + initIncrementalBaseLocation(); + + boolean somethingFound = false; + + if (findString != null && !findString.isEmpty()) { + + try { + somethingFound = findNext(findString, isForwardSearch()); + } catch (PatternSyntaxException ex) { + statusError(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state in this dialog + } + } + return somethingFound; + } + + /** + * Replaces all occurrences of the user's findString with the replace string. + * Returns the number of replacements that occur. + * + * @param findString the string to search for + * @param replaceString the replacement string + * @param caseSensitive should the search be case sensitive + * @param wholeWord does the search string represent a complete word + * @param regExSearch if true findString represents a regular + * expression + * @return the number of occurrences + * + * @since 3.0 + */ + public int replaceAll(String findString, String replaceString, boolean caseSensitive, boolean wholeWord, + boolean regExSearch) { + + int replaceCount = 0; + int findReplacePosition = 0; + + findReplacePosition = 0; + + if (!validateTargetState()) + return replaceCount; + + if (fTarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(true); + + try { + int index = 0; + while (index != -1) { + index = findAndSelect(findReplacePosition, findString); + if (index != -1) { // substring not contained from current position + Point selection = replaceSelection(replaceString, regExSearch); + replaceCount++; + findReplacePosition = selection.x + selection.y; + } + } + } finally { + if (fTarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) fTarget).setReplaceAllMode(false); + } + + return replaceCount; + } + + public int selectAll(String findString, boolean caseSensitive, boolean wholeWord, boolean regExSearch) { + + int replaceCount = 0; + int position = 0; + + if (!validateTargetState()) + return replaceCount; + + List selectedRegions = new ArrayList<>(); + int index = 0; + do { + index = findAndSelect(position, findString); + if (index != -1) { // substring not contained from current position + Point selection = fTarget.getSelection(); + selectedRegions.add(new Region(selection.x, selection.y)); + replaceCount++; + position = selection.x + selection.y; + } + } while (index != -1); + if (fTarget instanceof IFindReplaceTargetExtension4) { + ((IFindReplaceTargetExtension4) fTarget).setSelection(selectedRegions.toArray(IRegion[]::new)); + } + + return replaceCount; + } + + /** + * Returns the position of the specified search string, or -1 if + * the string can not be found when searching using the given options. + * + * @param findString the string to search for + * @param startPosition the position at which to start the search + * @param forwardSearch the direction of the search + * @param caseSensitive should the search be case sensitive + * @param wrapSearch should the search wrap to the start/end if arrived at + * the end/start + * @param wholeWord does the search string represent a complete word + * @param regExSearch if true findString represents a regular + * expression + * @param beep if true beeps when search does not find a + * match or needs to wrap + * @return the occurrence of the find string following the options or + * -1 if nothing found + * @since 3.0 + */ + public int findIndex(String findString, int startPosition) { + + if (isForwardSearch()) { + int index = findAndSelect(startPosition, findString); + if (index == -1) { + + status.doBeep(); + + if (isWrapSearch()) { + statusMessage(EditorMessages.FindReplace_Status_wrapped_label); + index = findAndSelect(-1, findString); + } + } + return index; + } + + // backward + int index = startPosition == 0 ? -1 + : findAndSelect(startPosition - 1, findString); + if (index == -1) { + + status.doBeep(); + + if (isWrapSearch()) { + statusMessage(EditorMessages.FindReplace_Status_wrapped_label); + index = findAndSelect(-1, findString); + } + } + return index; + } + + /** + * Searches for a string starting at the given offset and using the specified + * search directives. If a string has been found it is selected and its start + * offset is returned. + * + * @param offset the offset at which searching starts + * @param findString the string which should be found + * @param forwardSearch the direction of the search + * @param caseSensitive true performs a case sensitive search, + * false an insensitive search + * @param wholeWord if true only occurrences are reported in + * which the findString stands as a word by itself + * @param regExSearch if true findString represents a regular + * expression + * @return the position of the specified string, or -1 if the string has not + * been found + * @since 3.0 + */ + public int findAndSelect(int offset, String findString) { + if (fTarget instanceof IFindReplaceTargetExtension3) + return ((IFindReplaceTargetExtension3) fTarget).findAndSelect(offset, findString, isForwardSearch(), + isCaseSensitiveSearch(), isWholeWordSearch(), isRegexSearch()); + return fTarget.findAndSelect(offset, findString, isForwardSearch(), isCaseSensitiveSearch(), + isWholeWordSearch()); + } + + /** + * Replaces the selection with replaceString. If + * regExReplace is true, replaceString is + * a regex replace pattern which will get expanded if the underlying target + * supports it. Returns the region of the inserted text; note that the returned + * selection covers the expanded pattern in case of regex replace. + * + * @param replaceString the replace string (or a regex pattern) + * @param regExReplace true if replaceString is a + * pattern + * @return the selection after replacing, i.e. the inserted text + * @since 3.0 + */ + public Point replaceSelection(String replaceString, boolean regExReplace) { + if (fTarget instanceof IFindReplaceTargetExtension3) + ((IFindReplaceTargetExtension3) fTarget).replaceSelection(replaceString, regExReplace); + else + fTarget.replaceSelection(replaceString); + + return fTarget.getSelection(); + } + + /** + * Returns whether the specified search string can be found using the given + * options. + * + * @param findString the string to search for + * @param forwardSearch the direction of the search + * @param caseSensitive should the search be case sensitive + * @param wrapSearch should the search wrap to the start/end if arrived at + * the end/start + * @param wholeWord does the search string represent a complete word + * @param incremental is this an incremental search + * @param regExSearch if true findString represents a regular + * expression + * @param beep if true beeps when search does not find a + * match or needs to wrap + * @return true if the search string can be found using the given + * options + * + * @since 3.0 + */ + public boolean findNext(String findString, boolean forwardSearch) { + + if (fTarget == null) + return false; + + Point r = null; + if (isIncrementalSearch()) + r = fIncrementalBaseLocation; + else + r = fTarget.getSelection(); + + int findReplacePosition = r.x; + if (forwardSearch && !fNeedsInitialFindBeforeReplace || !forwardSearch && fNeedsInitialFindBeforeReplace) + findReplacePosition += r.y; + + fNeedsInitialFindBeforeReplace = false; + + int index = findIndex(findString, findReplacePosition); + + if (index == -1) { + String msg = NLSUtility.format(EditorMessages.FindReplace_Status_noMatchWithValue_label, findString); + statusMessage(false, EditorMessages.FindReplace_Status_noMatch_label, msg); + return false; + } + + if (forwardSearch && index >= findReplacePosition || !forwardSearch && index <= findReplacePosition) + statusMessage(""); //$NON-NLS-1$ + + return true; + } + + public boolean performFindFirstThenReplaceInASecondStep(String findString, String replaceString) { + if (fNeedsInitialFindBeforeReplace) { + performSearch(findString); + } + if (performReplaceSelection(replaceString)) { + performSearch(findString); + return true; + } + return false; + } + + public boolean performSelectAndReplace(String findString, String replaceString) { + if (fNeedsInitialFindBeforeReplace) + performSearch(findString); + return performReplaceSelection(replaceString); + } + + public void updateTarget(IFindReplaceTarget target, boolean isTargetEditable) { + fTargetEditable = isTargetEditable; + fNeedsInitialFindBeforeReplace = true; + + if (target != fTarget) { + if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) fTarget).endSession(); + + fTarget = target; + if (fTarget != null) + fIsTargetSupportingRegEx = fTarget instanceof IFindReplaceTargetExtension3; + + if (fTarget instanceof IFindReplaceTargetExtension) { + ((IFindReplaceTargetExtension) fTarget).beginSession(); + + setGlobalSearch(true); + } + } + + initIncrementalBaseLocation(); + } + + public void endSession() { + if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) fTarget).endSession(); + + fTarget = null; + } + + + public void deactivateScope() { + if (fTarget != null && (fTarget instanceof IFindReplaceTargetExtension)) + ((IFindReplaceTargetExtension) fTarget).setScope(null); + + fOldScope = null; + } + + public String getCurrentSelection() { + if (fTarget == null) { + return null; + } + + return fTarget.getSelectionText(); + } + + /** + * Returns whether the target is editable. + * + * @return true if target is editable + */ + public boolean isEditable() { + boolean isEditable = (fTarget == null ? false : fTarget.isEditable()); + return fTargetEditable && isEditable; + } + + public boolean supportsMultiSelection() { + return fTarget instanceof IFindReplaceTargetExtension4; + } + + public boolean isTargetAvailable() { + return fTarget != null; + } + + /** + * Sets the given status message in the status line. + * + * @param error true if it is an error + * @param dialogMessage the message to display in the dialog's status line + * @param editorMessage the message to display in the editor's status line + */ + private void statusMessage(boolean error, String dialogMessage, String editorMessage) { + status.setError(error); + status.setMessage(dialogMessage); + + IEditorStatusLine statusLine = getStatusLineManager(); + if (statusLine != null) + statusLine.setMessage(error, editorMessage, null); + } + + /** + * Sets the given error message in the status line. + * + * @param message the message + */ + private void statusError(String message) { + statusMessage(true, message, message); + } + + /** + * Sets the given message in the status line. + * + * @param message the message + */ + private void statusMessage(String message) { + statusMessage(false, message, message); + } + + public void updateSearchResultAfterTextWasModified(String searchString) { + if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) { + if (searchString.equals("") && fTarget != null) { //$NON-NLS-1$ + // empty selection at base location + int offset = fIncrementalBaseLocation.x; + + if (isForwardSearch() && !fNeedsInitialFindBeforeReplace + || !isForwardSearch() && fNeedsInitialFindBeforeReplace) + offset = offset + fIncrementalBaseLocation.y; + + fNeedsInitialFindBeforeReplace = false; + findAndSelect(offset, ""); //$NON-NLS-1$ + } else { + performSearch(false, searchString); + } + } + } + + public boolean isTargetEditable() { + return fTargetEditable; + } + +}