Procédure pas à pas : afficher la saisie semi-automatique des instructions

Vous pouvez implémenter l’achèvement de l’instruction basée sur la langue en définissant les identificateurs pour lesquels vous souhaitez fournir l’achèvement, puis en déclenchant une session d’achèvement. Vous pouvez définir la saisie semi-automatique des instructions dans le contexte d’un service de langage, définir votre propre extension de nom de fichier et le type de contenu, puis afficher la saisie semi-automatique pour ce type. Vous pouvez également déclencher la saisie semi-automatique pour un type de contenu existant, par exemple, « texte en clair ». Cette procédure pas à pas montre comment déclencher la saisie semi-automatique de l’instruction pour le type de contenu « texte en clair », qui est le type de contenu des fichiers texte. Le type de contenu « text » est l’ancêtre de tous les autres types de contenu, y compris le code et les fichiers XML.

La saisie semi-automatique de l’instruction est généralement déclenchée en tapant certains caractères, par exemple en tapant le début d’un identificateur tel que « using ». Il est généralement ignoré en appuyant sur la barre d’espace, l’onglet ou la touche Entrée pour valider une sélection. Vous pouvez implémenter les fonctionnalités IntelliSense qui se déclenchent lors de la saisie d’un caractère à l’aide d’un gestionnaire de commandes pour les séquences de touches (l’interface IOleCommandTarget ) et un fournisseur de gestionnaires qui implémente l’interface IVsTextViewCreationListener . Pour créer la source d’achèvement, qui est la liste des identificateurs qui participent à l’achèvement, implémentez l’interface ICompletionSource et un fournisseur de source d’achèvement (l’interface ICompletionSourceProvider ). Les fournisseurs sont des composants MEF (Managed Extensibility Framework). Ils sont responsables de l’exportation des classes source et du contrôleur et de l’importation de services et de répartiteurs( par exemple, le ITextStructureNavigatorSelectorService, qui permet la navigation dans la mémoire tampon de texte et le ICompletionBroker, qui déclenche la session d’achèvement.

Cette procédure pas à pas montre comment implémenter la saisie semi-automatique des instructions pour un ensemble codé en dur d’identificateurs. Dans les implémentations complètes, le service linguistique et la documentation linguistique sont chargés de fournir ce contenu.

Créer un projet MEF

Pour créer un projet MEF

  1. Créez un projet VSIX C#. (Dans le Boîte de dialogue Nouveau projet , sélectionnez Visual C# / Extensibilité, puis VSIX Project.) Nommez la solution CompletionTest.

  2. Ajoutez un modèle d’élément Classifieur d’éditeur au projet. Pour plus d’informations, consultez Créer une extension avec un modèle d’élément d’éditeur.

  3. Supprimez les fichiers de classe existants.

  4. Ajoutez les références suivantes au projet et vérifiez que CopyLocal est défini sur false:

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.15.0

    Microsoft.VisualStudio.Shell.Immutable.10.0

    Microsoft.VisualStudio.TextManager.Interop

Implémenter la source d’achèvement

La source d’achèvement est chargée de collecter l’ensemble d’identificateurs et d’ajouter le contenu à la fenêtre d’achèvement lorsqu’un utilisateur tape un déclencheur d’achèvement, par exemple les premières lettres d’un identificateur. Dans cet exemple, les identificateurs et leurs descriptions sont codés en dur dans la AugmentCompletionSession méthode. Dans la plupart des utilisations réelles, vous utiliseriez l’analyseur de votre langue pour obtenir les jetons pour remplir la liste de saisie semi-automatique.

Pour implémenter la source d’achèvement

  1. Ajoutez un fichier de classe et nommez-le TestCompletionSource.

  2. Ajoutez ces importations :

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio.Utilities;
    
  3. Modifiez la déclaration de classe pour TestCompletionSource qu’elle implémente ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Ajoutez des champs privés pour le fournisseur source, la mémoire tampon de texte et une liste d’objets Completion (qui correspondent aux identificateurs qui participeront à la session d’achèvement) :

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Ajoutez un constructeur qui définit le fournisseur source et la mémoire tampon. La TestCompletionSourceProvider classe est définie dans les étapes ultérieures :

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implémentez la AugmentCompletionSession méthode en ajoutant un jeu d’achèvement qui contient les achèvements que vous souhaitez fournir dans le contexte. Chaque jeu d’achèvements contient un ensemble d’achèvements Completion et correspond à un onglet de la fenêtre d’achèvement. (Dans les projets Visual Basic, les onglets de la fenêtre d’achèvement sont nommés Common and All.) La FindTokenSpanAtPosition méthode est définie à l’étape suivante.

    void ICompletionSource.AugmentCompletionSession(ICompletionSession session, IList<CompletionSet> completionSets)
    {
        List<string> strList = new List<string>();
        strList.Add("addition");
        strList.Add("adaptation");
        strList.Add("subtraction");
        strList.Add("summation");
        m_compList = new List<Completion>();
        foreach (string str in strList)
            m_compList.Add(new Completion(str, str, str, null, null));
    
        completionSets.Add(new CompletionSet(
            "Tokens",    //the non-localized title of the tab
            "Tokens",    //the display title of the tab
            FindTokenSpanAtPosition(session.GetTriggerPoint(m_textBuffer),
                session),
            m_compList,
            null));
    }
    
  7. La méthode suivante permet de rechercher le mot actuel à partir de la position du curseur :

    private ITrackingSpan FindTokenSpanAtPosition(ITrackingPoint point, ICompletionSession session)
    {
        SnapshotPoint currentPoint = (session.TextView.Caret.Position.BufferPosition) - 1;
        ITextStructureNavigator navigator = m_sourceProvider.NavigatorService.GetTextStructureNavigator(m_textBuffer);
        TextExtent extent = navigator.GetExtentOfWord(currentPoint);
        return currentPoint.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive);
    }
    
  8. Implémentez la Dispose() méthode :

    private bool m_isDisposed;
    public void Dispose()
    {
        if (!m_isDisposed)
        {
            GC.SuppressFinalize(this);
            m_isDisposed = true;
        }
    }
    

