
org.fife.ui.rsyntaxtextarea.folding.DefaultFoldManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
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