diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindAndReplaceMessageStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindAndReplaceMessageStatus.java new file mode 100644 index 00000000000..255333c3e8b --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindAndReplaceMessageStatus.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * 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; + +/** + * Use by FindAndReplace to signal warnings, errors and messages to + * FindAndReplaceDialog and FindAndReplaceOverlay. + * + * @since 3.17 + */ +class FindAndReplaceMessageStatus { + private boolean error; + private boolean warning; + private String message; + + public void resetStatus() { + error = false; + warning = false; + setMessage(""); //$NON-NLS-1$ + } + + 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; + } + + public void setWarning(boolean warning) { + this.warning = warning; + } + + public boolean isWarning() { + return warning; + } + + @Override + public FindAndReplaceMessageStatus clone() { + FindAndReplaceMessageStatus ret = new FindAndReplaceMessageStatus(); + ret.setMessage(message); + ret.setError(isError()); + ret.setWarning(isWarning()); + return ret; + } + +} \ No newline at end of file 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..9838f7631fc 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 FindReplaceLogic 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 FindReplaceLogic(); 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,27 @@ private void writeHistory(List history, IDialogSettings settings, String settings.put(sectionName, names); } + + private void evaluateFindReplacerStatus() { + FindAndReplaceMessageStatus status = findReplacer.getStatus(); + + String dialogMessage = status.getMessage(); + boolean error = status.isError(); + boolean warning = status.isWarning(); + + fStatusLabel.setText(dialogMessage); + + if (error) { + fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); + } + else { + fStatusLabel.setForeground(null); + } + + if ((error || warning) && okToUse(getShell())) { + getShell().getDisplay().beep(); + } + + findReplacer.resetStatus(); + } } diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceLogic.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceLogic.java new file mode 100644 index 00000000000..837ef737850 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceLogic.java @@ -0,0 +1,790 @@ +/******************************************************************************* + * 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; + +/** + * @since 3.17 + */ +class FindReplaceLogic { + private FindAndReplaceMessageStatus status = new FindAndReplaceMessageStatus(); + private IFindReplaceTarget target; + private IRegion oldScope; + private Point incrementalBaseLocation; + + /** + * Whether the search is incremental. (Search-as-you-type) + */ + private boolean isSearchAsYouType; + + /** + * Tells whether an initial find operation is needed before the replace + * operation. + * + * @since 3.0 + */ + private boolean nextReplactionOperationNeedsFindOperationFirst; + + /** + * Tells whether the target supports regular expressions. true if + * the target supports regular expressions + */ + private boolean fIsTargetSupportingRegEx; + private boolean useRegExSearch; + private boolean searchFoward; + private boolean searchInWholeWords; + private boolean wrapSearch; + private boolean respectCaseInSearch; + private boolean searchEntireDocument; + private boolean isTargetEditable; + + public Point getIncrementalBaseLocation() { + return incrementalBaseLocation; + } + + public void setIncrementalBaseLocation(Point incrementalBaseLocation) { + this.incrementalBaseLocation = incrementalBaseLocation; + } + + public boolean isGlobalSearch() { + return searchEntireDocument; + } + + public void setGlobalSearch(boolean globalSearch) { + searchEntireDocument = globalSearch; + } + + public boolean needsInitialFindBeforeReplace() { + return nextReplactionOperationNeedsFindOperationFirst; + } + + public void setNeedsInitialFindBeforeReplace(boolean needsInitialFindBeforeReplace) { + nextReplactionOperationNeedsFindOperationFirst = needsInitialFindBeforeReplace; + } + + public boolean isCaseSensitiveSearch() { + return respectCaseInSearch; + } + + public void setCaseSensitiveSearch(boolean caseSensitiveSearch) { + respectCaseInSearch = caseSensitiveSearch; + } + + public boolean isWrapSearch() { + return wrapSearch; + } + + public void setWrapSearch(boolean wrapSearch) { + this.wrapSearch = wrapSearch; + } + + public boolean isWholeWordSearchSetting() { + return searchInWholeWords; + } + + public void setWholeWordSearchSetting(boolean wholeWordSearch) { + searchInWholeWords = wholeWordSearch; + } + + public FindAndReplaceMessageStatus getStatus() { + return status.clone(); + } + + public void setStatus(FindAndReplaceMessageStatus 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 searchFoward; + } + + public void setForwardSearch(boolean forwardSearch) { + searchFoward = forwardSearch; + } + + public boolean isTargetSupportingRegEx() { + return fIsTargetSupportingRegEx; + } + + public void setIsTargetSupportingRegEx(boolean isTargetSupportingRegEx) { + fIsTargetSupportingRegEx = isTargetSupportingRegEx; + } + + public boolean isIncrementalSearch() { + return isSearchAsYouType; + } + + public void setIncrementalSearch(boolean incrementalSearch) { + this.isSearchAsYouType = incrementalSearch; + } + + public boolean isRegexSearch() { + return useRegExSearch; + } + + public void setRegexSearch(boolean regexSearch) { + useRegExSearch = regexSearch; + } + + + public boolean isRegExSearchAvailableAndChecked() { + return isRegexSearch() && fIsTargetSupportingRegEx; + } + + + /** + * initializes the anchor used as starting point for incremental searching. + * + * @since 2.0 + */ + public void initIncrementalBaseLocation() { + if (target != null && isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) { + incrementalBaseLocation = target.getSelection(); + } else { + incrementalBaseLocation = 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 (target == null || !(target instanceof IFindReplaceTargetExtension)) + return; + + IFindReplaceTargetExtension extensionTarget = (IFindReplaceTargetExtension) target; + + if (selectedLines) { + + IRegion scope; + if (oldScope == null) { + Point lineSelection = extensionTarget.getLineSelection(); + scope = new Region(lineSelection.x, lineSelection.y); + } else { + scope = oldScope; + oldScope = null; + } + + int offset = isForwardSearch() ? scope.getOffset() : scope.getOffset() + scope.getLength(); + + extensionTarget.setSelection(offset, 0); + extensionTarget.setScope(scope); + } else { + oldScope = 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 replaceString + * @param findString + * @param display + */ + public void performReplaceAll(String replaceString, String findString, 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 (target instanceof IFindReplaceTargetExtension2) { + IFindReplaceTargetExtension2 extension = (IFindReplaceTargetExtension2) target; + 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. + * + * @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 beep if true beeps when search + * does not find a match or needs to wrap + * @param forwardSearch the search direction + * @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 (target instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) target).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 (target instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) target).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 = target.getSelection(); + selectedRegions.add(new Region(selection.x, selection.y)); + replaceCount++; + position = selection.x + selection.y; + } + } while (index != -1); + if (target instanceof IFindReplaceTargetExtension4) { + ((IFindReplaceTargetExtension4) target).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.setWarning(true); + + 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.setWarning(true); + + 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 (target instanceof IFindReplaceTargetExtension3) + return ((IFindReplaceTargetExtension3) target).findAndSelect(offset, findString, isForwardSearch(), + isCaseSensitiveSearch(), isWholeWordSearch(), isRegexSearch()); + return target.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 (target instanceof IFindReplaceTargetExtension3) + ((IFindReplaceTargetExtension3) target).replaceSelection(replaceString, regExReplace); + else + target.replaceSelection(replaceString); + + return target.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 (target == null) + return false; + + Point r = null; + if (isIncrementalSearch()) + r = incrementalBaseLocation; + else + r = target.getSelection(); + + int findReplacePosition = r.x; + if (forwardSearch && !nextReplactionOperationNeedsFindOperationFirst || !forwardSearch && nextReplactionOperationNeedsFindOperationFirst) + findReplacePosition += r.y; + + nextReplactionOperationNeedsFindOperationFirst = 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 (nextReplactionOperationNeedsFindOperationFirst) { + performSearch(findString); + } + if (performReplaceSelection(replaceString)) { + performSearch(findString); + return true; + } + return false; + } + + public boolean performSelectAndReplace(String findString, String replaceString) { + if (nextReplactionOperationNeedsFindOperationFirst) + performSearch(findString); + return performReplaceSelection(replaceString); + } + + public void updateTarget(IFindReplaceTarget newRarget, boolean isTargetEditable) { + this.isTargetEditable = isTargetEditable; + nextReplactionOperationNeedsFindOperationFirst = true; + + if (this.target != newRarget) { + if (newRarget != null && newRarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) newRarget).endSession(); + + this.target = newRarget; + if (newRarget != null) + fIsTargetSupportingRegEx = newRarget instanceof IFindReplaceTargetExtension3; + + if (newRarget instanceof IFindReplaceTargetExtension) { + ((IFindReplaceTargetExtension) newRarget).beginSession(); + + setGlobalSearch(true); + } + } + + initIncrementalBaseLocation(); + } + + public void endSession() { + if (target != null && target instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) target).endSession(); + + target = null; + } + + + public void deactivateScope() { + if (target != null && (target instanceof IFindReplaceTargetExtension)) + ((IFindReplaceTargetExtension) target).setScope(null); + + oldScope = null; + } + + public String getCurrentSelection() { + if (target == null) { + return null; + } + + return target.getSelectionText(); + } + + /** + * Returns whether the target is editable. + * + * @return true if target is editable + */ + public boolean isEditable() { + boolean isEditable = (target == null ? false : target.isEditable()); + return isTargetEditable && isEditable; + } + + public boolean supportsMultiSelection() { + return target instanceof IFindReplaceTargetExtension4; + } + + public boolean isTargetAvailable() { + return target != 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("") && target != null) { //$NON-NLS-1$ + // empty selection at base location + int offset = incrementalBaseLocation.x; + + if (isForwardSearch() && !nextReplactionOperationNeedsFindOperationFirst + || !isForwardSearch() && nextReplactionOperationNeedsFindOperationFirst) + offset = offset + incrementalBaseLocation.y; + + nextReplactionOperationNeedsFindOperationFirst = false; + findAndSelect(offset, ""); //$NON-NLS-1$ + } else { + performSearch(false, searchString); + } + } + } + +}