/**
   * Paints the word-wrapped text.
   *
   * @param g The graphics context in which to paint.
   * @param a The shape (usually a rectangle) in which to paint.
   */
  public void paint(Graphics g, Shape a) {

    Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds();
    tabBase = alloc.x;

    Graphics2D g2d = (Graphics2D) g;
    host = (RSyntaxTextArea) getContainer();
    int ascent = host.getMaxAscent();
    int fontHeight = host.getLineHeight();
    FoldManager fm = host.getFoldManager();
    TokenPainter painter = host.getTokenPainter();
    Element root = getElement();

    // Whether token styles should always be painted, even in selections
    int selStart = host.getSelectionStart();
    int selEnd = host.getSelectionEnd();
    boolean useSelectedTextColor = host.getUseSelectedTextColor();

    int n = getViewCount(); // Number of lines.
    int x = alloc.x + getLeftInset();
    tempRect.y = alloc.y + getTopInset();
    Rectangle clip = g.getClipBounds();
    for (int i = 0; i < n; i++) {

      tempRect.x = x + getOffset(X_AXIS, i);
      // tempRect.y = y + getOffset(Y_AXIS, i);
      tempRect.width = getSpan(X_AXIS, i);
      tempRect.height = getSpan(Y_AXIS, i);
      // System.err.println("For line " + i + ": tempRect==" + tempRect);

      if (tempRect.intersects(clip)) {
        Element lineElement = root.getElement(i);
        int startOffset = lineElement.getStartOffset();
        int endOffset = lineElement.getEndOffset() - 1; // Why always "-1"?
        View view = getView(i);
        if (!useSelectedTextColor
            || selStart == selEnd
            || (startOffset >= selEnd || endOffset < selStart)) {
          drawView(painter, g2d, alloc, view, fontHeight, tempRect.y + ascent);
        } else {
          // System.out.println("Drawing line with selection: " + i);
          drawViewWithSelection(
              painter, g2d, alloc, view, fontHeight, tempRect.y + ascent, selStart, selEnd);
        }
      }

      tempRect.y += tempRect.height;

      Fold possibleFold = fm.getFoldForLine(i);
      if (possibleFold != null && possibleFold.isCollapsed()) {
        i += possibleFold.getCollapsedLineCount();
        // Visible indicator of collapsed lines
        Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
        if (c != null) {
          g.setColor(c);
          g.drawLine(x, tempRect.y - 1, alloc.width, tempRect.y - 1);
        }
      }
    }
  }
