1. 程式人生 > >EclipsePlug-in使用TextEditor開發自己的編輯器,實現關鍵字高亮和程式碼提示.

EclipsePlug-in使用TextEditor開發自己的編輯器,實現關鍵字高亮和程式碼提示.

        最近在開發EclipsePlug, 開發一個SQL程式碼編輯器, 所以就寫一篇文章. 希望對大家有幫助. 讓大家少走彎路. (程式碼可能不能執行, 但關鍵部分都有).  因為程式碼比較多. 所以可能不能一次性上傳完成. 畢竟我還要修改, 空話不多說. 直接上程式碼.

首先是Editor類, 我取名為SQLEditor, 繼承TextEditor:

package com.test.editors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextEditor;
import com.zdk.platform.studio.dbassistant.codeassistent.ColorManager;
import com.zdk.platform.studio.dbassistant.codeassistent.SQLConfiguration;
import com.zdk.platform.studio.dbassistant.codeassistent.SQLDocumentProvider;

/**
 * 類說明: SQL語句編輯器.
 * @author xiao天__
 *
 */
public class SQLEditor extends TextEditor {
	
	/**editorID**/
	public static String ID = "com.test.SQLEditor";
	
	private ColorManager colorManager;
	private SQLConfiguration configuration;
	
	public SQLEditor() {
		super();
		configuration = new SQLConfiguration();
		setSourceViewerConfiguration(configuration);
		setDocumentProvider(new SQLDocumentProvider());
	}
	
	/**
	 * 方法說明: 設定字型.
	 */
	public void initFont() {
		FontData fontData = new FontData("Consolas", 11, SWT.NORMAL);
		Font font = new Font(getEditorSite().getShell().getDisplay(), fontData);
		this.getSourceViewer().getTextWidget().setFont(font);
	}
	
	public void dispose() {
		if(colorManager != null) {
			colorManager.dispose();
		}
		super.dispose();
	}
	
	@Override
	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		initFont();
	}
}

如果大家使用的是有檔案的方式. 就可以直接配置Plug-in.xml檔案方式來開啟SQLEditor, 而小天__使用的是不需要檔案的方式來開啟SQLEditor, 主要是專案需要, 所以這裡給大家寫兩種方式:

方式一:

Plug-in.xml:

<plugin>
   <extension
         point="org.eclipse.ui.editors">
      <editor
            name="SQL編輯器"
            extensions="xml"
            icon="icons/sample.gif"
            contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
            class="com.test.SQLEditor"
            id="com.test.SQLEditor">
      </editor>
   </extension>
</plugin>

方式二:

Plug-in.xml的配置都是一樣的, 因為沒有檔案. 所以我們必須要構建一個Input物件:

SQLEditorInput:

package com.test.editors;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;

public class SQLEditorInput implements IStorageEditorInput {
	/**顯示名稱**/
	private String name;
	/**正文**/
	private String content = "";
	/**儲存器**/
	public IStorage storage;
	
	public SQLEditorInput(String name) {
		this.name = name;
		storage = new IStorage() {
			@Override
			public Object getAdapter(Class adapter) {
				return null;
			}
			@Override
			public boolean isReadOnly() {
				return isReadOnly;
			}
			@Override
			public String getName() {
				return SQLEditorInput.this.name;
			}
			@Override
			public IPath getFullPath() {
				return null;
			}
			@Override
			public InputStream getContents() throws CoreException {
				return new ByteArrayInputStream(content.getBytes());
			}
		};
	}
	
	public SQLEditorInput(String name, IStorage is) {
		this.name = name;
		this.storage = is;
	}
	
	@Override
	public boolean exists() {
		return true;
	}

