KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > jedit > search > SearchAndReplace


1 /*
2  * SearchAndReplace.java - Search and replace
3  * :tabSize=8:indentSize=8:noTabs=false:
4  * :folding=explicit:collapseFolds=1:
5  *
6  * Copyright (C) 1999, 2004 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.*;
29 import javax.swing.JOptionPane JavaDoc;
30 import javax.swing.text.Segment JavaDoc;
31 import org.gjt.sp.jedit.*;
32 import org.gjt.sp.jedit.gui.TextAreaDialog;
33 import org.gjt.sp.jedit.io.VFSManager;
34 import org.gjt.sp.jedit.msg.SearchSettingsChanged;
35 import org.gjt.sp.jedit.textarea.*;
36 import org.gjt.sp.util.SegmentCharSequence;
37 import org.gjt.sp.util.Log;
38 //}}}
39

40 /**
41  * Class that implements regular expression and literal search within
42  * jEdit buffers.<p>
43  *
44  * There are two main groups of methods in this class:
45  * <ul>
46  * <li>Property accessors - for changing search and replace settings.</li>
47  * <li>Actions - for performing search and replace.</li>
48  * </ul>
49  *
50  * The "HyperSearch" and "Keep dialog" features, as reflected in
51  * checkbox options in the search dialog, are not handled from within
52  * this class. If you wish to have these options set before the search dialog
53  * appears, make a prior call to either or both of the following:
54  *
55  * <pre> jEdit.setBooleanProperty("search.hypersearch.toggle",true);
56  * jEdit.setBooleanProperty("search.keepDialog.toggle",true);</pre>
57  *
58  * If you are not using the dialog to undertake a search or replace, you may
59  * call any of the search and replace methods (including
60  * {@link #hyperSearch(View)}) without concern for the value of these
61  * properties.
62  *
63  * @author Slava Pestov
64  * @author John Gellene (API documentation)
65  * @version $Id: SearchAndReplace.java 8169 2006-12-02 21:44:59Z kpouer $
66  */

67 public class SearchAndReplace
68 {
69     //{{{ Getters and setters
70

71     //{{{ setSearchString() method
72
/**
73      * Sets the current search string.
74      * @param search The new search string
75      */

76     public static void setSearchString(String JavaDoc search)
77     {
78         if(search.equals(SearchAndReplace.search))
79             return;
80
81         SearchAndReplace.search = search;
82         matcher = null;
83
84         EditBus.send(new SearchSettingsChanged(null));
85     } //}}}
86

87     //{{{ getSearchString() method
88
/**
89      * Returns the current search string.
90      */

91     public static String JavaDoc getSearchString()
92     {
93         return search;
94     } //}}}
95

96     //{{{ setReplaceString() method
97
/**
98      * Sets the current replacement string.
99      * @param replace The new replacement string
100      */

101     public static void setReplaceString(String JavaDoc replace)
102     {
103         if(replace.equals(SearchAndReplace.replace))
104             return;
105
106         SearchAndReplace.replace = replace;
107
108         EditBus.send(new SearchSettingsChanged(null));
109     } //}}}
110

111     //{{{ getReplaceString() method
112
/**
113      * Returns the current replacement string.
114      */

115     public static String JavaDoc getReplaceString()
116     {
117         return replace;
118     } //}}}
119

120     //{{{ setIgnoreCase() method
121
/**
122      * Sets the ignore case flag.
123      * @param ignoreCase True if searches should be case insensitive,
124      * false otherwise
125      */

126     public static void setIgnoreCase(boolean ignoreCase)
127     {
128         if(ignoreCase == SearchAndReplace.ignoreCase)
129             return;
130
131         SearchAndReplace.ignoreCase = ignoreCase;
132         matcher = null;
133
134         EditBus.send(new SearchSettingsChanged(null));
135     } //}}}
136

137     //{{{ getIgnoreCase() method
138
/**
139      * Returns the state of the ignore case flag.
140      * @return True if searches should be case insensitive,
141      * false otherwise
142      */

143     public static boolean getIgnoreCase()
144     {
145         return ignoreCase;
146     } //}}}
147

148     //{{{ setRegexp() method
149
/**
150      * Sets the state of the regular expression flag.
151      * @param regexp True if regular expression searches should be
152      * performed
153      */

154     public static void setRegexp(boolean regexp)
155     {
156         if(regexp == SearchAndReplace.regexp)
157             return;
158
159         SearchAndReplace.regexp = regexp;
160         if(regexp && reverse)
161             reverse = false;
162
163         matcher = null;
164
165         EditBus.send(new SearchSettingsChanged(null));
166     } //}}}
167

168     //{{{ getRegexp() method
169
/**
170      * Returns the state of the regular expression flag.
171      * @return True if regular expression searches should be performed
172      */

173     public static boolean getRegexp()
174     {
175         return regexp;
176     } //}}}
177

178     //{{{ setReverseSearch() method
179
/**
180      * Determines whether a reverse search will conducted from the current
181      * position to the beginning of a buffer. Note that reverse search and
182      * regular expression search is mutually exclusive; enabling one will
183      * disable the other.
184      * @param reverse True if searches should go backwards,
185      * false otherwise
186      */

187     public static void setReverseSearch(boolean reverse)
188     {
189         if(reverse == SearchAndReplace.reverse)
190             return;
191
192         SearchAndReplace.reverse = reverse;
193
194         EditBus.send(new SearchSettingsChanged(null));
195     } //}}}
196

197     //{{{ getReverseSearch() method
198
/**
199      * Returns the state of the reverse search flag.
200      * @return True if searches should go backwards,
201      * false otherwise
202      */

203     public static boolean getReverseSearch()
204     {
205         return reverse;
206     } //}}}
207

208     //{{{ setBeanShellReplace() method
209
/**
210      * Sets the state of the BeanShell replace flag.
211      * @param beanshell True if the replace string is a BeanShell expression
212      * @since jEdit 3.2pre2
213      */

214     public static void setBeanShellReplace(boolean beanshell)
215     {
216         if(beanshell == SearchAndReplace.beanshell)
217             return;
218
219         SearchAndReplace.beanshell = beanshell;
220
221         EditBus.send(new SearchSettingsChanged(null));
222     } //}}}
223

224     //{{{ getBeanShellReplace() method
225
/**
226      * Returns the state of the BeanShell replace flag.
227      * @return True if the replace string is a BeanShell expression
228      * @since jEdit 3.2pre2
229      */

230     public static boolean getBeanShellReplace()
231     {
232         return beanshell;
233     } //}}}
234

235     //{{{ setAutoWrap() method
236
/**
237      * Sets the state of the auto wrap around flag.
238      * @param wrap If true, the 'continue search from start' dialog
239      * will not be displayed
240      * @since jEdit 3.2pre2
241      */

242     public static void setAutoWrapAround(boolean wrap)
243     {
244         if(wrap == SearchAndReplace.wrap)
245             return;
246
247         SearchAndReplace.wrap = wrap;
248
249         EditBus.send(new SearchSettingsChanged(null));
250     } //}}}
251

252     //{{{ getAutoWrap() method
253
/**
254      * Returns the state of the auto wrap around flag.
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      * @return a SearchMatcher or null if there is no search or if the matcher can match empty String
280      *
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 JavaDoc {
287         if (matcher != null)
288             return matcher;
289
290         if (search == null || "".equals(search))
291             return null;
292
293         if (regexp)
294             matcher = new PatternSearchMatcher(search, ignoreCase);
295         else
296             matcher = new BoyerMooreSearchMatcher(search, ignoreCase);
297
298         if (matcher.nextMatch("", true, true, true, false) != null) {
299             Log.log(Log.WARNING, SearchAndReplace.class, "The matcher " + matcher + " can match empty string !");
300             matcher = null;
301         }
302
303         return matcher;
304     } //}}}
305

306     //{{{ setSearchFileSet() method
307
/**
308      * Sets the current search file set.
309      * @param fileset The file set to perform searches in
310      * @see AllBufferSet
311      * @see CurrentBufferSet
312      * @see DirectoryListSet
313      */

314     public static void setSearchFileSet(SearchFileSet fileset)
315     {
316         SearchAndReplace.fileset = fileset;
317
318         EditBus.send(new SearchSettingsChanged(null));
319     } //}}}
320

321     //{{{ getSearchFileSet() method
322
/**
323      * Returns the current search file set.
324      */

325     public static SearchFileSet getSearchFileSet()
326     {
327         return fileset;
328     } //}}}
329

330     //{{{ getSmartCaseReplace() method
331
/**
332      * Returns if the replacement string will assume the same case as
333      * each specific occurrence of the search string.
334      * @since jEdit 4.2pre10
335      */

336     public static boolean getSmartCaseReplace()
337     {
338         return (replace != null
339             && TextUtilities.getStringCase(replace)
340             == TextUtilities.LOWER_CASE);
341     } //}}}
342

343     //}}}
344

345     //{{{ Actions
346

347     //{{{ hyperSearch() method
348
/**
349      * Performs a HyperSearch.
350      * @param view The view
351      * @since jEdit 2.7pre3
352      */

353     public static boolean hyperSearch(View view)
354     {
355         return hyperSearch(view,false);
356     } //}}}
357

358     //{{{ hyperSearch() method
359
/**
360      * Performs a HyperSearch.
361      * @param view The view
362      * @param selection If true, will only search in the current selection.
363      * Note that the file set must be the current buffer file set for this
364      * to work.
365      * @since jEdit 4.0pre1
366      */

367     public static boolean hyperSearch(View view, boolean selection)
368     {
369         // component that will parent any dialog boxes
370
Component comp = SearchDialog.getSearchDialog(view);
371         if(comp == null)
372             comp = view;
373
374         record(view,"hyperSearch(view," + selection + ')',false,
375             !selection);
376
377         view.getDockableWindowManager().addDockableWindow(
378             HyperSearchResults.NAME);
379         final HyperSearchResults results = (HyperSearchResults)
380             view.getDockableWindowManager()
381             .getDockable(HyperSearchResults.NAME);
382         results.searchStarted();
383
384         try
385         {
386             SearchMatcher matcher = getSearchMatcher();
387             if(matcher == null)
388             {
389                 view.getToolkit().beep();
390                 results.searchFailed();
391                 return false;
392             }
393
394             Selection[] s;
395             if(selection)
396             {
397                 s = view.getTextArea().getSelection();
398                 if(s == null)
399                 {
400                     results.searchFailed();
401                     return false;
402                 }
403             }
404             else
405                 s = null;
406             VFSManager.runInWorkThread(new HyperSearchRequest(view,
407                 matcher,results,s));
408             return true;
409         }
410         catch(Exception JavaDoc e)
411         {
412             results.searchFailed();
413             handleError(comp,e);
414             return false;
415         }
416     } //}}}
417

418     //{{{ find() method
419
/**
420      * Finds the next occurance of the search string.
421      * @param view The view
422      * @return True if the operation was successful, false otherwise
423      */

424     public static boolean find(View view)
425     {
426         // component that will parent any dialog boxes
427
Component comp = SearchDialog.getSearchDialog(view);
428         if(comp == null || !comp.isShowing())
429             comp = view;
430
431                 String JavaDoc path = fileset.getNextFile(view,null);
432         if(path == null)
433         {
434             GUIUtilities.error(comp,"empty-fileset",null);
435             return false;
436         }
437
438         boolean _reverse = reverse && fileset instanceof CurrentBufferSet;
439         if(_reverse && regexp)
440         {
441             GUIUtilities.error(comp,"regexp-reverse",null);
442             return false;
443         }
444
445         try
446         {
447             view.showWaitCursor();
448
449             SearchMatcher matcher = getSearchMatcher();
450             if(matcher == null)
451             {
452                 view.getToolkit().beep();
453                 return false;
454             }
455
456             record(view,"find(view)",false,true);
457
458                         boolean repeat = false;
459 loop: for(;;)
460             {
461                 while(path != null)
462                 {
463                     Buffer buffer = jEdit.openTemporary(
464                         view,null,path,false);
465
466                     /* this is stupid and misleading.
467                      * but 'path' is not used anywhere except
468                      * the above line, and if this is done
469                      * after the 'continue', then we will
470                      * either hang, or be forced to duplicate
471                      * it inside the buffer == null, or add
472                      * a 'finally' clause. you decide which one's
473                      * worse. */

474                     path = fileset.getNextFile(view,path);
475
476                     if(buffer == null)
477                         continue loop;
478
479                     // Wait for the buffer to load
480
if(!buffer.isLoaded())
481                         VFSManager.waitForRequests();
482
483                     int start;
484
485                     if(view.getBuffer() == buffer && !repeat)
486                     {
487                         JEditTextArea textArea = view.getTextArea();
488                         Selection s = textArea.getSelectionAtOffset(
489                             textArea.getCaretPosition());
490                         if(s == null)
491                             start = textArea.getCaretPosition();
492                         else if(_reverse)
493                             start = s.getStart();
494                         else
495                             start = s.getEnd();
496                     }
497                     else if(_reverse)
498                         start = buffer.getLength();
499                     else
500                         start = 0;
501
502                     boolean _search = true;
503                     if (!_reverse && matcher.isMatchingEOL())
504                     {
505                         if (start < buffer.getLength())
506                             start += 1;
507                         else
508                             _search = false;
509                     }
510
511                     if(_search && find(view,buffer,start,repeat,_reverse))
512                         return true;
513                 }
514
515                 if(repeat)
516                 {
517                     if(!BeanShell.isScriptRunning())
518                     {
519                         view.getStatus().setMessageAndClear(
520                             jEdit.getProperty("view.status.search-not-found"));
521
522                         view.getToolkit().beep();
523                     }
524                     return false;
525                 }
526
527                 boolean restart;
528
529                 // if auto wrap is on, always restart search.
530
// if auto wrap is off, and we're called from
531
// a macro, stop search. If we're called
532
// interactively, ask the user what to do.
533
if(wrap)
534                 {
535                     if(!BeanShell.isScriptRunning())
536                     {
537                         view.getStatus().setMessageAndClear(
538                             jEdit.getProperty("view.status.auto-wrap"));
539                         // beep if beep property set
540
if(jEdit.getBooleanProperty("search.beepOnSearchAutoWrap"))
541                         {
542                             view.getToolkit().beep();
543                         }
544                     }
545                     restart = true;
546                 }
547                 else if(BeanShell.isScriptRunning())
548                 {
549                     restart = false;
550                 }
551                 else
552                 {
553                     Integer JavaDoc[] args = {Integer.valueOf(_reverse ? 1 : 0)};
554                     int result = GUIUtilities.confirm(comp,
555                         "keepsearching",args,
556                         JOptionPane.YES_NO_OPTION,
557                         JOptionPane.QUESTION_MESSAGE);
558                     restart = (result == JOptionPane.YES_OPTION);
559                 }
560
561                 if(restart)
562                 {
563                     // start search from beginning
564
path = fileset.getFirstFile(view);
565                     repeat = true;
566                 }
567                 else
568                     break loop;
569             }
570         }
571         catch(Exception JavaDoc e)
572         {
573             handleError(comp,e);
574         }
575         finally
576         {
577             view.hideWaitCursor();
578         }
579
580         return false;
581     } //}}}
582

583     //{{{ find() method
584
/**
585      * Finds the next instance of the search string in the specified
586      * buffer.
587      * @param view The view
588      * @param buffer The buffer
589      * @param start Location where to start the search
590      */

591     public static boolean find(View view, Buffer buffer, int start)
592         throws Exception JavaDoc
593     {
594         return find(view,buffer,start,false,false);
595     } //}}}
596

597     //{{{ find() method
598
/**
599      * Finds the next instance of the search string in the specified
600      * buffer.
601      * @param view The view
602      * @param buffer The buffer
603      * @param start Location where to start the search
604      * @param firstTime See {@link SearchMatcher#nextMatch(CharSequence,boolean,boolean,boolean,boolean)}.
605      * @since jEdit 4.1pre7
606      */

607     public static boolean find(View view, Buffer buffer, int start,
608         boolean firstTime, boolean reverse) throws Exception JavaDoc
609     {
610         SearchMatcher matcher = getSearchMatcher();
611         if(matcher == null)
612         {
613             view.getToolkit().beep();
614             return false;
615         }
616
617         Segment JavaDoc text = new Segment JavaDoc();
618         if(reverse)
619             buffer.getText(0,start,text);
620         else
621             buffer.getText(start,buffer.getLength() - start,text);
622
623         // the start and end flags will be wrong with reverse search enabled,
624
// but they are only used by the regexp matcher, which doesn't
625
// support reverse search yet.
626
//
627
// REMIND: fix flags when adding reverse regexp search.
628
SearchMatcher.Match match = matcher.nextMatch(new SegmentCharSequence(text,reverse),
629             start == 0,true,firstTime,reverse);
630
631         if(match != null)
632         {
633             jEdit.commitTemporary(buffer);
634             view.setBuffer(buffer);
635             JEditTextArea textArea = view.getTextArea();
636
637             if(reverse)
638             {
639                 textArea.setSelection(new Selection.Range(
640                     start - match.end,
641                     start - match.start));
642                 // make sure end of match is visible
643
textArea.scrollTo(start - match.start,false);
644                 textArea.moveCaretPosition(start - match.end);
645             }
646             else
647             {
648                 textArea.setSelection(new Selection.Range(
649                     start + match.start,
650                     start + match.end));
651                 textArea.moveCaretPosition(start + match.end);
652                 // make sure start of match is visible
653
textArea.scrollTo(start + match.start,false);
654             }
655
656             return true;
657         }
658         else
659             return false;
660     } //}}}
661

662     //{{{ replace() method
663
/**
664      * Replaces the current selection with the replacement string.
665      * @param view The view
666      * @return True if the operation was successful, false otherwise
667      */

668     public static boolean replace(View view)
669     {
670         // component that will parent any dialog boxes
671
Component comp = SearchDialog.getSearchDialog(view);
672         if(comp == null)
673             comp = view;
674
675         JEditTextArea textArea = view.getTextArea();
676
677         Buffer buffer = view.getBuffer();
678         if(!buffer.isEditable())
679             return false;
680
681         boolean smartCaseReplace = getSmartCaseReplace();
682
683         Selection[] selection = textArea.getSelection();
684         if (selection.length == 0)
685         {
686             try
687             {
688                 SearchMatcher matcher = getSearchMatcher();
689                 if ((matcher != null) && (matcher.isMatchingEOL()))
690                 {
691                     int caretPosition = textArea.getCaretPosition();
692                     selection = new Selection[] { new Selection.Range(caretPosition,caretPosition) };
693                 }
694                 else
695                 {
696                     view.getToolkit().beep();
697                     return false;
698                 }
699             }
700             catch (Exception JavaDoc e)
701             {
702                 handleError(comp,e);
703                 return false;
704             }
705         }
706
707         record(view,"replace(view)",true,false);
708
709         // a little hack for reverse replace and find
710
int caret = textArea.getCaretPosition();
711         Selection s = textArea.getSelectionAtOffset(caret);
712         if(s != null)
713             caret = s.getStart();
714
715         try
716         {
717             buffer.beginCompoundEdit();
718
719             SearchMatcher matcher = getSearchMatcher();
720             if(matcher == null)
721                 return false;
722
723             initReplace();
724
725             int retVal = 0;
726
727             for(int i = 0; i < selection.length; i++)
728             {
729                 s = selection[i];
730
731                 retVal += replaceInSelection(view,textArea,
732                     buffer,matcher,smartCaseReplace,s);
733             }
734
735             boolean _reverse = !regexp && reverse && fileset instanceof CurrentBufferSet;
736             if(_reverse)
737             {
738                 // so that Replace and Find continues from
739
// the right location
740
textArea.moveCaretPosition(caret);
741             }
742             else
743             {
744                 s = textArea.getSelectionAtOffset(
745                     textArea.getCaretPosition());
746                 if(s != null)
747                     textArea.moveCaretPosition(s.getEnd());
748             }
749
750             if(!BeanShell.isScriptRunning())
751             {
752                 Object JavaDoc[] args = {Integer.valueOf(retVal),
753                                         Integer.valueOf(1)};
754                 view.getStatus().setMessageAndClear(jEdit.getProperty(
755                     "view.status.replace-all",args));
756             }
757
758             if(retVal == 0)
759             {
760                 view.getToolkit().beep();
761                 return false;
762             }
763
764             return true;
765         }
766         catch(Exception JavaDoc e)
767         {
768             handleError(comp,e);
769         }
770         finally
771         {
772             buffer.endCompoundEdit();
773         }
774
775         return false;
776     } //}}}
777

778     //{{{ replace() method
779
/**
780      * Replaces text in the specified range with the replacement string.
781      * @param view The view
782      * @param buffer The buffer
783      * @param start The start offset
784      * @param end The end offset
785      * @return True if the operation was successful, false otherwise
786      */

787     public static boolean replace(View view, Buffer buffer, int start, int end)
788     {
789         if(!buffer.isEditable())
790             return false;
791
792         // component that will parent any dialog boxes
793
Component comp = SearchDialog.getSearchDialog(view);
794         if(comp == null)
795             comp = view;
796
797         boolean smartCaseReplace = getSmartCaseReplace();
798
799         try
800         {
801             buffer.beginCompoundEdit();
802
803             SearchMatcher matcher = getSearchMatcher();
804             if(matcher == null)
805                 return false;
806
807             initReplace();
808
809             int retVal = 0;
810
811             retVal += _replace(view,buffer,matcher,start,end,
812                 smartCaseReplace);
813
814             if(retVal != 0)
815                 return true;
816         }
817         catch(Exception JavaDoc e)
818         {
819             handleError(comp,e);
820         }
821         finally
822         {
823             buffer.endCompoundEdit();
824         }
825
826         return false;
827     } //}}}
828

829     //{{{ replaceAll() method
830
/**
831      * Replaces all occurances of the search string with the replacement
832      * string.
833      * @param view The view
834      */

835     public static boolean replaceAll(View view)
836     {
837         // component that will parent any dialog boxes
838
Component comp = SearchDialog.getSearchDialog(view);
839         if(comp == null)
840             comp = view;
841
842                 if(fileset.getFileCount(view) == 0)
843         {
844             GUIUtilities.error(comp,"empty-fileset",null);
845             return false;
846         }
847
848         record(view,"replaceAll(view)",true,true);
849
850         view.showWaitCursor();
851
852         boolean smartCaseReplace = (replace != null
853             && TextUtilities.getStringCase(replace)
854             == TextUtilities.LOWER_CASE);
855
856                 int fileCount = 0;
857                 int occurCount = 0;
858                 try
859         {
860             SearchMatcher matcher = getSearchMatcher();
861             if(matcher == null)
862                 return false;
863
864             initReplace();
865
866             String JavaDoc path = fileset.getFirstFile(view);
867 loop: while(path != null)
868             {
869                 Buffer buffer = jEdit.openTemporary(
870                     view,null,path,false);
871
872                 /* this is stupid and misleading.
873                  * but 'path' is not used anywhere except
874                  * the above line, and if this is done
875                  * after the 'continue', then we will
876                  * either hang, or be forced to duplicate
877                  * it inside the buffer == null, or add
878                  * a 'finally' clause. you decide which one's
879                  * worse. */

880                 path = fileset.getNextFile(view,path);
881
882                 if(buffer == null)
883                     continue loop;
884
885                 // Wait for buffer to finish loading
886
if(buffer.isPerformingIO())
887                     VFSManager.waitForRequests();
888
889                 if(!buffer.isEditable())
890                     continue loop;
891
892                 // Leave buffer in a consistent state if
893
// an error occurs
894
int retVal = 0;
895
896                 try
897                 {
898                     buffer.beginCompoundEdit();
899                     retVal = _replace(view,buffer,matcher,
900                         0,buffer.getLength(),
901                         smartCaseReplace);
902                 }
903                 finally
904                 {
905                     buffer.endCompoundEdit();
906                 }
907
908                 if(retVal != 0)
909                 {
910                     fileCount++;
911                     occurCount += retVal;
912                     jEdit.commitTemporary(buffer);
913                 }
914             }
915         }
916         catch(Exception JavaDoc e)
917         {
918             handleError(comp,e);
919         }
920         finally
921         {
922             view.hideWaitCursor();
923         }
924
925         /* Don't do this when playing a macro, cos it's annoying */
926         if(!BeanShell.isScriptRunning())
927         {
928             Object JavaDoc[] args = {Integer.valueOf(occurCount),
929                                 Integer.valueOf(fileCount)};
930             view.getStatus().setMessageAndClear(jEdit.getProperty(
931                 "view.status.replace-all",args));
932             if(occurCount == 0)
933                 view.getToolkit().beep();
934         }
935
936         return (fileCount != 0);
937     } //}}}
938

939     //}}}
940

941     //{{{ escapeRegexp() method
942
/**
943      * Escapes characters with special meaning in a regexp.
944      * @param multiline Should \n be escaped?
945      * @since jEdit 4.3pre1
946      */

947     public static String JavaDoc escapeRegexp(String JavaDoc str, boolean multiline)
948     {
949         return MiscUtilities.charsToEscapes(str,
950             "\r\t\\()[]{}$^*+?|."
951             + (multiline ? "" : "\n"));
952     } //}}}
953

954     //{{{ load() method
955
/**
956      * Loads search and replace state from the properties.
957      */

958     public static void load()
959     {
960         search = jEdit.getProperty("search.find.value");
961         replace = jEdit.getProperty("search.replace.value");
962         ignoreCase = jEdit.getBooleanProperty("search.ignoreCase.toggle");
963         regexp = jEdit.getBooleanProperty("search.regexp.toggle");
964         beanshell = jEdit.getBooleanProperty("search.beanshell.toggle");
965         wrap = jEdit.getBooleanProperty("search.wrap.toggle");
966
967         fileset = new CurrentBufferSet();
968
969         // Tags plugin likes to call this method at times other than
970
// startup; so we need to fire a SearchSettingsChanged to
971
// notify the search bar and so on.
972
matcher = null;
973         EditBus.send(new SearchSettingsChanged(null));
974     } //}}}
975

976     //{{{ save() method
977
/**
978      * Saves search and replace state to the properties.
979      */

980     public static void save()
981     {
982         jEdit.setProperty("search.find.value",search);
983         jEdit.setProperty("search.replace.value",replace);
984         jEdit.setBooleanProperty("search.ignoreCase.toggle",ignoreCase);
985         jEdit.setBooleanProperty("search.regexp.toggle",regexp);
986         jEdit.setBooleanProperty("search.beanshell.toggle",beanshell);
987         jEdit.setBooleanProperty("search.wrap.toggle",wrap);
988     } //}}}
989

990     //{{{ handleError() method
991
static void handleError(Component comp, Exception JavaDoc e)
992     {
993         Log.log(Log.ERROR,SearchAndReplace.class,e);
994         if(comp instanceof Dialog)
995         {
996             new TextAreaDialog((Dialog)comp,
997                 beanshell ? "searcherror-bsh"
998                 : "searcherror",e);
999         }
1000        else
1001        {
1002            new TextAreaDialog((Frame)comp,
1003                beanshell ? "searcherror-bsh"
1004                : "searcherror",e);
1005        }
1006    } //}}}
1007

1008    //{{{ Private members
1009

1010    //{{{ Instance variables
1011
private static String JavaDoc search;
1012    private static String JavaDoc replace;
1013    private static BshMethod replaceMethod;
1014    private static NameSpace replaceNS = new NameSpace(
1015        BeanShell.getNameSpace(),
1016        BeanShell.getNameSpace().getClassManager(),
1017        "search and replace");
1018    private static boolean regexp;
1019    private static boolean ignoreCase;
1020    private static boolean reverse;
1021    private static boolean beanshell;
1022    private static boolean wrap;
1023    private static SearchMatcher matcher;
1024    private static SearchFileSet fileset;
1025    //}}}
1026

1027    //{{{ initReplace() method
1028
/**
1029     * Set up BeanShell replace if necessary.
1030     */

1031    private static void initReplace() throws Exception JavaDoc
1032    {
1033        if(beanshell && replace.length() != 0)
1034        {
1035            replaceMethod = BeanShell.cacheBlock("replace",
1036                "return (" + replace + ");",true);
1037        }
1038        else
1039            replaceMethod = null;
1040    } //}}}
1041

1042    //{{{ record() method
1043
private static void record(View view, String JavaDoc action,
1044        boolean replaceAction, boolean recordFileSet)
1045    {
1046        Macros.Recorder recorder = view.getMacroRecorder();
1047
1048        if(recorder != null)
1049        {
1050            recorder.record("SearchAndReplace.setSearchString(\""
1051                + MiscUtilities.charsToEscapes(search) + "\");");
1052
1053            if(replaceAction)
1054            {
1055                recorder.record("SearchAndReplace.setReplaceString(\""
1056                    + MiscUtilities.charsToEscapes(replace) + "\");");
1057                recorder.record("SearchAndReplace.setBeanShellReplace("
1058                    + beanshell + ");");
1059            }
1060            else
1061            {
1062                // only record this if doing a find next
1063
recorder.record("SearchAndReplace.setAutoWrapAround("
1064                    + wrap + ");");
1065                recorder.record("SearchAndReplace.setReverseSearch("
1066                    + reverse + ");");
1067            }
1068
1069            recorder.record("SearchAndReplace.setIgnoreCase("
1070                + ignoreCase + ");");
1071            recorder.record("SearchAndReplace.setRegexp("
1072                + regexp + ");");
1073
1074            if(recordFileSet)
1075            {
1076                recorder.record("SearchAndReplace.setSearchFileSet("
1077                    + fileset.getCode() + ");");
1078            }
1079
1080            recorder.record("SearchAndReplace." + action + ';');
1081        }
1082    } //}}}
1083

1084    //{{{ replaceInSelection() method
1085
private static int replaceInSelection(View view, JEditTextArea textArea,
1086        Buffer buffer, SearchMatcher matcher, boolean smartCaseReplace,
1087        Selection s) throws Exception JavaDoc
1088    {
1089        /* if an occurence occurs at the
1090        beginning of the selection, the
1091        selection start will get moved.
1092        this sucks, so we hack to avoid it. */

1093        int start = s.getStart();
1094
1095        int returnValue;
1096
1097        if(s instanceof Selection.Range)
1098        {
1099            returnValue = _replace(view,buffer,matcher,
1100                s.getStart(),s.getEnd(),
1101                smartCaseReplace);
1102
1103            textArea.removeFromSelection(s);
1104            textArea.addToSelection(new Selection.Range(
1105                start,s.getEnd()));
1106        }
1107        else if(s instanceof Selection.Rect)
1108        {
1109            Selection.Rect rect = (Selection.Rect)s;
1110            int startCol = rect.getStartColumn(
1111                buffer);
1112            int endCol = rect.getEndColumn(
1113                buffer);
1114
1115            returnValue = 0;
1116            for(int j = s.getStartLine(); j <= s.getEndLine(); j++)
1117            {
1118                returnValue += _replace(view,buffer,matcher,
1119                    getColumnOnOtherLine(buffer,j,startCol),
1120                    getColumnOnOtherLine(buffer,j,endCol),
1121                    smartCaseReplace);
1122            }
1123            textArea.addToSelection(new Selection.Rect(
1124                start,s.getEnd()));
1125        }
1126        else
1127            throw new RuntimeException JavaDoc("Unsupported: " + s);
1128
1129        return returnValue;
1130    } //}}}
1131

1132    //{{{ _replace() method
1133
/**
1134     * Replaces all occurances of the search string with the replacement
1135     * string.
1136     * @param view The view
1137     * @param buffer The buffer
1138     * @param start The start offset
1139     * @param end The end offset
1140     * @param matcher The search matcher to use
1141     * @param smartCaseReplace See user's guide
1142     * @return The number of occurrences replaced
1143     */

1144    private static int _replace(View view, Buffer buffer,
1145        SearchMatcher matcher, int start, int end,
1146        boolean smartCaseReplace)
1147        throws Exception JavaDoc
1148    {
1149        int occurCount = 0;
1150
1151        boolean endOfLine = (buffer.getLineEndOffset(
1152            buffer.getLineOfOffset(end)) - 1 == end);
1153
1154        Segment JavaDoc text = new Segment JavaDoc();
1155        int offset = start;
1156loop: for(int counter = 0; ; counter++)
1157        {
1158            buffer.getText(offset,end - offset,text);
1159
1160            boolean startOfLine = (buffer.getLineStartOffset(
1161                buffer.getLineOfOffset(offset)) == offset);
1162
1163            SearchMatcher.Match occur = matcher.nextMatch(
1164                new SegmentCharSequence(text,false),
1165                startOfLine,endOfLine,counter == 0,
1166                false);
1167            if(occur == null)
1168                break loop;
1169
1170            String JavaDoc found = new String JavaDoc(text.array,
1171                text.offset + occur.start,
1172                occur.end - occur.start);
1173
1174            int length = replaceOne(view,buffer,occur,offset,
1175                found,smartCaseReplace);
1176            if(length == -1)
1177                offset += occur.end;
1178            else
1179            {
1180                offset += occur.start + length;
1181                end += (length - found.length());
1182                occurCount++;
1183            }
1184
1185            if (matcher.isMatchingEOL())
1186            {
1187                if (offset < buffer.getLength())
1188                    offset += 1;
1189                else
1190                    break loop;
1191
1192                if (offset >= end)
1193                    break loop;
1194            }
1195        }
1196
1197        return occurCount;
1198    } //}}}
1199

1200    //{{{ replaceOne() method
1201
/**
1202     * Replace one occurrence of the search string with the
1203     * replacement string.
1204     */

1205    private static int replaceOne(View view, Buffer buffer,
1206        SearchMatcher.Match occur, int offset, String JavaDoc found,
1207        boolean smartCaseReplace)
1208        throws Exception JavaDoc
1209    {
1210        String JavaDoc subst = replaceOne(view,occur,found);
1211        if(smartCaseReplace && ignoreCase)
1212        {
1213            int strCase = TextUtilities.getStringCase(found);
1214            if(strCase == TextUtilities.LOWER_CASE)
1215                subst = subst.toLowerCase();
1216            else if(strCase == TextUtilities.UPPER_CASE)
1217                subst = subst.toUpperCase();
1218            else if(strCase == TextUtilities.TITLE_CASE)
1219                subst = TextUtilities.toTitleCase(subst);
1220        }
1221
1222        if(subst != null)
1223        {
1224            int start = offset + occur.start;
1225            int end = offset + occur.end;
1226
1227            if (end - start > 0)
1228                buffer.remove(start,end - start);
1229            buffer.insert(start,subst);
1230            return subst.length();
1231        }
1232        else
1233            return -1;
1234    } //}}}
1235

1236    //{{{ replaceOne() method
1237
private static String JavaDoc replaceOne(View view,
1238        SearchMatcher.Match occur, String JavaDoc found)
1239        throws Exception JavaDoc
1240    {
1241        if(regexp)
1242        {
1243            if(replaceMethod != null)
1244                return regexpBeanShellReplace(view,occur);
1245            else
1246                return regexpReplace(occur,found);
1247        }
1248        else
1249        {
1250            if(replaceMethod != null)
1251                return literalBeanShellReplace(view,found);
1252            else
1253                return replace;
1254        }
1255    } //}}}
1256

1257    //{{{ regexpBeanShellReplace() method
1258
private static String JavaDoc regexpBeanShellReplace(View view,
1259        SearchMatcher.Match occur) throws Exception JavaDoc
1260    {
1261        for(int i = 0; i < occur.substitutions.length; i++)
1262        {
1263            replaceNS.setVariable("_" + i,
1264                occur.substitutions[i]);
1265        }
1266
1267        Object JavaDoc obj = BeanShell.runCachedBlock(
1268            replaceMethod,view,replaceNS);
1269        if(obj == null)
1270            return "";
1271        else
1272            return obj.toString();
1273    } //}}}
1274

1275    //{{{ regexpReplace() method
1276
private static String JavaDoc regexpReplace(SearchMatcher.Match occur,
1277        String JavaDoc found) throws Exception JavaDoc
1278    {
1279        StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
1280
1281        for(int i = 0; i < replace.length(); i++)
1282        {
1283            char ch = replace.charAt(i);
1284            switch(ch)
1285            {
1286            case '$':
1287                if(i == replace.length() - 1)
1288                {
1289                    buf.append(ch);
1290                    break;
1291                }
1292
1293                ch = replace.charAt(++i);
1294                if(ch == '$')
1295                    buf.append('$');
1296                else if(ch == '0')
1297                    buf.append(found);
1298                else if(Character.isDigit(ch))
1299                {
1300                    int n = ch - '0';
1301                    if(n < occur
1302                        .substitutions
1303                        .length)
1304                    {
1305                        buf.append(
1306                            occur
1307                            .substitutions
1308                            [n]
1309                        );
1310                    }
1311                }
1312                break;
1313            case '\\':
1314                if(i == replace.length() - 1)
1315                {
1316                    buf.append('\\');
1317                    break;
1318                }
1319                ch = replace.charAt(++i);
1320                switch(ch)
1321                {
1322                case 'n':
1323                    buf.append('\n');
1324                    break;
1325                case 't':
1326                    buf.append('\t');
1327                    break;
1328                default:
1329                    buf.append(ch);
1330                    break;
1331                }
1332                break;
1333            default:
1334                buf.append(ch);
1335                break;
1336            }
1337        }
1338
1339        return buf.toString();
1340    } //}}}
1341

1342    //{{{ literalBeanShellReplace() method
1343
private static String JavaDoc literalBeanShellReplace(View view, String JavaDoc found)
1344        throws Exception JavaDoc
1345    {
1346        replaceNS.setVariable("_0",found);
1347        Object JavaDoc obj = BeanShell.runCachedBlock(
1348            replaceMethod,
1349            view,replaceNS);
1350        if(obj == null)
1351            return "";
1352        else
1353            return obj.toString();
1354    } //}}}
1355

1356    //{{{ getColumnOnOtherLine() method
1357
/**
1358     * Should be somewhere else...
1359     */

1360    private static int getColumnOnOtherLine(Buffer buffer, int line,
1361        int col)
1362    {
1363        int returnValue = buffer.getOffsetOfVirtualColumn(
1364            line,col,null);
1365        if(returnValue == -1)
1366            return buffer.getLineEndOffset(line) - 1;
1367        else
1368            return buffer.getLineStartOffset(line) + returnValue;
1369    } //}}}
1370

1371    //}}}
1372
}
1373
Popular Tags