前文回顧:
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 sequence
endSequence the pattern's end sequence
token the token to be returned on success
escapeCharacter the escape character
breaksOnEOF indicates whether the end of the file terminates this rule successfully
*/ rules[1] = new EndOfLineRule("--", comment); /*
startSequence the pattern's start sequence
token 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 sequence
endSequence the pattern's end sequence
token the token to be returned on success
escapeCharacter 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 }
由於博主自己對這部分的代碼也沒有達到熟練使用的地步,因此編碼的過程有些混亂,這里還需要多加練習和實踐,才能領會其中的妙處!本文也僅僅是作為一個入門而已。
