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

时间:2018-02-24 16:17:05

标签: c# roslyn codefixprovider

我已经实现了一个自定义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类。该类应该能够将插入符号移动到指定位置。但我找不到如何使用这个类的任何说明。如果我从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方法中使用此类是否有意义,因为在调用DocumentNavigationOperation时Visual Studio尚未应用此方法中创建的新解决方案。

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

1 个答案:

答案 0 :(得分:0)

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

要使其正常运行,需要使用自定义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; }

}

可以在CodeAction而不是CodeFixProvider中使用新的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);
  }

  ...
}