Esempio n. 1
0
  // {{{ invoke() method
  public void invoke(View view) {
    try {
      BeanShell.runCachedBlock(
          code.get(), view, new NameSpace(BeanShell.getNameSpace(), "BeanShellAction.invoke()"));
    } catch (Throwable e) {
      Log.log(Log.ERROR, this, e);

      new BeanShellErrorDialog(view, e);
    }
  } // }}}
 /** Set up BeanShell replace if necessary. */
 private static void initReplace() throws Exception {
   if (beanshell && replace.length() != 0) {
     String text;
     if (replace.trim().startsWith("{")) text = replace;
     else text = "return (" + replace + ");";
     replaceMethod = BeanShell.cacheBlock("replace", text, true);
   } else replaceMethod = null;
 } // }}}
Esempio n. 3
0
 public BshMethod get() throws java.lang.Exception {
   if (cache != null) {
     BshMethod cached = cache.get();
     if (cached != null) {
       return cached;
     }
   }
   BshMethod newOne = BeanShell.cacheBlock(name, source, true);
   cache = new SoftReference<BshMethod>(newOne);
   return newOne;
 }
  // {{{ literalBeanShellReplace() method
  private static String literalBeanShellReplace(View view, JEditBuffer buffer, CharSequence found)
      throws Exception {
    replaceNS.setVariable("buffer", buffer);
    replaceNS.setVariable("_0", found);
    Object obj = BeanShell.runCachedBlock(replaceMethod, view, replaceNS);

    replaceNS.setVariable("_0", null, false);
    // Not really necessary because it is already cleared in the end of
    // BeanShell.runCachedBlock()
    replaceNS.setVariable("buffer", null, false);

    if (obj == null) return "";
    else return obj.toString();
  } // }}}
Esempio n. 5
0
  // {{{ isSelected() method
  public boolean isSelected(Component comp) {
    if (isSelected == null) return false;

    NameSpace global = BeanShell.getNameSpace();

    try {
      View view = GUIUtilities.getView(comp);

      // undocumented hack to allow browser actions to work.
      // XXX - clean up in 4.3
      global.setVariable("_comp", comp);

      return Boolean.TRUE.equals(
          BeanShell.runCachedBlock(
              isSelected.get(),
              view,
              new NameSpace(BeanShell.getNameSpace(), "BeanShellAction.isSelected()")));
    } catch (Throwable e) {
      Log.log(Log.ERROR, this, e);

      // dialogs f**k things up if a menu is visible, etc!
      // new BeanShellErrorDialog(view,e);

      // so that in the future we don't see streams of
      // exceptions
      isSelected = null;

      return false;
    } finally {
      try {
        global.setVariable("_comp", null);
      } catch (UtilEvalError err) {
        Log.log(Log.ERROR, this, err);
      }
    }
  } // }}}
  // {{{ regexpBeanShellReplace() method
  private static String regexpBeanShellReplace(
      View view, JEditBuffer buffer, SearchMatcher.Match occur) throws Exception {
    replaceNS.setVariable("buffer", buffer, false);
    for (int i = 0; i < occur.substitutions.length; i++) {
      replaceNS.setVariable("_" + i, occur.substitutions[i]);
    }

    Object obj = BeanShell.runCachedBlock(replaceMethod, view, replaceNS);

    for (int i = 0; i < occur.substitutions.length; i++) {
      replaceNS.setVariable("_" + i, null, false);
    }
    // Not really necessary because it is already cleared in the end of
    // BeanShell.runCachedBlock()
    replaceNS.setVariable("buffer", null, false);

    if (obj == null) return "";
    else return obj.toString();
  } // }}}
  /**
   * Replaces all occurrences of the search string with the replacement string.
   *
   * @param view The view
   * @param dontOpenChangedFiles Whether to open changed files or to autosave them quietly
   * @return the number of modified files
   */
  public static boolean replaceAll(View view, boolean dontOpenChangedFiles) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    if (fileset.getFileCount(view) == 0) {
      GUIUtilities.error(comp, "empty-fileset", null);
      return false;
    }

    record(view, "replaceAll(view)", true, true);

    view.showWaitCursor();

    boolean smartCaseReplace = getSmartCaseReplace();

    int fileCount = 0;
    int occurCount = 0;
    try {
      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) return false;

      initReplace();

      String path = fileset.getFirstFile(view);
      loop:
      while (path != null) {
        Buffer buffer = jEdit.openTemporary(view, null, path, false);

        /* this is stupid and misleading.
         * but 'path' is not used anywhere except
         * the above line, and if this is done
         * after the 'continue', then we will
         * either hang, or be forced to duplicate
         * it inside the buffer == null, or add
         * a 'finally' clause. you decide which one's
         * worse. */
        path = fileset.getNextFile(view, path);

        if (buffer == null) continue loop;

        // Wait for buffer to finish loading
        if (buffer.isPerformingIO()) VFSManager.waitForRequests();

        if (!buffer.isEditable()) continue loop;

        // Leave buffer in a consistent state if
        // an error occurs
        int retVal = 0;

        try {
          buffer.beginCompoundEdit();
          retVal = _replace(view, buffer, matcher, 0, buffer.getLength(), smartCaseReplace);
        } finally {
          buffer.endCompoundEdit();
        }

        if (retVal != 0) {
          fileCount++;
          occurCount += retVal;
          if (dontOpenChangedFiles) {
            buffer.save(null, null);
          } else {
            jEdit.commitTemporary(buffer);
            jEdit.getBufferSetManager().addBuffer(view, buffer);
          }
        }
      }
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      view.hideWaitCursor();
    }

    /* Don't do this when playing a macro, cos it's annoying */
    if (!BeanShell.isScriptRunning()) {
      Object[] args = {Integer.valueOf(occurCount), Integer.valueOf(fileCount)};
      view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.replace-all", args));
      if (occurCount == 0) view.getToolkit().beep();
    }

    return (fileCount != 0);
  } // }}}
