diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java index aef8441d4..d2b188812 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/RSyntaxUtilities.java @@ -371,7 +371,7 @@ else if (p1>doc.getLength()) { // token types, etc.), and get the x-location (in pixels) of the // beginning of this new token list. TokenSubList subList = TokenUtils.getSubTokenList(t, p0, e, textArea, - 0, TEMP_TOKEN); + 0, TEMP_TOKEN, false, 0); t = subList.tokenList; rect = t.listOffsetToView(textArea, e, p1, x0, rect); diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/Token.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/Token.java index b40117668..765c09b0c 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/Token.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/Token.java @@ -301,6 +301,19 @@ int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e, */ float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0); + /** + * Returns the width of this token given the specified parameters. + * + * @param textArea The text area in which the token is being painted. + * @param e Describes how to expand tabs. This parameter cannot be + * null. + * @param x0 The pixel-location at which the token begins. This is needed + * because of tabs. + * @param maxWidth the maximum width we care about measuring + * @return The width of the token, in pixels. + * @see #getWidthUpTo + */ + float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0, float maxWidth); /** * Returns the width of a specified number of characters in this token. @@ -318,6 +331,23 @@ int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e, float getWidthUpTo(int numChars, RSyntaxTextArea textArea, TabExpander e, float x0); + /** + * Returns the width of a specified number of characters in this token. + * For example, for the token "while", specifying a value of 3 + * here returns the width of the "whi" portion of the token. + * + * @param numChars The number of characters for which to get the width. + * @param textArea The text area in which the token is being painted. + * @param e How to expand tabs. This value cannot be null. + * @param x0 The pixel-location at which this token begins. This is needed + * because of tabs. + * @param maxWidth the maximum width we care about measuring + * @return The width of the specified number of characters in this token. + * @see #getWidth + */ + float getWidthUpTo(int numChars, RSyntaxTextArea textArea, + TabExpander e, float x0, float maxWidth); + /** * Returns whether this token's lexeme matches a specific character array. diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenImpl.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenImpl.java index 2a3b12533..c7e5c9a39 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenImpl.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenImpl.java @@ -570,6 +570,10 @@ public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0) { return getWidthUpTo(textCount, textArea, e, x0); } + @Override + public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0, float maxWidth) { + return getWidthUpTo(textCount, textArea, e, x0, maxWidth); + } @Override public float getWidthUpTo(int numChars, RSyntaxTextArea textArea, @@ -603,6 +607,51 @@ public float getWidthUpTo(int numChars, RSyntaxTextArea textArea, return width - x0; } + @Override + public float getWidthUpTo(int numChars, RSyntaxTextArea textArea, + TabExpander e, float x0, float maxWidth) { + float width = x0; + FontMetrics fm = textArea.getFontMetricsForTokenType(getType()); + if (fm != null) { + int w; + int currentStart = textOffset; + final int endBefore = textOffset + numChars; + for (int i = currentStart; i < endBefore; i++) { + if (text[i] == '\t') { + // Since TokenMaker implementations usually group all + // adjacent whitespace into a single token, there + // aren't usually any characters to compute a width + // for here, so we check before calling. + w = i - currentStart; + if (w > 0) { + width += fm.charsWidth(text, currentStart, w); + } + currentStart = i + 1; + width = e.nextTabStop(width, 0); + + if (width - x0 >= maxWidth) { + return maxWidth; + } + } else if ((i - currentStart) > 50) { + w = i - currentStart + 1; + width += fm.charsWidth(text, currentStart, w); + currentStart = i + 1; + + if (width - x0 >= maxWidth) { + return maxWidth; + } + } + } + + // Most (non-whitespace) tokens will have characters at this + // point to get the widths for, so we don't check for w>0 (mini- + // optimization). + w = endBefore - currentStart; + width += fm.charsWidth(text, currentStart, w); + } + return width - x0; + } + @Override public int hashCode() { diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenUtils.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenUtils.java index 9e12062b9..8b2a4904e 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenUtils.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/TokenUtils.java @@ -62,7 +62,7 @@ public static TokenSubList getSubTokenList(Token tokenList, int pos, TabExpander e, final RSyntaxTextArea textArea, float x0) { - return getSubTokenList(tokenList, pos, e, textArea, x0, null); + return getSubTokenList(tokenList, pos, e, textArea, x0, null, true, Float.MAX_VALUE); } @@ -100,6 +100,8 @@ public static TokenSubList getSubTokenList(Token tokenList, int pos, * @param tempToken A temporary token to use when creating the token list * result. This may be null but callers can pass in * a "buffer" token for performance if desired. + * @param careAboutWidth whether we need to know the width or the result or not + * @param maxWidth the maximum width we care about measuring, if we care about width * @return Information about the "sub" token list. This will be * null if pos was not a valid offset * into the token list. @@ -109,7 +111,10 @@ public static TokenSubList getSubTokenList(Token tokenList, int pos, TabExpander e, final RSyntaxTextArea textArea, float x0, - TokenImpl tempToken) { + TokenImpl tempToken, + boolean careAboutWidth, + float maxWidth) { + if (tempToken==null) { tempToken = new TokenImpl(); @@ -119,7 +124,9 @@ public static TokenSubList getSubTokenList(Token tokenList, int pos, // Loop through the token list until you find the one that contains // pos. Remember the cumulative width of all of these tokens. while (t!=null && t.isPaintable() && !t.containsPosition(pos)) { - x0 += t.getWidth(textArea, e, x0); + if (careAboutWidth) { + x0 += t.getWidth(textArea, e, x0, maxWidth); + } t = t.getNextToken(); } @@ -129,7 +136,9 @@ public static TokenSubList getSubTokenList(Token tokenList, int pos, if (t.getOffset()!=pos) { // Number of chars between p0 and token start. int difference = pos - t.getOffset(); - x0 += t.getWidthUpTo(t.length()-difference+1, textArea, e, x0); + if (careAboutWidth) { + x0 += t.getWidthUpTo(t.length()-difference+1, textArea, e, x0, maxWidth); + } tempToken.copyFrom(t); tempToken.makeStartAt(pos); diff --git a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java index ef82a64a0..ca8fb2fd1 100755 --- a/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java +++ b/RSyntaxTextArea/src/main/java/org/fife/ui/rsyntaxtextarea/WrappedSyntaxView.java @@ -18,6 +18,9 @@ import java.awt.Insets; import java.awt.Rectangle; import java.awt.Shape; +import java.util.Map.Entry; +import java.util.NavigableMap; +import java.util.TreeMap; import javax.swing.event.DocumentEvent; import javax.swing.text.BadLocationException; @@ -124,7 +127,7 @@ protected int calculateBreakPosition(int p0, Token tokenList, float x0) { // FIXME: Replace the code below with the commented-out line below. This will // allow long tokens to be broken at embedded spaces (such as MLC's). But it // currently throws BadLocationExceptions sometimes... - float tokenWidth = t.getWidth(textArea, this, x0); + float tokenWidth = t.getWidth(textArea, this, x0, currentWidth + 1); if (tokenWidth>currentWidth) { // If the current token alone is too long for this line, // break at a character boundary. @@ -212,7 +215,7 @@ private void childAllocation2(int line, int y, Rectangle alloc) { * @param fontHeight The height of the font being used. * @param y The y-coordinate at which to begin painting. */ - protected void drawView(TokenPainter painter, Graphics2D g, Rectangle r, + protected void drawView(TokenPainter painter, Graphics2D g, Rectangle r, Rectangle clip, View view, int fontHeight, int y, int line) { float x = r.x; @@ -246,11 +249,17 @@ protected void drawView(TokenPainter painter, Graphics2D g, Rectangle r, int p = calculateBreakPosition(p0, token, x); x = r.x; - h.paintLayeredHighlights(g, p0,p, r, host, this); + /* NB: for text drawing y is the baseline */ + final boolean paintThisLine = (y >= clip.getMinY()); + if (paintThisLine) { + h.paintLayeredHighlights(g, p0,p, r, host, this); + } while (token!=null && token.isPaintable() && token.getEndOffset()-1 clip.getMaxY()) { + break; + } + y += fontHeight; } // End of while (token!=null && token.isPaintable()). @@ -301,7 +320,7 @@ protected void drawView(TokenPainter painter, Graphics2D g, Rectangle r, * @param selEnd The end of the selection. */ protected void drawViewWithSelection(TokenPainter painter, Graphics2D g, - Rectangle r, View view, int fontHeight, int y, int selStart, + Rectangle r, Rectangle clip, View view, int fontHeight, int y, int selStart, int selEnd) { float x = r.x; @@ -336,66 +355,71 @@ protected void drawViewWithSelection(TokenPainter painter, Graphics2D g, int p = calculateBreakPosition(p0, token, x); x = r.x; - h.paintLayeredHighlights(g, p0,p, r, host, this); + /* NB: for text drawing y is the baseline */ + final boolean paintThisLine = (y >= clip.getMinY()); - while (token!=null && token.isPaintable() && token.getEndOffset()-1token.getOffset()) { + tempToken.copyFrom(token); + tempToken.textCount = selStart - tempToken.getOffset(); + x = painter.paint(tempToken,g,x,y,host, this); + tempToken.textCount = token.length(); + tempToken.makeStartAt(selStart); + // Clone required since token and tempToken must be + // different tokens for else statement below + token = new TokenImpl(tempToken); + } + int selCount = Math.min(token.length(), selEnd-token.getOffset()); + if (selCount==token.length()) { + x = painter.paintSelected(token, g, x,y, host, this, + useSTC); + } + else { + tempToken.copyFrom(token); + tempToken.textCount = selCount; + x = painter.paintSelected(tempToken, g, x,y, host, this, + useSTC); + tempToken.textCount = token.length(); + tempToken.makeStartAt(token.getOffset() + selCount); + token = tempToken; + x = painter.paint(token, g, x,y, host, this); + } - if (selStart>token.getOffset()) { - tempToken.copyFrom(token); - tempToken.textCount = selStart - tempToken.getOffset(); - x = painter.paint(tempToken,g,x,y,host, this); - tempToken.textCount = token.length(); - tempToken.makeStartAt(selStart); - // Clone required since token and tempToken must be - // different tokens for else statement below - token = new TokenImpl(tempToken); } - int selCount = Math.min(token.length(), selEnd-token.getOffset()); - if (selCount==token.length()) { - x = painter.paintSelected(token, g, x,y, host, this, - useSTC); - } - else { + // Selection ends in this token + else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); - tempToken.textCount = selCount; + tempToken.textCount = selEnd - tempToken.getOffset(); x = painter.paintSelected(tempToken, g, x,y, host, this, useSTC); tempToken.textCount = token.length(); - tempToken.makeStartAt(token.getOffset() + selCount); + tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x,y, host, this); } - } - - // Selection ends in this token - else if (token.containsPosition(selEnd)) { - tempToken.copyFrom(token); - tempToken.textCount = selEnd - tempToken.getOffset(); - x = painter.paintSelected(tempToken, g, x,y, host, this, - useSTC); - tempToken.textCount = token.length(); - tempToken.makeStartAt(selEnd); - token = tempToken; - x = painter.paint(token, g, x,y, host, this); - } - - // This token is entirely selected - else if (token.getOffset()>=selStart && - token.getEndOffset()<=selEnd) { - x = painter.paintSelected(token, g, x,y, host, this,useSTC); - } + // This token is entirely selected + else if (token.getOffset()>=selStart && + token.getEndOffset()<=selEnd) { + x = painter.paintSelected(token, g, x,y, host, this,useSTC); + } - // This token is entirely unselected - else { - x = painter.paint(token, g, x,y, host, this); + // This token is entirely unselected + else { + x = painter.paint(token, g, x,y, host, this); + } } - token = token.getNextToken(); } @@ -409,59 +433,61 @@ else if (token.getOffset()>=selStart && tokenOffset, token.getType(), token.getLanguageIndex()); token.setLanguageIndex(token.getLanguageIndex()); - // Selection starts in this token - if (token.containsPosition(selStart)) { + if (paintThisLine) { + // Selection starts in this token + if (token.containsPosition(selStart)) { + + if (selStart>token.getOffset()) { + tempToken.copyFrom(token); + tempToken.textCount = selStart - tempToken.getOffset(); + x = painter.paint(tempToken,g,x,y,host, this); + tempToken.textCount = token.length(); + tempToken.makeStartAt(selStart); + // Clone required since token and tempToken must be + // different tokens for else statement below + token = new TokenImpl(tempToken); + } - if (selStart>token.getOffset()) { - tempToken.copyFrom(token); - tempToken.textCount = selStart - tempToken.getOffset(); - x = painter.paint(tempToken,g,x,y,host, this); - tempToken.textCount = token.length(); - tempToken.makeStartAt(selStart); - // Clone required since token and tempToken must be - // different tokens for else statement below - token = new TokenImpl(tempToken); - } + int selCount = Math.min(token.length(), selEnd-token.getOffset()); + if (selCount==token.length()) { + x = painter.paintSelected(token, g, x,y, host, this, + useSTC); + } + else { + tempToken.copyFrom(token); + tempToken.textCount = selCount; + x = painter.paintSelected(tempToken, g, x,y, host, + this, useSTC); + tempToken.textCount = token.length(); + tempToken.makeStartAt(token.getOffset() + selCount); + token = tempToken; + x = painter.paint(token, g, x,y, host, this); + } - int selCount = Math.min(token.length(), selEnd-token.getOffset()); - if (selCount==token.length()) { - x = painter.paintSelected(token, g, x,y, host, this, - useSTC); } - else { + + // Selection ends in this token + else if (token.containsPosition(selEnd)) { tempToken.copyFrom(token); - tempToken.textCount = selCount; - x = painter.paintSelected(tempToken, g, x,y, host, - this, useSTC); + tempToken.textCount = selEnd - tempToken.getOffset(); + x = painter.paintSelected(tempToken, g, x,y, host, this, + useSTC); tempToken.textCount = token.length(); - tempToken.makeStartAt(token.getOffset() + selCount); + tempToken.makeStartAt(selEnd); token = tempToken; x = painter.paint(token, g, x,y, host, this); } - } - - // Selection ends in this token - else if (token.containsPosition(selEnd)) { - tempToken.copyFrom(token); - tempToken.textCount = selEnd - tempToken.getOffset(); - x = painter.paintSelected(tempToken, g, x,y, host, this, - useSTC); - tempToken.textCount = token.length(); - tempToken.makeStartAt(selEnd); - token = tempToken; - x = painter.paint(token, g, x,y, host, this); - } - - // This token is entirely selected - else if (token.getOffset()>=selStart && - token.getEndOffset()<=selEnd) { - x = painter.paintSelected(token, g, x,y, host, this,useSTC); - } + // This token is entirely selected + else if (token.getOffset()>=selStart && + token.getEndOffset()<=selEnd) { + x = painter.paintSelected(token, g, x,y, host, this,useSTC); + } - // This token is entirely unselected - else { - x = painter.paint(token, g, x,y, host, this); + // This token is entirely unselected + else { + x = painter.paint(token, g, x,y, host, this); + } } token = new TokenImpl(orig); @@ -469,11 +495,19 @@ else if (token.getOffset()>=selStart && } - // Paint parser (e.g. squiggle underline) highlights after - // text and selection - h.paintParserHighlights(g, p0,p, r, host, this); + if (paintThisLine) { + // Paint parser (e.g. squiggle underline) highlights after + // text and selection + h.paintParserHighlights(g, p0,p, r, host, this); + } p0 = (p==p0) ? p1 : p; + + /* NB: y is the baseline, so we check it before we add fontHeight */ + if (y > clip.getMaxY()) { + break; + } + y += fontHeight; } // End of while (token!=null && token.isPaintable()). @@ -923,12 +957,12 @@ public void paint(Graphics g, Shape a) { View view = getView(i); if (selStart==selEnd || startOffset>=selEnd || endOffset 1) { // invalidate the view itself since the children's // desired widths will be based upon this view's width. preferenceChanged(null, true, true); @@ -1156,6 +1190,18 @@ class WrappedLine extends View { private int nlines; private boolean widthChangePending; + /** + * A temporary lookup for a character position at the start of a line to the y offset of that line, + * to improve the performance when wrapping long lines. + */ + private transient NavigableMap posToHeightLookup; + private transient NavigableMap heightToPosLookup; + + /** + * The width used when the lookup tables were calculated, so they can be reset when the width changes. + */ + private transient int widthForLookups; + WrappedLine(Element elem) { super(elem); } @@ -1186,7 +1232,7 @@ final int calculateLineCount() { //System.err.println("... ... " + p0 + ", " + p1); nlines += 1; TokenSubList subList = TokenUtils.getSubTokenList(tokenList, p0, - WrappedSyntaxView.this, textArea, x0, lineCountTempToken); + WrappedSyntaxView.this, textArea, x0, lineCountTempToken, false, 0); x0 = subList!=null ? subList.x : x0; tokenList = subList!=null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tokenList, x0); @@ -1277,8 +1323,6 @@ public Shape modelToView(int pos, Shape a, Position.Bias b) alloc.width = 1; int p0 = getStartOffset(); int p1 = getEndOffset(); - int testP = (b == Position.Bias.Forward) ? pos : - Math.max(p0, pos - 1); // Get the token list for this line so we don't have to keep // recomputing it if this logical line spans multiple physical @@ -1289,9 +1333,28 @@ public Shape modelToView(int pos, Shape a, Position.Bias b) Token tokenList = doc.getTokenListForLine(line); float x0 = alloc.x;//0; + final int width = getWidth(); + + /* Use lookup table to improve performance */ + if (posToHeightLookup != null) { + if (widthForLookups == width) { + Entry floorEntry = posToHeightLookup.floorEntry(pos - 1); + if (floorEntry != null) { + p0 = floorEntry.getKey(); + alloc.y = floorEntry.getValue(); + } + } else { + posToHeightLookup = null; + } + } + + int testP = (b == Position.Bias.Forward) ? pos : + Math.max(p0, pos - 1); + + int loops = 0; while (p0 < p1) { TokenSubList subList = TokenUtils.getSubTokenList(tokenList, p0, - WrappedSyntaxView.this, textArea, x0, lineCountTempToken); + WrappedSyntaxView.this, textArea, x0, lineCountTempToken, true, width + 1); x0 = subList!=null ? subList.x : x0; tokenList = subList!=null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tokenList, x0); @@ -1320,7 +1383,15 @@ public Shape modelToView(int pos, Shape a, Position.Bias b) p0 = (p == p0) ? p1 : p; //System.err.println("... ... Incrementing y"); alloc.y += alloc.height; + loops++; + if (loops > 10) { + if (posToHeightLookup == null) { + posToHeightLookup = new TreeMap<>(); + widthForLookups = width; + } + posToHeightLookup.put(p0, alloc.y); + } } throw new BadLocationException(null, pos); @@ -1378,6 +1449,23 @@ else if (y > alloc.y + alloc.height) { int line = map.getElementIndex(p0); Token tlist = doc.getTokenListForLine(line); + final int width = getWidth(); + + /* Use lookup table to improve performance */ + if (heightToPosLookup != null) { + if (widthForLookups == width) { + Entry floorEntry = heightToPosLookup.floorEntry((int) fy); + if (floorEntry != null) { + alloc.y = floorEntry.getKey(); + p0 = floorEntry.getValue(); + } + } else { + heightToPosLookup = null; + } + } + + int loops = 0; + // Look at each physical line-chunk of this logical line. while (p0 alloc.y + alloc.height) { // lines so they start at the beginning of a physical // line. TokenSubList subList = TokenUtils.getSubTokenList(tlist, p0, - WrappedSyntaxView.this, textArea, alloc.x, lineCountTempToken); + WrappedSyntaxView.this, textArea, alloc.x, lineCountTempToken, false, 0); tlist = subList!=null ? subList.tokenList : null; int p = calculateBreakPosition(p0, tlist, alloc.x); @@ -1426,6 +1514,15 @@ else if (tlist != null) { p0 = (p == p0) ? p1 : p; alloc.y += alloc.height; + loops++; + + if (loops > 10) { + if (heightToPosLookup == null) { + heightToPosLookup = new TreeMap<>(); + widthForLookups = width; + } + heightToPosLookup.put(alloc.y, p0); + } } // End of while (p0 folds; + /** + * A {@link Map} from line number to {@link Fold}. + */ + private Map foldsByLine; private boolean codeFoldingEnabled; private PropertyChangeSupport support; private Listener l; @@ -71,6 +77,7 @@ public DefaultFoldManager(RSyntaxTextArea textArea) { textArea.addPropertyChangeListener(RSyntaxTextArea.SYNTAX_STYLE_PROPERTY, l); textArea.addPropertyChangeListener("document", l); folds = new ArrayList<>(); + foldsByLine = new HashMap<>(); updateFoldParser(); } @@ -84,6 +91,7 @@ public void addPropertyChangeListener(PropertyChangeListener l) { @Override public void clear() { folds.clear(); + foldsByLine.clear(); } @@ -163,7 +171,13 @@ public int getFoldCount() { @Override public Fold getFoldForLine(int line) { - return getFoldForLineImpl(null, folds, line); + if (foldsByLine.containsKey(line)) { + return foldsByLine.get(line); + } + + Fold result = getFoldForLineImpl(null, folds, line); + foldsByLine.put(line, result); + return result; } @@ -364,33 +378,46 @@ public boolean isFoldStartLine(int line) { @Override public boolean isLineHidden(int line) { - for (Fold fold : folds) { - if (fold.containsLine(line)) { - if (fold.isCollapsed()) { - return true; - } - else { - return isLineHiddenImpl(fold, line); - } - } - } - return false; + return isLineHiddenImpl(null, folds, line); } + private boolean isLineHiddenImpl(Fold parent, List folds, int line) { + if (folds == null) { + return false; + } + + int low = 0; + int high = folds.size() - 1; - private boolean isLineHiddenImpl(Fold parent, int line) { - for (int i=0; i> 1; + Fold midFold = folds.get(mid); + int startLine = midFold.getStartLine(); + if (line==startLine) { + /* The first line of a fold is never hidden */ + return false; + } + else if (lineendLine) { + low = mid + 1; } - else { - return isLineHiddenImpl(child, line); + else { // line>startLine && line<=endLine + if (midFold.isCollapsed()) { + return true; + } else if (midFold.getCollapsedLineCount() != 0) { + return isLineHiddenImpl(midFold, midFold.getChildren(), line); + } else { + return false; + } } } } - return false; + + return false; // No fold for this line } @@ -450,6 +477,7 @@ public void reparse() { keepFoldStates(newFolds, folds); } folds = newFolds; + foldsByLine = new HashMap<>(); // Let folks (gutter, etc.) know that folds have been updated. support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, folds); @@ -458,6 +486,7 @@ public void reparse() { } else { folds.clear(); + foldsByLine.clear(); } } @@ -484,6 +513,7 @@ public ParseResult parse(RSyntaxDocument doc, String style) { } else { folds = Collections.emptyList(); + foldsByLine = new HashMap<>(); textArea.repaint(); support.firePropertyChange(PROPERTY_FOLDS_UPDATED, null, null); } @@ -494,6 +524,7 @@ public ParseResult parse(RSyntaxDocument doc, String style) { @Override public void setFolds(List folds) { this.folds = folds; + this.foldsByLine = new HashMap<>(); }