Roslyn代码行动:如何检查预览或实际执行?

时间:2017-05-29 13:31:54

标签: c# visual-studio roslyn visual-studio-extensions roslyn-code-analysis

我目前正在尝试使用Roslyn和Code Actions,更具体的Code Refactorings。 这感觉很简单,但我有一个难以解决的问题。

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

现在我正在处理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;
}

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

我通过在Keven Pilch的答案之后深入挖掘和反复试验找到了我的问题的解决方案。他让我朝着正确的方向前进。

解决方案是覆盖我自己的CodeAction中的ComputePreviewOperationsAsyncGetChangedSolutionAsync方法。

这是我CustomCodeActionfull 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://source.roslyn.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代替。 写起来相当复杂,但我想上面的代码应该很好地解释它。

希望这有助于其他人。