/**
   * Modifies the passed-in token list to start at the specified offset. For example, if the token
   * list covered positions 20-60 in the document (inclusive) like so:
   *
   * <pre>
   *   [token1] -> [token2] -> [token3] -> [token4]
   *   20     30   31     40   41     50   51     60
   * </pre>
   *
   * and you used this method to make the token list start at position 44, then the token list would
   * be modified to be the following:
   *
   * <pre>
   *   [part-of-old-token3] -> [token4]
   *   44                 50   51     60
   * </pre>
   *
   * Tokens that come before the specified position are forever lost, and the token containing that
   * position is made to begin at that position if necessary. All token types remain the same as
   * they were originally.
   *
   * <p>This method can be useful if you are only interested in part of a token list (i.e., the line
   * it represents), but you don't want to modify the token list yourself.
   *
   * @param tokenList The list to make start at the specified position. This parameter is modified.
   * @param pos The position at which the new token list is to start. If this position is not in the
   *     passed-in token list, returned token list will either be <code>null</code> or the
   *     unpaintable token(s) at the end of the passed-in token list.
   * @param e How to expand tabs.
   * @param textArea The text area from which the token list came.
   * @param x0 The initial x-pixel position of the old token list.
   * @return The width, in pixels, of the part of the token list "removed from the front." This way,
   *     you know the x-offset of the "new" token list.
   */
  public static float makeTokenListStartAt(
      Token tokenList, int pos, TabExpander e, final RSyntaxTextArea textArea, float x0) {

    Token t = tokenList;

    // 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);
      t = t.getNextToken();
    }

    // Make the token that contains pos start at pos.
    if (t != null && t.isPaintable() && t.offset != pos) {
      // Number of chars between p0 and token start.
      int difference = pos - t.offset;
      x0 += t.getWidthUpTo(t.textCount - difference + 1, textArea, e, x0);
      t.makeStartAt(pos);
    }

    // Make the passed-in token list point to the proper place.
    // t can be null, for example, if line ends with unended MLC.
    if (t != null && t.isPaintable()) tokenList.copyFrom(t);
    else tokenList = null;
    t = null;

    // Return the x-offset (in pixels) of the newly-modified t.
    return x0;
  }
