使用 ICSharpCode.TextEditor
使用 TextEditorControl 在您的应用程序中添加一个支持语法高亮的编辑器。
- 下载源代码 - 396 KB
注意:ICSharpCode.TextEditor
和示例应用程序需要 C# 3.0 编译器,但它们配置为目标 .NET Framework 2.0。通常,我们从 SharpDevelop 源代码中获取 ICSharpCode.TextEditor
,但示例项目本身包含了最新版本 (3.0.0.3437)。
引言
SharpDevelop 拥有一个功能丰富(尽管文档不那么丰富)的文本编辑器控件。本文附带的演示展示了如何使用它来加载和保存文件,在文档中查找和替换字符串,执行剪贴板操作,使用 TextMarkers 来高亮显示字符串,使用 FoldingStrategy 允许用户折叠文档区域,使用书签,以及更改一些显示设置。
希望本文的剩余部分能提供足够的信息,指引您进行文本编辑器行为的任何定制。
ICSharpCode.TextEditor 的设计
文本编辑器实际上包含三个紧密耦合的嵌套控件
- 顶层是
TextEditorControl
,它包含一个或两个TextAreaControl
。当“分割”时,它有两个TextEditorControl
,如截图所示。 TextAreaControl
封装了水平和垂直滚动条以及一个TextArea
。TextArea
是实际获得焦点的控件。它负责绘制文本并处理键盘输入。
如果有什么比控件类更重要,那就是 IDocument
接口。IDocument
接口,由 DefaultDocument
类实现,是提供对 SharpDevelop 文本编辑器大多数功能访问的中心:撤销/重做,标记,书签,代码折叠,自动缩进,语法高亮,设置,以及最重要的,文本缓冲区管理!
这些基本类的图示如下
注意:在 Visual Studio 类图上,我无法显示一个类到它实现的接口的派生箭头,所以我用一个“接口棒棒糖”指向 DefaultDocument
的接口。
详细说明
下面是一个更完整的图,包括可以通过 TextArea
和 IDocument
的属性访问的其他类和接口。文本编辑器的功能,如代码折叠和语法高亮,被整齐地划分到单独的类或接口中,其中大多数可以被替换为自定义或派生版本,如果您需要的话。
TextEditor
(以及 SharpDevelop,总的来说)经常使用“策略”模式,所以您会经常看到这个词。在策略模式中,一个接口定义了所需的功能,但没有定义如何实现它。如果您需要不同的实现,可以编写自己的实现,并在 IDocument
中调用适当的 setter 来更改策略。至少理论上是这样。由于某些原因,MarkerStrategy
是一个 sealed
类,没有对应的接口,因此无法替换。
让我们来谈谈从 IDocument
分支出来的功能。
- 文档自动提供无限的撤销/重做功能。您无需做任何特殊操作即可确保程序化更改可以被撤销;只需确保使用
IDocument
中的方法修改文档,而不是使用ITextBufferStrategy
中的方法(后者会绕过撤销堆栈)。您可以通过将组放在匹配的IDocument.UndoStack.StartUndoGroup()
和IDocument.UndoStack.EndUndoGroup()
调用之间,将多个操作组合在一起,以便一个“撤销”命令可以撤销它们所有。 - 标记(
TextMarker
类的实例)是文本范围(具有起始和结束位置)。在将标记注册到文档的MarkerStrategy
后,当文档被修改时,标记的起始和结束点会自动移动。标记可以是可见的或不可见的;如果可见,标记可以用来下划线文本(带有拼写检查器式的波浪线),或者覆盖其覆盖区域的语法高亮。示例应用程序使用标记来实现其“高亮全部”命令。 - 书签是显示在“图标栏”边距中的矩形标记,用户可以通过按 F2 跳转到这些书签。示例项目演示了如何切换书签和在它们之间移动。
- 代码折叠允许折叠文本块。
ISharpCode.TextEditor
没有(可用的)代码折叠策略,因此如果您想创建一个具有代码折叠的编辑器,可以参考 SharpDevelop 的源代码。在演示中,我实现了一个简单的折叠策略,只支持#region/#endregion
块。DefaultDocument
和TextEditorControl
不会自动更新代码折叠标记,因此在演示中,折叠仅在文件首次加载时计算。 - “逻辑”行号是显示在边距中的“真实”行号。
- “可见”行号是应用折叠后的行号。术语“行号”本身通常指的是逻辑行号。
- 自动缩进以及在用户键入时对文档进行格式化的相关功能,旨在通过实现
IFormattingStrategy
来提供。DefaultFormattingStrategy
在按 Enter 键时简单地匹配前一行的缩进。同样,可以在 SharpDevelop 的源代码中找到更复杂的策略。 - 语法高亮通常由
DefaultHighlightingStrategy
实例提供,该实例根据具有“xshd”扩展名的 XML 文件来高亮显示文件。文本编辑器 DLL 中内置了十几个这样的文件作为资源,TextEditorControl
在加载文件时会根据文件的扩展名自动选择一个高亮器。它不会在文件名更改时更改高亮器;因此,演示的DoSaveAs
方法使用HighlightingStrategyFactory
来获取相应策略。网上有一些关于添加更多基于 XSHD 的高亮器的文章,例如这篇文章和这篇文章。 - 文本缓冲区策略管理文本缓冲区。默认的
GapTextBufferStrategy
背后的算法在 Wikipedia 和在 CodeProject 上有描述。 ITextEditorProperties
封装了各种选项,例如是否显示行号以及制表符的宽度。
奇怪的是,还有一个类具有类似的功能:TextAnchor
锚定到单个点,并在文档更改时自动移动,但您不能使用此类,因为它的构造函数是 internal
的。
在代码折叠存在的情况下,有两种行号。
IFormattingStrategy
还包含向后或向前搜索匹配括号的方法,以便它们可以被高亮显示,但这只是高亮匹配括号机制的一部分,该机制的实现跨越了多个类,包括 TextUtilities
、BracketHighlightingSheme
、BracketHighlight
和 TextArea
。总之,似乎 TextArea
是硬编码为仅提供 ()
、[]
和 {}
的括号匹配。
ITextEditorProperties
无法通知任何其他对象其属性已更改。如果您直接更改这些属性之一,并且它影响了控件的外观,控件不会自动重绘。因此,TextEditorControlBase
为 ITextEditorProperties
中它需要监视的每个属性都提供了一个包装器。例如,TextEditorControlBase.TabIndent
是 ITextEditorProperties.TabIndent
的一个包装器。顺便说一句,您可以共享一个 ITextEditorProperties
对象给多个文本编辑器,我在演示中就是这样做的。
除此之外,ICSharpCode.TextEditor 项目还包含一些与通常称为“智能感知”相关的代码:一个“提示窗口”(一个类似工具提示的窗口,通常用于显示方法签名)和一个“代码补全”列表。
ICSharpCode.TextEditor 本身实际上不执行智能感知,但它包含一些用于这些功能的 GUI 的代码。然而,这段代码不被文本编辑器直接使用,而且我的演示也没有展示它(事实上,我也不知道如何使用它)。
文本编辑器库非常庞大;图上还有一些其他杂项类未能包含,我也没有时间在这篇文章中描述它们。其中值得注意的包括 TextWord
,它是语法高亮的基本单位;LineManager
,DefaultDocument
用它来转换“偏移量”为“位置”;以及 TextUtilities
,一个静态方法集合。
这里有一些额外的提示
- 文档中的一个位置可以通过两种方式表示。首先,一个位置可以表示为行-列对,它们被打包在
TextLocation
结构中。更根本地说,您可以将文档视为一个字符数组,其长度为IDocument.TextLength
。这个数组的索引称为“偏移量”(类型:int
)。偏移量表示似乎更常见,但有些代码(例如SelectionManager
)要求位置以TextLocation
的形式提供。您可以使用IDocument.OffsetToPosition
和IDocument.PositionToOffset
在这两种表示之间进行转换。 - “
Caret
”是闪烁的光标。您可以通过更改Caret
的Line
、Column
或Position
属性来移动光标。 - 在 SharpDevelop 中可以使用键盘组合键调用的所有文本编辑器操作都被封装在
ICSharpCode.TextEditor.Actions.IEditAction
的实现中。示例应用程序的“编辑”菜单处理程序演示了一些这些操作。 TextArea
的左侧显示多达三个边距,由上面图表中未显示的三个类表示。它们不是独立的控件,而是TextArea
将鼠标和绘制命令传递给它们。FoldMargin
显示用于折叠或展开区域的小的 + 和 - 图标。如果您不使用代码折叠,恐怕没有办法隐藏该边距(好吧,您可以修改源代码)。IconBarMargin
显示书签(或 SharpDevelop 中的断点)等图标。可见性由ITextEditorProperties.IsIconBarVisible
控制。GutterMargin
显示行号。可见性由ITextEditorProperties.ShowLineNumbers
控制。- 文档对使用它的控件没有引用,所以我假设我们可以将同一个文档用于多个控件,管理一个没有控件的文档,或者编写一个新的控件实现。编辑器控件通过订阅文档的事件来获得文档更改的通知。
ICSharpCode.TextEditor
中最耗费资源的部分是其语法高亮,它可能消耗的内存是正在编辑的文本文件大小的十倍。绘制这些文本的代码会消耗大量 CPU 资源并分配大量的临时对象。- 我不是真正的专家;我只学到了足够关于
ICSharpCode.TextEditor
的知识来写这篇文章!玩得开心!
历史
- 2008 年 11 月 13 日 - 首次发布。
注意:尽管我的示例是在 MIT 许可下提供的,但 ICSharpCode.TextEditor
本身是根据 GNU LGPL 条款提供的。