4
* RSyntaxDocument.java - A document capable of syntax highlighting, used by
7
* This library is distributed under a modified BSD license. See the included
8
* RSyntaxTextArea.License.txt file for details.
10
package org.fife.ui.rsyntaxtextarea;
12
import java.awt.event.ActionEvent;
13
import java.io.IOException;
14
import java.io.ObjectInputStream;
15
import javax.swing.Action;
16
import javax.swing.event.*;
17
import javax.swing.text.*;
19
import org.fife.ui.rsyntaxtextarea.modes.AbstractMarkupTokenMaker;
20
import org.fife.ui.rtextarea.RDocument;
21
import org.fife.util.DynamicIntArray;
25
* The document used by {@link org.fife.ui.rsyntaxtextarea.RSyntaxTextArea}.
26
* This document is like <code>javax.swing.text.PlainDocument</code> except that
27
* it also keeps track of syntax highlighting in the document. It has a "style"
28
* attribute associated with it that determines how syntax highlighting is done
29
* (i.e., what language is being highlighted).<p>
31
* Instances of <code>RSyntaxTextArea</code> will only accept instances of
32
* <code>RSyntaxDocument</code>, since it is this document that keeps
33
* track of syntax highlighting. All others will cause an exception to be
36
* To change the language being syntax highlighted at any time, you merely have
37
* to call {@link #setSyntaxStyle}. Other than that, this document can be
38
* treated like any other save one caveat: all <code>DocumentEvent</code>s of
39
* type <code>CHANGE</code> use their offset and length values to represent the
40
* first and last lines, respectively, that have had their syntax coloring
41
* change. This is really a hack to increase the speed of the painting code
42
* and should really be corrected, but oh well.
44
* @author Robert Futrell
47
public class RSyntaxDocument extends RDocument implements SyntaxConstants {
50
* Creates a {@link TokenMaker} appropriate for a given programming
53
private transient TokenMakerFactory tokenMakerFactory;
56
* Splits text into tokens for the current programming language.
58
private transient TokenMaker tokenMaker;
61
* The current syntax style. Only cached to keep this class serializable.
63
private String syntaxStyle;
66
* Array of values representing the "last token type" on each line. This
67
* is used in cases such as multi-line comments: if the previous line
68
* ended with an (unclosed) multi-line comment, we can use this knowledge
69
* and start the current line's syntax highlighting in multi-line comment
72
protected transient DynamicIntArray lastTokensOnLines;
74
private transient int lastLine = -1;
75
private transient Token cachedTokenList;
76
private transient int useCacheCount = 0;
77
private transient int tokenRetrievalCount = 0;
79
private transient Segment s;
82
* If this is set to <code>true</code>, debug information about how much
83
* token caching is helping is printed to stdout.
85
private static final boolean DEBUG_TOKEN_CACHING = false;
89
* Constructs a plain text document. A default root element is created,
90
* and the tab size set to 5.
92
* @param syntaxStyle The syntax highlighting scheme to use.
94
public RSyntaxDocument(String syntaxStyle) {
95
this(null, syntaxStyle);
100
* Constructs a plain text document. A default root element is created,
101
* and the tab size set to 5.
103
* @param tmf The <code>TokenMakerFactory</code> for this document. If
104
* this is <code>null</code>, a default factory is used.
105
* @param syntaxStyle The syntax highlighting scheme to use.
107
public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle) {
108
putProperty(tabSizeAttribute, Integer.valueOf(5));
109
lastTokensOnLines = new DynamicIntArray(400);
110
lastTokensOnLines.add(Token.NULL); // Initial (empty) line.
112
setTokenMakerFactory(tmf);
113
setSyntaxStyle(syntaxStyle);
118
* Alerts all listeners to this document of an insertion. This is
119
* overridden so we can update our syntax highlighting stuff.<p>
120
* The syntax highlighting stuff has to be here instead of in
121
* <code>insertUpdate</code> because <code>insertUpdate</code> is not
122
* called by the undo/redo actions, but this method is.
124
* @param e The change.
127
protected void fireInsertUpdate(DocumentEvent e) {
129
cachedTokenList = null;
132
* Now that the text is actually inserted into the content and
133
* element structure, we can update our token elements and "last
134
* tokens on lines" structure.
137
Element lineMap = getDefaultRootElement();
138
DocumentEvent.ElementChange change = e.getChange(lineMap);
139
Element[] added = change==null ? null : change.getChildrenAdded();
141
int numLines = lineMap.getElementCount();
142
int line = lineMap.getElementIndex(e.getOffset());
143
int previousLine = line - 1;
144
int previousTokenType = (previousLine>-1 ?
145
lastTokensOnLines.get(previousLine) : Token.NULL);
147
// If entire lines were added...
148
if (added!=null && added.length>0) {
150
Element[] removed = change.getChildrenRemoved();
151
int numRemoved = removed!=null ? removed.length : 0;
153
int endBefore = line + added.length - numRemoved;
154
//System.err.println("... adding lines: " + line + " - " + (endBefore-1));
155
//System.err.println("... ... added: " + added.length + ", removed:" + numRemoved);
156
for (int i=line; i<endBefore; i++) {
158
setSharedSegment(i); // Loads line i's text into s.
160
int tokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
161
lastTokensOnLines.add(i, tokenType);
162
//System.err.println("--------- lastTokensOnLines.size() == " + lastTokensOnLines.getSize());
164
previousTokenType = tokenType;
166
} // End of for (int i=line; i<endBefore; i++).
168
// Update last tokens for lines below until they stop changing.
169
updateLastTokensBelow(endBefore, numLines, previousTokenType);
171
} // End of if (added!=null && added.length>0).
173
// Otherwise, text was inserted on a single line...
176
// Update last tokens for lines below until they stop changing.
177
updateLastTokensBelow(line, numLines, previousTokenType);
181
// Let all listeners know about the insertion.
182
super.fireInsertUpdate(e);
188
* This method is called AFTER the content has been inserted into the
189
* document and the element structure has been updated.<p>
190
* The syntax-highlighting updates need to be done here (as opposed to
191
* an override of <code>postRemoveUpdate</code>) as this method is called
192
* in response to undo/redo events, whereas <code>postRemoveUpdate</code>
194
* Now that the text is actually inserted into the content and element
195
* structure, we can update our token elements and "last tokens on
198
* @param chng The change that occurred.
202
protected void fireRemoveUpdate(DocumentEvent chng) {
204
cachedTokenList = null;
205
Element lineMap = getDefaultRootElement();
206
int numLines = lineMap.getElementCount();
208
DocumentEvent.ElementChange change = chng.getChange(lineMap);
209
Element[] removed = change==null ? null : change.getChildrenRemoved();
211
// If entire lines were removed...
212
if (removed!=null && removed.length>0) {
214
int line = change.getIndex(); // First line entirely removed.
215
int previousLine = line - 1; // Line before that.
216
int previousTokenType = (previousLine>-1 ?
217
lastTokensOnLines.get(previousLine) : Token.NULL);
219
Element[] added = change.getChildrenAdded();
220
int numAdded = added==null ? 0 : added.length;
222
// Remove the cached last-token values for the removed lines.
223
int endBefore = line + removed.length - numAdded;
224
//System.err.println("... removing lines: " + line + " - " + (endBefore-1));
225
//System.err.println("... added: " + numAdded + ", removed: " + removed.length);
227
lastTokensOnLines.removeRange(line, endBefore); // Removing values for lines [line-(endBefore-1)].
228
//System.err.println("--------- lastTokensOnLines.size() == " + lastTokensOnLines.getSize());
230
// Update last tokens for lines below until they've stopped changing.
231
updateLastTokensBelow(line, numLines, previousTokenType);
233
} // End of if (removed!=null && removed.size()>0).
235
// Otherwise, text was removed from just one line...
238
int line = lineMap.getElementIndex(chng.getOffset());
239
if (line>=lastTokensOnLines.getSize())
240
return; // If we're editing the last line in a document...
242
int previousLine = line - 1;
243
int previousTokenType = (previousLine>-1 ?
244
lastTokensOnLines.get(previousLine) : Token.NULL);
245
//System.err.println("previousTokenType for line : " + previousLine + " is " + previousTokenType);
246
// Update last tokens for lines below until they've stopped changing.
247
updateLastTokensBelow(line, numLines, previousTokenType);
251
// Let all of our listeners know about the removal.
252
super.fireRemoveUpdate(chng);
258
* Returns the closest {@link TokenTypes "standard" token type} for a given
259
* "internal" token type (e.g. one whose value is <code>< 0</code>).
261
* @param type The token type.
262
* @return The closest "standard" token type. If a mapping is not defined
263
* for this language, then <code>type</code> is returned.
265
public int getClosestStandardTokenTypeForInternalType(int type) {
266
return tokenMaker.getClosestStandardTokenTypeForInternalType(type);
271
* Returns whether closing markup tags should be automatically completed.
272
* This method only returns <code>true</code> if
273
* {@link #getLanguageIsMarkup()} also returns <code>true</code>.
275
* @return Whether markup closing tags should be automatically completed.
276
* @see #getLanguageIsMarkup()
278
public boolean getCompleteMarkupCloseTags() {
279
// TODO: Remove terrible dependency on AbstractMarkupTokenMaker
280
return getLanguageIsMarkup() &&
281
((AbstractMarkupTokenMaker)tokenMaker).getCompleteCloseTags();
286
* Returns whether the current programming language uses curly braces
287
* ('<tt>{</tt>' and '<tt>}</tt>') to denote code blocks.
289
* @return Whether curly braces denote code blocks.
291
public boolean getCurlyBracesDenoteCodeBlocks() {
292
return tokenMaker.getCurlyBracesDenoteCodeBlocks();
297
* Returns whether the current language is a markup language, such as
300
* @return Whether the current language is a markup language.
302
public boolean getLanguageIsMarkup() {
303
return tokenMaker.isMarkupLanguage();
308
* Returns the token type of the last token on the given line.
310
* @param line The line to inspect.
311
* @return The token type of the last token on the specified line. If
312
* the line is invalid, an exception is thrown.
314
public int getLastTokenTypeOnLine(int line) {
315
return lastTokensOnLines.get(line);
320
* Returns the text to place at the beginning and end of a
321
* line to "comment" it in the current programming language.
323
* @return The start and end strings to add to a line to "comment"
324
* it out. A <code>null</code> value for either means there
325
* is no string to add for that part. A value of
326
* <code>null</code> for the array means this language
327
* does not support commenting/uncommenting lines.
329
public String[] getLineCommentStartAndEnd() {
330
return tokenMaker.getLineCommentStartAndEnd();
335
* Returns whether tokens of the specified type should have "mark
336
* occurrences" enabled for the current programming language.
338
* @param type The token type.
339
* @return Whether tokens of this type should have "mark occurrences"
342
boolean getMarkOccurrencesOfTokenType(int type) {
343
return tokenMaker.getMarkOccurrencesOfTokenType(type);
348
* Returns the occurrence marker for the current language.
350
* @return The occurrence marker.
352
OccurrenceMarker getOccurrenceMarker() {
353
return tokenMaker.getOccurrenceMarker();
358
* This method returns whether auto indentation should be done if Enter
359
* is pressed at the end of the specified line.
361
* @param line The line to check.
362
* @return Whether an extra indentation should be done.
364
public boolean getShouldIndentNextLine(int line) {
365
Token t = getTokenListForLine(line);
366
t = t.getLastNonCommentNonWhitespaceToken();
367
return tokenMaker.getShouldIndentNextLineAfter(t);
372
* Returns a token list for the specified segment of text representing
373
* the specified line number. This method is basically a wrapper for
374
* <code>tokenMaker.getTokenList</code> that takes into account the last
375
* token on the previous line to assure token accuracy.
377
* @param line The line number of <code>text</code> in the document, >= 0.
378
* @return A token list representing the specified line.
380
public final Token getTokenListForLine(int line) {
382
tokenRetrievalCount++;
383
if (line==lastLine && cachedTokenList!=null) {
384
if (DEBUG_TOKEN_CACHING) {
386
System.err.println("--- Using cached line; ratio now: " +
387
useCacheCount + "/" + tokenRetrievalCount);
389
return cachedTokenList;
393
Element map = getDefaultRootElement();
394
Element elem = map.getElement(line);
395
int startOffset = elem.getStartOffset();
396
//int endOffset = (line==map.getElementCount()-1 ? elem.getEndOffset() - 1:
397
// elem.getEndOffset() - 1);
398
int endOffset = elem.getEndOffset() - 1; // Why always "-1"?
400
getText(startOffset,endOffset-startOffset, s);
401
} catch (BadLocationException ble) {
402
ble.printStackTrace();
405
int initialTokenType = line==0 ? Token.NULL :
406
getLastTokenTypeOnLine(line-1);
408
//return tokenMaker.getTokenList(s, initialTokenType, startOffset);
409
cachedTokenList = tokenMaker.getTokenList(s, initialTokenType, startOffset);
410
return cachedTokenList;
415
boolean insertBreakSpecialHandling(ActionEvent e) {
416
Action a = tokenMaker.getInsertBreakAction();
418
a.actionPerformed(e);
426
* Deserializes a document.
428
* @param in The stream to read from.
429
* @throws ClassNotFoundException
430
* @throws IOException
432
private void readObject(ObjectInputStream in)
433
throws ClassNotFoundException, IOException {
435
in.defaultReadObject();
437
// Install default TokenMakerFactory. To support custom TokenMakers,
438
// both JVM's should install default TokenMakerFactories that support
439
// the language they want to use beforehand.
440
setTokenMakerFactory(null);
442
// Handle other transient stuff
443
this.s = new Segment();
444
int lineCount = getDefaultRootElement().getElementCount();
445
lastTokensOnLines = new DynamicIntArray(lineCount);
446
setSyntaxStyle(syntaxStyle); // Actually install (transient) TokenMaker
452
* Makes our private <code>Segment s</code> point to the text in our
453
* document referenced by the specified element. Note that
454
* <code>line</code> MUST be a valid line number in the document.
456
* @param line The line number you want to get.
458
private final void setSharedSegment(int line) {
460
Element map = getDefaultRootElement();
461
//int numLines = map.getElementCount();
463
Element element = map.getElement(line);
465
throw new InternalError("Invalid line number: " + line);
466
int startOffset = element.getStartOffset();
467
//int endOffset = (line==numLines-1 ?
468
// element.getEndOffset()-1 : element.getEndOffset() - 1);
469
int endOffset = element.getEndOffset()-1; // Why always "-1"?
471
getText(startOffset, endOffset-startOffset, s);
472
} catch (BadLocationException ble) {
473
throw new InternalError("Text range not in document: " +
474
startOffset + "-" + endOffset);
481
* Sets the syntax style being used for syntax highlighting in this
482
* document. What styles are supported by a document is determined by its
483
* {@link TokenMakerFactory}. By default, all <code>RSyntaxDocument</code>s
484
* support all languages built into <code>RSyntaxTextArea</code>.
486
* @param styleKey The new style to use, such as
487
* {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. If this style is not
488
* known or supported by this document, then
489
* {@link SyntaxConstants#SYNTAX_STYLE_NONE} is used.
490
* @see #setSyntaxStyle(TokenMaker)
492
public void setSyntaxStyle(String styleKey) {
493
tokenMaker = tokenMakerFactory.getTokenMaker(styleKey);
494
updateSyntaxHighlightingInformation();
495
this.syntaxStyle = styleKey;
500
* Sets the syntax style being used for syntax highlighting in this
501
* document. You should call this method if you've created a custom token
502
* maker for a language not normally supported by
503
* <code>RSyntaxTextArea</code>.
505
* @param tokenMaker The new token maker to use.
506
* @see #setSyntaxStyle(String)
508
public void setSyntaxStyle(TokenMaker tokenMaker) {
509
this.tokenMaker = tokenMaker;
510
updateSyntaxHighlightingInformation();
515
* Sets the token maker factory used by this document.
517
* @param tmf The <code>TokenMakerFactory</code> for this document. If
518
* this is <code>null</code>, a default factory is used.
520
public void setTokenMakerFactory(TokenMakerFactory tmf) {
521
tokenMakerFactory = tmf!=null ? tmf :
522
TokenMakerFactory.getDefaultInstance();
527
* Loops through the last-tokens-on-lines array from a specified point
528
* onward, updating last-token values until they stop changing. This
529
* should be called when lines are updated/inserted/removed, as doing
530
* so may cause lines below to change color.
532
* @param line The first line to check for a change in last-token value.
533
* @param numLines The number of lines in the document.
534
* @param previousTokenType The last-token value of the line just before
536
* @return The last line that needs repainting.
538
private int updateLastTokensBelow(int line, int numLines, int previousTokenType) {
540
int firstLine = line;
542
// Loop through all lines past our starting point. Update even the last
543
// line's info, even though there aren't any lines after it that depend
544
// on it changing for them to be changed, as its state may be used
545
// elsewhere in the library.
547
//System.err.println("--- end==" + end + " (numLines==" + numLines + ")");
550
setSharedSegment(line); // Sets s's text to that of line 'line' in the document.
552
int oldTokenType = lastTokensOnLines.get(line);
553
int newTokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
554
//System.err.println("---------------- line " + line + "; oldTokenType==" + oldTokenType + ", newTokenType==" + newTokenType + ", s=='" + s + "'");
556
// If this line's end-token value didn't change, stop here. Note
557
// that we're saying this line needs repainting; this is because
558
// the beginning of this line did indeed change color, but the
560
if (oldTokenType==newTokenType) {
561
//System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
562
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
566
// If the line's end-token value did change, update it and
568
// NOTE: "setUnsafe" is okay here as the bounds checking was
569
// already done in lastTokensOnLines.get(line) above.
570
lastTokensOnLines.setUnsafe(line, newTokenType);
571
previousTokenType = newTokenType;
574
} // End of while (line<numLines).
576
// If any lines had their token types changed, fire a changed update
577
// for them. The view will repaint the area covered by the lines.
578
// FIXME: We currently cheat and send the line range that needs to be
579
// repainted as the "offset and length" of the change, since this is
580
// what the view needs. We really should send the actual offset and
582
if (line>firstLine) {
583
//System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
584
fireChangedUpdate(new DefaultDocumentEvent(firstLine, line,
585
DocumentEvent.EventType.CHANGE));
594
* Updates internal state information; e.g. the "last tokens on lines"
595
* data. After this, a changed update is fired to let listeners know that
596
* the document's structure has changed.<p>
598
* This is called internally whenever the syntax style changes.
600
protected void updateSyntaxHighlightingInformation() {
602
// Reinitialize the "last token on each line" array. Note that since
603
// the actual text in the document isn't changing, the number of lines
605
Element map = getDefaultRootElement();
606
int numLines = map.getElementCount();
607
int lastTokenType = Token.NULL;
608
for (int i=0; i<numLines; i++) {
610
lastTokenType = tokenMaker.getLastTokenTypeOnLine(s, lastTokenType);
611
lastTokensOnLines.set(i, lastTokenType);
614
// Let everybody know that syntax styles have (probably) changed.
615
fireChangedUpdate(new DefaultDocumentEvent(
616
0, numLines-1, DocumentEvent.EventType.CHANGE));
b'\\ No newline at end of file'