Implémenter le fournisseur source d’achèvement

Le fournisseur de source d’achèvement est la partie du composant MEF qui instancie la source d’achèvement.

Pour implémenter le fournisseur source d’achèvement

  1. Ajoutez une classe nommée TestCompletionSourceProvider qui implémente ICompletionSourceProvider. Exportez cette classe avec un ContentTypeAttribute « texte en clair » et un NameAttribute « test d’achèvement ».

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Importez un ITextStructureNavigatorSelectorServicemot actif dans la source de saisie semi-automatique.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implémentez la TryCreateCompletionSource méthode pour instancier la source d’achèvement.

    public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
    {
        return new TestCompletionSource(this, textBuffer);
    }
    

Implémenter le fournisseur de gestionnaires de commandes d’achèvement

Le fournisseur de gestionnaires de commandes de saisie semi-automatique est dérivé d’un IVsTextViewCreationListenerévénement de création d’affichage de texte et convertit l’affichage à partir d’un IVsTextView( qui permet l’ajout de la commande à la chaîne de commandes de l’interpréteur de commandes Visual Studio) en un ITextView. Étant donné que cette classe est une exportation MEF, vous pouvez également l’utiliser pour importer les services requis par le gestionnaire de commandes lui-même.

Pour implémenter le fournisseur de gestionnaires de commandes d’achèvement

  1. Ajoutez un fichier nommé TestCompletionCommandHandler.

  2. Ajoutez ces directives using :

    using System;
    using System.ComponentModel.Composition;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Editor;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.Utilities;
    
  3. Ajoutez une classe nommée TestCompletionHandlerProvider qui implémente IVsTextViewCreationListener. Exportez cette classe avec un NameAttribute « gestionnaire de saisie semi-automatique de jeton », un ContentTypeAttribute « texte en clair » et un TextViewRoleAttribute de Editable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Importez le IVsEditorAdaptersFactoryService, qui permet la conversion d’un IVsTextView vers un ITextView, un ICompletionBroker, et un SVsServiceProvider qui permet l’accès aux services Visual Studio standard.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implémentez la VsTextViewCreated méthode pour instancier le gestionnaire de commandes.

    public void VsTextViewCreated(IVsTextView textViewAdapter)
    {
        ITextView textView = AdapterService.GetWpfTextView(textViewAdapter);
        if (textView == null)
            return;
    
        Func<TestCompletionCommandHandler> createCommandHandler = delegate() { return new TestCompletionCommandHandler(textViewAdapter, textView, this); };
        textView.Properties.GetOrCreateSingletonProperty(createCommandHandler);
    }
    

Implémenter le gestionnaire de commandes d’achèvement

Étant donné que la saisie semi-automatique des instructions est déclenchée par des séquences de touches, vous devez implémenter l’interface IOleCommandTarget pour recevoir et traiter les séquences de touches qui déclenchent, valident et ignorent la session d’achèvement.

