
org.fife.ui.rsyntaxtextarea.folding.FoldManager Maven / Gradle / Ivy
/* * 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.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
if no open fold contains the * offset. */ public Fold getDeepestOpenFoldContaining(int offs) { Fold deepestFold = null; if (offs>-1) { for (int i=0; inull
if * no fold contains the offset. */ public Fold getDeepestFoldContaining(int offs) { Fold deepestFold = null; if (offs>-1) { for (int i=0; inull null 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 =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 getHiddenLineCountAbove(int, true). * @return The number of lines hidden in folds above line
. * @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 Whetherline
is the number of a physical * line (i.e. visible, not code-folded), or a logical one (i.e. any * line from the model). Ifline
was determined by a * raw line calculation (i.e.(visibleTopY / lineHeight)
), * this value should betrue
. It should be *false
when it was calculated from an offset in the * document (for example). * @return The number of lines hidden in folds aboveline
. */ public int getHiddenLineCountAbove(int line, boolean physical) { int count = 0; for (int i=0; i=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 line
is the number of a physical * line (i.e. visible, not code-folded), or a logical one (i.e. any * line from the model). Ifline
was determined by a * raw line calculation (i.e.(visibleTopY / lineHeight)
), * this value should betrue
. It should be *false
when it was calculated from an offset in the * document (for example). * @return The number of lines hidden in folds that are descendants of *fold
, orfold
itself, above *line
. */ private int getHiddenLineCountAboveImpl(Fold fold, int line, boolean physical) { int count = 0; if (fold.getEndLine()=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 > 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 true
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 *FoldParserManager
. * * @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" + 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); List children = possibleParentFold.getChildren(); if (children!=null) { keepFoldState(newFold, children); } } } } /** * 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 { for (int i=0; i null. */ 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 } } } }
© 2015 - 2025 Weber Informatics LLC | Privacy Policy