Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/gjt/sp/jedit/search/SearchAndReplace.java


1   /*
2    * SearchAndReplace.java - Search and replace
3    * :tabSize=8:indentSize=8:noTabs=false:
4    * :folding=explicit:collapseFolds=1:
5    *
6    * Copyright (C) 1999, 2000, 2001, 2002 Slava Pestov
7    * Portions copyright (C) 2001 Tom Locke
8    *
9    * This program is free software; you can redistribute it and/or
10   * modify it under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; either version 2
12   * of the License, or any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with this program; if not, write to the Free Software
21   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22   */
23  
24  package org.gjt.sp.jedit.search;
25  
26  //{{{ Imports
27  import bsh.*;
28  import java.awt.Component;
29  import javax.swing.JOptionPane;
30  import javax.swing.text.Segment;
31  import org.gjt.sp.jedit.*;
32  import org.gjt.sp.jedit.io.VFSManager;
33  import org.gjt.sp.jedit.msg.SearchSettingsChanged;
34  import org.gjt.sp.jedit.textarea.*;
35  import org.gjt.sp.util.CharIndexedSegment;
36  import org.gjt.sp.util.Log;
37  //}}}
38  
39  /**
40   * Class that implements regular expression and literal search within
41   * jEdit buffers.<p>
42   *
43   * There are two main groups of methods in this class:
44   * <ul>
45   * <li>Property accessors - for changing search and replace settings.</li>
46   * <li>Actions - for performing search and replace.</li>
47   * </ul>
48   *
49   * The "HyperSearch" and "Keep dialog" features, as reflected in
50   * checkbox options in the search dialog, are not handled from within
51   * this class. If you wish to have these options set before the search dialog
52   * appears, make a prior call to either or both of the following:
53   *
54   * <pre> jEdit.setBooleanProperty("search.hypersearch.toggle",true);
55   * jEdit.setBooleanProperty("search.keepDialog.toggle",true);</pre>
56   *
57   * If you are not using the dialog to undertake a search or replace, you may
58   * call any of the search and replace methods (including
59   * {@link #hyperSearch(View)}) without concern for the value of these properties.
60   *
61   * @author Slava Pestov
62   * @author John Gellene (API documentation)
63   * @version $Id: SearchAndReplace.java,v 1.57 2003/10/10 23:46:24 spestov Exp $
64   */
65  public class SearchAndReplace
66  {
67    //{{{ Getters and setters
68  
69    //{{{ setSearchString() method
70    /**
71     * Sets the current search string.
72     * @param search The new search string
73     */
74    public static void setSearchString(String search)
75    {
76      if(search.equals(SearchAndReplace.search))
77        return;
78  
79      SearchAndReplace.search = search;
80      matcher = null;
81  
82      EditBus.send(new SearchSettingsChanged(null));
83    } //}}}
84  
85    //{{{ getSearchString() method
86    /**
87     * Returns the current search string.
88     */
89    public static String getSearchString()
90    {
91      return search;
92    } //}}}
93  
94    //{{{ setReplaceString() method
95    /**
96     * Sets the current replacement string.
97     * @param search The new replacement string
98     */
99    public static void setReplaceString(String replace)
100   {
101     if(replace.equals(SearchAndReplace.replace))
102       return;
103 
104     SearchAndReplace.replace = replace;
105 
106     EditBus.send(new SearchSettingsChanged(null));
107   } //}}}
108 
109   //{{{ getReplaceString() method
110   /**
111    * Returns the current replacement string.
112    */
113   public static String getReplaceString()
114   {
115     return replace;
116   } //}}}
117 
118   //{{{ setIgnoreCase() method
119   /**
120    * Sets the ignore case flag.
121    * @param ignoreCase True if searches should be case insensitive,
122    * false otherwise
123    */
124   public static void setIgnoreCase(boolean ignoreCase)
125   {
126     if(ignoreCase == SearchAndReplace.ignoreCase)
127       return;
128 
129     SearchAndReplace.ignoreCase = ignoreCase;
130     matcher = null;
131 
132     EditBus.send(new SearchSettingsChanged(null));
133   } //}}}
134 
135   //{{{ getIgnoreCase() method
136   /**
137    * Returns the state of the ignore case flag.
138    * @return True if searches should be case insensitive,
139    * false otherwise
140    */
141   public static boolean getIgnoreCase()
142   {
143     return ignoreCase;
144   } //}}}
145 
146   //{{{ setRegexp() method
147   /**
148    * Sets the state of the regular expression flag.
149    * @param regexp True if regular expression searches should be
150    * performed
151    */
152   public static void setRegexp(boolean regexp)
153   {
154     if(regexp == SearchAndReplace.regexp)
155       return;
156 
157     SearchAndReplace.regexp = regexp;
158     if(regexp && reverse)
159       reverse = false;
160 
161     matcher = null;
162 
163     EditBus.send(new SearchSettingsChanged(null));
164   } //}}}
165 
166   //{{{ getRegexp() method
167   /**
168    * Returns the state of the regular expression flag.
169    * @return True if regular expression searches should be performed
170    */
171   public static boolean getRegexp()
172   {
173     return regexp;
174   } //}}}
175 
176   //{{{ setReverseSearch() method
177   /**
178    * Determines whether a reverse search will conducted from the current
179    * position to the beginning of a buffer. Note that reverse search and
180    * regular expression search is mutually exclusive; enabling one will
181    * disable the other.
182    * @param reverse True if searches should go backwards,
183    * false otherwise
184    */
185   public static void setReverseSearch(boolean reverse)
186   {
187     if(reverse == SearchAndReplace.reverse)
188       return;
189 
190     SearchAndReplace.reverse = reverse;
191 
192     EditBus.send(new SearchSettingsChanged(null));
193   } //}}}
194 
195   //{{{ getReverseSearch() method
196   /**
197    * Returns the state of the reverse search flag.
198    * @return True if searches should go backwards,
199    * false otherwise
200    */
201   public static boolean getReverseSearch()
202   {
203     return reverse;
204   } //}}}
205 
206   //{{{ setBeanShellReplace() method
207   /**
208    * Sets the state of the BeanShell replace flag.
209    * @param regexp True if the replace string is a BeanShell expression
210    * @since jEdit 3.2pre2
211    */
212   public static void setBeanShellReplace(boolean beanshell)
213   {
214     if(beanshell == SearchAndReplace.beanshell)
215       return;
216 
217     SearchAndReplace.beanshell = beanshell;
218 
219     EditBus.send(new SearchSettingsChanged(null));
220   } //}}}
221 
222   //{{{ getBeanShellReplace() method
223   /**
224    * Returns the state of the BeanShell replace flag.
225    * @return True if the replace string is a BeanShell expression
226    * @since jEdit 3.2pre2
227    */
228   public static boolean getBeanShellReplace()
229   {
230     return beanshell;
231   } //}}}
232 
233   //{{{ setAutoWrap() method
234   /**
235    * Sets the state of the auto wrap around flag.
236    * @param wrap If true, the 'continue search from start' dialog
237    * will not be displayed
238    * @since jEdit 3.2pre2
239    */
240   public static void setAutoWrapAround(boolean wrap)
241   {
242     if(wrap == SearchAndReplace.wrap)
243       return;
244 
245     SearchAndReplace.wrap = wrap;
246 
247     EditBus.send(new SearchSettingsChanged(null));
248   } //}}}
249 
250   //{{{ getAutoWrap() method
251   /**
252    * Returns the state of the auto wrap around flag.
253    * @param wrap If true, the 'continue search from start' dialog
254    * will not be displayed
255    * @since jEdit 3.2pre2
256    */
257   public static boolean getAutoWrapAround()
258   {
259     return wrap;
260   } //}}}
261 
262   //{{{ setSearchMatcher() method
263   /**
264    * Sets a custom search string matcher. Note that calling
265    * {@link #setSearchString(String)},
266    * {@link #setIgnoreCase(boolean)}, or {@link #setRegexp(boolean)}
267    * will reset the matcher to the default.
268    */
269   public static void setSearchMatcher(SearchMatcher matcher)
270   {
271     SearchAndReplace.matcher = matcher;
272 
273     EditBus.send(new SearchSettingsChanged(null));
274   } //}}}
275 
276   //{{{ getSearchMatcher() method
277   /**
278    * Returns the current search string matcher.
279    * @param reverseOK Replacement commands need a non-reversed matcher,
280    * so they set this to false
281    * @exception IllegalArgumentException if regular expression search
282    * is enabled, the search string or replacement string is invalid
283    * @since jEdit 4.1pre7
284    */
285   public static SearchMatcher getSearchMatcher()
286     throws Exception
287   {
288     if(matcher != null)
289       return matcher;
290 
291     if(search == null || "".equals(search))
292       return null;
293 
294     if(regexp)
295       matcher = new RESearchMatcher(search,ignoreCase);
296     else
297     {
298       matcher = new BoyerMooreSearchMatcher(search,ignoreCase);
299     }
300 
301     return matcher;
302   } //}}}
303 
304   //{{{ setSearchFileSet() method
305   /**
306    * Sets the current search file set.
307    * @param fileset The file set to perform searches in
308    * @see AllBufferSet
309    * @see CurrentBufferSet
310    * @see DirectoryListSet
311    */
312   public static void setSearchFileSet(SearchFileSet fileset)
313   {
314     SearchAndReplace.fileset = fileset;
315 
316     EditBus.send(new SearchSettingsChanged(null));
317   } //}}}
318 
319   //{{{ getSearchFileSet() method
320   /**
321    * Returns the current search file set.
322    */
323   public static SearchFileSet getSearchFileSet()
324   {
325     return fileset;
326   } //}}}
327 
328   //}}}
329 
330   //{{{ Actions
331 
332   //{{{ hyperSearch() method
333   /**
334    * Performs a HyperSearch.
335    * @param view The view
336    * @since jEdit 2.7pre3
337    */
338   public static boolean hyperSearch(View view)
339   {
340     return hyperSearch(view,false);
341   } //}}}
342 
343   //{{{ hyperSearch() method
344   /**
345    * Performs a HyperSearch.
346    * @param view The view
347    * @param selection If true, will only search in the current selection.
348    * Note that the file set must be the current buffer file set for this
349    * to work.
350    * @since jEdit 4.0pre1
351    */
352   public static boolean hyperSearch(View view, boolean selection)
353   {
354     // component that will parent any dialog boxes
355     Component comp = SearchDialog.getSearchDialog(view);
356     if(comp == null)
357       comp = view;
358 
359     record(view,"hyperSearch(view," + selection + ")",false,
360       !selection);
361 
362     view.getDockableWindowManager().addDockableWindow(
363       HyperSearchResults.NAME);
364     final HyperSearchResults results = (HyperSearchResults)
365       view.getDockableWindowManager()
366       .getDockable(HyperSearchResults.NAME);
367     results.searchStarted();
368 
369     try
370     {
371       SearchMatcher matcher = getSearchMatcher();
372       if(matcher == null)
373       {
374         view.getToolkit().beep();
375         results.searchFailed();
376         return false;
377       }
378 
379       Selection[] s;
380       if(selection)
381       {
382         s = view.getTextArea().getSelection();
383         if(s == null)
384         {
385           results.searchFailed();
386           return false;
387         }
388       }
389       else
390         s = null;
391       VFSManager.runInWorkThread(new HyperSearchRequest(view,
392         matcher,results,s));
393       return true;
394     }
395     catch(Exception e)
396     {
397       results.searchFailed();
398       Log.log(Log.ERROR,SearchAndReplace.class,e);
399       Object[] args = { e.toString() };
400       GUIUtilities.error(comp,
401         beanshell ? "searcherror-bsh"
402         : "searcherror",args);
403       return false;
404     }
405   } //}}}
406 
407   //{{{ find() method
408   /**
409    * Finds the next occurance of the search string.
410    * @param view The view
411    * @return True if the operation was successful, false otherwise
412    */
413   public static boolean find(View view)
414   {
415     // component that will parent any dialog boxes
416     Component comp = SearchDialog.getSearchDialog(view);
417     if(comp == null)
418       comp = view;
419 
420     boolean repeat = false;
421     String path = fileset.getNextFile(view,null);
422     if(path == null)
423     {
424       GUIUtilities.error(comp,"empty-fileset",null);
425       return false;
426     }
427 
428     boolean _reverse = reverse && fileset instanceof CurrentBufferSet;
429     if(_reverse && regexp)
430     {
431       GUIUtilities.error(comp,"regexp-reverse",null);
432       return false;
433     }
434 
435     try
436     {
437       view.showWaitCursor();
438 
439       SearchMatcher matcher = getSearchMatcher();
440       if(matcher == null)
441       {
442         view.getToolkit().beep();
443         return false;
444       }
445 
446       record(view,"find(view)",false,true);
447 
448 loop:      for(;;)
449       {
450         while(path != null)
451         {
452           Buffer buffer = jEdit.openTemporary(
453             view,null,path,false);
454 
455           /* this is stupid and misleading.
456            * but 'path' is not used anywhere except
457            * the above line, and if this is done
458            * after the 'continue', then we will
459            * either hang, or be forced to duplicate
460            * it inside the buffer == null, or add
461            * a 'finally' clause. you decide which one's
462            * worse. */
463           path = fileset.getNextFile(view,path);
464 
465           if(buffer == null)
466             continue loop;
467 
468           // Wait for the buffer to load
469           if(!buffer.isLoaded())
470             VFSManager.waitForRequests();
471 
472           int start;
473 
474           if(view.getBuffer() == buffer && !repeat)
475           {
476             JEditTextArea textArea = view.getTextArea();
477             Selection s = textArea.getSelectionAtOffset(
478               textArea.getCaretPosition());
479             if(s == null)
480               start = textArea.getCaretPosition();
481             else if(_reverse)
482               start = s.getStart();
483             else
484               start = s.getEnd();
485           }
486           else if(_reverse)
487             start = buffer.getLength();
488           else
489             start = 0;
490 
491           if(find(view,buffer,start,repeat,_reverse))
492             return true;
493         }
494 
495         if(repeat)
496         {
497           if(!BeanShell.isScriptRunning())
498           {
499             view.getStatus().setMessageAndClear(
500               jEdit.getProperty("view.status.search-not-found"));
501 
502             view.getToolkit().beep();
503           }
504           return false;
505         }
506 
507         boolean restart;
508 
509         // if auto wrap is on, always restart search.
510         // if auto wrap is off, and we're called from
511         // a macro, stop search. If we're called
512         // interactively, ask the user what to do.
513         if(wrap)
514         {
515           if(!BeanShell.isScriptRunning())
516           {
517             view.getStatus().setMessageAndClear(
518               jEdit.getProperty("view.status.auto-wrap"));
519             // beep if beep property set
520             if(jEdit.getBooleanProperty("search.beepOnSearchAutoWrap"))
521             {
522               view.getToolkit().beep();
523             }
524           }
525           restart = true;
526         }
527         else if(BeanShell.isScriptRunning())
528         {
529           restart = false;
530         }
531         else
532         {
533           Integer[] args = { new Integer(_reverse ? 1 : 0) };
534           int result = GUIUtilities.confirm(comp,
535             "keepsearching",args,
536             JOptionPane.YES_NO_OPTION,
537             JOptionPane.QUESTION_MESSAGE);
538           restart = (result == JOptionPane.YES_OPTION);
539         }
540 
541         if(restart)
542         {
543           // start search from beginning
544           path = fileset.getFirstFile(view);
545           repeat = true;
546         }
547         else
548           break loop;
549       }
550     }
551     catch(Exception e)
552     {
553       Log.log(Log.ERROR,SearchAndReplace.class,e);
554       Object[] args = { e.toString() };
555       GUIUtilities.error(comp,"searcherror",args);
556     }
557     finally
558     {
559       view.hideWaitCursor();
560     }
561 
562     return false;
563   } //}}}
564 
565   //{{{ find() method
566   /**
567    * Finds the next instance of the search string in the specified
568    * buffer.
569    * @param view The view
570    * @param buffer The buffer
571    * @param start Location where to start the search
572    */
573   public static boolean find(View view, Buffer buffer, int start)
574     throws Exception
575   {
576     return find(view,buffer,start,false,false);
577   } //}}}
578 
579   //{{{ find() method
580   /**
581    * Finds the next instance of the search string in the specified
582    * buffer.
583    * @param view The view
584    * @param buffer The buffer
585    * @param start Location where to start the search
586    * @param firstTime See {@link SearchMatcher#nextMatch(CharIndexed,
587    * boolean,boolean,boolean,boolean)}.
588    * @since jEdit 4.1pre7
589    */
590   public static boolean find(View view, Buffer buffer, int start,
591     boolean firstTime, boolean reverse) throws Exception
592   {
593     SearchMatcher matcher = getSearchMatcher();
594     if(matcher == null)
595     {
596       view.getToolkit().beep();
597       return false;
598     }
599 
600     Segment text = new Segment();
601     if(reverse)
602       buffer.getText(0,start,text);
603     else
604       buffer.getText(start,buffer.getLength() - start,text);
605 
606     // the start and end flags will be wrong with reverse search enabled,
607     // but they are only used by the regexp matcher, which doesn't
608     // support reverse search yet.
609     //
610     // REMIND: fix flags when adding reverse regexp search.
611     SearchMatcher.Match match = matcher.nextMatch(new CharIndexedSegment(text,reverse),
612       start == 0,true,firstTime,reverse);
613 
614     if(match != null)
615     {
616       jEdit.commitTemporary(buffer);
617       view.setBuffer(buffer);
618       JEditTextArea textArea = view.getTextArea();
619 
620       if(reverse)
621       {
622         textArea.setSelection(new Selection.Range(
623           start - match.end,
624           start - match.start));
625         // make sure end of match is visible
626         textArea.scrollTo(start - match.start,false);
627         textArea.moveCaretPosition(start - match.end);
628       }
629       else
630       {
631         textArea.setSelection(new Selection.Range(
632           start + match.start,
633           start + match.end));
634         textArea.moveCaretPosition(start + match.end);
635         // make sure start of match is visible
636         textArea.scrollTo(start + match.start,false);
637       }
638 
639       return true;
640     }
641     else
642       return false;
643   } //}}}
644 
645   //{{{ replace() method
646   /**
647    * Replaces the current selection with the replacement string.
648    * @param view The view
649    * @return True if the operation was successful, false otherwise
650    */
651   public static boolean replace(View view)
652   {
653     // component that will parent any dialog boxes
654     Component comp = SearchDialog.getSearchDialog(view);
655     if(comp == null)
656       comp = view;
657 
658     JEditTextArea textArea = view.getTextArea();
659 
660     Buffer buffer = view.getBuffer();
661     if(!buffer.isEditable())
662       return false;
663 
664     boolean smartCaseReplace = (replace != null
665       && TextUtilities.getStringCase(replace)
666       == TextUtilities.LOWER_CASE);
667 
668     Selection[] selection = textArea.getSelection();
669     if(selection.length == 0)
670     {
671       view.getToolkit().beep();
672       return false;
673     }
674 
675     record(view,"replace(view)",true,false);
676 
677     // a little hack for reverse replace and find
678     int caret = textArea.getCaretPosition();
679     Selection s = textArea.getSelectionAtOffset(caret);
680     if(s != null)
681       caret = s.getStart();
682 
683     try
684     {
685       buffer.beginCompoundEdit();
686 
687       SearchMatcher matcher = getSearchMatcher();
688       if(matcher == null)
689         return false;
690 
691       initReplace();
692 
693       int retVal = 0;
694 
695       for(int i = 0; i < selection.length; i++)
696       {
697         s = selection[i];
698 
699         /* if an occurence occurs at the
700         beginning of the selection, the
701         selection start will get moved.
702         this sucks, so we hack to avoid it. */
703         int start = s.getStart();
704 
705         if(s instanceof Selection.Range)
706         {
707           retVal += _replace(view,buffer,matcher,
708             s.getStart(),s.getEnd(),
709             smartCaseReplace);
710 
711           textArea.removeFromSelection(s);
712           textArea.addToSelection(new Selection.Range(
713             start,s.getEnd()));
714         }
715         else if(s instanceof Selection.Rect)
716         {
717           Selection.Rect rect = (Selection.Rect)s;
718           int startCol = rect.getStartColumn(
719             buffer);
720           int endCol = rect.getEndColumn(
721             buffer);
722 
723           for(int j = s.getStartLine(); j <= s.getEndLine(); j++)
724           {
725             retVal += _replace(view,buffer,
726               matcher,
727               getColumnOnOtherLine(buffer,j,startCol),
728               getColumnOnOtherLine(buffer,j,endCol),
729               smartCaseReplace);
730           }
731           textArea.addToSelection(new Selection.Rect(
732             start,s.getEnd()));
733         }
734       }
735 
736       boolean _reverse = !regexp && reverse && fileset instanceof CurrentBufferSet;
737       if(_reverse)
738       {
739         // so that Replace and Find continues from
740         // the right location
741         textArea.moveCaretPosition(caret);
742       }
743       else
744       {
745         s = textArea.getSelectionAtOffset(
746           textArea.getCaretPosition());
747         if(s != null)
748           textArea.moveCaretPosition(s.getEnd());
749       }
750 
751       if(retVal == 0)
752       {
753         view.getToolkit().beep();
754         return false;
755       }
756 
757       return true;
758     }
759     catch(Exception e)
760     {
761       Log.log(Log.ERROR,SearchAndReplace.class,e);
762       Object[] args = { e.toString() };
763       GUIUtilities.error(comp,
764         beanshell ? "searcherror-bsh"
765         : "searcherror",args);
766     }
767     finally
768     {
769       buffer.endCompoundEdit();
770     }
771 
772     return false;
773   } //}}}
774 
775   //{{{ replace() method
776   /**
777    * Replaces text in the specified range with the replacement string.
778    * @param view The view
779    * @param buffer The buffer
780    * @param start The start offset
781    * @param end The end offset
782    * @return True if the operation was successful, false otherwise
783    */
784   public static boolean replace(View view, Buffer buffer, int start, int end)
785   {
786     if(!buffer.isEditable())
787       return false;
788 
789     // component that will parent any dialog boxes
790     Component comp = SearchDialog.getSearchDialog(view);
791     if(comp == null)
792       comp = view;
793 
794     boolean smartCaseReplace = (replace != null
795       && TextUtilities.getStringCase(replace)
796       == TextUtilities.LOWER_CASE);
797 
798     try
799     {
800       buffer.beginCompoundEdit();
801 
802       SearchMatcher matcher = getSearchMatcher();
803       if(matcher == null)
804         return false;
805 
806       initReplace();
807 
808       int retVal = 0;
809 
810       retVal += _replace(view,buffer,matcher,start,end,
811         smartCaseReplace);
812 
813       if(retVal != 0)
814         return true;
815     }
816     catch(Exception e)
817     {
818       Log.log(Log.ERROR,SearchAndReplace.class,e);
819       Object[] args = { e.toString() };
820       GUIUtilities.error(comp,
821         beanshell ? "searcherror-bsh"
822         : "searcherror",args);
823     }
824     finally
825     {
826       buffer.endCompoundEdit();
827     }
828 
829     return false;
830   } //}}}
831 
832   //{{{ replaceAll() method
833   /**
834    * Replaces all occurances of the search string with the replacement
835    * string.
836    * @param view The view
837    */
838   public static boolean replaceAll(View view)
839   {
840     // component that will parent any dialog boxes
841     Component comp = SearchDialog.getSearchDialog(view);
842     if(comp == null)
843       comp = view;
844 
845     int fileCount = 0;
846     int occurCount = 0;
847 
848     if(fileset.getFileCount(view) == 0)
849     {
850       GUIUtilities.error(comp,"empty-fileset",null);
851       return false;
852     }
853 
854     record(view,"replaceAll(view)",true,true);
855 
856     view.showWaitCursor();
857 
858     boolean smartCaseReplace = (replace != null
859       && TextUtilities.getStringCase(replace)
860       == TextUtilities.LOWER_CASE);
861 
862     try
863     {
864       SearchMatcher matcher = getSearchMatcher();
865       if(matcher == null)
866         return false;
867 
868       initReplace();
869 
870       String path = fileset.getFirstFile(view);
871 loop:      while(path != null)
872       {
873         Buffer buffer = jEdit.openTemporary(
874           view,null,path,false);
875 
876         /* this is stupid and misleading.
877          * but 'path' is not used anywhere except
878          * the above line, and if this is done
879          * after the 'continue', then we will
880          * either hang, or be forced to duplicate
881          * it inside the buffer == null, or add
882          * a 'finally' clause. you decide which one's
883          * worse. */
884         path = fileset.getNextFile(view,path);
885 
886         if(buffer == null)
887           continue loop;
888 
889         // Wait for buffer to finish loading
890         if(buffer.isPerformingIO())
891           VFSManager.waitForRequests();
892 
893         if(!buffer.isEditable())
894           continue loop;
895 
896         // Leave buffer in a consistent state if
897         // an error occurs
898         int retVal = 0;
899 
900         try
901         {
902           buffer.beginCompoundEdit();
903           retVal = _replace(view,buffer,matcher,
904             0,buffer.getLength(),
905             smartCaseReplace);
906         }
907         finally
908         {
909           buffer.endCompoundEdit();
910         }
911 
912         if(retVal != 0)
913         {
914           fileCount++;
915           occurCount += retVal;
916           jEdit.commitTemporary(buffer);
917         }
918       }
919     }
920     catch(Exception e)
921     {
922       Log.log(Log.ERROR,SearchAndReplace.class,e);
923       Object[] args = { e.toString() };
924       GUIUtilities.error(comp,
925         beanshell ? "searcherror-bsh"
926         : "searcherror",args);
927     }
928     finally
929     {
930       view.hideWaitCursor();
931     }
932 
933     /* Don't do this when playing a macro, cos it's annoying */
934     if(!BeanShell.isScriptRunning())
935     {
936       Object[] args = { new Integer(occurCount),
937         new Integer(fileCount) };
938       view.getStatus().setMessageAndClear(jEdit.getProperty(
939         "view.status.replace-all",args));
940       if(occurCount == 0)
941         view.getToolkit().beep();
942     }
943 
944     return (fileCount != 0);
945   } //}}}
946 
947   //}}}
948 
949   //{{{ load() method
950   /**
951    * Loads search and replace state from the properties.
952    */
953   public static void load()
954   {
955     search = jEdit.getProperty("search.find.value");
956     replace = jEdit.getProperty("search.replace.value");
957     ignoreCase = jEdit.getBooleanProperty("search.ignoreCase.toggle");
958     regexp = jEdit.getBooleanProperty("search.regexp.toggle");
959     beanshell = jEdit.getBooleanProperty("search.beanshell.toggle");
960     wrap = jEdit.getBooleanProperty("search.wrap.toggle");
961 
962     fileset = new CurrentBufferSet();
963 
964     // Tags plugin likes to call this method at times other than
965     // startup; so we need to fire a SearchSettingsChanged to
966     // notify the search bar and so on.
967     matcher = null;
968     EditBus.send(new SearchSettingsChanged(null));
969   } //}}}
970 
971   //{{{ save() method
972   /**
973    * Saves search and replace state to the properties.
974    */
975   public static void save()
976   {
977     jEdit.setProperty("search.find.value",search);
978     jEdit.setProperty("search.replace.value",replace);
979     jEdit.setBooleanProperty("search.ignoreCase.toggle",ignoreCase);
980     jEdit.setBooleanProperty("search.regexp.toggle",regexp);
981     jEdit.setBooleanProperty("search.beanshell.toggle",beanshell);
982     jEdit.setBooleanProperty("search.wrap.toggle",wrap);
983   } //}}}
984 
985   //{{{ Private members
986 
987   //{{{ Instance variables
988   private static String search;
989   private static String replace;
990   private static BshMethod replaceMethod;
991   private static NameSpace replaceNS = new NameSpace(
992     BeanShell.getNameSpace(),
993     BeanShell.getNameSpace().getClassManager(),
994     "search and replace");
995   private static boolean regexp;
996   private static boolean ignoreCase;
997   private static boolean reverse;
998   private static boolean beanshell;
999   private static boolean wrap;
1000  private static SearchMatcher matcher;
1001  private static SearchFileSet fileset;
1002  //}}}
1003
1004  //{{{ initReplace() method
1005  /**
1006   * Set up BeanShell replace if necessary.
1007   */
1008  private static void initReplace() throws Exception
1009  {
1010    if(beanshell && replace.length() != 0)
1011    {
1012      replaceMethod = BeanShell.cacheBlock("replace",
1013        "return (" + replace + ");",true);
1014    }
1015    else
1016      replaceMethod = null;
1017  } //}}}
1018
1019  //{{{ record() method
1020  private static void record(View view, String action,
1021    boolean replaceAction, boolean recordFileSet)
1022  {
1023    Macros.Recorder recorder = view.getMacroRecorder();
1024
1025    if(recorder != null)
1026    {
1027      recorder.record("SearchAndReplace.setSearchString(\""
1028        + MiscUtilities.charsToEscapes(search) + "\");");
1029
1030      if(replaceAction)
1031      {
1032        recorder.record("SearchAndReplace.setReplaceString(\""
1033          + MiscUtilities.charsToEscapes(replace) + "\");");
1034        recorder.record("SearchAndReplace.setBeanShellReplace("
1035          + beanshell + ");");
1036      }
1037      else
1038      {
1039        // only record this if doing a find next
1040        recorder.record("SearchAndReplace.setAutoWrapAround("
1041          + wrap + ");");
1042        recorder.record("SearchAndReplace.setReverseSearch("
1043          + reverse + ");");
1044      }
1045
1046      recorder.record("SearchAndReplace.setIgnoreCase("
1047        + ignoreCase + ");");
1048      recorder.record("SearchAndReplace.setRegexp("
1049        + regexp + ");");
1050
1051      if(recordFileSet)
1052      {
1053        recorder.record("SearchAndReplace.setSearchFileSet("
1054          + fileset.getCode() + ");");
1055      }
1056
1057      recorder.record("SearchAndReplace." + action + ";");
1058    }
1059  } //}}}
1060
1061  //{{{ _replace() method
1062  /**
1063   * Replaces all occurances of the search string with the replacement
1064   * string.
1065   * @param view The view
1066   * @param buffer The buffer
1067   * @param start The start offset
1068   * @param end The end offset
1069   * @param matcher The search matcher to use
1070   * @param smartCaseReplace See user's guide
1071   * @return The number of occurrences replaced
1072   */
1073  private static int _replace(View view, Buffer buffer,
1074    SearchMatcher matcher, int start, int end,
1075    boolean smartCaseReplace)
1076    throws Exception
1077  {
1078    int occurCount = 0;
1079
1080    boolean endOfLine = (buffer.getLineEndOffset(
1081      buffer.getLineOfOffset(end)) - 1 == end);
1082
1083    Segment text = new Segment();
1084    int offset = start;
1085loop:    for(int counter = 0; ; counter++)
1086    {
1087      buffer.getText(offset,end - offset,text);
1088
1089      boolean startOfLine = (buffer.getLineStartOffset(
1090        buffer.getLineOfOffset(offset)) == offset);
1091
1092      SearchMatcher.Match occur = matcher.nextMatch(
1093        new CharIndexedSegment(text,false),
1094        startOfLine,endOfLine,counter == 0,
1095        false);
1096      if(occur == null)
1097        break loop;
1098      int _start = occur.start;
1099      int _length = occur.end - occur.start;
1100
1101      String found = new String(text.array,text.offset + _start,_length);
1102      String subst = _replace(occur,found);
1103      if(smartCaseReplace && ignoreCase)
1104      {
1105        int strCase = TextUtilities.getStringCase(found);
1106        if(strCase == TextUtilities.LOWER_CASE)
1107          subst = subst.toLowerCase();
1108        else if(strCase == TextUtilities.UPPER_CASE)
1109          subst = subst.toUpperCase();
1110        else if(strCase == TextUtilities.TITLE_CASE)
1111          subst = TextUtilities.toTitleCase(subst);
1112      }
1113
1114      if(subst != null)
1115      {
1116        buffer.remove(offset + _start,_length);
1117        buffer.insert(offset + _start,subst);
1118        occurCount++;
1119        offset += _start + subst.length();
1120
1121        end += (subst.length() - found.length());
1122      }
1123      else
1124        offset += _start + _length;
1125    }
1126
1127    return occurCount;
1128  } //}}}
1129
1130  //{{{ _replace() method
1131  private static String _replace(SearchMatcher.Match occur, String found)
1132    throws Exception
1133  {
1134    if(regexp)
1135    {
1136      if(replaceMethod != null)
1137      {
1138        for(int i = 0; i < occur.substitutions.length; i++)
1139        {
1140          replaceNS.setVariable("_" + i,
1141            occur.substitutions[i]);
1142        }
1143
1144        Object obj = BeanShell.runCachedBlock(
1145          replaceMethod,null,replaceNS);
1146        if(obj == null)
1147          return "";
1148        else
1149          return obj.toString();
1150      }
1151      else
1152      {
1153        StringBuffer buf = new StringBuffer();
1154
1155        for(int i = 0; i < replace.length(); i++)
1156        {
1157          char ch = replace.charAt(i);
1158          switch(ch)
1159          {
1160          case '$':
1161            if(i == replace.length() - 1)
1162            {
1163              buf.append(ch);
1164              break;
1165            }
1166
1167            ch = replace.charAt(++i);
1168            if(ch == '$')
1169              buf.append('$');
1170            else if(ch == '0')
1171              buf.append(found);
1172            else if(Character.isDigit(ch))
1173            {
1174              int n = ch - '0';
1175              if(n < occur
1176                .substitutions
1177                .length)
1178              {
1179                buf.append(
1180                  occur
1181                  .substitutions
1182                  [n]
1183                );
1184              }
1185            }
1186            break;
1187          case '\\':
1188            if(i == replace.length() - 1)
1189            {
1190              buf.append('\\');
1191              break;
1192            }
1193            ch = replace.charAt(++i);
1194            switch(ch)
1195            {
1196            case 'n':
1197              buf.append('\n');
1198              break;
1199            case 't':
1200              buf.append('\t');
1201              break;
1202            default:
1203              buf.append(ch);
1204              break;
1205            }
1206            break;
1207          default:
1208            buf.append(ch);
1209            break;
1210          }
1211        }
1212
1213        return buf.toString();
1214      }
1215    }
1216    else
1217    {
1218      if(replaceMethod != null)
1219      {
1220        replaceNS.setVariable("_0",found);
1221        Object obj = BeanShell.runCachedBlock(
1222          replaceMethod,
1223          null,replaceNS);
1224        if(obj == null)
1225          return "";
1226        else
1227          return obj.toString();
1228      }
1229      else
1230      {
1231        return replace;
1232      }
1233    }
1234  } //}}}
1235
1236  //{{{ getColumnOnOtherLine() method
1237  /**
1238   * Should be somewhere else...
1239   */
1240  private static int getColumnOnOtherLine(Buffer buffer, int line,
1241    int col)
1242  {
1243    int returnValue = buffer.getOffsetOfVirtualColumn(
1244      line,col,null);
1245    if(returnValue == -1)
1246      return buffer.getLineEndOffset(line) - 1;
1247    else
1248      return buffer.getLineStartOffset(line) + returnValue;
1249  } //}}}
1250
1251  //}}}
1252}