PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

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