Example #2
0
  /** Overridden to allow for folded regions. */
  @Override
  protected View getViewAtPoint(int x, int y, Rectangle alloc) {

    int lineCount = getViewCount();
    int curY = alloc.y + getOffset(Y_AXIS, 0); // Always at least 1 line
    host = (RSyntaxTextArea) getContainer();
    FoldManager fm = host.getFoldManager();

    for (int line = 1; line < lineCount; line++) {
      int span = getSpan(Y_AXIS, line - 1);
      if (y < curY + span) {
        childAllocation2(line - 1, curY, alloc);
        return getView(line - 1);
      }
      curY += span;
      Fold fold = fm.getFoldForLine(line - 1);
      if (fold != null && fold.isCollapsed()) {
        line += fold.getCollapsedLineCount();
      }
    }

    // Not found - return last line's view.
    childAllocation2(lineCount - 1, curY, alloc);
    return getView(lineCount - 1);
  }
  private Fold findOpenFoldClosestTo(Point p) {

    Fold fold = null;

    RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
    if (rsta.isCodeFoldingEnabled()) { // Should always be true
      int offs = rsta.viewToModel(p); // TODO: Optimize me
      if (offs > -1) {
        try {
          int line = rsta.getLineOfOffset(offs);
          int origLine = line;
          FoldManager fm = rsta.getFoldManager();
          do {
            fold = fm.getFoldForLine(line);
          } while (fold == null && line-- >= 0);
          if (fold != null && !fold.containsOrStartsOnLine(origLine)) {
            // Found closest fold, but doesn't actually contain line
            fold = null;
          }
        } catch (BadLocationException ble) {
          ble.printStackTrace(); // Never happens
        }
      }
    }

    return fold;
  }
 /**
  * Creates a fold that is a child of this one.
  *
  * @param type The type of fold.
  * @param startOffs The starting offset of the fold.
  * @return The child fold.
  * @throws BadLocationException If <code>startOffs</code> is invalid.
  * @see FoldType
  */
 public Fold createChild(int type, int startOffs) throws BadLocationException {
   Fold child = new Fold(type, textArea, startOffs);
   child.parent = this;
   if (children == null) {
     children = new ArrayList<Fold>();
   }
   children.add(child);
   return child;
 }
 /**
  * Returns the "deepest" fold containing the specified offset. It is assumed that it's already
  * been verified that <code>offs</code> is indeed contained in this fold.
  *
  * @param offs The offset.
  * @return The fold, or <code>null</code> if no child fold also contains the offset.
  * @see FoldManager#getDeepestFoldContaining(int)
  */
 Fold getDeepestFoldContaining(int offs) {
   Fold deepestFold = this;
   for (int i = 0; i < getChildCount(); i++) {
     Fold fold = getChild(i);
     if (fold.containsOffset(offs)) {
       deepestFold = fold.getDeepestFoldContaining(offs);
       break;
     }
   }
   return deepestFold;
 }
 /**
  * Removes this fold from its parent. This should only be called by {@link FoldParser}
  * implementations if they determine that a fold is all on a single line (and thus shouldn't be
  * remembered) after creating it.
  *
  * @return Whether this fold had a parent to be removed from.
  * @see #isOnSingleLine()
  */
 public boolean removeFromParent() {
   if (parent != null) {
     parent.removeMostRecentChild();
     parent = null;
     return true;
   }
   return false;
 }
 private void updateChildCollapsedLineCount(int count) {
   childCollapsedLineCount += count;
   // if (childCollapsedLineCount>getLineCount()) {
   //	Thread.dumpStack();
   // }
   if (!collapsed && parent != null) {
     parent.updateChildCollapsedLineCount(count);
   }
 }
  /**
   * Overridden to show the content of a collapsed fold on mouse-overs.
   *
   * @param e The mouse location.
   */
  public String getToolTipText(MouseEvent e) {

    String text = null;

    RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
    if (rsta.isCodeFoldingEnabled()) {
      FoldManager fm = rsta.getFoldManager();
      int pos = rsta.viewToModel(new Point(0, e.getY()));
      if (pos >= 0) { // Not -1
        int line = 0;
        try {
          line = rsta.getLineOfOffset(pos);
        } catch (BadLocationException ble) {
          ble.printStackTrace(); // Never happens
          return null;
        }
        Fold fold = fm.getFoldForLine(line);
        if (fold != null && fold.isCollapsed()) {

          int endLine = fold.getEndLine();
          if (fold.getLineCount() > 25) { // Not too big
            endLine = fold.getStartLine() + 25;
          }

          StringBuffer sb = new StringBuffer("<html><nobr>");
          while (line <= endLine && line < rsta.getLineCount()) { // Sanity
            Token t = rsta.getTokenListForLine(line);
            while (t != null && t.isPaintable()) {
              t.appendHTMLRepresentation(sb, rsta, true, true);
              t = t.getNextToken();
            }
            sb.append("<br>");
            line++;
          }

          text = sb.toString();
        }
      }
    }

    return text;
  }
