PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/org/gjt/sp/jedit/search/SearchAndReplace.java

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