3、编辑器的语法颜色功能
(1)基本概念
l 首先是定义各种Partition(文档中不重叠的文本),用以区分语法明显不同的部分
l 文档中的每个字符必须属于定义得某个Partition或缺省Partition(IDocument.DEFAULT_CONTENT_TYPE)
l 注释应该属于独立的一种Partition
l 由于log4j.properties中除了注释,每行多是name=value形式,所以分成3种Partition
Ø 注释(以#开始)
Ø Value值(包括=)
Ø 缺省Partition(包括name)
l 需要提供一个Partition扫描器供TextEditor调用,来决定哪些文本属于哪个Partition
l 还需要提供Token扫描器供TextEditor调用
l Token是用来设置颜色的最小文本单元
l 每个Partition需要有唯一的Token扫描器
(2)Partition扫描器
l Partition扫描器通过提供的解析规则(定义Partition)进行解析,将文本区分成不同的Partition
l 本例使用JFace提供的基于规则的扫描器,所以需要在plugin.xml清单编辑器的Dependencies页中添加org.eclipse.jface.text插件
package org.xqtu.log4j.editor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.RuleBasedPartitionScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.Token;
public class PropertiesPartitionScanner extends RuleBasedPartitionScanner {
public final static String LOG4J_COMMENT = "__log4j_comment";
public final static String LOG4J_VALUE = "__log4j_value";
public PropertiesPartitionScanner() {
super();
Token commentPartition = new Token(LOG4J_COMMENT);
Token valuePartition = new Token(LOG4J_VALUE);
SingleLineRule commentRule = new SingleLineRule("#", null,
commentPartition, (char) 0, true);
commentRule.setColumnConstraint(0);
SingleLineRule valueRule = new SingleLineRule("=", null,
valuePartition, (char) 0, true);
setPredicateRules(new IPredicateRule[] { commentRule, valueRule });
}
public static String[] getLegalContentTypes() {
return new String[] { IDocument.DEFAULT_CONTENT_TYPE,
PropertiesPartitionScanner.LOG4J_COMMENT,
PropertiesPartitionScanner.LOG4J_VALUE };
}
}
l 这里扩展了RuleBasedPartitionScanner,通过读取文本,应用提供的Partition规则
l 在开始部分定义了Partition类型常量
l 在构造方法中定义了每种Partition类型(这里部包括缺省Partition类型)的规则
l 首先创建每种Partition自己唯一的Token对象
l 使用SingleLineRule定义Partition规则,SingleLineRule包括四个参数:
Ø 开始序列匹配模式
Ø 结束序列匹配模式
Ø Escape字符
Ø 设置为true表示当到达文件结束时终止
l setColumnConstraint()方法设置Partition规则的列限制:只返回匹配从指定列数开始的符合规则的Token
l setPredicateRules()方法告诉Partition扫描器定义的Partition规则
l 最后的getLegalContentTypes()方法返回该Partition扫描器支持的Partition类型
(3)Token管理器
l 颜色(SWT Color类的特定实例)是有限的系统资源,需要跟踪它们,对它们进行管理:不多分配,在用完之后释放它们
l Token管理器用于对Token和颜色都进行跟踪和管理,主要目的是支持允许用户在Preferences中设置颜色的功能
package org.xqtu.log4j.editor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
public class TokenManager {
private Map colorTable = new HashMap(10);
private Map tokenTable = new HashMap(10);
private final IPreferenceStore preferenceStore;
public TokenManager(IPreferenceStore preferenceStore) {
this.preferenceStore = preferenceStore;
}
public IToken getToken(String prefKey) {
Token token = (Token) tokenTable.get(prefKey);
if (token == null) {
String colorName = preferenceStore.getString(prefKey);
RGB rgb = StringConverter.asRGB(colorName);
token = new Token(new TextAttribute(getColor(rgb)));
tokenTable.put(prefKey, token);
}
return token;
}
public void dispose() {
Iterator e = colorTable.values().iterator();
while (e.hasNext()) {
((Color) e.next()).dispose();
}
}
private Color getColor(RGB rgb) {
Color color = (Color) colorTable.get(rgb);
if (color == null) {
color = new Color(Display.getCurrent(), rgb);
colorTable.put(rgb, color);
}
return color;
}
public boolean affectsTextPresentation(PropertyChangeEvent event) {
Token token = (Token) tokenTable.get(event.getProperty());
return (token != null);
}
public void handlePreferenceStoreChanged(PropertyChangeEvent event) {
String prefKey = event.getProperty();
Token token = (Token) tokenTable.get(prefKey);
if (token != null) {
String colorName = preferenceStore.getString(prefKey);
RGB rgb = StringConverter.asRGB(colorName);
token.setData(new TextAttribute(getColor(rgb)));
}
}
}
l 开始部分定义了用于管理的Token和颜色的HashMap列表
l getToken()方法获得指定的Token:先在Token列表中查找,如果没有,就根据Preferences中获得的颜色,创建一个新的Token,并保存到Token列表中
l 在创建Token时调用getColor()方法,获得指定的颜色,其方法类似:先在颜色列表中查找,如果没有就分配一种新的颜色,并保存到颜色列表中
l TextAttribute对象用于描述文本的属性,本例只指定前景颜色,也可以指定背景颜色或式样,如粗体、斜体等
l dispose()方法用来释放所有的颜色资源
l 通常,编辑器通过注册在变化发生时调用的方法来监听Preferences的变化
Ø affectsTextPresentation()方法判断Preferences中属性的变化是否需要应用到编辑器中的文本,通常的方法只有属性是Token列表中某个Token的名字才需要应用
Ø handlePreferenceStoreChanged()方法在Preferences存储变化时应用属性变化到Toke;,同样,只有变化的属性名字是Token列表中某个Token,才会根据Preferences中指定的值替换Token的颜色
(4)注释Token扫描器
l 每个Partition需要有一个对应的Token扫描器
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.xqtu.log4j.Log4jPlugin;
import org.xqtu.log4j.editor.TokenManager;
public class CommentScanner extends RuleBasedScanner {
public CommentScanner(TokenManager tokenManager) {
IToken commentToken = tokenManager
.getToken(Log4jPlugin.PREF_COMMENT_COLOR);
setDefaultReturnToken(commentToken);
}
}
l 所有的Token扫描器都要扩展RuleBasedScanner
l 使用TokenManager的getToken()方法获得指定的Token
l Token被指派到Partition中文本的每个部分
l 由于注释Partition中的任何东西都是注释,所以不需要提供任何规则,只要指定返回的缺省Token
(5)缺省Token扫描器
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.xqtu.log4j.Log4jPlugin;
import org.xqtu.log4j.editor.TokenManager;
public class DefaultScanner extends RuleBasedScanner {
public DefaultScanner(TokenManager tokenManager) {
IToken propertyToken = tokenManager
.getToken(Log4jPlugin.PREF_PROPERTY_COLOR);
setDefaultReturnToken(propertyToken);
setRules(new IRule[] { new WhitespaceRule(new WhitespaceDetector()) });
}
}
l 缺省Partition中除了空白字符以外的所有内容都被认为是属性名字,所以需要调用setRules()方法设置空白字符的规则
l 探测器WhitespaceDetector实现IWhitespaceDetector接口,决定当前内容中的字符是否为空白字符
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.IWhitespaceDetector;
public class WhitespaceDetector implements IWhitespaceDetector {
public boolean isWhitespace(char c) {
return Character.isWhitespace(c);
}
}
(6)Value值Token扫描器
l Value值Token扫描器相对要复杂一些,因为需要处理关键字和象%m、%d{hh:mm:ss a}之类的格式
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.SingleLineRule;
import org.eclipse.jface.text.rules.WhitespaceRule;
import org.eclipse.jface.text.rules.WordRule;
import org.xqtu.log4j.Log4jPlugin;
import org.xqtu.log4j.editor.TokenManager;
public class ValueScanner extends RuleBasedScanner {
String[] keywords = { "ALL", "DEBUG", "ERROR", "FATAL", "INFO", "OFF",
"WARN", "INHERITED", "INHERIT", "NULL", "true", "false", };
public ValueScanner(TokenManager tokenManager) {
IToken defaultToken = tokenManager
.getToken(Log4jPlugin.PREF_DEFAULT_COLOR);
IToken formatToken = tokenManager
.getToken(Log4jPlugin.PREF_FORMAT_COLOR);
IToken keywordToken = tokenManager
.getToken(Log4jPlugin.PREF_KEYWORD_COLOR);
IRule braceRule = new SingleLineRule("{", "}", formatToken, (char) 0,
true);
WordRule keywordRule = new WordRule(new WordDetector());
for (int i = 0; i < keywords.length; i++) {
keywordRule.addWord(keywords[i], keywordToken);
}
IRule formatRule = new FormatRule(formatToken);
IRule whitespaceRule = new WhitespaceRule(new WhitespaceDetector());
setDefaultReturnToken(defaultToken);
setRules(new IRule[] { braceRule, formatRule, keywordRule,
whitespaceRule, });
}
}
l Value值Token扫描器处理包括关键字、格式化和缺省三种Token
l 在开始部分定义了关键字列表,使用WordRule来探测所有关键字Token,并应用了WordDetector探测器
l WordDetector探测器实现IWordDetector接口,决定以字母开始,包含字母或数字的内容为Word
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.IWordDetector;
public class WordDetector implements IWordDetector {
public boolean isWordStart(char c) {
return Character.isLetter(c);
}
public boolean isWordPart(char c) {
return Character.isLetterOrDigit(c);
}
}
l {}内的内容是作为格式化Token,使用SingleLineRule定义Token规则
l 第三个规则是自定义的格式化Token规则,在后面讲述
l 同样,需要定义空白字符规则来匹配空白字符
l 其它文本作为缺省Token返回
l 最后设置应用的Token规则
(7)自定义Token规则
l 由于Eclipse平台没有提供Log4j格式的规则,需要自定义规则
package org.xqtu.log4j.editor.scanners;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
public class FormatRule implements IRule {
private final IToken token;
public FormatRule(IToken token) {
this.token = token;
}
public IToken evaluate(ICharacterScanner scanner) {
int c = scanner.read();
if (c == '%') {
do {
c = scanner.read();
} while (c != ICharacterScanner.EOF
&& (Character.isLetterOrDigit((char) c) || c == '-' || c == '.'));
scanner.unread();
return token;
}
scanner.unread();
return Token.UNDEFINED;
}
}
l 自定义规则实现IRule接口,使用evaluate()方法逐个字符进行规则匹配
l 在构造方法中,先保存要匹配的Token
l 在evaluate()方法,使用IcharacterScanner逐个读取字符,进行规则匹配
l 如果以%开始就是格式化Token,读取包含字母、数字、“-”和“.”内容,作为Token返回
l 否则返回Token.UNDEFINED,表示Token没有匹配,以便Token扫描器进行下一条规则的匹配
l 其中,调用IcharacterScanner的unread()方法对没有匹配的内容进行回退读取操作