1

i have implemented a custom CodeFixProvider that adds some XML documentation to members.

Example:

public void MyMethod() { }

will be transformed to

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

The CodeFixProvider is implemented like this:

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);
  }

  ...
}

Everything is working like expected.

Now i want to add some extra functionality: After applying the code fix, the caret should be moved inside the empty summary tag.

I discovered the DocumentNavigationOperation class included in Microsoft.CodeAnalysis.Features NuGet package. This class should be able to move the caret to a specified position. But I can't find any instructions how to use this class. If i call it from inside my CreateXmlDocs method, an exception is thrown:

Navigation must be performed on the foreground thread.

Code:

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

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

  ...
}

I'm not sure if it makes sense to use this class inside my CreateXmlDocs method, because the new solution created inside this method isn't yet applied by Visual Studio when DocumentNavigationOperation is called.

Does anybody knows a solution to move the caret after applying a code fix?

fss
  • 113
  • 1
  • 5
  • Roslyn is meant to work agnostic of IDE: it could use Visual Studio but might just as well be using the command line. In that sense it makes less sense to have IDE-related features in your code fix since that is an environment thing and you might want to look into making an extension instead. It's been a while since I really worked with Roslyn but unless things have changed, that will be your reason. – Jeroen Vannevel Feb 26 '18 at 16:18

1 Answers1

1

Ok, in the meantime i found a solution for this.

A custom CodeAction is required to get it working:

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; }

}

The new CodeAction can be used in the CodeFixProvider instead of 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);
  }

  ...
}
fss
  • 113
  • 1
  • 5
  • I found the technique of creating a custom CodeAction interesting and useful. I found that I had to override the property EquivalenceKey in a similar manner to the Title property to avoid warnings. – Phil Jollans May 02 '20 at 19:06