Package org.fife.ui.rsyntaxtextarea.folding

Source Code of org.fife.ui.rsyntaxtextarea.folding.FoldManager$Listener

/*
* 10/08/2011
*
* FoldManager.java - Manages code folding in an RSyntaxTextArea instance.
*
* This library is distributed under a modified BSD license.  See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rsyntaxtextarea.folding;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;

import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.parser.AbstractParser;
import org.fife.ui.rsyntaxtextarea.parser.DefaultParseResult;
import org.fife.ui.rsyntaxtextarea.parser.ParseResult;
import org.fife.ui.rsyntaxtextarea.parser.Parser;


/**
* Manages code folding in an instance of RSyntaxTextArea.
*
* @author Robert Futrell
* @version 1.0
*/
public class FoldManager {

  private RSyntaxTextArea textArea;
  private FoldParser parser;
  private List folds;
  private boolean codeFoldingEnabled;
  private PropertyChangeSupport support;


  /**
   * Property fired when folds have been updated.
   */
  public static final String PROPERTY_FOLDS_UPDATED = "FoldsUpdated";


  /**
   * Constructor.
   *
   * @param textArea The text area whose folds we are managing.
   */
  public FoldManager(RSyntaxTextArea textArea) {
    this.textArea = textArea;
    support = new PropertyChangeSupport(this);
    Listener l = new Listener();
    textArea.getDocument().addDocumentListener(l);
    textArea.addPropertyChangeListener(RSyntaxTextArea.SYNTAX_STYLE_PROPERTY, l);
    folds = new ArrayList();
    updateFoldParser();
  }


  /**
   * Adds a property change listener to this fold manager.
   *
   * @param l The new listener.
   * @see #removePropertyChangeListener(PropertyChangeListener)
   */
  public void addPropertyChangeListener(PropertyChangeListener l) {
    support.addPropertyChangeListener(l);
  }


  /**
   * Removes all folds.
   */
  public void clear() {
    folds.clear();
  }


  /**
   * Ensures that the specified offset is not hidden in a collapsed fold.
   * Any folds containing this offset that are collapsed will be expanded.
   *
   * @param offs The offset.
   * @return Whether any folds had to be opened.
   * @see #getDeepestFoldContaining(int)
   */
  public boolean ensureOffsetNotInClosedFold(int offs) {
    boolean foldsOpened = false;
    Fold fold = getDeepestFoldContaining(offs);
    while (fold!=null) {
      if (fold.isCollapsed()) {
        fold.setCollapsed(false);
        foldsOpened = true;
      }
      fold = fold.getParent();
    }
    return foldsOpened;
  }


  /**
   * Returns the "deepest" nested fold containing the specified offset.
   *
   * @param offs The offset.
   * @return The deepest fold containing the offset, or <code>null</code> if
   *         no fold contains the offset.
   */
  public Fold getDeepestFoldContaining(int offs) {
    Fold deepestFold = null;
    if (offs>-1) {
      for (int i=0; i<folds.size(); i++) {
        Fold fold = getFold(i);
        if (fold.containsOffset(offs)) {
          deepestFold = fold.getDeepestFoldContaining(offs);
          break;
        }
      }
    }
    return deepestFold;
  }


  /**
   * Returns the "deepest" open fold containing the specified offset.
   *
   * @param offs The offset.
   * @return The fold, or <code>null</code> if no open fold contains the
   *         offset.
   */
  public Fold getDeepestOpenFoldContaining(int offs) {

    Fold deepestFold = null;

    if (offs>-1) {
      for (int i=0; i<folds.size(); i++) {
        Fold fold = getFold(i);
        if (fold.containsOffset(offs)) {
          if (fold.isCollapsed()) {
            return null;
          }
          deepestFold = fold.getDeepestOpenFoldContaining(offs);
          break;
        }
      }
    }

    return deepestFold;

  }


  /**
   * Returns a specific top-level fold, which may have child folds.
   *
   * @param index The index of the fold.
   * @return The fold.
   * @see #getFoldCount()
   */
  public Fold getFold(int index) {
    return (Fold)folds.get(index);
  }


  /**
   * Returns the number of top-level folds.
   *
   * @return The number of top-level folds.
   * @see #getFold(int)
   */
  public int getFoldCount() {
    return folds.size();
  }


