source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/Token.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 30.5 KB
Line 
1/*
2 * 02/21/2004
3 *
4 * Token.java - A token used in syntax highlighting.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rsyntaxtextarea;
10
11import java.awt.Color;
12import java.awt.Font;
13import java.awt.FontMetrics;
14import java.awt.Graphics2D;
15import java.awt.Rectangle;
16import java.awt.geom.Rectangle2D;
17import javax.swing.text.TabExpander;
18
19
20/**
21 * A generic token that functions as a node in a linked list of syntax
22 * highlighted tokens for some language.<p>
23 *
24 * A <code>Token</code> is a piece of text representing some logical token in
25 * source code for a programming language. For example, the line of C code:<p>
26 * <pre>
27 * int i = 0;
28 * </pre>
29 * would be broken into 8 <code>Token</code>s: the first representing
30 * <code>int</code>, the second whitespace, the third <code>i</code>, the fourth
31 * whitespace, the fifth <code>=</code>, etc.<p>
32 *
33 * @author Robert Futrell
34 * @version 0.3
35 */
36public abstract class Token implements TokenTypes {
37
38 /**
39 * The text this token represents. This is implemented as a segment so we
40 * can point directly to the text in the document without having to make a
41 * copy of it.
42 */
43 public char[] text;
44 public int textOffset;
45 public int textCount;
46
47 /**
48 * The offset into the document at which this token resides.
49 */
50 public int offset;
51
52 /**
53 * The type of token this is; for example, {@link #FUNCTION}.
54 */
55 public int type;
56
57 /**
58 * Whether this token is a hyperlink.
59 */
60 private boolean hyperlink;
61
62 /**
63 * The next token in this linked list.
64 */
65 private Token nextToken;
66
67 /**
68 * Rectangle used for filling token backgrounds.
69 */
70 private Rectangle2D.Float bgRect;
71
72 /**
73 * Micro-optimization; buffer used to compute tab width. If the width is
74 * correct it's not re-allocated, to prevent lots of very small garbage.
75 * Only used when painting tab lines.
76 */
77 private static char[] tabBuf;
78
79
80 /**
81 * Creates a "null token." The token itself is not null; rather, it
82 * signifies that it is the last token in a linked list of tokens and
83 * that it is not part of a "multi-line token."
84 */
85 public Token() {
86 this.text = null;
87 this.textOffset = -1;
88 this.textCount = -1;
89 this.type = NULL;
90 offset = -1;
91 hyperlink = false;
92 nextToken = null;
93 bgRect = new Rectangle2D.Float();
94 }
95
96
97 /**
98 * Constructor.
99 *
100 * @param line The segment from which to get the token.
101 * @param beg The first character's position in <code>line</code>.
102 * @param end The last character's position in <code>line</code>.
103 * @param startOffset The offset into the document at which this
104 * token begins.
105 * @param type A token type listed as "generic" above.
106 */
107 public Token(final char[] line, final int beg, final int end,
108 final int startOffset, final int type) {
109 this();
110 set(line, beg,end, startOffset, type);
111 }
112
113
114 /**
115 * Creates this token as a deep copy of the passed-in token.
116 *
117 * @param t2 The token from which to make a copy.
118 */
119 public Token(Token t2) {
120 this();
121 copyFrom(t2);
122 }
123
124
125 /**
126 * Appends HTML code for painting this token, using the given text area's
127 * color scheme.
128 *
129 * @param sb The buffer to append to.
130 * @param textArea The text area whose color scheme to use.
131 * @param fontFamily Whether to include the font family in the HTML for
132 * this token. You can pass <code>false</code> for this parameter
133 * if, for example, you are making all your HTML be monospaced,
134 * and don't want any crazy fonts being used in the editor to be
135 * reflected in your HTML.
136 * @return The buffer appended to.
137 * @see #getHTMLRepresentation(RSyntaxTextArea)
138 */
139 public StringBuffer appendHTMLRepresentation(StringBuffer sb,
140 RSyntaxTextArea textArea,
141 boolean fontFamily) {
142 return appendHTMLRepresentation(sb, textArea, fontFamily, false);
143 }
144
145
146 /**
147 * Appends HTML code for painting this token, using the given text area's
148 * color scheme.
149 *
150 * @param sb The buffer to append to.
151 * @param textArea The text area whose color scheme to use.
152 * @param fontFamily Whether to include the font family in the HTML for
153 * this token. You can pass <code>false</code> for this parameter
154 * if, for example, you are making all your HTML be monospaced,
155 * and don't want any crazy fonts being used in the editor to be
156 * reflected in your HTML.
157 * @param tabsToSpaces Whether to convert tabs into spaces.
158 * @return The buffer appended to.
159 * @see #getHTMLRepresentation(RSyntaxTextArea)
160 */
161 public StringBuffer appendHTMLRepresentation(StringBuffer sb,
162 RSyntaxTextArea textArea, boolean fontFamily,
163 boolean tabsToSpaces) {
164
165 SyntaxScheme colorScheme = textArea.getSyntaxScheme();
166 Style scheme = colorScheme.getStyle(type);
167 Font font = textArea.getFontForTokenType(type);//scheme.font;
168
169 if (font.isBold()) sb.append("<b>");
170 if (font.isItalic()) sb.append("<em>");
171 if (scheme.underline || isHyperlink()) sb.append("<u>");
172
173 sb.append("<font");
174 if (fontFamily) {
175 sb.append(" face=\"").append(font.getFamily()).append("\"");
176 }
177 sb.append(" color=\"").
178 append(getHTMLFormatForColor(scheme.foreground)).
179 append("\">");
180
181 // NOTE: Don't use getLexeme().trim() because whitespace tokens will
182 // be turned into NOTHING.
183 appendHtmlLexeme(textArea, sb, tabsToSpaces);
184
185 sb.append("</font>");
186 if (scheme.underline || isHyperlink()) sb.append("</u>");
187 if (font.isItalic()) sb.append("</em>");
188 if (font.isBold()) sb.append("</b>");
189
190 return sb;
191
192 }
193
194
195 /**
196 * Appends an HTML version of the lexeme of this token (i.e. no style
197 * HTML, but replacing chars such as <code>\t</code>, <code>&lt;</code>
198 * and <code>&gt;</code> with their escapes).
199 *
200 * @param textArea The text area.
201 * @param sb The buffer to append to.
202 * @param tabsToSpaces Whether to convert tabs into spaces.
203 * @return The same buffer.
204 */
205 private final StringBuffer appendHtmlLexeme(RSyntaxTextArea textArea,
206 StringBuffer sb, boolean tabsToSpaces) {
207
208 boolean lastWasSpace = false;
209 int i = textOffset;
210 int lastI = i;
211 String tabStr = null;
212
213 while (i<textOffset+textCount) {
214 char ch = text[i];
215 switch (ch) {
216 case ' ':
217 sb.append(text, lastI, i-lastI);
218 lastI = i+1;
219 sb.append(lastWasSpace ? "&nbsp;" : " ");
220 lastWasSpace = true;
221 break;
222 case '\t':
223 sb.append(text, lastI, i-lastI);
224 lastI = i+1;
225 if (tabsToSpaces && tabStr==null) {
226 tabStr = "";
227 for (int j=0; j<textArea.getTabSize(); j++) {
228 tabStr += "&nbsp;";
229 }
230 }
231 sb.append(tabsToSpaces ? tabStr : "&#09;");
232 lastWasSpace = false;
233 break;
234 case '<':
235 sb.append(text, lastI, i-lastI);
236 lastI = i+1;
237 sb.append("&lt;");
238 lastWasSpace = false;
239 break;
240 case '>':
241 sb.append(text, lastI, i-lastI);
242 lastI = i+1;
243 sb.append("&gt;");
244 lastWasSpace = false;
245 break;
246 default:
247 lastWasSpace = false;
248 break;
249 }
250 i++;
251 }
252 if (lastI<textOffset+textCount) {
253 sb.append(text, lastI, textOffset+textCount-lastI);
254 }
255 return sb;
256 }
257
258
259 /**
260 * Returns whether the token straddles the specified position in the
261 * document.
262 *
263 * @param pos The position in the document to check.
264 * @return Whether the specified position is straddled by this token.
265 */
266 public boolean containsPosition(int pos) {
267 return pos>=offset && pos<offset+textCount;
268 }
269
270
271 /**
272 * Makes one token point to the same text segment, and have the same value
273 * as another token.
274 *
275 * @param t2 The token from which to copy.
276 */
277 public void copyFrom(Token t2) {
278 text = t2.text;
279 textOffset = t2.textOffset;
280 textCount = t2.textCount;
281 offset = t2.offset;
282 type = t2.type;
283 nextToken = t2.nextToken;
284 }
285
286
287 /**
288 * Returns the position in the token's internal char array corresponding
289 * to the specified document position.<p>
290 * Note that this method does NOT do any bounds checking; you can pass in
291 * a document position that does not correspond to a position in this
292 * token, and you will not receive an Exception or any other notification;
293 * it is up to the caller to ensure valid input.
294 *
295 * @param pos A position in the document that is represented by this token.
296 * @return The corresponding token position >= <code>textOffset</code> and
297 * < <code>textOffset+textCount</code>.
298 * @see #tokenToDocument
299 */
300 public int documentToToken(int pos) {
301 return pos + (textOffset-offset);
302 }
303
304
305 /**
306 * Returns whether this token's lexeme ends with the specified characters.
307 *
308 * @param ch The characters.
309 * @return Whether this token's lexeme ends with the specified characters.
310 * @see #startsWith(char[])
311 */
312 public boolean endsWith(char[] ch) {
313 if (ch==null || ch.length>textCount) {
314 return false;
315 }
316 final int start = textOffset + textCount - ch.length;
317 for (int i=0; i<ch.length; i++) {
318 if (text[start+i]!=ch[i]) {
319 return false;
320 }
321 }
322 return true;
323 }
324
325
326 /**
327 * Returns a <code>String</code> of the form "#xxxxxx" good for use
328 * in HTML, representing the given color.
329 *
330 * @param color The color to get a string for.
331 * @return The HTML form of the color. If <code>color</code> is
332 * <code>null</code>, <code>#000000</code> is returned.
333 */
334 private static final String getHTMLFormatForColor(Color color) {
335 if (color==null) {
336 return "black";
337 }
338 String hexRed = Integer.toHexString(color.getRed());
339 if (hexRed.length()==1)
340 hexRed = "0" + hexRed;
341 String hexGreen = Integer.toHexString(color.getGreen());
342 if (hexGreen.length()==1)
343 hexGreen = "0" + hexGreen;
344 String hexBlue = Integer.toHexString(color.getBlue());
345 if (hexBlue.length()==1)
346 hexBlue = "0" + hexBlue;
347 return "#" + hexRed + hexGreen + hexBlue;
348 }
349
350
351 /**
352 * Returns a <code>String</code> containing HTML code for painting this
353 * token, using the given text area's color scheme.
354 *
355 * @param textArea The text area whose color scheme to use.
356 * @return The HTML representation of the token.
357 * @see #appendHTMLRepresentation(StringBuffer, RSyntaxTextArea, boolean)
358 */
359 public String getHTMLRepresentation(RSyntaxTextArea textArea) {
360 StringBuffer buf = new StringBuffer();
361 appendHTMLRepresentation(buf, textArea, true);
362 return buf.toString();
363 }
364
365
366 /**
367 * Returns the last token in this list that is not whitespace or a
368 * comment.
369 *
370 * @return The last non-comment, non-whitespace token, or <code>null</code>
371 * if there isn't one.
372 */
373 public Token getLastNonCommentNonWhitespaceToken() {
374
375 Token last = null;
376
377 for (Token t=this; t!=null && t.isPaintable(); t=t.nextToken) {
378 switch (t.type) {
379 case COMMENT_DOCUMENTATION:
380 case COMMENT_EOL:
381 case COMMENT_MULTILINE:
382 case WHITESPACE:
383 break;
384 default:
385 last = t;
386 break;
387 }
388 }
389
390 return last;
391
392 }
393
394
395 /**
396 * Returns the last paintable token in this token list, or <code>null</code>
397 * if there is no paintable token.
398 *
399 * @return The last paintable token in this token list.
400 */
401 public Token getLastPaintableToken() {
402 Token t = this;
403 while (t.isPaintable()) {
404 if (t.nextToken==null || !t.nextToken.isPaintable()) {
405 return t;
406 }
407 t = t.nextToken;
408 }
409 return null;
410 }
411
412
413 /**
414 * Returns the text of this token, as a string.<p>
415 *
416 * Note that this method isn't used much by the
417 * <code>rsyntaxtextarea</code> package internally, as it tries to limit
418 * memory allocation.
419 *
420 * @return The text of this token.
421 */
422 public String getLexeme() {
423 return new String(text, textOffset, textCount);
424 }
425
426
427 /**
428 * Determines the offset into this token list (i.e., into the
429 * document) that covers pixel location <code>x</code> if the token list
430 * starts at pixel location <code>x0</code><p>.
431 * This method will return the document position "closest" to the
432 * x-coordinate (i.e., if they click on the "right-half" of the
433 * <code>w</code> in <code>awe</code>, the caret will be placed in
434 * between the <code>w</code> and <code>e</code>; similarly, clicking on
435 * the left-half places the caret between the <code>a</code> and
436 * <code>w</code>). This makes it useful for methods such as
437 * <code>viewToModel</code> found in <code>javax.swing.text.View</code>
438 * subclasses.<p>
439 *
440 * This method is abstract so subclasses who paint themselves differently
441 * (i.e., {@link VisibleWhitespaceToken} is painted a tad differently than
442 * {@link DefaultToken} when rendering hints are enabled) can still return
443 * accurate results.
444 *
445 * @param textArea The text area from which the token list was derived.
446 * @param e How to expand tabs.
447 * @param x0 The pixel x-location that is the beginning of
448 * <code>tokenList</code>.
449 * @param x The pixel-position for which you want to get the corresponding
450 * offset.
451 * @return The position (in the document, NOT into the token list!) that
452 * covers the pixel location. If <code>tokenList</code> is
453 * <code>null</code> or has type <code>Token.NULL</code>, then
454 * <code>-1</code is returned; the caller should recognize this and
455 * return the actual end position of the (empty) line.
456 */
457 public abstract int getListOffset(RSyntaxTextArea textArea, TabExpander e,
458 float x0, float x);
459
460
461 /**
462 * Returns the token after this one in the linked list.
463 *
464 * @return The next token.
465 * @see #setNextToken
466 */
467 public Token getNextToken() {
468 return nextToken;
469 }
470
471
472 /**
473 * Returns the position in the document that represents the last character
474 * in the token that will fit into <code>endBeforeX-startX</code> pixels.
475 * For example, if you're using a monospaced 8-pixel-per-character font,
476 * have the token "while" and <code>startX</code> is <code>0</code> and
477 * <code>endBeforeX</code> is <code>30</code>, this method will return the
478 * document position of the "i" in "while", because the "i" ends at pixel
479 * <code>24</code>, while the "l" ends at <code>32</code>. If not even the
480 * first character fits in <code>endBeforeX-startX</code>, the first
481 * character's position is still returned so calling methods don't go into
482 * infinite loops.
483 *
484 * @param textArea The text area in which this token is being painted.
485 * @param e How to expand tabs.
486 * @param startX The x-coordinate at which the token will be painted. This
487 * is needed because of tabs.
488 * @param endBeforeX The x-coordinate for which you want to find the last
489 * character of <code>t</code> which comes before it.
490 * @return The last document position that will fit in the specified amount
491 * of pixels.
492 */
493 /*
494 * @see #getTokenListOffsetBeforeX
495 * FIXME: This method does not compute correctly! It needs to be abstract
496 * and implemented by subclasses.
497 */
498 public int getOffsetBeforeX(RSyntaxTextArea textArea, TabExpander e,
499 float startX, float endBeforeX) {
500
501 FontMetrics fm = textArea.getFontMetricsForTokenType(type);
502 int i = textOffset;
503 int stop = i + textCount;
504 float x = startX;
505
506 while (i<stop) {
507 if (text[i]=='\t')
508 x = e.nextTabStop(x, 0);
509 else
510 x += fm.charWidth(text[i]);
511 if (x>endBeforeX) {
512 // If not even the first character fits into the space, go
513 // ahead and say the first char does fit so we don't go into
514 // an infinite loop.
515 int intoToken = Math.max(i-textOffset, 1);
516 return offset + intoToken;
517 }
518 i++;
519 }
520
521 // If we got here, the whole token fit in (endBeforeX-startX) pixels.
522 return offset + textCount - 1;
523
524 }
525
526
527 /**
528 * Returns the width of this token given the specified parameters.
529 *
530 * @param textArea The text area in which the token is being painted.
531 * @param e Describes how to expand tabs. This parameter cannot be
532 * <code>null</code>.
533 * @param x0 The pixel-location at which the token begins. This is needed
534 * because of tabs.
535 * @return The width of the token, in pixels.
536 * @see #getWidthUpTo
537 */
538 public float getWidth(RSyntaxTextArea textArea, TabExpander e, float x0) {
539 return getWidthUpTo(textCount, textArea, e, x0);
540 }
541
542
543 /**
544 * Returns the width of a specified number of characters in this token.
545 * For example, for the token "while", specifying a value of <code>3</code>
546 * here returns the width of the "whi" portion of the token.<p>
547 *
548 * This method is abstract so subclasses who paint themselves differently
549 * (i.e., {@link VisibleWhitespaceToken} is painted a tad differently than
550 * {@link DefaultToken} when rendering hints are enabled) can still return
551 * accurate results.
552 *
553 * @param numChars The number of characters for which to get the width.
554 * @param textArea The text area in which the token is being painted.
555 * @param e How to expand tabs. This value cannot be <code>null</code>.
556 * @param x0 The pixel-location at which this token begins. This is needed
557 * because of tabs.
558 * @return The width of the specified number of characters in this token.
559 * @see #getWidth
560 */
561 public abstract float getWidthUpTo(int numChars, RSyntaxTextArea textArea,
562 TabExpander e, float x0);
563
564
565 /**
566 * Returns whether this token is of the specified type, with the specified
567 * lexeme.<p>
568 * This method is preferred over the other overload in performance-critical
569 * code where this operation may be called frequently, since it does not
570 * involve any String allocations.
571 *
572 * @param type The type to check for.
573 * @param lexeme The lexeme to check for.
574 * @return Whether this token has that type and lexeme.
575 * @see #is(int, String)
576 * @see #startsWith(char[])
577 */
578 public boolean is(int type, char[] lexeme) {
579 if (this.type==type && textCount==lexeme.length) {
580 for (int i=0; i<textCount; i++) {
581 if (text[textOffset+i]!=lexeme[i]) {
582 return false;
583 }
584 }
585 return true;
586 }
587 return false;
588 }
589
590
591 /**
592 * Returns whether this token is of the specified type, with the specified
593 * lexeme.<p>
594 * The other overload of this method is preferred over this one in
595 * performance-critical code, as this one involves a String allocation
596 * while the other does not.
597 *
598 * @param type The type to check for.
599 * @param lexeme The lexeme to check for.
600 * @return Whether this token has that type and lexeme.
601 * @see #is(int, char[])
602 */
603 public boolean is(int type, String lexeme) {
604 return this.type==type && textCount==lexeme.length() &&
605 lexeme.equals(getLexeme());
606 }
607
608
609 /**
610 * Returns whether this token is a comment.
611 *
612 * @return Whether this token is a comment.
613 * @see #isWhitespace()
614 */
615 public boolean isComment() {
616 return type>=Token.COMMENT_EOL && type<=Token.COMMENT_MARKUP;
617 }
618
619
620 /**
621 * Returns whether this token is a hyperlink.
622 *
623 * @return Whether this token is a hyperlink.
624 * @see #setHyperlink(boolean)
625 */
626 public boolean isHyperlink() {
627 return hyperlink;
628 }
629
630
631 /**
632 * Returns whether this token is an identifier.
633 *
634 * @return Whether this token is an identifier.
635 */
636 public boolean isIdentifier() {
637 return type==IDENTIFIER;
638 }
639
640
641 /**
642 * Returns whether this token is a {@link #SEPARATOR} representing a single
643 * left curly brace.
644 *
645 * @return Whether this token is a left curly brace.
646 * @see #isRightCurly()
647 */
648 public boolean isLeftCurly() {
649 return type==SEPARATOR && isSingleChar('{');
650 }
651
652
653 /**
654 * Returns whether this token is a {@link #SEPARATOR} representing a single
655 * right curly brace.
656 *
657 * @return Whether this token is a right curly brace.
658 * @see #isLeftCurly()
659 */
660 public boolean isRightCurly() {
661 return type==SEPARATOR && isSingleChar('}');
662 }
663
664
665 /**
666 * Returns whether or not this token is "paintable;" i.e., whether or not
667 * the type of this token is one such that it has an associated syntax
668 * style. What this boils down to is whether the token type is greater
669 * than <code>Token.NULL</code>.
670 *
671 * @return Whether or not this token is paintable.
672 */
673 public boolean isPaintable() {
674 return type>Token.NULL;
675 }
676
677
678 /**
679 * Returns whether this token is the specified single character.
680 *
681 * @param ch The character to check for.
682 * @return Whether this token's lexeme is the single character specified.
683 */
684 public boolean isSingleChar(char ch) {
685 return textCount==1 && text[textOffset]==ch;
686 }
687
688
689 /**
690 * Returns whether or not this token is whitespace.
691 *
692 * @return <code>true</code> iff this token is whitespace.
693 * @see #isComment()
694 */
695 public boolean isWhitespace() {
696 return type==WHITESPACE;
697 }
698
699
700 /**
701 * Returns the bounding box for the specified document location. The
702 * location must be in the specified token list; if it isn't,
703 * <code>null</code> is returned.
704 *
705 * @param textArea The text area from which the token list was derived.
706 * @param e How to expand tabs.
707 * @param pos The position in the document for which to get the bounding
708 * box in the view.
709 * @param x0 The pixel x-location that is the beginning of
710 * <code>tokenList</code>.
711 * @param rect The rectangle in which we'll be returning the results. This
712 * object is reused to keep from frequent memory allocations.
713 * @return The bounding box for the specified position in the model.
714 */
715 public abstract Rectangle listOffsetToView(RSyntaxTextArea textArea,
716 TabExpander e, int pos, int x0,
717 Rectangle rect);
718
719
720 /**
721 * Makes this token start at the specified offset into the document.
722 *
723 * @param pos The offset into the document this token should start at.
724 * Note that this token must already contain this position; if
725 * it doesn't, an exception is thrown.
726 * @throws IllegalArgumentException If pos is not already contained by
727 * this token.
728 * @see #moveOffset(int)
729 */
730 public void makeStartAt(int pos) {
731 if (pos<offset || pos>=(offset+textCount)) {
732 throw new IllegalArgumentException("pos " + pos +
733 " is not in range " + offset + "-" + (offset+textCount-1));
734 }
735 int shift = pos - offset;
736 offset = pos;
737 textOffset += shift;
738 textCount -= shift;
739 }
740
741
742 /**
743 * Moves the starting offset of this token.
744 *
745 * @param amt The amount to move the starting offset. This should be
746 * between <code>0</code> and <code>textCount</code>, inclusive.
747 * @throws IllegalArgumentException If <code>amt</code> is an invalid value.
748 * @see #makeStartAt(int)
749 */
750 public void moveOffset(int amt) {
751 if (amt<0 || amt>textCount) {
752 throw new IllegalArgumentException("amt " + amt +
753 " is not in range 0-" + textCount);
754 }
755 offset += amt;
756 textOffset += amt;
757 textCount -= amt;
758 }
759
760
761 /**
762 * Paints this token.
763 *
764 * @param g The graphics context in which to paint.
765 * @param x The x-coordinate at which to paint.
766 * @param y The y-coordinate at which to paint.
767 * @param host The text area this token is in.
768 * @param e How to expand tabs.
769 * @return The x-coordinate representing the end of the painted text.
770 */
771 public final float paint(Graphics2D g, float x, float y,
772 RSyntaxTextArea host, TabExpander e) {
773 return paint(g, x,y, host, e, 0);
774 }
775
776
777 /**
778 * Paints this token.
779 *
780 * @param g The graphics context in which to paint.
781 * @param x The x-coordinate at which to paint.
782 * @param y The y-coordinate at which to paint.
783 * @param host The text area this token is in.
784 * @param e How to expand tabs.
785 * @param clipStart The left boundary of the clip rectangle in which we're
786 * painting. This optimizes painting by allowing us to not paint
787 * paint when this token is "to the left" of the clip rectangle.
788 * @return The x-coordinate representing the end of the painted text.
789 */
790 public abstract float paint(Graphics2D g, float x, float y,
791 RSyntaxTextArea host,
792 TabExpander e, float clipStart);
793
794
795 /**
796 * Paints the background of a token.
797 *
798 * @param x The x-coordinate of the token.
799 * @param y The y-coordinate of the token.
800 * @param width The width of the token (actually, the width of the part of
801 * the token to paint).
802 * @param height The height of the token.
803 * @param g The graphics context with which to paint.
804 * @param fontAscent The ascent of the token's font.
805 * @param host The text area.
806 * @param color The color with which to paint.
807 */
808 protected void paintBackground(float x, float y, float width, float height,
809 Graphics2D g, int fontAscent,
810 RSyntaxTextArea host, Color color) {
811 // RSyntaxTextArea's bg can be null, so we must check for this.
812 Color temp = host.getBackground();
813 g.setXORMode(temp!=null ? temp : Color.WHITE);
814 g.setColor(color);
815 bgRect.setRect(x,y-fontAscent, width,height);
816 g.fill(bgRect);
817 g.setPaintMode();
818 }
819
820
821 /**
822 * Paints dotted "tab" lines; that is, lines that show where your caret
823 * would go to on the line if you hit "tab". This visual effect is usually
824 * done in the leading whitespace token(s) of lines.
825 *
826 * @param x The starting x-offset of this token. It is assumed that this
827 * is the left margin of the text area (may be non-zero due to
828 * insets), since tab lines are only painted for leading whitespace.
829 * @param y The baseline where this token was painted.
830 * @param endX The ending x-offset of this token.
831 * @param g The graphics context.
832 * @param e Used to expand tabs.
833 * @param host The text area.
834 */
835 protected void paintTabLines(int x, int y, int endX, Graphics2D g,
836 TabExpander e, RSyntaxTextArea host) {
837
838 // We allow tab lines to be painted in more than just Token.WHITESPACE,
839 // i.e. for MLC's and Token.IDENTIFIERS (for TokenMakers that return
840 // whitespace as identifiers for performance). But we only paint tab
841 // lines for the leading whitespace in the token. So, if this isn't a
842 // WHITESPACE token, figure out the leading whitespace's length.
843 if (type!=Token.WHITESPACE) {
844 int offs = textOffset;
845 for (; offs<textOffset+textCount; offs++) {
846 if (!RSyntaxUtilities.isWhitespace(text[offs])) {
847 break; // MLC text, etc.
848 }
849 }
850 int len = offs - textOffset;
851 if (len<2) { // Must be at least two whitespaces to see tab line
852 return;
853 }
854 //endX = x + (int)getWidthUpTo(len, host, e, x);
855 endX = (int)getWidthUpTo(len, host, e, 0);
856 }
857
858 // Get the length of a tab.
859 FontMetrics fm = host.getFontMetricsForTokenType(type);
860 int tabSize = host.getTabSize();
861 if (tabBuf==null || tabBuf.length<tabSize) {
862 tabBuf = new char[tabSize];
863 for (int i=0; i<tabSize; i++) {
864 tabBuf[i] = ' ';
865 }
866 }
867 // Note different token types (MLC's, whitespace) could possibly be
868 // using different fonts, which means we can't cache the actual width
869 // of a tab as it may be different per-token-type. We could keep a
870 // per-token-type cache, but we'd have to clear it whenever they
871 // modified token styles.
872 int tabW = fm.charsWidth(tabBuf, 0, tabSize);
873
874 // Draw any tab lines. Here we're assuming that "x" is the left
875 // margin of the editor.
876 g.setColor(host.getTabLineColor());
877 int x0 = x + tabW;
878 int y0 = y - fm.getAscent();
879 if ((y0&1)>0) {
880 // Only paint on even y-pixels to prevent doubling up between lines
881 y0++;
882 }
883 while (x0<endX) {
884 int y1 = y0;
885 int y2 = y0 + host.getLineHeight();
886 while (y1<y2) {
887 g.drawLine(x0, y1, x0, y1);
888 y1 += 2;
889 }
890 //g.drawLine(x0,y0, x0,y0+host.getLineHeight());
891 x0 += tabW;
892 }
893
894 }
895
896
897 /**
898 * Sets the value of this token to a particular segment of a document.
899 * The "next token" value is cleared.
900 *
901 * @param line The segment from which to get the token.
902 * @param beg The first character's position in <code>line</code>.
903 * @param end The last character's position in <code>line</code>.
904 * @param offset The offset into the document at which this token begins.
905 * @param type A token type listed as "generic" above.
906 */
907 public void set(final char[] line, final int beg, final int end,
908 final int offset, final int type) {
909 this.text = line;
910 this.textOffset = beg;
911 this.textCount = end - beg + 1;
912 this.type = type;
913 this.offset = offset;
914 nextToken = null;
915 }
916
917
918 /**
919 * Sets whether this token is a hyperlink.
920 *
921 * @param hyperlink Whether this token is a hyperlink.
922 * @see #isHyperlink()
923 */
924 public void setHyperlink(boolean hyperlink) {
925 this.hyperlink = hyperlink;
926 }
927
928
929 /**
930 * Sets the "next token" pointer of this token to point to the specified
931 * token.
932 *
933 * @param nextToken The new next token.
934 * @see #getNextToken()
935 */
936 public void setNextToken(Token nextToken) {
937 this.nextToken = nextToken;
938 }
939
940
941 /**
942 * Returns whether this token starts with the specified characters.
943 *
944 * @param chars The characters.
945 * @return Whether this token starts with those characters.
946 * @see #endsWith(char[])
947 * @see #is(int, char[])
948 */
949 public boolean startsWith(char[] chars) {
950 if (chars.length<=textCount){
951 for (int i=0; i<chars.length; i++) {
952 if (text[textOffset+i]!=chars[i]) {
953 return false;
954 }
955 }
956 return true;
957 }
958 return false;
959 }
960
961
962 /**
963 * Returns the position in the document corresponding to the specified
964 * position in this token's internal char array (<code>textOffset</code> -
965 * <code>textOffset+textCount-1</code>).<p>
966 * Note that this method does NOT do any bounds checking; you can pass in
967 * an invalid token position, and you will not receive an Exception or any
968 * other indication that the returned document position is invalid. It is
969 * up to the user to ensure valid input.
970 *
971 * @param pos A position in the token's internal char array
972 * (<code>textOffset</code> - <code>textOffset+textCount</code>).
973 * @return The corresponding position in the document.
974 * @see #documentToToken(int)
975 */
976 public int tokenToDocument(int pos) {
977 return pos + (offset-textOffset);
978 }
979
980
981 /**
982 * Returns this token as a <code>String</code>, which is useful for
983 * debugging.
984 *
985 * @return A string describing this token.
986 */
987 public String toString() {
988 return "[Token: " +
989 (type==Token.NULL ? "<null token>" :
990 "text: '" +
991 (text==null ? "<null>" : getLexeme() + "'; " +
992 "offset: " + offset + "; type: " + type + "; " +
993 "isPaintable: " + isPaintable() +
994 "; nextToken==null: " + (nextToken==null))) +
995 "]";
996 }
997
998
999}
Note: See TracBrowser for help on using the repository browser.