插件是eclipse的重要组成部分,eclipse本生就是一个插件集,通过插件开发可以实现其功能的无限扩展。其中org.eclipse.core.runtime是eclipse的核心插件,其它所有的插件都是由它来载入或停止。工作台(workbech )的初始化工作是由org.eclipse.ui.workbench插件来完成的,所有的界面元素都是由org.eclipse.ui这个包下面的类实现及载入。新建一个插件通常要定义它的扩展点(extension points ),可以在plugin.xml中的extension element部分指定,如果插件的建立是基于插件模板建立的,这部分工作会自动完成。有关eclipse platform 的结构图如下:
有关elipse platform的详细介绍参见http://help.eclipse.org/help31/index.jsp 。
如何创建一个editor plugin。 JFace Text Editor 是开发editor插件的依赖点,它是一个功能强大、结构复杂的组件,提供了一个开发editor的框架,开发的时候只需要编写自己兴趣的部分,基本的结构不需要另外的编写。
一、根据向导完成editor plugin的基本结构。首先new 一个 plugin-in project,由向导结合自己的需要一步步完成editor plugin 的建立,其中Plug-in ID 是唯一标识改插件的ID,不能与其它的ID重复,插件的载入、查找都需要用到。Plug-in classPath指定该插件的classpath。Actovator是插件的主类,其中定义了插件启动、停止的方法。选择的template 必须是Plug-in with an editor,File Extension指定该editor 应用的文件种类,如果在此处指定了特定的文件后缀名,引用该插件的时候就不需要单独设定(在菜单window-preferences-General-Editors-File Associations处设定)。
二、插件目录结构介绍。以默认方式打开META_INF文件夹下面的MANIFEST .MF 文件,选择Build文件项,在Binary Build 中指定的文件或者文件夹是最终打包发布的根目录中包含的文件或文件夹,在Source Build 指定的文件或者文件夹是插件运行环境中包含的文件或文件夹,通常对应一个.jar文件。src底下存放插件的源文件,打包的时候只需要指定 including soruce 就会把这个文件夹包含进来。plugin.xml是插件的一些配置信息,它的功能和MANIFEST.MF相似,不过MANIFEST.MF优先与 plugin.xml,如果打包发布的文件中包含META-INF这个文件夹,一些配置信息是从MANIFEST.MF中读取,而不是从 plugin.xml中。如果想要包含外部包,在MANIFEST.MF中指定Bundle-ClassPath: lib/my.jar,other.jar,或者是在plugin.xml中添加如下语句:
<runtime>
<library name="tpleditor.jar">
<export name="*"/>
</library>
<library name="lib/other.jar">
<export name="*"/>
<library>
</runtime>
三、eclipse版本对插件运行的影响。开发插件的时候这点必须小心,尽量确保兼容eclispe的不同版本。由于有的类可能只在一个eclipse版本中有,另外的版本没有,那么运行该插件的时候通常会报ClassNotFoundException异常,因此调用API的时候必须要确保它的兼容性。另外,依赖插件也必须要存在,否则也会报该异常。
开发editor plugin需要注意的几个要素。
一、文档分割(Document Partition)。 Editor编辑的内容封装在IDocument类中,对于编辑内容的遍历、定位都是在IDocument基础上来实现。 文档的分割信息以及其它原数据信息有Editor保存,定位信息封装在Position 类中。打开一个文档的时候编辑器 自动进行分割处理得到各种不同内容类型(content type )的互不重叠的文本块。分割器的设置是在 IDocument的createDocument()方法中设置,如下:
protected IDocument createDocument(Object element) throws CoreException {
IDocument document = super.createDocument(element);
if (document != null) {
IDocumentPartitioner partitioner = new XMLPartitioner(
new XMLPartitionScanner(), new String[] {
TplPartitionScanner.XML_TAG,
XMLPartitionScanner.XML_COMMENT,
TplPartitionScanner.XML_START_TAG,
XMLPartitionScanner.XML_END_TAG
});
partitioner.connect(document);
document.setDocumentPartitioner(partitioner);
}
return document;
}
二、错误标记(Error Marking)。 Error Marking对编辑的文档根据一定的规则进行验证,通过它可以减少我们编写文档时出现的错误。比如对与XML 的DTD 或者XML Schema,首先解析文档的时候对error进行标识,如用SAX ErrorHandler收集和定位所有error,接下来进行验证和标记,这个工作在文档加载或保存的时候会进行。验证(Validation)和错误标记(Error Marking)的初始化工作实在XXXEditor的 validateAndMark()方法中进行的,如下所示:
protected void validateAndMark()
{
IDocument document = getInputDocument();
MarkingErrorHandler markingErrorHandler =
new MarkingErrorHandler(getInputFile(), document);
markingErrorHandler.removeExistingMarkers();
XMLParser parser = new XMLParser();
parser.setErrorHandler(markingErrorHandler);
String text = document.get();
parser.doParse(text);
}
MarkingErrorHandler的实例化需要两个参数,一个是IFile类型的实例(用来执行marking),一个是IDocument 类型的实例(用于确定插入文档中的marker的位置)。
文档解析之前所有已存在的错误标记必须清除(clear)掉,解析文档的时候如果发现错误调用MarkingErrorHandler的 handleError()方法。如下所示:
protected void handleError(SAXParseException e, boolean isFatal)
{
int lineNumber = e.getLineNumber();
int columnNumber = e.getColumnNumber();
Map map = new HashMap();
MarkerUtilities.setLineNumber(map, lineNumber);
MarkerUtilities.setMessage(map, e.getMessage());
map.put(IMarker.MESSAGE, e.getMessage());
map.put(IMarker.LOCATION, file.getFullPath().toString());
Integer charStart = getCharStart(lineNumber, columnNumber);
if (charStart != null)
map.put(IMarker.CHAR_START, charStart);
Integer charEnd = getCharEnd(lineNumber, columnNumber);
if (charEnd != null)
map.put(IMarker.CHAR_END, charEnd);
map.put(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR));
try
{
MarkerUtilities.createMarker(file, map, ERROR_MARKER_ID);
}
catch (CoreException ee)
{
ee.printStackTrace();
}
}
这里通过解析器不仅取得了error信息,而且还得到了发生错误的位置信息。
三、内容辅助(Content Assistance)。 我们在编辑文件的时候往往需要根据输入的部分信息自动完成接下来
需要输入的内容。例如,当我们输入<c:的时候会列出所有的内置标签,当输入<c:se 的时候,会自动填写<c:set>
等。首先我们必须新建一个ContentAssistent类,然后设置它的内容辅助处理器(IContentAsssitProcessor),
在XXXConfiguraton中:
IContentAssistant getContentAssistant(ISourceViewer sourceViewer){
ContentAssistant assistant= new ContentAssistant();
assistant.setContentAssistProcessor(new
MyContentAssistProcessor,XXXPartitionScanner.XXXPartition_CONST);
.
.
.
assistant.setAutoActivationDelay(200);
assistant.enableAutoActivation(true);
assistant.enableAutoInsert(true);
return assistant;
}
其中每一种分区对应一个IContentAssistProcessor,分区常量作为key,IContentAssistProcessor存放一个map中。
编辑文档的时候根据分区种类得到唯一的一个IContentAssistProcessor,然后处理IContentAssistProcessor
中首先根据getCompletionProposalAutoActivationCharacters()方法判断输入的字符是否能够激活该功
能,然后再调用computeCompletionProposals(ITextViewer viewer, int offset)方法,得到一组需要弹出显
示的提示信息。比如如果定义:
char[] getCompletionProposalAutoActivationCharacters(){
return new String("c:").toCharArray();
}
则当我们输入c:时弹出所有的系统内置标签。computeCompletionProposals方法的offset是正在编辑的文档的
当前光标所在偏移量,这个参数是必要的,在处理最终显示的位置,显示的字符长度等等都有用。
四、语法高亮显示(Syntax Highlighting) 语法高亮功能是建立在doument partiitoning的基础之上的。
编辑器刚打开文件的时候是对文档进行完全的扫描,然后进行分割,每一块对应一种类型(content types),每个
文档快可以指定特定的扫描器(scanner),里面定义了一个或者多个扫描规则(scanner rule),文档显示的时候
调用scanner的evaluate方法,该方法返回IToken对象,最后根据该对象的显示出我们需要的结果。比如我们可以
如下定义:
public class MyTagScanner extends RuleBasedScanner {
public TplTagScanner(ColorManager manager) {
IToken string = new Token(new TextAttribute(manager
.getColor(TplColorConstants.STRING)));
IToken attr = new Token(new TextAttribute(manager
.getColor(MyColorConstants.ATTRIBUTE)));
IRule[] rules = new IRule[2];
rules[0] = new MultiLineRule(" ","=",attr,'\\');
rules[1] = new WhitespaceRule(new MyWhitespaceDetector());
setRules(rules);
}
}
需要注意扫描某个document segment的时候,已经返回的IToken的有效区域不会进入下次的扫描范围。
五、内容格式化(Content Formatting) 格式化就是按照特定的规则对文档重新排版使之更具可读性。
实现该功能首先定义格式化策略,然后通过SourceViewerConfiguration实现类给ISourceViewer添加
策略。格式化策略的定义如下所示:
public class TextFormattingStrategy extends DefaultFormattingStrategy
{
private static final String lineSeparator = System.getProperty("line.separator");
public String format(String content,
boolean isLineStart,
String indentation,
int[] positions)
{
if (indentation.length() == 0)
return content;
return lineSeparator + content.trim() + lineSeparator + indentation;
}
}
上面是对文本内容进行trim处理,并在前后加上换行符。这里只简单介绍一下,实际的处理要比这复杂。通过
重载SourceViewerConfiguration的getContentFormatter方法可以将格式化操作添加到编辑器中。
public IContentFormatter getContentFormatter(ISourceViewer sourceViewer)
{
ContentFormatter formatter = new ContentFormatter();
TextFormattingStrategy formattingStrategy = new TextFormattingStrategy();
OtherFormattingStrategy otherStrategy = new OtherFormattingStrategy();
formatter.setFormattingStrategy(formattingStrategy,someContentType);
formatter.setFormattingStrategy(otherStrategy ,otherContentType);
return formatter;
}
六、插件的路径。 得到插件所在的路径可以如下调用:Platform.asLocalURL(
Platform.getBundle("your plugin ID").getEntry("")).getFile();
得到当前工作空间的路径:
Platform.getInstanceLocation().getURL().getFile();
得到当前工作空间下的所有工程:
ResourcesPlugin.getWorkspace().getRoot().getProjects();
以上只是粗略的介绍了编辑器开发需要注意的实现,许多细节每个人的使用的习惯不同,可以自己实现。