在编辑器中

编辑器由多个不同的子系统组成,旨在使编辑器文本模型与文本视图和用户界面分开。

这些部分介绍编辑器的不同方面:

这些部分介绍编辑器的功能:

子系统

文本模型子系统

文本模型子系统负责表示文本并启用其操作。 文本模型子系统包含 ITextBuffer 接口,该接口描述编辑器要显示的字符序列。 可以通过多种方式对该文本进行修改、跟踪和其他操作。 文本模型还提供以下方面的类型:

  • 将文本与文件关联的服务,并管理在文件系统中读取和写入它们。

  • 一种差异服务,用于查找两个对象序列之间的最小差异。

  • 一个系统,用于根据其他缓冲区中文本的子集来描述某个缓冲区中的文本。

文本模型子系统没有用户界面(UI)概念。 例如,它不负责文本格式或文本布局,并且它不知道可能与文本关联的视觉装饰。

文本模型子系统的公共类型包含在 Microsoft.VisualStudio.Text.Data.dllMicrosoft.VisualStudio.CoreUtility.dll中,这仅依赖于 .NET Framework 基类库和托管扩展性框架(MEF)。

文本视图子系统

文本视图子系统负责设置文本格式和显示文本。 此子系统中的类型分为两个层,具体取决于类型是否依赖于 Windows Presentation Foundation (WPF)。 最重要的类型是 ITextViewIWpfTextView,它们控制要显示的文本行集,以及插入符号、选择和用于使用 WPF UI 元素装饰文本的功能。 此子系统还提供文本显示区域周围的边距。 可以扩展这些边距,并且可以包含不同类型的内容和视觉效果。 边距的示例包括行号显示和滚动条。

文本视图子系统的公共类型包含在 Microsoft.VisualStudio.Text.UI.dllMicrosoft.VisualStudio.Text.UI.Wpf.dll中。 第一个程序集包含独立于平台的元素,第二个程序集包含特定于 WPF 的元素。

分类子系统

分类子系统负责确定文本的字体属性。 分类器将文本拆分为不同的类,例如“keyword”或“comment”。 分类格式映射将这些类与实际的字体属性(例如“Blue Consolas 10 pt”)相关联。 当文本视图设置文本格式并呈现文本时,将使用此信息。 标记功能将在本主题后面更详细地介绍,它使数据能够与文本的某个范围相关联。

分类子系统的公共类型包含在 Microsoft.VisualStudio.Text.Logic.dll中,它们与 Microsoft.VisualStudio.Text.UI.Wpf.dll中包含的分类视觉方面进行交互。

操作子系统

操作子系统定义编辑器功能。 它为 Visual Studio 编辑器命令和撤消系统提供实现。

仔细查看文本模型和文本视图

文本模型

文本模型子系统由不同的文本类型分组组成。 其中包括文本缓冲区、文本快照和文本跨度。

文本缓冲区和文本快照

ITextBuffer 接口表示使用 UTF-16 编码的 Unicode 字符序列,这是 .NET Framework 中类型使用的 String 编码。 文本缓冲区可以保留为文件系统文档,但这不是必需的。

ITextBufferFactoryService 用于创建空文本缓冲区,或从字符串或来自TextReader初始化的文本缓冲区。 文本缓冲区可以持久化到文件系统中作为ITextDocument

任何线程都可以编辑文本缓冲区,直到线程通过调用获取 TakeThreadOwnership文本缓冲区的所有权。 之后,只有该线程才能执行编辑。

文本缓冲区可以在其生存期内经历许多版本。 每次编辑缓冲区时都会生成新版本,不可变 ITextSnapshot 表示该版本的缓冲区的内容。 由于文本快照是不可变的,因此可以在任何线程上访问文本快照,而无需限制,即使它表示的文本缓冲区会继续更改。

文本快照和文本快照行

