Roslyn 代码操作:如何检查是预览还是实际执行?



我目前正在试验Roslyn和Code Actions,更具体的代码重构。 感觉很容易,但我有一个我无法解决的困难。

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

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

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

我用我的代码的小示例创建了一个要点。这并没有真正的意义,但应该显示我想要实现的目标。通过 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 中重写ComputePreviewOperationsAsyncGetChangedSolutionAsync方法。

这是我CustomCodeAction的相关部分,或完整的要点在这里。

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
写起来听起来很复杂,但我想上面的代码应该很好地解释它。

希望这对其他人有所帮助。

最新更新