Exemple #2
0
 public Token getLastPaintableToken() {
   Token t = this;
   while (t.isPaintable()) {
     Token next = t.getNextToken();
     if (next == null || !next.isPaintable()) {
       return t;
     }
     t = next;
   }
   return null;
 }
 /**
  * Determines the width of the given token list taking tabs into consideration. This is
  * implemented in a 1.1 style coordinate system where ints are used and 72dpi is assumed.
  *
  * <p>
  *
  * @param tokenList The token list list representing the text.
  * @param textArea The text area in which this token list resides.
  * @param e The tab expander. This value cannot be <code>null</code>.
  * @param x0 The x-pixel coordinate of the start of the token list.
  * @return The width of the token list, in pixels.
  * @see #getTokenListWidthUpTo
  */
 public static final float getTokenListWidth(
     final Token tokenList, RSyntaxTextArea textArea, TabExpander e, float x0) {
   float width = x0;
   for (Token t = tokenList; t != null && t.isPaintable(); t = t.getNextToken()) {
     width += t.getWidth(textArea, e, width);
   }
   return width - x0;
 }
  /**
   * Modifies the passed-in token list to start at the specified offset. For example, if the token
   * list covered positions 20-60 in the document (inclusive) like so:
   *
   * <pre>
   *   [token1] -> [token2] -> [token3] -> [token4]
   *   20     30   31     40   41     50   51     60
   * </pre>
   *
   * and you used this method to make the token list start at position 44, then the token list would
   * be modified to be the following:
   *
   * <pre>
   *   [part-of-old-token3] -> [token4]
   *   44                 50   51     60
   * </pre>
   *
   * Tokens that come before the specified position are forever lost, and the token containing that
   * position is made to begin at that position if necessary. All token types remain the same as
   * they were originally.
   *
   * <p>This method can be useful if you are only interested in part of a token list (i.e., the line
   * it represents), but you don't want to modify the token list yourself.
   *
   * @param tokenList The list to make start at the specified position. This parameter is modified.
   * @param pos The position at which the new token list is to start. If this position is not in the
   *     passed-in token list, returned token list will either be <code>null</code> or the
   *     unpaintable token(s) at the end of the passed-in token list.
   * @param e How to expand tabs.
   * @param textArea The text area from which the token list came.
   * @param x0 The initial x-pixel position of the old token list.
   * @param tempToken A temporary token to use when creating the token list result. This may be
   *     <code>null</code> but callers can pass in a "buffer" token for performance if desired.
   * @return Information about the "sub" token list. This will be <code>null</code> if <code>pos
   *     </code> was not a valid offset into the token list.
   * @see #getSubTokenList(Token, int, TabExpander, RSyntaxTextArea, float)
   */
  public static TokenSubList getSubTokenList(
      Token tokenList,
      int pos,
      TabExpander e,
      final RSyntaxTextArea textArea,
      float x0,
      TokenImpl tempToken) {

    if (tempToken == null) {
      tempToken = new TokenImpl();
    }
    Token t = tokenList;

    // 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);
      t = t.getNextToken();
    }

    // Make the token that contains pos start at pos.
    if (t != null && t.isPaintable()) {

      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);
        tempToken.copyFrom(t);
        tempToken.makeStartAt(pos);

        return new TokenSubList(tempToken, x0);

      } else { // t.getOffset()==pos
        return new TokenSubList(t, x0);
      }
    }

    // This could be a null token, so we need to just return it.
    return new TokenSubList(tokenList, x0);
    // return null;

  }
 /**
  * Determines the width of the given token list taking tabs into consideration and only up to the
  * given index in the document (exclusive).
  *
  * @param tokenList The token list representing the text.
  * @param textArea The text area in which this token list resides.
  * @param e The tab expander. This value cannot be <code>null</code>.
  * @param x0 The x-pixel coordinate of the start of the token list.
  * @param upTo The document position at which you want to stop, exclusive. If this position is
  *     before the starting position of the token list, a width of <code>0</code> will be returned;
  *     similarly, if this position comes after the entire token list, the width of the entire
  *     token list is returned.
  * @return The width of the token list, in pixels, up to, but not including, the character at
  *     position <code>upTo</code>.
  * @see #getTokenListWidth
  */
 public static final float getTokenListWidthUpTo(
     final Token tokenList, RSyntaxTextArea textArea, TabExpander e, float x0, int upTo) {
   float width = 0;
   for (Token t = tokenList; t != null && t.isPaintable(); t = t.getNextToken()) {
     if (t.containsPosition(upTo)) {
       return width + t.getWidthUpTo(upTo - t.offset, textArea, e, x0 + width);
     }
     width += t.getWidth(textArea, e, x0 + width);
   }
   return width;
 }
  /**
   * This is called by the nested wrapped line views to determine the break location. This can be
   * reimplemented to alter the breaking behavior. It will either break at word or character
   * boundaries depending upon the break argument given at construction.
   */
  protected int calculateBreakPosition(int p0, Token tokenList, float x0) {
    // System.err.println("------ beginning calculateBreakPosition() --------");
    int p = p0;
    RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer();
    float currentWidth = getWidth();
    if (currentWidth == Integer.MAX_VALUE) currentWidth = getPreferredSpan(X_AXIS);
    // Make sure width>0; this is a huge hack to fix a bug where
    // loading text into an RTextArea before it is visible if word wrap
    // is enabled causes an infinite loop in calculateBreakPosition()
    // because of the 0-width!  We cannot simply check in setSize()
    // because the width is set to 0 somewhere else too somehow...
    currentWidth = Math.max(currentWidth, MIN_WIDTH);
    Token t = tokenList;
    while (t != null && t.isPaintable()) {
      // 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);
      if (tokenWidth > currentWidth) {
        // If the current token alone is too long for this line,
        // break at a character boundary.
        if (p == p0) {
          return t.getOffsetBeforeX(textArea, this, 0, currentWidth);
        }
        // Return the first non-whitespace char (i.e., don't start
        // off the continuation of a wrapped line with whitespace).
        return t.isWhitespace() ? p + t.length() : p;
        // return getBreakLocation(t, fm, x0, currentWidth, this);
      }
      currentWidth -= tokenWidth;
      x0 += tokenWidth;
      p += t.length();
      // System.err.println("*** *** *** token fit entirely (width==" + tokenWidth + "), adding " +
      // t.textCount + " to p, now p==" + p);
      t = t.getNextToken();
    }
    // System.err.println("... ... whole line fits; returning p==" + p);
    // System.err.println("------ ending calculateBreakPosition() --------");

    //		return p;
    return p + 1;
  }
