1 | /*
|
---|
2 | * 10/16/2004
|
---|
3 | *
|
---|
4 | * RSyntaxDocument.java - A document capable of syntax highlighting, used by
|
---|
5 | * RSyntaxTextArea.
|
---|
6 | *
|
---|
7 | * This library is distributed under a modified BSD license. See the included
|
---|
8 | * RSyntaxTextArea.License.txt file for details.
|
---|
9 | */
|
---|
10 | package org.fife.ui.rsyntaxtextarea;
|
---|
11 |
|
---|
12 | import java.awt.event.ActionEvent;
|
---|
13 | import java.io.IOException;
|
---|
14 | import java.io.ObjectInputStream;
|
---|
15 | import java.io.ObjectOutputStream;
|
---|
16 | import javax.swing.Action;
|
---|
17 | import javax.swing.event.*;
|
---|
18 | import javax.swing.text.*;
|
---|
19 |
|
---|
20 | import org.fife.ui.rsyntaxtextarea.modes.AbstractMarkupTokenMaker;
|
---|
21 | import org.fife.util.DynamicIntArray;
|
---|
22 |
|
---|
23 |
|
---|
24 | /**
|
---|
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>
|
---|
30 | *
|
---|
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
|
---|
34 | * thrown.<p>
|
---|
35 | *
|
---|
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.
|
---|
43 | *
|
---|
44 | * @author Robert Futrell
|
---|
45 | * @version 0.1
|
---|
46 | */
|
---|
47 | public class RSyntaxDocument extends PlainDocument implements SyntaxConstants {
|
---|
48 |
|
---|
49 | /**
|
---|
50 | * Creates a {@link TokenMaker} appropriate for a given programming
|
---|
51 | * language.
|
---|
52 | */
|
---|
53 | private transient TokenMakerFactory tokenMakerFactory;
|
---|
54 |
|
---|
55 | /**
|
---|
56 | * Splits text into tokens for the current programming language.
|
---|
57 | */
|
---|
58 | private transient TokenMaker tokenMaker;
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * The current syntax style. Only cached to keep this class serializable.
|
---|
62 | */
|
---|
63 | private String syntaxStyle;
|
---|
64 |
|
---|
65 | /**
|
---|
66 | * Array of values representing the "last token type" on each line. This
|
---|
67 | * is used in cases such as multiline comments: if the previous line
|
---|
68 | * ended with an (unclosed) multiline comment, we can use this knowledge
|
---|
69 | * and start the current line's syntax highlighting in multiline comment
|
---|
70 | * state.
|
---|
71 | */
|
---|
72 | protected transient DynamicIntArray lastTokensOnLines;
|
---|
73 |
|
---|
74 | private transient Segment s;
|
---|
75 |
|
---|
76 |
|
---|
77 | /**
|
---|
78 | * Constructs a plain text document. A default root element is created,
|
---|
79 | * and the tab size set to 5.
|
---|
80 | *
|
---|
81 | * @param syntaxStyle The syntax highlighting scheme to use.
|
---|
82 | */
|
---|
83 | public RSyntaxDocument(String syntaxStyle) {
|
---|
84 | this(null, syntaxStyle);
|
---|
85 | }
|
---|
86 |
|
---|
87 |
|
---|
88 | /**
|
---|
89 | * Constructs a plain text document. A default root element is created,
|
---|
90 | * and the tab size set to 5.
|
---|
91 | *
|
---|
92 | * @param tmf The <code>TokenMakerFactory</code> for this document. If
|
---|
93 | * this is <code>null</code>, a default factory is used.
|
---|
94 | * @param syntaxStyle The syntax highlighting scheme to use.
|
---|
95 | */
|
---|
96 | public RSyntaxDocument(TokenMakerFactory tmf, String syntaxStyle) {
|
---|
97 | super(new RGapContent());
|
---|
98 | putProperty(tabSizeAttribute, new Integer(5));
|
---|
99 | lastTokensOnLines = new DynamicIntArray(400);
|
---|
100 | lastTokensOnLines.add(Token.NULL); // Initial (empty) line.
|
---|
101 | s = new Segment();
|
---|
102 | setTokenMakerFactory(tmf);
|
---|
103 | setSyntaxStyle(syntaxStyle);
|
---|
104 | }
|
---|
105 |
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * Returns the character in the document at the specified offset.
|
---|
109 | *
|
---|
110 | * @param offset The offset of the character.
|
---|
111 | * @return The character.
|
---|
112 | * @throws BadLocationException If the offset is invalid.
|
---|
113 | */
|
---|
114 | public char charAt(int offset) throws BadLocationException {
|
---|
115 | return ((RGapContent)getContent()).charAt(offset);
|
---|
116 | }
|
---|
117 |
|
---|
118 |
|
---|
119 | /**
|
---|
120 | * Alerts all listeners to this document of an insertion. This is
|
---|
121 | * overridden so we can update our syntax highlighting stuff.<p>
|
---|
122 | * The syntax highlighting stuff has to be here instead of in
|
---|
123 | * <code>insertUpdate</code> because <code>insertUpdate</code> is not
|
---|
124 | * called by the undo/redo actions, but this method is.
|
---|
125 | *
|
---|
126 | * @param e The change.
|
---|
127 | */
|
---|
128 | protected void fireInsertUpdate(DocumentEvent e) {
|
---|
129 |
|
---|
130 | /*
|
---|
131 | * Now that the text is actually inserted into the content and
|
---|
132 | * element structure, we can update our token elements and "last
|
---|
133 | * tokens on lines" structure.
|
---|
134 | */
|
---|
135 |
|
---|
136 | Element lineMap = getDefaultRootElement();
|
---|
137 | DocumentEvent.ElementChange change = e.getChange(lineMap);
|
---|
138 | Element[] added = change==null ? null : change.getChildrenAdded();
|
---|
139 |
|
---|
140 | int numLines = lineMap.getElementCount();
|
---|
141 | int line = lineMap.getElementIndex(e.getOffset());
|
---|
142 | int previousLine = line - 1;
|
---|
143 | int previousTokenType = (previousLine>-1 ?
|
---|
144 | lastTokensOnLines.get(previousLine) : Token.NULL);
|
---|
145 |
|
---|
146 | // If entire lines were added...
|
---|
147 | if (added!=null && added.length>0) {
|
---|
148 |
|
---|
149 | Element[] removed = change.getChildrenRemoved();
|
---|
150 | int numRemoved = removed!=null ? removed.length : 0;
|
---|
151 |
|
---|
152 | int endBefore = line + added.length - numRemoved;
|
---|
153 | //System.err.println("... adding lines: " + line + " - " + (endBefore-1));
|
---|
154 | //System.err.println("... ... added: " + added.length + ", removed:" + numRemoved);
|
---|
155 | for (int i=line; i<endBefore; i++) {
|
---|
156 |
|
---|
157 | setSharedSegment(i); // Loads line i's text into s.
|
---|
158 |
|
---|
159 | int tokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
---|
160 | lastTokensOnLines.add(i, tokenType);
|
---|
161 | //System.err.println("--------- lastTokensOnLines.size() == " + lastTokensOnLines.getSize());
|
---|
162 |
|
---|
163 | previousTokenType = tokenType;
|
---|
164 |
|
---|
165 | } // End of for (int i=line; i<endBefore; i++).
|
---|
166 |
|
---|
167 | // Update last tokens for lines below until they stop changing.
|
---|
168 | updateLastTokensBelow(endBefore, numLines, previousTokenType);
|
---|
169 |
|
---|
170 | } // End of if (added!=null && added.length>0).
|
---|
171 |
|
---|
172 | // Otherwise, text was inserted on a single line...
|
---|
173 | else {
|
---|
174 |
|
---|
175 | // Update last tokens for lines below until they stop changing.
|
---|
176 | updateLastTokensBelow(line, numLines, previousTokenType);
|
---|
177 |
|
---|
178 | } // End of else.
|
---|
179 |
|
---|
180 | // Let all listeners know about the insertion.
|
---|
181 | super.fireInsertUpdate(e);
|
---|
182 |
|
---|
183 | }
|
---|
184 |
|
---|
185 |
|
---|
186 | /**
|
---|
187 | * This method is called AFTER the content has been inserted into the
|
---|
188 | * document and the element structure has been updated.<p>
|
---|
189 | * The syntax-highlighting updates need to be done here (as opposed to
|
---|
190 | * an override of <code>postRemoveUpdate</code>) as this method is called
|
---|
191 | * in response to undo/redo events, whereas <code>postRemoveUpdate</code>
|
---|
192 | * is not.<p>
|
---|
193 | * Now that the text is actually inserted into the content and element
|
---|
194 | * structure, we can update our token elements and "last tokens on
|
---|
195 | * lines" structure.
|
---|
196 | *
|
---|
197 | * @param chng The change that occurred.
|
---|
198 | * @see #removeUpdate
|
---|
199 | */
|
---|
200 | protected void fireRemoveUpdate(DocumentEvent chng) {
|
---|
201 |
|
---|
202 | Element lineMap = getDefaultRootElement();
|
---|
203 | int numLines = lineMap.getElementCount();
|
---|
204 |
|
---|
205 | DocumentEvent.ElementChange change = chng.getChange(lineMap);
|
---|
206 | Element[] removed = change==null ? null : change.getChildrenRemoved();
|
---|
207 |
|
---|
208 | // If entire lines were removed...
|
---|
209 | if (removed!=null && removed.length>0) {
|
---|
210 |
|
---|
211 | int line = change.getIndex(); // First line entirely removed.
|
---|
212 | int previousLine = line - 1; // Line before that.
|
---|
213 | int previousTokenType = (previousLine>-1 ?
|
---|
214 | lastTokensOnLines.get(previousLine) : Token.NULL);
|
---|
215 |
|
---|
216 | Element[] added = change.getChildrenAdded();
|
---|
217 | int numAdded = added==null ? 0 : added.length;
|
---|
218 |
|
---|
219 | // Remove the cached last-token values for the removed lines.
|
---|
220 | int endBefore = line + removed.length - numAdded;
|
---|
221 | //System.err.println("... removing lines: " + line + " - " + (endBefore-1));
|
---|
222 | //System.err.println("... added: " + numAdded + ", removed: " + removed.length);
|
---|
223 |
|
---|
224 | lastTokensOnLines.removeRange(line, endBefore); // Removing values for lines [line-(endBefore-1)].
|
---|
225 | //System.err.println("--------- lastTokensOnLines.size() == " + lastTokensOnLines.getSize());
|
---|
226 |
|
---|
227 | // Update last tokens for lines below until they've stopped changing.
|
---|
228 | updateLastTokensBelow(line, numLines, previousTokenType);
|
---|
229 |
|
---|
230 | } // End of if (removed!=null && removed.size()>0).
|
---|
231 |
|
---|
232 | // Otherwise, text was removed from just one line...
|
---|
233 | else {
|
---|
234 |
|
---|
235 | int line = lineMap.getElementIndex(chng.getOffset());
|
---|
236 | if (line>=lastTokensOnLines.getSize())
|
---|
237 | return; // If we're editing the last line in a document...
|
---|
238 |
|
---|
239 | int previousLine = line - 1;
|
---|
240 | int previousTokenType = (previousLine>-1 ?
|
---|
241 | lastTokensOnLines.get(previousLine) : Token.NULL);
|
---|
242 | //System.err.println("previousTokenType for line : " + previousLine + " is " + previousTokenType);
|
---|
243 | // Update last tokens for lines below until they've stopped changing.
|
---|
244 | updateLastTokensBelow(line, numLines, previousTokenType);
|
---|
245 |
|
---|
246 | }
|
---|
247 |
|
---|
248 | // Let all of our listeners know about the removal.
|
---|
249 | super.fireRemoveUpdate(chng);
|
---|
250 |
|
---|
251 | }
|
---|
252 |
|
---|
253 |
|
---|
254 | /**
|
---|
255 | * Returns whether closing markup tags should be automatically completed.
|
---|
256 | * This method only returns <code>true</code> if
|
---|
257 | * {@link #getLanguageIsMarkup()} also returns <code>true</code>.
|
---|
258 | *
|
---|
259 | * @return Whether markup closing tags should be automatically completed.
|
---|
260 | * @see #getLanguageIsMarkup()
|
---|
261 | */
|
---|
262 | public boolean getCompleteMarkupCloseTags() {
|
---|
263 | // TODO: Remove terrible dependency on AbstractMarkupTokenMaker
|
---|
264 | return getLanguageIsMarkup() &&
|
---|
265 | ((AbstractMarkupTokenMaker)tokenMaker).getCompleteCloseTags();
|
---|
266 | }
|
---|
267 |
|
---|
268 |
|
---|
269 | /**
|
---|
270 | * Returns whether the current programming language uses curly braces
|
---|
271 | * ('<tt>{</tt>' and '<tt>}</tt>') to denote code blocks.
|
---|
272 | *
|
---|
273 | * @return Whether curly braces denote code blocks.
|
---|
274 | */
|
---|
275 | public boolean getCurlyBracesDenoteCodeBlocks() {
|
---|
276 | return tokenMaker.getCurlyBracesDenoteCodeBlocks();
|
---|
277 | }
|
---|
278 |
|
---|
279 |
|
---|
280 | /**
|
---|
281 | * Returns whether the current language is a markup language, such as
|
---|
282 | * HTML, XML or PHP.
|
---|
283 | *
|
---|
284 | * @return Whether the current language is a markup language.
|
---|
285 | */
|
---|
286 | public boolean getLanguageIsMarkup() {
|
---|
287 | return tokenMaker.isMarkupLanguage();
|
---|
288 | }
|
---|
289 |
|
---|
290 |
|
---|
291 | /**
|
---|
292 | * Returns the token type of the last token on the given line.
|
---|
293 | *
|
---|
294 | * @param line The line to inspect.
|
---|
295 | * @return The token type of the last token on the specified line. If
|
---|
296 | * the line is invalid, an exception is thrown.
|
---|
297 | */
|
---|
298 | public int getLastTokenTypeOnLine(int line) {
|
---|
299 | return lastTokensOnLines.get(line);
|
---|
300 | }
|
---|
301 |
|
---|
302 |
|
---|
303 | /**
|
---|
304 | * Returns the text to place at the beginning and end of a
|
---|
305 | * line to "comment" it in the current programming language.
|
---|
306 | *
|
---|
307 | * @return The start and end strings to add to a line to "comment"
|
---|
308 | * it out. A <code>null</code> value for either means there
|
---|
309 | * is no string to add for that part. A value of
|
---|
310 | * <code>null</code> for the array means this language
|
---|
311 | * does not support commenting/uncommenting lines.
|
---|
312 | */
|
---|
313 | public String[] getLineCommentStartAndEnd() {
|
---|
314 | return tokenMaker.getLineCommentStartAndEnd();
|
---|
315 | }
|
---|
316 |
|
---|
317 |
|
---|
318 | /**
|
---|
319 | * Returns whether tokens of the specified type should have "mark
|
---|
320 | * occurrences" enabled for the current programming language.
|
---|
321 | *
|
---|
322 | * @param type The token type.
|
---|
323 | * @return Whether tokens of this type should have "mark occurrences"
|
---|
324 | * enabled.
|
---|
325 | */
|
---|
326 | boolean getMarkOccurrencesOfTokenType(int type) {
|
---|
327 | return tokenMaker.getMarkOccurrencesOfTokenType(type);
|
---|
328 | }
|
---|
329 |
|
---|
330 |
|
---|
331 | /**
|
---|
332 | * This method returns whether auto indentation should be done if Enter
|
---|
333 | * is pressed at the end of the specified line.
|
---|
334 | *
|
---|
335 | * @param line The line to check.
|
---|
336 | * @return Whether an extra indentation should be done.
|
---|
337 | */
|
---|
338 | public boolean getShouldIndentNextLine(int line) {
|
---|
339 | Token t = getTokenListForLine(line);
|
---|
340 | t = t.getLastNonCommentNonWhitespaceToken();
|
---|
341 | return tokenMaker.getShouldIndentNextLineAfter(t);
|
---|
342 | }
|
---|
343 |
|
---|
344 |
|
---|
345 | /**
|
---|
346 | * Returns a token list for the specified segment of text representing
|
---|
347 | * the specified line number. This method is basically a wrapper for
|
---|
348 | * <code>tokenMaker.getTokenList</code> that takes into account the last
|
---|
349 | * token on the previous line to assure token accuracy.
|
---|
350 | *
|
---|
351 | * @param line The line number of <code>text</code> in the document, >= 0.
|
---|
352 | * @return A token list representing the specified line.
|
---|
353 | */
|
---|
354 | public final Token getTokenListForLine(int line) {
|
---|
355 | Element map = getDefaultRootElement();
|
---|
356 | Element elem = map.getElement(line);
|
---|
357 | int startOffset = elem.getStartOffset();
|
---|
358 | //int endOffset = (line==map.getElementCount()-1 ? elem.getEndOffset() - 1:
|
---|
359 | // elem.getEndOffset() - 1);
|
---|
360 | int endOffset = elem.getEndOffset() - 1; // Why always "-1"?
|
---|
361 | try {
|
---|
362 | getText(startOffset,endOffset-startOffset, s);
|
---|
363 | } catch (BadLocationException ble) {
|
---|
364 | ble.printStackTrace();
|
---|
365 | return null;
|
---|
366 | }
|
---|
367 | int initialTokenType = line==0 ? Token.NULL :
|
---|
368 | getLastTokenTypeOnLine(line-1);
|
---|
369 | return tokenMaker.getTokenList(s, initialTokenType, startOffset);
|
---|
370 | }
|
---|
371 |
|
---|
372 |
|
---|
373 | boolean insertBreakSpecialHandling(ActionEvent e) {
|
---|
374 | Action a = tokenMaker.getInsertBreakAction();
|
---|
375 | if (a!=null) {
|
---|
376 | a.actionPerformed(e);
|
---|
377 | return true;
|
---|
378 | }
|
---|
379 | return false;
|
---|
380 | }
|
---|
381 |
|
---|
382 |
|
---|
383 | /**
|
---|
384 | * Returns whether whitespace is visible.
|
---|
385 | *
|
---|
386 | * @return Whether whitespace is visible.
|
---|
387 | * @see #setWhitespaceVisible(boolean)
|
---|
388 | */
|
---|
389 | public boolean isWhitespaceVisible() {
|
---|
390 | return tokenMaker==null ? false : tokenMaker.isWhitespaceVisible();
|
---|
391 | }
|
---|
392 |
|
---|
393 |
|
---|
394 | /**
|
---|
395 | * Deserializes a document.
|
---|
396 | *
|
---|
397 | * @param in The stream to read from.
|
---|
398 | * @throws ClassNotFoundException
|
---|
399 | * @throws IOException
|
---|
400 | */
|
---|
401 | private void readObject(ObjectInputStream in)
|
---|
402 | throws ClassNotFoundException, IOException {
|
---|
403 |
|
---|
404 | in.defaultReadObject();
|
---|
405 |
|
---|
406 | // Install default TokenMakerFactory. To support custom TokenMakers,
|
---|
407 | // both JVM's should install default TokenMakerFactories that support
|
---|
408 | // the language they want to use beforehand.
|
---|
409 | setTokenMakerFactory(null);
|
---|
410 |
|
---|
411 | // Handle other transient stuff
|
---|
412 | this.s = new Segment();
|
---|
413 | int lineCount = getDefaultRootElement().getElementCount();
|
---|
414 | lastTokensOnLines = new DynamicIntArray(lineCount);
|
---|
415 | setSyntaxStyle(syntaxStyle); // Actually install (transient) TokenMaker
|
---|
416 | setWhitespaceVisible(in.readBoolean()); // Do after setSyntaxStyle()
|
---|
417 |
|
---|
418 | }
|
---|
419 |
|
---|
420 |
|
---|
421 | /**
|
---|
422 | * Makes our private <code>Segment s</code> point to the text in our
|
---|
423 | * document referenced by the specified element. Note that
|
---|
424 | * <code>line</code> MUST be a valid line number in the document.
|
---|
425 | *
|
---|
426 | * @param line The line number you want to get.
|
---|
427 | */
|
---|
428 | private final void setSharedSegment(int line) {
|
---|
429 |
|
---|
430 | Element map = getDefaultRootElement();
|
---|
431 | //int numLines = map.getElementCount();
|
---|
432 |
|
---|
433 | Element element = map.getElement(line);
|
---|
434 | if (element==null)
|
---|
435 | throw new InternalError("Invalid line number: " + line);
|
---|
436 | int startOffset = element.getStartOffset();
|
---|
437 | //int endOffset = (line==numLines-1 ?
|
---|
438 | // element.getEndOffset()-1 : element.getEndOffset() - 1);
|
---|
439 | int endOffset = element.getEndOffset()-1; // Why always "-1"?
|
---|
440 | try {
|
---|
441 | getText(startOffset, endOffset-startOffset, s);
|
---|
442 | } catch (BadLocationException ble) {
|
---|
443 | throw new InternalError("Text range not in document: " +
|
---|
444 | startOffset + "-" + endOffset);
|
---|
445 | }
|
---|
446 |
|
---|
447 | }
|
---|
448 |
|
---|
449 |
|
---|
450 | /**
|
---|
451 | * Sets the syntax style being used for syntax highlighting in this
|
---|
452 | * document. What styles are supported by a document is determined by its
|
---|
453 | * {@link TokenMakerFactory}. By default, all <code>RSyntaxDocument</code>s
|
---|
454 | * support all languages built into <code>RSyntaxTextArea</code>.
|
---|
455 | *
|
---|
456 | * @param styleKey The new style to use, such as
|
---|
457 | * {@link SyntaxConstants#SYNTAX_STYLE_JAVA}. If this style is not
|
---|
458 | * known or supported by this document, then
|
---|
459 | * {@link SyntaxConstants#SYNTAX_STYLE_NONE} is used.
|
---|
460 | */
|
---|
461 | public void setSyntaxStyle(String styleKey) {
|
---|
462 | boolean wsVisible = isWhitespaceVisible();
|
---|
463 | tokenMaker = tokenMakerFactory.getTokenMaker(styleKey);
|
---|
464 | tokenMaker.setWhitespaceVisible(wsVisible);
|
---|
465 | updateSyntaxHighlightingInformation();
|
---|
466 | this.syntaxStyle = styleKey;
|
---|
467 | }
|
---|
468 |
|
---|
469 |
|
---|
470 | /**
|
---|
471 | * Sets the syntax style being used for syntax highlighting in this
|
---|
472 | * document. You should call this method if you've created a custom token
|
---|
473 | * maker for a language not normally supported by
|
---|
474 | * <code>RSyntaxTextArea</code>.
|
---|
475 | *
|
---|
476 | * @param tokenMaker The new token maker to use.
|
---|
477 | */
|
---|
478 | public void setSyntaxStyle(TokenMaker tokenMaker) {
|
---|
479 | tokenMaker.setWhitespaceVisible(isWhitespaceVisible());
|
---|
480 | this.tokenMaker = tokenMaker;
|
---|
481 | updateSyntaxHighlightingInformation();
|
---|
482 | }
|
---|
483 |
|
---|
484 |
|
---|
485 | /**
|
---|
486 | * Sets the token maker factory used by this document.
|
---|
487 | *
|
---|
488 | * @param tmf The <code>TokenMakerFactory</code> for this document. If
|
---|
489 | * this is <code>null</code>, a default factory is used.
|
---|
490 | */
|
---|
491 | public void setTokenMakerFactory(TokenMakerFactory tmf) {
|
---|
492 | tokenMakerFactory = tmf!=null ? tmf :
|
---|
493 | TokenMakerFactory.getDefaultInstance();
|
---|
494 | }
|
---|
495 |
|
---|
496 |
|
---|
497 | /**
|
---|
498 | * Sets whether whitespace is visible. This property is actually setting
|
---|
499 | * whether the tokens generated from this document "paint" something when
|
---|
500 | * they represent whitespace.
|
---|
501 | *
|
---|
502 | * @param visible Whether whitespace should be visible.
|
---|
503 | * @see #isWhitespaceVisible()
|
---|
504 | */
|
---|
505 | public void setWhitespaceVisible(boolean visible) {
|
---|
506 | tokenMaker.setWhitespaceVisible(visible);
|
---|
507 | }
|
---|
508 |
|
---|
509 |
|
---|
510 | /**
|
---|
511 | * Loops through the last-tokens-on-lines array from a specified point
|
---|
512 | * onward, updating last-token values until they stop changing. This
|
---|
513 | * should be called when lines are updated/inserted/removed, as doing
|
---|
514 | * so may cause lines below to change color.
|
---|
515 | *
|
---|
516 | * @param line The first line to check for a change in last-token value.
|
---|
517 | * @param numLines The number of lines in the document.
|
---|
518 | * @param previousTokenType The last-token value of the line just before
|
---|
519 | * <code>line</code>.
|
---|
520 | * @return The last line that needs repainting.
|
---|
521 | */
|
---|
522 | private int updateLastTokensBelow(int line, int numLines, int previousTokenType) {
|
---|
523 |
|
---|
524 | int firstLine = line;
|
---|
525 |
|
---|
526 | // Loop through all lines past our starting point. Update even the last
|
---|
527 | // line's info, even though there aren't any lines after it that depend
|
---|
528 | // on it changing for them to be changed, as its state may be used
|
---|
529 | // elsewhere in the library.
|
---|
530 | int end = numLines;
|
---|
531 | //System.err.println("--- end==" + end + " (numLines==" + numLines + ")");
|
---|
532 | while (line<end) {
|
---|
533 |
|
---|
534 | setSharedSegment(line); // Sets s's text to that of line 'line' in the document.
|
---|
535 |
|
---|
536 | int oldTokenType = lastTokensOnLines.get(line);
|
---|
537 | int newTokenType = tokenMaker.getLastTokenTypeOnLine(s, previousTokenType);
|
---|
538 | //System.err.println("---------------- line " + line + "; oldTokenType==" + oldTokenType + ", newTokenType==" + newTokenType + ", s=='" + s + "'");
|
---|
539 |
|
---|
540 | // If this line's end-token value didn't change, stop here. Note
|
---|
541 | // that we're saying this line needs repainting; this is because
|
---|
542 | // the beginning of this line did indeed change color, but the
|
---|
543 | // end didn't.
|
---|
544 | if (oldTokenType==newTokenType) {
|
---|
545 | //System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
---|
546 | fireChangedUpdate(new DefaultDocumentEvent(firstLine, line, DocumentEvent.EventType.CHANGE));
|
---|
547 | return line;
|
---|
548 | }
|
---|
549 |
|
---|
550 | // If the line's end-token value did change, update it and
|
---|
551 | // keep going.
|
---|
552 | // NOTE: "setUnsafe" is okay here as the bounds checking was
|
---|
553 | // already done in lastTokensOnLines.get(line) above.
|
---|
554 | lastTokensOnLines.setUnsafe(line, newTokenType);
|
---|
555 | previousTokenType = newTokenType;
|
---|
556 | line++;
|
---|
557 |
|
---|
558 | } // End of while (line<numLines).
|
---|
559 |
|
---|
560 | // If any lines had their token types changed, fire a changed update
|
---|
561 | // for them. The view will repaint the area covered by the lines.
|
---|
562 | // FIXME: We currently cheat and send the line range that needs to be
|
---|
563 | // repainted as the "offset and length" of the change, since this is
|
---|
564 | // what the view needs. We really should send the actual offset and
|
---|
565 | // length.
|
---|
566 | if (line>firstLine) {
|
---|
567 | //System.err.println("... ... ... repainting lines " + firstLine + "-" + line);
|
---|
568 | fireChangedUpdate(new DefaultDocumentEvent(firstLine, line,
|
---|
569 | DocumentEvent.EventType.CHANGE));
|
---|
570 | }
|
---|
571 |
|
---|
572 | return line;
|
---|
573 |
|
---|
574 | }
|
---|
575 |
|
---|
576 |
|
---|
577 | /**
|
---|
578 | * Updates internal state information; e.g. the "last tokens on lines"
|
---|
579 | * data. After this, a changed update is fired to let listeners know that
|
---|
580 | * the document's structure has changed.<p>
|
---|
581 | *
|
---|
582 | * This is called internally whenever the syntax style changes.
|
---|
583 | */
|
---|
584 | protected void updateSyntaxHighlightingInformation() {
|
---|
585 |
|
---|
586 | // Reinitialize the "last token on each line" array. Note that since
|
---|
587 | // the actual text in the document isn't changing, the number of lines
|
---|
588 | // is the same.
|
---|
589 | Element map = getDefaultRootElement();
|
---|
590 | int numLines = map.getElementCount();
|
---|
591 | int lastTokenType = Token.NULL;
|
---|
592 | for (int i=0; i<numLines; i++) {
|
---|
593 | setSharedSegment(i);
|
---|
594 | lastTokenType = tokenMaker.getLastTokenTypeOnLine(s, lastTokenType);
|
---|
595 | lastTokensOnLines.set(i, lastTokenType);
|
---|
596 | }
|
---|
597 |
|
---|
598 | // Let everybody know that syntax styles have (probably) changed.
|
---|
599 | fireChangedUpdate(new DefaultDocumentEvent(
|
---|
600 | 0, numLines-1, DocumentEvent.EventType.CHANGE));
|
---|
601 |
|
---|
602 | }
|
---|
603 |
|
---|
604 |
|
---|
605 | /**
|
---|
606 | * Overridden for custom serialization purposes.
|
---|
607 | *
|
---|
608 | * @param out The stream to write to.
|
---|
609 | * @throws IOException If an IO error occurs.
|
---|
610 | */
|
---|
611 | private void writeObject(ObjectOutputStream out)throws IOException {
|
---|
612 | out.defaultWriteObject();
|
---|
613 | out.writeBoolean(isWhitespaceVisible());
|
---|
614 | }
|
---|
615 |
|
---|
616 |
|
---|
617 | /**
|
---|
618 | * Document content that provides fast access to individual characters.
|
---|
619 | *
|
---|
620 | * @author Robert Futrell
|
---|
621 | * @version 1.0
|
---|
622 | */
|
---|
623 | private static class RGapContent extends GapContent {
|
---|
624 |
|
---|
625 | public RGapContent() {
|
---|
626 | }
|
---|
627 |
|
---|
628 | public char charAt(int offset) throws BadLocationException {
|
---|
629 | if (offset<0 || offset>=length()) {
|
---|
630 | throw new BadLocationException("Invalid offset", offset);
|
---|
631 | }
|
---|
632 | int g0 = getGapStart();
|
---|
633 | char[] array = (char[]) getArray();
|
---|
634 | if (offset<g0) { // below gap
|
---|
635 | return array[offset];
|
---|
636 | }
|
---|
637 | return array[getGapEnd() + offset - g0]; // above gap
|
---|
638 | }
|
---|
639 |
|
---|
640 | }
|
---|
641 |
|
---|
642 |
|
---|
643 | } |
---|