  /**
   * Returns the fold region that starts at the specified line.
   *
   * @param line The line number.
   * @return The fold, or <code>null</code> if the line is not the start
   *         of a fold region.
   * @see #isFoldStartLine(int)
   */
  public Fold getFoldForLine(int line) {
    return getFoldForLineImpl(null, folds, line);
  }


private Fold getFoldForLineImpl(Fold parent, List folds, int line) {

  int low = 0;
  int high = folds.size() - 1;

  while (low <= high) {
    int mid = (low + high) >> 1;
    Fold midFold = (Fold)folds.get(mid);
    int startLine = midFold.getStartLine();
    if (line==startLine) {
      return midFold;
    }
    else if (line<startLine) {
      high = mid - 1;
    }
    else {
      int endLine = midFold.getEndLine();
      if (line>=endLine) {
        low = mid + 1;
      }
      else { // line>startLine && line<=endLine
        List children = midFold.getChildren();
        return children!=null ? getFoldForLineImpl(midFold, children, line) : null;
      }
    }
  }

  return null; // No fold for this line
}


  /**
   * Returns the total number of hidden (folded) lines.
   *
   * @return The total number of hidden (folded) lines.
   * @see #getHiddenLineCountAbove(int)
   */
  public int getHiddenLineCount() {
    int count = 0;
    for (int i=0; i<folds.size(); i++) {
      count += ((Fold)folds.get(i)).getCollapsedLineCount();
    }
    return count;
  }


  /**
   * Returns the number of lines "hidden" by collapsed folds above the
   * specified line.
   *
   * @param line The line.  This is the line number for a logical line.
   *        For the line number of a physical line (i.e. visible, not folded),
   *        use <code>getHiddenLineCountAbove(int, true)</code>.
   * @return The number of lines hidden in folds above <code>line</code>.
   * @see #getHiddenLineCountAbove(int, boolean)
   */
  public int getHiddenLineCountAbove(int line) {
    return getHiddenLineCountAbove(line, false);
  }
 

  /**
   * Returns the number of lines "hidden" by collapsed folds above the
   * specified line.
   *
   * @param line The line.
   * @param physical Whether <code>line</code> is the number of a physical
   *        line (i.e. visible, not code-folded), or a logical one (i.e. any
   *        line from the model).  If <code>line</code> was determined by a
   *        raw line calculation (i.e. <code>(visibleTopY / lineHeight)</code>),
   *        this value should be <code>true</code>.  It should be
   *        <code>false</code> when it was calculated from an offset in the
   *        document (for example).
   * @return The number of lines hidden in folds above <code>line</code>.
   */
  public int getHiddenLineCountAbove(int line, boolean physical) {

    int count = 0;

    for (int i=0; i<folds.size(); i++) {
      Fold fold = (Fold)folds.get(i);
      int comp = physical ? line+count : line;
      if (fold.getStartLine()>=comp) {
        break;
      }
      count += getHiddenLineCountAboveImpl(fold, comp, physical);
    }

    return count;

  }


  /**
   * Returns the number of lines "hidden" by collapsed folds above the
   * specified line.
   *
   * @param fold The current fold in the recursive algorithm.  It and its
   *        children are examined.
   * @param line The line.
   * @param physical Whether <code>line</code> is the number of a physical
   *        line (i.e. visible, not code-folded), or a logical one (i.e. any
   *        line from the model).  If <code>line</code> was determined by a
   *        raw line calculation (i.e. <code>(visibleTopY / lineHeight)</code>),
   *        this value should be <code>true</code>.  It should be
   *        <code>false</code> when it was calculated from an offset in the
   *        document (for example).
   * @return The number of lines hidden in folds that are descendants of
   *         <code>fold</code>, or <code>fold</code> itself, above
   *         <code>line</code>.
   */
  private int getHiddenLineCountAboveImpl(Fold fold, int line, boolean physical) {

    int count = 0;

    if (fold.getEndLine()<line ||
        (fold.isCollapsed() && fold.getStartLine()<line)) {
      count = fold.getCollapsedLineCount();
    }
    else {
      int childCount = fold.getChildCount();
      for (int i=0; i<childCount; i++) {
        Fold child = fold.getChild(i);
        int comp = physical ? line+count : line;
        if (child.getStartLine()>=comp) {
          break;
        }
        count += getHiddenLineCountAboveImpl(child, comp, physical);
      }
    }

    return count;

  }


  /**
   * Returns the last visible line in the text area, taking into account
   * folds.
   *
   * @return The last visible line.
   */
  public int getLastVisibleLine() {

    int lastLine = textArea.getLineCount() - 1;

    if (isCodeFoldingSupportedAndEnabled()) {
      int foldCount = getFoldCount();
      if (foldCount>0) {
        Fold lastFold = getFold(foldCount-1);
        if (lastFold.containsLine(lastLine)) {
          if (lastFold.isCollapsed()) {
            lastLine = lastFold.getStartLine();
          }
          else { // Child fold may end on the same line as parent
            while (lastFold.getHasChildFolds()) {
              lastFold = lastFold.getLastChild();
              if (lastFold.containsLine(lastLine)) {
                if (lastFold.isCollapsed()) {
                  lastLine = lastFold.getStartLine();
                  break;
                }
              }
              else { // Higher up
                break;
              }
            }
          }
        }
      }
    }

    return lastLine;

  }


