Roslyn CodeFixProvider:应用代码修复后移动插入符号

Roslyn CodeFixProvider: Move caret after applying code fix

我已经实现了一个自定义 CodeFixProvider,它为成员添加了一些 XML 文档。

示例:

public void MyMethod() { }

将转换为

/// <summary></summary>
public void MyMethod() { }

CodeFixProvider是这样实现的:

public class MyCodeFixProvider : CodeFixProvider
{
  ...

  public async override Task RegisterCodeFixesAsync(CodeFixContext context)
  {
    await Task.Run(() =>
      {
        Diagnostics diagnostics = context.Diagnostics.First();
        CodeAction codeFix = CodeAction.Create("Title", c => CreateXmlDocs(...));

        context.RegisterCodeFix(codeFix, diagnostics);
      }
    ).ConfigureAwait(false);
  }

  ...
}

一切正常。

现在我想添加一些额外的功能:应用代码修复后,脱字符号应该移到空的摘要标签内。

我发现 Microsoft.CodeAnalysis.Features NuGet 包中包含 DocumentNavigationOperation class。这 class 应该能够将插入符号移动到指定位置。但是我找不到任何关于如何使用它的说明 class。如果我从我的 CreateXmlDocs 方法内部调用它,则会抛出异常:

Navigation must be performed on the foreground thread.

代码:

private static async Task<Solution> CreateXmlDocs()
{
  ...

  new DocumentNavigationOperation(newDocument.Id, 42)
    .Apply(newDocument.Project.Solution.Workspace, cancellationToken);

  ...
}

我不确定在我的 CreateXmlDocs 方法中使用此 class 是否有意义,因为在调用 DocumentNavigationOperation 时 Visual Studio 尚未应用此方法中创建的新解决方案.

有人知道应用代码修复后移动插入符号的解决方案吗?

好的,与此同时我找到了解决方案。

需要自定义 CodeAction 才能正常工作:

internal class NavigateAfterCodeChangeAction : CodeAction
{

  private readonly Func<CancellationToken, Task<Solution>> codeChangeOperation;

  private readonly Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation;

  public NavigateAfterCodeChangeAction(
    string title,
    Func<CancellationToken, Task<Solution>> codeChangeOperation,
    Func<Solution, CancellationToken, Task<NavigationTarget>> navigationTargetCalculation)
  {
    this.Title = title;
    this.codeChangeOperation = codeChangeOperation;
    this.navigationTargetCalculation = navigationTargetCalculation;
  }

  public override string Title { get; }

  protected override async Task<IEnumerable<CodeActionOperation>> ComputeOperationsAsync(CancellationToken cancellationToken)
  {
    var operations = new List<CodeActionOperation>();
    Solution changedSolution = await this.codeChangeOperation(cancellationToken);
    NavigationTarget navigationTarget = await this.navigationTargetCalculation(changedSolution, cancellationToken);

    operations.Add(new ApplyChangesOperation(changedSolution));

    if (navigationTarget != null)
    {
      operations.Add(new DocumentNavigationOperation(navigationTarget.DocumentId, navigationTarget.Position));
    }

    return operations;
  }
}

internal class NavigationTarget
{

  public NavigationTarget(DocumentId documentId, int position)
  {
    this.DocumentId = documentId;
    this.Position = position;
  }

  public DocumentId DocumentId { get; }

  public int Position { get; }

}

可以在CodeFixProvider中使用新的CodeAction代替CodeAction.Create():

public class MyCodeFixProvider : CodeFixProvider
{
  ...

  public async override Task RegisterCodeFixesAsync(CodeFixContext context)
  {
    await Task.Run(() =>
      {
        Diagnostics diagnostics = context.Diagnostics.First();
        CodeAction codeFix = new NavigateAfterCodeChangeAction(
          "Title",
          c => CreateXmlDocs(...)
          (s, c) => CalculateNavigationTarget(context.Document));

        context.RegisterCodeFix(codeFix, diagnostics);
      }
    ).ConfigureAwait(false);
  }

  private static NavigationTarget CalculateNavigationTarget(Document doc)
  {
    // Calculate the navigation target here...

    // Example: Navigate to position 42 of the document
    return new NavigationTarget(doc.Id, 42);
  }

  ...
}