/**
 * Class that implements regular expression and literal search within jEdit buffers.
 *
 * <p>There are two main groups of methods in this class:
 *
 * <ul>
 *   <li>Property accessors - for changing search and replace settings.
 *   <li>Actions - for performing search and replace.
 * </ul>
 *
 * The "HyperSearch" and "Keep dialog" features, as reflected in checkbox options in the search
 * dialog, are not handled from within this class. If you wish to have these options set before the
 * search dialog appears, make a prior call to either or both of the following:
 *
 * <pre> jEdit.setBooleanProperty("search.hypersearch.toggle",true);
 * jEdit.setBooleanProperty("search.keepDialog.toggle",true);</pre>
 *
 * If you are not using the dialog to undertake a search or replace, you may call any of the search
 * and replace methods (including {@link #hyperSearch(View)}) without concern for the value of these
 * properties.
 *
 * @author Slava Pestov
 * @author John Gellene (API documentation)
 * @version $Id$
 */
public class SearchAndReplace {
  // {{{ Getters and setters

  // {{{ setSearchString() method
  /**
   * Sets the current search string.
   *
   * @param search The new search string
   */
  public static void setSearchString(String search) {
    if (search.equals(SearchAndReplace.search)) return;

    SearchAndReplace.search = search;
    matcher = null;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getSearchString() method
  /** Returns the current search string. */
  public static String getSearchString() {
    return search;
  } // }}}

  // {{{ setReplaceString() method
  /**
   * Sets the current replacement string.
   *
   * @param replace The new replacement string
   */
  public static void setReplaceString(String replace) {
    if (replace.equals(SearchAndReplace.replace)) return;

    SearchAndReplace.replace = replace;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getReplaceString() method
  /** Returns the current replacement string. */
  public static String getReplaceString() {
    return replace;
  } // }}}

  // {{{ setWholeWord() method
  /**
   * Sets the whole word flag.
   *
   * @param wholeWord True if only whole words should be searched, false otherwise
   * @since 4.5pre1
   */
  public static void setWholeWord(boolean wholeWord) {
    if (wholeWord == SearchAndReplace.wholeWord) return;

    SearchAndReplace.wholeWord = wholeWord;
    matcher = null;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ setIgnoreCase() method
  /**
   * Sets the ignore case flag.
   *
   * @param ignoreCase True if searches should be case insensitive, false otherwise
   */
  public static void setIgnoreCase(boolean ignoreCase) {
    if (ignoreCase == SearchAndReplace.ignoreCase) return;

    SearchAndReplace.ignoreCase = ignoreCase;
    matcher = null;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getWholeWord() method
  /**
   * Returns the state of the whole word flag.
   *
   * @return True if only whole words should be searched, false otherwise
   * @since 4.5pre1
   */
  public static boolean getWholeWord() {
    return wholeWord;
  } // }}}

  // {{{ getIgnoreCase() method
  /**
   * Returns the state of the ignore case flag.
   *
   * @return True if searches should be case insensitive, false otherwise
   */
  public static boolean getIgnoreCase() {
    return ignoreCase;
  } // }}}

  // {{{ setRegexp() method
  /**
   * Sets the state of the regular expression flag.
   *
   * @param regexp True if regular expression searches should be performed
   */
  public static void setRegexp(boolean regexp) {
    if (regexp == SearchAndReplace.regexp) return;

    SearchAndReplace.regexp = regexp;
    if (regexp && reverse) reverse = false;

    matcher = null;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getRegexp() method
  /**
   * Returns the state of the regular expression flag.
   *
   * @return True if regular expression searches should be performed
   */
  public static boolean getRegexp() {
    return regexp;
  } // }}}

  // {{{ setReverseSearch() method
  /**
   * Determines whether a reverse search will conducted from the current position to the beginning
   * of a buffer. Note that reverse search and regular expression search is mutually exclusive;
   * enabling one will disable the other.
   *
   * @param reverse True if searches should go backwards, false otherwise
   */
  public static void setReverseSearch(boolean reverse) {
    if (reverse == SearchAndReplace.reverse) return;

    SearchAndReplace.reverse = reverse;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getReverseSearch() method
  /**
   * Returns the state of the reverse search flag.
   *
   * @return True if searches should go backwards, false otherwise
   */
  public static boolean getReverseSearch() {
    return reverse;
  } // }}}

  // {{{ setBeanShellReplace() method
  /**
   * Sets the state of the BeanShell replace flag.
   *
   * @param beanshell True if the replace string is a BeanShell expression
   * @since jEdit 3.2pre2
   */
  public static void setBeanShellReplace(boolean beanshell) {
    if (beanshell == SearchAndReplace.beanshell) return;

    SearchAndReplace.beanshell = beanshell;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getBeanShellReplace() method
  /**
   * Returns the state of the BeanShell replace flag.
   *
   * @return True if the replace string is a BeanShell expression
   * @since jEdit 3.2pre2
   */
  public static boolean getBeanShellReplace() {
    return beanshell;
  } // }}}

  // {{{ setAutoWrap() method
  /**
   * Sets the state of the auto wrap around flag.
   *
   * @param wrap If true, the 'continue search from start' dialog will not be displayed
   * @since jEdit 3.2pre2
   */
  public static void setAutoWrapAround(boolean wrap) {
    if (wrap == SearchAndReplace.wrap) return;

    SearchAndReplace.wrap = wrap;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getAutoWrap() method
  /**
   * Returns the state of the auto wrap around flag.
   *
   * @since jEdit 3.2pre2
   */
  public static boolean getAutoWrapAround() {
    return wrap;
  } // }}}

  // {{{ setSearchMatcher() method
  /**
   * Sets a custom search string matcher. Note that calling {@link #setSearchString(String)}, {@link
   * #setWholeWord(boolean)}, {@link #setIgnoreCase(boolean)}, or {@link #setRegexp(boolean)} will
   * reset the matcher to the default.
   */
  public static void setSearchMatcher(SearchMatcher matcher) {
    SearchAndReplace.matcher = matcher;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getSearchMatcher() method
  /**
   * Returns the current search string matcher.
   *
   * @return a SearchMatcher or null if there is no search or if the matcher can match empty String
   * @exception IllegalArgumentException if regular expression search is enabled, the search string
   *     or replacement string is invalid
   * @since jEdit 4.1pre7
   */
  public static SearchMatcher getSearchMatcher() throws Exception {
    if (matcher != null) return matcher;

    if (search == null || "".equals(search)) return null;

    if (regexp) {
      Pattern re = Pattern.compile(search, PatternSearchMatcher.getFlag(ignoreCase));
      matcher = new PatternSearchMatcher(re, ignoreCase, wholeWord);
    } else matcher = new BoyerMooreSearchMatcher(search, ignoreCase, wholeWord);

    return matcher;
  } // }}}

  // {{{ setSearchFileSet() method
  /**
   * Sets the current search file set.
   *
   * @param fileset The file set to perform searches in
   * @see AllBufferSet
   * @see CurrentBufferSet
   * @see DirectoryListSet
   */
  public static void setSearchFileSet(SearchFileSet fileset) {
    SearchAndReplace.fileset = fileset;

    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ getSearchFileSet() method
  /** Returns the current search file set. */
  public static SearchFileSet getSearchFileSet() {
    return fileset;
  } // }}}

  // {{{ getSmartCaseReplace() method
  /**
   * Returns if the replacement string will assume the same case as each specific occurrence of the
   * search string.
   *
   * @since jEdit 4.2pre10
   */
  public static boolean getSmartCaseReplace() {
    return (replace != null && TextUtilities.getStringCase(replace) == TextUtilities.LOWER_CASE);
  } // }}}

  // }}}

  // {{{ Actions

  // {{{ hyperSearch() method
  /**
   * Performs a HyperSearch.
   *
   * @param view The view
   * @since jEdit 2.7pre3
   */
  public static boolean hyperSearch(View view) {
    return hyperSearch(view, false);
  } // }}}

  // {{{ hyperSearch() method
  /**
   * Performs a HyperSearch.
   *
   * @param view The view
   * @param selection If true, will only search in the current selection. Note that the file set
   *     must be the current buffer file set for this to work.
   * @since jEdit 4.0pre1
   */
  public static boolean hyperSearch(View view, boolean selection) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    record(view, "hyperSearch(view," + selection + ')', false, !selection);

    view.getDockableWindowManager().addDockableWindow(HyperSearchResults.NAME);
    HyperSearchResults results =
        (HyperSearchResults) view.getDockableWindowManager().getDockable(HyperSearchResults.NAME);
    results.searchStarted();

    try {
      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) {
        view.getToolkit().beep();
        results.searchFailed();
        return false;
      }

      Selection[] s;
      if (selection) {
        s = view.getTextArea().getSelection();
        if (s == null) {
          results.searchFailed();
          return false;
        }
      } else s = null;
      ThreadUtilities.runInBackground(new HyperSearchRequest(view, matcher, results, s));
      return true;
    } catch (Exception e) {
      results.searchFailed();
      handleError(comp, e);
      return false;
    }
  } // }}}

  // {{{ find() method
  /**
   * Finds the next occurrence of the search string.
   *
   * @param view The view
   * @return True if the operation was successful, false otherwise
   */
  public static boolean find(View view) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null || !comp.isShowing()) comp = view;

    String path = fileset.getNextFile(view, null);
    if (path == null) {
      GUIUtilities.error(comp, "empty-fileset", null);
      return false;
    }

    try {
      view.showWaitCursor();

      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) {
        view.getToolkit().beep();
        return false;
      }

      record(view, "find(view)", false, true);

      boolean repeat = false;
      loop:
      for (; ; ) {
        while (path != null) {
          Buffer buffer = jEdit.openTemporary(view, null, path, false);

          /* this is stupid and misleading.
           * but 'path' is not used anywhere except
           * the above line, and if this is done
           * after the 'continue', then we will
           * either hang, or be forced to duplicate
           * it inside the buffer == null, or add
           * a 'finally' clause. you decide which one's
           * worse. */
          if (reverse) {
            path = fileset.getPrevFile(view, path);
          } else {
            path = fileset.getNextFile(view, path);
          }

          if (buffer == null) continue loop;

          // Wait for the buffer to load
          if (!buffer.isLoaded()) VFSManager.waitForRequests();

          int start;

          if (view.getBuffer() == buffer && !repeat) {
            JEditTextArea textArea = view.getTextArea();
            Selection s = textArea.getSelectionAtOffset(textArea.getCaretPosition());
            if (s == null) start = textArea.getCaretPosition();
            else if (reverse) start = s.getStart();
            else start = s.getEnd();
          } else if (reverse) start = buffer.getLength();
          else start = 0;

          if (find(view, buffer, start, repeat, reverse)) return true;
        }

        if (repeat) {
          if (!BeanShell.isScriptRunning()) {
            view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.search-not-found"));

            view.getToolkit().beep();
          }
          return false;
        }

        boolean restart;

        // if auto wrap is on, always restart search.
        // if auto wrap is off, and we're called from
        // a macro, stop search. If we're called
        // interactively, ask the user what to do.
        if (wrap) {
          if (!BeanShell.isScriptRunning()) {
            view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.auto-wrap"));
            // beep if beep property set
            if (jEdit.getBooleanProperty("search.beepOnSearchAutoWrap")) {
              view.getToolkit().beep();
            }
          }
          restart = true;
        } else if (BeanShell.isScriptRunning()) {
          restart = false;
        } else {
          Integer[] args = {Integer.valueOf(reverse ? 1 : 0)};
          int result =
              GUIUtilities.confirm(
                  comp,
                  "keepsearching",
                  args,
                  JOptionPane.YES_NO_OPTION,
                  JOptionPane.QUESTION_MESSAGE);
          restart = (result == JOptionPane.YES_OPTION);
        }

        if (restart) {
          // start search from beginning
          path = fileset.getFirstFile(view);
          repeat = true;
        } else break loop;
      }
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      view.hideWaitCursor();
    }

    return false;
  } // }}}

  // {{{ find() method
  /**
   * Finds the next instance of the search string in the specified buffer.
   *
   * @param view The view
   * @param buffer The buffer
   * @param start Location where to start the search
   */
  public static boolean find(View view, Buffer buffer, int start) throws Exception {
    return find(view, buffer, start, false, false);
  } // }}}

  // {{{ find() method
  /**
   * Finds the next instance of the search string in the specified buffer.
   *
   * @param view The view
   * @param buffer The buffer
   * @param start Location where to start the search
   * @param firstTime See {@link
   *     SearchMatcher#nextMatch(CharSequence,boolean,boolean,boolean,boolean)}.
   * @since jEdit 4.1pre7
   */
  public static boolean find(
      View view, Buffer buffer, int start, boolean firstTime, boolean reverse) throws Exception {

    EditBus.send(new PositionChanging(view.getEditPane()));

    SearchMatcher matcher = getSearchMatcher();
    if (matcher == null) {
      view.getToolkit().beep();
      return false;
    }

    CharSequence text;
    boolean startOfLine;
    boolean endOfLine;
    if (reverse) {
      text = new ReverseCharSequence(buffer.getSegment(0, start));
      startOfLine = true;
      endOfLine = (buffer.getLineEndOffset(buffer.getLineOfOffset(start)) - 1 == start);
    } else {
      text = buffer.getSegment(start, buffer.getLength() - start);
      startOfLine = (buffer.getLineStartOffset(buffer.getLineOfOffset(start)) == start);
      endOfLine = true;
    }

    String noWordSep = buffer.getStringProperty("noWordSep");
    matcher.setNoWordSep(noWordSep);
    SearchMatcher.Match match = matcher.nextMatch(text, startOfLine, endOfLine, firstTime, reverse);
    if (match != null) {
      jEdit.commitTemporary(buffer);
      view.setBuffer(buffer, true);
      JEditTextArea textArea = view.getTextArea();

      if (reverse) {
        textArea.setSelection(new Selection.Range(start - match.end, start - match.start));
        // make sure end of match is visible
        textArea.scrollTo(start - match.start, false);
        textArea.moveCaretPosition(start - match.end);
      } else {
        textArea.setSelection(new Selection.Range(start + match.start, start + match.end));
        textArea.moveCaretPosition(start + match.end);
        // make sure start of match is visible
        textArea.scrollTo(start + match.start, false);
      }

      return true;
    } else return false;
  } // }}}

  // {{{ replace() method
  /**
   * Replaces the current selection with the replacement string.
   *
   * @param view The view
   * @return True if the operation was successful, false otherwise
   */
  public static boolean replace(View view) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    JEditTextArea textArea = view.getTextArea();

    Buffer buffer = view.getBuffer();
    if (!buffer.isEditable()) return false;

    boolean smartCaseReplace = getSmartCaseReplace();

    Selection[] selection = textArea.getSelection();
    if (selection.length == 0) {
      view.getToolkit().beep();
      return false;
    }

    record(view, "replace(view)", true, false);

    // a little hack for reverse replace and find
    int caret = textArea.getCaretPosition();
    Selection s = textArea.getSelectionAtOffset(caret);
    if (s != null) caret = s.getStart();

    try {
      buffer.beginCompoundEdit();

      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) return false;

      initReplace();

      int retVal = 0;

      for (int i = 0; i < selection.length; i++) {
        s = selection[i];

        retVal += replaceInSelection(view, textArea, buffer, matcher, smartCaseReplace, s);
      }

      if (reverse) {
        // so that Replace and Find continues from
        // the right location
        textArea.moveCaretPosition(caret);
      } else {
        s = textArea.getSelectionAtOffset(textArea.getCaretPosition());
        if (s != null) textArea.moveCaretPosition(s.getEnd());
      }

      if (!BeanShell.isScriptRunning()) {
        Object[] args = {Integer.valueOf(retVal), Integer.valueOf(1)};
        view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.replace-all", args));
      }

      if (retVal == 0) {
        view.getToolkit().beep();
        return false;
      }

      return true;
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      buffer.endCompoundEdit();
    }

    return false;
  } // }}}

  // {{{ replace() method
  /**
   * Replaces text in the specified range with the replacement string.
   *
   * @param view The view
   * @param buffer The buffer
   * @param start The start offset
   * @param end The end offset
   * @return True if the operation was successful, false otherwise
   */
  public static boolean replace(View view, Buffer buffer, int start, int end) {
    if (!buffer.isEditable()) return false;

    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    boolean smartCaseReplace = getSmartCaseReplace();

    try {
      buffer.beginCompoundEdit();

      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) return false;

      initReplace();

      int retVal = 0;

      retVal += _replace(view, buffer, matcher, start, end, smartCaseReplace);

      if (retVal != 0) return true;
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      buffer.endCompoundEdit();
    }

    return false;
  } // }}}

  // {{{ replaceAll() method
  /**
   * Replaces all occurrences of the search string with the replacement string.
   *
   * @param view The view
   * @return the number of modified files
   */
  public static boolean replaceAll(View view) {
    return replaceAll(view, false);
  } // }}}

  // {{{ replaceAll() method
  /**
   * Replaces all occurrences of the search string with the replacement string.
   *
   * @param view The view
   * @param dontOpenChangedFiles Whether to open changed files or to autosave them quietly
   * @return the number of modified files
   */
  public static boolean replaceAll(View view, boolean dontOpenChangedFiles) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    if (fileset.getFileCount(view) == 0) {
      GUIUtilities.error(comp, "empty-fileset", null);
      return false;
    }

    record(view, "replaceAll(view)", true, true);

    view.showWaitCursor();

    boolean smartCaseReplace = getSmartCaseReplace();

    int fileCount = 0;
    int occurCount = 0;
    try {
      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) return false;

      initReplace();

      String path = fileset.getFirstFile(view);
      loop:
      while (path != null) {
        Buffer buffer = jEdit.openTemporary(view, null, path, false);

        /* this is stupid and misleading.
         * but 'path' is not used anywhere except
         * the above line, and if this is done
         * after the 'continue', then we will
         * either hang, or be forced to duplicate
         * it inside the buffer == null, or add
         * a 'finally' clause. you decide which one's
         * worse. */
        path = fileset.getNextFile(view, path);

        if (buffer == null) continue loop;

        // Wait for buffer to finish loading
        if (buffer.isPerformingIO()) VFSManager.waitForRequests();

        if (!buffer.isEditable()) continue loop;

        // Leave buffer in a consistent state if
        // an error occurs
        int retVal = 0;

        try {
          buffer.beginCompoundEdit();
          retVal = _replace(view, buffer, matcher, 0, buffer.getLength(), smartCaseReplace);
        } finally {
          buffer.endCompoundEdit();
        }

        if (retVal != 0) {
          fileCount++;
          occurCount += retVal;
          if (dontOpenChangedFiles) {
            buffer.save(null, null);
          } else {
            jEdit.commitTemporary(buffer);
            jEdit.getBufferSetManager().addBuffer(view, buffer);
          }
        }
      }
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      view.hideWaitCursor();
    }

    /* Don't do this when playing a macro, cos it's annoying */
    if (!BeanShell.isScriptRunning()) {
      Object[] args = {Integer.valueOf(occurCount), Integer.valueOf(fileCount)};
      view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.replace-all", args));
      if (occurCount == 0) view.getToolkit().beep();
    }

    return (fileCount != 0);
  } // }}}