  public int getVisibleLineAbove(int line) {

    if (line<=0 || line>=textArea.getLineCount()) {
      return -1;
    }

    do {
      line--;
    } while (line>=0 && isLineHidden(line));

    return line;

  }


  public int getVisibleLineBelow(int line) {

    int lineCount = textArea.getLineCount();
    if (line<0 || line>=lineCount-1) {
      return -1;
    }

    do {
      line++;
    } while (line<lineCount && isLineHidden(line));

    return line==lineCount ? -1 : line;

  }


//  private static int binaryFindFoldContainingLine(int line) {
//
//List allFolds;
//
//    int low = 0;
//    int high = allFolds.size() - 1;
//
//    while (low <= high) {
//      int mid = (low + high) >> 1;
//      Fold midVal = (Fold)allFolds.get(mid);
//      if (midVal.containsLine(line)) {
//        return mid;
//      }
//      if (line<=midVal.getStartLine()) {
//        high = mid - 1;
//      }
//      else { // line > midVal.getEndLine()
//        low = mid + 1;
//      }
//    }
//
//    return -(low + 1); // key not found
//
//  }


    /**
   * Returns whether code folding is enabled.  Note that only certain
   * languages support code folding; those that do not will ignore this
   * property.
   *
   * @return Whether code folding is enabled.
   * @see #setCodeFoldingEnabled(boolean)
   */
  public boolean isCodeFoldingEnabled() {
    return codeFoldingEnabled;
  }


  /**
   * Returns <code>true</code> if and only if code folding is enabled for
   * this text area, AND folding is supported for the language it is editing.
   * Whether or not folding is supported for a language depends on whether
   * a fold parser is registered for that language with the
   * <code>FoldParserManager</code>.
   *
   * @return Whether folding is supported and enabled for this text area.
   * @see FoldParserManager
   */
  public boolean isCodeFoldingSupportedAndEnabled() {
    return codeFoldingEnabled && parser!=null;
  }


  /**
   * Returns whether the specified line contains the start of a fold region.
   *
   * @param line The line.
   * @return Whether the line contains the start of a fold region.
   * @see #getFoldForLine(int)
   */
  public boolean isFoldStartLine(int line) {
    return getFoldForLine(line)!=null;
  }


  /**
   * Returns whether a line is hidden in a collapsed fold.
   *
   * @param line The line to check.
   * @return Whether the line is hidden in a collapsed fold.
   */
  public boolean isLineHidden(int line) {
    for (int i=0; i<folds.size(); i++) {
      Fold fold = (Fold)folds.get(i);
      if (fold.containsLine(line)) {
        if (fold.isCollapsed()) {
          return true;
        }
        else {
          return isLineHiddenImpl(fold, line);
        }
      }
    }
    return false;
  }


  private boolean isLineHiddenImpl(Fold parent, int line) {
    for (int i=0; i<parent.getChildCount(); i++) {
      Fold child = parent.getChild(i);
      if (child.containsLine(line)) {
        if (child.isCollapsed()) {
          return true;
        }
        else {
          return isLineHiddenImpl(child, line);
        }
      }
    }
    return false;
  }


  /**
   * Checks whether a single fold was there in the "old" set of folds.  If
   * it was, its collapsed state is preserved.
   *
   * @param newFold The "new" fold to check for.
   * @param oldFolds The previous folds before an edit occurred.
   */
  private void keepFoldState(Fold newFold, List oldFolds) {
    int previousLoc = Collections.binarySearch(oldFolds, newFold);
    //System.out.println(newFold + " => " + previousLoc);
    if (previousLoc>=0) {
      Fold prevFold = (Fold)oldFolds.get(previousLoc);
      newFold.setCollapsed(prevFold.isCollapsed());
    }
    else {
      //previousLoc = -(insertion point) - 1;
      int insertionPoint = -(previousLoc + 1);
      if (insertionPoint>0) {
        Fold possibleParentFold = (Fold)oldFolds.get(insertionPoint-1);
        if (possibleParentFold.containsOffset(
            newFold.getStartOffset())) {
          List children = possibleParentFold.getChildren();
          if (children!=null) {
            keepFoldState(newFold, children);
          }
        }
      }
    }
  }


