package org.eclipse.swt.examples.javaviewer; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2002. | |
* This file is made available under the terms of the Common Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/cpl-v10.html | |
*/ | |
import org.eclipse.swt.*; | |
import org.eclipse.swt.custom.*; | |
import org.eclipse.swt.graphics.*; | |
import org.eclipse.swt.widgets.*; | |
import java.util.*; | |
import java.io.*; | |
class JavaLineStyler implements LineStyleListener { | |
JavaScanner scanner = new JavaScanner(); | |
int[] tokenColors; | |
Color[] colors; | |
Vector blockComments = new Vector(); | |
public static final int EOF= -1; | |
public static final int EOL= 10; | |
public static final int WORD= 0; | |
public static final int WHITE= 1; | |
public static final int KEY= 2; | |
public static final int COMMENT= 3; | |
public static final int STRING= 5; | |
public static final int OTHER= 6; | |
public static final int NUMBER= 7; | |
public static final int MAXIMUM_TOKEN= 8; | |
public JavaLineStyler() { | |
initializeColors(); | |
scanner = new JavaScanner(); | |
} | |
Color getColor(int type) { | |
if (type < 0 || type >= tokenColors.length) { | |
return null; | |
} | |
return colors[tokenColors[type]]; | |
} | |
boolean inBlockComment(int start, int end) { | |
for (int i=0; i<blockComments.size(); i++) { | |
int[] offsets = (int[])blockComments.elementAt(i); | |
// start of comment in the line | |
if ((offsets[0] >= start) && (offsets[0] <= end)) return true; | |
// end of comment in the line | |
if ((offsets[1] >= start) && (offsets[1] <= end)) return true; | |
if ((offsets[0] <= start) && (offsets[1] >= end)) return true; | |
} | |
return false; | |
} | |
void initializeColors() { | |
Display display = Display.getDefault(); | |
colors= new Color[] { | |
new Color(display, new RGB(0, 0, 0)), // black | |
new Color(display, new RGB(255, 0, 0)), // red | |
new Color(display, new RGB(0, 255, 0)), // green | |
new Color(display, new RGB(0, 0, 255)) // blue | |
}; | |
tokenColors= new int[MAXIMUM_TOKEN]; | |
tokenColors[WORD]= 0; | |
tokenColors[WHITE]= 0; | |
tokenColors[KEY]= 3; | |
tokenColors[COMMENT]= 1; | |
tokenColors[STRING]= 2; | |
tokenColors[OTHER]= 0; | |
tokenColors[NUMBER]= 0; | |
} | |
void disposeColors() { | |
for (int i=0;i<colors.length;i++) { | |
colors[i].dispose(); | |
} | |
} | |
/** | |
* Event.detail line start offset (input) | |
* Event.text line text (input) | |
* LineStyleEvent.styles Enumeration of StyleRanges, need to be in order. (output) | |
* LineStyleEvent.background line background color (output) | |
*/ | |
public void lineGetStyle(LineStyleEvent event) { | |
Vector styles = new Vector(); | |
int token; | |
StyleRange lastStyle; | |
// If the line is part of a block comment, create one style for the entire line. | |
if (inBlockComment(event.lineOffset, event.lineOffset + event.lineText.length())) { | |
styles.addElement(new StyleRange(event.lineOffset, event.lineText.length(), getColor(COMMENT), null)); | |
event.styles = new StyleRange[styles.size()]; | |
styles.copyInto(event.styles); | |
return; | |
} | |
Color defaultFgColor = ((Control)event.widget).getForeground(); | |
scanner.setRange(event.lineText); | |
token = scanner.nextToken(); | |
while (token != EOF) { | |
if (token == OTHER) { | |
// do nothing for non-colored tokens | |
} else if (token != WHITE) { | |
Color color = getColor(token); | |
// Only create a style if the token color is different than the | |
// widget's default foreground color and the token's style is not | |
// bold. Keywords are bolded. | |
if ((!color.equals(defaultFgColor)) || (token == KEY)) { | |
StyleRange style = new StyleRange(scanner.getStartOffset() + event.lineOffset, scanner.getLength(), color, null); | |
if (token == KEY) { | |
style.fontStyle = SWT.BOLD; | |
} | |
if (styles.isEmpty()) { | |
styles.addElement(style); | |
} else { | |
// Merge similar styles. Doing so will improve performance. | |
lastStyle = (StyleRange)styles.lastElement(); | |
if (lastStyle.similarTo(style) && (lastStyle.start + lastStyle.length == style.start)) { | |
lastStyle.length += style.length; | |
} else { | |
styles.addElement(style); | |
} | |
} | |
} | |
} else if ((!styles.isEmpty()) && ((lastStyle=(StyleRange)styles.lastElement()).fontStyle == SWT.BOLD)) { | |
int start = scanner.getStartOffset() + event.lineOffset; | |
lastStyle = (StyleRange)styles.lastElement(); | |
// A font style of SWT.BOLD implies that the last style | |
// represents a java keyword. | |
if (lastStyle.start + lastStyle.length == start) { | |
// Have the white space take on the style before it to | |
// minimize the number of style ranges created and the | |
// number of font style changes during rendering. | |
lastStyle.length += scanner.getLength(); | |
} | |
} | |
token= scanner.nextToken(); | |
} | |
event.styles = new StyleRange[styles.size()]; | |
styles.copyInto(event.styles); | |
} | |
public void parseBlockComments(String text) { | |
blockComments = new Vector(); | |
StringReader buffer = new StringReader(text); | |
int ch; | |
boolean blkComment = false; | |
int cnt = 0; | |
int[] offsets = new int[2]; | |
boolean done = false; | |
try { | |
while (!done) { | |
switch (ch = buffer.read()) { | |
case -1 : { | |
if (blkComment) { | |
offsets[1] = cnt; | |
blockComments.addElement(offsets); | |
} | |
done = true; | |
break; | |
} | |
case '/' : { | |
ch = buffer.read(); | |
if ((ch == '*') && (!blkComment)) { | |
offsets = new int[2]; | |
offsets[0] = cnt; | |
blkComment = true; | |
cnt++; | |
} else { | |
cnt++; | |
} | |
cnt++; | |
break; | |
} | |
case '*' : { | |
if (blkComment) { | |
ch = buffer.read(); | |
cnt++; | |
if (ch == '/') { | |
blkComment = false; | |
offsets[1] = cnt; | |
blockComments.addElement(offsets); | |
} | |
} | |
cnt++; | |
break; | |
} | |
default : { | |
cnt++; | |
break; | |
} | |
} | |
} | |
} catch(IOException e) { | |
// ignore errors | |
} | |
} | |
/** | |
* A simple fuzzy scanner for Java | |
*/ | |
public class JavaScanner { | |
protected Hashtable fgKeys= null; | |
protected StringBuffer fBuffer= new StringBuffer(); | |
protected String fDoc; | |
protected int fPos; | |
protected int fEnd; | |
protected int fStartToken; | |
protected boolean fEofSeen= false; | |
private String[] fgKeywords= { | |
"abstract", | |
"boolean", "break", "byte", | |
"case", "catch", "char", "class", "continue", | |
"default", "do", "double", | |
"else", "extends", | |
"false", "final", "finally", "float", "for", | |
"if", "implements", "import", "instanceof", "int", "interface", | |
"long", | |
"native", "new", "null", | |
"package", "private", "protected", "public", | |
"return", | |
"short", "static", "super", "switch", "synchronized", | |
"this", "throw", "throws", "transient", "true", "try", | |
"void", "volatile", | |
"while" | |
}; | |
public JavaScanner() { | |
initialize(); | |
} | |
/** | |
* Returns the ending location of the current token in the document. | |
*/ | |
public final int getLength() { | |
return fPos - fStartToken; | |
} | |
/** | |
* Initialize the lookup table. | |
*/ | |
void initialize() { | |
fgKeys= new Hashtable(); | |
Integer k= new Integer(KEY); | |
for (int i= 0; i < fgKeywords.length; i++) | |
fgKeys.put(fgKeywords[i], k); | |
} | |
/** | |
* Returns the starting location of the current token in the document. | |
*/ | |
public final int getStartOffset() { | |
return fStartToken; | |
} | |
/** | |
* Returns the next lexical token in the document. | |
*/ | |
public int nextToken() { | |
int c; | |
fStartToken= fPos; | |
while (true) { | |
switch (c= read()) { | |
case EOF: | |
return EOF; | |
case '/': // comment | |
c= read(); | |
if (c == '/') { | |
while (true) { | |
c= read(); | |
if ((c == EOF) || (c == EOL)) { | |
unread(c); | |
return COMMENT; | |
} | |
} | |
} else { | |
unread(c); | |
} | |
return OTHER; | |
case '\'': // char const | |
character: for(;;) { | |
c= read(); | |
switch (c) { | |
case '\'': | |
return STRING; | |
case EOF: | |
unread(c); | |
return STRING; | |
case '\\': | |
c= read(); | |
break; | |
} | |
} | |
case '"': // string | |
string: for (;;) { | |
c= read(); | |
switch (c) { | |
case '"': | |
return STRING; | |
case EOF: | |
unread(c); | |
return STRING; | |
case '\\': | |
c= read(); | |
break; | |
} | |
} | |
case '0': case '1': case '2': case '3': case '4': | |
case '5': case '6': case '7': case '8': case '9': | |
do { | |
c= read(); | |
} while(Character.isDigit((char)c)); | |
unread(c); | |
return NUMBER; | |
default: | |
if (Character.isWhitespace((char)c)) { | |
do { | |
c= read(); | |
} while(Character.isWhitespace((char)c)); | |
unread(c); | |
return WHITE; | |
} | |
if (Character.isJavaIdentifierStart((char)c)) { | |
fBuffer.setLength(0); | |
do { | |
fBuffer.append((char)c); | |
c= read(); | |
} while(Character.isJavaIdentifierPart((char)c)); | |
unread(c); | |
Integer i= (Integer) fgKeys.get(fBuffer.toString()); | |
if (i != null) | |
return i.intValue(); | |
return WORD; | |
} | |
return OTHER; | |
} | |
} | |
} | |
/** | |
* Returns next character. | |
*/ | |
protected int read() { | |
if (fPos <= fEnd) { | |
return fDoc.charAt(fPos++); | |
} | |
return EOF; | |
} | |
public void setRange(String text) { | |
fDoc= text; | |
fPos= 0; | |
fEnd= fDoc.length() -1; | |
} | |
protected void unread(int c) { | |
if (c != EOF) | |
fPos--; | |
} | |
} | |
} |