可以将文本快照的内容查看为字符序列或行序列。 字符和行都从零开始编号。 空文本快照包含零个字符和一个空行。 行由任何有效的 Unicode 换行字符序列或缓冲区的开头或结尾分隔。 换行符在文本快照中显式表示,文本快照中的换行符不必全部相同。

注释

有关 Visual Studio 编辑器中的换行符的详细信息,请参阅 编码和换行符

文本行由对象 ITextSnapshotLine 表示,该对象可从特定行号或特定字符位置的文本快照中获取。

SnapshotPoints、SnapshotSpans 和 NormalizedSnapshotSpanCollections

A SnapshotPoint 表示快照中的字符位置。 保证该位置位于零和快照的长度之间。 一个 SnapshotSpan 代表快照中的一段文本。 它的结束位置确保在零和快照长度之间。 这 NormalizedSnapshotSpanCollection 由同一快照中的一组 SnapshotSpan 对象组成。

Spans 和 NormalizedSpanCollections

一种 Span 表示可以应用于文本快照中的一段文本的间隔。 快照位置从零开始,因此范围可以从任何位置开始,包括零。 End 范围的属性等于其 Start 属性和 Length 属性的总和。 一个 Span 不包括由 End 属性编制索引的字符。 例如,具有 Start=5 且 Length=3 的范围具有 End=8,并且包含位置 5、6 和 7 处的字符。 此范围的表示法为 [5..8]。

两个跨度相交(如果它们有任何共同位置,包括 End 位置)。 因此,[3, 5) 和 [2, 7) 的交集是 [3, 5),[3, 5) 和 [5, 7) 的交集是 [5, 5)。 请注意,[5, 5) 是一个空区间。

如果两个跨度具有共同位置,则两个跨度重叠,但结束位置除外。 空的跨度从不会与其他任何跨度重叠,并且两个跨度的重叠从不为空。

A NormalizedSpanCollection 是一个根据范围开始属性顺序排列的跨度列表。 在列表中,将重叠或相邻的范围合并。 例如,鉴于范围集 [5..9)、[0..1)、[3..6) 和 [9..10),规范化范围列表为 [0..1)、[3..10)。

ITextEdit、TextVersion 和文本更改通知

可以使用对象更改 ITextEdit 文本缓冲区的内容。 创建此类对象(通过使用其中一种 CreateEdit() 方法 ITextBuffer)将启动由文本编辑组成的文本事务。 每次编辑都是用字符串替换缓冲区中的一些文本范围。 启动事务时,每次编辑的坐标和内容都是基于缓冲区的快照状态表示。 该 ITextEdit 对象调整受同一事务中其他编辑影响的编辑坐标。

例如,请考虑包含此字符串的文本缓冲区:

abcdefghij

应用包含两个编辑的事务,一个编辑用字符X替换 [2..4) 中的范围,另一个编辑用字符Y替换 [6..9) 中的范围。 结果是此缓冲区:

abXefYj

第二次编辑的坐标是在应用第一次编辑之前,根据事务开头缓冲区的内容计算的。

当调用Apply()方法提交ITextEdit对象时,缓冲区所做的更改将生效。 如果至少有一个非空编辑,则会创建一个新的 ITextVersion,一个新的 ITextSnapshot,并引发一个 Changed 事件。 每个文本版本都有不同的文本快照。 文本快照表示编辑事务后文本缓冲区的完整状态,但文本版本仅描述从一个快照到下一个快照的更改。 一般情况下,文本快照应一次使用,然后丢弃,而文本版本必须保持活动状态一段时间。

文本版本包含一个 INormalizedTextChangeCollection。 此集合描述应用于快照时生成后续快照的更改。 集合中的每 ITextChange 一个都包含更改的字符位置、替换的字符串和替换字符串。 替换的字符串对于基本插入为空,替换字符串对于基本删除为空。 规范化集合始终 null 适用于最新版本的文本缓冲区。

任何时候只能为文本缓冲区实例化一个 ITextEdit 对象,并且所有文本编辑都必须在拥有文本缓冲区的线程上执行(如果已声明所有权)。 可以通过调用其 Cancel 方法或其 Dispose 方法来放弃文本编辑。