  // }}}

  // {{{ escapeRegexp() method
  /**
   * Escapes characters with special meaning in a regexp.
   *
   * @param str the string to escape
   * @param multiline Should \n be escaped?
   * @return the string with escaped characters
   * @since jEdit 4.3pre1
   */
  public static String escapeRegexp(String str, boolean multiline) {
    return StandardUtilities.charsToEscapes(str, "\r\t\\()[]{}$^*+?|." + (multiline ? "" : "\n"));
  } // }}}

  // {{{ load() method
  /** Loads search and replace state from the properties. */
  public static void load() {
    search = jEdit.getProperty("search.find.value");
    replace = jEdit.getProperty("search.replace.value");
    wholeWord = jEdit.getBooleanProperty("search.wholeWord.toggle");
    ignoreCase = jEdit.getBooleanProperty("search.ignoreCase.toggle");
    regexp = jEdit.getBooleanProperty("search.regexp.toggle");
    beanshell = jEdit.getBooleanProperty("search.beanshell.toggle");
    wrap = jEdit.getBooleanProperty("search.wrap.toggle");

    fileset = new CurrentBufferSet();

    // Tags plugin likes to call this method at times other than
    // startup; so we need to fire a SearchSettingsChanged to
    // notify the search bar and so on.
    matcher = null;
    EditBus.send(new SearchSettingsChanged(null));
  } // }}}

