/** {@inheritDoc} */
  public ParseResult parse(RSyntaxDocument doc, String style) {

    result.clearNotices();
    Element root = doc.getDefaultRootElement();
    result.setParsedLines(0, root.getElementCount() - 1);

    if (spf == null || doc.getLength() == 0) {
      return result;
    }

    try {
      SAXParser sp = spf.newSAXParser();
      Handler handler = new Handler(doc);
      DocumentReader r = new DocumentReader(doc);
      InputSource input = new InputSource(r);
      sp.parse(input, handler);
      r.close();
    } catch (SAXParseException spe) {
      // A fatal parse error - ignore; a ParserNotice was already created.
    } catch (Exception e) {
      // e.printStackTrace(); // Will print if DTD specified and can't be found
      result.addNotice(
          new DefaultParserNotice(this, "Error parsing XML: " + e.getMessage(), 0, -1, -1));
    }

    return result;
  }
  /**
   * Returns the bounding box (in the current view) of a specified position in the model. This
   * method is designed for line-wrapped views to use, as it allows you to specify a "starting
   * position" in the line, from which the x-value is assumed to be zero. The idea is that you
   * specify the first character in a physical line as <code>p0</code>, as this is the character
   * where the x-pixel value is 0.
   *
   * @param textArea The text area containing the text.
   * @param s A segment in which to load the line. This is passed in so we don't have to reallocate
   *     a new <code>Segment</code> for each call.
   * @param p0 The starting position in the physical line in the document.
   * @param p1 The position for which to get the bounding box in the view.
   * @param e How to expand tabs.
   * @param rect The rectangle whose x- and width-values are changed to represent the bounding box
   *     of <code>p1</code>. This is reused to keep from needlessly reallocating Rectangles.
   * @param x0 The x-coordinate (pixel) marking the left-hand border of the text. This is useful if
   *     the text area has a border, for example.
   * @return The bounding box in the view of the character <code>p1</code>.
   * @throws BadLocationException If <code>p0</code> or <code>p1</code> is not a valid location in
   *     the specified text area's document.
   * @throws IllegalArgumentException If <code>p0</code> and <code>p1</code> are not on the same
   *     line.
   */
  public static Rectangle getLineWidthUpTo(
      RSyntaxTextArea textArea, Segment s, int p0, int p1, TabExpander e, Rectangle rect, int x0)
      throws BadLocationException {

    RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();

    // Ensure p0 and p1 are valid document positions.
    if (p0 < 0) throw new BadLocationException("Invalid document position", p0);
    else if (p1 > doc.getLength()) throw new BadLocationException("Invalid document position", p1);

    // Ensure p0 and p1 are in the same line, and get the start/end
    // offsets for that line.
    Element map = doc.getDefaultRootElement();
    int lineNum = map.getElementIndex(p0);
    // We do ">1" because p1 might be the first position on the next line
    // or the last position on the previous one.
    // if (lineNum!=map.getElementIndex(p1))
    if (Math.abs(lineNum - map.getElementIndex(p1)) > 1)
      throw new IllegalArgumentException(
          "p0 and p1 are not on the " + "same line (" + p0 + ", " + p1 + ").");

    // Get the token list.
    Token t = doc.getTokenListForLine(lineNum);

    // Modify the token list 't' to begin at p0 (but still have correct
    // token types, etc.), and get the x-location (in pixels) of the
    // beginning of this new token list.
    makeTokenListStartAt(t, p0, e, textArea, 0);

    rect = t.listOffsetToView(textArea, e, p1, x0, rect);
    return rect;
  }
  /**
   * Provides a mapping from the view coordinate space to the logical coordinate space of the model.
   *
   * @param fx the X coordinate &gt;= 0
   * @param fy the Y coordinate &gt;= 0
   * @param a the allocated region to render into
   * @return the location within the model that best represents the given point in the view &gt;= 0
   */
  @Override
  public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {

    bias[0] = Position.Bias.Forward;

    Rectangle alloc = a.getBounds();
    RSyntaxDocument doc = (RSyntaxDocument) getDocument();
    int x = (int) fx;
    int y = (int) fy;

    // If they're asking about a view position above the area covered by
    // this view, then the position is assumed to be the starting position
    // of this view.
    if (y < alloc.y) {
      return getStartOffset();
    }

    // If they're asking about a position below this view, the position
    // is assumed to be the ending position of this view.
    else if (y > alloc.y + alloc.height) {
      return host.getLastVisibleOffset();
    }

    // They're asking about a position within the coverage of this view
    // vertically.  So, we figure out which line the point corresponds to.
    // If the line is greater than the number of lines contained, then
    // simply use the last line as it represents the last possible place
    // we can position to.
    else {

      Element map = doc.getDefaultRootElement();
      int lineIndex = Math.abs((y - alloc.y) / lineHeight); // metrics.getHeight() );
      FoldManager fm = host.getFoldManager();
      // System.out.print("--- " + lineIndex);
      lineIndex += fm.getHiddenLineCountAbove(lineIndex, true);
      // System.out.println(" => " + lineIndex);
      if (lineIndex >= map.getElementCount()) {
        return host.getLastVisibleOffset();
      }

      Element line = map.getElement(lineIndex);

      // If the point is to the left of the line...
      if (x < alloc.x) {
        return line.getStartOffset();
      } else if (x > alloc.x + alloc.width) {
        return line.getEndOffset() - 1;
      } else {
        // Determine the offset into the text
        int p0 = line.getStartOffset();
        Token tokenList = doc.getTokenListForLine(lineIndex);
        tabBase = alloc.x;
        int offs = tokenList.getListOffset((RSyntaxTextArea) getContainer(), this, tabBase, x);
        return offs != -1 ? offs : p0;
      }
    } // End of else.
  }
    /**
     * Provides a mapping from the document model coordinate space to the coordinate space of the
     * view mapped to it.
     *
     * @param pos the position to convert
     * @param a the allocated region to render into
     * @return the bounding box of the given position is returned
     * @exception BadLocationException if the given position does not represent a valid location in
     *     the associated document.
     */
    @Override
    public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {

      // System.err.println("--- begin modelToView ---");
      Rectangle alloc = a.getBounds();
      RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer();
      alloc.height = textArea.getLineHeight(); // metrics.getHeight();
      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
      // lines.
      RSyntaxDocument doc = (RSyntaxDocument) getDocument();
      Element map = doc.getDefaultRootElement();
      int line = map.getElementIndex(p0);
      Token tokenList = doc.getTokenListForLine(line);
      float x0 = alloc.x; // 0;

      while (p0 < p1) {
        TokenSubList subList =
            TokenUtils.getSubTokenList(
                tokenList, p0, WrappedSyntaxView.this, textArea, x0, lineCountTempToken);
        x0 = subList != null ? subList.x : x0;
        tokenList = subList != null ? subList.tokenList : null;
        int p = calculateBreakPosition(p0, tokenList, x0);
        if ((pos >= p0) && (testP < p)) { // pos < p)) {
          // it's in this line
          alloc =
              RSyntaxUtilities.getLineWidthUpTo(
                  textArea, s, p0, pos, WrappedSyntaxView.this, alloc, alloc.x);
          // System.err.println("--- end modelToView ---");
          return alloc;
        }
        // if (p == p1 && pos == p1) {
        if (p == p1 - 1 && pos == p1 - 1) {
          // Wants end.
          if (pos > p0) {
            alloc =
                RSyntaxUtilities.getLineWidthUpTo(
                    textArea, s, p0, pos, WrappedSyntaxView.this, alloc, alloc.x);
          }
          // System.err.println("--- end modelToView ---");
          return alloc;
        }

        p0 = (p == p0) ? p1 : p;
        // System.err.println("... ... Incrementing y");
        alloc.y += alloc.height;
      }

      throw new BadLocationException(null, pos);
    }