	@Override
	public ImageDescriptor getImageDescriptor() {
		return null;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public IPersistableElement getPersistable() {
		return null;
	}

	@Override
	public String getToolTipText() {
		return "SQL編輯器";
	}

	@Override
	public Object getAdapter(Class adapter) {
		return null;
	}

	@Override
	public IStorage getStorage() throws CoreException {
		return storage;
	}

	@Override
	public boolean equals(Object obj) {
		if(obj instanceof SQLEditorInput){
			SQLEditorInput newDbEditorInput = (SQLEditorInput)obj;
			if(this.name.equals(newDbEditorInput.getName())) {
				return true;
			}
		}
		return false;
	}
}

開啟編輯器方法:

這個方法可以寫在工具欄的事件裡. 右鍵選單裡. 這個就看需求了.

public void openView() {
	SQLEditorInput input = new SQLEditorInput("新建查詢");
	try {
		page.openEditor(input, "com.test.SQLEditor");
	} catch (PartInitException e) {
		e.printStackTrace();
	}
}

SQLConfiguration類:

package com.zdk.platform.studio.dbassistant.codeassistent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.formatter.ContentFormatter;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import com.zdk.platform.studio.dbassistant.codeassistent.assistent.SQLContentAssistent;

public class SQLConfiguration extends SourceViewerConfiguration {
	
	private SQLContentAssistent assistent;
	
	public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
		PresentationReconciler reconciler = new PresentationReconciler();
		DefaultDamagerRepairer dr = new DefaultDamagerRepairer(new SQLPartitionScanner());
		reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE);
		reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
		return reconciler;
	}
	
	@Override
	public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) {
		ContentFormatter formatter = new ContentFormatter();
		formatter.setFormattingStrategy(new SQLFormattingStrategy(), IDocument.DEFAULT_CONTENT_TYPE);
		formatter.enablePartitionAwareFormatting(true);
		return formatter;
	}

	/**
	 * 設定自動提示.
	 */
	@Override
	public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
		ContentAssistant contentAssistent = new ContentAssistant();
		assistent = new SQLContentAssistent();
		contentAssistent.setContentAssistProcessor(assistent, IDocument.DEFAULT_CONTENT_TYPE);
		contentAssistent.enableAutoActivation(true);
		contentAssistent.setAutoActivationDelay(200);
		return contentAssistent;
	}

	public DBConfig getDbConfig() {
		return dbConfig;
	}
}

SQLDocumentProvider類(說實話這個類.小天__都不清楚有什麼用):
package com.zdk.platform.studio.dbassistant.codeassistent;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.rules.FastPartitioner;
import org.eclipse.ui.editors.text.FileDocumentProvider;

public class SQLDocumentProvider extends FileDocumentProvider {
	protected IDocument createDocument(Object element) throws CoreException {
		IDocument document = super.createDocument(element);
		if (document != null) {
			IDocumentPartitioner partitioner =
				new FastPartitioner(
					new SQLPartitionScanner(),
					new String[] { });
			partitioner.connect(document);
			document.setDocumentPartitioner(partitioner);
		}
		return document;
	}
}

程式碼高亮部分:

ColorManager類:

package com.zdk.platform.studio.dbassistant.codeassistent;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;

/**
 * 類說明: 顏色管理類.
 * @author 小天__
 *
 */
public class ColorManager {
	
	/**註釋顏色**/
	public static final RGB COLOR_COMMENT = new RGB(0, 128, 0);
	/**關鍵字顏色**/
	public static final RGB COLOR_KEYWORD = new RGB(128, 0, 0);
	/**普通文字顏色**/
	public static final RGB COLOR_TEXT = new RGB(0, 0, 0);

	protected static Map fColorTable = new HashMap(10);
	
	public void dispose() {
		Iterator e = fColorTable.values().iterator();
		while (e.hasNext())
			 ((Color) e.next()).dispose();
	}
	
	public static Color getColor(RGB rgb) {
		Color color = (Color) fColorTable.get(rgb);
		if (color == null) {
			color = new Color(Display.getCurrent(), rgb);
			fColorTable.put(rgb, color);
		}
		return color;
	}
}