  // {{{ save() method
  /** Saves search and replace state to the properties. */
  public static void save() {
    jEdit.setProperty("search.find.value", search);
    jEdit.setProperty("search.replace.value", replace);
    jEdit.setBooleanProperty("search.wholeWord.toggle", wholeWord);
    jEdit.setBooleanProperty("search.ignoreCase.toggle", ignoreCase);
    jEdit.setBooleanProperty("search.regexp.toggle", regexp);
    jEdit.setBooleanProperty("search.beanshell.toggle", beanshell);
    jEdit.setBooleanProperty("search.wrap.toggle", wrap);
  } // }}}

  // {{{ handleError() method
  static void handleError(Component comp, Exception e) {
    Log.log(Log.ERROR, SearchAndReplace.class, e);
    if (comp instanceof Dialog) {
      new TextAreaDialog((Dialog) comp, beanshell ? "searcherror-bsh" : "searcherror", e);
    } else {
      new TextAreaDialog((Frame) comp, beanshell ? "searcherror-bsh" : "searcherror", e);
    }
  } // }}}

  // {{{ Private members

  // {{{ Instance variables
  private static String search;
  private static String replace;
  private static BshMethod replaceMethod;
  private static NameSpace replaceNS =
      new NameSpace(
          BeanShell.getNameSpace(),
          BeanShell.getNameSpace().getClassManager(),
          "search and replace");
  private static boolean regexp;
  private static boolean wholeWord;
  private static boolean ignoreCase;
  private static boolean reverse;
  private static boolean beanshell;
  private static boolean wrap;
  private static SearchMatcher matcher;
  private static SearchFileSet fileset;
  // }}}