Exemple #5
0
  @Override
  public ParseResult parse(RSyntaxDocument doc, String style) {

    Element root = doc.getDefaultRootElement();
    int lineCount = root.getElementCount();

    if (taskPattern == null || style == null || SyntaxConstants.SYNTAX_STYLE_NONE.equals(style)) {
      result.clearNotices();
      result.setParsedLines(0, lineCount - 1);
      return result;
    }

    // TODO: Pass in parsed line range and just do that
    result.clearNotices();
    result.setParsedLines(0, lineCount - 1);

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

      Token t = doc.getTokenListForLine(line);
      int offs = -1;
      int start = -1;
      String text = null;

      while (t != null && t.isPaintable()) {
        if (t.isComment()) {

          offs = t.offset;
          text = t.getLexeme();

          Matcher m = taskPattern.matcher(text);
          if (m.find()) {
            start = m.start();
            offs += start;
            break;
          }
        }
        t = t.getNextToken();
      }

      if (start > -1) {
        text = text.substring(start);
        // TODO: Strip off end of MLC's if they're there.
        int len = text.length();
        TaskNotice pn = new TaskNotice(this, text, line, offs, len);
        pn.setLevel(ParserNotice.INFO);
        pn.setShowInEditor(false);
        pn.setColor(COLOR);
        result.addNotice(pn);
      }
    }

    return result;
  }
    /** Calculate the number of lines that will be rendered by logical line when it is wrapped. */
    final int calculateLineCount() {

      int nlines = 0;
      int startOffset = getStartOffset();
      int p1 = getEndOffset();

      // Get the token list for this line so we don't have to keep
      // recomputing it if this logical line spans multiple physical
      // lines.
      RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer();
      RSyntaxDocument doc = (RSyntaxDocument) getDocument();
      Element map = doc.getDefaultRootElement();
      int line = map.getElementIndex(startOffset);
      Token tokenList = doc.getTokenListForLine(line);
      float x0 = 0; // FIXME:  should be alloc.x!! alloc.x;//0;

      // System.err.println(">>> calculateLineCount: " + startOffset + "-" + p1);
      for (int p0 = startOffset; p0 < p1; ) {
        // System.err.println("... ... " + p0 + ", " + p1);
        nlines += 1;
        TokenSubList subList =
            TokenUtils.getSubTokenList(
                tokenList, p0, WrappedSyntaxView.this, textArea, x0, lineCountTempToken);
        x0 = subList != null ? subList.x : x0;
        tokenList = subList != null ? subList.tokenList : null;
        int p = calculateBreakPosition(p0, tokenList, x0);

        // System.err.println("... ... ... break position p==" + p);
        p0 = (p == p0) ? ++p : p; // this is the fix of #4410243
        // we check on situation when
        // width is too small and
        // break position is calculated
        // incorrectly.
        // System.err.println("... ... ... new p0==" + p0);
      }
      /*
      int numLines = 0;
      try {
      	numLines = textArea.getLineCount();
      } catch (BadLocationException ble) {
      	ble.printStackTrace();
      }
      System.err.println(">>> >>> calculated number of lines for this view (line " + line + "/" + numLines + ": " + nlines);
      */
      return nlines;
    }
 /**
  * Returns a token list for the <i>physical</i> line above the physical line containing the
  * specified offset into the document. Note that for this plain (non-wrapped) view, this is simply
  * the token list for the logical line above the line containing <code>offset</code>, since lines
  * are not wrapped.
  *
  * @param offset The offset in question.
  * @return A token list for the physical (and in this view, logical) line before this one. If
  *     <code>offset</code> is in the first line in the document, <code>null</code> is returned.
  */
 public Token getTokenListForPhysicalLineAbove(int offset) {
   RSyntaxDocument document = (RSyntaxDocument) getDocument();
   Element map = document.getDefaultRootElement();
   int line = map.getElementIndex(offset);
   FoldManager fm = host.getFoldManager();
   if (fm == null) {
     line--;
     if (line >= 0) {
       return document.getTokenListForLine(line);
     }
   } else {
     line = fm.getVisibleLineAbove(line);
     if (line >= 0) {
       return document.getTokenListForLine(line);
     }
   }
   //		int line = map.getElementIndex(offset) - 1;
   //		if (line>=0)
   //			return document.getTokenListForLine(line);
   return null;
 }
 /**
  * Returns a token list for the <i>physical</i> line below the physical line containing the
  * specified offset into the document. Note that for this plain (non-wrapped) view, this is simply
  * the token list for the logical line below the line containing <code>offset</code>, since lines
  * are not wrapped.
  *
  * @param offset The offset in question.
  * @return A token list for the physical (and in this view, logical) line after this one. If
  *     <code>offset</code> is in the last physical line in the document, <code>null</code> is
  *     returned.
  */
 public Token getTokenListForPhysicalLineBelow(int offset) {
   RSyntaxDocument document = (RSyntaxDocument) getDocument();
   Element map = document.getDefaultRootElement();
   int lineCount = map.getElementCount();
   int line = map.getElementIndex(offset);
   if (!host.isCodeFoldingEnabled()) {
     if (line < lineCount - 1) {
       return document.getTokenListForLine(line + 1);
     }
   } else {
     FoldManager fm = host.getFoldManager();
     line = fm.getVisibleLineBelow(line);
     if (line >= 0 && line < lineCount) {
       return document.getTokenListForLine(line);
     }
   }
   //		int line = map.getElementIndex(offset);
   //		int lineCount = map.getElementCount();
   //		if (line<lineCount-1)
   //			return document.getTokenListForLine(line+1);
   return null;
 }
    /**
     * Provides a mapping from the view coordinate space to the logical coordinate space of the
     * model.
     *
     * @param fx the X coordinate
     * @param fy the Y coordinate
     * @param a the allocated region to render into
     * @return the location within the model that best represents the given point in the view
     * @see View#viewToModel
     */
    @Override
    public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {

      // PENDING(prinz) implement bias properly
      bias[0] = Position.Bias.Forward;

      Rectangle alloc = (Rectangle) a;
      RSyntaxDocument doc = (RSyntaxDocument) getDocument();
      int x = (int) fx;
      int y = (int) fy;
      if (y < alloc.y) {
        // above the area covered by this icon, so the the position
        // is assumed to be the start of the coverage for this view.
        return getStartOffset();
      } else if (y > alloc.y + alloc.height) {
        // below the area covered by this icon, so the the position
        // is assumed to be the end of the coverage for this view.
        return getEndOffset() - 1;
      } else {

        // positioned within the coverage of this view vertically,
        // so we figure out which line the point corresponds to.
        // if the line is greater than the number of lines
        // contained, then simply use the last line as it represents
        // the last possible place we can position to.

        RSyntaxTextArea textArea = (RSyntaxTextArea) getContainer();
        alloc.height = textArea.getLineHeight();
        int p1 = getEndOffset();

        // Get the token list for this line so we don't have to keep
        // recomputing it if this logical line spans multiple
        // physical lines.
        Element map = doc.getDefaultRootElement();
        int p0 = getStartOffset();
        int line = map.getElementIndex(p0);
        Token tlist = doc.getTokenListForLine(line);

        // Look at each physical line-chunk of this logical line.
        while (p0 < p1) {

          // We can always use alloc.x since we always break
          // lines so they start at the beginning of a physical
          // line.
          TokenSubList subList =
              TokenUtils.getSubTokenList(
                  tlist, p0, WrappedSyntaxView.this, textArea, alloc.x, lineCountTempToken);
          tlist = subList != null ? subList.tokenList : null;
          int p = calculateBreakPosition(p0, tlist, alloc.x);

          // If desired view position is in this physical chunk.
          if ((y >= alloc.y) && (y < (alloc.y + alloc.height))) {

            // Point is to the left of the line
            if (x < alloc.x) {
              return p0;
            }

            // Point is to the right of the line
            else if (x > alloc.x + alloc.width) {
              return p - 1;
            }

            // Point is in this physical line!
            else {

              // Start at alloc.x since this chunk starts
              // at the beginning of a physical line.
              int n = tlist.getListOffset(textArea, WrappedSyntaxView.this, alloc.x, x);

              // NOTE:  We needed to add the max() with
              // p0 as getTokenListForLine returns -1
              // for empty lines (just a null token).
              // How did this work before?
              // FIXME:  Have null tokens have their
              // offset but a -1 length.
              return Math.max(Math.min(n, p1 - 1), p0);
            } // End of else.
          } // End of if ((y>=alloc.y) && ...

          p0 = (p == p0) ? p1 : p;
          alloc.y += alloc.height;
        } // End of while (p0<p1).

        return getEndOffset() - 1;
      } // End of else.
    }
  public static int getMatchingBracketPosition(RSyntaxTextArea textArea) {

    try {

      // Actually position just BEFORE caret.
      int caretPosition = textArea.getCaretPosition() - 1;
      if (caretPosition > -1) {

        // Some variables that will be used later.
        Token token;
        Element map;
        int curLine;
        Element line;
        int start, end;
        RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument();
        char bracket = doc.charAt(caretPosition);

        // First, see if the previous char was a bracket
        // ('{', '}', '(', ')', '[', ']').
        // If it was, then make sure this bracket isn't sitting in
        // the middle of a comment or string.  If it isn't, then
        // initialize some stuff so we can continue on.
        char bracketMatch;
        boolean goForward;
        switch (bracket) {
          case '{':
          case '(':
          case '[':

            // Ensure this bracket isn't in a comment.
            map = doc.getDefaultRootElement();
            curLine = map.getElementIndex(caretPosition);
            line = map.getElement(curLine);
            start = line.getStartOffset();
            end = line.getEndOffset();
            token = doc.getTokenListForLine(curLine);
            token = RSyntaxUtilities.getTokenAtOffset(token, caretPosition);
            // All brackets are always returned as "separators."
            if (token.type != Token.SEPARATOR) {
              return -1;
            }
            bracketMatch = bracket == '{' ? '}' : (bracket == '(' ? ')' : ']');
            goForward = true;
            break;

          case '}':
          case ')':
          case ']':

            // Ensure this bracket isn't in a comment.
            map = doc.getDefaultRootElement();
            curLine = map.getElementIndex(caretPosition);
            line = map.getElement(curLine);
            start = line.getStartOffset();
            end = line.getEndOffset();
            token = doc.getTokenListForLine(curLine);
            token = RSyntaxUtilities.getTokenAtOffset(token, caretPosition);
            // All brackets are always returned as "separators."
            if (token.type != Token.SEPARATOR) {
              return -1;
            }
            bracketMatch = bracket == '}' ? '{' : (bracket == ')' ? '(' : '[');
            goForward = false;
            break;

          default:
            return -1;
        }

        if (goForward) {

          int lastLine = map.getElementCount();

          // Start just after the found bracket since we're sure
          // we're not in a comment.
          start = caretPosition + 1;
          int numEmbedded = 0;
          boolean haveTokenList = false;

          while (true) {

            doc.getText(start, end - start, charSegment);
            int segOffset = charSegment.offset;

            for (int i = segOffset; i < segOffset + charSegment.count; i++) {

              char ch = charSegment.array[i];

              if (ch == bracket) {
                if (haveTokenList == false) {
                  token = doc.getTokenListForLine(curLine);
                  haveTokenList = true;
                }
                int offset = start + (i - segOffset);
                token = RSyntaxUtilities.getTokenAtOffset(token, offset);
                if (token.type == Token.SEPARATOR) numEmbedded++;
              } else if (ch == bracketMatch) {
                if (haveTokenList == false) {
                  token = doc.getTokenListForLine(curLine);
                  haveTokenList = true;
                }
                int offset = start + (i - segOffset);
                token = RSyntaxUtilities.getTokenAtOffset(token, offset);
                if (token.type == Token.SEPARATOR) {
                  if (numEmbedded == 0) return offset;
                  numEmbedded--;
                }
              }
            } // End of for (int i=segOffset; i<segOffset+charSegment.count; i++).

            // Bail out if we've gone through all lines and
            // haven't found the match.
            if (++curLine == lastLine) return -1;

            // Otherwise, go through the next line.
            haveTokenList = false;
            line = map.getElement(curLine);
            start = line.getStartOffset();
            end = line.getEndOffset();
          } // End of while (true).

        } // End of if (goForward).

        // Otherwise, we're going backward through the file
        // (since we found '}', ')' or ']').
        else { // goForward==false

          // End just before the found bracket since we're sure
          // we're not in a comment.
          end = caretPosition; // - 1;
          int numEmbedded = 0;
          boolean haveTokenList = false;
          Token t2;

          while (true) {

            doc.getText(start, end - start, charSegment);
            int segOffset = charSegment.offset;
            int iStart = segOffset + charSegment.count - 1;

            for (int i = iStart; i >= segOffset; i--) {

              char ch = charSegment.array[i];

              if (ch == bracket) {
                if (haveTokenList == false) {
                  token = doc.getTokenListForLine(curLine);
                  haveTokenList = true;
                }
                int offset = start + (i - segOffset);
                t2 = RSyntaxUtilities.getTokenAtOffset(token, offset);
                if (t2.type == Token.SEPARATOR) numEmbedded++;
              } else if (ch == bracketMatch) {
                if (haveTokenList == false) {
                  token = doc.getTokenListForLine(curLine);
                  haveTokenList = true;
                }
                int offset = start + (i - segOffset);
                t2 = RSyntaxUtilities.getTokenAtOffset(token, offset);
                if (t2.type == Token.SEPARATOR) {
                  if (numEmbedded == 0) return offset;
                  numEmbedded--;
                }
              }
            } // End of for (int i=segOffset; i<segOffset+charSegment.count; i++).

            // Bail out if we've gone through all lines and
            // haven't found the match.
            if (--curLine == -1) return -1;

            // Otherwise, get ready for going through the
            // next line.
            haveTokenList = false;
            line = map.getElement(curLine);
            start = line.getStartOffset();
            end = line.getEndOffset();
          } // End of while (true).
        } // End of else.
      } // End of if (caretPosition>-1).

    } catch (BadLocationException ble) {
      // Shouldn't ever happen.
      ble.printStackTrace();
    }

    // Something went wrong...
    return -1;
  }
  /** {@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);
    }
  }