Procédure pas à pas : Afficher les suggestions d’ampoule
Les ampoules sont des icônes dans l’éditeur Visual Studio qui s’étendent pour afficher un ensemble d’actions, par exemple, des correctifs pour les problèmes identifiés par les analyseurs de code intégrés ou la refactorisation du code.
Dans les éditeurs Visual C# et Visual Basic, vous pouvez également utiliser la plateforme du compilateur .NET (« Roslyn ») pour écrire et empaqueter vos propres analyseurs de code avec des actions qui affichent automatiquement des ampoules. Pour plus d’informations, consultez :
Comment faire : écrire un diagnostic et un correctif de code Visual Basic
D’autres langages tels que C++ fournissent également des ampoules pour certaines actions rapides, telles qu’une suggestion de création d’une implémentation stub de cette fonction.
Voici à quoi ressemble une ampoule. Dans un projet Visual Basic ou Visual C#, un soulignement ondulé rouge apparaît sous un nom de variable lorsqu’elle n’est pas valide. Si vous placez la souris sur l’identificateur non valide, une ampoule apparaît près du curseur.
ampoule
Si vous cliquez sur la flèche vers le bas par l’ampoule, un ensemble d’actions suggérées s’affiche, ainsi qu’un aperçu de l’action sélectionnée. Dans ce cas, il affiche les modifications apportées à votre code si vous exécutez l’action.
Vous pouvez utiliser des ampoules pour fournir vos propres actions suggérées. Par exemple, vous pouvez fournir des actions pour déplacer les accolades ouvrantes vers une nouvelle ligne ou les déplacer vers la fin de la ligne précédente. La procédure pas à pas suivante montre comment créer une ampoule qui apparaît sur le mot actuel et a deux actions suggérées : Convertir en majuscules et Convertir en minuscules.
Créer un projet MEF (Managed Extensibility Framework)
Créez un projet VSIX C#. (Dans la boîte de dialogue Nouveau projet, sélectionnez Visual C# / Extensibilité, puis projet VSIX.) Nommez la solution
LightBulbTest
.Ajoutez un modèle d'élément de classifieur d’éditeur au projet. Pour plus d’informations, consultez Créer une extension avec un modèle d’élément d’éditeur.
Supprimez les fichiers de classe existants.
Ajoutez la référence suivante au projet et définissez Copie locale sur
False
:Microsoft.VisualStudio.Language.Intellisense
Ajoutez un nouveau fichier de classe et nommez-le LightBulbTest.
Ajoutez les directives using suivantes :
using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; using System.Threading;
Implémenter le fournisseur de source d’ampoule
Dans le fichier de classe LightBulbTest.cs, supprimez la classe LightBulbTest. Ajoutez une classe nommée TestSuggestedActionsSourceProvider qui implémente ISuggestedActionsSourceProvider. Exportez-la avec le nom Test Actions Suggérées et un ContentTypeAttribute de « texte ».
[Export(typeof(ISuggestedActionsSourceProvider))] [Name("Test Suggested Actions")] [ContentType("text")] internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
Dans la classe de fournisseur source, importez le ITextStructureNavigatorSelectorService et ajoutez-le en tant que propriété.
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
Implémentez la méthode CreateSuggestedActionsSource pour retourner un objet ISuggestedActionsSource. La source est abordée dans la section suivante.
public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { if (textBuffer == null || textView == null) { return null; } return new TestSuggestedActionsSource(this, textView, textBuffer); }
Implémenter l'ISuggestedActionSource
La source d’action suggérée est chargée de collecter l’ensemble des actions suggérées et de les ajouter dans le contexte approprié. Dans ce cas, le contexte est le mot actuel et les actions suggérées sont UpperCaseSuggestedAction et LowerCaseSuggestedAction, qui est décrite dans la section suivante.
Ajoutez une classe TestSuggestedActionsSource qui implémente ISuggestedActionsSource.
internal class TestSuggestedActionsSource : ISuggestedActionsSource
Ajoutez des champs privés et en lecture seule pour le fournisseur de source d’action suggéré, la mémoire tampon de texte et la vue de texte.
private readonly TestSuggestedActionsSourceProvider m_factory; private readonly ITextBuffer m_textBuffer; private readonly ITextView m_textView;
Ajoutez un constructeur qui définit les champs privés.
public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer) { m_factory = testSuggestedActionsSourceProvider; m_textBuffer = textBuffer; m_textView = textView; }
Ajoutez une méthode privée qui retourne le mot qui se trouve actuellement sous le curseur. La méthode suivante examine l’emplacement actuel du curseur et demande au navigateur de structure de texte l’étendue du mot. Si le curseur se trouve sur un mot, la TextExtent est retournée dans le paramètre out ; sinon, le paramètre
out
estnull
et la méthode retournefalse
.private bool TryGetWordUnderCaret(out TextExtent wordExtent) { ITextCaret caret = m_textView.Caret; SnapshotPoint point; if (caret.Position.BufferPosition > 0) { point = caret.Position.BufferPosition - 1; } else { wordExtent = default(TextExtent); return false; } ITextStructureNavigator navigator = m_factory.NavigatorService.GetTextStructureNavigator(m_textBuffer); wordExtent = navigator.GetExtentOfWord(point); return true; }
Implémentez la méthode HasSuggestedActionsAsync. L’éditeur appelle cette méthode pour savoir s’il faut afficher l’ampoule. Cet appel est souvent effectué, par exemple, chaque fois que le curseur passe d’une ligne à une autre, ou lorsque la souris pointe sur une erreur de quiggle. Il est asynchrone pour permettre à d’autres opérations d’interface utilisateur de continuer pendant que cette méthode fonctionne. Dans la plupart des cas, cette méthode doit effectuer un parsing et une analyse de la ligne actuelle, de sorte que le traitement peut prendre un certain temps.
Dans cette implémentation, elle obtient de façon asynchrone le TextExtent et détermine si l’étendue est significative, comme dans le cas contraire, si elle a du texte autre que l’espace blanc.
public Task<bool> HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { TextExtent extent; if (TryGetWordUnderCaret(out extent)) { // don't display the action if the extent has whitespace return extent.IsSignificant; } return false; }); }
Implémentez la méthode GetSuggestedActions, qui retourne un tableau d’objets SuggestedActionSet qui contiennent les différents objets ISuggestedAction. Cette méthode est appelée lorsque l’ampoule est développée.
Avertissement
Vous devez vous assurer que les implémentations de
HasSuggestedActionsAsync()
et deGetSuggestedActions()
sont cohérentes ; autrement dit, siHasSuggestedActionsAsync()
retournetrue
,GetSuggestedActions()
doit avoir des actions à afficher. Dans de nombreux cas,HasSuggestedActionsAsync()
est appelé juste avantGetSuggestedActions()
, mais ce n’est pas toujours le cas. Par exemple, si l’utilisateur appelle les actions de l’ampoule en appuyant sur (Ctrl+ .) uniquementGetSuggestedActions()
est appelée.public IEnumerable<SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { TextExtent extent; if (TryGetWordUnderCaret(out extent) && extent.IsSignificant) { ITrackingSpan trackingSpan = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); var upperAction = new UpperCaseSuggestedAction(trackingSpan); var lowerAction = new LowerCaseSuggestedAction(trackingSpan); return new SuggestedActionSet[] { new SuggestedActionSet(new ISuggestedAction[] { upperAction, lowerAction }) }; } return Enumerable.Empty<SuggestedActionSet>(); }
Définissez un événement
SuggestedActionsChanged
.public event EventHandler<EventArgs> SuggestedActionsChanged;
Pour terminer l’implémentation, ajoutez des implémentations pour les méthodes
Dispose()
etTryGetTelemetryId()
. Vous ne souhaitez pas effectuer de télémétrie. Retournez simplementfalse
et définissez le GUID surEmpty
.public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample provider and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
Implémenter des actions Ampoule
Dans le projet, ajoutez une référence à Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll et définissez Copy Local sur
False
.Créez deux classes, la première nommée
UpperCaseSuggestedAction
et la seconde nomméeLowerCaseSuggestedAction
. Les deux classes implémentent ISuggestedAction.internal class UpperCaseSuggestedAction : ISuggestedAction internal class LowerCaseSuggestedAction : ISuggestedAction
Les deux classes sont identiques, sauf que l'une appelle ToUpper et l'autre appelle ToLower. Les étapes suivantes couvrent uniquement la classe d’action 'Uppercase', mais vous devez implémenter les deux classes. Utilisez les étapes d’implémentation de l’action majuscule en tant que modèle pour l’implémentation de l’action minuscule.
Ajoutez les directives d’utilisation suivantes pour ces classes :
using Microsoft.VisualStudio.Imaging.Interop; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
Déclarez un ensemble de champs privés.
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
Ajoutez un constructeur qui définit les champs.
public UpperCaseSuggestedAction(ITrackingSpan span) { m_span = span; m_snapshot = span.TextBuffer.CurrentSnapshot; m_upper = span.GetText(m_snapshot).ToUpper(); m_display = string.Format("Convert '{0}' to upper case", span.GetText(m_snapshot)); }
Implémentez la méthode GetPreviewAsync afin qu’elle affiche l’aperçu de l’action.
public Task<object> GetPreviewAsync(CancellationToken cancellationToken) { var textBlock = new TextBlock(); textBlock.Padding = new Thickness(5); textBlock.Inlines.Add(new Run() { Text = m_upper }); return Task.FromResult<object>(textBlock); }
Implémentez la méthode GetActionSetsAsync afin qu’elle retourne une énumération SuggestedActionSet vide.
public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<SuggestedActionSet>>(null); }
Implémentez les propriétés comme suit.
public bool HasActionSets { get { return false; } } public string DisplayText { get { return m_display; } } public ImageMoniker IconMoniker { get { return default(ImageMoniker); } } public string IconAutomationText { get { return null; } } public string InputGestureText { get { return null; } } public bool HasPreview { get { return true; } }
Implémentez la méthode Invoke en remplaçant le texte dans l’étendue par son équivalent en majuscules.
public void Invoke(CancellationToken cancellationToken) { m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper); }
Avertissement
La méthode Appeler de l’action Ampoule ne doit pas afficher l’interface utilisateur. Si votre action affiche une nouvelle interface utilisateur (par exemple, une boîte de dialogue d’aperçu ou de sélection), n’affichez pas l’interface utilisateur directement à partir de la méthode Invoke, mais planifiez plutôt l’affichage de votre interface utilisateur après le retour de Invoke.
Pour terminer l’implémentation, ajoutez les méthodes
Dispose()
etTryGetTelemetryId()
.public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample action and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
N’oubliez pas de faire la même chose pour
LowerCaseSuggestedAction
en modifiant le texte d’affichage en « Convertir '{0}' en minuscules » et en changeant l’appel de ToUpper en ToLower.
Générer et tester le code
Pour tester ce code, générez la solution LightBulbTest et exécutez-la dans l’instance expérimentale.
Générez la solution.
Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.
Créez un fichier texte et tapez du texte. Vous devriez voir une ampoule à gauche du texte.
Pointez sur l’ampoule. Vous devriez voir une flèche vers le bas.
Lorsque vous cliquez sur l’ampoule, deux actions suggérées doivent s’afficher, ainsi que l’aperçu de l’action sélectionnée.
Si vous cliquez sur la première action, tout le texte du mot actuel doit être converti en majuscules. Si vous cliquez sur la deuxième action, tout le texte doit être converti en minuscules.