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

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

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

Text Decoration
developerWorks


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

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

将此页作为电子邮件发送


样例代码

级别: 中级

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

2008 年 4 月 10 日

    Text Decoration(文本装饰)是指对文本进行一些附加的视觉效果处理。本文介绍和文本装饰相关的概念并讨论如何在 JTF 中支持文本装饰。

Text Decoration

对于代码编辑器来说,Text Decoration(文本装饰)是一个必需的功能。我们先来看看 Java 编辑器中对文本装饰的应用:

图1. Java 编辑器中的文本装饰
Java 编辑器中的文本装饰

可以看到 Java 编辑器会把错误的部分用一个红色的波浪线标记出来,还可以看到对于模版来说,模版参数周围有一个小矩形,而且在左边还有相应的错误图标显示。这些都叫做文本装饰。

提示:模版会在以后的文章中进行介绍

我们在编辑代码的时候,出错是不可避免的,因此有了文本装饰功能之后,我们可以快速的发现错误位置,这对代码编辑是非常有用的。下面就来看一看要实现文本装饰功能都需要了解哪些概念。

Annotation

Annotation(标注)这个术语已经使用的很泛滥了,在很多地方都可以看到,比如 J2SE 5.0 中也有 Annotation 的概念。放到 JTF 中来讲,标注指的是和某块文本区域绑定的特定信息,至于特定信息是什么,是可以自定义的。可以观察一下 JTF 中 Annotation 这个类,可以看到它有类型信息,有附加的文本信息,同时它还有很多子类,而且子类也都包含了一些扩展信息。

标注包含类型信息是很重要的,通过类型,就可以知道某个标注的目的和作用了,然后可以在界面上反映出来。图 1 中的红色波浪线,实际上是因为在那个位置上有一个错误类型的标注。

Ruler

Ruler(标尺)是显示辅助信息的一块区域,它一般附着在编辑器的周围。比如图1中那两个表示错误的图标就是显示在 VerticalRuler(垂直标尺)上的。标尺可以多于一个,在图 1 中可以看到另外一个用来显示行号的标尺,需要多少个标尺可以定制的。

IAnnotationPresentation

名称里面带了“Presentation”字样的都和界面渲染有关,而 IAnnotationPresentation 就是用来渲染标注的。这个接口只是负责在标尺上绘制标注,图1中表示错误的图标就是出自这里。如果你自己定义的标注没有实现这个接口,那么就不会出现在标尺上了。

IAnnotationModel

IAnnotationModel 是用来管理标注的。它负责管理某个文档上所有的标注,并且负责触发相应的标注事件,比如添加,删除等等。也许这个接口叫做 IAnnotationManager 更合适些。由于 Eclipse 不断在发展中,它的 API 时常需要增强,但是为了不破坏与旧版本的兼容性,这些扩展一般都是以扩展接口的方式出现的。对于 IAnnotationModel 来说,你可以发现还有一个 IAnnotationModelExtension 接口,里面有一些更强更方便的方法。不光是 IAnnotationModel,你在很多地方都可以发现名字是 Extension 结尾的接口,所以请记住这种现象,当我们浏览代码的时候,不要以为某个接口就已经是全部了。

IAnnotationAccess

这个接口可以用来访问标注的一些信息。但是在 3.0 之后,标注的信息都可以通过 Annotation 类访问到,所以这个接口已经过时了,只是为了向下兼容而保留,我就不罗嗦了。

AnnotationPainter

这是 JTF 中和标注绘制相关的重要类。上面提到的 IAnnotationPresentation 负责在标尺上绘制标注,而它是负责在编辑器(也就是 StyledText)里面绘制标注的。我们看到的红色波浪线和矩形框就归它负责。

IDrawingStrategy

这个接口是定义在 AnnotationPainter 内部的,它是绘画工作真正完成的地方。AnnotationPainter 实际上只是调用它来完成绘图操作而已。如果你在 Hierarchy 视图中看一下 IDrawingStrategy,会发现它有很多实现类。那个红色的波浪线就是 SquigglesStrategy 的杰作。

AnnotationPreference

