Roslyn Code Action: Как проверить предварительный просмотр или реальное исполнение?

2

В настоящее время я экспериментирую с 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;
}
  • 0
    Я искал это на днях, я закончил тем, что написал этот ужасный хак
  • 0
    Я думаю, это может сработать, да. Но я чувствую, что мое решение - это "чище", если можно так сказать.
Показать ещё 6 комментариев
Теги:
visual-studio
visual-studio-extensions
roslyn
roslyn-code-analysis

2 ответа

1
Лучший ответ

Я нашел решение своей проблемы, копая глубже и проб и ошибок после ответа Кевена Пилча. Он ударил меня в правильном направлении.

Решение заключалось в том, чтобы переопределить 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.
Звучит довольно сложно в письменной форме, но я думаю, что код выше должен объяснить это достаточно хорошо.

Надеюсь, это поможет другим людям.

  • 0
    Я не знаю, будет ли это решение работать, когда вы получаете изменения для всего документа
  • 0
    Я не понимаю Что ты конкретно имеешь ввиду?
Показать ещё 6 комментариев
2

Вы можете переопределить ComputePreviewOperationsAsync чтобы иметь другое поведение для предварительного просмотра из обычного кода.

  • 0
    Я видел, что это возможно, и даже пытался создать собственное действие кода с этим переопределением. Но эта функция должна возвращать IEnumerable<CodeActionOperation> , это полностью отличается от того, что делает обычный код в моем действии. Я не до конца понимаю, что мне там делать. У вас есть пример, как этого можно достичь?
  • 0
    Взгляните на переопределения в исходном браузере здесь, где Roslyn сам использует это.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню