diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png new file mode 100644 index 00000000000..1a71a7923a9 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_next.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png new file mode 100644 index 00000000000..7eaaff97987 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/dlcl16/select_prev.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png new file mode 100644 index 00000000000..9fcc646d92b Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_next.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png new file mode 100644 index 00000000000..f2e6a039c5d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/elcl16/select_prev.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png new file mode 100644 index 00000000000..7be33cd83c8 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/case_sensitive.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png new file mode 100644 index 00000000000..018dc33f6fe Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/close_replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png new file mode 100644 index 00000000000..c6aeae4d462 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/open_replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif new file mode 100644 index 00000000000..81fb7b4a39d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/regex_gear.gif differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png new file mode 100644 index 00000000000..d9a07e503ef Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png new file mode 100644 index 00000000000..b6af9dbf57b Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/replace_all.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png new file mode 100644 index 00000000000..997b0545b89 Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/search_all.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png new file mode 100644 index 00000000000..bd2eacec42d Binary files /dev/null and b/bundles/org.eclipse.ui.workbench.texteditor/icons/full/obj16/whole_word.png differ diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogic.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogic.java new file mode 100644 index 00000000000..dd942f3b3d9 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogic.java @@ -0,0 +1,743 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ui.internal.findandreplace; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +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; + +import org.eclipse.ui.texteditor.IEditorStatusLine; +import org.eclipse.ui.texteditor.IFindReplaceTargetExtension2; + +public class FindReplaceLogic implements IFindReplaceLogic { + private IFindReplaceLogicStatus status; + private IFindReplaceTarget target; + private IRegion oldScope; + private Point incrementalBaseLocation; + + private boolean isTargetSupportingRegEx; + private boolean isTargetEditable; + private Set searchOptions = new HashSet<>(); + + @Override + public void activate(SearchOptions searchOption) { + searchOptions.add(searchOption); + + switch (searchOption) { + case FORWARD: + case INCREMENTAL: + if (shouldInitIncrementalBaseLocation()) { + initIncrementalBaseLocation(); + } + //$FALL-THROUGH$ + // $CASES-OMITTED$ + default: + break; + } + } + + @Override + public void deactivate(SearchOptions searchOption) { + searchOptions.remove(searchOption); + + if (searchOption == SearchOptions.FORWARD && shouldInitIncrementalBaseLocation()) { + initIncrementalBaseLocation(); + } + } + + @Override + public boolean isActive(SearchOptions searchOption) { + return searchOptions.contains(searchOption); + } + + /** + * Returns the current status of FindReplaceLogic. Assumes that + * resetStatus was run before the last operation. The Status can + * inform about events such as an error happening, a warning happening (e.g.: + * the search-string wasn't found) and can including a message that the UI may + * choose to print. + * + * @return FindAndReplaceMessageStatus + */ + @Override + public IFindReplaceLogicStatus getStatus() { + if (status == null) { + return new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.NONE); + } + return status; + } + + /** + * Call before running an operation of FindReplaceLogic. Resets the internal + * status. + */ + private void resetStatus() { + status = null; + } + + /** + * 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 isWholeWordSearchAvailableAndActive() { + return isActive(SearchOptions.WHOLE_WORD) && !isRegExSearchAvailableAndActive(); + } + + @Override + public boolean isRegExSearchAvailableAndActive() { + return isActive(SearchOptions.REGEX) && isTargetSupportingRegEx; + } + + + /** + * Initializes the anchor used as starting point for incremental searching. + * + */ + private void initIncrementalBaseLocation() { + if (target != null && isActive(SearchOptions.INCREMENTAL) && !isRegExSearchAvailableAndActive()) { + incrementalBaseLocation = target.getSelection(); + } else { + incrementalBaseLocation = new Point(0, 0); + } + } + + public boolean shouldInitIncrementalBaseLocation() { + return isActive(SearchOptions.INCREMENTAL) && !isActive(SearchOptions.REGEX); + } + + /** + * 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 + */ + @Override + public void useSelectedLines(boolean selectedLines) { + if (shouldInitIncrementalBaseLocation()) { + 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 = isActive(SearchOptions.FORWARD) ? 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. + * + * status: {@see #getStatus() } message: String telling a user how many + * replacements were performed or that the find-String could not be matched at + * all + * + * @param findString The string that will be replaced + * @param replaceString The string that will replace the findString + * @param display the UI's Display + */ + @Override + public void performReplaceAll(String findString, String replaceString, Display display) { + resetStatus(); + + 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$ + } + } + + try { + ReplaceAllRunnable runnable = new ReplaceAllRunnable(); + BusyIndicator.showWhile(display, runnable); + replaceCount = runnable.numberOfOccurrences; + + if (replaceCount != 0) { + if (replaceCount == 1) { // not plural + statusLineMessage(FindReplaceMessages.FindReplace_Status_replacement_label); + } else { + String msg = FindReplaceMessages.FindReplace_Status_replacements_label; + msg = NLSUtility.format(msg, String.valueOf(replaceCount)); + statusLineMessage(msg); + } + status = new FindReplaceLogicReplaceAllStatus(replaceCount); + } else { + String msg = NLSUtility.format(FindReplaceMessages.FindReplace_Status_noMatchWithValue_label, + findString); + statusLineMessage(false, msg); + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + } catch (PatternSyntaxException ex) { + status = new FindReplaceLogicMessage(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state in this dialog + } + } + } + + /** + * Selects all occurrences of findString. + * + * @param findString The String to find and select + * @param display The UI's Display The UI's Display + */ + @Override + public void performSelectAll(String findString, Display display) { + resetStatus(); + + int selectCount = 0; + + if (findString != null && !findString.isEmpty()) { + + class SelectAllRunnable implements Runnable { + public int numberOfOccurrences; + + @Override + public void run() { + numberOfOccurrences = selectAll(findString); + } + } + + try { + SelectAllRunnable runnable = new SelectAllRunnable(); + BusyIndicator.showWhile(display, runnable); + selectCount = runnable.numberOfOccurrences; + + if (selectCount != 0) { + if (selectCount == 1) { // not plural + statusLineMessage(FindReplaceMessages.FindReplace_Status_selection_label); + } else { + String msg = FindReplaceMessages.FindReplace_Status_selections_label; + msg = NLSUtility.format(msg, String.valueOf(selectCount)); + statusLineMessage(msg); + } + status = new FindReplaceLogicFindAllStatus(selectCount); + } else { + String msg = NLSUtility.format(FindReplaceMessages.FindReplace_Status_noMatchWithValue_label, + findString); + statusLineMessage(false, msg); + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + } catch (PatternSyntaxException ex) { + status = new FindReplaceLogicMessage(ex.getLocalizedMessage()); + } catch (IllegalStateException ex) { + // we don't keep state + } + } + } + + /** + * Validates the state of the find/replace target. Validates the state of this + * target. The predominate intent of this method is to take any action probably + * necessary to ensure that the target can persistently be changed. Regardless + * of whether there is something to do or not, returns whether the target can be + * edited. + * + * @return true if target can be changed, false + * otherwise + */ + private boolean prepareTargetForEditing() { + if (target instanceof IFindReplaceTargetExtension2) { + IFindReplaceTargetExtension2 extension = (IFindReplaceTargetExtension2) target; + if (!extension.validateTargetState()) { + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.READONLY); + return false; + } + } + return isEditable(); + } + + /** + * Replaces the current selection of the target with the user's replace string. + * + * @param replaceString the String to replace the selection with + * + * @return true if the operation was successful + */ + private boolean replaceSelection(String replaceString) { + + if (!prepareTargetForEditing()) { + return false; + } + + if (replaceString == null) { + replaceString = ""; //$NON-NLS-1$ + } + + boolean replaced; + try { + replaceSelection(replaceString, isRegExSearchAvailableAndActive()); + replaced = true; + } catch (PatternSyntaxException ex) { + status = new FindReplaceLogicMessage(ex.getLocalizedMessage()); + replaced = false; + } catch (IllegalStateException ex) { + replaced = false; + } + + return replaced; + } + + /** + * Locates the user's findString in the target + * + * @param searchString the String to search for + * @return Whether the string was found in the target + * + */ + @Override + public boolean performSearch(String searchString) { + return performSearch(shouldInitIncrementalBaseLocation(), searchString); + } + + /** + * Locates the user's findString in the text of the target. + * + * @param mustInitIncrementalBaseLocation true if base location + * must be initialized + * @param findString the String to search for + * @return Whether the string was found in the target + */ + private boolean performSearch(boolean mustInitIncrementalBaseLocation, String findString) { + if (mustInitIncrementalBaseLocation) { + initIncrementalBaseLocation(); + } + resetStatus(); + + boolean somethingFound = false; + + if (findString != null && !findString.isEmpty()) { + + try { + somethingFound = findNext(findString, isActive(SearchOptions.FORWARD)); + } catch (PatternSyntaxException ex) { + status = new FindReplaceLogicMessage(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 + * expression + * @return the number of occurrences + * + */ + private int replaceAll(String findString, String replaceString) { + + int replaceCount = 0; + int findReplacePosition = 0; + + findReplacePosition = 0; + + if (!prepareTargetForEditing()) { + 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, isRegExSearchAvailableAndActive()); + replaceCount++; + findReplacePosition = selection.x + selection.y; + } + } + } finally { + if (target instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) target).setReplaceAllMode(false); + } + + return replaceCount; + } + + /** + * @param findString the String to select as part of the search + * @return The amount of selected Elements + */ + private int selectAll(String findString) { + + int selectCount = 0; + int position = 0; + + if (!prepareTargetForEditing()) { + return selectCount; + } + + 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)); + selectCount++; + position = selection.x + selection.y; + } + } while (index != -1); + if (target instanceof IFindReplaceTargetExtension4) { + ((IFindReplaceTargetExtension4) target).setSelection(selectedRegions.toArray(IRegion[]::new)); + } + + return selectCount; + } + + /** + * 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 + * @return the occurrence of the find string following the options or + * -1 if nothing found + */ + private int findIndex(String findString, int startPosition) { + int index = 0; + if (isActive(SearchOptions.FORWARD)) { + index = findAndSelect(startPosition, findString); + } else { + index = startPosition == 0 ? -1 + : findAndSelect(startPosition - 1, findString); + } + + if (index == -1) { + + if (isActive(SearchOptions.WRAP)) { + statusLineMessage(FindReplaceMessages.FindReplace_Status_wrapped_label); + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.WRAPPED); + index = findAndSelect(-1, findString); + } else { + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + } + 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 + * @return the position of the specified string, or -1 if the string has not + * been found + */ + @Override + public int findAndSelect(int offset, String findString) { + if (target instanceof IFindReplaceTargetExtension3) + return ((IFindReplaceTargetExtension3) target).findAndSelect(offset, findString, + isActive(SearchOptions.FORWARD), + isActive(SearchOptions.CASE_SENSITIVE), isWholeWordSearchAvailableAndActive(), isActive(SearchOptions.REGEX)); + return target.findAndSelect(offset, findString, isActive(SearchOptions.FORWARD), + isActive(SearchOptions.CASE_SENSITIVE), + isWholeWordSearchAvailableAndActive()); + } + + /** + * 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 + */ + private 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 + * @return true if the search string can be found using the given + * options + * + */ + private boolean findNext(String findString, boolean forwardSearch) { + + if (target == null) { + return false; + } + + Point r = null; + if (isActive(SearchOptions.INCREMENTAL)) { + r = incrementalBaseLocation; + } else { + r = target.getSelection(); + } + + int findReplacePosition = r.x; + if (forwardSearch // && !nextReplaceOperationNeedsFindOperationFirst + ) { // || !forwardSearch && nextReplaceOperationNeedsFindOperationFirst) { + findReplacePosition += r.y; + } + + // nextReplaceOperationNeedsFindOperationFirst = false; + + int index = findIndex(findString, findReplacePosition); + + if (index == -1) { + String msg = NLSUtility.format(FindReplaceMessages.FindReplace_Status_noMatchWithValue_label, findString); + statusLineMessage(false, msg); + status = new FindReplaceLogicStatus(FindReplaceLogicStatus.MessageCode.NO_MATCH); + return false; + } + + if (forwardSearch && index >= findReplacePosition || !forwardSearch && index <= findReplacePosition) { + statusLineMessage(""); //$NON-NLS-1$ + } + + return true; + } + + /** + * Replaces the selection and jumps to the next occurrence of findString + * instantly. If another operation annotated that we need to select the + * occurrence of findString first before replacing, this method does so. (eg, + * after replacing once, we automatically perform findAndSelect + * once before being able to replace again). + */ + @Override + public boolean performReplaceAndFind(String findString, String replaceString) { + resetStatus(); + if (performSelectAndReplace(findString, replaceString)) { + performSearch(findString); + return true; + } + return false; + } + + @Override + public boolean performSelectAndReplace(String findString, String replaceString) { + resetStatus(); + boolean needToSelectFirst = !getCurrentSelection().equals(findString); + if (needToSelectFirst) { + performSearch(findString); + } + return replaceSelection(replaceString); + } + + @Override + public void updateTarget(IFindReplaceTarget newTarget, boolean canEditTarget) { + resetStatus(); + this.isTargetEditable = canEditTarget; + // nextReplaceOperationNeedsFindOperationFirst = true; + + if (this.target != newTarget) { + if (newTarget != null && newTarget instanceof IFindReplaceTargetExtension) + ((IFindReplaceTargetExtension) newTarget).endSession(); + + this.target = newTarget; + if (newTarget != null) + isTargetSupportingRegEx = newTarget instanceof IFindReplaceTargetExtension3; + + if (newTarget instanceof IFindReplaceTargetExtension) { + ((IFindReplaceTargetExtension) newTarget).beginSession(); + + activate(SearchOptions.GLOBAL); + } + } + + initIncrementalBaseLocation(); + } + + @Override + public void dispose() { + if (target != null && target instanceof IFindReplaceTargetExtension) { + ((IFindReplaceTargetExtension) target).endSession(); + } + + target = null; + } + + private String getCurrentSelection() { + if (target == null) { + return null; + } + + return target.getSelectionText(); + } + + /** + * Returns whether the target is editable. + * + * @return true if target is editable + */ + private boolean isEditable() { + boolean isEditable = (target == null ? false : target.isEditable()); + return isTargetEditable && isEditable; + } + + /** + * Sets the given status message in the status line. + * + * @param error true if it is an error + * @param editorMessage the message to display in the editor's status line + */ + private void statusLineMessage(boolean error, String editorMessage) { + IEditorStatusLine statusLine = getStatusLineManager(); + if (statusLine != null) { + statusLine.setMessage(error, editorMessage, null); + } + } + + /** + * Sets the given message in the status line. + * + * @param message the message + */ + private void statusLineMessage(String message) { + statusLineMessage(false, message); + } + + /** + * Updates the search result after the Text was Modified. Used in combination + * with setIncrementalSearch(true). This method specifically allows + * for "search-as-you-type" + * + * "Search-as-you-type" is not compatible with RegEx-search. This will + * initialize the base-location for search (if not initialized already) but will + * not update it, meaning that incrementally searching the same string twice in + * a row will always yield the same result, unless the Base location was + * modified (eg., by performing "find next") + * + * @param searchString the String that is to be searched + */ + @Override + public void performIncrementalSearch(String searchString) { + resetStatus(); + + if (isActive(SearchOptions.INCREMENTAL) && !isRegExSearchAvailableAndActive()) { + if (searchString.equals("") && target != null) { //$NON-NLS-1$ + // empty selection at base location + int offset = incrementalBaseLocation.x; + + if (isActive(SearchOptions.FORWARD)) { // && !nextReplaceOperationNeedsFindOperationFirst + // || !isActive(SearchOptions.FORWARD) && + // nextReplaceOperationNeedsFindOperationFirst) { + offset = offset + incrementalBaseLocation.y; + } + + // nextReplaceOperationNeedsFindOperationFirst = false; + findAndSelect(offset, ""); //$NON-NLS-1$ + } else { + performSearch(false, searchString); + } + } + } + + @Override + public IFindReplaceTarget getTarget() { + return target; + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java new file mode 100644 index 00000000000..379d1c6c954 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicFindAllStatus.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +public class FindReplaceLogicFindAllStatus implements IFindReplaceLogicStatus { + private int selectCount; + + public FindReplaceLogicFindAllStatus(int selectCount) { + if (selectCount <= 0) { + // invalid value - what to do? Throw an exception?! @HeikoKlare + } + this.selectCount = selectCount; + } + + public int getSelectCount() { + return selectCount; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java new file mode 100644 index 00000000000..2ad8406eb87 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessage.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +/** + * Avoid using this class. It is used as a glue to correctly map to the + * error-messages generated by RegEx-Errors which are directly displayed in + * plain text. + * + * Prefer using and extending {@link FindReplaceLogicStatus} + */ +public class FindReplaceLogicMessage implements IFindReplaceLogicStatus { + + private String message; + + public FindReplaceLogicMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessageGenerator.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessageGenerator.java new file mode 100644 index 00000000000..a0301507ff7 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicMessageGenerator.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +import org.eclipse.ui.internal.texteditor.NLSUtility; + +public class FindReplaceLogicMessageGenerator implements IFindReplaceLogicStatusVisitor { + + @Override + public String visit(IFindReplaceLogicStatus status) { + return ""; //$NON-NLS-1$ + } + + @Override + public String visit(FindReplaceLogicReplaceAllStatus status) { + int replaceCount = status.getReplaceCount(); + if (replaceCount == 1) { + return FindReplaceMessages.FindReplace_Status_replacement_label; + } + return NLSUtility.format(FindReplaceMessages.FindReplace_Status_replacements_label, replaceCount); + } + + @Override + public String visit(FindReplaceLogicStatus status) { + FindReplaceLogicStatus.MessageCode messageCode = status.getMessageCode(); + String message; + switch (messageCode) { + case NO_MATCH: + message = FindReplaceMessages.FindReplace_Status_noMatch_label; + break; + case WRAPPED: + message = FindReplaceMessages.FindReplace_Status_wrapped_label; + break; + case READONLY: + message = FindReplaceMessages.FindReplaceDialog_read_only; + break; + case NONE: + default: + message = ""; //$NON-NLS-1$ + } + + return message; + } + + @Override + public String visit(FindReplaceLogicMessage status) { + return status.getMessage(); + } + + @Override + public String visit(FindReplaceLogicFindAllStatus status) { + int selectCount = status.getSelectCount(); + if (selectCount == 1) { + return FindReplaceMessages.FindReplace_Status_selection_label; + } + return NLSUtility.format(FindReplaceMessages.FindReplace_Status_selections_label, selectCount); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java new file mode 100644 index 00000000000..7ef140b2b46 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicReplaceAllStatus.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +public class FindReplaceLogicReplaceAllStatus implements IFindReplaceLogicStatus { + private int replaceCount; + + public FindReplaceLogicReplaceAllStatus(int replaceCount) { + this.replaceCount = replaceCount; + } + + public int getReplaceCount() { + return replaceCount; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java new file mode 100644 index 00000000000..0d3bf628bbc --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceLogicStatus.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +public class FindReplaceLogicStatus implements IFindReplaceLogicStatus { + + public enum MessageCode { + NO_MATCH, + NONE, WRAPPED, READONLY, + } + + private MessageCode messageCode; + + public FindReplaceLogicStatus(MessageCode errorCode) { + this.messageCode = errorCode; + } + + public MessageCode getMessageCode() { + return messageCode; + } + + @Override + public T visit(IFindReplaceLogicStatusVisitor visitor) { + return visitor.visit(this); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java new file mode 100644 index 00000000000..e53b4c4e396 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ui.internal.findandreplace; + +import org.eclipse.osgi.util.NLS; + +final public class FindReplaceMessages extends NLS { + + public static final String BUNDLE_NAME = FindReplaceMessages.class.getName(); + + private FindReplaceMessages() { + // Do not instantiate + } + + static { + NLS.initializeMessages(BUNDLE_NAME, FindReplaceMessages.class); + } + + public static String FindReplace_Status_replacement_label; + public static String FindReplace_Status_replacements_label; + public static String FindReplace_Status_noMatchWithValue_label; + public static String FindReplace_Status_wrapped_label; + public static String FindReplaceDialog_read_only; + public static String FindReplace_Status_selections_label; + public static String FindReplace_Status_selection_label; + public static String FindReplace_Status_noMatch_Label; + public static String FindReplace_Status_noMatch_label; + public static String FindNext_Status_noMatch_label; + + // The "classic" Find/Replace-Dialog + public static String FindReplace_Dialog_Title; + public static String FindReplace_Find_label; + public static String FindReplace_Replace_label; + public static String FindReplace_Direction; + public static String FindReplace_ForwardRadioButton_label; + public static String FindReplace_BackwardRadioButton_label; + public static String FindReplace_Scope; + public static String FindReplace_GlobalRadioButton_label; + public static String FindReplace_SelectedRangeRadioButton_label; + public static String FindReplace_Options; + public static String FindReplace_CaseCheckBox_label; + public static String FindReplace_WrapCheckBox_label; + public static String FindReplace_WholeWordCheckBox_label; + public static String FindReplace_IncrementalCheckBox_label; + public static String FindReplace_RegExCheckbox_label; + public static String FindReplace_FindNextButton_label; + public static String FindReplace_ReplaceFindButton_label; + public static String FindReplace_ReplaceSelectionButton_label; + public static String FindReplace_ReplaceAllButton_label; + public static String FindReplace_SelectAllButton_label; + public static String FindReplace_CloseButton_label; + + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties new file mode 100644 index 00000000000..8762ce8a59b --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/FindReplaceMessages.properties @@ -0,0 +1,47 @@ +################################################################################ +# 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 +############################################################################ + +# FindReplace_Status_noMatch_label=String not found +FindReplace_Status_noMatchWithValue_label=String {0} not found +FindReplace_Status_replacement_label=1 match replaced +FindReplace_Status_replacements_label={0} matches replaced +FindReplace_Status_selection_label=1 match selected +FindReplace_Status_selections_label={0} matches selected +FindReplace_Status_wrapped_label=Wrapped search +FindReplace_Status_noMatch_label=String not found +FindNext_Status_noMatch_label=String {0} not found +FindReplaceDialog_read_only=Cannot replace. File is read-only. + +# Messages for the "classic" Find-Replace-Dialog +FindReplace_Dialog_Title= Find/Replace +FindReplace_Find_label=&Find: +FindReplace_Replace_label=R&eplace with: +FindReplace_Direction=Direction +FindReplace_ForwardRadioButton_label=F&orward +FindReplace_BackwardRadioButton_label=&Backward +FindReplace_Scope=Scope +FindReplace_GlobalRadioButton_label=A&ll +FindReplace_SelectedRangeRadioButton_label=Selec&ted lines +FindReplace_Options=Options +FindReplace_CaseCheckBox_label=&Case sensitive +FindReplace_WrapCheckBox_label=Wra&p search +FindReplace_WholeWordCheckBox_label=&Whole word +FindReplace_IncrementalCheckBox_label=&Incremental +FindReplace_RegExCheckbox_label= Regular e&xpressions +FindReplace_FindNextButton_label=Fi&nd +FindReplace_ReplaceFindButton_label=Replace/Fin&d +FindReplace_ReplaceSelectionButton_label=&Replace +FindReplace_ReplaceAllButton_label=Replace &All +FindReplace_SelectAllButton_label=&Select All +FindReplace_CloseButton_label=Close \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java new file mode 100644 index 00000000000..c874092c68c --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/HistoryStore.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.Vector; + +import org.eclipse.jface.dialogs.IDialogSettings; + +/** + * Store for search-histories or replace-histories inside of a dialog.. Stores + * the nodes using the DialogSettings-mechanism. + */ +public class HistoryStore { + IDialogSettings settingsManager; + private int historySize; + Vector history; + String sectionName; + + /** + * @param settingsManager + * @param sectionName The name of the section in the DialogSettings + * containing the history + * @param historySize how many entries to keep in the history + */ + public HistoryStore(IDialogSettings settingsManager, String sectionName, int historySize) { + this.settingsManager = settingsManager; + this.historySize = historySize; + loadSection(sectionName); + } + + public Vector get() { + return history; + } + + public String get(int index) { + return history.get(index); + } + + + public void add(String historyItem) { + if (sectionName == null) { + throw new Error("No section loaded"); //$NON-NLS-1$ + } + if (historyItem != null && !historyItem.isEmpty()) + history.add(0, historyItem); + + writeHistory(); + } + + public boolean isEmpty() { + return history.isEmpty(); + } + + private void loadSection(String newSectionName) { + this.sectionName = newSectionName; + + history = new Vector<>(Arrays.asList(settingsManager.getArray(newSectionName))); + } + + /** + * Writes the given history into the given dialog store. + */ + private void writeHistory() { + int itemCount = history.size(); + Set distinctItems = new HashSet<>(itemCount); + for (int i = 0; i < itemCount; i++) { + String item = history.get(i); + if (distinctItems.contains(item)) { + history.remove(i--); + itemCount--; + } else { + distinctItems.add(item); + } + } + + while (history.size() > historySize) + history.remove(historySize); + + String[] names = new String[history.size()]; + history.toArray(names); + settingsManager.put(sectionName, names); + } + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogic.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogic.java new file mode 100644 index 00000000000..0987569520b --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogic.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +import org.eclipse.swt.widgets.Display; + +import org.eclipse.jface.text.IFindReplaceTarget; + +/** + * Implements a generalized logic operator for in-file Find/Replace-Operations. + * Requires a target that inherits from {@link IFindReplaceTarget} to operate. + * Allows enabling or disabling different {@link SearchOptions} which will be + * applied to subsequent operations. + */ +public interface IFindReplaceLogic { + + /** + * Activate a search option + * + * @param searchOption option + */ + public void activate(SearchOptions searchOption); + + /** + * Deactivate a search option + * + * @param searchOption option + */ + public void deactivate(SearchOptions searchOption); + + /** + * @param searchOption option + * @return whether the option is active + */ + public boolean isActive(SearchOptions searchOption); + + /** + * Returns the current status of FindReplaceLogic. The Status can inform about + * events such as an error happening, a warning happening (e.g.: the + * search-string wasn't found) and brings a method to retrieve a message that + * can directly be displayed to the user. + * + * @return FindAndReplaceMessageStatus + */ + IFindReplaceLogicStatus getStatus(); + + boolean isRegExSearchAvailableAndActive(); + + /** + * 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 + */ + void useSelectedLines(boolean selectedLines); + + /** + * Updates the search result after the Text was Modified. Used in combination + * with setIncrementalSearch(true). This method specifically allows + * for "search-as-you-type" + * + * "Search-as-you-type" is not compatible with RegEx-search. This will + * initialize the base-location for search (if not initialized already) but will + * not update it, meaning that incrementally searching the same string twice in + * a row will always yield the same result, unless the Base location was + * modified (eg., by performing "find next") + * + * @param searchString the String that is to be searched + */ + void performIncrementalSearch(String searchString); + + /** + * Replaces all occurrences of the user's findString with the replace string. + * Indicate to the user the number of replacements that occur. + * + * @param findString The string that will be replaced + * @param replaceString The string that will replace the findString + * @param display the display on which the busy feedback should be + * displayed. If the display is null, the Display for the + * current thread will be used. If there is no Display for + * the current thread,the runnable code will be executed + * and no busy feedback will be displayed.y + */ + void performReplaceAll(String findString, String replaceString, Display display); + + /** + * Selects all occurrences of findString. + * + * @param findString The String to find and select + * @param display The UI's Display The UI's Display + */ + void performSelectAll(String findString, Display display); + + /** + * Locates the user's findString in the target + * + * @param searchString the String to search for + * @return Whether the string was found in the target + * + */ + boolean performSearch(String searchString); + + /** + * 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 + * @return the position of the specified string, or -1 if the string has not + * been found + */ + int findAndSelect(int offset, String findString); + + /** + * Replaces the selection and jumps to the next occurrence of findString + * instantly. Will not fail in case the selection is invalidated, eg. after a + * replace operation or after the target was updated + * + * @param findString the string to replace + * @param replaceString the string to put in place of findString + * @return whether a replacement has been performed + */ + boolean performReplaceAndFind(String findString, String replaceString); + + boolean performSelectAndReplace(String findString, String replaceString); + + void updateTarget(IFindReplaceTarget newTarget, boolean canEditTarget); + + void dispose(); + + /* + * @return the Target that FindReplaceLogic operates on + */ + IFindReplaceTarget getTarget(); + +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java new file mode 100644 index 00000000000..7d367a059e6 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatus.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +/** + * Marker interface for all Status-objects that a Find/Replace-Operation could + * return + */ +public interface IFindReplaceLogicStatus { + public T visit(IFindReplaceLogicStatusVisitor visitor); +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java new file mode 100644 index 00000000000..bdfdb87a740 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/IFindReplaceLogicStatusVisitor.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +public interface IFindReplaceLogicStatusVisitor { + public T visit(IFindReplaceLogicStatus status); + + public T visit(FindReplaceLogicReplaceAllStatus status); + + public T visit(FindReplaceLogicStatus status); + + public T visit(FindReplaceLogicMessage status); + + public T visit(FindReplaceLogicFindAllStatus status); + +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/SearchOptions.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/SearchOptions.java new file mode 100644 index 00000000000..f3238e0989e --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/SearchOptions.java @@ -0,0 +1,18 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.internal.findandreplace; + +public enum SearchOptions { + GLOBAL, WRAP, CASE_SENSITIVE, WHOLE_WORD, REGEX, FORWARD, INCREMENTAL, +} diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java index 9715d5c0e64..fcb5fd0b47c 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.java @@ -98,34 +98,7 @@ private EditorMessages() { public static String Editor_statusline_position_pattern_selection; public static String Editor_statusline_error_label; public static String WorkbenchChainedTextFontFieldEditor_defaultWorkbenchTextFont; - public static String FindReplace_title; - public static String FindReplace_Find_label; - public static String FindReplace_Replace_label; - public static String FindReplace_Direction; - public static String FindReplace_ForwardRadioButton_label; - public static String FindReplace_BackwardRadioButton_label; - public static String FindReplace_Scope; - public static String FindReplace_GlobalRadioButton_label; - public static String FindReplace_SelectedRangeRadioButton_label; - public static String FindReplace_Options; - public static String FindReplace_CaseCheckBox_label; - public static String FindReplace_WrapCheckBox_label; - public static String FindReplace_WholeWordCheckBox_label; - public static String FindReplace_IncrementalCheckBox_label; - public static String FindReplace_RegExCheckbox_label; - public static String FindReplace_FindNextButton_label; - public static String FindReplace_ReplaceFindButton_label; - public static String FindReplace_ReplaceSelectionButton_label; - public static String FindReplace_ReplaceAllButton_label; - public static String FindReplace_SelectAllButton_label; - public static String FindReplace_CloseButton_label; - public static String FindReplace_Status_noMatch_label; - public static String FindReplace_Status_noMatchWithValue_label; - public static String FindReplace_Status_replacement_label; - public static String FindReplace_Status_replacements_label; - public static String FindReplace_Status_selection_label; - public static String FindReplace_Status_selections_label; - public static String FindReplace_Status_wrapped_label; + public static String FindNext_Status_noMatch_label; public static String AbstractDocumentProvider_ok; public static String AbstractDocumentProvider_error; diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties index 8ef0eb868c5..2bed0396c3e 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/EditorMessages.properties @@ -95,34 +95,6 @@ Editor_statusline_error_label=? ## Others ## WorkbenchChainedTextFontFieldEditor_defaultWorkbenchTextFont= -FindReplace_title= Find/Replace -FindReplace_Find_label=&Find: -FindReplace_Replace_label=R&eplace with: -FindReplace_Direction=Direction -FindReplace_ForwardRadioButton_label=F&orward -FindReplace_BackwardRadioButton_label=&Backward -FindReplace_Scope=Scope -FindReplace_GlobalRadioButton_label=A&ll -FindReplace_SelectedRangeRadioButton_label=Selec&ted lines -FindReplace_Options=Options -FindReplace_CaseCheckBox_label=&Case sensitive -FindReplace_WrapCheckBox_label=Wra&p search -FindReplace_WholeWordCheckBox_label=&Whole word -FindReplace_IncrementalCheckBox_label=&Incremental -FindReplace_RegExCheckbox_label= Regular e&xpressions -FindReplace_FindNextButton_label=Fi&nd -FindReplace_ReplaceFindButton_label=Replace/Fin&d -FindReplace_ReplaceSelectionButton_label=&Replace -FindReplace_ReplaceAllButton_label=Replace &All -FindReplace_SelectAllButton_label=&Select All -FindReplace_CloseButton_label=Close -FindReplace_Status_noMatch_label=String not found -FindReplace_Status_noMatchWithValue_label=String ''{0}'' not found -FindReplace_Status_replacement_label=1 match replaced -FindReplace_Status_replacements_label={0} matches replaced -FindReplace_Status_selection_label=1 match selected -FindReplace_Status_selections_label={0} matches selected -FindReplace_Status_wrapped_label=Wrapped search FindNext_Status_noMatch_label=String ''{0}'' not found diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java index 5839928d3c8..6c7c7fa9599 100644 --- a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceAction.java @@ -34,16 +34,15 @@ import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.IWorkbenchWindow; - /** - * An action which opens a Find/Replace dialog. - * The dialog while open, tracks the active workbench part - * and retargets itself to the active find/replace target. + * An action which opens a Find/Replace dialog. The dialog while open, tracks + * the active workbench part and retargets itself to the active find/replace + * target. *

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

*

* This class may be instantiated; it is not intended to be subclassed. @@ -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..2e3f6deb64a 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 @@ -15,18 +15,12 @@ *******************************************************************************/ package org.eclipse.ui.texteditor; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; -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 +56,20 @@ 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.findandreplace.FindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicMessageGenerator; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.FindReplaceMessages; +import org.eclipse.ui.internal.findandreplace.HistoryStore; +import org.eclipse.ui.internal.findandreplace.IFindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.IFindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.SearchOptions; import org.eclipse.ui.internal.texteditor.SWTUtil; @@ -82,9 +77,10 @@ * Find/Replace dialog. The dialog is opened on a particular * target but can be re-targeted. Internally used by the FindReplaceAction */ -class FindReplaceDialog extends Dialog { +public class FindReplaceDialog extends Dialog { private static final int CLOSE_BUTTON_ID = 101; + private IFindReplaceLogic findReplacer; /** * Updates the find replace dialog on activation changes. @@ -108,18 +104,14 @@ public void shellDeactivated(ShellEvent e) { fGlobalRadioButton.setSelection(true); fSelectedRangeRadioButton.setSelection(false); - fUseSelectedLines= false; - - if (fTarget != null && (fTarget instanceof IFindReplaceTargetExtension)) - ((IFindReplaceTargetExtension) fTarget).setScope(null); - - fOldScope= null; - 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 +121,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 +129,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; + findReplacer.performIncrementalSearch(getFindString()); - fNeedsInitialFindBeforeReplace= false; - findAndSelect(offset, "", isForwardSearch(), isCaseSensitiveSearch(), isWholeWordSearch(), isRegExSearchAvailableAndChecked()); //$NON-NLS-1$ - } else { - performSearch(false, false, isForwardSearch()); - } - } - - updateButtonState(!isIncrementalSearch()); + updateButtonState(!findReplacer.isActive(SearchOptions.INCREMENTAL)); } } /** 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 HistoryStore findHistory; + private HistoryStore replaceHistory; - 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 +173,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 +193,13 @@ 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; + setupSearchHistory(); readConfiguration(); + updateButtonState(); setShellStyle(getShellStyle() ^ SWT.APPLICATION_MODAL | SWT.MODELESS); setBlockOnOpen(false); @@ -302,9 +243,9 @@ public void create() { // fill in combo contents fFindField.removeModifyListener(fFindModifyListener); - updateCombo(fFindField, fFindHistory); + updateCombo(fFindField, findHistory.get()); fFindField.addModifyListener(fFindModifyListener); - updateCombo(fReplaceField, fReplaceHistory); + updateCombo(fReplaceField, replaceHistory.get()); // get find string initFindStringFromSelection(); @@ -313,8 +254,9 @@ public void create() { if (fDialogPositionInit != null) shell.setBounds(fDialogPositionInit); - shell.setText(EditorMessages.FindReplace_title); - // shell.setImage(null); + shell.setText(FindReplaceMessages.FindReplace_Dialog_Title); + + updateButtonState(); } /** @@ -330,67 +272,75 @@ private Composite createButtonSection(Composite parent) { layout.numColumns= -2; // this is intended panel.setLayout(layout); - fFindNextButton= makeButton(panel, EditorMessages.FindReplace_FindNextButton_label, 102, true, new SelectionAdapter() { + fFindNextButton = makeButton(panel, FindReplaceMessages.FindReplace_FindNextButton_label, 102, true, + new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) - initIncrementalBaseLocation(); - - fNeedsInitialFindBeforeReplace= false; - performSearch(((e.stateMask & SWT.MODIFIER_MASK) == SWT.SHIFT) ^ isForwardSearch()); + boolean somethingFound = findReplacer.performSearch(getFindString()); + writeSelection(); + updateButtonState(!somethingFound); updateFindHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fFindNextButton, SWT.FILL, true, SWT.FILL, false); - fSelectAllButton = makeButton(panel, EditorMessages.FindReplace_SelectAllButton_label, 106, false, + fSelectAllButton = makeButton(panel, FindReplaceMessages.FindReplace_SelectAllButton_label, 106, false, 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); new Label(panel, SWT.NONE); // filler - fReplaceFindButton= makeButton(panel, EditorMessages.FindReplace_ReplaceFindButton_label, 103, false, new SelectionAdapter() { + fReplaceFindButton = makeButton(panel, FindReplaceMessages.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.performReplaceAndFind(getFindString(), getReplaceString())) { + writeSelection(); + } + updateButtonState(); updateFindAndReplaceHistory(); + evaluateFindReplacerStatus(); } }); setGridData(fReplaceFindButton, SWT.FILL, false, SWT.FILL, false); - fReplaceSelectionButton= makeButton(panel, EditorMessages.FindReplace_ReplaceSelectionButton_label, 104, false, new SelectionAdapter() { + fReplaceSelectionButton = makeButton(panel, FindReplaceMessages.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); - fReplaceAllButton= makeButton(panel, EditorMessages.FindReplace_ReplaceAllButton_label, 105, false, new SelectionAdapter() { + fReplaceAllButton = makeButton(panel, FindReplaceMessages.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()); - return panel; } @@ -527,7 +477,7 @@ private Composite createDirectionGroup(Composite parent) { panel.setLayout(layout); Group group= new Group(panel, SWT.SHADOW_ETCHED_IN); - group.setText(EditorMessages.FindReplace_Direction); + group.setText(FindReplaceMessages.FindReplace_Direction); GridLayout groupLayout= new GridLayout(); group.setLayout(groupLayout); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); @@ -535,29 +485,30 @@ private Composite createDirectionGroup(Composite parent) { SelectionListener selectionListener= new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearchAvailableAndChecked()) - initIncrementalBaseLocation(); + activateInFindReplacerIf(SearchOptions.FORWARD, fForwardRadioButton.getSelection()); } @Override public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing } }; fForwardRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); - fForwardRadioButton.setText(EditorMessages.FindReplace_ForwardRadioButton_label); + fForwardRadioButton.setText(FindReplaceMessages.FindReplace_ForwardRadioButton_label); setGridData(fForwardRadioButton, SWT.LEFT, false, SWT.CENTER, false); fForwardRadioButton.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fForwardRadioButton); Button backwardRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); - backwardRadioButton.setText(EditorMessages.FindReplace_BackwardRadioButton_label); + backwardRadioButton.setText(FindReplaceMessages.FindReplace_BackwardRadioButton_label); setGridData(backwardRadioButton, SWT.LEFT, false, SWT.CENTER, false); backwardRadioButton.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(backwardRadioButton); - backwardRadioButton.setSelection(!fForwardInit); - fForwardRadioButton.setSelection(fForwardInit); + activateInFindReplacerIf(SearchOptions.FORWARD, true); // search forward by default + backwardRadioButton.setSelection(!findReplacer.isActive(SearchOptions.FORWARD)); + fForwardRadioButton.setSelection(findReplacer.isActive(SearchOptions.FORWARD)); return panel; } @@ -578,22 +529,22 @@ private Composite createScopeGroup(Composite parent) { panel.setLayout(layout); Group group= new Group(panel, SWT.SHADOW_ETCHED_IN); - group.setText(EditorMessages.FindReplace_Scope); + group.setText(FindReplaceMessages.FindReplace_Scope); GridLayout groupLayout= new GridLayout(); group.setLayout(groupLayout); group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); fGlobalRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); - fGlobalRadioButton.setText(EditorMessages.FindReplace_GlobalRadioButton_label); + fGlobalRadioButton.setText(FindReplaceMessages.FindReplace_GlobalRadioButton_label); setGridData(fGlobalRadioButton, SWT.LEFT, false, SWT.CENTER, false); - fGlobalRadioButton.setSelection(fGlobalInit); + fGlobalRadioButton.setSelection(findReplacer.isActive(SearchOptions.GLOBAL)); fGlobalRadioButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (!fGlobalRadioButton.getSelection() || !fUseSelectedLines) + if (!fGlobalRadioButton.getSelection() || findReplacer.isActive(SearchOptions.GLOBAL)) return; - fUseSelectedLines= false; - useSelectedLines(false); + activateInFindReplacerIf(SearchOptions.GLOBAL, true); + findReplacer.useSelectedLines(false); } @Override @@ -603,17 +554,16 @@ public void widgetDefaultSelected(SelectionEvent e) { storeButtonWithMnemonicInMap(fGlobalRadioButton); fSelectedRangeRadioButton= new Button(group, SWT.RADIO | SWT.LEFT); - fSelectedRangeRadioButton.setText(EditorMessages.FindReplace_SelectedRangeRadioButton_label); + fSelectedRangeRadioButton.setText(FindReplaceMessages.FindReplace_SelectedRangeRadioButton_label); setGridData(fSelectedRangeRadioButton, SWT.LEFT, false, SWT.CENTER, false); - fSelectedRangeRadioButton.setSelection(!fGlobalInit); - fUseSelectedLines= !fGlobalInit; + fSelectedRangeRadioButton.setSelection(!findReplacer.isActive(SearchOptions.GLOBAL)); fSelectedRangeRadioButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (!fSelectedRangeRadioButton.getSelection() || fUseSelectedLines) + if (!fSelectedRangeRadioButton.getSelection() || !findReplacer.isActive(SearchOptions.GLOBAL)) return; - fUseSelectedLines= true; - useSelectedLines(true); + activateInFindReplacerIf(SearchOptions.GLOBAL, false); + findReplacer.useSelectedLines(true); } @Override @@ -625,42 +575,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 @@ -679,7 +593,7 @@ private Composite createInputPanel(Composite parent) { panel.setLayout(layout); Label findLabel= new Label(panel, SWT.LEFT); - findLabel.setText(EditorMessages.FindReplace_Find_label); + findLabel.setText(FindReplaceMessages.FindReplace_Find_label); setGridData(findLabel, SWT.LEFT, false, SWT.CENTER, false); // Create the find content assist field @@ -698,7 +612,7 @@ private Composite createInputPanel(Composite parent) { fFindField.addModifyListener(fFindModifyListener); fReplaceLabel= new Label(panel, SWT.LEFT); - fReplaceLabel.setText(EditorMessages.FindReplace_Replace_label); + fReplaceLabel.setText(FindReplaceMessages.FindReplace_Replace_label); setGridData(fReplaceLabel, SWT.LEFT, false, SWT.CENTER, false); // Create the replace content assist field @@ -733,7 +647,7 @@ private Composite createOptionsGroup(Composite parent) { panel.setLayout(layout); Group group= new Group(panel, SWT.SHADOW_NONE); - group.setText(EditorMessages.FindReplace_Options); + group.setText(FindReplaceMessages.FindReplace_Options); GridLayout groupLayout= new GridLayout(); groupLayout.numColumns= 2; groupLayout.makeColumnsEqualWidth= true; @@ -743,6 +657,7 @@ private Composite createOptionsGroup(Composite parent) { SelectionListener selectionListener= new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { + setupFindReplacer(); storeSettings(); } @@ -752,36 +667,34 @@ public void widgetDefaultSelected(SelectionEvent e) { }; fCaseCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); - fCaseCheckBox.setText(EditorMessages.FindReplace_CaseCheckBox_label); + fCaseCheckBox.setText(FindReplaceMessages.FindReplace_CaseCheckBox_label); setGridData(fCaseCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fCaseCheckBox.setSelection(fCaseInit); + fCaseCheckBox.setSelection(findReplacer.isActive(SearchOptions.CASE_SENSITIVE)); fCaseCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fCaseCheckBox); fWrapCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); - fWrapCheckBox.setText(EditorMessages.FindReplace_WrapCheckBox_label); + fWrapCheckBox.setText(FindReplaceMessages.FindReplace_WrapCheckBox_label); setGridData(fWrapCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fWrapCheckBox.setSelection(fWrapInit); + fWrapCheckBox.setSelection(findReplacer.isActive(SearchOptions.WRAP)); fWrapCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fWrapCheckBox); fWholeWordCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); - fWholeWordCheckBox.setText(EditorMessages.FindReplace_WholeWordCheckBox_label); + fWholeWordCheckBox.setText(FindReplaceMessages.FindReplace_WholeWordCheckBox_label); setGridData(fWholeWordCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fWholeWordCheckBox.setSelection(fWholeWordInit); + fWholeWordCheckBox.setSelection(findReplacer.isActive(SearchOptions.WHOLE_WORD)); fWholeWordCheckBox.addSelectionListener(selectionListener); storeButtonWithMnemonicInMap(fWholeWordCheckBox); fIncrementalCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); - fIncrementalCheckBox.setText(EditorMessages.FindReplace_IncrementalCheckBox_label); + fIncrementalCheckBox.setText(FindReplaceMessages.FindReplace_IncrementalCheckBox_label); setGridData(fIncrementalCheckBox, SWT.LEFT, false, SWT.CENTER, false); - fIncrementalCheckBox.setSelection(fIncrementalInit); + fIncrementalCheckBox.setSelection(findReplacer.isActive(SearchOptions.INCREMENTAL)); fIncrementalCheckBox.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - if (isIncrementalSearch() && !isRegExSearch()) - initIncrementalBaseLocation(); - + setupFindReplacer(); storeSettings(); } @@ -792,29 +705,30 @@ public void widgetDefaultSelected(SelectionEvent e) { storeButtonWithMnemonicInMap(fIncrementalCheckBox); fIsRegExCheckBox= new Button(group, SWT.CHECK | SWT.LEFT); - fIsRegExCheckBox.setText(EditorMessages.FindReplace_RegExCheckbox_label); + fIsRegExCheckBox.setText(FindReplaceMessages.FindReplace_RegExCheckbox_label); setGridData(fIsRegExCheckBox, SWT.LEFT, false, SWT.CENTER, false); ((GridData)fIsRegExCheckBox.getLayoutData()).horizontalSpan= 2; - fIsRegExCheckBox.setSelection(fIsRegExInit); + fIsRegExCheckBox.setSelection(findReplacer.isActive(SearchOptions.REGEX)); 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.isRegExSearchAvailableAndActive()); fWholeWordCheckBox.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateButtonState(); } }); - fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fIncrementalCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndActive()); return panel; } @@ -836,7 +750,7 @@ private Composite createStatusAndCloseButton(Composite parent) { fStatusLabel= new Label(panel, SWT.LEFT); setGridData(fStatusLabel, SWT.FILL, true, SWT.CENTER, false); - String label= EditorMessages.FindReplace_CloseButton_label; + String label = FindReplaceMessages.FindReplace_CloseButton_label; Button closeButton = createButton(panel, CLOSE_BUTTON_ID, label, false); setGridData(closeButton, SWT.RIGHT, false, SWT.BOTTOM, false); @@ -856,142 +770,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. @@ -1003,14 +781,6 @@ private Rectangle getDialogBoundaries() { return fDialogPositionInit; } - /** - * Returns the dialog's history. - * @return the dialog's history - */ - private List getFindHistory() { - return fFindHistory; - } - // ------- accessors --------------------------------------- /** @@ -1024,14 +794,6 @@ private String getFindString() { return ""; //$NON-NLS-1$ } - /** - * Returns the dialog's replace history. - * @return the dialog's replace history - */ - private List getReplaceHistory() { - return fReplaceHistory; - } - /** * Retrieves the replacement string from the appropriate text input field and returns it. * @return the replacement string @@ -1091,13 +853,10 @@ private void handleDialogClose() { // store current settings in case of re-open storeSettings(); - if (fTarget != null && fTarget instanceof IFindReplaceTargetExtension) - ((IFindReplaceTargetExtension) fTarget).endSession(); + findReplacer.dispose(); // prevent leaks fActiveShell= null; - fTarget= null; - } /** @@ -1105,11 +864,12 @@ private void handleDialogClose() { * @since 3.0 */ private void writeSelection() { - if (fTarget == null) + String selection = 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 +878,6 @@ private void writeSelection() { */ private void storeSettings() { fDialogPositionInit= getDialogBoundaries(); - fWrapInit= isWrapSearch(); - fWholeWordInit= isWholeWordSetting(); - fCaseInit= isCaseSensitiveSearch(); - fIsRegExInit= isRegExSearch(); - fIncrementalInit= isIncrementalSearch(); - fForwardInit= isForwardSearch(); writeConfiguration(); } @@ -1134,9 +888,9 @@ private void storeSettings() { * action's target. */ private void initFindStringFromSelection() { - if (fTarget != null && okToUse(fFindField)) { - String fullSelection= fTarget.getSelectionText(); - boolean isRegEx= isRegExSearchAvailableAndChecked(); + String fullSelection = getCurrentSelection(); + if (fullSelection != null && okToUse(fFindField)) { + boolean isRegEx = findReplacer.isRegExSearchAvailableAndActive(); fFindField.removeModifyListener(fFindModifyListener); if (!fullSelection.isEmpty()) { String firstLine= getFirstLine(fullSelection); @@ -1144,15 +898,14 @@ 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$ - if (!fFindHistory.isEmpty()) - fFindField.setText(fFindHistory.get(0)); + if (!findHistory.isEmpty()) + fFindField.setText(findHistory.get(0)); else fFindField.setText(""); //$NON-NLS-1$ } @@ -1162,114 +915,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 +944,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 --------------------------------------- @@ -1680,23 +1000,33 @@ private void updateButtonState() { * @since 3.0 */ private void updateButtonState(boolean disableReplace) { + setupFindReplacer(); if (okToUse(getShell()) && okToUse(fFindNextButton)) { - boolean selection= false; - if (fTarget != null) - selection= !fTarget.getSelectionText().isEmpty(); + boolean hasActiveSelection = false; + String selection = getCurrentSelection(); + if (selection != null) + hasActiveSelection = !selection.isEmpty(); - boolean enable= fTarget != null && (fActiveShell == fParentShell || fActiveShell == getShell()); + boolean enable = (findReplacer.getTarget() != null) + && (fActiveShell == fParentShell || fActiveShell == getShell()); String str= getFindString(); boolean findString= str != null && !str.isEmpty(); - fWholeWordCheckBox.setEnabled(isWord(str) && !isRegExSearchAvailableAndChecked()); - + fWholeWordCheckBox.setEnabled(isWord(str) && !findReplacer.isRegExSearchAvailableAndActive()); 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.getTarget() instanceof IFindReplaceTargetExtension4)); + fReplaceSelectionButton + .setEnabled(!disableReplace && enable && findReplacer.getTarget().isEditable() && hasActiveSelection + && (findReplacer.getTarget().getSelection().toString() != "" //$NON-NLS-1$ + || !findReplacer.isRegExSearchAvailableAndActive())); + fReplaceFindButton + .setEnabled(!disableReplace && enable && findReplacer.getTarget().isEditable() && findString + && hasActiveSelection + && (findReplacer.getTarget().getSelection().toString() != "" //$NON-NLS-1$ + || !findReplacer.isRegExSearchAvailableAndActive())); + fReplaceAllButton.setEnabled(enable && findReplacer.getTarget().isEditable() && findString); } } @@ -1738,7 +1068,7 @@ private void updateCombo(Combo combo, List content) { private void updateFindAndReplaceHistory() { updateFindHistory(); if (okToUse(fReplaceField)) { - updateHistory(fReplaceField, fReplaceHistory); + updateHistory(fReplaceField, replaceHistory.get()); } } @@ -1754,7 +1084,7 @@ private void updateFindHistory() { if (Util.isLinux()) fFindModifyListener.ignoreNextEvent(); - updateHistory(fFindField, fFindHistory); + updateHistory(fFindField, findHistory.get()); fFindField.addModifyListener(fFindModifyListener); } } @@ -1779,15 +1109,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 +1117,37 @@ 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(); + boolean globalSearch = findReplacer.isActive(SearchOptions.GLOBAL); - 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; - } - } + fGlobalRadioButton.setSelection(globalSearch); + boolean useSelectedLines = !globalSearch; + fSelectedRangeRadioButton.setSelection(useSelectedLines); + findReplacer.useSelectedLines(useSelectedLines); if (okToUse(fIsRegExCheckBox)) - fIsRegExCheckBox.setEnabled(fIsTargetSupportingRegEx); + fIsRegExCheckBox.setEnabled(findReplacer.getTarget() instanceof IFindReplaceTargetExtension3); if (okToUse(fWholeWordCheckBox)) - fWholeWordCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fWholeWordCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndActive()); if (okToUse(fIncrementalCheckBox)) - fIncrementalCheckBox.setEnabled(!isRegExSearchAvailableAndChecked()); + fIncrementalCheckBox.setEnabled(!findReplacer.isRegExSearchAvailableAndActive()); if (okToUse(fReplaceLabel)) { - fReplaceLabel.setEnabled(isEditable()); - fReplaceField.setEnabled(isEditable()); + fReplaceLabel.setEnabled(findReplacer.getTarget().isEditable()); + fReplaceField.setEnabled(findReplacer.getTarget().isEditable()); + fReplaceAllButton.setEnabled(findReplacer.getTarget().isEditable()); if (initializeFindString) { initFindStringFromSelection(); - fGiveFocusToFindField= true; + fGiveFocusToFindField = true; } - initIncrementalBaseLocation(); } + updateButtonState(); - setContentAssistsEnablement(isRegExSearchAvailableAndChecked()); + setContentAssistsEnablement(findReplacer.isRegExSearchAvailableAndActive()); } /** @@ -1863,6 +1172,14 @@ public void setParentShell(Shell shell) { //--------------- configuration handling -------------- + /** + * Sets up the required managers for search history + */ + private void setupSearchHistory() { + findHistory = new HistoryStore(getDialogSettings(), "findhistory", HISTORY_SIZE); //$NON-NLS-1$ + replaceHistory = new HistoryStore(getDialogSettings(), "replacehistory", HISTORY_SIZE); //$NON-NLS-1$ + } + /** * Returns the dialog settings object used to share state * between several find/replace dialogs. @@ -1901,25 +1218,19 @@ 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$ - - String[] findHistory= s.getArray("findhistory"); //$NON-NLS-1$ - if (findHistory != null) { - List history= getFindHistory(); - history.clear(); - Collections.addAll(history, findHistory); - } + activateInFindReplacerIf(SearchOptions.WRAP, s.get("wrap") == null || s.getBoolean("wrap")); //$NON-NLS-1$ //$NON-NLS-2$ + activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, s.getBoolean("casesensitive")); //$NON-NLS-1$ + activateInFindReplacerIf(SearchOptions.WHOLE_WORD, s.getBoolean("wholeword")); //$NON-NLS-1$ + activateInFindReplacerIf(SearchOptions.INCREMENTAL, s.getBoolean("incremental")); //$NON-NLS-1$ + activateInFindReplacerIf(SearchOptions.REGEX, s.getBoolean("isRegEx")); //$NON-NLS-1$ + } - String[] replaceHistory= s.getArray("replacehistory"); //$NON-NLS-1$ - if (replaceHistory != null) { - List history= getReplaceHistory(); - history.clear(); - Collections.addAll(history, replaceHistory); - } + private void setupFindReplacer() { + activateInFindReplacerIf(SearchOptions.WRAP, fWrapCheckBox.getSelection()); + activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, fCaseCheckBox.getSelection()); + activateInFindReplacerIf(SearchOptions.WHOLE_WORD, fWholeWordCheckBox.getSelection()); + activateInFindReplacerIf(SearchOptions.INCREMENTAL, fIncrementalCheckBox.getSelection()); + activateInFindReplacerIf(SearchOptions.REGEX, fIsRegExCheckBox.getSelection()); } /** @@ -1928,52 +1239,65 @@ private void readConfiguration() { 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.isActive(SearchOptions.WRAP)); //$NON-NLS-1$ + s.put("casesensitive", findReplacer.isActive(SearchOptions.CASE_SENSITIVE)); //$NON-NLS-1$ + s.put("wholeword", findReplacer.isActive(SearchOptions.WHOLE_WORD)); //$NON-NLS-1$ + s.put("incremental", findReplacer.isActive(SearchOptions.INCREMENTAL)); //$NON-NLS-1$ + s.put("isRegEx", findReplacer.isActive(SearchOptions.REGEX)); //$NON-NLS-1$ - List history= getFindHistory(); String findString= getFindString(); - if (!findString.isEmpty()) - history.add(0, findString); - writeHistory(history, s, "findhistory"); //$NON-NLS-1$ + findHistory.add(findString); - history= getReplaceHistory(); String replaceString= getReplaceString(); - if (!replaceString.isEmpty()) - history.add(0, replaceString); - writeHistory(history, s, "replacehistory"); //$NON-NLS-1$ + replaceHistory.add(replaceString); } - /** - * Writes the given history into the given dialog store. - * - * @param history the history - * @param settings the dialog settings - * @param sectionName the section name - * @since 3.2 - */ - private void writeHistory(List history, IDialogSettings settings, String sectionName) { - int itemCount= history.size(); - Set distinctItems= new HashSet<>(itemCount); - for (int i= 0; i < itemCount; i++) { - String item= history.get(i); - if (distinctItems.contains(item)) { - history.remove(i--); - itemCount--; - } else { - distinctItems.add(item); + private void activateInFindReplacerIf(SearchOptions option, boolean shouldActivate) { + if (shouldActivate) { + findReplacer.activate(option); + } else { + findReplacer.deactivate(option); + } + } + + private void evaluateFindReplacerStatus() { + IFindReplaceLogicStatus status = findReplacer.getStatus(); + + String dialogMessage = status.visit(new FindReplaceLogicMessageGenerator()); + + fStatusLabel.setText(dialogMessage); + + if (status instanceof FindReplaceLogicStatus statusMessage) { + switch (statusMessage.getMessageCode()) { + case NO_MATCH: + case READONLY: + fStatusLabel.setForeground(JFaceColors.getErrorText(fStatusLabel.getDisplay())); + tryToBeep(); + break; + case WRAPPED: + tryToBeep(); + break; + case NONE: + default: + break; } } + else { + fStatusLabel.setForeground(null); + } - while (history.size() > HISTORY_SIZE) - history.remove(HISTORY_SIZE); + } - String[] names= new String[history.size()]; - history.toArray(names); - settings.put(sectionName, names); + private void tryToBeep() { + if (okToUse(getShell())) { + getShell().getDisplay().beep(); + } + } + private String getCurrentSelection() { + IFindReplaceTarget target = findReplacer.getTarget(); + if (target == null) + return null; + return target.getSelectionText(); } } 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..eda138187d8 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlay.java @@ -0,0 +1,756 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.texteditor; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Scrollable; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.window.Window; + +import org.eclipse.jface.text.IFindReplaceTarget; + +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicMessageGenerator; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.IFindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.SearchOptions; + +/** + * @since 3.18 + */ +public class FindReplaceOverlay extends Dialog { + + FindReplaceAction parentAction; + FindReplaceLogic findReplacer; + IWorkbenchPart targetPart; + boolean overlayOpen; + boolean replaceBarOpen; + + Composite container; + Button replaceToggle; + + Composite contentGroup; + + Composite searchContainer; + Composite searchBarContainer; + Text searchBar; + ToolBar searchTools; + ToolItem wholeWordSearchButton; + ToolItem caseSensitiveSearchButton; + ToolItem regexSearchButton; + ToolItem searchUpButton; + ToolItem searchDownButton; + ToolItem searchAllButton; + + Composite replaceContainer; + Composite replaceBarContainer; + Text replaceBar; + ToolBar replaceTools; + ToolItem replaceButton; + ToolItem replaceAllButton; + + Link openOldDialog; + + public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget target, + FindReplaceAction parentAction) { + super(parent); + this.parentAction = parentAction; + createFindReplacer(target); + + setShellStyle(SWT.MODELESS); + setBlockOnOpen(false); + targetPart = part; + } + + @Override + protected boolean isResizable() { + return false; + } + + private void createFindReplacer(IFindReplaceTarget target) { + findReplacer = new FindReplaceLogic(); + boolean isTargetEditable = false; + if (target != null) { + isTargetEditable = target.isEditable(); + } + findReplacer.updateTarget(target, isTargetEditable); + findReplacer.activate(SearchOptions.INCREMENTAL); + findReplacer.activate(SearchOptions.GLOBAL); + findReplacer.activate(SearchOptions.WRAP); + findReplacer.activate(SearchOptions.FORWARD); + } + + KeyListener shortcuts = new KeyListener() { + + private void performEnterAction(KeyEvent e) { + // probably not the right way to implement this. + // What do you think, @HeikoKlare? + boolean isShiftPressed = (e.stateMask & SWT.SHIFT) != 0; + boolean isCtrlPressed = (e.stateMask & SWT.CTRL) != 0; + if (okayToUse(replaceBar) && replaceBar.isFocusControl()) { + if (isCtrlPressed) { + findReplacer.performReplaceAll(getFindString(), getReplaceString(), getShell().getDisplay()); + } else { + performSingleReplace(); + } + } else { + if (isCtrlPressed) { + findReplacer.performSelectAll(getFindString(), getShell().getDisplay()); + } else { + boolean oldForwardSearchSetting = findReplacer.isActive(SearchOptions.FORWARD); + activateInFindReplacerIf(SearchOptions.FORWARD, !isShiftPressed); + findReplacer.deactivate(SearchOptions.INCREMENTAL); + findReplacer.performSearch(getFindString()); + activateInFindReplacerIf(SearchOptions.FORWARD, oldForwardSearchSetting); + findReplacer.activate(SearchOptions.INCREMENTAL); + } + } + } + + @Override + public void keyPressed(KeyEvent e) { + if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'F' || e.keyCode == 'f')) { + parentAction.closeModernOverlay(); + } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'R' || e.keyCode == 'r')) { + replaceToggle.setSelection(!replaceToggle.getSelection()); + replaceToggle.notifyListeners(SWT.Selection, null); + } else if ((e.stateMask & SWT.CTRL) != 0 && (e.keyCode == 'W' || e.keyCode == 'w')) { + wholeWordSearchButton.setSelection(!wholeWordSearchButton.getSelection()); + wholeWordSearchButton.notifyListeners(SWT.Selection, null); + } else if (e.keyCode == SWT.CR) { + performEnterAction(e); + } + } + + @Override + public void keyReleased(KeyEvent e) { + // Do nothing + } + + }; + ControlListener shellMovementListener = new ControlListener() { + @Override + public void controlMoved(ControlEvent e) { + positionToPart(); + } + + @Override + public void controlResized(ControlEvent e) { + positionToPart(); + } + }; + PaintListener widgetMovementListener = new PaintListener() { + + @Override + public void paintControl(PaintEvent e) { + positionToPart(); + } + + }; + IPartListener partListener = new IPartListener() { + @Override + public void partActivated(IWorkbenchPart part) { + if (getShell() != null) { + getShell().setVisible(isPartCurrentlyDisplayedInPartSash()); + } + } + + @Override + public void partDeactivated(IWorkbenchPart part) { + // Do nothing + } + + @Override + public void partBroughtToTop(IWorkbenchPart part) { + if (getShell() != null) { + getShell().setVisible(isPartCurrentlyDisplayedInPartSash()); + } + } + + @Override + public void partClosed(IWorkbenchPart part) { + close(); + } + + @Override + public void partOpened(IWorkbenchPart part) { + // Do nothing + } + }; + + public boolean isPartCurrentlyDisplayedInPartSash() { + IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + + // Check if the targetPart is currently displayed on the active page + boolean isPartDisplayed = false; + + if (activePage != null) { + IWorkbenchPart activePart = activePage.getActivePart(); + if (activePart != null && activePart == targetPart) { + isPartDisplayed = true; + } + } + + return isPartDisplayed; + } + + @Override + public void create() { + if (overlayOpen) { + return; + } + super.create(); + } + + @Override + public boolean close() { + if (!overlayOpen) { + return true; + } + overlayOpen = false; + replaceBarOpen = false; + unbindListeners(); + container.dispose(); + return super.close(); + } + + @Override + public int open() { + int returnCode; + positionToPart(); + if (overlayOpen) { + searchBar.forceFocus(); + findReplacer.performIncrementalSearch(getFindString()); + returnCode = Window.CANCEL; + } else { + bindListeners(); + returnCode = super.open(); + } + overlayOpen = true; + return returnCode; + } + + private void unbindListeners() { + if (targetPart != null && targetPart instanceof StatusTextEditor textEditor) { + Control targetWidget = textEditor.getSourceViewer().getTextWidget(); + if (targetWidget != null) { + targetWidget.getShell().removeControlListener(shellMovementListener); + targetWidget.removePaintListener(widgetMovementListener); + targetPart.getSite().getPage().removePartListener(partListener); + } + } + } + + private void bindListeners() { + if (targetPart instanceof StatusTextEditor textEditor) { + Control targetWidget = textEditor.getSourceViewer().getTextWidget(); + + targetWidget.getShell().addControlListener(shellMovementListener); + targetWidget.addPaintListener(widgetMovementListener); + targetPart.getSite().getPage().addPartListener(partListener); + } + } + + @Override + public Control createContents(Composite parent) { + Control ret = createDialog(parent); + initFindStringFromSelection(); + positionToPart(); + return ret; + } + + public Control createDialog(final Composite parent) { + createMainContainer(parent); + + // createLinks(); + createFindContainer(); + createSearchBar(); + createSearchTools(); + + container.layout(); + + applyDialogFont(container); + return container; + } + + @SuppressWarnings("unused") + private void createLinks() { + openOldDialog = new Link(contentGroup, SWT.NONE); + GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(openOldDialog); + openOldDialog.setText("classic Find/Replace"); //$NON-NLS-1$ + openOldDialog.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + parentAction.showClassicDialog(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + + }); + } + + private void createSearchTools() { + searchTools = new ToolBar(searchContainer, SWT.HORIZONTAL); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.CENTER).applyTo(searchTools); + wholeWordSearchButton = new ToolItem(searchTools, SWT.CHECK); + wholeWordSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_WHOLE_WORD)); + wholeWordSearchButton.setToolTipText("Only find in whole words"); //$NON-NLS-1$ + wholeWordSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.WHOLE_WORD, wholeWordSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + caseSensitiveSearchButton = new ToolItem(searchTools, SWT.CHECK); + caseSensitiveSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_CASE_SENSITIVE)); + caseSensitiveSearchButton.setToolTipText("Match case"); //$NON-NLS-1$ + caseSensitiveSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.CASE_SENSITIVE, caseSensitiveSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + regexSearchButton = new ToolItem(searchTools, SWT.CHECK); + regexSearchButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_FIND_REGEX)); + regexSearchButton.setToolTipText("Search for a regular expression"); //$NON-NLS-1$ + regexSearchButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + activateInFindReplacerIf(SearchOptions.REGEX, regexSearchButton.getSelection()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + + regexSearchButton = new ToolItem(searchTools, SWT.SEPARATOR); + + searchAllButton = new ToolItem(searchTools, SWT.PUSH); + searchAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_SEARCH_ALL)); + searchAllButton.setToolTipText("Search all (Ctrl + Enter)"); //$NON-NLS-1$ + searchAllButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.performSelectAll(getFindString(), getShell().getDisplay()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + + }); + searchUpButton = new ToolItem(searchTools, SWT.PUSH); + searchUpButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.ELCL_FIND_PREV)); + searchUpButton.setToolTipText("Search backward (Shift + Enter)"); //$NON-NLS-1$ + searchUpButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.deactivate(SearchOptions.FORWARD); + findReplacer.performSearch(getFindString()); + findReplacer.activate(SearchOptions.FORWARD); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + searchDownButton = new ToolItem(searchTools, SWT.PUSH); + searchDownButton.setSelection(true); // by default, search down + searchDownButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.ELCL_FIND_NEXT)); + searchDownButton.setToolTipText("Search forward (Enter)"); //$NON-NLS-1$ + searchDownButton.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + // TODO extract into transaction + findReplacer.activate(SearchOptions.FORWARD); + findReplacer.activate(SearchOptions.INCREMENTAL); + findReplacer.deactivate(SearchOptions.FORWARD); + findReplacer.performSearch(getFindString()); + findReplacer.deactivate(SearchOptions.INCREMENTAL); + findReplacer.activate(SearchOptions.FORWARD); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + + } + + private void createReplaceTools() { + replaceTools = new ToolBar(replaceContainer, SWT.HORIZONTAL); + GridDataFactory.fillDefaults().grab(false, true).align(GridData.CENTER, GridData.CENTER).applyTo(replaceTools); + replaceButton = new ToolItem(replaceTools, SWT.PUSH); + replaceButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE)); + replaceButton.setToolTipText("Replace (Enter)"); //$NON-NLS-1$ + replaceButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + performSingleReplace(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + replaceAllButton = new ToolItem(replaceTools, SWT.PUSH); + replaceAllButton.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_REPLACE_ALL)); + replaceAllButton.setToolTipText("Replace All (Ctrl + Enter)"); //$NON-NLS-1$ + replaceAllButton.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + findReplacer.performReplaceAll(getFindString(), getReplaceString(), getShell().getDisplay()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do Nothing + } + }); + } + + private void createSearchBar() { + searchBar = new Text(searchBarContainer, SWT.SINGLE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBar); + searchBar.forceFocus(); + searchBar.selectAll(); + searchBar.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + findReplacer.performIncrementalSearch(getFindString()); + } + }); + + searchBar.addKeyListener(shortcuts); + + searchBar.setMessage("Find"); //$NON-NLS-1$ + } + + private void createReplaceBar() { + replaceBar = new Text(replaceBarContainer, SWT.SINGLE); + GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(replaceBar); + replaceBar.setMessage("Replace"); //$NON-NLS-1$ + replaceBar.addKeyListener(shortcuts); + } + + private void createFindContainer() { + searchContainer = new Composite(contentGroup, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL) + .applyTo(searchContainer); + GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).applyTo(searchContainer); + searchBarContainer = new Composite(searchContainer, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(searchBarContainer); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).applyTo(searchBarContainer); + } + + private void createReplaceContainer() { + replaceContainer = new Composite(contentGroup, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(replaceContainer); + GridLayoutFactory.fillDefaults().margins(0, 1).numColumns(2).equalWidth(false) + .applyTo(replaceContainer); + replaceBarContainer = new Composite(replaceContainer, SWT.BORDER); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL) + .applyTo(replaceBarContainer); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false) + .applyTo(replaceBarContainer); + } + + private void createMainContainer(final Composite parent) { + container = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).margins(2, 2).spacing(2, 0).applyTo(container); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(container); + + if (findReplacer.getTarget().isEditable()) { + createReplaceToggle(); + } + + contentGroup = new Composite(container, SWT.NULL); + GridLayoutFactory.fillDefaults().numColumns(1).equalWidth(false).spacing(2, 2).applyTo(contentGroup); + GridDataFactory.fillDefaults().grab(true, true).align(GridData.FILL, GridData.FILL).applyTo(contentGroup); + } + + private void createReplaceToggle() { + replaceToggle = new Button(container, SWT.PUSH); // https://stackoverflow.com/questions/33161797/how-to-remove-border-of-swt-button-so-that-it-seems-like-a-label + GridDataFactory.fillDefaults().grab(false, true).align(GridData.BEGINNING, GridData.FILL) + .applyTo(replaceToggle); + replaceToggle.setToolTipText("Toggle input for replace (Ctrl+R)"); //$NON-NLS-1$ + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_OPEN_REPLACE)); + replaceToggle.addSelectionListener(new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + if (!replaceBarOpen) { + createReplaceDialog(); + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_CLOSE_REPLACE)); + } else { + hideReplace(); + replaceToggle.setImage(FindReplaceOverlayImages.get(FindReplaceOverlayImages.OBJ_OPEN_REPLACE)); + } + replaceToggle.setSelection(false); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + // Do nothing + } + }); + } + + public void hideReplace() { + if (!replaceBarOpen) { + return; + } + replaceBarOpen = false; + replaceContainer.dispose(); + replaceTools.dispose(); + replaceBar.dispose(); + positionToPart(); + searchBar.forceFocus(); + } + + public void createReplaceDialog() { + if (replaceBarOpen) { + return; + } + replaceBarOpen = true; + createReplaceContainer(); + createReplaceBar(); + createReplaceTools(); + positionToPart(); + replaceBar.forceFocus(); + } + + private void enableSearchTools(boolean enable) { + ((GridData) searchTools.getLayoutData()).exclude = !enable; + searchTools.setVisible(enable); + + if (enable) { + ((GridLayout) searchTools.getParent().getLayout()).numColumns = 2; + } else { + ((GridLayout) searchTools.getParent().getLayout()).numColumns = 1; + } + } + + private void enableReplaceToggle(boolean enable) { + if (!okayToUse(replaceToggle)) { + return; + } + ((GridData) replaceToggle.getLayoutData()).exclude = !enable; + replaceToggle.setVisible(enable); + } + + private void enableReplaceTools(boolean enable) { + if (!okayToUse(replaceTools)) { + return; + } + ((GridData) replaceTools.getLayoutData()).exclude = !enable; + replaceTools.setVisible(enable); + + if (enable) { + ((GridLayout) replaceTools.getParent().getLayout()).numColumns = 2; + } else { + ((GridLayout) replaceTools.getParent().getLayout()).numColumns = 1; + } + } + + private int getIdealDialogWidth(Rectangle targetBounds) { + enableSearchTools(true); + enableReplaceTools(true); + enableReplaceToggle(true); + Point toolBarWidth = searchTools.getSize(); + GC gc = new GC(searchBar); + gc.setFont(searchBar.getFont()); + Point idealWidth = gc.stringExtent("THIS TEXT HAS A REASONABLE LENGTH FOR SEARCHING"); //$NON-NLS-1$ + Point idealCompromiseWidth = gc.stringExtent("THIS TEXT HAS A REASONABLE"); //$NON-NLS-1$ + Point worstCompromiseWidth = gc.stringExtent("THIS TEXT "); //$NON-NLS-1$ + + int newWidth = idealWidth.x + toolBarWidth.x; + if (newWidth > targetBounds.width * 0.7) { + newWidth = (int) (targetBounds.width * 0.7); + } + if (newWidth < idealCompromiseWidth.x + toolBarWidth.x) { + enableSearchTools(false); + enableReplaceTools(false); + } + if (newWidth < worstCompromiseWidth.x + toolBarWidth.x) { + newWidth = (int) (targetBounds.width * 0.95); + enableReplaceToggle(false); + } + return newWidth; + } + + private Point getNewPosition(Widget targetTextWidget, Point targetOrigin, Rectangle targetBounds) { + Point scrollBarSize = ((Scrollable) targetTextWidget).getVerticalBar().getSize(); + + int newX = targetOrigin.x + targetBounds.width - container.getBounds().width - scrollBarSize.x + - ((StyledText) targetTextWidget).getRightMargin(); + int newY = targetOrigin.y; + return new Point(newX, newY); + } + + /** + * When making the text-bar 100% small and then regrowing it, we want the text + * to start at the first character again. + */ + private void repositionTextSelection() { + if (okayToUse(searchBar) && !searchBar.isFocusControl()) { + searchBar.setSelection(0, 0); + } + if (okayToUse(replaceBar) && !replaceBar.isFocusControl()) { + replaceBar.setSelection(0, 0); + } + } + + public void positionToPart() { + getShell().layout(true); + container.layout(true); + if (targetPart instanceof StatusTextEditor textEditor) { + Control targetWidget = textEditor.getSourceViewer().getTextWidget(); + if (targetWidget == null || targetWidget.isDisposed()) { + this.close(); + return; + } + StyledText targetTextWidget = textEditor.getSourceViewer().getTextWidget(); + Point targetOrigin = targetTextWidget.toDisplay(0, 0); + Rectangle targetBounds = targetTextWidget.getBounds(); + + int newWidth = getIdealDialogWidth(targetBounds); + int newHeight = container.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + getShell().setSize(new Point(newWidth, newHeight)); + + Point newPosition = getNewPosition(targetTextWidget, targetOrigin, targetBounds); + getShell().setLocation(newPosition); + + repositionTextSelection(); + } + container.layout(true); + getShell().layout(true); + } + + private String getFindString() { + return searchBar.getText(); + } + + private String getReplaceString() { + if (replaceBar.isDisposed()) + return ""; //$NON-NLS-1$ + return replaceBar.getText(); + + } + + private void performSingleReplace() { + findReplacer.performSelectAndReplace(getFindString(), getReplaceString()); + findReplacer.performIncrementalSearch(getFindString()); + evaluateFindReplacerStatus(); + } + + private void initFindStringFromSelection() { + searchBar.setText(findReplacer.getTarget().getSelectionText()); + } + + private void evaluateFindReplacerStatus() { + IFindReplaceLogicStatus status = findReplacer.getStatus(); + + String dialogMessage = status.visit(new FindReplaceLogicMessageGenerator()); + + if (status instanceof FindReplaceLogicStatus statusMessage) { + switch (statusMessage.getMessageCode()) { + case NO_MATCH: + case READONLY: + case WRAPPED: + tryToBeep(); + break; + case NONE: + default: + break; + } + } + + } + + private void tryToBeep() { + Shell dialogShell = getShell(); + if (dialogShell != null && !dialogShell.isDisposed()) { + getShell().getDisplay().beep(); + } + } + + private void activateInFindReplacerIf(SearchOptions option, boolean shouldActivate) { + if (shouldActivate) { + findReplacer.activate(option); + } else { + findReplacer.deactivate(option); + } + } + + private boolean okayToUse(Widget widget) { + return widget != null && !widget.isDisposed(); + } +} \ No newline at end of file diff --git a/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java new file mode 100644 index 00000000000..5430426e1f8 --- /dev/null +++ b/bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/FindReplaceOverlayImages.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ +package org.eclipse.ui.texteditor; + +import java.net.URL; + +import org.osgi.framework.Bundle; + +import org.eclipse.swt.graphics.Image; + +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Platform; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; + +import org.eclipse.ui.internal.texteditor.TextEditorPlugin; + +/** + * @HeikoKlare Modeled after TemplatePageImages.java - I'm not sure if this + * approach is good or bad. I copy-pasted methods, increasing + * code-duplication. We should consider implementing a unified + * method for inserting icons (.. or does it already exist?) + */ +class FindReplaceOverlayImages { + + static final String PREFIX_ELCL = TextEditorPlugin.PLUGIN_ID + ".elcl."; //$NON-NLS-1$ + + static final String PREFIX_DLCL = TextEditorPlugin.PLUGIN_ID + ".dlcl."; //$NON-NLS-1$ + + static final String PREFIX_OBJ = TextEditorPlugin.PLUGIN_ID + ".obj."; //$NON-NLS-1$ + + static final String ELCL_FIND_NEXT = PREFIX_ELCL + "select_next.png"; //$NON-NLS-1$ + + static final String ELCL_FIND_PREV = PREFIX_ELCL + "select_prev.png"; //$NON-NLS-1$ + + static final String OBJ_FIND_REGEX = PREFIX_OBJ + "regex_gear.gif"; //$NON-NLS-1$ + + static final String OBJ_REPLACE = PREFIX_OBJ + "replace.png"; //$NON-NLS-1$ + + static final String OBJ_REPLACE_ALL = PREFIX_OBJ + "replace_all.png"; //$NON-NLS-1$ + + static final String OBJ_WHOLE_WORD = PREFIX_OBJ + "whole_word.png"; //$NON-NLS-1$ + + static final String OBJ_CASE_SENSITIVE = PREFIX_OBJ + "case_sensitive.png"; //$NON-NLS-1$ + + static final String OBJ_OPEN_REPLACE = PREFIX_OBJ + "open_replace.png"; //$NON-NLS-1$ + + static final String OBJ_CLOSE_REPLACE = PREFIX_OBJ + "close_replace.png"; //$NON-NLS-1$ + + static final String OBJ_SEARCH_ALL = PREFIX_OBJ + "search_all.png"; //$NON-NLS-1$ + + /** + * The image registry containing {@link Image images}. + */ + private static ImageRegistry fgImageRegistry; + + private static String ICONS_PATH = "$nl$/icons/full/"; //$NON-NLS-1$ + + // Use IPath and toOSString to build the names to ensure they have the + // slashes correct + private final static String ELCL = ICONS_PATH + "elcl16/"; //$NON-NLS-1$ + + private final static String DLCL = ICONS_PATH + "dlcl16/"; //$NON-NLS-1$ + + private final static String OBJ = ICONS_PATH + "obj16/"; //$NON-NLS-1$ + + /** + * Declare all images + */ + private static void declareImages() { + declareRegistryImage(ELCL_FIND_NEXT, ELCL + "select_next.png"); //$NON-NLS-1$ + declareRegistryImage(ELCL_FIND_PREV, ELCL + "select_prev.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_FIND_REGEX, OBJ + "regex_gear.gif"); //$NON-NLS-1$ + declareRegistryImage(OBJ_REPLACE_ALL, OBJ + "replace_all.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_REPLACE, OBJ + "replace.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_WHOLE_WORD, OBJ + "whole_word.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_CASE_SENSITIVE, OBJ + "case_sensitive.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_OPEN_REPLACE, OBJ + "open_replace.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_CLOSE_REPLACE, OBJ + "close_replace.png"); //$NON-NLS-1$ + declareRegistryImage(OBJ_SEARCH_ALL, OBJ + "search_all.png"); //$NON-NLS-1$ + } + + /** + * Declare an Image in the registry table. + * + * @param key the key to use when registering the image + * @param path the path where the image can be found. This path is relative to + * where this plugin class is found (i.e. typically the packages + * directory) + */ + private final static void declareRegistryImage(String key, String path) { + ImageDescriptor desc = ImageDescriptor.getMissingImageDescriptor(); + Bundle bundle = Platform.getBundle(TextEditorPlugin.PLUGIN_ID); + URL url = null; + if (bundle != null) { + url = FileLocator.find(bundle, IPath.fromOSString(path), null); + desc = ImageDescriptor.createFromURL(url); + } + fgImageRegistry.put(key, desc); + } + + /** + * Returns the ImageRegistry. + * + * @return image registry + */ + public static ImageRegistry getImageRegistry() { + if (fgImageRegistry == null) { + initializeImageRegistry(); + } + return fgImageRegistry; + } + + /** + * Initialize the image registry by declaring all of the required graphics. This + * involves creating JFace image descriptors describing how to create/find the + * image should it be needed. The image is not actually allocated until + * requested. + * + * Prefix conventions Wizard Banners WIZBAN_ Preference Banners PREF_BAN_ + * Property Page Banners PROPBAN_ Color toolbar CTOOL_ Enable toolbar ETOOL_ + * Disable toolbar DTOOL_ Local enabled toolbar ELCL_ Local Disable toolbar + * DLCL_ Object large OBJL_ Object small OBJS_ View VIEW_ Product images PROD_ + * Misc images MISC_ + * + * Where are the images? The images (typically pngs) are found in the same + * location as this plugin class. This may mean the same package directory as + * the package holding this class. The images are declared using this.getClass() + * to ensure they are looked up via this plugin class. + * + * @return the image registry + * @see org.eclipse.jface.resource.ImageRegistry + */ + public static ImageRegistry initializeImageRegistry() { + fgImageRegistry = TextEditorPlugin.getDefault().getImageRegistry(); + declareImages(); + return fgImageRegistry; + } + + /** + * Returns the image managed under the given key in this registry. + * + * @param key the image's key + * @return the image managed under the given key + */ + public static Image get(String key) { + return getImageRegistry().get(key); + } + + /** + * Returns the image descriptor for the given key in this registry. + * + * @param key the image's key + * @return the image descriptor for the given key + */ + public static ImageDescriptor getDescriptor(String key) { + return getImageRegistry().getDescriptor(key); + } +} diff --git a/releng/org.eclipse.text.releng/.project b/releng/org.eclipse.text.releng/.project new file mode 100644 index 00000000000..c79a93b4658 --- /dev/null +++ b/releng/org.eclipse.text.releng/.project @@ -0,0 +1,11 @@ + + + org.eclipse.text.releng + + + + + + + + diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF index f54c838a382..2144346c73c 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/META-INF/MANIFEST.MF @@ -9,7 +9,8 @@ Export-Package: org.eclipse.ui.workbench.texteditor.tests, org.eclipse.ui.workbench.texteditor.tests.minimap, org.eclipse.ui.workbench.texteditor.tests.revisions, - org.eclipse.ui.workbench.texteditor.tests.rulers + org.eclipse.ui.workbench.texteditor.tests.rulers, + org.eclipse.ui.findandreplace Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", org.eclipse.jface.text;bundle-version="[3.5.0,4.0.0)", @@ -21,3 +22,5 @@ Require-Bundle: Bundle-RequiredExecutionEnvironment: JavaSE-17 Eclipse-BundleShape: dir Automatic-Module-Name: org.eclipse.ui.workbench.texteditor.tests +Import-Package: org.mockito, + org.mockito.stubbing;version="5.5.0" diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java new file mode 100644 index 00000000000..211d59d945e --- /dev/null +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/findandreplace/FindReplaceLogicTest.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright (c) 2023 Vector Informatik GmbH and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vector Informatik GmbH - initial API and implementation + *******************************************************************************/ + +package org.eclipse.ui.findandreplace; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.text.TextViewer; + +import org.eclipse.ui.internal.findandreplace.FindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicFindAllStatus; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicMessage; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicReplaceAllStatus; +import org.eclipse.ui.internal.findandreplace.FindReplaceLogicStatus; +import org.eclipse.ui.internal.findandreplace.IFindReplaceLogic; +import org.eclipse.ui.internal.findandreplace.SearchOptions; + + +public class FindReplaceLogicTest { + Shell parentShell; + + private IFindReplaceLogic setupFindReplaceLogicObject(TextViewer target) { + IFindReplaceLogic findReplaceLogic= new FindReplaceLogic(); + if (target != null) { + findReplaceLogic.updateTarget(target.getFindReplaceTarget(), true); + } + + return findReplaceLogic; + } + + private TextViewer setupTextViewer(String contentText) { + TextViewer textViewer= new TextViewer(parentShell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + textViewer.setDocument(new Document(contentText)); + textViewer.getControl().setFocus(); + return textViewer; + } + + @After + public void disposeShell() { + if (parentShell != null) { + parentShell.dispose(); + } + } + + @Before + public void setupShell() { + parentShell= new Shell(); + } + + @Test + @Ignore("https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203") + public void testPerformReplaceAllBackwards() { + TextViewer textViewer= setupTextViewer(""); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + + performReplaceAllBaseTestcases(findReplaceLogic, textViewer); + } + + @Test + public void testPerformReplaceAllForwards() { + TextViewer textViewer= setupTextViewer(""); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + performReplaceAllBaseTestcases(findReplaceLogic, textViewer); + } + + /** + * Expects the TextViewer to contain a document with "aaaa" + * + * @param findReplaceLogic logic-object that will be tested + * @param textViewer textviewer-object that contains the contents on which findReplaceLogic + * operates + */ + @SuppressWarnings("boxing") + private void performReplaceAllBaseTestcases(IFindReplaceLogic findReplaceLogic, TextViewer textViewer) { + Display display= parentShell.getDisplay(); + textViewer.setDocument(new Document("aaaa")); + + findReplaceLogic.performReplaceAll("a", "b", display); + assertThat(textViewer.getDocument().get(), equalTo("bbbb")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 4); + + findReplaceLogic.performReplaceAll("b", "aa", display); + assertThat(textViewer.getDocument().get(), equalTo("aaaaaaaa")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 4); + + findReplaceLogic.performReplaceAll("b", "c", display); + assertThat(textViewer.getDocument().get(), equalTo("aaaaaaaa")); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + + findReplaceLogic.performReplaceAll("aaaaaaaa", "d", display); // https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203 + assertThat(textViewer.getDocument().get(), equalTo("d")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("d", null, display); + assertThat(textViewer.getDocument().get(), equalTo("")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + textViewer.getDocument().set("f"); + findReplaceLogic.performReplaceAll("f", "", display); + assertThat(textViewer.getDocument().get(), equalTo("")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + + IFindReplaceTarget mockFindReplaceTarget= Mockito.mock(IFindReplaceTarget.class); + Mockito.when(mockFindReplaceTarget.isEditable()).thenReturn(false); + + findReplaceLogic.updateTarget(mockFindReplaceTarget, false); + findReplaceLogic.performReplaceAll("a", "b", display); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + @Test + public void testPerformReplaceAllForwardRegEx() { + TextViewer textViewer= setupTextViewer("hello@eclipse.com looks.almost@like_an_email"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.REGEX); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performReplaceAll(".+\\@.+\\.com", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo(" looks.almost@like_an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("( looks.)|(like_)", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 2); + + findReplaceLogic.performReplaceAll("[", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsMessageWithString(findReplaceLogic, "Unclosed character class near index 0\r\n" + + "[\r\n" + + "^"); + + } + + @Test + public void testPerformReplaceAllForward() { + TextViewer textViewer= setupTextViewer("hello@eclipse.com looks.almost@like_an_email"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.REGEX); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performReplaceAll(".+\\@.+\\.com", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo(" looks.almost@like_an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 1); + + findReplaceLogic.performReplaceAll("( looks.)|(like_)", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsReplaceAllWithCount(findReplaceLogic, 2); + + findReplaceLogic.performReplaceAll("[", "", parentShell.getDisplay()); + assertThat(textViewer.getDocument().get(), equalTo("almost@an_email")); + expectStatusIsMessageWithString(findReplaceLogic, "Unclosed character class near index 0\r\n" + + "[\r\n" + + "^"); + } + + @Test + public void testPerformSelectAndReplace() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performSearch(""); // select first, then replace. We don't need to perform a second search + findReplaceLogic.performSelectAndReplace("", " "); + assertThat(textViewer.getDocument().get(), equalTo("Hello World!")); + expectStatusEmpty(findReplaceLogic); + + findReplaceLogic.performSelectAndReplace("", " "); // perform the search yourself and replace that automatically + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusEmpty(findReplaceLogic); + } + + @Test + public void testPerformSelectAndReplaceBackward() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.deactivate(SearchOptions.FORWARD); + findReplaceLogic.activate(SearchOptions.WRAP); // this only works if the search was wrapped + + findReplaceLogic.performSearch(""); // select first, then replace. We don't need to perform a second search + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.WRAPPED); + findReplaceLogic.performSelectAndReplace("", " "); + assertThat(textViewer.getDocument().get(), equalTo("HelloWorld !")); + + findReplaceLogic.performSelectAndReplace("", " "); // perform the search yourself and replace that automatically + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusEmpty(findReplaceLogic); + } + + + @SuppressWarnings("boxing") + @Test + public void testPerformReplaceAndFind() { + TextViewer textViewer= setupTextViewer("HelloWorld!"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + boolean status = findReplaceLogic.performReplaceAndFind("", " "); + assertThat(status, is(true)); + assertThat(textViewer.getDocument().get(), equalTo("Hello World!")); + assertThat(findReplaceLogic.getTarget().getSelectionText(), equalTo("")); + expectStatusEmpty(findReplaceLogic); + + status= findReplaceLogic.performReplaceAndFind("", " "); + assertThat(status, is(true)); + assertThat(textViewer.getDocument().get(), equalTo("Hello World !")); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + + status= findReplaceLogic.performReplaceAndFind("", " "); + assertEquals("Status wasn't correctly returned", false, status); + assertEquals("Text shouldn't have been changed", "Hello World !", textViewer.getDocument().get()); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + @Test + public void testPerformSelectAllForward() { + TextViewer textViewer= setupTextViewer("AbAbAbAb"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + + findReplaceLogic.performSelectAll("b", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 4); + // I don't have access to getAllSelectionPoints or similar (not yet implemented), so I cannot really test for correct behavior + // related to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1047 + + findReplaceLogic.performSelectAll("AbAbAbAb", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 1); + } + + + @Test + @Ignore("https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203") + public void testPerformSelectAllBackward() { + TextViewer textViewer= setupTextViewer("AbAbAbAb"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.deactivate(SearchOptions.FORWARD); + + findReplaceLogic.performSelectAll("b", parentShell.getDisplay()); // https://github.com/eclipse-platform/eclipse.platform.ui/issues/1203 maybe related? + expectStatusIsFindAllWithCount(findReplaceLogic, 4); + // I don't have access to getAllSelectionPoints or similar (not yet implemented), so I cannot really test for correct behavior + // related to https://github.com/eclipse-platform/eclipse.platform.ui/issues/1047 + + findReplaceLogic.performSelectAll("AbAbAbAb", parentShell.getDisplay()); + expectStatusIsFindAllWithCount(findReplaceLogic, 1); + } + + @Test + public void testSelectWholeWords() { + TextViewer textViewer= setupTextViewer("Hello World of get and getters, set and setters"); + IFindReplaceLogic findReplaceLogic= setupFindReplaceLogicObject(textViewer); + findReplaceLogic.activate(SearchOptions.FORWARD); + findReplaceLogic.activate(SearchOptions.WHOLE_WORD); + findReplaceLogic.deactivate(SearchOptions.WRAP); + + findReplaceLogic.performSearch("get"); + findReplaceLogic.performSearch("get"); + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NO_MATCH); + } + + private void expectStatusEmpty(IFindReplaceLogic findReplaceLogic) { + expectStatusIsCode(findReplaceLogic, FindReplaceLogicStatus.MessageCode.NONE); + } + + private void expectStatusIsCode(IFindReplaceLogic findReplaceLogic, FindReplaceLogicStatus.MessageCode code) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicStatus.class)); + assertThat(((FindReplaceLogicStatus) findReplaceLogic.getStatus()).getMessageCode(), equalTo(code)); + } + + @SuppressWarnings("boxing") + private void expectStatusIsReplaceAllWithCount(IFindReplaceLogic findReplaceLogic, int count) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicReplaceAllStatus.class)); + assertThat(((FindReplaceLogicReplaceAllStatus) findReplaceLogic.getStatus()).getReplaceCount(), equalTo(count)); + } + + @SuppressWarnings("boxing") + private void expectStatusIsFindAllWithCount(IFindReplaceLogic findReplaceLogic, int count) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicFindAllStatus.class)); + assertThat(((FindReplaceLogicFindAllStatus) findReplaceLogic.getStatus()).getSelectCount(), equalTo(count)); + } + + private void expectStatusIsMessageWithString(IFindReplaceLogic findReplaceLogic, String message) { + assertThat(findReplaceLogic.getStatus(), instanceOf(FindReplaceLogicMessage.class)); + assertThat(((FindReplaceLogicMessage) findReplaceLogic.getStatus()).getMessage(), equalTo(message)); + } + +} diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java deleted file mode 100644 index 0cef0d6329c..00000000000 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/FindReplaceDialogTest.java +++ /dev/null @@ -1,319 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2000, 2016 IBM Corporation and others. - * - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License 2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.ui.workbench.texteditor.tests; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.ResourceBundle; - -import org.junit.After; -import org.junit.FixMethodOrder; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; -import org.junit.runners.MethodSorters; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Shell; - -import org.eclipse.text.tests.Accessor; - -import org.eclipse.jface.util.Util; - -import org.eclipse.jface.text.Document; -import org.eclipse.jface.text.IFindReplaceTarget; -import org.eclipse.jface.text.TextViewer; - -import org.eclipse.ui.PlatformUI; - -/** - * Tests the FindReplaceDialog. - * - * @since 3.1 - */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class FindReplaceDialogTest { - - @Rule - public TestName testName = new TestName(); - - private Accessor fFindReplaceDialog; - private TextViewer fTextViewer; - - private void runEventQueue() { - Display display= PlatformUI.getWorkbench().getDisplay(); - for (int i= 0; i < 10; i++) { // workaround for https://bugs.eclipse.org/323272 - while (display.readAndDispatch()) { - // do nothing - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // do nothing - } - } - } - - private void openFindReplaceDialog() { - Shell shell= PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); - fFindReplaceDialog= new Accessor("org.eclipse.ui.texteditor.FindReplaceDialog", getClass().getClassLoader(), new Object[] { shell }); - fFindReplaceDialog.invoke("create", null); - } - - private void openTextViewerAndFindReplaceDialog() { - fTextViewer= new TextViewer(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - fTextViewer.setDocument(new Document("line\nline\nline")); - fTextViewer.getControl().setFocus(); - - Accessor fFindReplaceAction; - fFindReplaceAction= new Accessor("org.eclipse.ui.texteditor.FindReplaceAction", getClass().getClassLoader(), new Class[] {ResourceBundle.class, String.class, Shell.class, IFindReplaceTarget.class}, new Object[] {ResourceBundle.getBundle("org.eclipse.ui.texteditor.ConstructedEditorMessages"), "Editor.FindReplace.", fTextViewer.getControl().getShell(), fTextViewer.getFindReplaceTarget()}); - fFindReplaceAction.invoke("run", null); - - Object fFindReplaceDialogStub= fFindReplaceAction.get("fgFindReplaceDialogStub"); - if (fFindReplaceDialogStub == null) - fFindReplaceDialogStub= fFindReplaceAction.get("fgFindReplaceDialogStubShell"); - Accessor fFindReplaceDialogStubAccessor= new Accessor(fFindReplaceDialogStub, "org.eclipse.ui.texteditor.FindReplaceAction$FindReplaceDialogStub", getClass().getClassLoader()); - - fFindReplaceDialog= new Accessor(fFindReplaceDialogStubAccessor.invoke("getDialog", null), "org.eclipse.ui.texteditor.FindReplaceDialog", getClass().getClassLoader()); - } - - @After - public void tearDown() throws Exception { - if (fFindReplaceDialog != null) { - fFindReplaceDialog.invoke("close", null); - fFindReplaceDialog= null; - } - - if (fTextViewer != null) { - fTextViewer.getControl().dispose(); - fTextViewer= null; - } - } - - @Test - public void test01InitialButtonState() { - openFindReplaceDialog(); - - Boolean value; - value= (Boolean)fFindReplaceDialog.invoke("isWholeWordSearch", null); - assertFalse(value.booleanValue()); - value= (Boolean)fFindReplaceDialog.invoke("isWholeWordSetting", null); - assertFalse(value.booleanValue()); - value= (Boolean)fFindReplaceDialog.invoke("isWrapSearch", null); - assertTrue(value.booleanValue()); - value= (Boolean)fFindReplaceDialog.invoke("isRegExSearch", null); - assertFalse(value.booleanValue()); - value= (Boolean)fFindReplaceDialog.invoke("isRegExSearchAvailableAndChecked", null); - assertFalse(value.booleanValue()); - Button checkbox= (Button)fFindReplaceDialog.get("fIsRegExCheckBox"); - assertTrue(checkbox.isEnabled()); - checkbox= (Button)fFindReplaceDialog.get("fWholeWordCheckBox"); - assertFalse(checkbox.isEnabled()); // there's no word in the Find field - } - - @Test - public void testDisableWholeWordIfRegEx() { - openFindReplaceDialog(); - - Combo findField= (Combo)fFindReplaceDialog.get("fFindField"); - findField.setText("word"); - - Button isRegExCheckBox= (Button)fFindReplaceDialog.get("fIsRegExCheckBox"); - Button wholeWordCheckbox= (Button)fFindReplaceDialog.get("fWholeWordCheckBox"); - - assertTrue(isRegExCheckBox.isEnabled()); - assertTrue(wholeWordCheckbox.isEnabled()); - - fFindReplaceDialog.set("fIsTargetSupportingRegEx", true); - isRegExCheckBox.setSelection(true); - wholeWordCheckbox.setSelection(true); - fFindReplaceDialog.invoke("updateButtonState", null); - - assertTrue(isRegExCheckBox.isEnabled()); - assertFalse(wholeWordCheckbox.isEnabled()); - assertTrue(wholeWordCheckbox.getSelection()); - } - - @Test - public void testDisableWholeWordIfNotWord() { - openFindReplaceDialog(); - - Combo findField= (Combo)fFindReplaceDialog.get("fFindField"); - Button isRegExCheckBox= (Button)fFindReplaceDialog.get("fIsRegExCheckBox"); - Button wholeWordCheckbox= (Button)fFindReplaceDialog.get("fWholeWordCheckBox"); - - fFindReplaceDialog.set("fIsTargetSupportingRegEx", false); - isRegExCheckBox.setSelection(false); - wholeWordCheckbox.setSelection(true); - fFindReplaceDialog.invoke("updateButtonState", null); - - findField.setText("word"); - assertTrue(isRegExCheckBox.isEnabled()); - assertTrue(wholeWordCheckbox.isEnabled()); - assertTrue(wholeWordCheckbox.getSelection()); - - findField.setText("no word"); - assertTrue(isRegExCheckBox.isEnabled()); - assertFalse(wholeWordCheckbox.isEnabled()); - assertTrue(wholeWordCheckbox.getSelection()); - } - - @Test - public void testFocusNotChangedWhenEnterPressed() { - openTextViewerAndFindReplaceDialog(); - - Combo findField= (Combo)fFindReplaceDialog.get("fFindField"); - findField.setFocus(); - findField.setText("line"); - final Event event= new Event(); - - event.type= SWT.Traverse; - event.detail= SWT.TRAVERSE_RETURN; - event.character= SWT.CR; - event.doit= true; - findField.traverse(SWT.TRAVERSE_RETURN, event); - runEventQueue(); - - Shell shell= ((Shell)fFindReplaceDialog.get("fActiveShell")); - if (shell == null && Util.isGtk()) { - if (ScreenshotTest.isRunByGerritHudsonJob()) { - takeScreenshot(); - return; - } else - fail("this test does not work on GTK unless the runtime workbench has focus. Screenshot: " + takeScreenshot()); - } - - if (Util.isMac()) - /* On the Mac, checkboxes only take focus if "Full Keyboard Access" is enabled in the System Preferences. - * Let's not assume that someone pressed Ctrl+F7 on every test machine... */ - return; - - assertTrue(findField.isFocusControl()); - - Button wrapSearchBox= (Button)fFindReplaceDialog.get("fWrapCheckBox"); - wrapSearchBox.setFocus(); - event.doit= true; - findField.traverse(SWT.TRAVERSE_RETURN, event); - runEventQueue(); - assertTrue(wrapSearchBox.isFocusControl()); - - Button allScopeBox= (Button)fFindReplaceDialog.get("fGlobalRadioButton"); - allScopeBox.setFocus(); - event.doit= true; - findField.traverse(SWT.TRAVERSE_RETURN, event); - runEventQueue(); - assertTrue(allScopeBox.isFocusControl()); - } - - private String takeScreenshot() { - return ScreenshotTest.takeScreenshot(FindReplaceDialogTest.class, testName.getMethodName(), System.out); - } - - @Test - public void testFocusNotChangedWhenButtonMnemonicPressed() { - if (Util.isMac()) - return; // Mac doesn't support mnemonics. - - openTextViewerAndFindReplaceDialog(); - - Combo findField= (Combo)fFindReplaceDialog.get("fFindField"); - findField.setText("line"); - final Event event= new Event(); - - runEventQueue(); - Shell shell= ((Shell)fFindReplaceDialog.get("fActiveShell")); - if (shell == null && Util.isGtk()) - if (ScreenshotTest.isRunByGerritHudsonJob()) { - takeScreenshot(); - return; - } else - fail("this test does not work on GTK unless the runtime workbench has focus. Screenshot: " + takeScreenshot()); - - Button wrapSearchBox= (Button)fFindReplaceDialog.get("fWrapCheckBox"); - wrapSearchBox.setFocus(); - event.detail= SWT.TRAVERSE_MNEMONIC; - event.character= 'n'; - event.doit= false; - wrapSearchBox.traverse(SWT.TRAVERSE_MNEMONIC, event); - runEventQueue(); - assertTrue(wrapSearchBox.isFocusControl()); - - Button allScopeBox= (Button)fFindReplaceDialog.get("fGlobalRadioButton"); - allScopeBox.setFocus(); - event.detail= SWT.TRAVERSE_MNEMONIC; - event.doit= false; - allScopeBox.traverse(SWT.TRAVERSE_MNEMONIC, event); - runEventQueue(); - assertTrue(allScopeBox.isFocusControl()); - - event.detail= SWT.TRAVERSE_MNEMONIC; - event.character= 'r'; - event.doit= false; - allScopeBox.traverse(SWT.TRAVERSE_MNEMONIC, event); - runEventQueue(); - assertTrue(allScopeBox.isFocusControl()); - } - - @Test - public void testShiftEnterReversesSearchDirection() { - openTextViewerAndFindReplaceDialog(); - - Combo findField= (Combo)fFindReplaceDialog.get("fFindField"); - findField.setText("line"); - IFindReplaceTarget target= (IFindReplaceTarget)fFindReplaceDialog.get("fTarget"); - runEventQueue(); - Shell shell= ((Shell)fFindReplaceDialog.get("fActiveShell")); - if (shell == null && Util.isGtk()) { - if (ScreenshotTest.isRunByGerritHudsonJob()) { - takeScreenshot(); - return; - } else - fail("this test does not work on GTK unless the runtime workbench has focus. Screenshot: " + takeScreenshot()); - } - final Event event= new Event(); - - event.detail= SWT.TRAVERSE_RETURN; - event.character= SWT.CR; - findField.traverse(SWT.TRAVERSE_RETURN, event); - runEventQueue(); - assertEquals(0, (target.getSelection()).x); - assertEquals(4, (target.getSelection()).y); - - event.doit= true; - findField.traverse(SWT.TRAVERSE_RETURN, event); - runEventQueue(); - assertEquals(5, (target.getSelection()).x); - assertEquals(4, (target.getSelection()).y); - - event.stateMask= SWT.SHIFT; - event.doit= true; - findField.traverse(SWT.TRAVERSE_RETURN, event); - assertEquals(0, (target.getSelection()).x); - assertEquals(4, (target.getSelection()).y); - - Button forwardRadioButton= (Button)fFindReplaceDialog.get("fForwardRadioButton"); - forwardRadioButton.setSelection(false); - event.doit= true; - forwardRadioButton.traverse(SWT.TRAVERSE_RETURN, event); - assertEquals(5, (target.getSelection()).x); - } - -} diff --git a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java index c1b2c55bd49..9d34cf24f25 100644 --- a/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java +++ b/tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/WorkbenchTextEditorTestSuite.java @@ -32,7 +32,6 @@ */ @RunWith(Suite.class) @SuiteClasses({ - FindReplaceDialogTest.class, HippieCompletionTest.class, RangeTest.class, ChangeRegionTest.class,