扩展 NetBeans IDE 6.0 中的 C/C++ 编辑器以提供标记实例的高亮显示
由 Sergey Grinev 贡献和维护
2007 年 11 月 [修订版本号:V6.0-1]
本文档适用于 NetBeans IDE 6.0 发行版
翻译:Shinzey(NetBeans 中文社区成员)
在本教程中,您将看到怎样扩展 C/C++ 编辑器的功能。我们用 NetBeans 编辑器的高亮 API 来创建一个高亮显示代码的 NetBeans 模块。
我们还将用 C/C++ 引用 API 来从语言模型获取信息。
要获取更多有关在 NetBeans IDE 中使用 C/C++ 应用程序的信息,请参见 NetBeans 网站上的C/C++ 应用程序学习课程页。
教程需求
在继续前,请确保您重新检查了本节的需求。
先决条件
本教程假设您已经有一些使用 IDE 的基本知识和 Java 编程经验。
本教程所需的软件
在开始前,您需要安装 NetBeans 6.0。您将同时需要 C/C++ 和 Java SE 支持,所以最佳选择是选择“下载全部”选项并在安装时排除 Base IDE、Java SE 和 C/C++ 包之外的所有模块。
准备项目
对本教程,我们需要两个项目。一个是管理我们的插件的源代码的 NetBeans 模块。另一个是用来测试它的 C++ 项目。
创建 NetBeans 插件模块
- 选择“文件”>“新建项目”。在“新建项目”向导中,选择“类别”下的“NetBeans 模块”和“项目”下的“模块”。单击“下一步”。
- 在“名称和位置”页,在“项目名称”字段中键入 MarkOccurrences,并把“项目位置”设置到磁盘上一个适当的文件夹里。请选中“独立模块”和“设置为主项目”,如果没有选中的话。单击“下一步”。
- 在“基本模块配置”页,在“代码名称基”字段中键入 org.netbeans.modules.markoccurrences。单击“完成”。
- 在本项目中,我们需要一些依赖关系。在“项目”窗口,右键单击“库”节点并在“添加模块依赖关系”对话框中选择“库”,然后添加屏幕快照里列出的各个库。C/C++ 模块 API 正处于开发中,所以您需要在对话框中选择“显示非-API 模块”,以便在模块列表中看到它们。

- 右键单击各个 C/C++ 模块,选择“编辑”,然后选择“实现版本”。
创建测试应用程序
- 选择“文件”>“新建项目”。选择“样例”>“C/C++”>“C/C++”类别下的 Args 项目。单击“下一步”。
- 在“项目名称和位置”页,把“项目位置”设置到磁盘上一个适当的文件夹里。单击“完成”。
- 现已创建了 Args_1 项目。在编辑器中打开 arg.c 源文件。我们将使用这个文件来测试我们的模块。
页首
创建高亮基础结构
现在我们将使用 NetBeans API 来向 C/C++ 编辑器添加高亮显示。
创建高亮提供者
- 在标记实例项目的“源包”节点中右键单击包 org.netbeans.modules.markoccurrences,然后选择“新建”>“Java 类”。
- 将新类命名为 MarkOccurrencesHighlighter 并单击“完成”。
- 用下面的代码替换新类中的代码:
package org.netbeans.modules.markoccurrences;
import java.awt.Color;
import java.lang.ref.WeakReference;
import javax.swing.JEditorPane;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.Document;
import javax.swing.text.StyleConstants;
import org.netbeans.api.editor.settings.AttributesUtilities;
import org.netbeans.modules.cnd.modelutil.CsmUtilities;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.openide.cookies.EditorCookie;
import org.openide.loaders.DataObject;
public class MarkOccurrencesHighlighter implements CaretListener {
private static final AttributeSet defaultColors = AttributesUtilities.createImmutable(StyleConstants.Background, new Color(236, 235, 163));
public void caretUpdate(CaretEvent e) {
bag.clear();
bag.addHighlight(0, 5, defaultColors);
}
private final WeakReference<Document> weakDoc;
public MarkOccurrencesHighlighter(Document doc) {
bag = new OffsetsBag(doc);
weakDoc = new WeakReference<Document>((Document) doc);
DataObject dobj = NbEditorUtilities.getDataObject(weakDoc.get());
JEditorPane[] panes = CsmUtilities.getOpenedPanesInEQ(dobj.getCookie(EditorCookie.class));
if (panes != null && panes.length > 0) {
panes[0].addCaretListener(this);
}
}
private final OffsetsBag bag;
public OffsetsBag getHighlightsBag() {
return bag;
}
}
本类尚未提供任何智能功能。它只是向光标事件注册了一个侦听器,并高亮显示文档开头的符号。
创建并注册 HighlightsLayerFactory
现在我们创建 HighlightsLayerFactory,来让 NetBeans 知道我们的高亮显示提供者。
- 向项目源文件添加一个新的 Java 类,并将其命名为 MarkOccurrencesHighlightsLayerFactory。
- 用下面的代码替换新类中的代码:
package org.netbeans.modules.markoccurrences;
import javax.swing.text.Document;
import org.netbeans.spi.editor.highlighting.HighlightsLayer;
import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
import org.netbeans.spi.editor.highlighting.ZOrder;
public class MarkOccurrencesHighlightsLayerFactory implements HighlightsLayerFactory {
public static MarkOccurrencesHighlighter getMarkOccurrencesHighlighter(Document doc) {
MarkOccurrencesHighlighter highlighter = (MarkOccurrencesHighlighter) doc.getProperty(MarkOccurrencesHighlighter.class);
if (highlighter == null) {
doc.putProperty(MarkOccurrencesHighlighter.class, highlighter = new MarkOccurrencesHighlighter(doc));
}
return highlighter;
}
public HighlightsLayer[] createLayers(Context context) {
return new HighlightsLayer[] {
HighlightsLayer.create(
MarkOccurrencesHighlighter.class.getName(),
ZOrder.CARET_RACK.forPosition(2000),
true,
getMarkOccurrencesHighlighter(context.getDocument()).getHighlightsBag())
};
}
}
- 我们已经提供了一个 HighlightsLayerFactory 的实现,它用 MarkOccurrencesHighlighte类提供的数据创建了一个高亮显示层。现在我们需要在 layer.xml 中注册这个类。打开 org.netbeans.modules.markoccurrences 包中的 layer.xml,并将其内容更改为以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
<folder name="Editors">
<folder name="text">
<folder name="x-c++">
<file name="org-netbeans-modules-markoccurrences-MarkOccurrencesHighlightsLayerFactory.instance" />
</folder>
<folder name="x-c">
<file name="org-netbeans-modules-markoccurrences-MarkOccurrencesHighlightsLayerFactory.instance" />
</folder>
</folder>
</folder>
</filesystem>
现在我们已经准备好第一次运行我们的高亮显示器了。
- 生成项目。
- 项目成功生成后,运行它。
- 打开我们在前一节中创建的 Args 项目。
- 在编辑器中打开 args.c 文件,并单击文件中的任一位置。高亮显示看起来就会像下面的例子一样:

太棒了。我们的高亮显示工作了。现在我们来教它变得更有用。
页首
从 C/C++ 语言模型收集信息
- 在 MarkOccurrencesHighlighter.java 类中,删除我们粗略实现的 caretUpdate() ,并添加以下代码:
private WeakReference<CsmFile> weakFile;
public void caretUpdate(CaretEvent e) {
bag.clear();
CsmFile file = getCsmFile();
if (file != null) {
CsmReference ref = CsmReferenceResolver.getDefault().findReference(file, e.getDot());
if (ref != null && ref.getReferencedObject() != null) {
Collection<CsmReference> out = CsmReferenceRepository.getDefault().getReferences(ref.getReferencedObject(), file, true);
for (CsmReference csmReference : out) {
bag.addHighlight(csmReference.getStartOffset(), csmReference.getEndOffset(), defaultColors);
}
}
}
}
private CsmFile getCsmFile() {
if (weakFile == null || weakFile.get() == null) {
if (weakDoc == null || weakDoc.get() == null) {
return null;
}
DataObject dobj = NbEditorUtilities.getDataObject(weakDoc.get());
CsmFile file = CsmUtilities.getCsmFile(dobj, false);
if (file != null) {
weakFile = new WeakReference<CsmFile>(file);
} else {
return null;
}
}
return weakFile.get();
}
在 caretUpdate() 方法中,我们使用 CsmReferenceResolver 来查找光标下对语言实体的引用。如果存在有效的实体,我们就向 CsmReferenceRepository 询问文件中所有相同实体的出现位置并存储它们的偏移量。getCsmFile() 方法是一段衔接代码,用来确保我们不保留任何语言模型数据。
- 按下 Ctrl-Shift-I 以修复导入(或者右键单击,然后选择“修复导入”)。
- 生成并运行项目。
- 如果把鼠标放到 main() 的 argc 参数上,您就将看到如下的高亮显示:

- 单击文件的不同位置以查看标记实例是怎样工作的。您也许想尝试更复杂的项目中以查看它怎样与类、宏等等协同工作。
页首
提高性能
对静态文本来说,我们当前的代码足够好了,但将在编辑文件的过程中产生严重的延迟。出现延迟的原因是我们每按下一个键,就立即开始搜索。要解决这个问题,我们将推迟任务以分析代码,如果鼠标位置在任务开始前改变了,我们就取消并重新计划任务。
- 在 MarkOccurrencesHighlighter.java 类中,把先前的 caretUpdate() 实现更改为以下代码:
public void caretUpdate(CaretEvent e) {
bag.clear();
lastCaret = e.getDot();
scheduleUpdate();
}
private int lastCaret;
private RequestProcessor.Task task = null;
private final static int DELAY = 1000;
public void scheduleUpdate() {
if (task==null) {
task = RequestProcessor.getDefault().create(new Runnable() {
public void run() {
CsmFile file = getCsmFile();
if (file != null) {
CsmReference ref = CsmReferenceResolver.getDefault().findReference(file, lastCaret);
if (ref!=null && ref.getReferencedObject()!=null) {
Collection<CsmReference> out = CsmReferenceRepository.getDefault().getReferences(ref.getReferencedObject(), file, true);
for (CsmReference csmReference : out) {
bag.addHighlight(csmReference.getStartOffset(), csmReference.getEndOffset(), defaultColors);
}
}
}
}
}, true);
task.setPriority(Thread.MIN_PRIORITY);
}
task.cancel();
task.schedule(DELAY);
}
在这个代码块中,我们使用 org.openide.util.RequestProcessor 来处理代码分析任务。如果得到几个光标更新,我们就取消先前的任务,记住鼠标位置,然后在稍后的时间重新计划任务。
- 修复导入,然后生成并运行项目。
- 现在您将注意到,在键入大块代码时没有延迟了。
页首
下载