ITextBuffer还提供了类似于ITextEdit接口上找到的Insert()Delete()Replace()方法。 调用这些对象的效果与创建 ITextEdit 对象的效果相同,进行类似的调用,然后应用编辑。

跟踪点和跟踪范围

ITrackingPoint 个表示文本缓冲区中的字符位置。 如果以导致字符位置移位的方式编辑缓冲区,跟踪点会随其移动。 例如,如果跟踪点引用缓冲区中的位置 10,并在缓冲区开头插入 5 个字符,则跟踪点将引用位置 15。 如果插入恰好发生在跟踪点所表示的位置,其行为将由其PointTrackingMode决定,该行为可以是PositiveNegative。 如果跟踪模式为正,跟踪点将引用相同的字符,该字符现在位于插入的末尾。 如果跟踪模式为负数,跟踪点指原始位置的第一个插入字符。 如果删除跟踪点所表示位置的字符,跟踪点将移动到删除范围之后的第一个字符。 例如,如果跟踪点引用位置 5 处的字符,并且删除位置 3 到 6 处的字符,则跟踪点指位置 3 处的字符。

一个 ITrackingSpan 表示一系列字符,而不是只表示一个位置。 其行为由其 SpanTrackingMode确定。 如果范围跟踪模式为 SpanTrackingMode.EdgeInclusive,跟踪范围将增大以合并在其边缘插入的文本。 如果跨度跟踪模式为 SpanTrackingMode.EdgeExclusive,跟踪范围不会包含在其边缘插入的文本。 但是,如果跨度跟踪模式为 SpanTrackingMode.EdgePositive,则插入将当前位置推送到开始位置,如果跨度跟踪模式为 SpanTrackingMode.EdgeNegative,则插入会将当前位置推送到末尾。

你可以获取跟踪点的位置,或者获取任何特定文本缓冲区快照中跟踪范围的跨度。 可以从任何线程安全地引用跟踪点和跟踪范围。

内容类型

内容类型是定义不同类型的内容的机制。 内容类型可以是文件类型,例如“text”、“code”或“binary”,也可以是技术类型,如“xml”、“vb”或“c#”。 例如,“using”一词是 C# 和 Visual Basic 中的关键字,但不在其他编程语言中。 因此,此关键字的定义仅限于“c#”和“vb”内容类型。

内容类型用作编辑器的装饰和其他元素的筛选器。 每个内容类型定义了许多编辑器功能和扩展点。 例如,纯文本文件、XML 文件和 Visual Basic 源代码文件的文本着色不同。 创建文本缓冲区时通常会为其分配内容类型,并且可以更改文本缓冲区的内容类型。

内容类型可以从多个其他内容类型继承。 使用该 ContentTypeDefinition 函数可将多个基类型指定为给定内容类型的父类型。

开发人员可以定义自己的内容类型,并使用 IContentTypeRegistryService 注册它们。 许多编辑器功能都可以通过使用ContentTypeAttribute来定义特定内容类型。 例如,可以定义编辑器边距、装饰和鼠标处理程序,以便它们仅适用于显示特定内容类型的编辑器。

文本视图

模型视图控制器(MVC)模式的视图部分定义了文本视图、视图的格式、以及图形元素(如滚动条和光标)。 Visual Studio 编辑器的所有呈现元素都基于 WPF。

文本视图

ITextView 接口是文本视图的平台无关的表示形式。 它主要用于在窗口中显示文本文档,但也可用于其他目的,例如在工具提示中。

文本视图引用不同类型的文本缓冲区。 该 TextViewModel 属性引用指向这三个不同的 ITextViewModel 文本缓冲区的对象:数据缓冲区,即顶级数据级缓冲区、编辑缓冲区、在其中进行编辑的缓冲区,以及显示在文本视图中的缓冲区。

文本格式化基于附加到基础文本缓冲区的分类器,并通过附加到文本视图的装饰提供程序进行修饰。

文本视图坐标系

文本视图坐标系指定文本视图中的位置。 在此坐标系中,x 值 0.0 对应于要显示的文本的左边缘,y 值 0.0 对应于要显示的文本的上边缘。 x 坐标从左到右增加,y 坐标从上到下增加。

视区(文本窗口中可见的文本部分)不能以与垂直滚动相同的方式水平滚动。 视口通过更改其左坐标进行水平滚动,使其与绘图表面同步移动。 但是,只能通过更改呈现的文本来垂直滚动视区,而这会触发 LayoutChanged 事件。

坐标系中的距离对应于逻辑像素。 如果在没有缩放转换的情况下显示文本呈现图面,则文本呈现坐标系中的一个单位对应于屏幕上的一个像素。

边距

ITextViewMargin 接口表示边距,并支持控制边距及其大小的可见性。 有四个预定义边距,它们名为“Top”、“Left”、“Right”和“Bottom”,并附加到视图的上边缘、下边缘、左边缘或右边缘。 这些边距是可以容纳其他边距的容器。 该接口定义返回边距大小和边距可见性的方法。 边距是视觉元素,它们为附加的文本视图提供了更多信息。 例如,行号边距显示文本视图的行号。 字形边距显示 UI 元素。

IWpfTextViewMarginProvider 接口处理边距的创建和放置。 可以将边距相对于其他边距进行排列。 优先级较高的边距靠近文本视图。 例如,如果有两个左边距 A 和边距 B,而边距 B 的优先级比边距 A 低,则边距 B 显示在边距 A 的左侧。

文本视图容器

IWpfTextViewHost 接口包含文本视图和任何伴随视图的修饰,例如滚动条。 文本视图主机还包含附加到视图边框的边距。

带格式的文本

文本视图中显示的文本由 ITextViewLine 对象组成。 每个文本视图行对应于文本视图中的一行文本。 基础文本缓冲区中的长行可以部分遮盖(如果未启用换行),也可以分解成多个文本视图行。 该 ITextViewLine 接口包含用于坐标与字符之间映射的方法和属性,以及可能与文本行关联的装饰功能。

ITextViewLine 对象是使用 IFormattedLineSource 接口创建的。 如果只是担心视图中当前显示的文本,则可以忽略格式设置源。 如果你对视图中未显示的文本格式感兴趣(例如,要支持格式文本剪切和粘贴),则可以用于 IFormattedLineSource 设置文本缓冲区中的文本格式。

文本视图每次格式化一个 ITextSnapshotLine

编辑器功能

编辑器的功能设计为使功能的定义与其实现分开。 编辑器包括以下功能:

  • 标记和分类器

  • 装饰品

  • 投影

  • Outlining

  • 鼠标和按键绑定

  • 操作和基元

  • IntelliSense

标记和分类器

标签是与一段文本关联的标记。 可以通过不同的方式显示它们,例如,使用文本着色、下划线、图形或弹出窗口。 分类器是一种标记。

其他类型的标签包括 TextMarkerTag 文本突出显示、OutliningRegionTag 大纲显示和ErrorTag 编译错误。

分类类型

IClassificationType接口表示等效类,该类是文本的抽象类别。 分类类型可以多继承自其他分类类型。 例如,编程语言分类可能包括“keyword”、“comment”和“identifier”,所有这些分类都继承自“code”。 自然语言分类类型可能包括“名词”、“动词”和“形容词”,这些类型都继承自“自然语言”。

Classifications

分类是特定分类类型的实例,通常跨越文本范围。 A ClassificationSpan 用于表示分类。 分类范围可视为涵盖特定文本范围的标签,并告知系统此文本跨度是特定分类类型。

分类器

IClassifier这是一种将文本分解为一组分类的机制。 必须为特定内容类型定义分类器,并为特定文本缓冲区实例化。 客户端必须实现 IClassifier 才能参与文本分类。

