前文回顾:
1 插件学习篇
4 SWT编程须知
7 SWT布局详解
这篇讲解依然是一个重头的技巧,就是【代码的着色】。大家在使用各种编辑器的时候都会发现,有些关键词和一些注释之类的都会以不同的颜色进行显示,那么它是怎么做到呢?先看一下示例的运行效果!
凭空思考下:
【IO书写】首先这些输入的东西可能是以一些字符串的形式进行书写。
【分词分块】然后必然经过分词,把他们分成一块一块的。
【着色】这样之后扫描每个分块进行分类,不同的分类显示不同的颜色!
大体上是这样一个过程,那么Eclipse是怎样做到的呢?
SourceViewer!—— 代码编写的视图窗口
这里主要是用了一个特殊的view模型:SourceViewer,它是一种特殊的文本视图,让我们可以配置自己的代码显示规则!看一下官方的API
SourceViewer(Composite parent, IVerticalRuler ruler, int styles) //Constructs a new source viewer.
这里第一个跟第三个参数都跟普通的Control控件差不多。
中间的参数用于设置代码的一个垂直规则(其实就是编辑器左边和右边有提示效果的垂直边栏),想了解的话可以参考它的官方API。
使用方法如下:
VerticalRuler(int width) //Constructs a vertical ruler with the given width.
如果不想有其他的配置,可以设置它的宽度为0。
接下来需要设置它的配置对象,用于对着色,分词等信息进行配置。
public void configure(SourceViewerConfiguration configuration); /*Description copied from interface: ISourceViewer Configures the source viewer using the given configuration. Prior to 3.0 this method can only be called once. Since 3.0 this method can be called again after a call to ISourceViewerExtension2.unconfigure(). Specified by: configure in interface ISourceViewer Parameters: configuration - the source viewer configuration to be used */
Document!—— 代码文档,提供切分分块等操作.
这个文档对象需要我们提供一个分块对象,对输入的文件流进行分块。这里主要使用一个接口IDocumentPartitioner,常用的实现类是FastPartitioner。
public FastPartitioner(IPartitionTokenScanner scanner, String[] legalContentTypes)
第一个参数是一个扫描对象,第二个参数用于分块的规则。
实现过程
抽象的设想大概如上面所述,但是做起来还是很困难,虽然知道了用什么类或者方法,但是如何组织才是最大的困难!这里借助一个开源源码,书写SQL语句的编辑器,来讲解一下代码着色的主要过程!
我们要解决的问题大致如下:
如何进行分块?
如何进行着色?
如何附加到编辑器上?
一下是代码编写的思维导图
1 创建SQL对应的SourceViewer的配置类SQLConfiguration
所有自定仪的配置类都要继承Eclipse的SourceViewerConfiguration类。
需要重写的类大致有如下几个:
1.1 getConfiguredContentTypes 这个方法用于返回一个数组,这个数组规定了需要进行处理的类型,当遇到这种类型匹配的分块时,就会进行响应的处理。这里的业务场景是这样:我们编写SQL语句,相应进行处理着色的应该是关键字、字符串、注释。其他的输入对象我们就不需要进行处理了。下面便是返回的三种类型标识。
public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) { return new String[] { IDocument.DEFAULT_CONTENT_TYPE, SQLPartitionScanner.SQL_COMMENT, SQLPartitionScanner.SQL_STRING }; }
1.2 接下来是对应的三种扫描器
private RuleBasedScanner getCommentScanner(){//注释扫描器 RuleBasedScanner scanner = new RuleBasedScanner(); EditorColorProvider colorProvider = Activator.getDefault().getEditorColorProvider(); scanner.setDefaultReturnToken( colorProvider.getToken(Activator.PREF_COLOR_COMMENT)); return scanner; } private RuleBasedScanner getStringScanner(){//字符串扫描器 RuleBasedScanner scanner = new RuleBasedScanner(); EditorColorProvider colorProvider = Activator.getDefault().getEditorColorProvider(); scanner.setDefaultReturnToken( colorProvider.getToken(Activator.PREF_COLOR_STRING)); return scanner; } private RuleBasedScanner getDefaultScanner(){//默认关键字扫描器 return new SQLKeywordPartitionScanner(); }
这里根据自定义编写我们自己的关键字扫描器
public static class SQLKeywordPartitionScanner extends RuleBasedScanner { private static String[] KEYWORDS = {"select", "update", "insert", "into", "delete", "from", "where", "values", "set", "order", "by", "left", "outer", "join", "having", "group", "create", "alter", "drop", "table"}; public SQLKeywordPartitionScanner(){ IToken keyword = Activator.getDefault().getEditorColorProvider().getToken( Activator.PREF_COLOR_KEYWORD); IToken other = Activator.getDefault().getEditorColorProvider().getToken( Activator.PREF_COLOR_DEFAULT); WordRule wordRule = new WordRule(new IWordDetector() { public boolean isWordPart(char c) { return Character.isJavaIdentifierPart(c); } public boolean isWordStart(char c) { return Character.isJavaIdentifierStart(c); } }, other); for (int i = 0; i < KEYWORDS.length; i++) { wordRule.addWord(KEYWORDS[i], keyword); wordRule.addWord(KEYWORDS[i].toUpperCase(), keyword); } IRule[] rules = new IRule[2]; rules[0] = wordRule; rules[1] = new WhitespaceRule(new IWhitespaceDetector() { public boolean isWhitespace(char character) { return Character.isWhitespace(character); } }); setRules(rules); } }
1.3 getPresentationReconciler 是源码视图使用的表现协调器,翻译的比较蹩脚,其实就是每一种类型的分块如何展现!
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { PresentationReconciler reconciler = new PresentationReconciler(); DefaultDamagerRepairer commentDR = new DefaultDamagerRepairer(getCommentScanner()); reconciler.setDamager(commentDR, SQLPartitionScanner.SQL_COMMENT); reconciler.setRepairer(commentDR, SQLPartitionScanner.SQL_COMMENT); DefaultDamagerRepairer stringDR = new DefaultDamagerRepairer(getStringScanner()); reconciler.setDamager(stringDR, SQLPartitionScanner.SQL_STRING); reconciler.setRepairer(stringDR, SQLPartitionScanner.SQL_STRING); DefaultDamagerRepairer keywordDR = new DefaultDamagerRepairer(getDefaultScanner()); reconciler.setDamager(keywordDR, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(keywordDR, IDocument.DEFAULT_CONTENT_TYPE); return reconciler; }
1.4 着色方法EditorColorProvider,提供对不同的类型显示不同的颜色
这个类提供了一个map,里面包含了对应的类型及其对应的RGB颜色的Color对象,通过查询这个map,可以获取相应的颜色,进行着色。
public IToken getToken(String prefKey){ Token token = (Token) tokenTable.get(prefKey); if (token == null){ String colorName = store.getString(prefKey); RGB rgb = StringConverter.asRGB(colorName); token = new Token(new TextAttribute(getColor(rgb))); tokenTable.put(prefKey, token); } return token; }
关于这里面的store,是前一篇讲解过的插件初始化设定的参数。用法可以参考前一篇帖子,这里贴出initializer类中的实现:
store.setDefault(Activator.PREF_COLOR_DEFAULT, StringConverter.asString(new RGB(0,0,0))); store.setDefault(Activator.PREF_COLOR_COMMENT, StringConverter.asString(new RGB(0,128,0))); store.setDefault(Activator.PREF_COLOR_STRING, StringConverter.asString(new RGB(0,0,255))); store.setDefault(Activator.PREF_COLOR_KEYWORD, StringConverter.asString(new RGB(128,0,128)));
2 创建分块扫描器
这里是针对注释以及字符串进行分块。
需要在够咱函数中创建一个分块规则:IPredicateRule 数组。具体规则的参数可以参考下面的参数。
public class SQLPartitionScanner extends RuleBasedPartitionScanner { public static final String SQL_COMMENT = "__sql_comment"; public static final String SQL_STRING = "__sql_string"; public SQLPartitionScanner() { IPredicateRule[] rules = new IPredicateRule[4]; IToken comment = new Token(SQL_COMMENT); rules[0] = new MultiLineRule("/*", "*/", comment, (char) 0, true);/*startSequence the pattern‘s start sequenceendSequence the pattern‘s end sequencetoken the token to be returned on successescapeCharacter the escape characterbreaksOnEOF indicates whether the end of the file terminates this rule successfully*/ rules[1] = new EndOfLineRule("--", comment); /*startSequence the pattern‘s start sequencetoken the token to be returned on success*/ IToken string = new Token(SQL_STRING); rules[2] = new SingleLineRule("\"", "\"", string, ‘\\‘); rules[3] = new SingleLineRule("\‘", "\‘", string, ‘\\‘); /*startSequence the pattern‘s start sequenceendSequence the pattern‘s end sequencetoken the token to be returned on successescapeCharacter the escape character*/ setPredicateRules(rules); } }
3 这样基本上需要的准备工作就做完了,接下来要进行使用了。
首先在合适的位置触发编辑对话框的弹出!
DDLEditDialog dialog = new DDLEditDialog(viewer.getControl().getShell()); dialog.open();
设定SQL配置类,以及分块扫描器
SourceViewer sqlEditor = new SourceViewer(parent, new VerticalRuler(0), SWT.V_SCROLL | SWT.H_SCROLL); //设置配置项 sqlEditor.configure(new SQLConfiguration()); sqlEditor.getTextWidget().setFont(JFaceResources.getTextFont()); Document document = new Document(); //设置分块扫描器 IDocumentPartitioner partitioner = new FastPartitioner( new SQLPartitionScanner(), new String[] { SQLPartitionScanner.SQL_COMMENT, SQLPartitionScanner.SQL_STRING }); partitioner.connect(document); document.setDocumentPartitioner(partitioner); sqlEditor.setDocument(document); sqlEditor.getControl().setLayoutData(new GridData(GridData.FILL_BOTH)); StyledText text = sqlEditor.getTextWidget();
全部代码
源码ZIP包下载:源码插件下载
使用方法:
1 打开视图SampleView
2 双击下面的任意一行!
3 弹出对话框进行编辑!
1 Activator插件启动类
1 package testpreference; 2 3 import org.eclipse.jface.preference.IPreferenceStore; 4 import org.eclipse.jface.resource.ImageDescriptor; 5 import org.eclipse.ui.plugin.AbstractUIPlugin; 6 import org.osgi.framework.BundleContext; 7 8 import testpreference.dialog.EditorColorProvider; 9 10 /** 11 * The activator class controls the plug-in life cycle 12 */ 13 public class Activator extends AbstractUIPlugin { 14 15 // The plug-in ID 16 public static final String PLUGIN_ID = "TestPreference"; 17 18 // for SQL editor 19 public static final String PREF_COLOR_DEFAULT = "colorDefault"; 20 public static final String PREF_COLOR_COMMENT = "colorComment"; 21 public static final String PREF_COLOR_STRING = "colorString"; 22 public static final String PREF_COLOR_KEYWORD = "colorKeyword"; 23 24 private IPreferenceStore store; 25 private EditorColorProvider colorProvider; 26 // The shared instance 27 private static Activator plugin; 28 29 public Activator() { 30 } 31 32 public void start(BundleContext context) throws Exception { 33 super.start(context); 34 plugin = this; 35 this.colorProvider = new EditorColorProvider(getPreferenceStore()); 36 } 37 38 public EditorColorProvider getEditorColorProvider(){ 39 return this.colorProvider; 40 } 41 42 ... 43 44 public static Activator getDefault() { 45 return plugin; 46 } 47 }
2 Action触发对话框
1 doubleClickAction = new Action() { 2 public void run() { 3 DDLEditDialog dialog = new DDLEditDialog(viewer.getControl().getShell()); 4 dialog.open(); 5 } 6 };
3 对话框实现类
1 package testpreference.dialog; 2 3 import org.eclipse.jface.dialogs.Dialog; 4 import org.eclipse.jface.dialogs.IDialogConstants; 5 import org.eclipse.jface.resource.JFaceResources; 6 import org.eclipse.jface.text.Document; 7 import org.eclipse.jface.text.IDocument; 8 import org.eclipse.jface.text.IDocumentPartitioner; 9 import org.eclipse.jface.text.presentation.IPresentationReconciler; 10 import org.eclipse.jface.text.presentation.PresentationReconciler; 11 import org.eclipse.jface.text.rules.DefaultDamagerRepairer; 12 import org.eclipse.jface.text.rules.EndOfLineRule; 13 import org.eclipse.jface.text.rules.FastPartitioner; 14 import org.eclipse.jface.text.rules.IPredicateRule; 15 import org.eclipse.jface.text.rules.IRule; 16 import org.eclipse.jface.text.rules.IToken; 17 import org.eclipse.jface.text.rules.IWhitespaceDetector; 18 import org.eclipse.jface.text.rules.IWordDetector; 19 import org.eclipse.jface.text.rules.MultiLineRule; 20 import org.eclipse.jface.text.rules.RuleBasedPartitionScanner; 21 import org.eclipse.jface.text.rules.RuleBasedScanner; 22 import org.eclipse.jface.text.rules.SingleLineRule; 23 import org.eclipse.jface.text.rules.Token; 24 import org.eclipse.jface.text.rules.WhitespaceRule; 25 import org.eclipse.jface.text.rules.WordRule; 26 import org.eclipse.jface.text.source.ISourceViewer; 27 import org.eclipse.jface.text.source.SourceViewer; 28 import org.eclipse.jface.text.source.SourceViewerConfiguration; 29 import org.eclipse.jface.text.source.VerticalRuler; 30 import org.eclipse.swt.SWT; 31 import org.eclipse.swt.custom.StyledText; 32 import org.eclipse.swt.graphics.Point; 33 import org.eclipse.swt.layout.GridData; 34 import org.eclipse.swt.widgets.Composite; 35 import org.eclipse.swt.widgets.Control; 36 import org.eclipse.swt.widgets.Shell; 37 38 import testpreference.Activator; 39 40 public class DDLEditDialog extends Dialog{ 41 42 public DDLEditDialog(Shell parent) { 43 super(parent); 44 setShellStyle(getShellStyle()|SWT.RESIZE); 45 } 46 47 protected Point getInitialSize() { 48 return new Point(600, 450); 49 } 50 51 protected Control createDialogArea(Composite parent) { 52 getShell().setText("DDL"); 53 54 SourceViewer sqlEditor = new SourceViewer(parent, new VerticalRuler(0), SWT.V_SCROLL | SWT.H_SCROLL); 55 //设置配置项 56 sqlEditor.configure(new SQLConfiguration()); 57 58 sqlEditor.getTextWidget().setFont(JFaceResources.getTextFont()); 59 60 Document document = new Document(); 61 IDocumentPartitioner partitioner = new FastPartitioner( 62 new SQLPartitionScanner(), 63 new String[] { 64 SQLPartitionScanner.SQL_COMMENT, 65 SQLPartitionScanner.SQL_STRING 66 }); 67 partitioner.connect(document); 68 document.setDocumentPartitioner(partitioner); 69 sqlEditor.setDocument(document); 70 sqlEditor.getControl().setLayoutData(new GridData(GridData.FILL_BOTH)); 71 72 StyledText text = sqlEditor.getTextWidget(); 73 74 return text; 75 } 76 77 protected void createButtonsForButtonBar(Composite parent) { 78 createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); 79 } 80 81 public class SQLConfiguration extends SourceViewerConfiguration { 82 83 public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) { 84 return new String[] { IDocument.DEFAULT_CONTENT_TYPE, 85 SQLPartitionScanner.SQL_COMMENT, 86 SQLPartitionScanner.SQL_STRING }; 87 } 88 89 private RuleBasedScanner getCommentScanner(){ 90 RuleBasedScanner scanner = new RuleBasedScanner(); 91 EditorColorProvider colorProvider = Activator.getDefault().getEditorColorProvider(); 92 scanner.setDefaultReturnToken( 93 colorProvider.getToken(Activator.PREF_COLOR_COMMENT)); 94 return scanner; 95 } 96 97 private RuleBasedScanner getStringScanner(){ 98 RuleBasedScanner scanner = new RuleBasedScanner(); 99 EditorColorProvider colorProvider = Activator.getDefault().getEditorColorProvider(); 100 scanner.setDefaultReturnToken( 101 colorProvider.getToken(Activator.PREF_COLOR_STRING)); 102 return scanner; 103 } 104 105 private RuleBasedScanner getDefaultScanner(){ 106 return new SQLKeywordPartitionScanner(); 107 } 108 109 110 public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) { 111 112 PresentationReconciler reconciler = new PresentationReconciler(); 113 114 DefaultDamagerRepairer commentDR = new DefaultDamagerRepairer(getCommentScanner()); 115 reconciler.setDamager(commentDR, SQLPartitionScanner.SQL_COMMENT); 116 reconciler.setRepairer(commentDR, SQLPartitionScanner.SQL_COMMENT); 117 118 DefaultDamagerRepairer stringDR = new DefaultDamagerRepairer(getStringScanner()); 119 reconciler.setDamager(stringDR, SQLPartitionScanner.SQL_STRING); 120 reconciler.setRepairer(stringDR, SQLPartitionScanner.SQL_STRING); 121 122 DefaultDamagerRepairer keywordDR = new DefaultDamagerRepairer(getDefaultScanner()); 123 reconciler.setDamager(keywordDR, IDocument.DEFAULT_CONTENT_TYPE); 124 reconciler.setRepairer(keywordDR, IDocument.DEFAULT_CONTENT_TYPE); 125 126 return reconciler; 127 } 128 } 129 130 131 132 133 /** 134 * 关键词分词 135 * @author Administrator 136 * 137 */ 138 public static class SQLKeywordPartitionScanner extends RuleBasedScanner { 139 140 private static String[] KEYWORDS = {"select", "update", "insert", 141 "into", "delete", "from", "where", "values", "set", "order", "by", 142 "left", "outer", "join", "having", "group", "create", "alter", "drop", "table"}; 143 144 public SQLKeywordPartitionScanner(){ 145 IToken keyword = Activator.getDefault().getEditorColorProvider().getToken( 146 Activator.PREF_COLOR_KEYWORD); 147 IToken other = Activator.getDefault().getEditorColorProvider().getToken( 148 Activator.PREF_COLOR_DEFAULT); 149 150 WordRule wordRule = new WordRule(new IWordDetector() { 151 public boolean isWordPart(char c) { 152 return Character.isJavaIdentifierPart(c); 153 } 154 155 public boolean isWordStart(char c) { 156 return Character.isJavaIdentifierStart(c); 157 } 158 }, other); 159 for (int i = 0; i < KEYWORDS.length; i++) { 160 wordRule.addWord(KEYWORDS[i], keyword); 161 wordRule.addWord(KEYWORDS[i].toUpperCase(), keyword); 162 } 163 IRule[] rules = new IRule[2]; 164 rules[0] = wordRule; 165 rules[1] = new WhitespaceRule(new IWhitespaceDetector() { 166 public boolean isWhitespace(char character) { 167 return Character.isWhitespace(character); 168 } 169 }); 170 171 setRules(rules); 172 } 173 174 } 175 176 /** 177 * 用于SQL编辑分区,区分字符串或者注释 178 * @author Administrator 179 * 180 */ 181 public class SQLPartitionScanner extends RuleBasedPartitionScanner { 182 183 public static final String SQL_COMMENT = "__sql_comment"; 184 public static final String SQL_STRING = "__sql_string"; 185 186 public SQLPartitionScanner() { 187 IPredicateRule[] rules = new IPredicateRule[4]; 188 189 IToken comment = new Token(SQL_COMMENT); 190 rules[0] = new MultiLineRule("/*", "*/", comment, (char) 0, true); 191 rules[1] = new EndOfLineRule("--", comment); 192 193 IToken string = new Token(SQL_STRING); 194 rules[2] = new SingleLineRule("\"", "\"", string, ‘\\‘); 195 rules[3] = new SingleLineRule("\‘", "\‘", string, ‘\\‘); 196 197 setPredicateRules(rules); 198 } 199 200 } 201 }
4 颜色提供类
1 package testpreference.dialog; 2 3 import java.util.HashMap; 4 import java.util.Iterator; 5 import java.util.Map; 6 7 import org.eclipse.jface.preference.IPreferenceStore; 8 import org.eclipse.jface.resource.StringConverter; 9 import org.eclipse.jface.text.TextAttribute; 10 import org.eclipse.jface.text.rules.IToken; 11 import org.eclipse.jface.text.rules.Token; 12 import org.eclipse.jface.util.PropertyChangeEvent; 13 import org.eclipse.swt.graphics.Color; 14 import org.eclipse.swt.graphics.RGB; 15 import org.eclipse.swt.widgets.Display; 16 17 public class EditorColorProvider { 18 19 private Map<RGB, Color> colorTable = new HashMap<RGB, Color>(10); 20 private Map<String, IToken> tokenTable = new HashMap<String, IToken>(10); 21 IPreferenceStore store; 22 23 public EditorColorProvider(IPreferenceStore store) { 24 this.store = store; 25 } 26 27 public IToken getToken(String prefKey){ 28 Token token = (Token) tokenTable.get(prefKey); 29 if (token == null){ 30 String colorName = store.getString(prefKey); 31 RGB rgb = StringConverter.asRGB(colorName); 32 token = new Token(new TextAttribute(getColor(rgb))); 33 tokenTable.put(prefKey, token); 34 } 35 return token; 36 } 37 38 public void dispose(){ 39 Iterator<Color> e = colorTable.values().iterator(); 40 while (e.hasNext()){ 41 e.next().dispose(); 42 } 43 } 44 45 public Color getColor(String prefKey){ 46 String colorName = store.getString(prefKey); 47 RGB rgb = StringConverter.asRGB(colorName); 48 return getColor(rgb); 49 } 50 51 private Color getColor(RGB rgb) { 52 Color color = (Color) colorTable.get(rgb); 53 if (color == null){ 54 color = new Color(Display.getCurrent(), rgb); 55 colorTable.put(rgb, color); 56 } 57 return color; 58 } 59 60 public boolean affectsTextPresentation(PropertyChangeEvent event){ 61 Token token = (Token) tokenTable.get(event.getProperty()); 62 return (token != null); 63 } 64 65 public void handlePreferenceStoreChanged(PropertyChangeEvent event){ 66 String prefKey = event.getProperty(); 67 Token token = (Token) tokenTable.get(prefKey); 68 if (token != null){ 69 String colorName = store.getString(prefKey); 70 RGB rgb = StringConverter.asRGB(colorName); 71 token.setData(new TextAttribute(getColor(rgb))); 72 } 73 } 74 }
5 preferenceStore设置初始化参数
1 package testpreference.preference; 2 3 import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; 4 import org.eclipse.jface.preference.IPreferenceStore; 5 import org.eclipse.jface.resource.StringConverter; 6 import org.eclipse.swt.graphics.RGB; 7 8 import testpreference.Activator; 9 10 public class AbstractPreferenceInitializer1 extends 11 AbstractPreferenceInitializer { 12 13 public AbstractPreferenceInitializer1() { 14 // TODO Auto-generated constructor stub 15 } 16 17 @Override 18 public void initializeDefaultPreferences() { 19 IPreferenceStore store = Activator.getDefault().getPreferenceStore(); 20 21 // store.setDefault(Activator.PREF_PARAM_1, "hello"); 22 // store.setDefault(Activator.PREF_PARAM_2, "xingoo"); 23 24 store.setDefault(Activator.PREF_COLOR_DEFAULT, StringConverter.asString(new RGB(0,0,0))); 25 store.setDefault(Activator.PREF_COLOR_COMMENT, StringConverter.asString(new RGB(0,128,0))); 26 store.setDefault(Activator.PREF_COLOR_STRING, StringConverter.asString(new RGB(0,0,255))); 27 store.setDefault(Activator.PREF_COLOR_KEYWORD, StringConverter.asString(new RGB(128,0,128))); 28 } 29 }
由于博主自己对这部分的代码也没有达到熟练使用的地步,因此编码的过程有些混乱,这里还需要多加练习和实践,才能领会其中的妙处!本文也仅仅是作为一个入门而已。