不要以为什么样的标注画成什么样子是规定死了的,实际上它们都是通过 Eclipse 的 Preference 系统来控制的。请打开 Eclipse 的 Preference 设置,找到 General->Editors->Text Editors->Annotations,你会发现原来这里都可以控制。而这些选项就被包装在了 AnnotationPreference 中。如果你想添加一种标注的配置信息,可以通过程序方式,也可以通过 org.eclipse.ui.editors.markerAnnotationSpecification 扩展点。

MarkerAnnotationPreferences

AnnotationPreference 包装的只是一种标注的配置信息,要得到全部的信息,可以通过 MarkerAnnotationPreferences 类。




回页首


实现文本装饰功能

我打算实现一个语法错误检查的功能,如果用户输入的源代码有问题,则把它标记出来。让我们一步步开始这个过程。

SourceViewerDecorationSupport

不得不说和文本装饰相关的概念、接口非常的多,而我列举的只是一些最重要的,还不是全部。这么复杂的功能,要直接写到 SourceViewer 里面去不免显的臃肿,所以 SourceViewer 通过 SourceViewerDecorationSupport 来支持文本装饰功能,避免了把所有代码都塞到一个类里面去。

我在本系列第一部分提过,之所以没有用 Eclipse 标准 editors 扩展点来做演示,是因为 Editor 本身为我们隐藏了一些东西,文本装饰的支持就是其中之一。现在我们手动添加文本装饰的支持,在 ExprViewer 中增加一个 configureDecorationSupport 方法并在 configure 方法中调用它。代码如下:

清单 1. 在 ExprViewer 中使用 SourceViewerDecorationSupport

               
protected void configureDecorationSupport()
{
// create support object
decorationSupport = new SourceViewerDecorationSupport(
  this, null, new DefaultMarkerAnnotationAccess(), ColorManager.getInstance());

// add other annotation preference
MarkerAnnotationPreferences prefs = new MarkerAnnotationPreferences();
MarkerAnnotationPreferences.initializeDefaultValues(
Activator.getDefault().getPreferenceStore());
Iterator<Object> e = prefs.getAnnotationPreferences().iterator();
while(e.hasNext()) {
// add to support
decorationSupport.setAnnotationPreference((AnnotationPreference)e.next());
}

// install support
decorationSupport.install(Activator.getDefault().getPreferenceStore());
}


我没有增加自定义的标注,所以这个方法并不复杂。如果你有自己的标注类型,那么可以用程序方式或者扩展方式定义自己的标注配置信息,然后调用 SourceViewerDecorationSupport 的 setAnnotationPreference 方法将你的配置添加到库中

错误检测支持

下一步再一次和 ANTLR 扯上了关系,如果没有解析器的支持,要知道源代码中的错误恐怕有点困难。幸好我们已经有了很多基础工具,但是仍然有点不够。我稍微修改了一下 ANTLR 的文法,支持把错误保存到一个 Map 中。这样解析完成后,我就可以直接从解析器得到错误列表了。同时 SharedParser 做了少许增强,以便得到最近一次解析时的错误信息。这些修改就不一一列出了,总之我现在具有了得到错误位置与错误信息的能力。

扩展 Annotation

下一步是扩展 Annotation 类,因为 Annotation 类并没有实现 IAnnotationPresentation。我想让错误也在标尺上显示出来,所以要扩展 Annotation。我定义了 ExprAnnotation,它的代码的核心是 paint 方法,作用是在标尺上画一个背景,然后再画一个错误图标。至于画图的具体细节没有必要关心,熟悉 SWT 的 GC 使用方式的话应该不是问题,这里我就略过不提了。

Ruler 支持

提示:注意我们的布局代码也发生了变化,原来是 viewer.getTextWidget().setLayoutData(…), 现在是 viewer.getControl().setLayoutData(…)。因为安装了标尺之后,viewer 内部会对布局做了一些调整,这个时候 StyledText 不再是顶层控件,所以要使用 getControl。如果不了解的话,界面的布局会出乎意料,恐怕一时半会也不知道问题从何而来。