  // {{{ initReplace() method
  /** Set up BeanShell replace if necessary. */
  private static void initReplace() throws Exception {
    if (beanshell && replace.length() != 0) {
      String text;
      if (replace.trim().startsWith("{")) text = replace;
      else text = "return (" + replace + ");";
      replaceMethod = BeanShell.cacheBlock("replace", text, true);
    } else replaceMethod = null;
  } // }}}

  // {{{ record() method
  private static void record(
      View view, String action, boolean replaceAction, boolean recordFileSet) {
    Macros.Recorder recorder = view.getMacroRecorder();

    if (recorder != null) {
      recorder.record(
          "SearchAndReplace.setSearchString(\""
              + StandardUtilities.charsToEscapes(search)
              + "\");");

      if (replaceAction) {
        recorder.record(
            "SearchAndReplace.setReplaceString(\""
                + StandardUtilities.charsToEscapes(replace)
                + "\");");
        recorder.record("SearchAndReplace.setBeanShellReplace(" + beanshell + ");");
      } else {
        // only record this if doing a find next
        recorder.record("SearchAndReplace.setAutoWrapAround(" + wrap + ");");
        recorder.record("SearchAndReplace.setReverseSearch(" + reverse + ");");
      }

      recorder.record("SearchAndReplace.setWholeWord(" + wholeWord + ");");
      recorder.record("SearchAndReplace.setIgnoreCase(" + ignoreCase + ");");
      recorder.record("SearchAndReplace.setRegexp(" + regexp + ");");

      if (recordFileSet) {
        recorder.record("SearchAndReplace.setSearchFileSet(" + fileset.getCode() + ");");
      }

      recorder.record("SearchAndReplace." + action + ';');
    }
  } // }}}

