`
hqs7636
  • 浏览: 216302 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

基于 JFace Text Framework 构建全功能代码编辑器: 第 7 部分:

阅读更多
https://www.ibm.com/developerworks/cn/opensource/os-cn-ecljtf7/

Quick Assistant
developerWorks


文档选项
将打印机的版面设置成横向打印模式

打印本页
将此页作为电子邮件发送

将此页作为电子邮件发送


样例代码

级别: 中级

马 若劼 (maruojie@cn.ibm.com), 软件工程师, IBM 中国软件开发中心

2008 年 4 月 24 日

    Quick Assistant(快速帮助)的基本用途是为源代码中的错误提供一些快速的解决方案,它和 Content Assistant(内容提供)虽目的不同,但架构类似。本文介绍如何快速帮助的概念和实现方法。

Quick Assistant

Quick Assistant(快速帮助)的基本用途是为源代码中的错误提供一些快速的解决方案。快速的意思是指这个方案足够简单或者足够模式化,可以由程序帮你自动完成。当然快速帮助是无法解决深层次的问题的,不过一般我们在编写代码的时候,犯的最多的都是一些小错误,所以快速帮助是个非常有用的功能。

在 Java 编辑器中,快速帮助看上去就是下图的样子:

图1. Java 编辑器中的快速帮助
Java 编辑器中的快速帮助

提示:如果还不了解内容提示的概念,参见本系列第四部分了解更多信息。

可见,不管是从名字上,还是界面上,快速帮助都非常类似我提过的 Content Assistant(内容提示)功能。实际上,它们的架构和实现方式也差不多。

快速帮助是基于 Annotation(标注)的,我们已经在本系列第五部分中介绍了如何创建一个标注并显示出来。标注包含一个类型信息,比如错误或者警告或者只是提示。快速帮助的基本想法就是判断光标所在位置有没有标注,如果有则检查标注的类型,如果是你感兴趣的类型,比如错误,则触发快速帮助。

由于其和内容提示的相似性,我就不废话了,让我们直接看看如何实现快速帮助吧。




回页首


实现快速帮助

在目前的例子里,我已经把语法错误显示出来了。因为快速帮助可以针对标注类型来触发,所以我打算增加一种错误类型:Undelcared Variable(未声明的变量),比如在下面的例子中:

清单1. 错误的语法,变量 b 未声明

                a = 3;
a = b;


a 被赋值了两次,第一次是用常量,第二次是把变量b的值赋给变量a。如果根据目前的解析器文法来说,这段代码的语法没有问题,但是语义有问题,因为 b 没有声明过。

对于这种新的错误类型,我会认为用户也许是敲错了变量名,所以会显示出声明过的变量名列表,如果用户选择了一个,则未声明的变量被替换成声明过的变量。对于原来的错误,我不提供快速帮助,这样大家就可以看出差别了。

实现底层支持

要增加错误类型,又需要修改解析器文法,我已经完成这部分,所有未声明的变量都被保存到了一个列表里面。同样,又增强了 SharedParser 以便得到这些未声明的变量。

添加标注类型

我不能还是使用 org.eclipse.ui.workbench.texteditor.error 这样的标注类型,需要有所区别才行。所以我通过 org.eclipse.ui.editors.annotationTypes 扩展点添加了一个标注类型,它的父类还是 org.eclipse.ui.workbench.texteditor.error,这样的话它就可以继承父类的一些设置,不用我去创建 AnnotationPreference 了。扩展的声明如下:

清单2. 错误的语法,变量 b 未声明

               
      <extension
       point="org.eclipse.ui.editors.annotationTypes">
    <type
          name="jtf.tutorial.annotation.undeclared.variable"
          super="org.eclipse.ui.workbench.texteditor.error">
    </type>
</extension>


然后我在 SyntaxChecker 中为每个未声明的变量创建了这个类型的标注,因为有了底层支持,这个过程很简单,就不贴出代码了。我们看看实际的效果:

图2. 新的标注类型:未声明的变量
新的标注类型:未声明的变量

IQuickAssistProcessor

前面两步是把准备工作做完了,现在才轮到真正的接口上场了。IQuickAssistProcessor 的角色和 IContentAssistProcessor 的角色是完全相同的,只是具体方法不同罢了。下面是 IQuickAssistProcessor 的声明:

清单3. IQuickAssistProcessor 接口

               
     public interface IQuickAssistProcessor {
String getErrorMessage();

boolean canFix(Annotation annotation);

boolean canAssist(IQuickAssistInvocationContext invocationContext);

ICompletionProposal[] computeQuickAssistProposals(
IQuickAssistInvocationContext invocationContext);
}


快速帮助比内容提示多了一个上下文相关的接口:IQuickAssistInvocationContext。它可以用来为快速帮助提供一些辅助信息。实际上,内容提示也是需要上下文信息的,但是 JTF 没有另外做一个接口来包装它,如果你要做一个复杂的内容提示功能,往往需要自己来定义一个上下文信息接口。

canFix 方法在内容提示中也没有类似物。这个方法主要是提供给 Marker 系统来使用的,比如下图中所示的右键菜单:

图3. Problem 视图中的右键菜单
Problem 视图中的右键菜单

提示:如果不了解 Marker 是什么,可以参考 Eclipse.org 的文章: Mark My Words

当你在 Problem 视图中右键点击一个错误的时候,弹出菜单中有一个 Quick Fix 的菜单项,如果 canFix()返回 false,那么这个菜单项就会变灰而不可用了。由于我的例子中没有创建 Marker,所以这个方法用处有限。

canAssist 方法给了你一个检查不同上下文的机会,这样你就可以通过不同的上下文信息激活不同的快速帮助了。computeQuickAssistProposals 方法比较简单,除了名字和参数不同,它和内容提示中的 computeCompletionProposals 基本类似。

其它的方法都和内容提示类似,来看看例子中是怎么实现这个接口的,我只列出了 computeQuickAssistProposals,其它方法的实现都非常简短,在此省略。

清单4. ExprQuickAssistProcessor 实现了 IQuickAssistProcessor 接口

               
public class ExprQuickAssistProcessor implements IQuickAssistProcessor {
// other code


public ICompletionProposal[] computeQuickAssistProposals(
IQuickAssistInvocationContext invocationContext) {
// get viewer
ISourceViewer viewer = invocationContext.getSourceViewer();

// get annotation
Annotation anno = getAnnotation(viewer, invocationContext.getOffset());
if(anno == null || !canFix(anno))
return null;

// get doc
IDocument doc = viewer.getDocument();

// get tree
Tree tree = TreeManager.getTree(doc);
if(tree == null)
return null;

// get all declared variables
List<String> variables = TreeHelper.getVariables(tree);

// create proposals
Position pos = viewer.getAnnotationModel().getPosition(anno);
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for(String var : variables) {
proposals.add(new CompletionProposal(
var, pos.getOffset(), pos.getLength(), var.length(),
null, var, null, "Add your info here"));
}
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
}


这段代码和内容提示中的很相似,不同点是这里会去取得当前光标处的标注,然后用 canFix 方法检查标注是否可以被修正。最后,还是返回一个 Proposal 的数组。

配置

最后一步,几乎是把 ExprConfiguration 的 getContentAssistant 方法复制了一遍,只不过那些接口换成了快速帮助的。大家可以对比一下 getQuickAssistAssistant 和 getContentAssistant 有什么不同。提醒大家注意一下:快速帮助并没有和文本类型绑定在一起。这是合理的,因为快速帮助一般是用来修正错误的,在一个错误的源代码里面,可能很难知道某个地方到底应该是什么样的文本类型,因此根据文本类型来注册快速帮助功能是有问题的。除了不同点,看到更多的应该是共同点,比如相似的 Assistant/Processor 结构,相似的信息显示控件。注意,又是信息显示控件,到现在为止,已经有内容提示,快速帮助,文本悬浮和标注悬浮都使用了它。

快捷键处理

在 Eclipse 里面,触发快速帮助的缺省快捷键是 Ctrl+1,和内容提示一样,需要给快速帮助安装一个快捷键处理器,由于我已经在演示内容提示的时候打完了基础,现在只要改改 ExprViewer 的 createHandlers,添加一个 handler 就可以了。

效果

当你将光标放在未声明变量上,再按 Ctrl+1 时,可以看到弹出的框里面列出了所有声明过的变量,选择一个之后,未声明的变量会被替换。如下图所示:

图4. 快速帮助效果图
快速帮助效果图

读者可以尝试修正一下其它类型的错误,不会有反应,因为 ExprQuickAssistProcessor 里面只接受“未声明变量”这个标注类型




回页首


结束语

我没有实现 IQuickAssistInvocationContext 接口,如果是像 Java 编辑器这样复杂的应用,那就很可能需要了,大家可以想想,如何利用好这个接口。本文还提到了在 Problem 视图中有 Quick Fix 的菜单项,大家不妨尝试将编辑器中的错误添加到 Problem 视图中,然后使用快速帮助功能。




回页首


声明

本文仅代表作者的个人观点,不代表 IBM 的立场。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics