PageRenderTime 471ms CodeModel.GetById 410ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-0-pre3/org/gjt/sp/jedit/search/SearchAndReplace.java

#
Java | 955 lines | 574 code | 124 blank | 257 comment | 120 complexity | 8ddc422193a31ec9842a51d3f27a30e8 MD5 | raw file
  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 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
 24package org.gjt.sp.jedit.search;
 25
 26//{{{ Imports
 27import javax.swing.text.Segment;
 28import javax.swing.JOptionPane;
 29import java.awt.Component;
 30import org.gjt.sp.jedit.io.VFSManager;
 31import org.gjt.sp.jedit.msg.SearchSettingsChanged;
 32import org.gjt.sp.jedit.textarea.*;
 33import org.gjt.sp.jedit.*;
 34import org.gjt.sp.util.Log;
 35//}}}
 36
 37/**
 38 * Class that implements regular expression and literal search within
 39 * jEdit buffers.
 40 * @author Slava Pestov
 41 * @version $Id: SearchAndReplace.java 3933 2001-12-03 10:52:27Z spestov $
 42 */
 43public class SearchAndReplace
 44{
 45	//{{{ Getters and setters
 46
 47	//{{{ setSearchString() method
 48	/**
 49	 * Sets the current search string.
 50	 * @param search The new search string
 51	 */
 52	public static void setSearchString(String search)
 53	{
 54		if(search.equals(SearchAndReplace.search))
 55			return;
 56
 57		SearchAndReplace.search = search;
 58		matcher = null;
 59
 60		EditBus.send(new SearchSettingsChanged(null));
 61	} //}}}
 62
 63	//{{{ getSearchString() method
 64	/**
 65	 * Returns the current search string.
 66	 */
 67	public static String getSearchString()
 68	{
 69		return search;
 70	} //}}}
 71
 72	//{{{ setReplaceString() method
 73	/**
 74	 * Sets the current replacement string.
 75	 * @param search The new replacement string
 76	 */
 77	public static void setReplaceString(String replace)
 78	{
 79		if(replace.equals(SearchAndReplace.replace))
 80			return;
 81
 82		SearchAndReplace.replace = replace;
 83		matcher = null;
 84
 85		EditBus.send(new SearchSettingsChanged(null));
 86	} //}}}
 87
 88	//{{{ getRepalceString() method
 89	/**
 90	 * Returns the current replacement string.
 91	 */
 92	public static String getReplaceString()
 93	{
 94		return replace;
 95	} //}}}
 96
 97	//{{{ setIgnoreCase() method
 98	/**
 99	 * Sets the ignore case flag.
100	 * @param ignoreCase True if searches should be case insensitive,
101	 * false otherwise
102	 */
103	public static void setIgnoreCase(boolean ignoreCase)
104	{
105		if(ignoreCase == SearchAndReplace.ignoreCase)
106			return;
107
108		SearchAndReplace.ignoreCase = ignoreCase;
109		matcher = null;
110
111		EditBus.send(new SearchSettingsChanged(null));
112	} //}}}
113
114	//{{{ getIgnoreCase() method
115	/**
116	 * Returns the state of the ignore case flag.
117	 * @return True if searches should be case insensitive,
118	 * false otherwise
119	 */
120	public static boolean getIgnoreCase()
121	{
122		return ignoreCase;
123	} //}}}
124
125	//{{{ setRegexp() method
126	/**
127	 * Sets the state of the regular expression flag.
128	 * @param regexp True if regular expression searches should be
129	 * performed
130	 */
131	public static void setRegexp(boolean regexp)
132	{
133		if(regexp == SearchAndReplace.regexp)
134			return;
135
136		SearchAndReplace.regexp = regexp;
137		matcher = null;
138
139		EditBus.send(new SearchSettingsChanged(null));
140	} //}}}
141
142	//{{{ getRegexp() method
143	/**
144	 * Returns the state of the regular expression flag.
145	 * @return True if regular expression searches should be performed
146	 */
147	public static boolean getRegexp()
148	{
149		return regexp;
150	} //}}}
151
152	//{{{ setReverseSearch() method
153	/**
154	 * Sets the reverse search flag. Note that currently, only literal
155	 * reverse searches are supported.
156	 * @param reverse True if searches should go backwards,
157	 * false otherwise
158	 */
159	public static void setReverseSearch(boolean reverse)
160	{
161		if(reverse == SearchAndReplace.reverse)
162			return;
163
164		SearchAndReplace.reverse = reverse;
165
166		matcher = null;
167
168		EditBus.send(new SearchSettingsChanged(null));
169	} //}}}
170
171	//{{{ getReverseSearch() method
172	/**
173	 * Returns the state of the reverse search flag.
174	 * @return True if searches should go backwards,
175	 * false otherwise
176	 */
177	public static boolean getReverseSearch()
178	{
179		return reverse;
180	} //}}}
181
182	//{{{ setBeanShellReplace() method
183	/**
184	 * Sets the state of the BeanShell replace flag.
185	 * @param regexp True if the replace string is a BeanShell expression
186	 * @since jEdit 3.2pre2
187	 */
188	public static void setBeanShellReplace(boolean beanshell)
189	{
190		if(beanshell == SearchAndReplace.beanshell)
191			return;
192
193		SearchAndReplace.beanshell = beanshell;
194		matcher = null;
195
196		EditBus.send(new SearchSettingsChanged(null));
197	} //}}}
198
199	//{{{ getBeanShellReplace() method
200	/**
201	 * Returns the state of the BeanShell replace flag.
202	 * @return True if the replace string is a BeanShell expression
203	 * @since jEdit 3.2pre2
204	 */
205	public static boolean getBeanShellReplace()
206	{
207		return beanshell;
208	} //}}}
209
210	//{{{ setAutoWrap() method
211	/**
212	 * Sets the state of the auto wrap around flag.
213	 * @param wrap If true, the 'continue search from start' dialog
214	 * will not be displayed
215	 * @since jEdit 3.2pre2
216	 */
217	public static void setAutoWrapAround(boolean wrap)
218	{
219		if(wrap == SearchAndReplace.wrap)
220			return;
221
222		SearchAndReplace.wrap = wrap;
223
224		EditBus.send(new SearchSettingsChanged(null));
225	} //}}}
226
227	//{{{ getAutoWrap() method
228	/**
229	 * Returns the state of the auto wrap around flag.
230	 * @param wrap If true, the 'continue search from start' dialog
231	 * will not be displayed
232	 * @since jEdit 3.2pre2
233	 */
234	public static boolean getAutoWrapAround()
235	{
236		return wrap;
237	} //}}}
238
239	//{{{ setSearchMatcher() method
240	/**
241	 * Sets the current search string matcher. Note that calling
242	 * <code>setSearchString</code>, <code>setReplaceString</code>,
243	 * <code>setIgnoreCase</code> or <code>setRegExp</code> will
244	 * reset the matcher to the default.
245	 */
246	public static void setSearchMatcher(SearchMatcher matcher)
247	{
248		SearchAndReplace.matcher = matcher;
249
250		EditBus.send(new SearchSettingsChanged(null));
251	} //}}}
252
253	//{{{ getSearchMatcher() method
254	/**
255	 * Returns the current search string matcher.
256	 * @exception IllegalArgumentException if regular expression search
257	 * is enabled, the search string or replacement string is invalid
258	 */
259	public static SearchMatcher getSearchMatcher()
260		throws Exception
261	{
262		return getSearchMatcher(true);
263	} //}}}
264
265	//{{{ getSearchMatcher() method
266	/**
267	 * Returns the current search string matcher.
268	 * @param reverseOK Replacement commands need a non-reversed matcher,
269	 * so they set this to false
270	 * @exception IllegalArgumentException if regular expression search
271	 * is enabled, the search string or replacement string is invalid
272	 */
273	public static SearchMatcher getSearchMatcher(boolean reverseOK)
274		throws Exception
275	{
276		reverseOK &= (fileset instanceof CurrentBufferSet);
277
278		if(matcher != null && (reverseOK || !reverse))
279			return matcher;
280
281		if(search == null || "".equals(search))
282			return null;
283
284		// replace must not be null
285		String replace = (SearchAndReplace.replace == null ? "" : SearchAndReplace.replace);
286
287		String replaceMethod;
288		if(beanshell && replace.length() != 0)
289		{
290			replaceMethod = BeanShell.cacheBlock("replace","return ("
291				+ replace + ");",false);
292		}
293		else
294			replaceMethod = null;
295
296		if(regexp)
297			matcher = new RESearchMatcher(search,replace,ignoreCase,
298				beanshell,replaceMethod);
299		else
300		{
301			matcher = new BoyerMooreSearchMatcher(search,replace,
302				ignoreCase,reverse && reverseOK,beanshell,
303				replaceMethod);
304		}
305
306		return matcher;
307	} //}}}
308
309	//{{{ setSearchFileSet() method
310	/**
311	 * Sets the current search file set.
312	 * @param fileset The file set to perform searches in
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	//}}}
331
332	//{{{ Actions
333
334	//{{{ hyperSearch() method
335	/**
336	 * Performs a HyperSearch.
337	 * @param view The view
338	 * @since jEdit 2.7pre3
339	 */
340	public static boolean hyperSearch(View view)
341	{
342		return hyperSearch(view,false);
343	} //}}}
344
345	//{{{ hyperSearch() method
346	/**
347	 * Performs a HyperSearch.
348	 * @param view The view
349	 * @param selection If true, will only search in the current selection.
350	 * Note that the file set must be the current buffer file set for this
351	 * to work.
352	 * @since jEdit 4.0pre1
353	 */
354	public static boolean hyperSearch(View view, boolean selection)
355	{
356		record(view,"hyperSearch(view," + selection + ")",false,
357			!selection);
358
359		view.getDockableWindowManager().addDockableWindow(
360			HyperSearchResults.NAME);
361		final HyperSearchResults results = (HyperSearchResults)
362			view.getDockableWindowManager()
363			.getDockable(HyperSearchResults.NAME);
364		results.searchStarted();
365
366		try
367		{
368			Selection[] s;
369			if(selection)
370			{
371				s = view.getTextArea().getSelection();
372				if(s == null)
373					return false;
374			}
375			else
376				s = null;
377			VFSManager.runInWorkThread(new HyperSearchRequest(view,
378				getSearchMatcher(false),results,s));
379			return true;
380		}
381		catch(Exception e)
382		{
383			Log.log(Log.ERROR,SearchAndReplace.class,e);
384			Object[] args = { e.getMessage() };
385			if(args[0] == null)
386				args[0] = e.toString();
387			GUIUtilities.error(view,"searcherror",args);
388			return false;
389		}
390	} //}}}
391
392	//{{{ find() method
393	/**
394	 * Finds the next occurance of the search string.
395	 * @param view The view
396	 * @return True if the operation was successful, false otherwise
397	 */
398	public static boolean find(View view)
399	{
400		boolean repeat = false;
401		String path = fileset.getNextFile(view,null);
402		if(path == null)
403			return false;
404
405		try
406		{
407			SearchMatcher matcher = getSearchMatcher(true);
408			if(matcher == null)
409			{
410				view.getToolkit().beep();
411				return false;
412			}
413
414			record(view,"find(view)",false,true);
415
416			view.showWaitCursor();
417
418loop:			for(;;)
419			{
420				while(path != null)
421				{
422					Buffer buffer = jEdit.openTemporary(
423						view,null,path,false);
424
425					/* this is stupid and misleading.
426					 * but 'path' is not used anywhere except
427					 * the above line, and if this is done
428					 * after the 'continue', then we will
429					 * either hang, or be forced to duplicate
430					 * it inside the buffer == null, or add
431					 * a 'finally' clause. you decide which one's
432					 * worse. */
433					path = fileset.getNextFile(view,path);
434
435					if(buffer == null)
436						continue loop;
437
438					// Wait for the buffer to load
439					if(!buffer.isLoaded())
440						VFSManager.waitForRequests();
441
442					int start;
443
444					if(view.getBuffer() == buffer && !repeat)
445					{
446						JEditTextArea textArea = view.getTextArea();
447						Selection s = textArea.getSelectionAtOffset(
448							textArea.getCaretPosition());
449						if(s == null)
450							start = textArea.getCaretPosition();
451						else if(reverse)
452							start = s.getStart();
453						else
454							start = s.getEnd();
455					}
456					else if(reverse)
457						start = buffer.getLength();
458					else
459						start = 0;
460
461					if(find(view,buffer,start))
462						return true;
463				}
464
465				if(repeat)
466				{
467					if(!BeanShell.isScriptRunning())
468					{
469						view.getStatus().setMessageAndClear(
470							jEdit.getProperty("view.status.search-not-found"));
471
472						view.getToolkit().beep();
473					}
474					return false;
475				}
476
477				boolean restart;
478
479				if(BeanShell.isScriptRunning())
480				{
481					restart = true;
482				}
483				else if(wrap)
484				{
485					view.getStatus().setMessageAndClear(
486						jEdit.getProperty("view.status.auto-wrap"));
487					// beep if beep property set
488					if(jEdit.getBooleanProperty("search.beepOnSearchAutoWrap"))
489					{
490						view.getToolkit().beep();
491					}
492					restart = true;
493				}
494				else
495				{
496					Integer[] args = { new Integer(reverse ? 1 : 0) };
497					int result = GUIUtilities.confirm(view,
498						"keepsearching",args,
499						JOptionPane.YES_NO_OPTION,
500						JOptionPane.QUESTION_MESSAGE);
501					restart = (result == JOptionPane.YES_OPTION);
502				}
503
504				if(restart)
505				{
506					// start search from beginning
507					path = fileset.getFirstFile(view);
508					repeat = true;
509				}
510				else
511					break loop;
512			}
513		}
514		catch(Exception e)
515		{
516			Log.log(Log.ERROR,SearchAndReplace.class,e);
517			Object[] args = { e.getMessage() };
518			if(args[0] == null)
519				args[0] = e.toString();
520			GUIUtilities.error(view,"searcherror",args);
521		}
522		finally
523		{
524			view.hideWaitCursor();
525		}
526
527		return false;
528	} //}}}
529
530	//{{{ find() method
531	/**
532	 * Finds the next instance of the search string in the specified
533	 * buffer.
534	 * @param view The view
535	 * @param buffer The buffer
536	 * @param start Location where to start the search
537	 */
538	public static boolean find(final View view, final Buffer buffer, final int start)
539		throws Exception
540	{
541		SearchMatcher matcher = getSearchMatcher(true);
542
543		Segment text = new Segment();
544		if(reverse)
545			buffer.getText(0,start,text);
546		else
547			buffer.getText(start,buffer.getLength() - start,text);
548
549		// the start and end flags will be wrong with reverse search enabled,
550		// but they are only used by the regexp matcher, which doesn't
551		// support reverse search yet.
552		//
553		// REMIND: fix flags when adding reverse regexp search.
554		int[] match = matcher.nextMatch(new CharIndexedSegment(text,reverse),
555			start == 0,true);
556		if(match != null)
557		{
558			jEdit.commitTemporary(buffer);
559			view.setBuffer(buffer);
560			JEditTextArea textArea = view.getTextArea();
561
562			if(reverse)
563			{
564				textArea.setSelection(new Selection.Range(
565					start - match[1],
566					start - match[0]));
567				textArea.moveCaretPosition(start - match[1]);
568			}
569			else
570			{
571				textArea.setSelection(new Selection.Range(
572					start + match[0],
573					start + match[1]));
574				textArea.moveCaretPosition(start + match[1]);
575			}
576
577			return true;
578		}
579		else
580			return false;
581	} //}}}
582
583	//{{{ replace() method
584	/**
585	 * Replaces the current selection with the replacement string.
586	 * @param view The view
587	 * @return True if the operation was successful, false otherwise
588	 */
589	public static boolean replace(View view)
590	{
591		JEditTextArea textArea = view.getTextArea();
592
593		Selection[] selection = textArea.getSelection();
594		if(selection.length == 0)
595		{
596			view.getToolkit().beep();
597			return false;
598		}
599
600		record(view,"replace(view)",true,false);
601
602		Buffer buffer = view.getBuffer();
603
604		try
605		{
606			buffer.beginCompoundEdit();
607
608			int retVal = 0;
609
610			for(int i = 0; i < selection.length; i++)
611			{
612				Selection s = selection[i];
613
614				/* if an occurence occurs at the
615				beginning of the selection, the
616				selection start will get moved.
617				this sucks, so we hack to avoid it. */
618				int start = s.getStart();
619
620				retVal += _replace(view,buffer,
621					s.getStart(),s.getEnd());
622
623				// this has no effect if the selection
624				// no longer exists
625				textArea.removeFromSelection(s);
626				if(s instanceof Selection.Range)
627				{
628					textArea.addToSelection(new Selection.Range(
629						start,s.getEnd()));
630				}
631				else if(s instanceof Selection.Rect)
632				{
633					textArea.addToSelection(new Selection.Rect(
634						start,s.getEnd()));
635				}
636			}
637
638			if(retVal == 0)
639			{
640				view.getToolkit().beep();
641				return false;
642			}
643
644			return true;
645		}
646		catch(Exception e)
647		{
648			Log.log(Log.ERROR,SearchAndReplace.class,e);
649			Object[] args = { e.getMessage() };
650			if(args[0] == null)
651				args[0] = e.toString();
652			GUIUtilities.error(view,"searcherror",args);
653		}
654		finally
655		{
656			buffer.endCompoundEdit();
657		}
658
659		return false;
660	} //}}}
661
662	//{{{ replace() method
663	/**
664	 * Replaces text in the specified range with the replacement string.
665	 * @param view The view
666	 * @param buffer The buffer
667	 * @param start The start offset
668	 * @param end The end offset
669	 * @return True if the operation was successful, false otherwise
670	 */
671	public static boolean replace(View view, Buffer buffer, int start, int end)
672	{
673		JEditTextArea textArea = view.getTextArea();
674
675		try
676		{
677			int retVal = 0;
678
679			buffer.beginCompoundEdit();
680
681			retVal += _replace(view,buffer,start,end);
682
683			if(retVal != 0)
684				return true;
685		}
686		catch(Exception e)
687		{
688			Log.log(Log.ERROR,SearchAndReplace.class,e);
689			Object[] args = { e.getMessage() };
690			if(args[0] == null)
691				args[0] = e.toString();
692			GUIUtilities.error(view,"searcherror",args);
693		}
694		finally
695		{
696			buffer.endCompoundEdit();
697		}
698
699		return false;
700	} //}}}
701
702	//{{{ replaceAll() method
703	/**
704	 * Replaces all occurances of the search string with the replacement
705	 * string.
706	 * @param view The view
707	 */
708	public static boolean replaceAll(View view)
709	{
710		int fileCount = 0;
711		int occurCount = 0;
712
713		record(view,"replaceAll(view)",true,true);
714
715		view.showWaitCursor();
716
717		try
718		{
719			String path = fileset.getFirstFile(view);
720loop:			while(path != null)
721			{
722				Buffer buffer = jEdit.openTemporary(
723						view,null,path,false);
724
725				/* this is stupid and misleading.
726				 * but 'path' is not used anywhere except
727				 * the above line, and if this is done
728				 * after the 'continue', then we will
729				 * either hang, or be forced to duplicate
730				 * it inside the buffer == null, or add
731				 * a 'finally' clause. you decide which one's
732				 * worse. */
733				path = fileset.getNextFile(view,path);
734
735				if(buffer == null)
736					continue loop;
737
738				// Wait for buffer to finish loading
739				if(buffer.isPerformingIO())
740					VFSManager.waitForRequests();
741
742				// Leave buffer in a consistent state if
743				// an error occurs
744				try
745				{
746					buffer.beginCompoundEdit();
747					int retVal = _replace(view,buffer,
748						0,buffer.getLength());
749					if(retVal != 0)
750					{
751						fileCount++;
752						occurCount += retVal;
753						jEdit.commitTemporary(buffer);
754					}
755				}
756				finally
757				{
758					buffer.endCompoundEdit();
759				}
760			}
761		}
762		catch(Exception e)
763		{
764			Log.log(Log.ERROR,SearchAndReplace.class,e);
765			Object[] args = { e.getMessage() };
766			if(args[0] == null)
767				args[0] = e.toString();
768			GUIUtilities.error(view,"searcherror",args);
769		}
770		finally
771		{
772			view.hideWaitCursor();
773		}
774
775		/* Don't do this when playing a macro, cos it's annoying */
776		if(!BeanShell.isScriptRunning())
777		{
778			Object[] args = { new Integer(occurCount),
779				new Integer(fileCount) };
780			view.getStatus().setMessageAndClear(jEdit.getProperty(
781				"view.status.replace-all",args));
782		}
783
784		return (fileCount != 0);
785	} //}}}
786
787	//}}}
788
789	//{{{ load() method
790	/**
791	 * Loads search and replace state from the properties.
792	 */
793	public static void load()
794	{
795		search = jEdit.getProperty("search.find.value");
796		replace = jEdit.getProperty("search.replace.value");
797		ignoreCase = jEdit.getBooleanProperty("search.ignoreCase.toggle");
798		regexp = jEdit.getBooleanProperty("search.regexp.toggle");
799		beanshell = jEdit.getBooleanProperty("search.beanshell.toggle");
800		wrap = jEdit.getBooleanProperty("search.wrap.toggle");
801
802		fileset = new CurrentBufferSet();
803
804		// Tags plugin likes to call this method at times other than
805		// startup; so we need to fire a SearchSettingsChanged to
806		// notify the search bar and so on.
807		matcher = null;
808		EditBus.send(new SearchSettingsChanged(null));
809	} //}}}
810
811	//{{{ save() method
812	/**
813	 * Saves search and replace state to the properties.
814	 */
815	public static void save()
816	{
817		jEdit.setProperty("search.find.value",search);
818		jEdit.setProperty("search.replace.value",replace);
819		jEdit.setBooleanProperty("search.ignoreCase.toggle",ignoreCase);
820		jEdit.setBooleanProperty("search.regexp.toggle",regexp);
821		jEdit.setBooleanProperty("search.beanshell.toggle",beanshell);
822		jEdit.setBooleanProperty("search.wrap.toggle",wrap);
823	} //}}}
824
825	//{{{ Private members
826
827	//{{{ Instance variables
828	private static String search;
829	private static String replace;
830	private static boolean regexp;
831	private static boolean ignoreCase;
832	private static boolean reverse;
833	private static boolean beanshell;
834	private static boolean wrap;
835	private static SearchMatcher matcher;
836	private static SearchFileSet fileset;
837	//}}}
838
839	//{{{ record() method
840	private static void record(View view, String action,
841		boolean replaceAction, boolean recordFileSet)
842	{
843		Macros.Recorder recorder = view.getMacroRecorder();
844
845		if(recorder != null)
846		{
847			recorder.record("SearchAndReplace.setSearchString(\""
848				+ MiscUtilities.charsToEscapes(search) + "\");");
849
850			if(replaceAction)
851			{
852				recorder.record("SearchAndReplace.setReplaceString(\""
853					+ MiscUtilities.charsToEscapes(replace) + "\");");
854				recorder.record("SearchAndReplace.setBeanShellReplace("
855					+ beanshell + ");");
856			}
857			else
858			{
859				// only record this if doing a find next
860				recorder.record("SearchAndReplace.setAutoWrapAround("
861					+ wrap + ");");
862				recorder.record("SearchAndReplace.setReverseSearch("
863					+ reverse + ");");
864			}
865
866			recorder.record("SearchAndReplace.setIgnoreCase("
867				+ ignoreCase + ");");
868			recorder.record("SearchAndReplace.setRegexp("
869				+ regexp + ");");
870
871			if(recordFileSet)
872			{
873				recorder.record("SearchAndReplace.setSearchFileSet("
874					+ fileset.getCode() + ");");
875			}
876
877			recorder.record("SearchAndReplace." + action + ";");
878		}
879	} //}}}
880
881	//{{{ _replace() method
882	/**
883	 * Replaces all occurances of the search string with the replacement
884	 * string.
885	 * @param view The view
886	 * @param buffer The buffer
887	 * @param start The start offset
888	 * @param end The end offset
889	 * @return True if the replace operation was successful, false
890	 * if no matches were found
891	 */
892	private static int _replace(View view, Buffer buffer,
893		int start, int end) throws Exception
894	{
895		if(!buffer.isEditable())
896			return 0;
897
898		SearchMatcher matcher = getSearchMatcher(false);
899		if(matcher == null)
900			return 0;
901
902		int occurCount = 0;
903
904		boolean smartCaseReplace = (TextUtilities.getStringCase(replace)
905			== TextUtilities.LOWER_CASE);
906
907		Segment text = new Segment();
908		int offset = start;
909loop:		for(;;)
910		{
911			buffer.getText(offset,end - offset,text);
912			int[] occur = matcher.nextMatch(
913				new CharIndexedSegment(text,false),
914				start == 0,end == buffer.getLength());
915			if(occur == null)
916				break loop;
917			int _start = occur[0];
918			int _length = occur[1] - occur[0];
919
920			String found = new String(text.array,text.offset + _start,_length);
921			String subst = matcher.substitute(found);
922			if(smartCaseReplace && ignoreCase)
923			{
924				int strCase = TextUtilities.getStringCase(found);
925				if(strCase == TextUtilities.LOWER_CASE)
926					subst = subst.toLowerCase();
927				else if(strCase == TextUtilities.UPPER_CASE)
928					subst = subst.toUpperCase();
929				else if(strCase == TextUtilities.TITLE_CASE)
930					subst = TextUtilities.toTitleCase(subst);
931			}
932
933			if(subst != null)
934			{
935				buffer.remove(offset + _start,_length);
936				buffer.insert(offset + _start,subst);
937				occurCount++;
938				offset += _start + subst.length();
939				if(subst.length() == 0 && _length == 0)
940					offset++;
941
942				end += (subst.length() - found.length());
943			}
944			else if(_length == 0)
945			{
946				// avoid infinite loop
947				offset += _start + 1;
948			}
949			else
950				offset += _start + _length;
951		}
952
953		return occurCount;
954	} //}}}
955}