您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
为 Perl、Python 和 PHP 构建 Eclipse 开发环境
 
作者 Matthew Scarpino,火龙果软件    发布于 2014-07-10
  1905  次浏览      20
 

使用 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。

   
1905 次浏览       20
相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

Android手机开发(一)
理解Javascript
非典型ajax实践
彻底的Ajax
javascript 使用Cookies
使用 jQuery 简化 Ajax 开发
更多...   


Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术


某航空公司IT部 JavaScript实践
某电视软件 HTML5和JavaScript
中航信 JavaScript高级应用开发
大庆油田 web界面Ajax开发技术
和利时 使用AJAX进行WEB应用开发
更多...