Roslyn Code Azione: come verificare se l’anteprima o l’esecuzione reale?

Attualmente sto sperimentando con Roslyn e Code Actions, Refactoring di codice più specifici. Sembra un po ‘facile, ma ho una difficoltà che non riesco a risolvere.

Le azioni di codice vengono eseguite una volta su un’area di lavoro fittizia come opzione di “anteprima”, in modo che sia ansible visualizzare le modifiche effettive prima di fare clic sull’azione ed eseguirla rispetto all’area di lavoro reale.

Ora mi sto occupando di alcune cose che Roslyn non può davvero fare (ancora), quindi sto facendo alcune modifiche tramite EnvDTE . Lo so, è brutto, ma non sono riuscito a trovare un altro modo.

Quindi il problema qui è: quando passo il mouse sopra la mia azione di codice, il codice viene eseguito come anteprima, e NON dovrebbe fare le modifiche di EnvDTE . Questi dovrebbero essere fatti solo quando avviene la vera esecuzione.

Ho creato un gist con un piccolo esempio del mio codice . Non ha davvero senso, ma dovrebbe mostrare quello che voglio raggiungere. Esegui alcune modifiche tramite roslyn, quindi fai qualcosa via EnvDTE , come cambiare la posizione del cursore. Ma ovviamente solo sulla vera esecuzione.

La parte pertinente per coloro che non possono fare clic sull’elenco:

 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 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; } 

È ansible scegliere di sovrascrivere ComputePreviewOperationsAsync per avere un comportamento diverso per le anteprime dal codice normale.

Ho trovato la soluzione al mio problema scavando più a fondo e tentativi ed errori dopo la risposta di Keven Pilch . Mi ha urtato nella giusta direzione.

La soluzione era di sostituire sia i metodi GetChangedSolutionAsync metodi GetChangedSolutionAsync nella mia CodeAction.

Qui la parte pertinente della mia CustomCodeAction , o la spiegazione completa qui .

 private readonly Func> _createChangedSolution; protected override async Task> 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 GetChangedSolutionAsync(CancellationToken cancellationToken) { const bool isPreview = false; return GetChangedSolutionWithPreviewAsync(cancellationToken, isPreview); } protected virtual Task GetChangedSolutionWithPreviewAsync(CancellationToken cancellationToken, bool isPreview) { return _createChangedSolution(cancellationToken, isPreview); } 

Il codice per creare l’azione rimane abbastanza simile, tranne che il bool è stato aggiunto e posso verificarlo quindi:

 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 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; } 

Perché quei due?
ComputePreviewOperationsAsync chiama il normale ComputeOperationsAsync , che internamente chiama ComputeOperationsAsync . Questo calcolo esegue GetChangedSolutionAsync . Quindi entrambi i modi – anteprima e non – finiscono in GetChangedSolutionAsync . Questo è quello che voglio veramente, chiamando lo stesso codice, ottenendo una soluzione molto simile, ma dando un flag bool se è in anteprima o no.
Quindi ho scritto la mia GetChangedSolutionWithPreviewAsync che uso invece. Ho sostituito la GetChangedSolutionAsync predefinita usando la mia funzione Ottieni personalizzata, quindi ComputePreviewOperationsAsync con un corpo completamente personalizzato. Invece di chiamare ComputeOperationsAsync , come quello predefinito, ho copiato il codice di quella funzione e modificato per utilizzare invece il mio GetChangedSolutionWithPreviewAsync .
Sembra piuttosto complicato da scritto, ma immagino che il codice sopra dovrebbe spiegarlo abbastanza bene.

Spero che questo aiuti le altre persone.