在编辑器中更改文本
编辑(即在 Visual Studio 编辑器中打开的文本文档的更改)可能来自 Visual Studio 中的用户交互,或者语言服务和其他扩展的编程更改。 扩展必须准备好处理实时发生的文档文本更改。
在主 Visual Studio IDE 进程外部运行的扩展使用异步设计模式与 Visual Studio IDE 进程进行通信。 这意味着使用异步方法调用,如 C# 中的 async
关键字指示,并由方法名称上的 Async
后缀强化。 异步性在需要响应用户操作的编辑器中具有显著优势。 传统的同步 API 调用(如果花费的时间超过预期)将停止响应用户输入,从而创建一个持续到 API 调用完成的 UI 冻结。 用户对新式交互式应用程序的期望是,文本编辑器始终保持响应状态,并且永远不会阻止它们正常工作。 因此,使扩展是异步的,以满足用户期望至关重要。
有关异步编程的详细信息,请参阅使用 Async 和 Await 的异步编程。
在新的 Visual Studio 扩展性模型中,扩展是相对于用户的第二类:它不能直接修改编辑器或文本文档。 所有状态更改都是异步和协作的,Visual Studio IDE 代表扩展执行请求的更改。 该扩展可以请求对文档或文本视图的特定版本进行一个或多个更改,但可能会拒绝来自扩展的更改,例如文档的该区域是否已更改。
通过在 EditorExtensibility
上使用 EditAsync()
方法请求编辑。
如果熟悉旧版 Visual Studio 扩展,ITextDocumentEditor
与从 ITextBuffer 和 ITextDocument 中更改状态的方法几乎相同,并支持大多数相同的功能。
MutationResult result = await this.Extensibility.Editor().EditAsync(
batch =>
{
var editor = document.AsEditable(batch);
editor.Replace(textView.Selection.Extent, newGuidString);
},
cancellationToken);
为避免错置编辑,将按以下方式应用编辑器扩展的编辑:
- 扩展请求根据文档的最新版本进行编辑。
- 该请求可包含一个或多个文本编辑、插入点位置更改等内容。 任何实现
IEditable
的类型都可以在单个EditAsync()
请求中更改,包括ITextViewSnapshot
和ITextDocumentSnapshot
。 编辑由编辑器完成,可通过AsEditable()
在特定类上请求编辑。 - 编辑请求被发送到 Visual Studio IDE,只有当被修改的对象自请求发出以来没有发生变化时,编辑才会成功。 如果文档已更改,可能会拒绝更改,要求扩展在较新版本上重试。 突变运算的结果存储在
result
中。 - 编辑以原子方式应用,这意味着不会中断其他执行线程。 最佳做法是将所有应在短时间内完成的更改整合到一个单一的
EditAsync()
调用中,以减少因用户编辑或语言服务操作在两次编辑之间发生(例如,扩展编辑与 Roslyn C# 移动插入点的操作交错进行)而导致意外行为的可能性。
更改插入点位置或从扩展中选择文本
从扩展编辑文本文档会隐式影响插入点位置。 例如,在插入点处插入某些文本会将插入点移动到插入的文本的末尾。 扩展还可以使用 ITextViewSnapshot.AsEditable().SetSelections()
显式将插入点设置到不同的位置,或进行文本选择。 为了说明,以下代码将插入一些文本,但会将插入点保持在原始位置:
await this.Extensibility.Editor().EditAsync(batch =>
{
var caret = textView.Selection.Extent.Start;
textView.Document.AsEditable(batch).Replace(textView.Selection.Extent, newGuidString);
textView.AsEditable(batch).SetSelections([new Selection(activePosition: caret, anchorPosition: caret, insertionPosition: caret)]);
},
cancellationToken);
并发执行
⚠️ | 编辑器扩展有时可以并发运行 |
---|
初始版本有一个已知问题,可能会导致编辑器扩展代码并发执行。 每个异步方法都保证按正确的顺序调用,但在第一个 await
之后的后续操作可能会交错。 如果扩展依赖于执行顺序,请考虑维护传入请求队列以保留顺序,直到此问题得到解决。
有关详细信息,请参阅 StreamJsonRpc 默认排序和并发。