  // {{{ replaceInSelection() method
  private static int replaceInSelection(
      View view,
      TextArea textArea,
      Buffer buffer,
      SearchMatcher matcher,
      boolean smartCaseReplace,
      Selection s)
      throws Exception {
    /* if an occurence occurs at the
    beginning of the selection, the
    selection start will get moved.
    this sucks, so we hack to avoid it. */
    int start = s.getStart();

    int returnValue;

    if (s instanceof Selection.Range) {
      returnValue = _replace(view, buffer, matcher, s.getStart(), s.getEnd(), smartCaseReplace);

      textArea.removeFromSelection(s);
      textArea.addToSelection(new Selection.Range(start, s.getEnd()));
    } else if (s instanceof Selection.Rect) {
      Selection.Rect rect = (Selection.Rect) s;
      int startCol = rect.getStartColumn(buffer);
      int endCol = rect.getEndColumn(buffer);

      returnValue = 0;
      for (int j = s.getStartLine(); j <= s.getEndLine(); j++) {
        returnValue +=
            _replace(
                view,
                buffer,
                matcher,
                getColumnOnOtherLine(buffer, j, startCol),
                getColumnOnOtherLine(buffer, j, endCol),
                smartCaseReplace);
      }
      textArea.addToSelection(new Selection.Rect(start, s.getEnd()));
    } else throw new RuntimeException("Unsupported: " + s);

    return returnValue;
  } // }}}

