001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.texteditor;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014: import java.util.ResourceBundle;
015: import java.util.regex.PatternSyntaxException;
016:
017: import org.eclipse.swt.graphics.Point;
018: import org.eclipse.swt.widgets.Shell;
019:
020: import org.eclipse.jface.action.IStatusLineManager;
021: import org.eclipse.jface.dialogs.IDialogSettings;
022:
023: import org.eclipse.jface.text.IFindReplaceTarget;
024: import org.eclipse.jface.text.IFindReplaceTargetExtension3;
025: import org.eclipse.jface.text.TextUtilities;
026:
027: import org.eclipse.ui.IEditorPart;
028: import org.eclipse.ui.IWorkbenchPart;
029: import org.eclipse.ui.IWorkbenchWindow;
030: import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
031:
032: /**
033: * An action which finds the next/previous occurrence of the last search or the
034: * current selection if present.
035: * <p>
036: * This class may be instantiated; it is not intended to be subclassed.
037: * </p>
038: *
039: * @since 2.0
040: */
041: public class FindNextAction extends ResourceAction implements IUpdate {
042:
043: /** The action's target */
044: private IFindReplaceTarget fTarget;
045: /** The part the action is bound to */
046: private IWorkbenchPart fWorkbenchPart;
047: /** The workbench window */
048: private IWorkbenchWindow fWorkbenchWindow;
049: /** The dialog settings to retrieve the last search */
050: private IDialogSettings fDialogSettings;
051: /** The find history as initially given in the dialog settings. */
052: private List fFindHistory = new ArrayList();
053: /** The find string as initially given in the dialog settings. */
054: private String fFindString;
055: /** The search direction as initially given in the dialog settings. */
056: private boolean fForward;
057: /** The wrapping flag as initially given in the dialog settings. */
058: private boolean fWrapInit;
059: /** The case flag as initially given in the dialog settings. */
060: private boolean fCaseInit;
061: /** The whole word flag as initially given in the dialog settings. */
062: private boolean fWholeWordInit;
063: /**
064: * The regExSearch flag as initially given in the dialog settings.
065: *
066: * @since 3.0
067: */
068: private boolean fRegExSearch;
069: /**
070: * The last selection set by find/replace.
071: *
072: * @since 3.0
073: */
074: private String fSelection;
075:
076: /**
077: * Creates a new find/replace action for the given workbench part.
078: * The action configures its visual representation from the given
079: * resource bundle.
080: *
081: * @param bundle the resource bundle
082: * @param prefix a prefix to be prepended to the various resource keys
083: * (described in <code>ResourceAction</code> constructor), or
084: * <code>null</code> if none
085: * @param workbenchPart the workbench part
086: * @param forward the search direction
087: * @see ResourceAction#ResourceAction(ResourceBundle, String)
088: */
089: public FindNextAction(ResourceBundle bundle, String prefix,
090: IWorkbenchPart workbenchPart, boolean forward) {
091: super (bundle, prefix);
092: fWorkbenchPart = workbenchPart;
093: fForward = forward;
094: update();
095: }
096:
097: /**
098: * Creates a new find/replace action for the given workbench window.
099: * The action configures its visual representation from the given
100: * resource bundle.
101: *
102: * @param bundle the resource bundle
103: * @param prefix a prefix to be prepended to the various resource keys
104: * (described in <code>ResourceAction</code> constructor), or
105: * <code>null</code> if none
106: * @param workbenchWindow the workbench window
107: * @param forward the search direction
108: * @see ResourceAction#ResourceAction(ResourceBundle, String)
109: *
110: * @deprecated use FindReplaceAction(ResourceBundle, String, IWorkbenchPart, boolean) instead
111: */
112: public FindNextAction(ResourceBundle bundle, String prefix,
113: IWorkbenchWindow workbenchWindow, boolean forward) {
114: super (bundle, prefix);
115: fWorkbenchWindow = workbenchWindow;
116: fForward = forward;
117: update();
118: }
119:
120: /**
121: * Returns the find string based on the selection or the find history.
122: * @return the find string
123: */
124: private String getFindString() {
125: String fullSelection = fTarget.getSelectionText();
126: String firstLine = getFirstLine(fullSelection);
127: if ((firstLine.length() == 0 || fRegExSearch
128: && fullSelection.equals(fSelection))
129: && !fFindHistory.isEmpty())
130: return (String) fFindHistory.get(0);
131: else if (fRegExSearch && fullSelection.length() > 0)
132: return FindReplaceDialog
133: .escapeForRegExPattern(fullSelection);
134: else
135: return firstLine;
136: }
137:
138: /**
139: * Returns the status line manager of the active editor.
140: * @return the status line manager of the active editor
141: */
142: private IStatusLineManager getStatusLineManager() {
143: IEditorPart editor = fWorkbenchPart.getSite().getPage()
144: .getActiveEditor();
145: if (editor == null)
146: return null;
147:
148: return editor.getEditorSite().getActionBars()
149: .getStatusLineManager();
150: }
151:
152: /**
153: * Sets the "no matches found" error message to the status line.
154: *
155: * @since 3.0
156: */
157: private void statusNotFound() {
158: fWorkbenchPart.getSite().getShell().getDisplay().beep();
159:
160: IStatusLineManager manager = getStatusLineManager();
161: if (manager == null)
162: return;
163:
164: manager
165: .setMessage(EditorMessages.FindNext_Status_noMatch_label);
166: }
167:
168: /**
169: * Clears the status line.
170: */
171: private void statusClear() {
172: IStatusLineManager manager = getStatusLineManager();
173: if (manager == null)
174: return;
175:
176: manager.setErrorMessage(""); //$NON-NLS-1$
177: manager.setMessage(""); //$NON-NLS-1$
178: }
179:
180: /*
181: * @see IAction#run()
182: */
183: public void run() {
184: if (fTarget != null) {
185: readConfiguration();
186:
187: fFindString = getFindString();
188: if (fFindString == null) {
189: statusNotFound();
190: return;
191: }
192:
193: boolean wholeWord = fWholeWordInit && !fRegExSearch
194: && isWord(fFindString);
195:
196: statusClear();
197: if (!findNext(fFindString, fForward, fCaseInit, fWrapInit,
198: wholeWord, fRegExSearch))
199: statusNotFound();
200:
201: writeConfiguration();
202: }
203: }
204:
205: /**
206: * Tests whether each character in the given
207: * string is a letter.
208: *
209: * @param str
210: * @return <code>true</code> if the given string is a word
211: * @since 3.2
212: */
213: private boolean isWord(String str) {
214: if (str == null || str.length() == 0)
215: return false;
216:
217: for (int i = 0; i < str.length(); i++) {
218: if (!Character.isJavaIdentifierPart(str.charAt(i)))
219: return false;
220: }
221: return true;
222: }
223:
224: /*
225: * @see IUpdate#update()
226: */
227: public void update() {
228:
229: if (fWorkbenchPart == null && fWorkbenchWindow != null)
230: fWorkbenchPart = fWorkbenchWindow.getPartService()
231: .getActivePart();
232:
233: if (fWorkbenchPart != null)
234: fTarget = (IFindReplaceTarget) fWorkbenchPart
235: .getAdapter(IFindReplaceTarget.class);
236: else
237: fTarget = null;
238:
239: setEnabled(fTarget != null && fTarget.canPerformFind());
240: }
241:
242: /*
243: * @see FindReplaceDialog#findIndex(String, int, boolean, boolean, boolean, boolean)
244: * @since 3.0
245: */
246: private int findIndex(String findString, int startPosition,
247: boolean forwardSearch, boolean caseSensitive,
248: boolean wrapSearch, boolean wholeWord, boolean regExSearch) {
249:
250: if (forwardSearch) {
251: if (wrapSearch) {
252: int index = findAndSelect(startPosition, findString,
253: true, caseSensitive, wholeWord, regExSearch);
254: if (index == -1) {
255: beep();
256: index = findAndSelect(-1, findString, true,
257: caseSensitive, wholeWord, regExSearch);
258: }
259: return index;
260: }
261: return findAndSelect(startPosition, findString, true,
262: caseSensitive, wholeWord, regExSearch);
263: }
264:
265: // backward
266: if (wrapSearch) {
267: int index = findAndSelect(startPosition - 1, findString,
268: false, caseSensitive, wholeWord, regExSearch);
269: if (index == -1) {
270: beep();
271: index = findAndSelect(-1, findString, false,
272: caseSensitive, wholeWord, regExSearch);
273: }
274: return index;
275: }
276: return findAndSelect(startPosition - 1, findString, false,
277: caseSensitive, wholeWord, regExSearch);
278: }
279:
280: /**
281: * Returns whether the specified search string can be found using the given options.
282: *
283: * @param findString the string to search for
284: * @param forwardSearch the search direction
285: * @param caseSensitive should the search honor cases
286: * @param wrapSearch should the search wrap to the start/end if end/start reached
287: * @param wholeWord does the find string represent a complete word
288: * @param regExSearch if <code>true</code> findString represents a regular expression
289: * @return <code>true</code> if the find string can be found using the given options
290: * @since 3.0
291: */
292: private boolean findNext(String findString, boolean forwardSearch,
293: boolean caseSensitive, boolean wrapSearch,
294: boolean wholeWord, boolean regExSearch) {
295:
296: Point r = fTarget.getSelection();
297: int findReplacePosition = r.x;
298: if (forwardSearch)
299: findReplacePosition += r.y;
300:
301: int index = findIndex(findString, findReplacePosition,
302: forwardSearch, caseSensitive, wrapSearch, wholeWord,
303: regExSearch);
304:
305: if (index != -1)
306: return true;
307:
308: return false;
309: }
310:
311: private void beep() {
312: Shell shell = null;
313: if (fWorkbenchPart != null)
314: shell = fWorkbenchPart.getSite().getShell();
315: else if (fWorkbenchWindow != null)
316: shell = fWorkbenchWindow.getShell();
317:
318: if (shell != null && !shell.isDisposed())
319: shell.getDisplay().beep();
320: }
321:
322: /**
323: * Searches for a string starting at the given offset and using the specified search
324: * directives. If a string has been found it is selected and its start offset is
325: * returned.
326: *
327: * @param offset the offset at which searching starts
328: * @param findString the string which should be found
329: * @param forwardSearch the direction of the search
330: * @param caseSensitive <code>true</code> performs a case sensitive search, <code>false</code> an insensitive search
331: * @param wholeWord if <code>true</code> only occurrences are reported in which the findString stands as a word by itself
332: * @param regExSearch if <code>true</code> findString represents a regular expression
333: * @return the position of the specified string, or -1 if the string has not been found
334: * @since 3.0
335: */
336: private int findAndSelect(int offset, String findString,
337: boolean forwardSearch, boolean caseSensitive,
338: boolean wholeWord, boolean regExSearch) {
339: if (fTarget instanceof IFindReplaceTargetExtension3) {
340: try {
341: return ((IFindReplaceTargetExtension3) fTarget)
342: .findAndSelect(offset, findString,
343: forwardSearch, caseSensitive,
344: wholeWord, regExSearch);
345: } catch (PatternSyntaxException ex) {
346: return -1;
347: }
348: }
349: return fTarget.findAndSelect(offset, findString, forwardSearch,
350: caseSensitive, wholeWord);
351: }
352:
353: //--------------- configuration handling --------------
354:
355: /**
356: * Returns the dialog settings object used to share state
357: * between several find/replace dialogs.
358: *
359: * @return the dialog settings to be used
360: */
361: private IDialogSettings getDialogSettings() {
362: IDialogSettings settings = TextEditorPlugin.getDefault()
363: .getDialogSettings();
364: fDialogSettings = settings.getSection(FindReplaceDialog.class
365: .getName());
366: if (fDialogSettings == null)
367: fDialogSettings = settings
368: .addNewSection(FindReplaceDialog.class.getName());
369: return fDialogSettings;
370: }
371:
372: /**
373: * Initializes itself from the dialog settings with the same state
374: * as at the previous invocation.
375: */
376: private void readConfiguration() {
377: IDialogSettings s = getDialogSettings();
378:
379: fWrapInit = s.getBoolean("wrap"); //$NON-NLS-1$
380: fCaseInit = s.getBoolean("casesensitive"); //$NON-NLS-1$
381: fWholeWordInit = s.getBoolean("wholeword"); //$NON-NLS-1$
382: fRegExSearch = s.getBoolean("isRegEx"); //$NON-NLS-1$
383: fSelection = s.get("selection"); //$NON-NLS-1$
384:
385: String[] findHistory = s.getArray("findhistory"); //$NON-NLS-1$
386: if (findHistory != null) {
387: fFindHistory.clear();
388: for (int i = 0; i < findHistory.length; i++)
389: fFindHistory.add(findHistory[i]);
390: }
391: }
392:
393: /**
394: * Stores its current configuration in the dialog store.
395: */
396: private void writeConfiguration() {
397: if (fFindString == null)
398: return;
399:
400: IDialogSettings s = getDialogSettings();
401: s.put("selection", fTarget.getSelectionText()); //$NON-NLS-1$
402:
403: if (!fFindHistory.isEmpty()
404: && fFindString.equals(fFindHistory.get(0)))
405: return;
406:
407: int index = fFindHistory.indexOf(fFindString);
408: if (index != -1)
409: fFindHistory.remove(index);
410: fFindHistory.add(0, fFindString);
411:
412: while (fFindHistory.size() > 8)
413: fFindHistory.remove(8);
414: String[] names = new String[fFindHistory.size()];
415: fFindHistory.toArray(names);
416: s.put("findhistory", names); //$NON-NLS-1$
417: }
418:
419: /**
420: * Returns the first line of the given selection.
421: *
422: * @param selection the selection
423: * @return the first line of the selection
424: */
425: private String getFirstLine(String selection) {
426: if (selection.length() > 0) {
427: int[] info = TextUtilities.indexOf(
428: TextUtilities.DELIMITERS, selection, 0);
429: if (info[0] > 0)
430: return selection.substring(0, info[0]);
431: else if (info[0] == -1)
432: return selection;
433: }
434: return selection;
435: }
436: }
|