From 30c9c81d38ada7d158935baa9a52c08d1c97aef2 Mon Sep 17 00:00:00 2001 From: SuspiciousActivity <38253929+SuspiciousActivity@users.noreply.github.com> Date: Thu, 26 Nov 2020 17:07:36 +0100 Subject: [PATCH] Fixed crash --- .../fife/ui/rsyntaxtextarea/TokenImpl.java | 1666 +++++++++-------- 1 file changed, 838 insertions(+), 828 deletions(-) diff --git a/src/org/fife/ui/rsyntaxtextarea/TokenImpl.java b/src/org/fife/ui/rsyntaxtextarea/TokenImpl.java index 779baab..4d39498 100644 --- a/src/org/fife/ui/rsyntaxtextarea/TokenImpl.java +++ b/src/org/fife/ui/rsyntaxtextarea/TokenImpl.java @@ -1,828 +1,838 @@ -/* - * 02/21/2004 - * Token.java - A token used in syntax highlighting. - * This library is distributed under a modified BSD license. See the included - * RSyntaxTextArea.License.txt file for details. - */ -package org.fife.ui.rsyntaxtextarea; - -import java.awt.Color; -import java.awt.Font; -import java.awt.FontMetrics; -import java.awt.Rectangle; - -import javax.swing.text.Segment; -import javax.swing.text.TabExpander; -import javax.swing.text.Utilities; - -/** - * The default implementation of {@link Token}. - *
- *
- * Note: The instances of Token returned by
- * {@link RSyntaxDocument}s are pooled and should always be treated as
- * immutable. They should not be cast to TokenImpl and modified.
- * Modifying tokens you did not create yourself can and will result in rendering
- * issues and/or runtime exceptions. You have been warned!
- *
- * @author Robert Futrell
- * @version 0.3
- */
-public class TokenImpl implements Token {
-
- /**
- * The text this token represents. This is implemented as a segment so we can
- * point directly to the text in the document without having to make a copy of
- * it.
- */
- public char[] text;
- public int textOffset;
- public int textCount;
- /**
- * The offset into the document at which this token resides.
- */
- private int offset;
- /**
- * The type of token this is; for example, {@link #FUNCTION}.
- */
- private int type;
- /**
- * Whether this token is a hyperlink.
- */
- private boolean hyperlink;
- /**
- * The next token in this linked list.
- */
- private Token nextToken;
- /**
- * The language this token is in, >= 0.
- */
- private int languageIndex;
-
- /**
- * Creates a "null" token. The token itself is not null; rather, it signifies
- * that it is the last token in a linked list of tokens and that it is not part
- * of a "multi-line token."
- */
- public TokenImpl() {
- this.text = null;
- this.textOffset = -1;
- this.textCount = -1;
- this.setType(NULL);
- setOffset(-1);
- hyperlink = false;
- nextToken = null;
- }
-
- /**
- * Constructor.
- *
- * @param line The segment from which to get the token.
- * @param beg The first character's position in line.
- * @param end The last character's position in line.
- * @param startOffset The offset into the document at which this token begins.
- * @param type A token type listed as "generic" above.
- * @param languageIndex The language index for this token.
- */
- public TokenImpl(Segment line, int beg, int end, int startOffset, int type, int languageIndex) {
- this(line.array, beg, end, startOffset, type, languageIndex);
- }
-
- /**
- * Constructor.
- *
- * @param line The segment from which to get the token.
- * @param beg The first character's position in line.
- * @param end The last character's position in line.
- * @param startOffset The offset into the document at which this token begins.
- * @param type A token type listed as "generic" above.
- * @param languageIndex The language index for this token.
- */
- public TokenImpl(char[] line, int beg, int end, int startOffset, int type, int languageIndex) {
- this();
- set(line, beg, end, startOffset, type);
- setLanguageIndex(languageIndex);
- }
-
- /**
- * Creates this token as a copy of the passed-in token.
- *
- * @param t2 The token from which to make a copy.
- */
- public TokenImpl(Token t2) {
- this();
- copyFrom(t2);
- }
-
- @Override
- public StringBuilder appendHTMLRepresentation(StringBuilder sb, RSyntaxTextArea textArea, boolean fontFamily) {
- return appendHTMLRepresentation(sb, textArea, fontFamily, false);
- }
-
- @Override
- public StringBuilder appendHTMLRepresentation(StringBuilder sb, RSyntaxTextArea textArea, boolean fontFamily,
- boolean tabsToSpaces) {
- SyntaxScheme colorScheme = textArea.getSyntaxScheme();
- Style scheme = colorScheme.getStyle(getType());
- Font font = textArea.getFontForTokenType(getType());// scheme.font;
- if (font.isBold()) {
- sb.append("");
- }
- if (font.isItalic()) {
- sb.append("");
- }
- if (scheme.underline || isHyperlink()) {
- sb.append("");
- }
- boolean needsFontTag = fontFamily || !isWhitespace();
- if (needsFontTag) {
- sb.append("');
- }
- // NOTE: Don't use getLexeme().trim() because whitespace tokens will
- // be turned into NOTHING.
- appendHtmlLexeme(textArea, sb, tabsToSpaces);
- if (needsFontTag) {
- sb.append("");
- }
- if (scheme.underline || isHyperlink()) {
- sb.append("");
- }
- if (font.isItalic()) {
- sb.append("");
- }
- if (font.isBold()) {
- sb.append("");
- }
- return sb;
- }
-
- /**
- * Appends an HTML version of the lexeme of this token (i.e. no style HTML, but
- * replacing chars such as \t, < and
- * > with their escapes).
- *
- * @param textArea The text area.
- * @param sb The buffer to append to.
- * @param tabsToSpaces Whether to convert tabs into spaces.
- * @return The same buffer.
- */
- private StringBuilder appendHtmlLexeme(RSyntaxTextArea textArea, StringBuilder sb, boolean tabsToSpaces) {
- boolean lastWasSpace = false;
- int i = textOffset;
- int lastI = i;
- String tabStr = null;
- while (i < textOffset + textCount) {
- char ch = text[i];
- switch (ch) {
- case ' ':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append(lastWasSpace ? " " : " ");
- lastWasSpace = true;
- break;
- case '\t':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- if (tabsToSpaces && tabStr == null) {
- StringBuilder stringBuilder = new StringBuilder();
- for (int j = 0; j < textArea.getTabSize(); j++) {
- stringBuilder.append(" ");
- }
- tabStr = stringBuilder.toString();
- }
- sb.append(tabsToSpaces ? tabStr : " ");
- lastWasSpace = false;
- break;
- case '&':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append("&");
- lastWasSpace = false;
- break;
- case '<':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append("<");
- lastWasSpace = false;
- break;
- case '>':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append(">");
- lastWasSpace = false;
- break;
- case '\'':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append("'");
- lastWasSpace = false;
- break;
- case '"':
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append(""");
- lastWasSpace = false;
- break;
- case '/': // OWASP-recommended to escape even though unnecessary
- sb.append(text, lastI, i - lastI);
- lastI = i + 1;
- sb.append("/");
- lastWasSpace = false;
- break;
- default:
- lastWasSpace = false;
- break;
- }
- i++;
- }
- if (lastI < textOffset + textCount) {
- sb.append(text, lastI, textOffset + textCount - lastI);
- }
- return sb;
- }
-
- @Override
- public char charAt(int index) {
- return text[textOffset + index];
- }
-
- @Override
- public boolean containsPosition(int pos) {
- return pos >= getOffset() && pos < getOffset() + textCount;
- }
-
- /**
- * Makes one token point to the same text segment, and have the same value as
- * another token.
- *
- * @param t2 The token from which to copy.
- */
- public void copyFrom(Token t2) {
- text = t2.getTextArray();
- textOffset = t2.getTextOffset();
- textCount = t2.length();
- setOffset(t2.getOffset());
- setType(t2.getType());
- hyperlink = t2.isHyperlink();
- languageIndex = t2.getLanguageIndex();
- nextToken = t2.getNextToken();
- }
-
- @Override
- public int documentToToken(int pos) {
- return pos + (textOffset - getOffset());
- }
-
- @Override
- public boolean endsWith(char[] ch) {
- if (ch == null || ch.length > textCount) {
- return false;
- }
- final int start = textOffset + textCount - ch.length;
- for (int i = 0; i < ch.length; i++) {
- if (text[start + i] != ch[i]) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj == this) {
- return true;
- }
- if (!(obj instanceof Token)) {
- return false;
- }
- Token t2 = (Token) obj;
- return offset == t2.getOffset() && type == t2.getType() && languageIndex == t2.getLanguageIndex()
- && hyperlink == t2.isHyperlink() && ((getLexeme() == null && t2.getLexeme() == null)
- || (getLexeme() != null && getLexeme().equals(t2.getLexeme())));
- }
-
- @Override
- public int getEndOffset() {
- return offset + textCount;
- }
-
- /**
- * Returns a String of the form "#xxxxxx" good for use in HTML,
- * representing the given color.
- *
- * @param color The color to get a string for.
- * @return The HTML form of the color. If color is
- * null, #000000 is returned.
- */
- private static String getHTMLFormatForColor(Color color) {
- if (color == null) {
- return "black";
- }
- String hexRed = Integer.toHexString(color.getRed());
- if (hexRed.length() == 1) {
- hexRed = "0" + hexRed;
- }
- String hexGreen = Integer.toHexString(color.getGreen());
- if (hexGreen.length() == 1) {
- hexGreen = "0" + hexGreen;
- }
- String hexBlue = Integer.toHexString(color.getBlue());
- if (hexBlue.length() == 1) {
- hexBlue = "0" + hexBlue;
- }
- return "#" + hexRed + hexGreen + hexBlue;
- }
-
- @Override
- public String getHTMLRepresentation(RSyntaxTextArea textArea) {
- StringBuilder buf = new StringBuilder();
- appendHTMLRepresentation(buf, textArea, true);
- return buf.toString();
- }
-
- @Override
- public int getLanguageIndex() {
- return languageIndex;
- }
-
- @Override
- 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;
- }
-
- @Override
- public Token getLastPaintableToken() {
- Token t = this;
- while (t.isPaintable()) {
- Token next = t.getNextToken();
- if (next == null || !next.isPaintable()) {
- return t;
- }
- t = next;
- }
- return null;
- }
-
- @Override
- public String getLexeme() {
- return text == null ? null : new String(text, textOffset, textCount);
- }
-
- @Override
- public int getListOffset(RSyntaxTextArea textArea, TabExpander e, float x0, float x) {
- // If the coordinate in question is before this line's start, quit.
- if (x0 >= x) {
- return getOffset();
- }
- float currX = x0; // x-coordinate of current char.
- float nextX = x0; // x-coordinate of next char.
- float stableX = x0; // Cached ending x-coord. of last tab or token.
- TokenImpl token = this;
- int last = getOffset();
- FontMetrics fm = null;
- while (token != null && token.isPaintable()) {
- fm = textArea.getFontMetricsForTokenType(token.getType());
- char[] text = token.text;
- int start = token.textOffset;
- int end = start + token.textCount;
- for (int i = start; i < end; i++) {
- currX = nextX;
- if (text[i] == '\t') {
- nextX = e.nextTabStop(nextX, 0);
- stableX = nextX; // Cache ending x-coord. of tab.
- start = i + 1; // Do charsWidth() from next char.
- } else {
- nextX = stableX + fm.charsWidth(text, start, i - start + 1);
- }
- if (x >= currX && x < nextX) {
- if ((x - currX) < (nextX - x)) {
- return last + i - token.textOffset;
- }
- return last + i + 1 - token.textOffset;
- }
- }
- stableX = nextX; // Cache ending x-coordinate of token.
- last += token.textCount;
- token = (TokenImpl) token.getNextToken();
- }
- // If we didn't find anything, return the end position of the text.
- return last;
- }
-
- @Override
- public Token getNextToken() {
- return nextToken;
- }
-
- @Override
- public int getOffset() {
- return offset;
- }
-
- @Override
- public int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e, float startX, float endBeforeX) {
- FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
- int i = textOffset;
- int stop = i + textCount;
- float x = startX;
- while (i < stop) {
- if (text[i] == '\t') {
- x = e.nextTabStop(x, 0);
- } else {
- x += fm.charWidth(text[i]);
- }
- if (x > endBeforeX) {
- // If not even the first character fits into the space, go
- // ahead and say the first char does fit so we don't go into
- // an infinite loop.
- int intoToken = Math.max(i - textOffset, 1);
- return getOffset() + intoToken;
- }
- i++;
- }
- // If we got here, the whole token fit in (endBeforeX-startX) pixels.
- return getOffset() + textCount - 1;
- }
-
- @Override
- public char[] getTextArray() {
- return text;
- }
-
- @Override
- public int getTextOffset() {
- return textOffset;
- }
-
- @Override
- public int getType() {
- return type;
- }
-
- @Override
- public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0) {
- return getWidthUpTo(textCount, textArea, e, x0);
- }
-
- @Override
- public float getWidthUpTo(int numChars, RSyntaxTextArea textArea, TabExpander e, float x0) {
- float width = x0;
- FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
- if (fm != null) {
- int w;
- int currentStart = textOffset;
- int endBefore = textOffset + numChars;
- for (int i = currentStart; i < endBefore; i++) {
- if (text[i] == '\t') {
- // Since TokenMaker implementations usually group all
- // adjacent whitespace into a single token, there
- // aren't usually any characters to compute a width
- // for here, so we check before calling.
- w = i - currentStart;
- if (w > 0) {
- width += fm.charsWidth(text, currentStart, w);
- }
- currentStart = i + 1;
- width = e.nextTabStop(width, 0);
- }
- }
- // Most (non-whitespace) tokens will have characters at this
- // point to get the widths for, so we don't check for w>0 (mini-
- // optimization).
- w = endBefore - currentStart;
- width += fm.charsWidth(text, currentStart, w);
- }
- return width - x0;
- }
-
- @Override
- public int hashCode() {
- return offset + (getLexeme() == null ? 0 : getLexeme().hashCode());
- }
-
- @Override
- public boolean is(char[] lexeme) {
- if (textCount == lexeme.length) {
- for (int i = 0; i < textCount; i++) {
- if (text[textOffset + i] != lexeme[i]) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean is(int type, char[] lexeme) {
- if (this.getType() == type && textCount == lexeme.length) {
- for (int i = 0; i < textCount; i++) {
- if (text[textOffset + i] != lexeme[i]) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean is(int type, String lexeme) {
- return this.getType() == type && textCount == lexeme.length() && lexeme.equals(getLexeme());
- }
-
- @Override
- public boolean isComment() {
- return getType() >= Token.COMMENT_EOL && getType() <= Token.COMMENT_MARKUP;
- }
-
- @Override
- public boolean isCommentOrWhitespace() {
- return isComment() || isWhitespace();
- }
-
- @Override
- public boolean isHyperlink() {
- return hyperlink;
- }
-
- @Override
- public boolean isIdentifier() {
- return getType() == IDENTIFIER;
- }
-
- @Override
- public boolean isLeftCurly() {
- return getType() == SEPARATOR && isSingleChar('{');
- }
-
- @Override
- public boolean isRightCurly() {
- return getType() == SEPARATOR && isSingleChar('}');
- }
-
- @Override
- public boolean isPaintable() {
- return getType() > Token.NULL;
- }
-
- @Override
- public boolean isSingleChar(char ch) {
- return textCount == 1 && text[textOffset] == ch;
- }
-
- @Override
- public boolean isSingleChar(int type, char ch) {
- return this.getType() == type && isSingleChar(ch);
- }
-
- @Override
- public boolean isWhitespace() {
- return getType() == WHITESPACE;
- }
-
- @Override
- public int length() {
- return textCount;
- }
-
- @Override
- public Rectangle listOffsetToView(RSyntaxTextArea textArea, TabExpander e, int pos, int x0, Rectangle rect) {
- int stableX = x0; // Cached ending x-coord. of last tab or token.
- TokenImpl token = this;
- FontMetrics fm = null;
- Segment s = new Segment();
- while (token != null && token.isPaintable()) {
- fm = textArea.getFontMetricsForTokenType(token.getType());
- if (fm == null) {
- return rect; // Don't return null as things'll error.
- }
- char[] text = token.text;
- int start = token.textOffset;
- int end = start + token.textCount;
- // If this token contains the position for which to get the
- // bounding box...
- if (token.containsPosition(pos)) {
- s.array = token.text;
- s.offset = token.textOffset;
- s.count = pos - token.getOffset();
- // Must use this (actually fm.charWidth()), and not
- // fm.charsWidth() for returned value to match up with where
- // text is actually painted on OS X!
- int w = Utilities.getTabbedTextWidth(s, fm, stableX, e, token.getOffset());
- rect.x = stableX + w;
- end = token.documentToToken(pos);
- if (text[end] == '\t') {
- rect.width = fm.charWidth(' ');
- } else {
- rect.width = fm.charWidth(text[end]);
- }
- return rect;
- }
- // If this token does not contain the position for which to get
- // the bounding box...
- else {
- s.array = token.text;
- s.offset = token.textOffset;
- s.count = token.textCount;
- stableX += Utilities.getTabbedTextWidth(s, fm, stableX, e, token.getOffset());
- }
- token = (TokenImpl) token.getNextToken();
- }
- // If we didn't find anything, we're at the end of the line. Return
- // a width of 1 (so selection highlights don't extend way past line's
- // text). A ConfigurableCaret will know to paint itself with a larger
- // width.
- rect.x = stableX;
- rect.width = 1;
- return rect;
- }
-
- /**
- * Makes this token start at the specified offset into the document.
- *
- *
- * Note: You should not modify Token instances you did not
- * create yourself (e.g., came from an RSyntaxDocument). If you do,
- * rendering issues and/or runtime exceptions will likely occur. You have been
- * warned!
- *
- * @param pos The offset into the document this token should start at. Note that
- * this token must already contain this position; if it doesn't, an
- * exception is thrown.
- * @throws IllegalArgumentException If pos is not already contained by this
- * token.
- * @see #moveOffset(int)
- */
- public void makeStartAt(int pos) {
- if (pos < getOffset() || pos >= (getOffset() + textCount)) {
- throw new IllegalArgumentException(
- "pos " + pos + " is not in range " + getOffset() + "-" + (getOffset() + textCount - 1));
- }
- int shift = pos - getOffset();
- setOffset(pos);
- textOffset += shift;
- textCount -= shift;
- }
-
- /**
- * Moves the starting offset of this token.
- *
- *
- * Note: You should not modify
+ *
+ * Note: The instances of
+ *
+ * Note: You should not modify
+ *
+ * Note: You should not modify Token instances you did not
- * create yourself (e.g., came from an RSyntaxDocument). If you do,
- * rendering issues and/or runtime exceptions will likely occur. You have been
- * warned!
- *
- * @param amt The amount to move the starting offset. This should be between
- * 0 and textCount, inclusive.
- * @throws IllegalArgumentException If amt is an invalid value.
- * @see #makeStartAt(int)
- */
- public void moveOffset(int amt) {
- if (amt < 0 || amt > textCount) {
- throw new IllegalArgumentException("amt " + amt + " is not in range 0-" + textCount);
- }
- setOffset(getOffset() + amt);
- textOffset += amt;
- textCount -= amt;
- }
-
- /**
- * Sets the value of this token to a particular segment of a document. The "next
- * token" value is cleared.
- *
- * @param line The segment from which to get the token.
- * @param beg The first character's position in line.
- * @param end The last character's position in line.
- * @param offset The offset into the document at which this token begins.
- * @param type A token type listed as "generic" above.
- */
- public void set(final char[] line, final int beg, final int end, final int offset, final int type) {
- this.text = line;
- this.textOffset = beg;
- this.textCount = end - beg + 1;
- this.setType(type);
- this.setOffset(offset);
- nextToken = null;
- }
-
- /**
- * Sets whether this token is a hyperlink.
- *
- * @param hyperlink Whether this token is a hyperlink.
- * @see #isHyperlink()
- */
- @Override
- public void setHyperlink(boolean hyperlink) {
- this.hyperlink = hyperlink;
- }
-
- /**
- * Sets the language index for this token. If this value is positive, it denotes
- * a specific "secondary" language this token represents (such as JavaScript
- * code or CSS embedded in an HTML file). If this value is 0, this
- * token is in the "main" language being edited. Negative values are invalid and
- * treated as 0.
- *
- * @param languageIndex The new language index. A value of 0
- * denotes the "main" language, any positive value denotes
- * a specific secondary language. Negative values will be
- * treated as 0.
- * @see #getLanguageIndex()
- */
- @Override
- public void setLanguageIndex(int languageIndex) {
- this.languageIndex = languageIndex;
- }
-
- /**
- * Sets the "next token" pointer of this token to point to the specified token.
- *
- * @param nextToken The new next token.
- * @see #getNextToken()
- */
- public void setNextToken(Token nextToken) {
- this.nextToken = nextToken;
- }
-
- /**
- * Sets the offset into the document at which this token resides.
- *
- * @param offset The new offset into the document.
- * @see #getOffset()
- */
- public void setOffset(int offset) {
- this.offset = offset;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void setType(int type) {
- this.type = type;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean startsWith(char[] chars) {
- if (chars.length <= textCount) {
- for (int i = 0; i < chars.length; i++) {
- if (text[textOffset + i] != chars[i]) {
- return false;
- }
- }
- return true;
- }
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int tokenToDocument(int pos) {
- return pos + (getOffset() - textOffset);
- }
-
- /**
- * Returns this token as a String, which is useful for debugging.
- *
- * @return A string describing this token.
- */
- @Override
- public String toString() {
- return "[Token: "
- + (getType() == Token.NULL ? "Token returned by
+ * {@link RSyntaxDocument}s are pooled and should always be treated as
+ * immutable. They should not be cast to TokenImpl and modified.
+ * Modifying tokens you did not create yourself can and will result in rendering
+ * issues and/or runtime exceptions. You have been warned!
+ *
+ * @author Robert Futrell
+ * @version 0.3
+ */
+public class TokenImpl implements Token {
+
+ /**
+ * The text this token represents. This is implemented as a segment so we can
+ * point directly to the text in the document without having to make a copy of
+ * it.
+ */
+ public char[] text;
+ public int textOffset;
+ public int textCount;
+ /**
+ * The offset into the document at which this token resides.
+ */
+ private int offset;
+ /**
+ * The type of token this is; for example, {@link #FUNCTION}.
+ */
+ private int type;
+ /**
+ * Whether this token is a hyperlink.
+ */
+ private boolean hyperlink;
+ /**
+ * The next token in this linked list.
+ */
+ private Token nextToken;
+ /**
+ * The language this token is in, >= 0.
+ */
+ private int languageIndex;
+
+ /**
+ * Creates a "null" token. The token itself is not null; rather, it signifies
+ * that it is the last token in a linked list of tokens and that it is not part
+ * of a "multi-line token."
+ */
+ public TokenImpl() {
+ this.text = null;
+ this.textOffset = -1;
+ this.textCount = -1;
+ this.setType(NULL);
+ setOffset(-1);
+ hyperlink = false;
+ nextToken = null;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param line The segment from which to get the token.
+ * @param beg The first character's position in line.
+ * @param end The last character's position in line.
+ * @param startOffset The offset into the document at which this token begins.
+ * @param type A token type listed as "generic" above.
+ * @param languageIndex The language index for this token.
+ */
+ public TokenImpl(Segment line, int beg, int end, int startOffset, int type, int languageIndex) {
+ this(line.array, beg, end, startOffset, type, languageIndex);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param line The segment from which to get the token.
+ * @param beg The first character's position in line.
+ * @param end The last character's position in line.
+ * @param startOffset The offset into the document at which this token begins.
+ * @param type A token type listed as "generic" above.
+ * @param languageIndex The language index for this token.
+ */
+ public TokenImpl(char[] line, int beg, int end, int startOffset, int type, int languageIndex) {
+ this();
+ set(line, beg, end, startOffset, type);
+ setLanguageIndex(languageIndex);
+ }
+
+ /**
+ * Creates this token as a copy of the passed-in token.
+ *
+ * @param t2 The token from which to make a copy.
+ */
+ public TokenImpl(Token t2) {
+ this();
+ copyFrom(t2);
+ }
+
+ @Override
+ public StringBuilder appendHTMLRepresentation(StringBuilder sb, RSyntaxTextArea textArea, boolean fontFamily) {
+ return appendHTMLRepresentation(sb, textArea, fontFamily, false);
+ }
+
+ @Override
+ public StringBuilder appendHTMLRepresentation(StringBuilder sb, RSyntaxTextArea textArea, boolean fontFamily,
+ boolean tabsToSpaces) {
+ SyntaxScheme colorScheme = textArea.getSyntaxScheme();
+ Style scheme = colorScheme.getStyle(getType());
+ Font font = textArea.getFontForTokenType(getType());// scheme.font;
+ if (font.isBold()) {
+ sb.append("");
+ }
+ if (font.isItalic()) {
+ sb.append("");
+ }
+ if (scheme.underline || isHyperlink()) {
+ sb.append("");
+ }
+ boolean needsFontTag = fontFamily || !isWhitespace();
+ if (needsFontTag) {
+ sb.append("');
+ }
+ // NOTE: Don't use getLexeme().trim() because whitespace tokens will
+ // be turned into NOTHING.
+ appendHtmlLexeme(textArea, sb, tabsToSpaces);
+ if (needsFontTag) {
+ sb.append("");
+ }
+ if (scheme.underline || isHyperlink()) {
+ sb.append("");
+ }
+ if (font.isItalic()) {
+ sb.append("");
+ }
+ if (font.isBold()) {
+ sb.append("");
+ }
+ return sb;
+ }
+
+ /**
+ * Appends an HTML version of the lexeme of this token (i.e. no style HTML, but
+ * replacing chars such as \t, < and
+ * > with their escapes).
+ *
+ * @param textArea The text area.
+ * @param sb The buffer to append to.
+ * @param tabsToSpaces Whether to convert tabs into spaces.
+ * @return The same buffer.
+ */
+ private StringBuilder appendHtmlLexeme(RSyntaxTextArea textArea, StringBuilder sb, boolean tabsToSpaces) {
+ boolean lastWasSpace = false;
+ int i = textOffset;
+ int lastI = i;
+ String tabStr = null;
+ while (i < textOffset + textCount) {
+ char ch = text[i];
+ switch (ch) {
+ case ' ':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append(lastWasSpace ? " " : " ");
+ lastWasSpace = true;
+ break;
+ case '\t':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ if (tabsToSpaces && tabStr == null) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int j = 0; j < textArea.getTabSize(); j++) {
+ stringBuilder.append(" ");
+ }
+ tabStr = stringBuilder.toString();
+ }
+ sb.append(tabsToSpaces ? tabStr : " ");
+ lastWasSpace = false;
+ break;
+ case '&':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append("&");
+ lastWasSpace = false;
+ break;
+ case '<':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append("<");
+ lastWasSpace = false;
+ break;
+ case '>':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append(">");
+ lastWasSpace = false;
+ break;
+ case '\'':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append("'");
+ lastWasSpace = false;
+ break;
+ case '"':
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append(""");
+ lastWasSpace = false;
+ break;
+ case '/': // OWASP-recommended to escape even though unnecessary
+ sb.append(text, lastI, i - lastI);
+ lastI = i + 1;
+ sb.append("/");
+ lastWasSpace = false;
+ break;
+ default:
+ lastWasSpace = false;
+ break;
+ }
+ i++;
+ }
+ if (lastI < textOffset + textCount) {
+ sb.append(text, lastI, textOffset + textCount - lastI);
+ }
+ return sb;
+ }
+
+ @Override
+ public char charAt(int index) {
+ return text[textOffset + index];
+ }
+
+ @Override
+ public boolean containsPosition(int pos) {
+ return pos >= getOffset() && pos < getOffset() + textCount;
+ }
+
+ /**
+ * Makes one token point to the same text segment, and have the same value as
+ * another token.
+ *
+ * @param t2 The token from which to copy.
+ */
+ public void copyFrom(Token t2) {
+ text = t2.getTextArray();
+ textOffset = t2.getTextOffset();
+ textCount = t2.length();
+ setOffset(t2.getOffset());
+ setType(t2.getType());
+ hyperlink = t2.isHyperlink();
+ languageIndex = t2.getLanguageIndex();
+ nextToken = t2.getNextToken();
+ }
+
+ @Override
+ public int documentToToken(int pos) {
+ return pos + (textOffset - getOffset());
+ }
+
+ @Override
+ public boolean endsWith(char[] ch) {
+ if (ch == null || ch.length > textCount) {
+ return false;
+ }
+ final int start = textOffset + textCount - ch.length;
+ for (int i = 0; i < ch.length; i++) {
+ if (text[start + i] != ch[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Token)) {
+ return false;
+ }
+ Token t2 = (Token) obj;
+ return offset == t2.getOffset() && type == t2.getType() && languageIndex == t2.getLanguageIndex()
+ && hyperlink == t2.isHyperlink() && ((getLexeme() == null && t2.getLexeme() == null)
+ || (getLexeme() != null && getLexeme().equals(t2.getLexeme())));
+ }
+
+ @Override
+ public int getEndOffset() {
+ return offset + textCount;
+ }
+
+ /**
+ * Returns a String of the form "#xxxxxx" good for use in HTML,
+ * representing the given color.
+ *
+ * @param color The color to get a string for.
+ * @return The HTML form of the color. If color is
+ * null, #000000 is returned.
+ */
+ private static String getHTMLFormatForColor(Color color) {
+ if (color == null) {
+ return "black";
+ }
+ String hexRed = Integer.toHexString(color.getRed());
+ if (hexRed.length() == 1) {
+ hexRed = "0" + hexRed;
+ }
+ String hexGreen = Integer.toHexString(color.getGreen());
+ if (hexGreen.length() == 1) {
+ hexGreen = "0" + hexGreen;
+ }
+ String hexBlue = Integer.toHexString(color.getBlue());
+ if (hexBlue.length() == 1) {
+ hexBlue = "0" + hexBlue;
+ }
+ return "#" + hexRed + hexGreen + hexBlue;
+ }
+
+ @Override
+ public String getHTMLRepresentation(RSyntaxTextArea textArea) {
+ StringBuilder buf = new StringBuilder();
+ appendHTMLRepresentation(buf, textArea, true);
+ return buf.toString();
+ }
+
+ @Override
+ public int getLanguageIndex() {
+ return languageIndex;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public Token getLastPaintableToken() {
+ Token t = this;
+ while (t.isPaintable()) {
+ Token next = t.getNextToken();
+ if (next == null || !next.isPaintable()) {
+ return t;
+ }
+ t = next;
+ }
+ return null;
+ }
+
+ @Override
+ public String getLexeme() {
+ return text == null ? null : new String(text, textOffset, textCount);
+ }
+
+ @Override
+ public int getListOffset(RSyntaxTextArea textArea, TabExpander e, float x0, float x) {
+ // If the coordinate in question is before this line's start, quit.
+ if (x0 >= x) {
+ return getOffset();
+ }
+ float currX = x0; // x-coordinate of current char.
+ float nextX = x0; // x-coordinate of next char.
+ float stableX = x0; // Cached ending x-coord. of last tab or token.
+ TokenImpl token = this;
+ int last = getOffset();
+ FontMetrics fm = null;
+ while (token != null && token.isPaintable()) {
+ if (token.textCount > 10000) {
+ return getOffset() + (int) (x / 7);
+ }
+ fm = textArea.getFontMetricsForTokenType(token.getType());
+ char[] text = token.text;
+ int start = token.textOffset;
+ int end = start + token.textCount;
+ for (int i = start; i < end; i++) {
+ currX = nextX;
+ if (text[i] == '\t') {
+ nextX = e.nextTabStop(nextX, 0);
+ stableX = nextX; // Cache ending x-coord. of tab.
+ start = i + 1; // Do charsWidth() from next char.
+ } else {
+ nextX = stableX + fm.charsWidth(text, start, i - start + 1);
+ }
+ if (x >= currX && x < nextX) {
+ if ((x - currX) < (nextX - x)) {
+ return last + i - token.textOffset;
+ }
+ return last + i + 1 - token.textOffset;
+ }
+ }
+ stableX = nextX; // Cache ending x-coordinate of token.
+ last += token.textCount;
+ token = (TokenImpl) token.getNextToken();
+ }
+ // If we didn't find anything, return the end position of the text.
+ return last;
+ }
+
+ @Override
+ public Token getNextToken() {
+ return nextToken;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+
+ @Override
+ public int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e, float startX, float endBeforeX) {
+ FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
+ int i = textOffset;
+ int stop = i + textCount;
+ float x = startX;
+ while (i < stop) {
+ if (text[i] == '\t') {
+ x = e.nextTabStop(x, 0);
+ } else {
+ x += fm.charWidth(text[i]);
+ }
+ if (x > endBeforeX) {
+ // If not even the first character fits into the space, go
+ // ahead and say the first char does fit so we don't go into
+ // an infinite loop.
+ int intoToken = Math.max(i - textOffset, 1);
+ return getOffset() + intoToken;
+ }
+ i++;
+ }
+ // If we got here, the whole token fit in (endBeforeX-startX) pixels.
+ return getOffset() + textCount - 1;
+ }
+
+ @Override
+ public char[] getTextArray() {
+ return text;
+ }
+
+ @Override
+ public int getTextOffset() {
+ return textOffset;
+ }
+
+ @Override
+ public int getType() {
+ return type;
+ }
+
+ @Override
+ public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0) {
+ return getWidthUpTo(textCount, textArea, e, x0);
+ }
+
+ @Override
+ public float getWidthUpTo(int numChars, RSyntaxTextArea textArea, TabExpander e, float x0) {
+ float width = x0;
+ FontMetrics fm = textArea.getFontMetricsForTokenType(getType());
+ if (fm != null) {
+ int w;
+ int currentStart = textOffset;
+ int endBefore = textOffset + numChars;
+ for (int i = currentStart; i < endBefore; i++) {
+ if (text[i] == '\t') {
+ // Since TokenMaker implementations usually group all
+ // adjacent whitespace into a single token, there
+ // aren't usually any characters to compute a width
+ // for here, so we check before calling.
+ w = i - currentStart;
+ if (w > 0) {
+ width += fm.charsWidth(text, currentStart, w);
+ }
+ currentStart = i + 1;
+ width = e.nextTabStop(width, 0);
+ }
+ }
+ // Most (non-whitespace) tokens will have characters at this
+ // point to get the widths for, so we don't check for w>0 (mini-
+ // optimization).
+ w = endBefore - currentStart;
+ width += fm.charsWidth(text, currentStart, w);
+ }
+ return width - x0;
+ }
+
+ @Override
+ public int hashCode() {
+ return offset + (getLexeme() == null ? 0 : getLexeme().hashCode());
+ }
+
+ @Override
+ public boolean is(char[] lexeme) {
+ if (textCount == lexeme.length) {
+ for (int i = 0; i < textCount; i++) {
+ if (text[textOffset + i] != lexeme[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean is(int type, char[] lexeme) {
+ if (this.getType() == type && textCount == lexeme.length) {
+ for (int i = 0; i < textCount; i++) {
+ if (text[textOffset + i] != lexeme[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean is(int type, String lexeme) {
+ return this.getType() == type && textCount == lexeme.length() && lexeme.equals(getLexeme());
+ }
+
+ @Override
+ public boolean isComment() {
+ return getType() >= Token.COMMENT_EOL && getType() <= Token.COMMENT_MARKUP;
+ }
+
+ @Override
+ public boolean isCommentOrWhitespace() {
+ return isComment() || isWhitespace();
+ }
+
+ @Override
+ public boolean isHyperlink() {
+ return hyperlink;
+ }
+
+ @Override
+ public boolean isIdentifier() {
+ return getType() == IDENTIFIER;
+ }
+
+ @Override
+ public boolean isLeftCurly() {
+ return getType() == SEPARATOR && isSingleChar('{');
+ }
+
+ @Override
+ public boolean isRightCurly() {
+ return getType() == SEPARATOR && isSingleChar('}');
+ }
+
+ @Override
+ public boolean isPaintable() {
+ return getType() > Token.NULL;
+ }
+
+ @Override
+ public boolean isSingleChar(char ch) {
+ return textCount == 1 && text[textOffset] == ch;
+ }
+
+ @Override
+ public boolean isSingleChar(int type, char ch) {
+ return this.getType() == type && isSingleChar(ch);
+ }
+
+ @Override
+ public boolean isWhitespace() {
+ return getType() == WHITESPACE;
+ }
+
+ @Override
+ public int length() {
+ return textCount;
+ }
+
+ @Override
+ public Rectangle listOffsetToView(RSyntaxTextArea textArea, TabExpander e, int pos, int x0, Rectangle rect) {
+ int stableX = x0; // Cached ending x-coord. of last tab or token.
+ TokenImpl token = this;
+ FontMetrics fm = null;
+ Segment s = new Segment();
+ while (token != null && token.isPaintable()) {
+ fm = textArea.getFontMetricsForTokenType(token.getType());
+ if (fm == null) {
+ return rect; // Don't return null as things'll error.
+ }
+ char[] text = token.text;
+ int start = token.textOffset;
+ int end = start + token.textCount;
+ // If this token contains the position for which to get the
+ // bounding box...
+ if (token.containsPosition(pos)) {
+ s.array = token.text;
+ s.offset = token.textOffset;
+ s.count = pos - token.getOffset();
+ // Must use this (actually fm.charWidth()), and not
+ // fm.charsWidth() for returned value to match up with where
+ // text is actually painted on OS X!
+ int w = Utilities.getTabbedTextWidth(s, fm, stableX, e, token.getOffset());
+ rect.x = stableX + w;
+ end = token.documentToToken(pos);
+ if (text[end] == '\t') {
+ rect.width = fm.charWidth(' ');
+ } else {
+ rect.width = fm.charWidth(text[end]);
+ }
+ return rect;
+ }
+ // If this token does not contain the position for which to get
+ // the bounding box...
+ else {
+ s.array = token.text;
+ s.offset = token.textOffset;
+ s.count = token.textCount;
+ stableX += Utilities.getTabbedTextWidth(s, fm, stableX, e, token.getOffset());
+ }
+ token = (TokenImpl) token.getNextToken();
+ }
+ // If we didn't find anything, we're at the end of the line. Return
+ // a width of 1 (so selection highlights don't extend way past line's
+ // text). A ConfigurableCaret will know to paint itself with a larger
+ // width.
+ rect.x = stableX;
+ rect.width = 1;
+ return rect;
+ }
+
+ /**
+ * Makes this token start at the specified offset into the document.
+ * Token instances you did not
+ * create yourself (e.g., came from an RSyntaxDocument). If you do,
+ * rendering issues and/or runtime exceptions will likely occur. You have been
+ * warned!
+ *
+ * @param pos The offset into the document this token should start at. Note that
+ * this token must already contain this position; if it doesn't, an
+ * exception is thrown.
+ * @throws IllegalArgumentException If pos is not already contained by this
+ * token.
+ * @see #moveOffset(int)
+ */
+ public void makeStartAt(int pos) {
+ if (pos < getOffset() || pos >= (getOffset() + textCount)) {
+ throw new IllegalArgumentException(
+ "pos " + pos + " is not in range " + getOffset() + "-" + (getOffset() + textCount - 1));
+ }
+ int shift = pos - getOffset();
+ setOffset(pos);
+ textOffset += shift;
+ textCount -= shift;
+ }
+
+ /**
+ * Moves the starting offset of this token.
+ * Token instances you did not
+ * create yourself (e.g., came from an RSyntaxDocument). If you do,
+ * rendering issues and/or runtime exceptions will likely occur. You have been
+ * warned!
+ *
+ * @param amt The amount to move the starting offset. This should be between
+ * 0 and textCount, inclusive.
+ * @throws IllegalArgumentException If amt is an invalid value.
+ * @see #makeStartAt(int)
+ */
+ public void moveOffset(int amt) {
+ if (amt < 0 || amt > textCount) {
+ throw new IllegalArgumentException("amt " + amt + " is not in range 0-" + textCount);
+ }
+ setOffset(getOffset() + amt);
+ textOffset += amt;
+ textCount -= amt;
+ }
+
+ /**
+ * Sets the value of this token to a particular segment of a document. The "next
+ * token" value is cleared.
+ *
+ * @param line The segment from which to get the token.
+ * @param beg The first character's position in line.
+ * @param end The last character's position in line.
+ * @param offset The offset into the document at which this token begins.
+ * @param type A token type listed as "generic" above.
+ */
+ public void set(final char[] line, final int beg, final int end, final int offset, final int type) {
+ this.text = line;
+ this.textOffset = beg;
+ this.textCount = end - beg + 1;
+ this.setType(type);
+ this.setOffset(offset);
+ nextToken = null;
+ }
+
+ /**
+ * Sets whether this token is a hyperlink.
+ *
+ * @param hyperlink Whether this token is a hyperlink.
+ * @see #isHyperlink()
+ */
+ @Override
+ public void setHyperlink(boolean hyperlink) {
+ this.hyperlink = hyperlink;
+ }
+
+ /**
+ * Sets the language index for this token. If this value is positive, it denotes
+ * a specific "secondary" language this token represents (such as JavaScript
+ * code or CSS embedded in an HTML file). If this value is 0, this
+ * token is in the "main" language being edited. Negative values are invalid and
+ * treated as 0.
+ *
+ * @param languageIndex The new language index. A value of 0
+ * denotes the "main" language, any positive value denotes
+ * a specific secondary language. Negative values will be
+ * treated as 0.
+ * @see #getLanguageIndex()
+ */
+ @Override
+ public void setLanguageIndex(int languageIndex) {
+ this.languageIndex = languageIndex;
+ }
+
+ /**
+ * Sets the "next token" pointer of this token to point to the specified token.
+ *
+ * @param nextToken The new next token.
+ * @see #getNextToken()
+ */
+ public void setNextToken(Token nextToken) {
+ this.nextToken = nextToken;
+ }
+
+ /**
+ * Sets the offset into the document at which this token resides.
+ *
+ * @param offset The new offset into the document.
+ * @see #getOffset()
+ */
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public void reset() {
+ text = null;
+ offset = -1;
+ type = Token.NULL;
+ nextToken = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean startsWith(char[] chars) {
+ if (chars.length <= textCount) {
+ for (int i = 0; i < chars.length; i++) {
+ if (text[textOffset + i] != chars[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int tokenToDocument(int pos) {
+ return pos + (getOffset() - textOffset);
+ }
+
+ /**
+ * Returns this token as a String, which is useful for debugging.
+ *
+ * @return A string describing this token.
+ */
+ @Override
+ public String toString() {
+ return "[Token: "
+ + (getType() == Token.NULL ? "