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
。
写起来好像比较复杂,不过我想上面的代码应该能很好的解释了。
希望这对其他人有帮助。
我目前正在试验 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
。
写起来好像比较复杂,不过我想上面的代码应该能很好的解释了。
希望这对其他人有帮助。