SQLPartitionScanner:

package com.test.codeassistent;

import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.rules.EndOfLineRule;
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;

import com.test.rule.KeyWordDetector;
import com.test.rule.KeyWordRule;

public class SQLPartitionScanner extends RuleBasedPartitionScanner {

	private TextAttribute keywordAttr = new TextAttribute(ColorManager.getColor(ColorManager.COLOR_KEYWORD));

	public SQLPartitionScanner() {
		IPredicateRule[] rules = new IPredicateRule[1];
		rules[0] = new KeyWordRule(new KeyWordDetector(), new Token(keywordAttr));
		setPredicateRules(rules);
	}
}

KeyWordRule:
package com.zdk.platform.studio.dbassistant.codeassistent.rule;

import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.IWordDetector;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.jface.text.rules.WordRule;

import com.test.SQLPartitionScanner;

public class KeyWordRule extends WordRule implements IPredicateRule {
	
	private StringBuffer fBuffer= new StringBuffer();
	private boolean fIgnoreCase= false;
	/**關鍵字**/
	public String keywords = "insert,update,delete,select";
	
	public KeyWordRule(IWordDetector detector, IToken defaultToken) {
		super(detector, new Token(SQLPartitionScanner.textAttr));
		//增加關鍵字
		String[] keywordArray = keywords.split(",");
		for (int i = 0; i < keywordArray.length; i++) {
			String keywrod = keywordArray[i];
			addWord(keywrod, defaultToken);
		}
	}
	
	public IToken evaluate(ICharacterScanner scanner) {
		int c= scanner.read();
		if (c != ICharacterScanner.EOF && fDetector.isWordStart((char) c)) {
			if (fColumn == UNDEFINED || (fColumn == scanner.getColumn() - 1)) {

				fBuffer.setLength(0);
				do {
					fBuffer.append((char) c);
					c= scanner.read();
				} while (c != ICharacterScanner.EOF && fDetector.isWordPart((char) c));
				scanner.unread();

				String buffer= fBuffer.toString();

				if (fIgnoreCase) {
					buffer = buffer.toLowerCase();
				}
				IToken token= (IToken)fWords.get(buffer);
				
				if (token != null) {
					return token;
				}
				
				if (fDefaultToken.isUndefined()) {
					unreadBuffer(scanner);
				}
				return fDefaultToken;
			}
		}
		scanner.unread();
		return Token.UNDEFINED;
	}

	@Override
	public IToken getSuccessToken() {
		return Token.UNDEFINED;
	}

	@Override
	public IToken evaluate(ICharacterScanner scanner, boolean resume) {
		return this.fDefaultToken;
	}
	
}

KeyWordDetector:
package com.zdk.platform.studio.dbassistant.codeassistent.rule;

/*******************************************************************************
 * Copyright (c) 2000 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     QNX Software System
 *******************************************************************************/

import org.eclipse.jface.text.rules.IWordDetector;

/**
 * A C aware word detector.
 */
public class KeyWordDetector implements IWordDetector {

	/**
	 * @see IWordDetector#isWordIdentifierStart
	 */
	public boolean isWordStart(char c) {
		if(64 < c && c < 123 || c == '=') {	//大寫字母A-Z, 小寫字母a-z;
			return true;
		}
		return false;
	}

	/**
	 * @see IWordDetector#isWordIdentifierPart
	 */
	public boolean isWordPart(char c) {
		if(64 < c && c < 123 || c == '=') {	//大寫字母A-Z, 小寫字母a-z;
			return true;
		}
		return false;
	}
}

程式碼提示部分:

SQLContentAssistent:

package com.test.assistent;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;

import com.zdk.platform.studio.pojo.DBConfig;

public class SQLContentAssistent implements IContentAssistProcessor {
	 
	/**提示集合**/
	public List<IAssistentContent> assistentContentList = new ArrayList<IAssistentContent>();
	