Example #9
0
  /**
   * Fetches the allocation for the given child view to render into.
   *
   * <p>Overridden to account for lines hidden by collapsed folded regions.
   *
   * @param line The index of the child, >= 0 && < getViewCount()
   * @param a The allocation to this view
   * @return The allocation to the child
   */
  public Shape getChildAllocationImpl(int line, Shape a) {

    Rectangle alloc = getInsideAllocation(a);
    host = (RSyntaxTextArea) getContainer();
    FoldManager fm = host.getFoldManager();
    int y = alloc.y;

    // TODO: Make cached getOffset() calls for Y_AXIS valid even for
    // folding, to speed this up!
    for (int i = 0; i < line; i++) {
      y += getSpan(Y_AXIS, i);
      Fold fold = fm.getFoldForLine(i);
      if (fold != null && fold.isCollapsed()) {
        i += fold.getCollapsedLineCount();
      }
    }

    childAllocation2(line, y, alloc);
    return alloc;
  }
    public void mouseClicked(MouseEvent e) {

      //			// TODO: Implement code folding with word wrap enabled
      //			if (textArea.getLineWrap()) {
      //				UIManager.getLookAndFeel().provideErrorFeedback(textArea);
      //				return;
      //			}

      Point p = e.getPoint();
      int line = rowAtPoint(p);

      RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
      FoldManager fm = rsta.getFoldManager();

      Fold fold = fm.getFoldForLine(line);
      if (fold != null) {
        fold.toggleCollapsedState();
        getGutter().repaint();
        textArea.repaint();
      }
    }
  /**
   * Paints the word-wrapped text.
   *
   * @param g The graphics context in which to paint.
   * @param a The shape (usually a rectangle) in which to paint.
   */
  public void paint(Graphics g, Shape a) {

    Rectangle alloc = (a instanceof Rectangle) ? (Rectangle) a : a.getBounds();
    tabBase = alloc.x;

    Graphics2D g2d = (Graphics2D) g;
    host = (RSyntaxTextArea) getContainer();
    int ascent = host.getMaxAscent();
    int fontHeight = host.getLineHeight();
    FoldManager fm = host.getFoldManager();

    int n = getViewCount(); // Number of lines.
    int x = alloc.x + getLeftInset();
    tempRect.y = alloc.y + getTopInset();
    Rectangle clip = g.getClipBounds();
    for (int i = 0; i < n; i++) {
      tempRect.x = x + getOffset(X_AXIS, i);
      // tempRect.y = y + getOffset(Y_AXIS, i);
      tempRect.width = getSpan(X_AXIS, i);
      tempRect.height = getSpan(Y_AXIS, i);
      // System.err.println("For line " + i + ": tempRect==" + tempRect);
      if (tempRect.intersects(clip)) {
        View view = getView(i);
        drawView(g2d, alloc, view, fontHeight, tempRect.y + ascent);
      }
      tempRect.y += tempRect.height;
      Fold possibleFold = fm.getFoldForLine(i);
      if (possibleFold != null && possibleFold.isCollapsed()) {
        i += possibleFold.getCollapsedLineCount();
        // Visible indicator of collapsed lines
        Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
        if (c != null) {
          g.setColor(c);
          g.drawLine(x, tempRect.y - 1, alloc.width, tempRect.y - 1);
        }
      }
    }
  }
