All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fife.ui.rsyntaxtextarea.folding.DefaultFoldManager Maven / Gradle / Ivy

Go to download

RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.

The newest version!
/*
 * 10/08/2011
 *
 * This library is distributed under a modified BSD license.  See the included
 * LICENSE 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.RSyntaxUtilities;
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;
import org.fife.ui.rtextarea.RDocument;


/**
 * The default implementation of a fold manager.  Besides keeping track of
 * folds, this class behaves as follows:
 *
 * 
    *
  • If text containing a newline is inserted in a collapsed fold, * that fold, and any ancestor folds, are expanded. This ensures that * modified text is always visible to the user. *
  • If the text area's {@link RSyntaxTextArea#SYNTAX_STYLE_PROPERTY} * changes, the current fold parser is uninstalled, and one appropriate * for the new language, if any, is installed. *
* * The folding strategy to use is retrieved from {@link FoldParserManager}. * * @author Robert Futrell * @version 1.0 */ public class DefaultFoldManager implements FoldManager { private RSyntaxTextArea textArea; private Parser rstaParser; private FoldParser foldParser; private List folds; private boolean codeFoldingEnabled; private PropertyChangeSupport support; private Listener l; /** * Constructor. * * @param textArea The text area whose folds we are managing. */ public DefaultFoldManager(RSyntaxTextArea textArea) { this.textArea = textArea; support = new PropertyChangeSupport(this); l = new Listener(); textArea.getDocument().addDocumentListener(l); textArea.addPropertyChangeListener(RSyntaxTextArea.SYNTAX_STYLE_PROPERTY, l); textArea.addPropertyChangeListener("document", l); folds = new ArrayList<>(); updateFoldParser(); } @Override public void addPropertyChangeListener(PropertyChangeListener l) { support.addPropertyChangeListener(l); } @Override public void clear() { folds.clear(); } @Override 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(); } if (foldsOpened) { // Folds changing state mean gutter is stale RSyntaxUtilities.possiblyRepaintGutter(textArea); } return foldsOpened; } @Override public Fold getDeepestFoldContaining(int offs) { Fold deepestFold = null; if (offs>-1) { for (int i=0; i-1) { for (int i=0; i folds, int line) { int low = 0; int high = folds.size() - 1; while (low <= high) { int mid = (low + high) >> 1; Fold midFold = 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 } @Override public int getHiddenLineCount() { int count = 0; for (Fold fold : folds) { count += fold.getCollapsedLineCount(); } return count; } @Override public int getHiddenLineCountAbove(int line) { return getHiddenLineCountAbove(line, false); } @Override public int getHiddenLineCountAbove(int line, boolean physical) { int count = 0; for (Fold fold : folds) { 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 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). If line was determined by a * raw line calculation (i.e. (visibleTopY / lineHeight)), * this value should be true. 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, or fold 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; } @Override 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; } @Override public int getVisibleLineAbove(int line) { if (line<=0 || line>=textArea.getLineCount()) { return -1; } do { line--; } while (line>=0 && isLineHidden(line)); return line; } @Override public int getVisibleLineBelow(int line) { int lineCount = textArea.getLineCount(); if (line<0 || line>=lineCount-1) { return -1; } do { line++; } while (line oldFolds) { int previousLoc = Collections.binarySearch(oldFolds, newFold); //System.out.println(newFold + " => " + previousLoc); if (previousLoc>=0) { Fold prevFold = oldFolds.get(previousLoc); newFold.setCollapsed(prevFold.isCollapsed()); } else { //previousLoc = -(insertion point) - 1; int insertionPoint = -(previousLoc + 1); if (insertionPoint>0) { Fold possibleParentFold = oldFolds.get(insertionPoint-1); if (possibleParentFold.containsOffset( newFold.getStartOffset())) { List children = possibleParentFold.getChildren(); if (children!=null) { keepFoldState(newFold, children); } } } } } private void keepFoldStates(List newFolds, List oldFolds) { for (Fold newFold : newFolds) { keepFoldState(newFold, folds); List newChildFolds = newFold.getChildren(); if (newChildFolds!=null) { keepFoldStates(newChildFolds, oldFolds); } } } @Override public void removePropertyChangeListener(PropertyChangeListener l) { support.removePropertyChangeListener(l); } @Override public void reparse() { if (codeFoldingEnabled && foldParser!=null) { // Re-calculate folds. Keep the fold state of folds that are // still around. List newFolds = foldParser.getFolds(textArea); if (newFolds==null) { newFolds = Collections.emptyList(); } 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(); } } @Override public void setCodeFoldingEnabled(boolean enabled) { if (enabled!=codeFoldingEnabled) { codeFoldingEnabled = enabled; if (rstaParser!=null) { textArea.removeParser(rstaParser); } if (enabled) { rstaParser = new AbstractParser() { @Override public ParseResult parse(RSyntaxDocument doc, String style) { reparse(); return new DefaultParseResult(this); } }; textArea.addParser(rstaParser); support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, null); //reparse(); } else { folds = Collections.emptyList(); textArea.repaint(); support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, null); } } } @Override 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() { foldParser = FoldParserManager.get().getFoldParser( textArea.getSyntaxEditingStyle()); } /** * Listens for events in the text editor. */ private final class Listener implements DocumentListener, PropertyChangeListener { @Override public void changedUpdate(DocumentEvent e) { } @Override 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(); } } } @Override public void propertyChange(PropertyChangeEvent e) { String name = e.getPropertyName(); if (RSyntaxTextArea.SYNTAX_STYLE_PROPERTY.equals(name)) { // Syntax style changed in editor. updateFoldParser(); reparse(); // Even if no fold parser change, highlighting did } else if ("document".equals(name)) { // The document switched out from under us RDocument old = (RDocument)e.getOldValue(); if (old != null) { old.removeDocumentListener(this); } RDocument newDoc = (RDocument)e.getNewValue(); if (newDoc != null) { newDoc.addDocumentListener(this); } reparse(); } } @Override 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