	/**開始位置**/
	public int startIndex = 0;
	/**當前文件**/
	public IDocument doc;
	/**資料連線物件**/
	private DBConfig dbConfig;
	
	/**
	 * 構造方法: 新增提示種類.
	 */
	public SQLContentAssistent() {
		super();
		assistentContentList.add(new KeywordAssistentData());
	}

	@Override
	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
		this.doc = viewer.getDocument();
		List list = new KeywordAssistentData().getAssistentData(this.doc, offset);
		return (CompletionProposal[]) list.toArray(new CompletionProposal[list.size()]);
	}

	@Override
	public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
		// TODO 自動生成的方法存根
		return null;
	}

	/**
	 * 設定何時啟用
	 */
	@Override
	public char[] getCompletionProposalAutoActivationCharacters() {
		return new char[] {'.'};
	}

	@Override
	public char[] getContextInformationAutoActivationCharacters() {
		return null;
	}

	@Override
	public String getErrorMessage() {
		// TODO 自動生成的方法存根
		return null;
	}

	@Override
	public IContextInformationValidator getContextInformationValidator() {
		// TODO 自動生成的方法存根
		return null;
	}
	
	/**
	 * 方法說明: 獲取提示前面的字串.
	 * @param doc
	 * @param offest
	 * @return
	 */
	public String getFrontText(IDocument doc, int offest) {
		StringBuffer buf = new StringBuffer();
		while(true) {	//迴圈新增關鍵字.
			try {
				char c = doc.getChar(--offest);
				startIndex = offest;
				if(Character.isWhitespace(c)) {
					startIndex++;
					break;
				}
				if(c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == ',') {	//結束符號.
					startIndex++;
					break;
				}
				buf.append(c);
			} catch (BadLocationException e) {
				break;
			}
		}
		return buf.reverse().toString();
	}
}

KeywordAssistentData:
package com.test.assistent;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.CompletionProposal;

import com.zdk.platform.studio.dbassistant.codeassistent.Contents;

/**
 * 類說明: 關鍵字提示資料類.
 * @author Administrator
 *
 */
public class KeywordAssistentData implements IAssistentContent {
	
	private int start = 0; 
	
	@Override
	public List<CompletionProposal> getAssistentData(IDocument doc, int offset) {
		
		String str = getFrontText(doc, offset);
		List<CompletionProposal> list = new ArrayList<CompletionProposal>();
		//用正則匹配.
		Pattern p = null;
		Matcher m = null;
		for (int i = 0; i < Contents.KEY_WORD_ARRAY.length; i++) {
			String keyWord = Contents.KEY_WORD_ARRAY[i];
			p = Pattern.compile("(^" + str + ")", Pattern.CASE_INSENSITIVE);
			m = p.matcher(keyWord);
			if(m.find()) {
				String insert = Contents.KEY_WORD_ARRAY[i];
				//建立替換類容.  insert:替換文字, offset:替換其實位置.
				//0:替換結束位置.偏移量, insert.length:替換完成後.游標位置偏移量.
				CompletionProposal proposal = new CompletionProposal(insert, start, offset - start, insert.length());
				list.add(proposal);
			}
		}
		return list;
	}
	
	/**
	 * 方法說明: 獲取提示前面的字串.
	 * @param doc
	 * @param offest
	 * @return
	 */
	public String getFrontText(IDocument doc, int offest) {
		StringBuffer buf = new StringBuffer();
		while(true) {	//迴圈新增關鍵字.
			try {
				char c = doc.getChar(--offest);
				start = offest;
				if(Character.isWhitespace(c)) {
					start++;
					break;
				}
				if(c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == ',') {	//結束符號.
					start++;
					break;
				}
				buf.append(c);
			} catch (BadLocationException e) {
				break;
			}
		}
		return buf.reverse().toString();
	}
}

以上就是大部分程式碼了.