Example #12
0
  /**
   * Sets whether this <code>Fold</code> is collapsed. Calling this method will update both the text
   * area and all <code>Gutter</code> components.
   *
   * @param collapsed Whether this fold should be collapsed.
   * @see #isCollapsed()
   * @see #toggleCollapsedState()
   */
  public void setCollapsed(boolean collapsed) {

    if (collapsed != this.collapsed) {

      // Change our fold state and cached info about folded line count.
      int lineCount = getLineCount();
      int linesToCollapse = lineCount - childCollapsedLineCount;
      if (!collapsed) { // If we're expanding
        linesToCollapse = -linesToCollapse;
      }
      // System.out.println("Hiding lines: " + linesToCollapse +
      //		" (" + lineCount + ", " + linesToCollapse + ")");
      this.collapsed = collapsed;
      if (parent != null) {
        parent.updateChildCollapsedLineCount(linesToCollapse);
      }

      // If an end point of the selection is being hidden, move the caret
      // "out" of the fold.
      if (collapsed) {
        int dot = textArea.getSelectionStart(); // Forgive variable name
        Element root = textArea.getDocument().getDefaultRootElement();
        int dotLine = root.getElementIndex(dot);
        boolean updateCaret = containsLine(dotLine);
        if (!updateCaret) {
          int mark = textArea.getSelectionEnd();
          if (mark != dot) {
            int markLine = root.getElementIndex(mark);
            updateCaret = containsLine(markLine);
          }
        }
        if (updateCaret) {
          dot = root.getElement(getStartLine()).getEndOffset() - 1;
          textArea.setCaretPosition(dot);
        }
      }

      textArea.foldToggled(this);
    }
  }
  /** {@inheritDoc} */
  @Override
  public List<Fold> getFolds(RSyntaxTextArea textArea) {

    List<Fold> folds = new ArrayList<Fold>();

    Fold currentFold = null;
    int lineCount = textArea.getLineCount();
    boolean inMLC = false;
    int mlcStart = 0;
    int importStartLine = -1;
    int lastSeenImportLine = -1;
    int importGroupStartOffs = -1;
    int importGroupEndOffs = -1;
    int lastRightCurlyLine = -1;
    Fold prevFold = null;

    try {

      for (int line = 0; line < lineCount; line++) {

        Token t = textArea.getTokenListForLine(line);
        while (t != null && t.isPaintable()) {

          if (getFoldableMultiLineComments() && t.isComment()) {

            // Java-specific stuff
            if (java) {

              if (importStartLine > -1) {
                if (lastSeenImportLine > importStartLine) {
                  Fold fold = null;
                  // Any imports found *should* be a top-level fold,
                  // but we're extra lenient here and allow groups
                  // of them anywhere to keep our parser better-behaved
                  // if they have random "imports" throughout code.
                  if (currentFold == null) {
                    fold = new Fold(FoldType.IMPORTS, textArea, importGroupStartOffs);
                    folds.add(fold);
                  } else {
                    fold = currentFold.createChild(FoldType.IMPORTS, importGroupStartOffs);
                  }
                  fold.setEndOffset(importGroupEndOffs);
                }
                importStartLine =
                    lastSeenImportLine = importGroupStartOffs = importGroupEndOffs = -1;
              }
            }

            if (inMLC) {
              // If we found the end of an MLC that started
              // on a previous line...
              if (t.endsWith(C_MLC_END)) {
                int mlcEnd = t.getEndOffset() - 1;
                if (currentFold == null) {
                  currentFold = new Fold(FoldType.COMMENT, textArea, mlcStart);
                  currentFold.setEndOffset(mlcEnd);
                  folds.add(currentFold);
                  currentFold = null;
                } else {
                  currentFold = currentFold.createChild(FoldType.COMMENT, mlcStart);
                  currentFold.setEndOffset(mlcEnd);
                  currentFold = currentFold.getParent();
                }
                // System.out.println("Ending MLC at: " + mlcEnd + ", parent==" + currentFold);
                inMLC = false;
                mlcStart = 0;
              }
              // Otherwise, this MLC is continuing on to yet
              // another line.
            } else {
              // If we're an MLC that ends on a later line...
              if (t.getType() != Token.COMMENT_EOL && !t.endsWith(C_MLC_END)) {
                // System.out.println("Starting MLC at: " + t.offset);
                inMLC = true;
                mlcStart = t.getOffset();
              }
            }

          } else if (isLeftCurly(t)) {

            // Java-specific stuff
            if (java) {

              if (importStartLine > -1) {
                if (lastSeenImportLine > importStartLine) {
                  Fold fold = null;
                  // Any imports found *should* be a top-level fold,
                  // but we're extra lenient here and allow groups
                  // of them anywhere to keep our parser better-behaved
                  // if they have random "imports" throughout code.
                  if (currentFold == null) {
                    fold = new Fold(FoldType.IMPORTS, textArea, importGroupStartOffs);
                    folds.add(fold);
                  } else {
                    fold = currentFold.createChild(FoldType.IMPORTS, importGroupStartOffs);
                  }
                  fold.setEndOffset(importGroupEndOffs);
                }
                importStartLine =
                    lastSeenImportLine = importGroupStartOffs = importGroupEndOffs = -1;
              }
            }

            // If a new fold block starts on the same line as the
            // previous one ends, we treat it as one big block
            // (e.g. K&R-style "} else {")
            if (prevFold != null && line == lastRightCurlyLine) {
              currentFold = prevFold;
              // Keep currentFold.endOffset where it was, so that
              // unclosed folds at end of the file work as well
              // as possible
              prevFold = null;
              lastRightCurlyLine = -1;
            } else if (currentFold == null) { // A top-level fold
              currentFold = new Fold(FoldType.CODE, textArea, t.getOffset());
              folds.add(currentFold);
            } else { // A nested fold
              currentFold = currentFold.createChild(FoldType.CODE, t.getOffset());
            }

          } else if (isRightCurly(t)) {

            if (currentFold != null) {
              currentFold.setEndOffset(t.getOffset());
              Fold parentFold = currentFold.getParent();
              // System.out.println("... Adding regular fold at " + t.offset + ", parent==" +
              // parentFold);
              // Don't add fold markers for single-line blocks
              if (currentFold.isOnSingleLine()) {
                if (!currentFold.removeFromParent()) {
                  folds.remove(folds.size() - 1);
                }
              } else {
                // Remember the end of the last completed fold,
                // in case it needs to get merged with the next
                // one (e.g. K&R "} else {" style)
                lastRightCurlyLine = line;
                prevFold = currentFold;
              }
              currentFold = parentFold;
            }

          }

          // Java-specific folding rules
          else if (java) {

            if (t.is(Token.RESERVED_WORD, KEYWORD_IMPORT)) {
              if (importStartLine == -1) {
                importStartLine = line;
                importGroupStartOffs = t.getOffset();
                importGroupEndOffs = t.getOffset();
              }
              lastSeenImportLine = line;
            } else if (importStartLine > -1
                && t.isIdentifier()
                && // SEPARATOR &&
                t.isSingleChar(';')) {
              importGroupEndOffs = t.getOffset();
            }
          }

          t = t.getNextToken();
        }
      }

    } catch (BadLocationException ble) { // Should never happen
      ble.printStackTrace();
    }

    return folds;
  }
  /**
   * Paints folding icons when line wrapping is enabled.
   *
   * @param g The graphics context.
   */
  private void paintComponentWrapped(Graphics g) {

    // The variables we use are as follows:
    // - visibleRect is the "visible" area of the text area; e.g.
    // [0,100, 300,100+(lineCount*cellHeight)-1].
    // actualTop.y is the topmost-pixel in the first logical line we
    // paint.  Note that we may well not paint this part of the logical
    // line, as it may be broken into many physical lines, with the first
    // few physical lines scrolled past.  Note also that this is NOT the
    // visible rect of this line number list; this line number list has
    // visible rect == [0,0, insets.left-1,visibleRect.height-1].
    // - offset (<=0) is the y-coordinate at which we begin painting when
    // we begin painting with the first logical line.  This can be
    // negative, signifying that we've scrolled past the actual topmost
    // part of this line.

    // The algorithm is as follows:
    // - Get the starting y-coordinate at which to paint.  This may be
    //   above the first visible y-coordinate as we're in line-wrapping
    //   mode, but we always paint entire logical lines.
    // - Paint that line's indicator, if appropriate.  Increment y to be
    //   just below the are we just painted (i.e., the beginning of the
    //   next logical line's view area).
    // - Get the ending visual position for that line.  We can now loop
    //   back, paint this line, and continue until our y-coordinate is
    //   past the last visible y-value.

    // We avoid using modelToView/viewToModel where possible, as these
    // methods trigger a parsing of the line into syntax tokens, which is
    // costly.  It's cheaper to just grab the child views' bounds.

    // Some variables we'll be using.
    int width = getWidth();

    RTextAreaUI ui = (RTextAreaUI) textArea.getUI();
    View v = ui.getRootView(textArea).getView(0);
    Document doc = textArea.getDocument();
    Element root = doc.getDefaultRootElement();
    int topPosition = textArea.viewToModel(new Point(visibleRect.x, visibleRect.y));
    int topLine = root.getElementIndex(topPosition);
    int cellHeight = textArea.getLineHeight();
    FoldManager fm = ((RSyntaxTextArea) textArea).getFoldManager();

    // Compute the y at which to begin painting text, taking into account
    // that 1 logical line => at least 1 physical line, so it may be that
    // y<0.  The computed y-value is the y-value of the top of the first
    // (possibly) partially-visible view.
    Rectangle visibleEditorRect = ui.getVisibleEditorRect();
    Rectangle r = LineNumberList.getChildViewBounds(v, topLine, visibleEditorRect);
    int y = r.y;
    y += (cellHeight - collapsedFoldIcon.getIconHeight()) / 2;

    int visibleBottom = visibleRect.y + visibleRect.height;
    int x = width - 10;
    int line = topLine;
    boolean paintingOutlineLine =
        foldWithOutlineShowing != null && foldWithOutlineShowing.containsLine(line);
    int lineCount = root.getElementCount();

    while (y < visibleBottom && line < lineCount) {

      int curLineH = LineNumberList.getChildViewBounds(v, line, visibleEditorRect).height;

      if (paintingOutlineLine) {
        g.setColor(getForeground());
        int w2 = width / 2;
        if (line == foldWithOutlineShowing.getEndLine()) {
          int y2 = y + curLineH - cellHeight / 2;
          g.drawLine(w2, y, w2, y2);
          g.drawLine(w2, y2, width - 2, y2);
          paintingOutlineLine = false;
        } else {
          g.drawLine(w2, y, w2, y + curLineH);
        }
      }
      Fold fold = fm.getFoldForLine(line);
      if (fold != null) {
        if (fold == foldWithOutlineShowing && !fold.isCollapsed()) {
          g.setColor(getForeground());
          int w2 = width / 2;
          g.drawLine(w2, y + cellHeight / 2, w2, y + curLineH);
          paintingOutlineLine = true;
        }
        if (fold.isCollapsed()) {
          collapsedFoldIcon.paintIcon(this, g, x, y);
          y += LineNumberList.getChildViewBounds(v, line, visibleEditorRect).height;
          line += fold.getLineCount() + 1;
        } else {
          expandedFoldIcon.paintIcon(this, g, x, y);
          y += curLineH;
          line++;
        }
      } else {
        y += curLineH;
        line++;
      }
    }
  }
  protected void paintComponent(Graphics g) {

    if (textArea == null) {
      return;
    }

    visibleRect = g.getClipBounds(visibleRect);
    if (visibleRect == null) { // ???
      visibleRect = getVisibleRect();
    }
    // System.out.println("IconRowHeader repainting: " + visibleRect);
    if (visibleRect == null) {
      return;
    }

    Color bg = getBackground();
    if (getGutter() != null) { // Should always be true
      bg = getGutter().getBackground();
    }
    g.setColor(bg);
    g.fillRect(0, visibleRect.y, getWidth(), visibleRect.height);

    RSyntaxTextArea rsta = (RSyntaxTextArea) textArea;
    if (!rsta.isCodeFoldingEnabled()) {
      return; // We should be hidden in this case, but still...
    }

    if (textArea.getLineWrap()) {
      paintComponentWrapped(g);
      return;
    }

    // Get where to start painting (top of the row).
    // We need to be "scrolled up" up just enough for the missing part of
    // the first line.
    int cellHeight = textArea.getLineHeight();
    int topLine = visibleRect.y / cellHeight;
    int y = topLine * cellHeight + (cellHeight - collapsedFoldIcon.getIconHeight()) / 2;
    textAreaInsets = textArea.getInsets(textAreaInsets);
    if (textAreaInsets != null) {
      y += textAreaInsets.top;
    }

    // Get the first and last lines to paint.
    FoldManager fm = rsta.getFoldManager();
    topLine += fm.getHiddenLineCountAbove(topLine, true);

    int width = getWidth();
    int x = width - 10;
    int line = topLine;
    boolean paintingOutlineLine =
        foldWithOutlineShowing != null && foldWithOutlineShowing.containsLine(line);

    while (y < visibleRect.y + visibleRect.height) {
      if (paintingOutlineLine) {
        g.setColor(getForeground());
        int w2 = width / 2;
        if (line == foldWithOutlineShowing.getEndLine()) {
          int y2 = y + cellHeight / 2;
          g.drawLine(w2, y, w2, y2);
          g.drawLine(w2, y2, width - 2, y2);
          paintingOutlineLine = false;
        } else {
          g.drawLine(w2, y, w2, y + cellHeight);
        }
      }
      Fold fold = fm.getFoldForLine(line);
      if (fold != null) {
        if (fold == foldWithOutlineShowing && !fold.isCollapsed()) {
          g.setColor(getForeground());
          int w2 = width / 2;
          g.drawLine(w2, y + cellHeight / 2, w2, y + cellHeight);
          paintingOutlineLine = true;
        }
        if (fold.isCollapsed()) {
          collapsedFoldIcon.paintIcon(this, g, x, y);
          // Skip to next line to paint, taking extra care for lines with
          // block ends and begins together, e.g. "} else {"
          do {
            line += fold.getLineCount();
            fold = fm.getFoldForLine(line);
          } while (fold != null && fold.isCollapsed());
        } else {
          expandedFoldIcon.paintIcon(this, g, x, y);
        }
      }
      line++;
      y += cellHeight;
    }
  }
  /**
   * Actually paints the text area. Only lines that have been damaged are repainted.
   *
   * @param g The graphics context with which to paint.
   * @param a The allocated region in which to render.
   */
  @Override
  public void paint(Graphics g, Shape a) {

    RSyntaxDocument document = (RSyntaxDocument) getDocument();

    Rectangle alloc = a.getBounds();

    tabBase = alloc.x;
    host = (RSyntaxTextArea) getContainer();

    Rectangle clip = g.getClipBounds();
    // An attempt to speed things up for files with long lines.  Note that
    // this will actually slow things down a bit for the common case of
    // regular-length lines, but it doesn't make a perceivable difference.
    clipStart = clip.x;
    clipEnd = clipStart + clip.width;

    lineHeight = host.getLineHeight();
    ascent = host.getMaxAscent(); // metrics.getAscent();
    int heightAbove = clip.y - alloc.y;
    int linesAbove = Math.max(0, heightAbove / lineHeight);

    FoldManager fm = host.getFoldManager();
    linesAbove += fm.getHiddenLineCountAbove(linesAbove, true);
    Rectangle lineArea = lineToRect(a, linesAbove);
    int y = lineArea.y + ascent;
    int x = lineArea.x;
    Element map = getElement();
    int lineCount = map.getElementCount();

    // Whether token styles should always be painted, even in selections
    int selStart = host.getSelectionStart();
    int selEnd = host.getSelectionEnd();

    RSyntaxTextAreaHighlighter h = (RSyntaxTextAreaHighlighter) host.getHighlighter();

    Graphics2D g2d = (Graphics2D) g;
    Token token;
    // System.err.println("Painting lines: " + linesAbove + " to " + (endLine-1));

    TokenPainter painter = host.getTokenPainter();
    int line = linesAbove;
    // int count = 0;
    while (y < clip.y + clip.height + ascent && line < lineCount) {

      Fold fold = fm.getFoldForLine(line);
      Element lineElement = map.getElement(line);
      int startOffset = lineElement.getStartOffset();
      // int endOffset = (line==lineCount ? lineElement.getEndOffset()-1 :
      //							lineElement.getEndOffset()-1);
      int endOffset = lineElement.getEndOffset() - 1; // Why always "-1"?
      h.paintLayeredHighlights(g2d, startOffset, endOffset, a, host, this);

      // Paint a line of text.
      token = document.getTokenListForLine(line);
      if (selStart == selEnd || startOffset >= selEnd || endOffset < selStart) {
        drawLine(painter, token, g2d, x, y, line);
      } else {
        // System.out.println("Drawing line with selection: " + line);
        drawLineWithSelection(painter, token, g2d, x, y, selStart, selEnd);
      }

      if (fold != null && fold.isCollapsed()) {

        // Visible indicator of collapsed lines
        Color c = RSyntaxUtilities.getFoldedLineBottomColor(host);
        if (c != null) {
          g.setColor(c);
          g.drawLine(x, y + lineHeight - ascent - 1, host.getWidth(), y + lineHeight - ascent - 1);
        }

        // Skip to next line to paint, taking extra care for lines with
        // block ends and begins together, e.g. "} else {"
        do {
          int hiddenLineCount = fold.getLineCount();
          if (hiddenLineCount == 0) {
            // Fold parser identified a zero-line fold region.
            // This is really a bug, but we'll be graceful here
            // and avoid an infinite loop.
            break;
          }
          line += hiddenLineCount;
          fold = fm.getFoldForLine(line);
        } while (fold != null && fold.isCollapsed());
      }

      y += lineHeight;
      line++;
      // count++;

    }

    // System.out.println("SyntaxView: lines painted=" + count);

  }