在保留插入符号的同时替换文档中的文本

Replacing text in document while preserving the caret

我正在开发一个扩展,它使用外部程序来格式化内部代码 Visual Studio。

如果我使用 ITextEdit.Replace(...) 替换文件内容,插入符号将放在文档末尾,这是错误的。

我想做的是在替换文件内容之前在文本缓冲区中保存当前插入符位置的快照,然后将插入符位置设置为它之前在替换内容之前位于文本缓冲区中。

但是,ITextEdit.Apply() 正在生成一个新快照,导致 _textView.Caret.MoveTo(point) 抛出一个例外:

System.ArgumentException: The supplied SnapshotPoint is on an incorrect snapshot.
Parameter name: bufferPosition
at Microsoft.VisualStudio.Text.Editor.Implementation.CaretElement.InternalMoveTo(VirtualSnapshotPoint bufferPosition, PositionAffinity caretAffinity, Boolean captureHorizontalPosition, Boolean captureVerticalPosition, Boolean raiseEvent)
at Microsoft.VisualStudio.Text.Editor.Implementation.CaretElement.MoveTo(SnapshotPoint bufferPosition)

我也试过创建一个新的快照点而不是使用 _textView.Caret.Position.BufferPosition,像这样:

var point = new SnapshotPoint(_textView.TextSnapshot, 0);

抛出相同的 "The supplied SnapshotPoint is on an incorrect snapshot." 异常。

public class MyCommand
{
    private readonly IWpfTextView _textView;
    private readonly MyFormatter _formatter;
    private readonly ITextDocument _document;

    public MyCommand(IWpfTextView textView, MyFormatter formatter, ITextDocument document)
    {
        _textView = textView;
        _formatter = formatter;
        _document = document;
    }

    public void Format()
    {
        var input = _document.TextBuffer.CurrentSnapshot.GetText();
        var output = _formatter.format(input);

        // get caret snapshot point
        var point = _textView.Caret.Position.BufferPosition;

        using (var edit = _document.TextBuffer.CreateEdit())
        {
            edit.Replace(0, _document.TextBuffer.CurrentSnapshot.Length, output);
            edit.Apply();
        }

        // set caret position
        _textView.Caret.MoveTo(point);
    }
}

我不想实现一些自定义插入符号 "history",我想按预期的方式进行。 此外,我希望将移动插入符视为编辑的一部分,从而保持 "ctrl+z" 功能完整。

一如既往,非常感谢您的帮助!

您可以检索该点的位置,然后创建一个新的 SnapshotPoint 并移动到它。

像这样:

var point = _textView.Caret.Position.BufferPosition;
int position = point.Position;

        using (var edit = _document.TextBuffer.CreateEdit())
        {
            edit.Replace(0, _document.TextBuffer.CurrentSnapshot.Length, output);
            edit.Apply();
        }

        // set caret position
        _textView.Caret.MoveTo(new SnapshotPoint(_textView.TextSnapshot, position));

此外,您可以像这样创建和使用扩展程序: https://github.com/jaredpar/EditorUtils/blob/master/Src/EditorUtils/Extensions.cs

虽然 Cole Wu - MSFT 对我的问题的回答帮助我解决了我的问题,但它并没有完全符合我的要求:

使用提供的代码可以移动插入符号,但是由于我替换了文档的全部内容,即使插入符号位于文档的下方,它也会将滚动位置重置为顶部。

此外:这仍然生成 "multiple undos" 而不是仅生成一个:即替换文档的内容和移动插入符号是两个单独的 "undos",这要求用户按两次 CTRL + Z 而不是一次来撤消替换。

为了解决这个问题,我将代码更改为以下内容:

public class MyCommand
{
    private readonly IWpfTextView _textView;
    private readonly MyFormatter _formatter;
    private readonly ITextDocument _document;
    private readonly ITextBufferUndoManager _undoManager;

    public MyCommand(IWpfTextView textView, MyFormatter formatter, ITextDocument document, ITextBufferUndoManager undoManager)
    {
        _textView = textView;
        _formatter = formatter;
        _document = document;
        _undoManager = undoManager;
    }

    public void Format()
    {
        var input = _textView.TextSnapshot.GetText();
        var output = _formatter.format(input);

        using (var undo = _undoManager.TextBufferUndoHistory.CreateTransaction("Format"))
        using (var edit = _undoManager.TextBuffer.CreateEdit(EditOptions.DefaultMinimalChange, 0, null))
        {
            edit.Replace(0, _textView.TextSnapshot.Length, output);
            edit.Apply();

            undo.Complete();
        }
    }
}

这并不完全我想要的方式,因为插入符号有时(当位于尾随空格处时)跳到开头下一行而不是当前行的末尾。

不过,已经很接近了!