Exemple #7
0
  public Token getLastNonCommentNonWhitespaceToken() {

    Token last = null;

    for (Token t = this; t != null && t.isPaintable(); t = t.getNextToken()) {
      switch (t.getType()) {
        case COMMENT_DOCUMENTATION:
        case COMMENT_EOL:
        case COMMENT_MULTILINE:
        case COMMENT_KEYWORD:
        case COMMENT_MARKUP:
        case WHITESPACE:
          break;
        default:
          last = t;
          break;
      }
    }

    return last;
  }
  /**
   * Draws the passed-in text using syntax highlighting for the current language. It is assumed that
   * the entire line is either not in a selected region, or painting with a selection-foreground
   * color is turned off.
   *
   * @param painter The painter to render the tokens.
   * @param token The list of tokens to draw.
   * @param g The graphics context in which to draw.
   * @param x The x-coordinate at which to draw.
   * @param y The y-coordinate at which to draw.
   * @return The x-coordinate representing the end of the painted text.
   */
  private float drawLine(
      TokenPainter painter, Token token, Graphics2D g, float x, float y, int line) {

    float nextX = x; // The x-value at the end of our text.
    boolean paintBG = host.getPaintTokenBackgrounds(line, y);

    while (token != null && token.isPaintable() && nextX < clipEnd) {
      nextX = painter.paint(token, g, nextX, y, host, this, clipStart, paintBG);
      token = token.getNextToken();
    }

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", nextX, y);
    }

    // Return the x-coordinate at the end of the painted text.
    return nextX;
  }
  /**
   * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple
   * lines if necessary. Any selected text is rendered with the editor's "selected text" color.
   *
   * @param painter The painter to use to render tokens.
   * @param g The graphics context in which to paint.
   * @param r The rectangle in which to paint.
   * @param view The <code>View</code> to paint.
   * @param fontHeight The height of the font being used.
   * @param y The y-coordinate at which to begin painting.
   * @param selStart The start of the selection.
   * @param selEnd The end of the selection.
   */
  protected void drawViewWithSelection(
      TokenPainter painter,
      Graphics2D g,
      Rectangle r,
      View view,
      int fontHeight,
      int y,
      int selStart,
      int selEnd) {

    float x = r.x;

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

    RSyntaxDocument document = (RSyntaxDocument) getDocument();
    Element map = getElement();

    int p0 = view.getStartOffset();
    int lineNumber = map.getElementIndex(p0);
    int p1 = view.getEndOffset(); // - 1;

    setSegment(p0, p1 - 1, document, drawSeg);
    // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")");
    int start = p0 - drawSeg.offset;
    Token token = document.getTokenListForLine(lineNumber);

    // If this line is an empty line, then the token list is simply a
    // null token.  In this case, the line highlight will be skipped in
    // the loop below, so unfortunately we must manually do it here.
    if (token != null && token.getType() == Token.NULL) {
      h.paintLayeredHighlights(g, p0, p1, r, host, this);
      return;
    }

    // Loop through all tokens in this view and paint them!
    while (token != null && token.isPaintable()) {

      int p = calculateBreakPosition(p0, token, x);
      x = r.x;

      h.paintLayeredHighlights(g, p0, p, r, host, this);

      while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) {

        // 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);
          }

          int selCount = Math.min(token.length(), selEnd - token.getOffset());
          if (selCount == token.length()) {
            x = painter.paintSelected(token, g, x, y, host, this);
          } else {
            tempToken.copyFrom(token);
            tempToken.textCount = selCount;
            x = painter.paintSelected(tempToken, g, x, y, host, this);
            tempToken.textCount = token.length();
            tempToken.makeStartAt(token.getOffset() + selCount);
            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);
          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);
        }

        // This token is entirely unselected
        else {
          x = painter.paint(token, g, x, y, host, this);
        }

        token = token.getNextToken();
      }

      // If there's a token that's going to be split onto the next line
      if (token != null && token.isPaintable() && token.getOffset() < p) {

        int tokenOffset = token.getOffset();
        Token orig = token;
        token =
            new TokenImpl(
                drawSeg, tokenOffset - start, p - 1 - start, tokenOffset, token.getType());

        // 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);
          }

          int selCount = Math.min(token.length(), selEnd - token.getOffset());
          if (selCount == token.length()) {
            x = painter.paintSelected(token, g, x, y, host, this);
          } else {
            tempToken.copyFrom(token);
            tempToken.textCount = selCount;
            x = painter.paintSelected(tempToken, g, x, y, host, this);
            tempToken.textCount = token.length();
            tempToken.makeStartAt(token.getOffset() + selCount);
            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);
          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);
        }

        // This token is entirely unselected
        else {
          x = painter.paint(token, g, x, y, host, this);
        }

        token = new TokenImpl(orig);
        ((TokenImpl) token).makeStartAt(p);
      }

      p0 = (p == p0) ? p1 : p;
      y += fontHeight;
    } // End of while (token!=null && token.isPaintable()).

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", x, y - fontHeight);
    }
  }
