Roslyn Code Action:如何检查是预览还是实际执行?

Roslyn Code Action: How to check if preview or real execution?

我目前正在试验 Roslyn 和代码操作,更具体的代码重构。 感觉有点容易,但是我有一个难点我解决不了。

作为 "preview" 选项针对虚拟工作区执行一次代码操作,以便您可以在单击操作并针对真实工作区执行之前查看实际更改。

现在我正在处理一些 Roslyn 还不能真正做的事情,所以我正在通过 EnvDTE 做一些改变。我知道,这很糟糕,但我找不到其他方法。

所以这里的问题是: 当我将鼠标悬停在我的代码操作上时,代码将作为预览执行,它不应该进行 EnvDTE 更改。这些应该只在真正执行时才完成。

我创建了一个 gist with a small example of my code。它并没有真正意义,但应该显示我想要实现的目标。通过 roslyn 做一些修改,然后通过 EnvDTE 做一些事情,比如改变光标位置。但当然只是在真正的执行上。

无法点开要点的相关部分:

public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
    var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
    var node = root.FindNode(context.Span);

    var dec = node as MethodDeclarationSyntax;
    if (dec == null)
        return;

    context.RegisterRefactoring(CodeAction.Create("MyAction", c => DoMyAction(context.Document, dec, c)));
}

private static async Task<Solution> DoMyAction(Document document, MethodDeclarationSyntax method, CancellationToken cancellationToken)
{
    var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken);
    var root = await syntaxTree.GetRootAsync(cancellationToken);

    // some - for the question irrelevant - roslyn changes, like:
    document = document.WithSyntaxRoot(root.ReplaceNode(method, method.WithIdentifier(SyntaxFactory.ParseToken(method.Identifier.Text + "Suffix"))));

    // now the DTE magic
    var preview = false; // <--- TODO: How to check if I am in preview here?
    if (!preview)
    {
        var requestedItem = DTE.Solution.FindProjectItem(document.FilePath);
        var window = requestedItem.Open(Constants.vsViewKindCode);
        window.Activate();

        var position = method.Identifier.GetLocation().GetLineSpan().EndLinePosition;
        var textSelection = (TextSelection) window.Document.Selection;
        textSelection.MoveTo(position.Line, position.Character);
    }

    return document.Project.Solution;
}

您可以选择覆盖 ComputePreviewOperationsAsync 以使预览具有与常规代码不同的行为。

我在 Keven Pilch 的回答后通过更深入的挖掘和反复试验找到了解决我的问题的方法。他把我撞到了正确的方向。

解决方案是在我自己的 CodeAction 中覆盖这两个 ComputePreviewOperationsAsync and the GetChangedSolutionAsync 方法。

这里是我的相关部分CustomCodeAction,或者full gist here

private readonly Func<CancellationToken, bool, Task<Solution>> _createChangedSolution;

protected override async Task<IEnumerable<CodeActionOperation>> ComputePreviewOperationsAsync(CancellationToken cancellationToken)
{
    const bool isPreview = true;
    // Content copied from http://sourceroslyn.io/#Microsoft.CodeAnalysis.Workspaces/CodeActions/CodeAction.cs,81b0a0866b894b0e,references
    var changedSolution = await GetChangedSolutionWithPreviewAsync(cancellationToken, isPreview).ConfigureAwait(false);
    if (changedSolution == null)
        return null;

    return new CodeActionOperation[] { new ApplyChangesOperation(changedSolution) };
}

protected override Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
    const bool isPreview = false;
    return GetChangedSolutionWithPreviewAsync(cancellationToken, isPreview);
}

protected virtual Task<Solution> GetChangedSolutionWithPreviewAsync(CancellationToken cancellationToken, bool isPreview)
{
    return _createChangedSolution(cancellationToken, isPreview);
}

创建动作的代码非常相似,只是添加了 bool,然后我可以检查它:

public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
    // [...]

    context.RegisterRefactoring(CustomCodeAction.Create("MyAction",
        (c, isPreview) => DoMyAction(context.Document, dec, c, isPreview)));
}

private static async Task<Solution> DoMyAction(Document document, MethodDeclarationSyntax method, CancellationToken cancellationToken, bool isPreview)
{
    // some - for the question irrelevant - roslyn changes, like:
    // [...]

    // now the DTE magic
    if (!isPreview)
    {
        // [...]
    }

    return document.Project.Solution;
}

为什么是那两个?
ComputePreviewOperationsAsync 调用正常的 ComputeOperationsAsync,后者在内部调用 ComputeOperationsAsync。此计算执行 GetChangedSolutionAsync。因此,无论是预览还是非预览,都以 GetChangedSolutionAsync 结束。这就是我真正想要的,调用相同的代码,得到一个非常相似的解决方案,但如果它是预览或不是,则给出一个 bool 标志。
所以我写了我自己的 GetChangedSolutionWithPreviewAsync 来代替。我已经使用我的自定义 Get 函数覆盖了默认值 GetChangedSolutionAsync,然后使用完全自定义的主体覆盖了 ComputePreviewOperationsAsync。我没有调用默认调用的 ComputeOperationsAsync,而是复制了该函数的代码,并将其修改为使用我的 GetChangedSolutionWithPreviewAsync
写起来好像比较复杂,不过我想上面的代码应该能很好的解释了。

希望这对其他人有帮助。