跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

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

马 若劼, 软件工程师, IBM 中国软件开发中心
马若劼是 Lotus Forms 部门的一位软件工程师,主要从事电子表单技术的研发工作。他在 Eclipse 和 Java 方面有多年研发经验,同时也是国内著名开源项目 LumaQQ 的创立者

简介: Text Folding(文本折叠)可以把文本区域的某块内容展开或者收起,这样可以隐藏那些不想看到的内容,避免注意力被分散。本文介绍文本折叠的相关概念并实现一个简单的例子。

查看本系列更多内容

发布日期: 2008 年 5 月 29 日
级别: 中级
访问情况 : 1470 次浏览
评论: 


Text Folding

Text Folding(文本折叠)其实在 Eclipse 2.1 的时候就已经出现了苗头,在 Eclipse 3.0 之后,文本折叠的架构被重新设计了,开始逐渐变的实用。我们先来了解一下JTF为了文本折叠做了哪些工作。

ProjectionViewer

SourceViewer 本身不支持文本折叠,所以 JTF 添加了一个 ProjectionViewer 专门支持文本折叠,它是 SourceViewer 的子类。

主从文档结构

JTF 是不可能神奇的知道哪些文本是可以折叠的,这些事情都得有人告诉它。在有文本折叠的情况下,我们能看到的只是一部分文字,但是创建的 IDocument 实例里面是包含了所有文字的,怎么才能知道哪些不该显示?所以 JTF 引入了一个 Master/Slave(主/从)文档架构,主文档就是包含了所有内容的文档,而从文档则只包含用户能看得见的内容,从文档的实现类是 ProjectionDocument。既然存在两个文档,那么就带了同步的问题,所以又引入了 ProjectionDocumentManager 来维护主从文档的同步。需要注意的是,ProjectionDocument 不能直接创建,必须通过 ProjectionDocumentManager 的 createSlaveDocument 方法来创建。

好消息是:我们在实现文本折叠时不用关心什么主从文档架构,因为这些都被JTF包装好了,在上层,我们看到的只有一个文档。

ProjectionAnnotation

在 Java 编辑器中可以看到,可以折叠的地方旁边都有个小图标,它显示了当前的折叠状态,并且可以点击它改变折叠状态。从界面上看,你应该能想到:这不就是标注吗?没错,确实是标注,只不过这个标注是显示在另外一条标尺上的,和显示错误标注的标尺不同罢了。文本折叠标注的实现类是 ProjectionAnnotation,在实现文本折叠的过程中,很重要的一部分工作就是及时刷新 ProjectionAnnotation。

我在本系列第五部分讲述文本标注的时候提到过许多和标注相关的概念,它们也同样适用于文本折叠标注。比如,可以通过 IDrawingStrategy 来自定义标注的外观,缺省情况下它是一个省略号。如下图所示:


图 1. 缺省的文本折叠标注外观
缺省的文本折叠标注外观

Summarizable Annotation

文本折叠还带来了另外一个问题:如果被折叠的区域内有其它标注,在文本收起的时候该如何处理?为了解决这个问题,JTF 引入了 Summarizable Annotation 的概念。Summarizable Annotation 将在文本收起的时候保持可见。如下面所示:


图 2. 展开时
展开时

图 3. 收起时
收起时

从图 3 中看到,文本收起的时候,这个错误标注被显示在了折叠处。你可以随意指定哪种标注是 Summarizable 的。

Projection Hover

为了方便用户查看收起的文本内容,文本折叠也支持悬浮提示,这又是信息显示控件的一个应用。以前的文章反复提到过类似的应用,这里不再赘述。

ProjectionSupport

ProjectionSupport 是用来管理文本折叠的一些可配置功能的,比如哪些标注是 Summarizable 的。它必须安装到 ProjectionViewer 或者子类上,才能真正的提供文本折叠的支持。


实现文本折叠