Pour implémenter le gestionnaire de commandes d’achèvement

  1. Ajoutez une classe nommée TestCompletionCommandHandler qui implémente IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Ajoutez des champs privés pour le gestionnaire de commandes suivant (auquel vous passez la commande), l’affichage texte, le fournisseur de gestionnaires de commandes (qui permet l’accès à différents services) et une session d’achèvement :

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Ajoutez un constructeur qui définit la vue de texte et les champs du fournisseur, puis ajoute la commande à la chaîne de commandes :

    internal TestCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, TestCompletionHandlerProvider provider)
    {
        this.m_textView = textView;
        this.m_provider = provider;
    
        //add the command to the command chain
        textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
    }
    
  4. Implémentez la QueryStatus méthode en transmettant la commande :

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implémentez la méthode Exec. Lorsque cette méthode reçoit une séquence de touches, elle doit effectuer l’une des opérations suivantes :

    • Autorisez l’écriture du caractère dans la mémoire tampon, puis déclenchez ou filtrez l’achèvement. (Les caractères d’impression effectuent cette opération.)

    • Validez l’achèvement, mais n’autorisez pas l’écriture du caractère dans la mémoire tampon. (Espace blanc, Onglet et Entrée effectuez cette opération lorsqu’une session de saisie semi-automatique est affichée.)

    • Autoriser l’envoi de la commande au gestionnaire suivant. (Toutes les autres commandes.)

      Étant donné que cette méthode peut afficher l’interface utilisateur, appelez IsInAutomationFunction pour vous assurer qu’elle n’est pas appelée dans un contexte d’automatisation :

      public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
      {
          if (VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider))
          {
              return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
          }
          //make a copy of this so we can look at it after forwarding some commands
          uint commandID = nCmdID;
          char typedChar = char.MinValue;
          //make sure the input is a char before getting it
          if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
          {
              typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
          }
      
          //check for a commit character
          if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
              || nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB
              || (char.IsWhiteSpace(typedChar) || char.IsPunctuation(typedChar)))
          {
              //check for a selection
              if (m_session != null && !m_session.IsDismissed)
              {
                  //if the selection is fully selected, commit the current session
                  if (m_session.SelectedCompletionSet.SelectionStatus.IsSelected)
                  {
                      m_session.Commit();
                      //also, don't add the character to the buffer
                      return VSConstants.S_OK;
                  }
                  else
                  {
                      //if there is no selection, dismiss the session
                      m_session.Dismiss();
                  }
              }
          }
      
          //pass along the command so the char is added to the buffer
          int retVal = m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
          bool handled = false;
          if (!typedChar.Equals(char.MinValue) && char.IsLetterOrDigit(typedChar))
          {
              if (m_session == null || m_session.IsDismissed) // If there is no active session, bring up completion
              {
                  this.TriggerCompletion();
                  m_session.Filter();
              }
              else    //the completion session is already active, so just filter
              {
                  m_session.Filter();
              }
              handled = true;
          }
          else if (commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE   //redo the filter if there is a deletion
              || commandID == (uint)VSConstants.VSStd2KCmdID.DELETE)
          {
              if (m_session != null && !m_session.IsDismissed)
                  m_session.Filter();
              handled = true;
          }
          if (handled) return VSConstants.S_OK;
          return retVal;
      }
      

  6. Ce code est une méthode privée qui déclenche la session d’achèvement :

    private bool TriggerCompletion()
    {
        //the caret must be in a non-projection location 
        SnapshotPoint? caretPoint =
        m_textView.Caret.Position.Point.GetPoint(
        textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);
        if (!caretPoint.HasValue)
        {
            return false;
        }
    
        m_session = m_provider.CompletionBroker.CreateCompletionSession
     (m_textView,
            caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive),
            true);
    
        //subscribe to the Dismissed event on the session 
        m_session.Dismissed += this.OnSessionDismissed;
        m_session.Start();
    
        return true;
    }
    
  7. L’exemple suivant est une méthode privée qui se désabonne de l’événement Dismissed :

    private void OnSessionDismissed(object sender, EventArgs e)
    {
        m_session.Dismissed -= this.OnSessionDismissed;
        m_session = null;
    }
    

Générer et tester le code

Pour tester ce code, générez la solution CompletionTest et exécutez-la dans l’instance expérimentale.

Pour générer et tester la solution CompletionTest

  1. Générez la solution.

  2. Lorsque vous exécutez ce projet dans le débogueur, une deuxième instance de Visual Studio est démarrée.

  3. Créez un fichier texte et tapez du texte incluant le mot « ajouter ».

  4. Lorsque vous tapez d’abord « a », puis « d », une liste qui contient « addition » et « adaptation » doit apparaître. Notez que l’ajout est sélectionné. Lorsque vous tapez un autre « d », la liste doit contenir uniquement « addition », qui est maintenant sélectionnée. Vous pouvez valider « addition » en appuyant sur La barre d’espace, l’onglet ou la touche Entrée , ou ignorer la liste en tapant Échap ou toute autre touche.