  // {{{ _replace() method
  /**
   * Replaces all occurrences of the search string with the replacement string.
   *
   * @param view The view
   * @param buffer The buffer
   * @param start The start offset
   * @param end The end offset
   * @param matcher The search matcher to use
   * @param smartCaseReplace See user's guide
   * @return The number of occurrences replaced
   */
  private static int _replace(
      View view,
      JEditBuffer buffer,
      SearchMatcher matcher,
      int start,
      int end,
      boolean smartCaseReplace)
      throws Exception {
    String noWordSep = buffer.getStringProperty("noWordSep");
    matcher.setNoWordSep(noWordSep);
    int occurCount = 0;

    boolean endOfLine = (buffer.getLineEndOffset(buffer.getLineOfOffset(end)) - 1 == end);

    int offset = start;
    loop:
    for (int counter = 0; ; counter++) {
      boolean startOfLine = (buffer.getLineStartOffset(buffer.getLineOfOffset(offset)) == offset);

      CharSequence text = buffer.getSegment(offset, end - offset);
      SearchMatcher.Match occur =
          matcher.nextMatch(text, startOfLine, endOfLine, counter == 0, false);
      if (occur == null) break loop;

      CharSequence found = text.subSequence(occur.start, occur.end);

      int length = replaceOne(view, buffer, occur, offset, found, smartCaseReplace);
      if (length == -1) offset += occur.end;
      else {
        offset += occur.start + length;
        end += (length - found.length());
        occurCount++;
      }
    }

    return occurCount;
  } // }}}

  // {{{ replaceOne() method
  /** Replace one occurrence of the search string with the replacement string. */
  private static int replaceOne(
      View view,
      JEditBuffer buffer,
      SearchMatcher.Match occur,
      int offset,
      CharSequence found,
      boolean smartCaseReplace)
      throws Exception {
    String subst = replaceOne(view, buffer, occur, found);
    if (smartCaseReplace && ignoreCase) {
      int strCase = TextUtilities.getStringCase(found);
      if (strCase == TextUtilities.LOWER_CASE) subst = subst.toLowerCase();
      else if (strCase == TextUtilities.UPPER_CASE) subst = subst.toUpperCase();
      else if (strCase == TextUtilities.TITLE_CASE) subst = TextUtilities.toTitleCase(subst);
    }

    if (subst != null) {
      int start = offset + occur.start;
      int end = offset + occur.end;

      if (end - start > 0) buffer.remove(start, end - start);
      buffer.insert(start, subst);
      return subst.length();
    } else return -1;
  } // }}}

  // {{{ replaceOne() method
  private static String replaceOne(
      View view, JEditBuffer buffer, SearchMatcher.Match occur, CharSequence found)
      throws Exception {
    if (regexp) {
      if (replaceMethod != null) return regexpBeanShellReplace(view, buffer, occur);
      else return regexpReplace(occur, found);
    } else {
      if (replaceMethod != null) return literalBeanShellReplace(view, buffer, found);
      else return replace;
    }
  } // }}}

  // {{{ regexpBeanShellReplace() method
  private static String regexpBeanShellReplace(
      View view, JEditBuffer buffer, SearchMatcher.Match occur) throws Exception {
    replaceNS.setVariable("buffer", buffer, false);
    for (int i = 0; i < occur.substitutions.length; i++) {
      replaceNS.setVariable("_" + i, occur.substitutions[i]);
    }

    Object obj = BeanShell.runCachedBlock(replaceMethod, view, replaceNS);

    for (int i = 0; i < occur.substitutions.length; i++) {
      replaceNS.setVariable("_" + i, null, false);
    }
    // Not really necessary because it is already cleared in the end of
    // BeanShell.runCachedBlock()
    replaceNS.setVariable("buffer", null, false);

    if (obj == null) return "";
    else return obj.toString();
  } // }}}

  // {{{ regexpReplace() method
  private static String regexpReplace(SearchMatcher.Match occur, CharSequence found)
      throws Exception {
    StringBuilder buf = new StringBuilder();

    for (int i = 0; i < replace.length(); i++) {
      char ch = replace.charAt(i);
      switch (ch) {
        case '$':
          if (i == replace.length() - 1) {
            // last character of the replace string,
            // it is not a capturing group
            buf.append(ch);
            break;
          }

          ch = replace.charAt(++i);
          if (ch == '$') {
            // It was $$, so it is an escaped $
            buf.append('$');
          } else if (ch == '0') {
            // $0 meaning the first capturing group :
            // the found value
            buf.append(found);
          } else if (Character.isDigit(ch)) {
            int n = ch - '0';
            while (i < replace.length() - 1) {
              ch = replace.charAt(++i);
              if (Character.isDigit(ch)) {
                n = n * 10 + (ch - '0');
              } else {
                // The character is not
                // a digit, going back and
                // end loop
                i--;
                break;
              }
            }
            if (n < occur.substitutions.length) {
              String subs = occur.substitutions[n];
              if (subs != null) buf.append(subs);
            }
          }
          break;
        case '\\':
          if (i == replace.length() - 1) {
            buf.append('\\');
            break;
          }
          ch = replace.charAt(++i);
          switch (ch) {
            case 'n':
              buf.append('\n');
              break;
            case 't':
              buf.append('\t');
              break;
            default:
              buf.append(ch);
              break;
          }
          break;
        default:
          buf.append(ch);
          break;
      }
    }

    return buf.toString();
  } // }}}

  // {{{ literalBeanShellReplace() method
  private static String literalBeanShellReplace(View view, JEditBuffer buffer, CharSequence found)
      throws Exception {
    replaceNS.setVariable("buffer", buffer);
    replaceNS.setVariable("_0", found);
    Object obj = BeanShell.runCachedBlock(replaceMethod, view, replaceNS);

    replaceNS.setVariable("_0", null, false);
    // Not really necessary because it is already cleared in the end of
    // BeanShell.runCachedBlock()
    replaceNS.setVariable("buffer", null, false);

    if (obj == null) return "";
    else return obj.toString();
  } // }}}