分类器聚合器

分类器聚合器是一种机制,可将一个文本缓冲区的所有分类器合并到一组分类中。 例如,C# 分类器和英语分类器都可以在 C# 文件中通过注释创建分类。 请考虑以下注释:

// This method produces a classifier

C# 分类器可能会将整个范围标记为注释,而英语分类器可能会将“produces”分类为“动词”,将“method”分类为“名词”。 聚合器生成一组非重叠分类,集的类型基于所有贡献。

分类器聚合器也是分类器,因为它将文本拆分为一组分类。 分类器聚合器还确保没有重叠的分类,并且分类已排序。 单个分类器可以自由返回任意一组分类,这些分类可以以任意顺序出现,并且可以以任何方式重叠。

分类格式设置和文本着色

文本格式是基于文本分类构建的功能的示例。 文本视图层使用它来确定应用程序中文本的显示。 文本格式化区域依赖于 WPF,但分类的逻辑定义不依赖于它。

分类格式是特定分类类型的一组格式属性。 这些格式继承自父分类类型的格式。

IClassificationFormatMap是分类类型到一组文本格式属性的映射。 编辑器中格式映射的实现处理分类格式的所有导出。

装饰品

装饰是与文本视图中字符的字体和颜色不直接相关的图形效果。 例如,在许多编程语言中用于标记无法编译代码的红色波浪线是一种嵌入式装饰元素,而工具提示则是弹出式装饰元素。 装饰派生自 UIElement 并实现 ITag。 专用的装饰标记类型包括SpaceNegotiatingAdornmentTag,用于占据视图中文字相同空间的装饰,以及ErrorTag,用于波浪下划线的装饰。

嵌入装饰是构成格式化文本视图一部分的图形。 它们按不同的 Z 顺序层进行组织。 有三个内置层,如下所示:文本、光标和选定内容。 但是,开发人员可以定义更多层,并按照彼此的顺序排列。 三种嵌入装饰是文本相对装饰(在文本移动时移动,在删除文本时删除)、视图相对装饰(这与视图的非文本部分有关)和所有者控制的装饰(开发人员必须管理其放置)。

弹出窗口装饰是显示在文本视图上方的小窗口中的图形,例如工具提示。

投影