本文计划为每条超过一行的语句加上文本折叠功能,经过了上面基本概念的介绍之后,让我们看看如何一步步的达到预定的目标。

底层支持

既然我要为每个超过一行的语句提供文本折叠功能,在底层我必须能够得到每一条语句的范围。方法很多,我采用的是遍历符号列表,相应的方法已经加入到了 TokenList 中,方法名叫 getLineRanges。

使用 ProjectionViewer

接着,要让 ExprViewer 从 ProjectionViewer 继承,而不是 SourceViewer,同时,需要安装 ProjectionSupport。在 ExprViewer 的 configure 方法中,我创建了 ProjectionSupport 并安装:


清单 1. 在 ExprViewer 中使用 ProjectionSupport
                
// projection support
ProjectionSupport projectionSupport = new ProjectionSupport(
	this, new DefaultMarkerAnnotationAccess(), ColorManager.getInstance());
projectionSupport.
	addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error");
projectionSupport.
	addSummarizableAnnotationType("jtf.tutorial.annotation.undeclared.variable");
projectionSupport.install();

因为我的例子中有两种标注,因此我把它们都指定为 Summarizable 的。

标尺的变化

之前已经指出,文本折叠的标注是显示在另外一个标尺上的。所以至少需要两个标尺,一个显示错误标注,一个显示文本折叠标注。VerticalRuler 本身并不具备嵌套另一个标尺的能力,我们需要换用 CompositeRuler。CompositeRuler 中可以包含多列,这样就可以支持多个标尺了。修改的代码位于 JTFDialog 中:


清单 2. 添加新的 Ruler 列
                
// create viewer
CompositeRuler ruler = new CompositeRuler();
AnnotationRulerColumn column = new AnnotationRulerColumn(
	12, new DefaultMarkerAnnotationAccess());
column.addAnnotationType("org.eclipse.ui.workbench.texteditor.error");
column.addAnnotationType("jtf.tutorial.annotation.undeclared.variable");
ruler.addDecorator(0, column);

注意如果用了 CompositeRuler,添加的实际上是 IVerticalRulerColumn 的实现了。

在适当的时候更新文本折叠标注

最后一步就是找一个合适的时机来更新文本折叠标注,第一时间你可能会想到添加一个 IDocumentListener,这样文本每次变化的时候就可以更新文本折叠标注。这是可以的,不过我要用另外一种方式。看看 SourceViewerConfiguration,它有一个 getReconciler 方法,返回类型是 IReconciler 接口。Reconciler 翻译过来是“调解人”,确实很是费解,你可以做如下理解:现在有一段文字,在你没有编辑它的时候,可以认为文字处于一种稳定的状态;当你开始编辑的时候,由于你不断的输入新字符,因此文字的稳定状态被打破了,各种建立好的关系被破坏,于是就出现了一个调解人来使破坏后的文字重回稳定状态。所以,Reconciler 会在你编辑文字之后被调用,但不是立刻!这样可以避免不必要的调用,提高性能。缺点是你必须等那么一小会儿才能看到结果。在本系列第二部分中,已经提到了 PresentationReconciler,虽然它并不是实现了 IReconciler 接口,但是其概念是一致的。

更新文本折叠标注的事情很适合用 Reconciler 来实现,所以我覆盖了 ExprConfiguration 的 getReconciler 方法:


清单 3. 使用 Reconciler
                
public IReconciler getReconciler(ISourceViewer sourceViewer) {
  return new MonoReconciler(new ExprReconcilingStrategy((ExprViewer)sourceViewer), 
                    false);
}

我返回了一个 MonoReconciler,这是 JTF 缺省提供的。为了创建 MonoReconciler,还必须提供一个 IReconcilingStrategy 接口的实现,它是事情真正完成的地方,下面是 ExprReconcilingStrategy 的实现:


