В настоящее время я экспериментирую с Roslyn и Code Actions, более конкретными Code Refactorings. Это кажется легким, но у меня есть трудность, которую я не могу решить.
Действия с кодом выполняются один раз против фиктивной рабочей области в качестве опции "предварительного просмотра", чтобы вы могли видеть фактические изменения перед тем, как щелкнуть действие и выполнить его против реального рабочего пространства.
Теперь я имею дело с некоторыми вещами, которые Roslyn не может сделать (пока), поэтому я делаю некоторые изменения через EnvDTE
. Я знаю, это плохо, но я не мог найти другого пути.
Итак, проблема здесь: когда я наводил на себя действие над кодом, код запускается как предварительный просмотр, и он НЕ должен делать изменения EnvDTE
. Это нужно делать только тогда, когда происходит реальный казнь.
Я создал сущность с небольшим примером моего кода. Это действительно не имеет смысла, но я должен показать, чего я хочу достичь. Сделайте некоторые изменения через roslyn, затем сделайте что-то через EnvDTE
, например, 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
и GetChangedSolutionAsync
в моей собственной CodeAction.
Здесь соответствующая часть моего CustomCodeAction
, или полный 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://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
который я использую вместо этого. Я GetChangedSolutionAsync
значение по умолчанию GetChangedSolutionAsync
используя мою пользовательскую функцию Get, а затем ComputePreviewOperationsAsync
с полностью настроенным телом. Вместо вызова ComputeOperationsAsync
, который используется по умолчанию, я скопировал код этой функции и изменил его, чтобы вместо этого использовать GetChangedSolutionWithPreviewAsync
.
Звучит довольно сложно в письменной форме, но я думаю, что код выше должен объяснить это достаточно хорошо.
Надеюсь, это поможет другим людям.
Вы можете переопределить ComputePreviewOperationsAsync
чтобы иметь другое поведение для предварительного просмотра из обычного кода.
IEnumerable<CodeActionOperation>
, это полностью отличается от того, что делает обычный код в моем действии. Я не до конца понимаю, что мне там делать. У вас есть пример, как этого можно достичь?