gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.swing / org.gvsig.scripting.swing.impl / src / main / java / org / gvsig / scripting / swing / impl / syntaxhighlight / styles / JavaStyledDocument.java @ 1440
History | View | Annotate | Download (13.8 KB)
1 | 165 | jobacas | package org.gvsig.scripting.swing.impl.syntaxhighlight.styles; |
---|---|---|---|
2 | |||
3 | import java.awt.Color; |
||
4 | import java.util.HashSet; |
||
5 | 212 | cordinyana | import java.util.Set; |
6 | 165 | jobacas | |
7 | import javax.swing.text.AttributeSet; |
||
8 | import javax.swing.text.BadLocationException; |
||
9 | import javax.swing.text.DefaultEditorKit; |
||
10 | import javax.swing.text.DefaultStyledDocument; |
||
11 | import javax.swing.text.Element; |
||
12 | import javax.swing.text.MutableAttributeSet; |
||
13 | import javax.swing.text.SimpleAttributeSet; |
||
14 | import javax.swing.text.StyleConstants; |
||
15 | |||
16 | |||
17 | public class JavaStyledDocument extends DefaultStyledDocument { |
||
18 | /**
|
||
19 | *
|
||
20 | */
|
||
21 | private static final long serialVersionUID = 2859530660266127826L; |
||
22 | |||
23 | 212 | cordinyana | private final DefaultStyledDocument doc; |
24 | private final Element rootElement; |
||
25 | 165 | jobacas | |
26 | private boolean multiLineComment; |
||
27 | 212 | cordinyana | private final MutableAttributeSet normal; |
28 | private final MutableAttributeSet keyword; |
||
29 | private final MutableAttributeSet comment; |
||
30 | private final MutableAttributeSet quote; |
||
31 | 165 | jobacas | |
32 | 212 | cordinyana | private final Set<String> keywords; |
33 | 165 | jobacas | |
34 | public JavaStyledDocument()
|
||
35 | { |
||
36 | doc = this;
|
||
37 | rootElement = doc.getDefaultRootElement(); |
||
38 | putProperty( DefaultEditorKit.EndOfLineStringProperty, "\n" ); |
||
39 | |||
40 | normal = new SimpleAttributeSet(); |
||
41 | StyleConstants.setForeground(normal, Color.black); |
||
42 | |||
43 | comment = new SimpleAttributeSet(); |
||
44 | StyleConstants.setForeground(comment, Color.gray); |
||
45 | StyleConstants.setItalic(comment, true); |
||
46 | |||
47 | keyword = new SimpleAttributeSet(); |
||
48 | StyleConstants.setForeground(keyword, Color.blue); |
||
49 | |||
50 | quote = new SimpleAttributeSet(); |
||
51 | StyleConstants.setForeground(quote, Color.red); |
||
52 | |||
53 | 212 | cordinyana | keywords = new HashSet<String>(); |
54 | 165 | jobacas | keywords.add( "abstract" );
|
55 | keywords.add( "boolean" );
|
||
56 | keywords.add( "break" );
|
||
57 | keywords.add( "byte" );
|
||
58 | keywords.add( "byvalue" );
|
||
59 | keywords.add( "case" );
|
||
60 | keywords.add( "cast" );
|
||
61 | keywords.add( "catch" );
|
||
62 | keywords.add( "char" );
|
||
63 | keywords.add( "class" );
|
||
64 | keywords.add( "const" );
|
||
65 | keywords.add( "continue" );
|
||
66 | keywords.add( "default" );
|
||
67 | keywords.add( "do" );
|
||
68 | keywords.add( "double" );
|
||
69 | keywords.add( "else" );
|
||
70 | keywords.add( "extends" );
|
||
71 | keywords.add( "false" );
|
||
72 | keywords.add( "final" );
|
||
73 | keywords.add( "finally" );
|
||
74 | keywords.add( "float" );
|
||
75 | keywords.add( "for" );
|
||
76 | keywords.add( "future" );
|
||
77 | keywords.add( "generic" );
|
||
78 | keywords.add( "goto" );
|
||
79 | keywords.add( "if" );
|
||
80 | keywords.add( "implements" );
|
||
81 | keywords.add( "import" );
|
||
82 | keywords.add( "inner" );
|
||
83 | keywords.add( "instanceof" );
|
||
84 | keywords.add( "int" );
|
||
85 | keywords.add( "interface" );
|
||
86 | keywords.add( "long" );
|
||
87 | keywords.add( "native" );
|
||
88 | keywords.add( "new" );
|
||
89 | keywords.add( "null" );
|
||
90 | keywords.add( "operator" );
|
||
91 | keywords.add( "outer" );
|
||
92 | keywords.add( "package" );
|
||
93 | keywords.add( "private" );
|
||
94 | keywords.add( "protected" );
|
||
95 | keywords.add( "public" );
|
||
96 | keywords.add( "rest" );
|
||
97 | keywords.add( "return" );
|
||
98 | keywords.add( "short" );
|
||
99 | keywords.add( "static" );
|
||
100 | keywords.add( "super" );
|
||
101 | keywords.add( "switch" );
|
||
102 | keywords.add( "synchronized" );
|
||
103 | keywords.add( "this" );
|
||
104 | keywords.add( "throw" );
|
||
105 | keywords.add( "throws" );
|
||
106 | keywords.add( "transient" );
|
||
107 | keywords.add( "true" );
|
||
108 | keywords.add( "try" );
|
||
109 | keywords.add( "var" );
|
||
110 | keywords.add( "void" );
|
||
111 | keywords.add( "volatile" );
|
||
112 | keywords.add( "while" );
|
||
113 | } |
||
114 | |||
115 | /*
|
||
116 | * Override to apply syntax highlighting after the document has been updated
|
||
117 | */
|
||
118 | 212 | cordinyana | @Override
|
119 | public void insertString(int offset, String str, AttributeSet a) throws BadLocationException |
||
120 | 165 | jobacas | { |
121 | if (str.equals("{")) |
||
122 | str = addMatchingBrace(offset); |
||
123 | |||
124 | super.insertString(offset, str, a);
|
||
125 | processChangedLines(offset, str.length()); |
||
126 | } |
||
127 | |||
128 | /*
|
||
129 | * Override to apply syntax highlighting after the document has been updated
|
||
130 | */
|
||
131 | 212 | cordinyana | @Override
|
132 | public void remove(int offset, int length) throws BadLocationException |
||
133 | 165 | jobacas | { |
134 | super.remove(offset, length);
|
||
135 | processChangedLines(offset, 0);
|
||
136 | } |
||
137 | |||
138 | /*
|
||
139 | * Determine how many lines have been changed,
|
||
140 | * then apply highlighting to each line
|
||
141 | */
|
||
142 | public void processChangedLines(int offset, int length) |
||
143 | throws BadLocationException |
||
144 | { |
||
145 | String content = doc.getText(0, doc.getLength()); |
||
146 | |||
147 | // The lines affected by the latest document update
|
||
148 | |||
149 | int startLine = rootElement.getElementIndex( offset );
|
||
150 | int endLine = rootElement.getElementIndex( offset + length );
|
||
151 | |||
152 | // Make sure all comment lines prior to the start line are commented
|
||
153 | // and determine if the start line is still in a multi line comment
|
||
154 | |||
155 | setMultiLineComment( commentLinesBefore( content, startLine ) ); |
||
156 | |||
157 | // Do the actual highlighting
|
||
158 | |||
159 | for (int i = startLine; i <= endLine; i++) |
||
160 | { |
||
161 | applyHighlighting(content, i); |
||
162 | } |
||
163 | |||
164 | // Resolve highlighting to the next end multi line delimiter
|
||
165 | |||
166 | if (isMultiLineComment())
|
||
167 | commentLinesAfter(content, endLine); |
||
168 | else
|
||
169 | highlightLinesAfter(content, endLine); |
||
170 | } |
||
171 | |||
172 | /*
|
||
173 | * Highlight lines when a multi line comment is still 'open'
|
||
174 | * (ie. matching end delimiter has not yet been encountered)
|
||
175 | */
|
||
176 | private boolean commentLinesBefore(String content, int line) |
||
177 | { |
||
178 | int offset = rootElement.getElement( line ).getStartOffset();
|
||
179 | |||
180 | // Start of comment not found, nothing to do
|
||
181 | |||
182 | int startDelimiter = lastIndexOf( content, getStartDelimiter(), offset - 2 ); |
||
183 | |||
184 | if (startDelimiter < 0) |
||
185 | return false; |
||
186 | |||
187 | // Matching start/end of comment found, nothing to do
|
||
188 | |||
189 | int endDelimiter = indexOf( content, getEndDelimiter(), startDelimiter );
|
||
190 | |||
191 | if (endDelimiter < offset & endDelimiter != -1) |
||
192 | return false; |
||
193 | |||
194 | // End of comment not found, highlight the lines
|
||
195 | |||
196 | doc.setCharacterAttributes(startDelimiter, offset - startDelimiter + 1, comment, false); |
||
197 | return true; |
||
198 | } |
||
199 | |||
200 | /*
|
||
201 | * Highlight comment lines to matching end delimiter
|
||
202 | */
|
||
203 | private void commentLinesAfter(String content, int line) |
||
204 | { |
||
205 | int offset = rootElement.getElement( line ).getEndOffset();
|
||
206 | |||
207 | // End of comment not found, nothing to do
|
||
208 | |||
209 | int endDelimiter = indexOf( content, getEndDelimiter(), offset );
|
||
210 | |||
211 | if (endDelimiter < 0) |
||
212 | return;
|
||
213 | |||
214 | // Matching start/end of comment found, comment the lines
|
||
215 | |||
216 | int startDelimiter = lastIndexOf( content, getStartDelimiter(), endDelimiter );
|
||
217 | |||
218 | if (startDelimiter < 0 || startDelimiter <= offset) |
||
219 | { |
||
220 | doc.setCharacterAttributes(offset, endDelimiter - offset + 1, comment, false); |
||
221 | } |
||
222 | } |
||
223 | |||
224 | /*
|
||
225 | * Highlight lines to start or end delimiter
|
||
226 | */
|
||
227 | private void highlightLinesAfter(String content, int line) |
||
228 | throws BadLocationException |
||
229 | { |
||
230 | int offset = rootElement.getElement( line ).getEndOffset();
|
||
231 | |||
232 | // Start/End delimiter not found, nothing to do
|
||
233 | |||
234 | int startDelimiter = indexOf( content, getStartDelimiter(), offset );
|
||
235 | int endDelimiter = indexOf( content, getEndDelimiter(), offset );
|
||
236 | |||
237 | if (startDelimiter < 0) |
||
238 | startDelimiter = content.length(); |
||
239 | |||
240 | if (endDelimiter < 0) |
||
241 | endDelimiter = content.length(); |
||
242 | |||
243 | int delimiter = Math.min(startDelimiter, endDelimiter); |
||
244 | |||
245 | if (delimiter < offset)
|
||
246 | return;
|
||
247 | |||
248 | // Start/End delimiter found, reapply highlighting
|
||
249 | |||
250 | int endLine = rootElement.getElementIndex( delimiter );
|
||
251 | |||
252 | for (int i = line + 1; i < endLine; i++) |
||
253 | { |
||
254 | Element branch = rootElement.getElement( i );
|
||
255 | Element leaf = doc.getCharacterElement( branch.getStartOffset() );
|
||
256 | AttributeSet as = leaf.getAttributes();
|
||
257 | |||
258 | if ( as.isEqual(comment) )
|
||
259 | applyHighlighting(content, i); |
||
260 | } |
||
261 | } |
||
262 | |||
263 | /*
|
||
264 | * Parse the line to determine the appropriate highlighting
|
||
265 | */
|
||
266 | private void applyHighlighting(String content, int line) |
||
267 | throws BadLocationException |
||
268 | { |
||
269 | int startOffset = rootElement.getElement( line ).getStartOffset();
|
||
270 | int endOffset = rootElement.getElement( line ).getEndOffset() - 1; |
||
271 | |||
272 | int lineLength = endOffset - startOffset;
|
||
273 | int contentLength = content.length();
|
||
274 | |||
275 | if (endOffset >= contentLength)
|
||
276 | endOffset = contentLength - 1;
|
||
277 | |||
278 | // check for multi line comments
|
||
279 | // (always set the comment attribute for the entire line)
|
||
280 | |||
281 | if (endingMultiLineComment(content, startOffset, endOffset)
|
||
282 | || isMultiLineComment() |
||
283 | || startingMultiLineComment(content, startOffset, endOffset) ) |
||
284 | { |
||
285 | doc.setCharacterAttributes(startOffset, endOffset - startOffset + 1, comment, false); |
||
286 | return;
|
||
287 | } |
||
288 | |||
289 | // set normal attributes for the line
|
||
290 | |||
291 | doc.setCharacterAttributes(startOffset, lineLength, normal, true);
|
||
292 | |||
293 | // check for single line comment
|
||
294 | |||
295 | int index = content.indexOf(getSingleLineDelimiter(), startOffset);
|
||
296 | |||
297 | if ( (index > -1) && (index < endOffset) ) |
||
298 | { |
||
299 | doc.setCharacterAttributes(index, endOffset - index + 1, comment, false); |
||
300 | endOffset = index - 1;
|
||
301 | } |
||
302 | |||
303 | // check for tokens
|
||
304 | |||
305 | checkForTokens(content, startOffset, endOffset); |
||
306 | } |
||
307 | |||
308 | /*
|
||
309 | * Does this line contain the start delimiter
|
||
310 | */
|
||
311 | private boolean startingMultiLineComment(String content, int startOffset, int endOffset) |
||
312 | throws BadLocationException |
||
313 | { |
||
314 | int index = indexOf( content, getStartDelimiter(), startOffset );
|
||
315 | |||
316 | if ( (index < 0) || (index > endOffset) ) |
||
317 | return false; |
||
318 | else
|
||
319 | { |
||
320 | setMultiLineComment( true );
|
||
321 | return true; |
||
322 | } |
||
323 | } |
||
324 | |||
325 | /*
|
||
326 | * Does this line contain the end delimiter
|
||
327 | */
|
||
328 | private boolean endingMultiLineComment(String content, int startOffset, int endOffset) |
||
329 | throws BadLocationException |
||
330 | { |
||
331 | int index = indexOf( content, getEndDelimiter(), startOffset );
|
||
332 | |||
333 | if ( (index < 0) || (index > endOffset) ) |
||
334 | return false; |
||
335 | else
|
||
336 | { |
||
337 | setMultiLineComment( false );
|
||
338 | return true; |
||
339 | } |
||
340 | } |
||
341 | |||
342 | /*
|
||
343 | * We have found a start delimiter
|
||
344 | * and are still searching for the end delimiter
|
||
345 | */
|
||
346 | private boolean isMultiLineComment() |
||
347 | { |
||
348 | return multiLineComment;
|
||
349 | } |
||
350 | |||
351 | private void setMultiLineComment(boolean value) |
||
352 | { |
||
353 | multiLineComment = value; |
||
354 | } |
||
355 | |||
356 | /*
|
||
357 | * Parse the line for tokens to highlight
|
||
358 | */
|
||
359 | private void checkForTokens(String content, int startOffset, int endOffset) |
||
360 | { |
||
361 | while (startOffset <= endOffset)
|
||
362 | { |
||
363 | // skip the delimiters to find the start of a new token
|
||
364 | |||
365 | while ( isDelimiter( content.substring(startOffset, startOffset + 1) ) ) |
||
366 | { |
||
367 | if (startOffset < endOffset)
|
||
368 | startOffset++; |
||
369 | else
|
||
370 | return;
|
||
371 | } |
||
372 | |||
373 | // Extract and process the entire token
|
||
374 | |||
375 | if ( isQuoteDelimiter( content.substring(startOffset, startOffset + 1) ) ) |
||
376 | startOffset = getQuoteToken(content, startOffset, endOffset); |
||
377 | else
|
||
378 | startOffset = getOtherToken(content, startOffset, endOffset); |
||
379 | } |
||
380 | } |
||
381 | |||
382 | /*
|
||
383 | *
|
||
384 | */
|
||
385 | private int getQuoteToken(String content, int startOffset, int endOffset) |
||
386 | { |
||
387 | String quoteDelimiter = content.substring(startOffset, startOffset + 1); |
||
388 | String escapeString = getEscapeString(quoteDelimiter);
|
||
389 | |||
390 | int index;
|
||
391 | int endOfQuote = startOffset;
|
||
392 | |||
393 | // skip over the escape quotes in this quote
|
||
394 | |||
395 | index = content.indexOf(escapeString, endOfQuote + 1);
|
||
396 | |||
397 | while ( (index > -1) && (index < endOffset) ) |
||
398 | { |
||
399 | endOfQuote = index + 1;
|
||
400 | index = content.indexOf(escapeString, endOfQuote); |
||
401 | } |
||
402 | |||
403 | // now find the matching delimiter
|
||
404 | |||
405 | index = content.indexOf(quoteDelimiter, endOfQuote + 1);
|
||
406 | |||
407 | if ( (index < 0) || (index > endOffset) ) |
||
408 | endOfQuote = endOffset; |
||
409 | else
|
||
410 | endOfQuote = index; |
||
411 | |||
412 | doc.setCharacterAttributes(startOffset, endOfQuote - startOffset + 1, quote, false); |
||
413 | |||
414 | return endOfQuote + 1; |
||
415 | } |
||
416 | |||
417 | /*
|
||
418 | *
|
||
419 | */
|
||
420 | private int getOtherToken(String content, int startOffset, int endOffset) |
||
421 | { |
||
422 | int endOfToken = startOffset + 1; |
||
423 | |||
424 | while ( endOfToken <= endOffset )
|
||
425 | { |
||
426 | if ( isDelimiter( content.substring(endOfToken, endOfToken + 1) ) ) |
||
427 | break;
|
||
428 | |||
429 | endOfToken++; |
||
430 | } |
||
431 | |||
432 | String token = content.substring(startOffset, endOfToken);
|
||
433 | |||
434 | if ( isKeyword( token ) )
|
||
435 | { |
||
436 | doc.setCharacterAttributes(startOffset, endOfToken - startOffset, keyword, false);
|
||
437 | } |
||
438 | |||
439 | return endOfToken + 1; |
||
440 | } |
||
441 | |||
442 | /*
|
||
443 | * Assume the needle will the found at the start/end of the line
|
||
444 | */
|
||
445 | private int indexOf(String content, String needle, int offset) |
||
446 | { |
||
447 | int index;
|
||
448 | |||
449 | while ( (index = content.indexOf(needle, offset)) != -1 ) |
||
450 | { |
||
451 | String text = getLine( content, index ).trim();
|
||
452 | |||
453 | if (text.startsWith(needle) || text.endsWith(needle))
|
||
454 | break;
|
||
455 | else
|
||
456 | offset = index + 1;
|
||
457 | } |
||
458 | |||
459 | return index;
|
||
460 | } |
||
461 | |||
462 | /*
|
||
463 | * Assume the needle will the found at the start/end of the line
|
||
464 | */
|
||
465 | private int lastIndexOf(String content, String needle, int offset) |
||
466 | { |
||
467 | int index;
|
||
468 | |||
469 | while ( (index = content.lastIndexOf(needle, offset)) != -1 ) |
||
470 | { |
||
471 | String text = getLine( content, index ).trim();
|
||
472 | |||
473 | if (text.startsWith(needle) || text.endsWith(needle))
|
||
474 | break;
|
||
475 | else
|
||
476 | offset = index - 1;
|
||
477 | } |
||
478 | |||
479 | return index;
|
||
480 | } |
||
481 | |||
482 | private String getLine(String content, int offset) |
||
483 | { |
||
484 | int line = rootElement.getElementIndex( offset );
|
||
485 | Element lineElement = rootElement.getElement( line );
|
||
486 | int start = lineElement.getStartOffset();
|
||
487 | int end = lineElement.getEndOffset();
|
||
488 | return content.substring(start, end - 1); |
||
489 | } |
||
490 | |||
491 | /*
|
||
492 | * Override for other languages
|
||
493 | */
|
||
494 | protected boolean isDelimiter(String character) |
||
495 | { |
||
496 | String operands = ";:{}()[]+-/%<=>!&|^~*"; |
||
497 | |||
498 | if (Character.isWhitespace( character.charAt(0) ) || |
||
499 | operands.indexOf(character) != -1 )
|
||
500 | return true; |
||
501 | else
|
||
502 | return false; |
||
503 | } |
||
504 | |||
505 | /*
|
||
506 | * Override for other languages
|
||
507 | */
|
||
508 | protected boolean isQuoteDelimiter(String character) |
||
509 | { |
||
510 | String quoteDelimiters = "\"'"; |
||
511 | |||
512 | if (quoteDelimiters.indexOf(character) < 0) |
||
513 | return false; |
||
514 | else
|
||
515 | return true; |
||
516 | } |
||
517 | |||
518 | /*
|
||
519 | * Override for other languages
|
||
520 | */
|
||
521 | protected boolean isKeyword(String token) |
||
522 | { |
||
523 | return keywords.contains( token );
|
||
524 | } |
||
525 | |||
526 | /*
|
||
527 | * Override for other languages
|
||
528 | */
|
||
529 | protected String getStartDelimiter() |
||
530 | { |
||
531 | return "/*"; |
||
532 | } |
||
533 | |||
534 | /*
|
||
535 | * Override for other languages
|
||
536 | */
|
||
537 | protected String getEndDelimiter() |
||
538 | { |
||
539 | return "*/"; |
||
540 | } |
||
541 | |||
542 | /*
|
||
543 | * Override for other languages
|
||
544 | */
|
||
545 | protected String getSingleLineDelimiter() |
||
546 | { |
||
547 | return "//"; |
||
548 | } |
||
549 | |||
550 | /*
|
||
551 | * Override for other languages
|
||
552 | */
|
||
553 | protected String getEscapeString(String quoteDelimiter) |
||
554 | { |
||
555 | return "\\" + quoteDelimiter; |
||
556 | } |
||
557 | |||
558 | /*
|
||
559 | *
|
||
560 | */
|
||
561 | protected String addMatchingBrace(int offset) throws BadLocationException |
||
562 | { |
||
563 | StringBuffer whiteSpace = new StringBuffer(); |
||
564 | int line = rootElement.getElementIndex( offset );
|
||
565 | int i = rootElement.getElement(line).getStartOffset();
|
||
566 | |||
567 | while (true) |
||
568 | { |
||
569 | String temp = doc.getText(i, 1); |
||
570 | |||
571 | if (temp.equals(" ") || temp.equals("\t")) |
||
572 | { |
||
573 | whiteSpace.append(temp); |
||
574 | i++; |
||
575 | } |
||
576 | else
|
||
577 | break;
|
||
578 | } |
||
579 | |||
580 | return "{\n" + whiteSpace.toString() + "\t\n" + whiteSpace.toString() + "}"; |
||
581 | } |
||
582 | |||
583 | } |