  // {{{ getColumnOnOtherLine() method
  /** Should be somewhere else... */
  private static int getColumnOnOtherLine(Buffer buffer, int line, int col) {
    int returnValue = buffer.getOffsetOfVirtualColumn(line, col, null);
    if (returnValue == -1) return buffer.getLineEndOffset(line) - 1;
    else return buffer.getLineStartOffset(line) + returnValue;
  } // }}}

  // }}}
}
  /**
   * Replaces the current selection with the replacement string.
   *
   * @param view The view
   * @return True if the operation was successful, false otherwise
   */
  public static boolean replace(View view) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null) comp = view;

    JEditTextArea textArea = view.getTextArea();

    Buffer buffer = view.getBuffer();
    if (!buffer.isEditable()) return false;

    boolean smartCaseReplace = getSmartCaseReplace();

    Selection[] selection = textArea.getSelection();
    if (selection.length == 0) {
      view.getToolkit().beep();
      return false;
    }

    record(view, "replace(view)", true, false);

    // a little hack for reverse replace and find
    int caret = textArea.getCaretPosition();
    Selection s = textArea.getSelectionAtOffset(caret);
    if (s != null) caret = s.getStart();

    try {
      buffer.beginCompoundEdit();

      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) return false;

      initReplace();

      int retVal = 0;

      for (int i = 0; i < selection.length; i++) {
        s = selection[i];

        retVal += replaceInSelection(view, textArea, buffer, matcher, smartCaseReplace, s);
      }

      if (reverse) {
        // so that Replace and Find continues from
        // the right location
        textArea.moveCaretPosition(caret);
      } else {
        s = textArea.getSelectionAtOffset(textArea.getCaretPosition());
        if (s != null) textArea.moveCaretPosition(s.getEnd());
      }

      if (!BeanShell.isScriptRunning()) {
        Object[] args = {Integer.valueOf(retVal), Integer.valueOf(1)};
        view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.replace-all", args));
      }

      if (retVal == 0) {
        view.getToolkit().beep();
        return false;
      }

      return true;
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      buffer.endCompoundEdit();
    }

    return false;
  } // }}}
  /**
   * Finds the next occurrence of the search string.
   *
   * @param view The view
   * @return True if the operation was successful, false otherwise
   */
  public static boolean find(View view) {
    // component that will parent any dialog boxes
    Component comp = SearchDialog.getSearchDialog(view);
    if (comp == null || !comp.isShowing()) comp = view;

    String path = fileset.getNextFile(view, null);
    if (path == null) {
      GUIUtilities.error(comp, "empty-fileset", null);
      return false;
    }

    try {
      view.showWaitCursor();

      SearchMatcher matcher = getSearchMatcher();
      if (matcher == null) {
        view.getToolkit().beep();
        return false;
      }

      record(view, "find(view)", false, true);

      boolean repeat = false;
      loop:
      for (; ; ) {
        while (path != null) {
          Buffer buffer = jEdit.openTemporary(view, null, path, false);

          /* this is stupid and misleading.
           * but 'path' is not used anywhere except
           * the above line, and if this is done
           * after the 'continue', then we will
           * either hang, or be forced to duplicate
           * it inside the buffer == null, or add
           * a 'finally' clause. you decide which one's
           * worse. */
          if (reverse) {
            path = fileset.getPrevFile(view, path);
          } else {
            path = fileset.getNextFile(view, path);
          }

          if (buffer == null) continue loop;

          // Wait for the buffer to load
          if (!buffer.isLoaded()) VFSManager.waitForRequests();

          int start;

          if (view.getBuffer() == buffer && !repeat) {
            JEditTextArea textArea = view.getTextArea();
            Selection s = textArea.getSelectionAtOffset(textArea.getCaretPosition());
            if (s == null) start = textArea.getCaretPosition();
            else if (reverse) start = s.getStart();
            else start = s.getEnd();
          } else if (reverse) start = buffer.getLength();
          else start = 0;

          if (find(view, buffer, start, repeat, reverse)) return true;
        }

        if (repeat) {
          if (!BeanShell.isScriptRunning()) {
            view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.search-not-found"));

            view.getToolkit().beep();
          }
          return false;
        }

        boolean restart;

        // if auto wrap is on, always restart search.
        // if auto wrap is off, and we're called from
        // a macro, stop search. If we're called
        // interactively, ask the user what to do.
        if (wrap) {
          if (!BeanShell.isScriptRunning()) {
            view.getStatus().setMessageAndClear(jEdit.getProperty("view.status.auto-wrap"));
            // beep if beep property set
            if (jEdit.getBooleanProperty("search.beepOnSearchAutoWrap")) {
              view.getToolkit().beep();
            }
          }
          restart = true;
        } else if (BeanShell.isScriptRunning()) {
          restart = false;
        } else {
          Integer[] args = {Integer.valueOf(reverse ? 1 : 0)};
          int result =
              GUIUtilities.confirm(
                  comp,
                  "keepsearching",
                  args,
                  JOptionPane.YES_NO_OPTION,
                  JOptionPane.QUESTION_MESSAGE);
          restart = (result == JOptionPane.YES_OPTION);
        }

        if (restart) {
          // start search from beginning
          path = fileset.getFirstFile(view);
          repeat = true;
        } else break loop;
      }
    } catch (Exception e) {
      handleError(comp, e);
    } finally {
      view.hideWaitCursor();
    }

    return false;
  } // }}}