现在的编辑器还没有一个标尺,所以稍微修改一下 JTFDialog,传给 ExprViewer 的构造函数一个 VerticalRuler 实例即可。

触发语法检查

万事具备,只欠东风。现在只要找到一个合适的时机进行语法检查,并安装我们的标注就可以了。文本改变事件是一个选择,也是最简单的选择,所以我新建了一个 SyntaxChecker 类,并把这个类注册为文本事件监听器。让我们看看文本改变时它会做些什么:

清单2. SyntaxChecker

                public void documentChanged(DocumentEvent event) {
// get model
IAnnotationModel model = viewer.getAnnotationModel();
if(model == null || !(model instanceof IAnnotationModelExtension))
return;

// create map contains annotations to be added
Map<Annotation, Position> toBeAdded = new HashMap<Annotation, Position>();

// get annotations to be removed
Annotation[] toBeRemoved = getAnnotations(new String[] {
        "org.eclipse.ui.workbench.texteditor.error"
});

// get document
IDocument doc = event.getDocument();

// parse it
TreeManager.getTree(doc);

// get errors
Map<Token, String> errors = SharedParser.getLastErrors();

// add annotation
for(Token token : errors.keySet()) {
CommonToken ct = (CommonToken)token;
Annotation anno = new ExprAnnotation("org.eclipse.ui.workbench.texteditor.error",
errors.get(token));
Position pos = new Position(
ct.getStartIndex(), ct.getStopIndex() - ct.getStartIndex() + 1);
toBeAdded.put(anno, pos);
}

// replace annotation one time, this provides a better performance
// than remove/add one by one
((IAnnotationModelExtension)model).replaceAnnotations(toBeRemoved, toBeAdded);
}


我只列出了这个最关键的方法。它的基本流程是:

   1. 做一些必要的检查
   2. 得到之前已经存在的错误标注
   3. 解析源代码,得到错误列表
   4. 为每个错误创建一个 ExprAnnotation,并把标注类型设为错误类型
   5. 通过 replaceAnnotations 批量刷新标注

需要解释一下的是第 5 步,为什么要用 replaceAnnotations?主要还是出于性能的考虑,因为你每次添加删除一个标注,都会触发一系列的连锁反应,所以如果你一个个的添加删除,速度会非常慢。replaceAnnotations 解决了这个问题,因为它是一次全部替换。但是这个方法是 IAnnotationModelExtension 接口里面的,为了保险起见,需要先检查类型。当然我们这个例子很简单,并没有显著的性能问题。

效果

下面是运行后的效果,假如我没有输入分号,错误的标注就显示出来了。

图2. 文本装饰效果图
文本装饰效果图




回页首


结束语

让我来指出还是做的不够的地方吧:

   1. 错误检测太过简单,比如不能标注出未声明的变量。要做一个专业的代码编辑器的话,错误检测可不能这样简单。
   2. 语法检查是在文本内容发生变化后立刻触发,当源代码越来越多的时候,这很可能造成性能问题。我们可以从很多方面想办法,比如提高语法解析器的效率,或者减少不必要的语法检查次数,可以考虑用定时器,当用户隔一段时间没有输入动作时再进行语法检查。最好的办法是使用 Reconciler,关于 Reconciler 的概念会在本系列的第 11 部分中提到。
   3. 我没有自定义标注类型,用的是 Eclipse 自带的类型。那么不妨尝试一下定义自己的标注类型,可以通过 org.eclipse.ui.editors.annotationTypes 扩展点。
   4. 我也没有自定义标注的渲染方式,用 IDrawingStrategy 尝试画一个很酷的标注吧。要分两步走,实现 IDrawingStrategy,然后注册你的 IDrawingStrategy。
   5. 我用的是标准的垂直标尺,尝试实现一个自己的标尺,把标注画到上面。
   6. 标注只有界面上的提示,没有任何文字上的信息,用户很难知道到底是什么错误。不过不要担心,这是因为我还没有提到 Text Hover(文本悬浮帮助),以后的文章将完善这个功能。

这些不足的地方留给有兴趣的读者。




回页首


声明

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

相关推荐

Global site tag (gtag.js) - Google Analytics