清单 4. ExprReconcilingStrategy 实现了 IReconcilingStrategy 接口
                
   public class ExprReconcilingStrategy implements IReconcilingStrategy {
	private IDocument doc;
	private ExprViewer viewer;
	
	public ExprReconcilingStrategy(ExprViewer viewer) {
		this.viewer = viewer;
	}
	
	private void internalReconcile() {
		TokenList list = TokenManager.getTokenList(doc);
		viewer.updateProjectionAnnotations(list.getLineRanges());
	}

	public void reconcile(IRegion partition) {
		internalReconcile();
	}

	public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
		internalReconcile();
	}

	public void setDocument(IDocument document) {
		doc = document;
	}
}

关键的代码在 internalReconcile 中,只是重新得到每条语句的范围,然后刷新文本折叠标注,updateProjectionAnnotations 方法的实现是:


清单 5. updateProjectionAnnotations 的实现
                
   
   public void updateProjectionAnnotations(List<Position> positions) {
	// create projection annotation
	Annotation[] annotations = new Annotation[positions.size()];
	Map<Annotation, Position> newAnnotations = new HashMap<Annotation, Position>();
	for(int i = 0; i > positions.size(); i++){
		ProjectionAnnotation annotation = new ProjectionAnnotation();
		newAnnotations.put(annotation, positions.get(i));
		annotations[i] = annotation;
	}
	
	// get old annotation
	Map<Position, Annotation> oldAnnotations = new HashMap<Position, Annotation>();
	ProjectionAnnotationModel model = getProjectionAnnotationModel();
	Iterator iter = model.getAnnotationIterator();
	while(iter.hasNext()) {
		Annotation anno = (Annotation)iter.next();
		Position pos = model.getPosition(anno);
		oldAnnotations.put(pos, anno);
	}
	
	// remove duplicated
	List<Annotation> newKey = new ArrayList<Annotation>(newAnnotations.keySet());
	for(Annotation anno : newKey) {
		Position pos = newAnnotations.get(anno);
		if(oldAnnotations.containsKey(pos)) {
			oldAnnotations.remove(pos);
			newAnnotations.remove(anno);
		}
	}
	
	// replace annotations
	getProjectionAnnotationModel().modifyAnnotations(
		oldAnnotations.values().toArray(new Annotation[0]), newAnnotations, null);
}

看上去有点长,其实流程很简单:

  1. 为每条超过一行的语句创建一个新标注
  2. 得到所有的老标注
  3. 比较新老标注,去掉重复的
  4. 使用 modifyAnnotations 方法批量替换,不要一个一个替换,那样太慢

效果

现在运行一下本文的例子,当你输入一条超过一行的语句时,你会发现在等待了大概一秒钟之后,文本折叠标注就出现了。如图:


图 4. 语句超过一行时的文本折叠支持
语句超过一行时的文本折叠支持

结束语

我已经介绍完了 JTF 的所有关键特性,并从头到尾实现了一个代码编辑器,虽然它很简陋,但是它具有了 Java 编辑器的大部分功能!本系列的例子只是一个起点,它不是没有 bug,它也离完美距离甚远,而所有的缺点都是留给读者的课题。这也许不是本系列的最后一篇,因为 JTF 在不断发展,Eclipse 也在不断发展,所以本系列也有可能不断发展,希望本系列能成为你开发过程中的踏脚石,谢谢各位一直阅读到这里,再见。


声明

本文仅代表作者的个人观点,不代表 IBM 的立场。



下载

描述名字大小下载方法
第十一小节示例代码jtf.tutorial.part11.zip1140KBHTTP

关于下载方法的信息


参考资料

关于作者

马若劼是 Lotus Forms 部门的一位软件工程师,主要从事电子表单技术的研发工作。他在 Eclipse 和 Java 方面有多年研发经验,同时也是国内著名开源项目 LumaQQ 的创立者

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=310818
ArticleTitle=基于 JFace Text Framework 构建全功能代码编辑器: 第 11 部分:Text Folding
publish-date=05292008
author1-email=maruojie@cn.ibm.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。