/** * 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 >= 0 * @param fy the Y coordinate >= 0 * @param a the allocated region to render into * @return the location within the model that best represents the given point in the view >= 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); }
/** * 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; }
@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; }
/** * 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; }
/** 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; }
/** * 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 >= 0 * @param a the allocated region to render into * @return the bounding box of the given position * @exception BadLocationException if the given position does not represent a valid location in * the associated document * @see View#modelToView */ @Override public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { // line coordinates Element map = getElement(); RSyntaxDocument doc = (RSyntaxDocument) getDocument(); int lineIndex = map.getElementIndex(pos); Token tokenList = doc.getTokenListForLine(lineIndex); Rectangle lineArea = lineToRect(a, lineIndex); tabBase = lineArea.x; // Used by listOffsetToView(). // int x = (int)RSyntaxUtilities.getTokenListWidthUpTo(tokenList, // (RSyntaxTextArea)getContainer(), // this, 0, pos); // We use this method instead as it returns the actual bounding box, // not just the x-coordinate. lineArea = tokenList.listOffsetToView((RSyntaxTextArea) getContainer(), this, pos, tabBase, lineArea); return lineArea; }
/** * 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); } }
/** * 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); } }
/** * 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; }
/** * 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); }
/** {@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); } }