PageRenderTime 195ms CodeModel.GetById 131ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/search/SearchAndReplace.java

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