投影是一种用于构造不同类型的文本缓冲区的技术,该缓冲区实际上不存储文本,而是合并其他文本缓冲区中的文本。 例如,投影缓冲区可用于连接其他两个缓冲区中的文本,并显示结果,就好像它只位于一个缓冲区中,或者隐藏一个缓冲区中的部分文本。 投影缓冲区可以充当另一个投影缓冲区的源缓冲区。 可以通过多种不同的方式构造一组由投影相关的缓冲区以重新排列文本。 (此类集也称为 缓冲区图。Visual Studio 文本大纲显示功能通过使用投影缓冲区隐藏折叠的文本来实现,ASP.NET 页面的 Visual Studio 编辑器使用投影来支持嵌入式语言,如 Visual Basic 和 C# 。

使用 IProjectionBufferFactoryService 可以创建 IProjectionBuffer。 投影缓冲区由称为ITrackingSpan对象的有序序列表示。 这些范围的内容显示为字符序列。 将源范围提取出来的文本缓冲区称为源缓冲区。 投影缓冲区的客户端不必知道它与普通文本缓冲区不同。

投影缓冲区侦听源缓冲区上的文本更改事件。 当源范围中的文本发生更改时,投影缓冲区会将更改的文本坐标映射到自己的坐标,并引发相应的文本更改事件。 例如,请考虑包含以下内容的源缓冲区 A 和 B:

A: ABCDE
B: vwxyz

如果投影缓冲区 P 由两个文本范围构成,一个包含所有缓冲区 A,另一个包含所有缓冲区 B,则 P 具有以下内容:

P: ABCDEvwxyz

如果从缓冲区 B 中删除了子字符串 xy ,则缓冲区 P 将引发一个事件,该事件指示删除位置 7 和 8 处的字符。

也可以直接编辑投影缓冲区。 它将编辑传播到相应的源缓冲区。 例如,如果将字符串插入缓冲区 P 位置 6(字符“v”的原始位置),则插入将传播到位置 1 处的缓冲区 B。

源范围有一些限制,这些限制会影响到投影缓冲区的使用。 源范围可能不会重叠;投影缓冲区中的位置不能映射到任何源缓冲区中的多个位置,源缓冲区中的位置不能映射到投影缓冲区中的多个位置。 源缓冲区关系中不允许循环。

当投影缓冲区的源缓冲区集合或源跨度集合发生变化时,会触发事件。 省略缓冲区是一种特殊的投影缓冲区。 它主要用于大纲显示以及展开和折叠文本块的操作。 省略缓冲区是基于一个源缓冲区的,并且省略缓冲区中的区段顺序必须与源缓冲区中的顺序相同。

缓冲区图

IBufferGraph 接口允许跨投影缓冲区图进行映射。 所有文本缓冲区和投影缓冲区都收集在定向无环图中,这与语言编译器生成的抽象语法树非常类似。 图形由顶部缓冲区定义,可以是任何文本缓冲区。 缓冲区图可以从顶部缓冲区的点映射到源缓冲区中的某个点,也可以从顶部缓冲区中的范围映射到源缓冲区中的一组范围。 同样,它可以将点或范围从源缓冲区映射到顶部缓冲区中的某个点。 缓冲图是使用IBufferGraphFactoryService创建的。

事件和投影缓冲区

修改投影缓冲区时,修改将从投影缓冲区发送到依赖于它的缓冲区。 修改所有缓冲区后,将引发缓冲区更改事件,从最深的缓冲区开始。

Outlining

大纲视图功能是指在文本视图中能够展开或折叠不同的文本块。 大纲定义为一种 ITag,与定义装饰的方式相同。 一 OutliningRegionTag 个标记,用于定义可以展开或折叠的文本区域。 若要使用大纲功能,必须导入IOutliningManagerService以获取IOutliningManager。 大纲管理器枚举、折叠和扩展不同的块,这些块表示为 ICollapsible 对象,并相应地引发事件。

鼠标绑定

鼠标绑定将鼠标移动关联到不同的命令。 鼠标绑定是使用 IMouseProcessorProvider定义的,键绑定是使用 IKeyProcessorProvider定义的。 自动IWpfTextViewHost会实例化所有绑定,并将其与视图中的鼠标事件相连接。

IMouseProcessor 接口包含不同鼠标事件的预处理和后处理事件处理程序。 若要处理其中一个事件,可以替代其中的 MouseProcessorBase一些方法。

编辑器操作

编辑器操作可用于自动与编辑器交互,便于编写脚本或用于其他目的。 可以导入IEditorOperationsFactoryService以访问指定的ITextView上的操作。 然后,可以使用这些对象以便修改所选内容、滚动视图或将插入符移动到视图的不同部分。

IntelliSense

IntelliSense 支持语句完成、签名帮助(也称为参数信息)、快速信息和灯泡。

语句补全功能提供方法名称、XML 元素以及其他编码或标记元素的潜在完成项的弹出列表。 通常,用户手势触发完成会话过程。 用户界面显示候选完成项的列表,用户可以选择一个或关闭列表。 ICompletionBroker 负责创建和触发 ICompletionSessionICompletionSource计算CompletionSet会话的完成项。

排查导入/导出问题:访问 MEF 组合错误日志

如果您尝试导入当前 VS 安装中不存在的项目,或者不正确地编写导入或导出,则可能会遇到问题。 查找和解决这些问题的主要方法是引用托管扩展框架(MEF)组合错误日志,该日志存储在 %localappdata%\Microsoft\VisualStudio[yourVSVersion]\ComponentModelCache\Microsoft.VisualStudio.Default.err