级别: 中级 马 若劼 (maruojie@cn.ibm.com), 软件工程师, IBM 中国软件开发中心
2008 年 5 月 08 日 Template(模版)是可以用来快速添加某种固定形式的代码,提高代码编辑的速度。模版和JTF的其它特性有或多或少的联系,比如内容提示,比如标注。本文介绍模版的相关概念,并给出一个简单的实现。
Template
Template(模版)可以用来快速添加某种固定形式的代码,中间还可以插入参数。对于 Java 编辑器来说,你可以在 Eclipse 的设置中找到相应的属性页,路径是 General->Java->Editor->Templates。这个属性页是Eclipse标准的模版属性页,因为它做的比较完善,所以一般不需要自己写一个。
仔细探索一下这个属性页,尝试编辑一下模版,你可能会发现很多不了解的概念,下面我会一一解释
模版属性
模版包含一些基本属性:
- Name(名称):这个模版的名字,必须唯一
- Context(上下文):上下文是指这个模版可以在什么位置或者什么情况下出现,它相当于对模版做了一个粗略的分类。这个属性可以方便你在某些时候隐藏掉不需要显示的模版。
- Description(描述):一段详细介绍模版功能的文字,还记得内容提示的时候可以通过 IInformationControl 显示一些帮助信息吗?这个属性很适合显示在那里。
- Auto Insert(自动插入):当用户的输入只可能定位到一个模版的时候,是否自动把这个模版插入到编辑器中。不然还是提示用户选择一个模版插入,即使只有一个模版。
模版的定义
模版可以通过扩展的方式定义,扩展点是 org.eclipse.ui.editors.templates,也可以通过属性页手动添加,也可以通过程序方式添加。通过扩展点添加的模版,可以认为是“静态模版”,即缺省就存在的,后面两种方式则更灵活一些,但是稍微麻烦一点。
模版的存储
模版是需要持久化的,它最终会被存放到一个XML文件里面。我们并不需要知道这个 XML 文件在哪里,格式是什么,这些事情被 TemplateStore 封装了,我们直接用它就行。在装载的模版的过程中,既要装载通过扩展点定义的模版,又要装载用户手动添加的模版,TemplateStore 只能装载通过扩展点定义的模版,所以一般是使用 TemplateStore 的子类 ContributionTemplateStore,它提供了装载用户自定义模版的能力。
模版参数
模版中可以嵌入参数,即我们看到的 ${arg} 的形式。JTF 缺省定义了一些参数,程序员也可以自己定义参数。参数大致可以分为两种:自动解析式和输入式。自动解析的参数有 time,date 等等,这类参数在插入模版到编辑器的时候,会自动替换成相应的时间,日期等等。输入式的参数则相当于一个占位符,用户通过键盘输入替换掉参数内容。后面的例子中不会演示自定义参数,有兴趣的读者可以看看 TemplateVariable,GlobalTemplateVariables和 TemplateVariableResolver 这些类。
实现模版功能
下面我来实现模版功能,然后能让模版在内容提示中出现。
定义模版上下文和缺省模版
首先通过扩展方式添加一个模版上下文类型,并且添加一个缺省的模版:
清单 1. 通过扩展点定义模版上下文
<extension
point="org.eclipse.ui.editors.templates">
<contextType
class="jtf.tutorial.template.ExprTemplateContextType"
id="jtf.tutorial.template.contextType"
name="Expr Template">
</contextType>
<template
autoinsert="true"
contextTypeId="jtf.tutorial.template.contextType"
description="Declare a variable"
id="jtf.tutorial.template.variableDeclaration"
name="variableDeclaration">
<pattern>
${variable} = ${integer}
</pattern>
</template>
</extension>
|
通过扩展方式还是很简单的,缺省的模版叫做“variableDeclaration”,它的信息都放在扩展定义里了,注意正确的方式应该是把一些字符串放到资源文件中,为了简单我没有这样做。
模版上下文类型需要一个实现类,在里面你可以管理你的模版参数,下面是 ExprTemplateContextType 的代码:
清单2. ExprTemplateContextType 实现
public class ExprTemplateContextType extends TemplateContextType {
public ExprTemplateContextType() {
addGlobalResolvers();
}
/**
* We add support for global variables
*/
private void addGlobalResolvers() {
addResolver(new GlobalTemplateVariables.Cursor());
addResolver(new GlobalTemplateVariables.WordSelection());
addResolver(new GlobalTemplateVariables.LineSelection());
addResolver(new GlobalTemplateVariables.Dollar());
addResolver(new GlobalTemplateVariables.Date());
addResolver(new GlobalTemplateVariables.Year());
addResolver(new GlobalTemplateVariables.Time());
addResolver(new GlobalTemplateVariables.User());
}
}
|
我添加了很多全局参数,所以我可以在编辑模版时看到它们,如下图所示:
图1. 全局参数支持
TemplateStore
我们需要一些方法,可以得到 TemplateStore,一般来讲,可以把这些方法放到插件的入口类中,因为一个插件只需要一个 TemplateStore。所以在本例中,我放到了 jtf.tutorial.Activator 里面。你可以看到两个新增的方法:getTemplateStore和getContextTypeRegistry。我使用了 ContributionTemplateStore,因为我需要装载用户自定义的模版。ContextTypeRegistry 的作用很好理解,因为同时可能存在多种模版类型,Eclipse需要把每类模版的相关信息管理起来,因此把模版上下文注册到了 ContextTypeRegistry 中。
添加属性页
因为我要支持用户自定义模版,所以需要添加一个模版属性页。只要继承 TemplatePreferencePage 并添加 org.eclipse.ui.preferencePages 扩展即可。在此不赘述了。
添加到内容提示
到上一步为止,设置对话框中也出现了我的属性页,用户可以自定义模版了。但是模版没有任何用武之地,还需要将模版添加到内容提示中才有意义。内容提示是由一个个 Proposal 组成的,对于模版,也有对应的 Proposal 实现:TemplateProposal。所以,接下来修改 ExprContentAssistProcessor,插入这么一段代码:
清单3. 修改 ExprContentAssistProcessor 以支持模版
// get template context type
TemplateContextType contextType = Activator.getDefault().
getContextTypeRegistry().getContextType(Activator.EXPR_CONTEXT_TYPE);
// create template context
TemplateContext context = new DocumentTemplateContext(contextType, doc, offset, 0);
// get all template
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
Template[] templates = Activator.getDefault().
getTemplateStore().getTemplates(Activator.EXPR_CONTEXT_TYPE);
for(Template t : templates) {
proposals.add(new TemplateProposal(t, context, new Region(offset ,0), null));
}
|
这是一个非常简化的版本,正常情况下,你需要判断光标之前有没有其它字符,如果有,只应该显示以光标之前字符串开头的模版,当你定义了多个模版上下文类型的时候,逻辑就要更复杂了。即便我已经省略了很多东西,创建 TemplateProposal 似乎还是比普通的 Proposal 麻烦一些,需要得到 TemplateContext,而 TemplateContext 需要 TemplateContextType。
效果
我们的缺省模版现在可以出现在内容提示里了,选择之后,模版的内容就被插入到了编辑器中:
图 2. 内容提示中的模版
图 3. 模版被选择后
Linked Model
有趣的事情还没有完,当模版被插入之后,可以看到每个参数的周围都有一个小框,你可以用 Tab 键在各个参数之间切换,当你按下回车键的时候,小框就消失了,也无法用 Tab 键导航了。
我在以前的文章中说过,这些框其实是标注,所以并不神奇。神奇的是可以用 Tab 键在参数之间导航,而且还有更神奇的,请看下图:
图 4. 修改后的 variableDeclaration 模版
我把 variableDeclaration 模版修改了一下,变成了“${variable} = ${integer} + ${integer} * ${factor}; ”。注意第二个参数和第三个参数名字是相同的。当你编辑第二个参数的时候,第三个参数周围不是一个空心矩形框,而是一个实心的蓝色背景,并且当你修改了第二个参数之后,第三个参数也跟着变了。
这些看上去很有趣的功能叫做 Linked Model(链式模型),其实就是把一些相关联的标注管理了起来,之所以叫链式模型,我个人认为可能有两个原因:
- 可以用 Tab 键在它们之间按先后顺序切换,好像一个链表一样
- 第二个参数改变会导致第三个参数也改变,也就是说第三个标注好像链接到了第二个标注上一样
为了实现这样一些功能,JTF 到底创建了多少种标注呢?看上去只有两种或者三种,实际上有四种,如下图所示:
图5. 标注类型
普通的空心矩形框表示的是 Target(目标)类型的标注,当一个 Target 类型的标注拥有焦点时,就成为了 Master(主)类型标注,也叫做Focus(焦点)类型标注。对于第三个参数,它和第二个参数名称相同,所以 JTF 为它创建了一个 Slave(从)类型标注。注意最后一个标注,我不是画线画的不准,而是在那个肉眼看不到的地方,还存在一个 Exit(退出)类型标注。这些词不是我发明的,如果你看看 LinkedPositionAnnotations 这个类就知道了。
将这些标注联系起来的管理方式,就叫做链式模型,由于它即牵涉到标注的管理,又牵涉到界面的绘制,所以在实现上采用了 MVC 的模式。LinkedModeModel 是模型部分,LinkedModeUI 是界面和控制部分。这两个类不是全部,还有很多其它的相关类,甚至是内部类,最有必要了解的是 LinkedModeUI.IExitPolicy 这个内部接口。一般来说,当你按下回车的时候,你会从模版编辑中退出来,这就是由 IExitPolicy 来判断的。所以,你可以定制退出的行为。Eclipse 里面有没有定制退出行为的例子呢?有的,在 Java 编辑器里,输入引号之后,编辑器会自动帮你插入另一个引号,如果你再输入一个引号,你不会看到三个引号,而是光标移到了自动插入的引号之后,假如你不继续输入引号,还是按回车,光标也会移到自动插入的引号之后。这就是自定义退出行为的例子,Java 编辑器会检查你输入的是什么引号,当你再按下它的时候,你就退出链式编辑状态了,Java 编辑器的这个功能同样适用于各种括号。这个功能,可以称为 Auto Completion(自动补全),我觉得还不能说它是JTF的标准特性之一,它只是链式模型的一个有趣的应用。
 | | 提示:如果你想要看看证据的话,浏览一下 CompilationUnitEditor 中的内部类 BracketInserter 和 ExitPolicy,这个类在 org.eclipse.jdt.ui 中。 |
|
所以,这种用 Tab 键在很多个标注之间导航的功能,不是模版的专利,你可以用在任何你想用的地方。比如:插入一个函数时,可以用链式模型管理函数的参数,就好像 Java 编辑器那样。至于如何做,TemplateProposal 已经给了你一个不错的例子。
结束语
在实现模版的过程中,我也顺便提及了一些相关概念,因为模版和其它的特性有着微妙的联系。下面是一些值得思考的问题:
- 试试看自定义模版参数
- 自定义 TemplateStore?没有什么不可以。
- 尝试使用多个模版上下文类型
- 想想看在什么场合可以使用链式模型这个有趣的功能呢
声明
本文仅代表作者的个人观点,不代表IBM的立场。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 第九小节示例代码 | jtf.tutorial.part9.zip | 1140KB | HTTP |
|---|
参考资料
关于作者  | |  | 马若劼是 Lotus Forms 部门的一位软件工程师,主要从事电子表单技术的研发工作。他在 Eclipse 和 Java 方面有多年研发经验,同时也是国内著名开源项目 LumaQQ 的创立者 |
对本文的评价
|