使用
Dynamic Languages Toolkit (DLTK) 创建自己的 IDE
Eclipse 为构建编译性语言(比如 C)和 Java? 编程语言的工具提供了大量特性,但对脚本语言(比如
Perl、Python 和 PHP)的支持却很少。庆幸的是,Eclipse Dynamic Languages
Toolkit (DLTK) 为这些语言以及类似的语言提供了支持。通过本文学习构建基于 DLTK 的 IDE
的步骤,并且探索每个步骤的样例代码。
开始之前
关于本教程
本教程显示如何通过 Eclipse DLTK 为脚本语言构建开发工具,并且特别解释了如何在基于插件的项目中实现语法着色、用户首选项和解释器集成。
目标
这个教程分步解释了如何构建基于 DLTK 的开发环境。这个讨论通过关注基于
Octave 数值计算语言的实际插件项目来介绍 DLTK。讨论的主题包括:
创建插件项目。
配置编辑器和 DLTK 文本工具。
添加在文本编辑器中控制语法颜色的类。
启用用户首选项。
将脚本解释器集成到开发环境。
添加定制控制台,以在工作台和解释器之间进行通信。
先决条件
本教程是为熟悉 Eclipse 并对构建动态语言(比如 PHP、Tcl、Ruby
和 Python)工具感兴趣的 Java 开发人员编写的。它要求读者基本理解插件和基于 Eclipse
的开发工具。
系统需求
为了构建本教程中的示例项目,必须具备安装有 Eclipse(V3.4 或更新版本)的计算机,以及最新的
Java Runtime Environment (JRE)。此外,必须安装较新版本的 DLTK,我们将在
“安装 DLTK” 小节中对此进行说明(参见 参考资料 获得 Eclipse 下载链接)。
DLTK 和 DLTK 编辑器介绍
Eclipse 提供大量用于构建开发工具的特性,但这些特性不易理解和使用。在这些类中,大部分都需大量的配置才能正确工作,比如
TextEditor。相反,DLTK 提供一个预打包的开发环境,只需进行一些小的调整就能工作。用 DLTK
构建开发工具就像用各种混合原料烤面包一样:结果不是很有创意,但是可以以最少的工作获得最佳的质量和可靠性。
DLTK 和 Eclipse 的第二个区别就是支持的语言。Eclipse
的构建特性基于编译性语言,比如 Java、C 和 C++。而 DLTK 则偏向动态语言,其中包括脚本语言,比如
PHP、Ruby 和 Tcl 等。在理想的情况下,这些语言的开发工具应该能够轻松地集成脚本解释器、逐行进行命令计算,以及在控制台中输入命令。与
Eclipse 不同,DLTK 支持所有这些特性。
安装 DLTK
如果您还没有安装 DLTK,请按以下步骤操作:
打开 Eclipse,然后单击 Help > Software Updates。
单击 Available Software 选项卡。
单击 Add Site,然后输入 http://download.eclipse.org/technology/dltk/updates—
DLTK 更新站点的 URL。
选择 DLTK 复选框,然后单击 Install。
基于 DLTK 的插件项目:Octave IDE
本教程不仅仅是讨论 DLTK 及其特性。它将教您如何逐步创建 Octave
脚本语言的基于 DLTK 的集成开发环境(IDE)。Octave 是由 GNU Public License
(GPL) 授权的工具集,用于执行数学计算,尤其适合涉及矩阵的情况。Octave 命令易于理解,并且与流行的专有工具
Matlab 非常相似。您可以从 下载 小节下载它的示例代码。
将要构建的 Octave IDE 不如 Eclipse 那般完美,但是它可以创建
Octave 脚本(*.m 文件),在多功能文本编辑器中编辑脚本,并且单击一个按钮就能运行 Octave
解释器,然后可以查看显示在 IDE 控制台中的结果。图 1 显示了编辑器和控制台。
图 1. Octave IDE
本教程通过 4 个步骤解释构建 Octave IDE 的过程:
创建 Octave 编辑器并添加特性。
存储并响应用户首选项。
集成 Octave 解释器。
在控制台中显示解释器的输出。
首先要处理的是 Octave 脚本编辑器。这个主题有很多需要理解的地方,但在编写代码之前,我们先退一步,看看
DLTK 编辑器是如何工作的。这些讨论基本适用于所有 Eclipse 文本编辑器。
DLTK 文本编辑器的高级介绍
从最基础的级别讲,DLTK 文本编辑器仅是一个带有许多额外特性的普通 Standard
Widget Toolkit (SWT) StyledText 部件。这些特性分为两类:
响应用户事件
根据用户的击键更改编辑器显示
文件输入/输出(I/O)
在 IDE 和用户系统上的文件之间传输字符数据
在 DLTK 编辑器中,由 ScriptSourceViewer 对象负责处理事件。这个对象是由
ScriptEditor 创建的,它包围 StyledText 部件,并且管理其对用户击键的响应。这个响应通常涉及到修改部件的显示或更新编辑器的内部数据。数据内嵌在
IDocument 对象中,这个对象不仅存储文本,并且还存储与行号、位置和区域(即分区)相关的信息。
ScriptSourceViewer 通过访问称为 SourceModuleDocumentProvider
的对象更新 IDocument。除了能够访问 IDocument 之外,这个对象还处理文件操作。DLTK
替您处理所有编辑器文件交互,但如果需要进一步了解,可以阅读关于该对象的超类 TextFileDocumentProvider
的文档:。
如果熟悉 Model-View-Controller (MVC) 模式,ScriptSourceViewer、IDocument
和 StyledText 部件之间的关系就会很清晰。IDocument 用作编辑器的 Model(模型)部分,包含与其表示无关的数据。StyledText
部件用作编辑器的 View(视图)部分,而 ScriptSourceViewer 则用作 Controller(控制器)部分,用于管理
Model、View 和用户之间的通信。图 2 显示了这一关系。
图 2. DLTK 编辑器的基本组成部分
如果能理解上面的解释内容和图 2,那么就很容易理解接下来的技术讨论。下一小节将开始创建
Octave 插件。
开始 DLTK 项目
为简单起见,本教程将所有 Octave IDE 特性集成到一个简单的插件中。总体来说,就是用多个插件实现一个特性:这些插件分别包含核心类、UI
类、调试类等等。但是本教程的代码只需要一个插件,因此运行 Eclipse 时,单击 File >
New > Project,选择 Plug-in Project 选项,然后创建一个新的插件项目。在示例代码中,项目名为
org.dworks.octaveide,并且插件类为 OctavePlugin(但是您可以选择自己喜欢的名称)。
配置 Octave 插件
在创建插件之后,下一步便是将一些扩展添加到 Octave IDE 插件的配置文件中:plugin.xml(需要单击
File > New > File 来创建该文件)。这些扩展告诉 Eclipse 这个插件如何在工作台中工作。首先,plugin.xml
文件需要两个简单的扩展:一个用于识别 Octave 编辑器,另一个用于识别需要编辑的文件的类型。清单 1
给出了第一个扩展,它定义 Octave 编辑器的特征。
清单 1. Octave IDE 编辑器扩展
<extension point="org.eclipse.ui.editors"> <editor id="org.dworks.octaveide.editor.OctaveEditor" class="org.dworks.octaveide.editor.OctaveEditor" contributorClass="org.dworks.octaveide.actions.OctaveActionContributor" default="true" icon="icons/oct.gif" name="%OctaveEditor.name"> <contentTypeBinding contentTypeId="org.dworks.octaveide.octaveContentType"> </contentTypeBinding> </editor> </extension> |
这个扩展包含几个非常重要的属性。class 属性定义构成编辑器的基类:OctaveEditor。随后您将创建这个类。contributorClass
属性识别为编辑器提供行为的类。default 属性要求 OctaveEditor 应该是相关文件的默认编辑器。name
属性识别将在编辑器中显示的文本。就像在 plugin.properties 文件中定义的一样,OctaveEditor.name
属性对应 “Octave Script Editor”。
这个扩展中最后需要解释的是 contentTypeBinding。Eclipse 包含一个内容类型注册表,用来表示文件格式和命名约定。通过将
contentTypeBinding 元素添加到编辑器的扩展,就可以将编辑器与文件后缀、文件名,甚至文件别名关联起来。但您的
Octave 编辑器只需要关注 *.m 文件,因此 org.eclipse.core.runtime.contentTypes
扩展很简单。清单 2 给出了第二个扩展。
清单 2. Octave IDE ContentType 扩展
<extension point="org.eclipse.core.runtime.contentTypes"> <content-type id="org.dworks.octaveide.octaveContentType" base-type="org.eclipse.core.runtime.text" file-extensions="m" name="%octaveContentType" priority="high"> </content-type> </extension> |
许多属性都可以用来定制内容类型,但是这个声明将 org.dworks.octaveide.octaveContentType
内容类型识别为基于文本的,并且与文件后缀 .m 相关联。priority 属性要求如果一个文件与多个内容类型相关联,某个类型必须具有很高的优先级。
创建 DLTK 语言工具箱类
使用 DLTK 的最大好处是定制 IDE 的工作方式很简单。这个定制可以通过 DLTK 的语言工具箱接口来实现(IDLTKCoreLanguageToolkit、IDLTKDebugUILanguageToolkit
和 IDLTKUILanguageToolkit 等)。实现这些接口之后,它们就给出配置 IDE 的各个方面的方法,比如解析、颜色使用、用户首选项和解释器的访问。
本教程主要关注 DLTK UI,因此仅需关心 IDLTKUILanguageToolkit。这个接口中的方法执行两项功能之一:它们或者返回处理编辑器配置的类,或者提供在
plugin.xml 中声明的配置对象的 ID。现在列出这些方法中的 7 个。
getEditorId(Object elementID)
返回编辑器的 ID
getTextTools()
返回 ScriptTextTools 对象,它定制如何在编辑器中显示文本
createSourceViewerConfiguration()
返回 ScriptSourceViewerConfiguration 对象,它配置编辑器的源代码查看器的操作
getPreferenceStore()
返回 IPreferenceStore 对象,它包含用户设置
getEditorPreferencePages()
返回与编辑器设置相关的首选项页面的 ID
getInterpreterPreferencePages()
返回与脚本解释器相关的首选项页面的 ID
getIntepreterId()
返回脚本解释器的 ID
在示例代码中,OctaveUILanguageToolkit 类使用对象和标识符补充了这些方法。在这个工具箱中,与列出的前两个方法对应的两个对象是
OctaveEditor 和 OctaveTextTools。
创建 Octave 编辑器和 Octave 文本工具
现在编辑器扩展已经配置完成,接下来首先需要做的就是创建 OctaveEditor。由于有了 DLTK,所以不需要编写很多代码:DLTK
的 ScriptEditor 类(OctaveEditor 的超类)帮助您处理大部分编辑器操作。您仅需一些配置方法。在
OctaveEditor 中,最重要的方法是 getTextTools(),它返回一个 ScriptTextTools
对象。
ScriptTextTools 类十分方便。这个中心类访问其他几个提供编辑器特性的类。通过编写 ScriptTextTools
的子类,编辑器能够定制这些特性的执行方式。Octave 编辑器创建一个称为 OctaveTextTools
的子类,它能够访问两个重要的对象:
OctavePartitionScanner
读取编辑器文本并决定分区界限
OctaveSourceViewerConfiguration
配置 ScriptSourceViewer 及其响应用户生成的事件的方式
这些对象在实现语法着色 时尤为重要 — 即根据文本的语法功能改变代码中文本的颜色。下一个主题将更加详细地解释这个特性。
DLTK 编辑器中的语法着色
语法着色是任何专业源代码编辑器最引人注目的方面之一。颜色不仅让?码的阅读更加方便,并且使输入错误更明显。图
3 显示如何通过不同的颜色将注释、数字、关键字和字符串与普通的代码区分开来。
图 3. Octave IDE 中的语法着色
在 Octave 编辑器中配置语法着色
DLTK 通过 4 个步骤处理语法着色:
ScriptSourceViewer 通过向文档的分区器(partitioner)发出通知来响应击键。
分区器根据规则对文档的文本分区。
分区结束之后,查看器通知显示调解器(presentation reconciler)分析修改后的分区。
调解器调用销毁器/修复器(damager/repairer)来分析受影响的区域,然后应用语法着色功能。
在这种方式下,编辑器使用分治(divide-and-conquer)策略来为文本添加颜色。首先,它将文档分为多个区域。然后,分析修改后的区域并确定需要更新语法颜色的地方。接下来的两个小节将更加深入地解释这个过程,并且演示如何在
Octave IDE 中实现语法着色。
文档分区
除了文本之外,IDocument 还存储一组 Position,它们用于确定文本内部的区域的界限。这些区域不仅使语法着色成为可能(注释和代码采用不同的颜色),并且使您能够在文本的不同区域上使用不同的工具。例如,不让脚本解析器分析注释行。通过分区,可以确保解析器仅分析包含实际代码的区域。
由两个中心对象来完成文档分区:一个实现 IDocumentPartitioner,另一个实现 IPartitionTokenScanner。分区器将文本提交给扫描器进行分析,然后生成
IToken。分区器使用这些标记(token)在文档的内部设置 Position。
我们希望 OctaveEditor 能够在用户打开 *.m 时自动初始化分区器和扫描器。为此,必须使用
org.eclipse.core.filebuffers.documentSetup 扩展点的一个扩展在
plugin.xml 中显式地定义文档的配置。这个扩展在文档初始化期间识别用于配置文档的类。清单 3 给出了为
Octave IDE 插件定义的扩展。
清单 3. 配置文档
<extension point="org.eclipse.core.filebuffers.documentSetup"> id="org.dworks.octaveide.editor.OctaveDocumentSetup" name="%documentSetupName" <participant extensions="m" class="org.dworks.octaveide.editor.OctaveDocumentSetup"> </participant> </extension> |
OctaveDocumentSetup 调用 DLTK ScriptTextTools 对象初始化 *.m
文档。默认情况下,这将创建一个用作文档分区器的 FastPartitioner。
每个 DLTK 编辑器都必须创建自己的 IPartitionTokenScanner 对象来读取字符和生成与分区对应的标记。例如,如果想要为以
‘/*’ 开始并以 ‘*/’ 结束的文本创建一个分区,则要配置扫描器,让它在遇到这种形式的文本时生成正确的标记。Octave
插件将文本分为 3 个区域:
注释
由 OCTAVE_COMMENT 识别,它等效于 "__octave_comment__"
字符串
由 OCTAVE_STRING 识别,它等效于 "__octave_string__"
普通脚本代码
由 IDocument.DEFAULT_CONTENT_TYPE 识别,它等效于 "__dftl_partition_content_type"
这些分区是在 IOctavePartititons 接口中定义的。这些分区用于初始化 FastPartitioner,后者将分区发送给分区扫描器。
可以使用一组逻辑规则识别 Octave 编辑器中的分区,因此 OctavePartitionScanner
将扩展 RuleBasedPartitionScanner 类。这个类中最重要的方法是 setPartitionRules,它接受一个
IPredicateRule 对象数组。规则的目标是将文本的各个部分转换成 IToken。从头编写规则是很困难的,庆幸的是
Eclipse 提供了 IPredicateRule 接口的许多有用实现。Octave IDE 使用其中两个来识别分区:SingleLineRule
和 MultiLineRule。这两个类具有类似的构造器,它们由以下信息(至少)初始化:
1.开始分区的字符
2.结束分区的字符
3.对应于被识别分区的 IToken
例如,假设您希望创建这样一个规则,它在发现以单引号(')开始和结束的文本时生成一个字符串标记。这可以使用以下代码实现:
IToken stringToken = new Token(OctavePartitions.OCTAVE_STRING); new SingleLineRule("'", "'", stringToken, '\\'); |
清单 4 给出了 OctavePartitionScanner 用于在 Octave 脚本中检测分区的完整代码。在遇到前面有一个百分号(%)并且后面跟着一个新行的文本时,Octave
编辑器将创建注释分区。字符串分区由单引号确定界限。
清单 4. Octave IDE 分区扫描器
// Declare the different tokens IToken string = new Token(IOctavePartitions.OCTAVE_STRING); IToken comment = new Token(IOctavePartitions.OCTAVE_COMMENT);
// Create the list of rules that produce tokens
List<IPredicateRule> rules = new ArrayList<IPredicateRule>();
rules.add(new SingleLineRule("%", "\n",
comment));
rules.add(new SingleLineRule("'", "'",
string));
IPredicateRule[] result = new IPredicateRule[rules.size()];
rules.toArray(result);
setPredicateRules(result); |
创建 OctavePartitionScanner 并且初始化其规则之后,编辑器分区的配置就完成了。下一步就是告诉编辑器如何在分区内部扫描文本并为其添加颜色。
在分区内部扫描文本
如前所述,DLTK 编辑器依靠 ScriptSourceViewer 响应用户事件,比如击键。可以通过
ScriptSourceViewerConfiguration 对象来调整查看器的操作。像 ScriptTextTools
类一样,配置对象仅需完成少量工作。相反,它的方法识别协助源代码查看器操作的类。对于语法着色,这些 “帮助”
方法中最重要的两个是:
getPresentationReconciler()
返回在发生变更时控制文本如何显示的 IPresentationReconciler
initializeScanners()
返回一组 AbstractScriptScanner,它们像 RuleBasedPartitionScanner
在前面描述的那样执行同一类型的文本扫描
这两个方法是紧密相关的。下面的讨论解释 Octave IDE 如何实现它们,以提供文本颜色。
IPresentationReconciler
根据 Eclipse 的说法,调解器 是一个跟踪文档内容并对更改作出响应的对象。显示调解器 是通过改变文档的显示方式来响应文档更改的调解器。特别需要注意的是,IPresentationReconciler
通过调用两个对象来响应更改:
IPresentationDamager
决定文档修改的范围
IPresentationRepairer
创建一个 TextPresentation 对象来响应由 IPresentationDamager 识别到的更改
这两个对象按顺序操作。当用户在一个分区中修改文本时,IPresentationReconciler 会给
IPresentationDamager 一个分区名。在销毁器识别到修改后的区域之后,IPresentationRepairer
将分析这个区域,看看区域中文本显示的哪些地方需要更新,以及如何更新。
为简单起见,Eclipse 提供一个 DefaultDamagerRepairer 作为销毁器和修复器。Octave
IDE 为这三个分区的每个分区构造一个 DefaultDamagerRepairer,并使用不同的扫描器初始化它们。清单
5 显示了这个调解器。
清单 5. 配置显示调解器
// Create a DefaultDamagerRepairer for the default partition (code) DefaultDamagerRepairer dr = new DefaultDamagerRepairer(codeScanner); reconciler.setDamager(dr, IDocument.DEFAULT_CONTENT_TYPE); reconciler.setRepairer(dr, IDocument.DEFAULT_CONTENT_TYPE);
// Create a DefaultDamagerRepairer for string
partitions
dr = new DefaultDamagerRepairer(stringScanner);
reconciler.setDamager(dr, OctavePartitions.OCTAVE_STRING);
reconciler.setRepairer(dr, OctavePartitions.OCTAVE_STRING);
// Create a DefaultDamagerRepairer for comment
partitions
dr = new DefaultDamagerRepairer(commentScanner);
reconciler.setDamager(dr, OctavePartitions.OCTAVE_COMMENT);
reconciler.setRepairer(dr, OctavePartitions.OCTAVE_COMMENT); |
就像分区扫描器使用规则识别分区一样,所有这些扫描器都根据类似的规则创建标记。下一小节详细讨论文本扫描器。
在分区内扫描文本
ScriptSourceViewerConfiguration 的构造器调用 initializeScanners()
创建分析文本所需的扫描器。清单 6 显示 OctaveSourceViewerConfiguration
类中 initializeScanners() 的完整实现。
清单 6. 创建脚本扫描器
private AbstractScriptScanner codeScanner, stringScanner, commentScanner;
protected void initializeScanners() {
// Create a scanner for the script code
codeScanner = new OctaveCodeScanner(getColorManager(),
fPreferenceStore);
// Create a scanner for string partitions
stringScanner = new SingleTokenScriptScanner(getColorManager(),
fPreferenceStore, IOctavePartitions.OCTAVE_STRING);
// Create a scanner for comment partitions
commentScanner = new SingleTokenScriptScanner(getColorManager(),
fPreferenceStore, IOctavePartitions.OCTAVE_COMMENT);
} |
由于分区扫描器已经识别字符串和注释,因此不需要重新分析这些分区。所以 stringScanner 和
commentScanner 都是 SingleTokenScriptScanner。这个类并不根据规则做出决定,但通常返回相同类型的
IToken。例如,stringScanner 返回一个用 OctavePartitions.OCTAVE_STRING
初始化的标记,以及 commentScanner 返回一个用 OctavePartitions.OCTAVE_COMMENT
初始化的标记。稍后您将看到这些标记属性如何决定文本的颜色。
codeScanner 用得最多,并且是 IDE 文本分析的主力。这个扫描器是一个 OctaveCodeScanner,它像
PartitionTokenScanner 一样根据规则创建标记。它并不基于 MultiLineRule
或 SingleLineRule,而是基于两个 IRule 实现:
WordRule
当扫描器遇到特定的单词时返回一个标记
NumberRule
当扫描器遇到数字时返回一个标记
这些规则在 OctaveCodeScanner 的 createRules 方法中创建。清单 7 完整地显示了这个方法。
清单 7. 创建脚本扫描器
protected List<IRule> createRules() {
// Create tokens
IToken keywordToken = getToken(DLTKColorConstants.DLTK_KEYWORD);
IToken numberToken = getToken(DLTKColorConstants.DLTK_NUMBER);
IToken defaultToken = getToken(DLTKColorConstants.DLTK_DEFAULT);
// Create and populate list
List<IRule> ruleList = new ArrayList<IRule>();
// Create word rule to detect keywords
WordRule wordRule = new WordRule(new OctaveWordDetector(),
defaultToken);
for (int i = 0; i < IOctaveKeywords.keywords.length;
i++)
wordRule.addWord(IOctaveKeywords.keywords[i],
keywordToken);
ruleList.add(wordRule);
// Create number rule to detect numbers
NumberRule numberRule = new NumberRule(numberToken);
ruleList.add(numberRule);
// Set the token returned for default text
setDefaultReturnToken(defaultToken);
return ruleList;
} |
必须将在清单 7 中创建的标记与清单 4 中的标记区分开来。Token 构造器可以以任何 Object
为参数,并且这个 Object 告诉标记接收者应该采取什么行动。在清单 4 中,使用告诉 FastPartitioner
如何更新文档分区的分区名初始化 Token。在清单 7 中,使用告诉 IDE 如何在编辑器中显示文本的
TextPresentation 对象初始化 Token。
清单 7 通过调用 getToken 将每个 Token 与一个 TextPresentation
关联起来。这个方法搜索由用户首选项填充的 Map。要理解这个映射是如何更新的,必须先理解 DLTK 首选项是如何工作的。这就是接下来的讨论主题。
配置 DLTK 首选项
前面的讨论显示了用户首选项如何决定文本显示。现在我们更进一步,演示如何创建接收用户首选项的页面。您可以通过单击
Eclipse 中的 Window > Preferences 来访问这些首选项。在讨论的最后,我将解释用户首选项如何将脚本解释器集成到
IDE 中。
首选项存储
Eclipse 的首选项存储 就像一个属性文件:它包含用于识别用户首选项设置的名称-值对。但它们之间有两处重要区别。除了当前值之外,每个首选项都包含一个默认值,并且首选项值必须是以下
6 中基本类型之一:
Boolean
int
long
float
double
String
通过调用由 IPreferenceStore 接口定义的方法来存储和获取首选项。setValue 和
setDefaultValue 方法添加并更新首选项。要获取首选项的值,可以调用 getBoolean、getInt、getLong、getFloat、getDouble
或 getString 之一。有一些类似的方法可以访问默认值,比如 getDefaultDouble。
要访问与 SWT 对象有关的首选项(尤其是 RGB、Rectangle、FontData 和 FontData[]
对象),可以调用 PreferenceConverter 类中的方法。
在初始化期间,DLTK ScriptEditor 创建了一个包含 4 个 IPreferenceStore
对象的 ArrayList:
ScopedPreferenceStore,是为 Octave 插件(org.dworks.octaveide)创建的
ScopedPreferenceStore,是为 Eclipse 文本编辑器插件(org.eclipse.ui.editors.text)创建的
一个提供与项目有关的首选项的 EclipsePreferencesAdapter,包含需要编辑的脚本
一个 PreferencesAdapter,包含与 DLTK 核心相关联的首选项
这些存储被合并成一个 ChainedPreferenceStore。如果两个或两个以上的首选项存储包含相同的首选项,则由先添加的存储提供值。
初始化首选项
Eclipse 允许初始化期间使用扩展点 org.eclipse.core.runtime.preferences
设置首选项。Octave IDE 扩展这个点并将 OctavePreferenceInitializer
类标识为为 IDE 首选项提供默认值。反过来,OctavePreferenceInitializer 类又扩展抽象类
AbstractPreferenceInitializer,后者惟一要求的方法是 initializeDefaultPreferences。IDE
在初始化期间调用这个方法。清单 8 显示了如何通过代码实现它。
清单 8. 初始化默认首选项
public void initializeDefaultPreferences() { IPreferenceStore store = OctavePlugin.getDefault().getPreferenceStore(); OctavePreferenceConstants.initializeDefaultValues(store); } |
这个简单的例程访问 Octave 插件的首选项存储,并调用 OctavePreferenceConstants
类来初始化存储的值。它是 DLTK 的 PreferenceConstants 类的子类,PreferenceConstants
类能够定义并初始化各种常见的脚本首选项。在本教程中,仅初始化用于处理语法颜色的首选项。清单 9 展示如何实现初始化。
清单 9. 设置语法颜色首选项
public static void initializeDefaultValues(IPreferenceStore store) {
// Set default preferences for the editor
PreferenceConstants.initializeDefaultValues(store);
// Make keywords blue and bold
PreferenceConverter.setDefault(store, DLTKColorConstants.DLTK_KEYWORD,
new RGB(40, 0, 200));
store.setDefault(DLTKColorConstants.DLTK_KEYWORD
+
PreferenceConstants.EDITOR_BOLD_SUFFIX, true);
// Set default values for other preferences
PreferenceConverter.setDefault(store,
DLTKColorConstants.DLTK_SINGLE_LINE_COMMENT, new
RGB(25, 200, 25));
PreferenceConverter.setDefault(store, DLTKColorConstants.DLTK_NUMBER,
new RGB(255, 25, 25));
PreferenceConverter.setDefault(store, DLTKColorConstants.DLTK_STRING,
new RGB(50, 100, 100));
} |
Octave 首选项的第一个选项用于处理关键字。它的代码通过两个步骤在 IDE 中配置关键字的颜色和样式(bold/italics/strikethrough)。首先,调用
PreferenceConverter 将 DLTKColorConstants.DLTK_KEYWORD
与蓝色关联起来。其次,调用 store.setDefault 方法将 DLTKColorConstants.DLTK_KEYWORD
+ PreferenceConstants.EDITOR_BOLD_SUFFIX 和 true 关联起来。这两行代码确保在使用
DLTKColorConstants.DLTK_KEYWORD 初始化任何 Token 时,相应的文本就显示为粗体蓝色。
Preference pages 和 configuration blocks
现在您已经知道如何初始化首选项,那么也应该为用户提供配置首选项的机会。Eclipse 通过 preference
pages 来实现这个功能。DLTK 使用称为 configuration blocks 的对象简化了这些页面的配置过程。这个小节将描述这两个类,以及它们如何协作以在
Octave IDE 中提供用户首选项。
Eclipse preference pages
单击 Eclipse 中的 Window > Preferences 时,Preferences
窗口的左边面板将以层次结构的方式显示用户可配置的选项。对于 Octave IDE,您需要使用一个称为 Octave
并带有子首选项的顶级首选项,用于处理语法颜色和 Octave 解释器。图 4 显示了这个窗口。
图 4. Octave 首选项窗口
在 Octave 项目中,plugin.xml 通过扩展 org.eclipse.ui.preferencePages
定义 3 个首选项主题。每个扩展都需要具有 id、name 和 class 属性。两个子首选项具有一个额外属性
category,它命名父首选项的 id。清单 10 给出了这些扩展。
清单 10. Octave 首选项扩展
<extension point="org.eclipse.ui.preferencePages"> <page class="org.dworks.octaveide.preferences.OctaveMainPreferencePage" id="org.dworks.octaveide.preferences.OctaveMainPreferencePage" name="%MainPreferencePage.name"/> <page category="org.dworks.octaveide.preferences.OctaveMainPreferencePage" class="org.dworks.octaveide.preferences.OctaveSyntaxColorPage" id="org.dworks.octaveide.preferences.OctaveSyntaxColorPage" name="%SyntaxColorPreferencePage.name"/> <page category="org.dworks.octaveide.preferences.OctaveMainPreferencePage" class="org.dworks.octaveide.preferences.OctaveInterpreterPreferencePage" id="org.dworks.octaveide.preferences.OctaveInterpreterPreferencePage" name="%InterpreterPreferencePage.name"/> </extension> |
class 属性命名一个实现 IWorkbenchPreferencePage(IPreferencePage
的子接口)的类。DLTK 提供它自己的 IWorkbenchPreferencePage 实现,称为 AbstractConfigurationBlockPreferencePage。DLTK
还提供几个有其他用途的子类。图 5 描述了这些类接口的关系。
图 5. Preference page
类的层次结构
清单 10 中的 3 个类都是 AbstractConfigurationBlockPreferencePage
的直接子类。查看它们的 Java 文件时,可以看到它们包含的代码很少。每个类提供一个 ID、一个标签,以及到插件的首选项存储的访问。此外,每个
AbstractConfigurationBlockPreferencePage 必须实现方法 createConfigurationBlock(),该方法构造提供页面图形元素的对象。这个对象必须实现
DLTK 接口 IPreferenceConfigurationBlock。下一小节将讨论这个重要的接口。
DLTK configuration blocks
对呈现 IDE 首选项而言,最难的部分就是构建 UI。例如,Octave 语法着色首选项页面不仅允许用户选择颜色和样式,并且还提供一个用于显示实际效果的嵌入式文本编辑器。从头构建这些图形控件非常耗费时间,不过
DLTK 的 IPreferenceConfigurationBlock 能够帮上大忙。图 6 显示了
configuration block 类的层次结构。
图 6. Configuration block
类的层次结构
语法着色 configuration block 类
相关的第一个类是 AbstractScriptEditorColoringConfigurationBlock,它为用户提供语法着色首选项。由
createSyntaxPage 方法完成主要工作,即创建图形首选项页面。具体来讲,它构造了一个 TreeViewer,以显示语法类型和一组用于表示颜色和样式首选项的按钮。然后,它调用
createPreviewer 构建嵌入式文本编辑器。
Octave 插件代码包含 AbstractScriptEditorColoringConfigurationBlock
的一个子类,称为 OctaveSyntaxColorConfigurationBlock。除了构造器之外,还实现了
4 个方法:
createPreviewViewer
返回一个包围嵌入式文本编辑器的查看器
createSimpleSourceViewerConfiguration
返回一个在嵌入式文本编辑器中配置查看器的基本对象
setDocumentPartitioning
调用 IDocumentSetupParticipant 来设置文档的分区
getSyntaxColorListModel
返回一个 2-D 数组,它识别可以添加颜色的不同语法类型,以及首选项存储中的对应键
前 3 个方法很好理解,因为嵌入式文本编辑器不需要复杂的查看器、查看器配置对象或文档设置参与者。然而最后一个方法
getSyntaxColorListModel 需要解释一下。这个方法提供一个 2-D 数组,它的元素对应于页面的
TreeViewer 中的语法类型。每个元素需要具有 3 个信息:名称、类别和页面首选项存储中的对应键。
回过头来看看这个例子,图 4 表明 Octave 语法颜色模型包含 4 个语法类型:keywords、numbers、strings
和 single-line comments。前 3 个语法类型属于 General 范围,而最后一个语法类型属于
Comment 范围。清单 11 中的代码显示了如何在 OctaveSyntaxColorConfigurationBlock.getSyntaxColorListModel
中配置这些类型。
清单 11. 创建语法颜色列表模型
protected String[][] getSyntaxColorListModel() { return new String[][] { { "Single-Line Comments", DLTKColorConstants.DLTK_SINGLE_LINE_COMMENT, sCommentsCategory }, { "Keywords", DLTKColorConstants.DLTK_KEYWORD, sCoreCategory }, { "Strings", DLTKColorConstants.DLTK_STRING, sCoreCategory }, { "Numbers", DLTKColorConstants.DLTK_NUMBER, sCoreCategory }}; } |
当用户在页面中更改首选项设置并单击 OK 时,页面将更新首选项存储中相应的值。编辑器通过更新文本的表示来响应首选项更改。
解释器首选项
图 4 中的最后一个首选项选项用于设置 IDE 的脚本解释器。DLTK 提供两个类来实现这个功能:ScriptInterpreterPreferencePage
和 InterpretersBlock。InterpretersBlock 创建页面的图形控件,其中包含一个提供说明的
Label;一个列出所有可用解释器的 CheckboxTableViewer;以及一组允许用户添加、编辑、复制、删除和搜索解释器的按钮。图
7 显示了这个页面,以及用户单击 Add 时出现的窗口。
图 7. 解释器首选项页面和窗口
抽象类 InterpretersBlock 要求子类实现两个方法:getCurrentNature 和
createInterpreterDialog。第一个方法返回一个表示解释器、源代码语言和其他相关对象的
String。通常在 DLTK 扩展点中使用这种特征,它非常重要。如果您的 Eclipse 为不同的动态语言包含多个基于
DLTK 的特性,就很容易根据各自的特征区分它们。
第二个方法 createInterpreterDialog 构造一个窗口,当用户在首选项页面上单击 Add
时弹出。这个窗口(见图 7)必须至少包含 4 个信息之一:可执行解释器的位置、解释器的名称、解释器的类型,以及解释器需要执行的库的位置。要构造这个窗口,createInterpreterDialog
方法需要 DLTK 类 AddScriptInterpreterDialog 的一个实例。
AddScriptInterpreterDialog 构造器接受一个解释器类型的数组和一个实现 IInterpreterInstall
的对象。具有多个解释器类型使 IDE 能够支持同一语言的多个解释器。Octave IDE 仅支持一种安装类型,并且使用
org.eclipse.dltk.launching.interpreterInstallTypes 的一个扩展在
plugin.xml 中标识该类型。这些首选项扩展如清单 12 所示。
清单 12. Octave 首选项扩展
<extension point="org.eclipse.dltk.launching.interpreterInstallTypes"> <interpreterInstallType class="org.dworks.octaveide.launch.OctaveInterpreterInstallType" id="org.dworks.octaveide.launch.OctaveInterpreterInstallType"> </interpreterInstallType> </extension> |
class 属性标识一个实现 IInterpreterInstallType 的对象。每个安装类型都有一个名称、ID
和特征(一个用于标识解释器和源类型的 String),以及解释器和所有必需库的位置。另外,每个 IInterpreterInstallType
必须创建一个以上的 IInterpreterInstall 对象。
尽管 IInterpreterInstallType 对象定义了解释器安装的一个类,但还必须为每个特定安装创建一个
IInterpreterInstall 对象。这个对象用作解释器的主数据存储对象,它包含以下信息:
1.名称
解释器安装的标签
2.ID
解释器安装的逻辑标识符
3.特征
IDE 的逻辑标识符(解释器和源文件等)
4.安装位置
解释器安装的路径
5.库的位置
解释器所需的库的路径
6.执行环境
环境变量和进程配置
7.解释器参数
指向解释器的参数
最后,每个 IInterpreterInstall 必须提供对 IInterpreterRunner
对象的访问。这个对象负责实际启动解释器,下一小节不仅讨论它的工作方式,还讨论它如何融入到解释器-IDE
交互中。
DLTK 解释器和控制台
DLTK 应用程序的最重要特性不是文本编辑器或首选项,而是实现通过单击按钮启动解释器以及在 IDE 控制台中查看它的输出。DLTK
解释器和控制台是紧密关联的,通过代码管理它们非常复杂。在深入了解所有类和接口之前,现在先大致了解一下当用户单击
Run 时触发的事件:
IDE 通过创建一个启动对象来响应单击按钮事件,从而收集启动信息并为启动添加一个进程。
启动的创建将通知所有启动监听器,包括 DLTK 控制台管理器。
控制台管理器获得启动信息,并使用该信息创建 3 个对象:一个控制台服务器,一个解释器对象和一个控制台工厂。
控制台服务器创建一个套接字(默认端口是 25000),然后等待连接。
控制台工厂启动解释器并创建一个控制台。
控制台管理器访问 Eclipse 控制台插件并添加新的控制台。
DLTK 类处理大部分事情,但您仍然需要完成一些事情。Octave IDE 必须支持与 Octave
相关的启动,然后需要特定于 Octave 的类来管理控制台和解释器。这里将逐一讨论这些主题。
配置 DLTK 启动
在 Eclipse 中,启动应用程序的过程称为在 Run 模型下启动应用程序。由于 Eclipse 支持许多启动执行文件,所以您要确保当用户执行
Octave 脚本时启动 Octave 解释器。这种关联通过启动配置类型 来实现,并且通过在 Eclipse
中单击 Run > Run 可以看到所有可用的类型。图 8 显示了启动配置类型。
图 8. Octave 启动配置类型
要为 Octave 创建启动配置类型,第一步就是将 org.eclipse.debug.core.launchConfigurationTypes
的一个扩展添加到 plugin.xml。在图 8 中,可以看到在窗口中选中的 Octave 配置类型。清单
13 给出了定义这个新类型的扩展。
启动 13. 定义 Octave 启动配置类型
<extension point="org.eclipse.debug.core.launchConfigurationTypes"> <launchConfigurationType id="org.dworks.octaveide.launch.OctaveLaunchConfigurationType" delegate="org.dworks.octaveide.launch.OctaveLaunchConfigurationDelegate" modes="run" public="true" name="%OctaveLaunchConfigurationType.name" </launchConfigurationType> </extension> |
示例项目会执行 Octave 脚本,但不调试它们。因此,需要将 modes 属性设置为 run 而不是
run,debug。最重要的属性是 delegate,它标识实现 ILaunchConfigurationDelegate
的类。这个接口定义一个方法 launch,当用户创建启动配置并单击 Run 时会调用该方法。
DLTK 提供它自己的 ILaunchConfigurationDelegate 实现,称为 AbstractScriptLaunchConfigurationDelegate。当这个类的
launch 方法被调用时,它会调用 3 个重要的方法:
createInterpreterConfig
这个方法构造一个 InterpreterConfig 对象,它接收来自启动配置的所有信息。
getInterpreterRunner
这个方法访问由解释器安装类型创建的 IInterpreterInstall
对象,并使用它构造一个 IInterpreterRunner。
runRunner
这个方法为解释器创建一个进程,并将其添加到启动中。
这个过程看起来有些复杂,但前两个方法仅组织了关于启动的信息;InterpreterConfig
存储来自启动配置的信息,而 IInterpreterInstall 存储来自首选项的信息。第 3 个方法并没有
实际启动解释器,理解这点很重要。它为解释器创建一个 IProcess,并将其添加到 Launch 对象。
DLTK 控制台管理器
当 DLTK 插件 org.eclipse.dltk.console.ui
启动时,它将访问 Eclipse Debug UI 插件,并添加一个 ScriptConsoleManager
实例作为启动监听器。当添加、更改或删除启动时,都会通知这些监听器。ScriptConsoleManager
仅响应添加的启动,它的第一个任务是检查启动是否具有可识别的特征。如果是的话,该管理器将创建 3 个重要的对象:一个控制台服务器、一个解释器对象和一个控制台工厂。
管理器创建一个 ScriptConsoleServer 来管理解释器和控制台之间的通信。在初始化期间,服务器将一个
ServerSocket 绑定到端口 25000,并告诉它等待进来的连接请求。这些请求以 ConsoleRequest
对象的形式出现,并且当服务器接受到新的请求时,它将调用每个新请求的 consoleConnected 方法。
创建服务器之后,ScriptConsoleManager 访问一个表示解释器的
ConsoleRequest。它通过在 plugin.xml 中搜索 org.eclipse.dltk.console.scriptInterpreter
的扩展点来实现这个目的。这个扩展点标识一个特征目标和一个实现 IScriptInterpreter(ConsoleRequest
的子接口)的类。控制台管理器在获取解释器对象之后,就可以开始创建控制台了。为此,需要调用工作台工厂。
控制台工厂和控制台
通常,如果您想从插件访问 Eclipse 控制台,仅需扩展?个扩展点:org.eclipse.ui.console.consoleFactories。这个扩展必须标识一个实现
IConsoleFactory 的类。然后,当在 Console 视图中单击 Open Console
时,Eclipse 将调用这个类的 openConsole 方法创建一个新的控制台。
DLTK 让事情变得复杂一些。DLTK ScriptConsoleManager
也需要访问控制台工厂,但它需要一个实现 IScriptConsoleFactory 的类。IConsoleFactory
和 IScriptConsoleFactory 都需要相同的 openConsole 方法,这很方便。不过,您需要创建两个扩展点:一个用于
Eclipse 控制台工厂,一个用于 DLTK 控制台工厂。清单 14 展示了这两个 Octave IDE
扩展点。
清单 14. 标识 Octave 控制台工厂
<extension point="org.eclipse.dltk.console.ui.scriptConsole"> <scriptConsole class="org.dworks.octaveide.launch.OctaveConsoleFactory" natureID="org.dworks.octaveide.nature" /> </extension> <extension point="org.eclipse.ui.console.consoleFactories"> <consoleFactory class="org.dworks.octaveide.launch.OctaveConsoleFactory" icon="icons/oct.gif" label="%OctaveConsole.Console" /> </extension> |
当 ScriptConsoleManager 访问一个脚本控制台工厂时,它调用该工厂的
openConsole 方法,并通过它创建最重要的 ScriptConsole 对象。就像 Eclipse
文本编辑器一样,这个控制台使用基于文档的方法显示文本;它有一个 ConsoleDocument、一个 IConsoleDocumentPartitioner
以及一些 Position 和 Region 数组。
创建 ScriptConsole 之后,它将调用引用由控制台创建的 IScriptInterpreter
对象的 setInterpreter。这个方法以 InitialStreamReader 的形式在控制台和解释器之间创建一个连接。这个流阅读器通过调用
IScriptInterpreter.getInitialOutputStream 将自身连接到解释器。接下来,InitialStreamReader
读取解释器输出的每行内容,并发送到控制台进行显示。显示文本之后,控制台会输出一个提示,并进入可编辑模式。
在创建 ScriptConsole 之后,ScriptConsoleManager
将访问 Eclipse ConsoleManager,并向可用控制台列表添加新的对象。现在,新的控制台对用户是可见的,并且解释器也可以访问它。
运行解释器
我们仔细看看由 ScriptConsoleManager 创建的 IScriptInterpreter
对象。这个接口扩展 ConsoleRequest 接口,这意味着可以将它提交给 ScriptConsoleServer
以进行处理。控制台管理器处理这个提交,并且当服务器收到解释器的请求时,它将调用 IScriptInterpreter.consoleConnected(IScriptConsoleIO
protocol)。
这个方法担任两个重要的角色:它构造实现在控制台和解释器之间传输数据的 InitialStreamReader,并且这个方法的参数标识用于通信的协议。IScriptConsoleIO
定义两个从解释器接收数据的重要方法:
InterpreterResponse execInterpreter(String
command)
向解释器发送一个脚本命令,然后接收一个响应
ShellResponse execShell(String command,
String[] args)
向解释器发送一个 shell 命令,然后接收一个响应
这两个函数将命令指向解释器并接收解释器的输出。每个 IScriptConsoleIO
协议对象都使用 InputStream 和 OutputStream 进行初始化。当 execInterpreter
被调用时,协议将命令发送给解释器的 OutputStream。当解释器进行响应时,协议接收 InterpreterResponse(实际上是一个
String)并通过 InputStream 将其发送到控制台。
所有与解释器进行的通信都依赖于协议的方法。例如,IScriptInterpreter
接口包含将命令发送到解释器的 exec(String command) 方法。这个命令通过调用 IScriptConsoleIO.execInterpreter
来提交命令。同样,解释器的 close 方法也依赖于 IScriptConsoleIO.execShell。
结束语
正如本教程所示,DLTK 提供许多用于为动态语言创建开发环境的特性。创建这些环境不需要编写很多代码,但您要明确自己的目的。为了配置文本表示,您必须理解编辑器、查看器和文档之间是如何交互的,并且要大体了解基于规则的分区和扫描。要支持用户首选项,您必须理解
Eclipse preference pages 和 DLTK configuration blocks
类的奇怪层次结构。要启动动态语言项目,您必须处理实现控制台和脚本解释器之间的通信的线程、流和协议方法。
掌握 DLTK 不太容易,要精通它则更加困难。但是如果您要为特定的脚本语言构建功能完整的
IDE,就没有比它更好的工具集了。除了本教程中的材料之外,DLTK 还为 PHP 和 Tcl 等语言提供许多示例
IDE。
|