  /**
   * Called when new folds come in from the fold parser.  Checks whether any
   * folds from the "old" fold list are still in the "new" list; if so, their
   * collapsed state is preserved.
   *
   * @param newFolds The "new" folds after an edit occurred.  This cannot be
   *        <code>null</code>.
   * @param oldFolds The previous folds before the edit occurred.
   */
  private void keepFoldStates(List newFolds, List oldFolds) {
    for (int i=0; i<newFolds.size(); i++) {
      Fold newFold = (Fold)newFolds.get(i);
      keepFoldState(newFold, folds);
      List newChildFolds = newFold.getChildren();
      if (newChildFolds!=null) {
        keepFoldStates(newChildFolds, oldFolds);
      }
    }
  }


  /**
   * Removes a property change listener from this fold manager.
   *
   * @param l The listener to remove.
   * @see #addPropertyChangeListener(PropertyChangeListener)
   */
  public void removePropertyChangeListener(PropertyChangeListener l) {
    support.removePropertyChangeListener(l);
  }


  /**
   * Forces an immediate reparsing for folds, if folding is enabled.  This
   * usually does not need to be called by the programmer, since fold
   * parsing is done automatically by RSTA.
   */
  public void reparse() {

    if (codeFoldingEnabled && parser!=null) {

      // Re-calculate folds.  Keep the fold state of folds that are
      // still around.
      List newFolds = parser.getFolds(textArea);
      if (newFolds==null) {
        newFolds = Collections.EMPTY_LIST;
      }
      else {
        keepFoldStates(newFolds, folds);
      }
      folds = newFolds;

      // Let folks (gutter, etc.) know that folds have been updated.
      support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, folds);
      textArea.repaint();

    }
    else {
      folds.clear();
    }

  }


  /**
   * Sets whether code folding is enabled.  Note that only certain
   * languages will support code folding out of the box.  Those languages
   * which do not support folding will ignore this property.
   *
   * @param enabled Whether code folding should be enabled.
   * @see #isCodeFoldingEnabled()
   */
  public void setCodeFoldingEnabled(boolean enabled) {
    if (enabled!=codeFoldingEnabled) {
      codeFoldingEnabled = enabled;
      if (tempParser!=null) {
        textArea.removeParser(tempParser);
      }
      if (enabled) {
        tempParser = new AbstractParser() {
          public ParseResult parse(RSyntaxDocument doc, String style) {
            reparse();
            return new DefaultParseResult(this);
          }
        };
        textArea.addParser(tempParser);
        support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, null);
        //reparse();
      }
      else {
        folds = Collections.EMPTY_LIST;
        textArea.repaint();
        support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, null);
      }
    }
  }
private Parser tempParser;


  /**
   * Sets the folds for this fold manager.
   *
   * @param folds The new folds.  This should not be <code>null</code>.
   */
  public void setFolds(List folds) {
    this.folds = folds;
  }


  /**
   * Updates the fold parser to be the one appropriate for the language
   * currently being highlighted.
   */
  private void updateFoldParser() {
    parser = FoldParserManager.get().getFoldParser(
                      textArea.getSyntaxEditingStyle());
  }


  /**
   * Listens for events in the text editor.
   */
  private class Listener implements DocumentListener, PropertyChangeListener {

    public void changedUpdate(DocumentEvent e) {
    }

    public void insertUpdate(DocumentEvent e) {
      // Adding text containing a newline to the visible line of a folded
      // Fold causes that Fold to unfold.  Check only start offset of
      // insertion since that's the line that was "modified".
      int startOffs = e.getOffset();
      int endOffs = startOffs + e.getLength();
      Document doc = e.getDocument();
      Element root = doc.getDefaultRootElement();
      int startLine = root.getElementIndex(startOffs);
      int endLine = root.getElementIndex(endOffs);
      if (startLine!=endLine) { // Inserted text covering > 1 line...
        Fold fold = getFoldForLine(startLine);
        if (fold!=null && fold.isCollapsed()) {
          fold.toggleCollapsedState();
        }
      }
    }

    public void propertyChange(PropertyChangeEvent e) {
      // Syntax style changed in editor.
      updateFoldParser();
      reparse(); // Even if no fold parser change, highlighting did
    }
    public void removeUpdate(DocumentEvent e) {
      // Removing text from the visible line of a folded Fold causes that
      // Fold to unfold.  We only need to check the removal offset since
      // that's the new caret position.
      int offs = e.getOffset();
      try {
        int lastLineModified = textArea.getLineOfOffset(offs);
        //System.out.println(">>> " + lastLineModified);
        Fold fold = getFoldForLine(lastLineModified);
        //System.out.println("&&& " + fold);
        if (fold!=null && fold.isCollapsed()) {
          fold.toggleCollapsedState();
        }
      } catch (BadLocationException ble) {
        ble.printStackTrace(); // Never happens
      }
    }

  }


}
TOP

Related Classes of org.fife.ui.rsyntaxtextarea.folding.FoldManager$Listener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.