Exemple #10
0
  /**
   * Draws a single view (i.e., a line of text for a wrapped view), wrapping the text onto multiple
   * lines if necessary.
   *
   * @param painter The painter to use to render tokens.
   * @param g The graphics context in which to paint.
   * @param r The rectangle in which to paint.
   * @param view The <code>View</code> to paint.
   * @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, View view, int fontHeight, int y) {

    float x = r.x;

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

    RSyntaxDocument document = (RSyntaxDocument) getDocument();
    Element map = getElement();

    int p0 = view.getStartOffset();
    int lineNumber = map.getElementIndex(p0);
    int p1 = view.getEndOffset(); // - 1;

    setSegment(p0, p1 - 1, document, drawSeg);
    // System.err.println("drawSeg=='" + drawSeg + "' (p0/p1==" + p0 + "/" + p1 + ")");
    int start = p0 - drawSeg.offset;
    Token token = document.getTokenListForLine(lineNumber);

    // If this line is an empty line, then the token list is simply a
    // null token.  In this case, the line highlight will be skipped in
    // the loop below, so unfortunately we must manually do it here.
    if (token != null && token.getType() == Token.NULL) {
      h.paintLayeredHighlights(g, p0, p1, r, host, this);
      return;
    }

    // Loop through all tokens in this view and paint them!
    while (token != null && token.isPaintable()) {

      int p = calculateBreakPosition(p0, token, x);
      x = r.x;

      h.paintLayeredHighlights(g, p0, p, r, host, this);

      while (token != null && token.isPaintable() && token.getEndOffset() - 1 < p) { // <=p) {
        x = painter.paint(token, g, x, y, host, this);
        token = token.getNextToken();
      }

      if (token != null && token.isPaintable() && token.getOffset() < p) {
        int tokenOffset = token.getOffset();
        tempToken.set(
            drawSeg.array, tokenOffset - start, p - 1 - start, tokenOffset, token.getType());
        painter.paint(tempToken, g, x, y, host, this);
        tempToken.copyFrom(token);
        tempToken.makeStartAt(p);
        token = new TokenImpl(tempToken);
      }

      p0 = (p == p0) ? p1 : p;
      y += fontHeight;
    } // End of while (token!=null && token.isPaintable()).

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", x, y - fontHeight);
    }
  }
  /**
   * Draws the passed-in text using syntax highlighting for the current language. Tokens are checked
   * for being in a selected region, and are rendered appropriately if they are.
   *
   * @param painter The painter to render the tokens.
   * @param token The list of tokens to draw.
   * @param g The graphics context in which to draw.
   * @param x The x-coordinate at which to draw.
   * @param y The y-coordinate at which to draw.
   * @param selStart The start of the selection.
   * @param selEnd The end of the selection.
   * @return The x-coordinate representing the end of the painted text.
   */
  private float drawLineWithSelection(
      TokenPainter painter, Token token, Graphics2D g, float x, float y, int selStart, int selEnd) {

    float nextX = x; // The x-value at the end of our text.
    boolean useSTC = host.getUseSelectedTextColor();

    while (token != null && token.isPaintable() && nextX < clipEnd) {

      // Selection starts in this token
      if (token.containsPosition(selStart)) {

        if (selStart > token.getOffset()) {
          tempToken.copyFrom(token);
          tempToken.textCount = selStart - tempToken.getOffset();
          nextX = painter.paint(tempToken, g, nextX, y, host, this, clipStart);
          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 tokenLen = token.length();
        int selCount = Math.min(tokenLen, selEnd - token.getOffset());
        if (selCount == tokenLen) {
          nextX = painter.paintSelected(token, g, nextX, y, host, this, clipStart, useSTC);
        } else {
          tempToken.copyFrom(token);
          tempToken.textCount = selCount;
          nextX = painter.paintSelected(tempToken, g, nextX, y, host, this, clipStart, useSTC);
          tempToken.textCount = token.length();
          tempToken.makeStartAt(token.getOffset() + selCount);
          token = tempToken;
          nextX = painter.paint(token, g, nextX, y, host, this, clipStart);
        }

      }

      // Selection ends in this token
      else if (token.containsPosition(selEnd)) {
        tempToken.copyFrom(token);
        tempToken.textCount = selEnd - tempToken.getOffset();
        nextX = painter.paintSelected(tempToken, g, nextX, y, host, this, clipStart, useSTC);
        tempToken.textCount = token.length();
        tempToken.makeStartAt(selEnd);
        token = tempToken;
        nextX = painter.paint(token, g, nextX, y, host, this, clipStart);
      }

      // This token is entirely selected
      else if (token.getOffset() >= selStart && token.getEndOffset() <= selEnd) {
        nextX = painter.paintSelected(token, g, nextX, y, host, this, clipStart, useSTC);
      }

      // This token is entirely unselected
      else {
        nextX = painter.paint(token, g, nextX, y, host, this, clipStart);
      }

      token = token.getNextToken();
    }

    // NOTE: We should re-use code from Token (paintBackground()) here,
    // but don't because I'm just too lazy.
    if (host.getEOLMarkersVisible()) {
      g.setColor(host.getForegroundForTokenType(Token.WHITESPACE));
      g.setFont(host.getFontForTokenType(Token.WHITESPACE));
      g.drawString("\u00B6", nextX, y);
    }

    // Return the x-coordinate at the end of the painted text.
    return nextX;
  }
  /** {@inheritDoc} */
  public void markOccurrences(
      RSyntaxDocument doc,
      Token t,
      RSyntaxTextAreaHighlighter h,
      MarkOccurrencesHighlightPainter p) {

    char[] lexeme = t.getLexeme().toCharArray();
    int tokenOffs = t.getOffset();
    Element root = doc.getDefaultRootElement();
    int lineCount = root.getElementCount();
    int curLine = root.getElementIndex(t.getOffset());

    // For now, we only check for tags on the current line, for
    // simplicity.  Tags spanning multiple lines aren't common anyway.
    boolean found = false;
    boolean forward = true;
    t = doc.getTokenListForLine(curLine);
    while (t != null && t.isPaintable()) {
      if (t.getType() == Token.MARKUP_TAG_DELIMITER) {
        if (t.isSingleChar('<') && t.getOffset() + 1 == tokenOffs) {
          found = true;
          break;
        } else if (t.is(CLOSE_TAG_START) && t.getOffset() + 2 == tokenOffs) {
          found = true;
          forward = false;
          break;
        }
      }
      t = t.getNextToken();
    }

    if (!found) {
      return;
    }

    if (forward) {

      int depth = 0;
      t = t.getNextToken().getNextToken();

      do {

        while (t != null && t.isPaintable()) {
          if (t.getType() == Token.MARKUP_TAG_DELIMITER) {
            if (t.isSingleChar('<')) {
              depth++;
            } else if (t.is(TAG_SELF_CLOSE)) {
              if (depth > 0) {
                depth--;
              } else {
                return; // No match for this tag
              }
            } else if (t.is(CLOSE_TAG_START)) {
              if (depth > 0) {
                depth--;
              } else {
                Token match = t.getNextToken();
                if (match != null && match.is(lexeme)) {
                  try {
                    int end = match.getOffset() + match.length();
                    h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
                    end = tokenOffs + match.length();
                    h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
                  } catch (BadLocationException ble) {
                    ble.printStackTrace(); // Never happens
                  }
                }
                return; // We're done!
              }
            }
          }
          t = t.getNextToken();
        }

        if (++curLine < lineCount) {
          t = doc.getTokenListForLine(curLine);
        }

      } while (curLine < lineCount);

    } else { // !forward

      Stack<Token> matches = new Stack<Token>();
      boolean inPossibleMatch = false;
      t = doc.getTokenListForLine(curLine);
      final int endBefore = tokenOffs - 2; // Stop before "</".

      do {

        while (t != null && t.getOffset() < endBefore && t.isPaintable()) {
          if (t.getType() == Token.MARKUP_TAG_DELIMITER) {
            if (t.isSingleChar('<')) {
              Token next = t.getNextToken();
              if (next != null) {
                if (next.is(lexeme)) {
                  matches.push(next);
                  inPossibleMatch = true;
                } else {
                  inPossibleMatch = false;
                }
                t = next;
              }
            } else if (t.isSingleChar('>')) {
              inPossibleMatch = false;
            } else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) {
              matches.pop();
            } else if (t.is(CLOSE_TAG_START)) {
              Token next = t.getNextToken();
              if (next != null) {
                // Invalid XML might not have a match
                if (next.is(lexeme) && !matches.isEmpty()) {
                  matches.pop();
                }
                t = next;
              }
            }
          }
          t = t.getNextToken();
        }

        if (!matches.isEmpty()) {
          try {
            Token match = matches.pop();
            int end = match.getOffset() + match.length();
            h.addMarkedOccurrenceHighlight(match.getOffset(), end, p);
            end = tokenOffs + match.length();
            h.addMarkedOccurrenceHighlight(tokenOffs, end, p);
          } catch (BadLocationException ble) {
            ble.printStackTrace(); // Never happens
          }
          return;
        }

        if (--curLine >= 0) {
          t = doc.getTokenListForLine(curLine);
        }

      } while (curLine >= 0);
    }
  }