Condividi tramite


Procedura dettagliata: Visualizzare il completamento istruzioni

È possibile implementare il completamento dell'istruzione basata sul linguaggio definendo gli identificatori per i quali si vuole fornire il completamento e quindi attivando una sessione di completamento. È possibile definire il completamento delle istruzioni nel contesto di un servizio linguistico, definire l'estensione del nome file e il tipo di contenuto e quindi visualizzare il completamento solo per quel tipo. In alternativa, è possibile attivare il completamento per un tipo di contenuto esistente, ad esempio "testo non crittografato". Questa procedura dettagliata illustra come attivare il completamento dell'istruzione per il tipo di contenuto "testo non crittografato", ovvero il tipo di contenuto dei file di testo. Il tipo di contenuto "text" è il predecessore di tutti gli altri tipi di contenuto, inclusi il codice e i file XML.

Il completamento dell'istruzione viene in genere attivato digitando determinati caratteri, ad esempio digitando l'inizio di un identificatore, ad esempio "using". In genere viene ignorato premendo la barra spaziatrice, tabulazione o invio per eseguire il commit di una selezione. È possibile implementare le funzionalità di IntelliSense che si attivano quando si digita un carattere usando un gestore comandi per le sequenze di tasti (l'interfaccia IOleCommandTarget ) e un provider di gestori che implementa l'interfaccia IVsTextViewCreationListener . Per creare l'origine di completamento, ovvero l'elenco di identificatori che partecipano al completamento, implementare l'interfaccia ICompletionSource e un provider di origine di completamento (l'interfaccia ICompletionSourceProvider ). I provider sono parti del componente MEF (Managed Extensibility Framework). Sono responsabili dell'esportazione delle classi di origine e controller e dell'importazione di servizi e broker, ad esempio , ITextStructureNavigatorSelectorServiceche abilita la navigazione nel buffer di testo e , ICompletionBrokerche attiva la sessione di completamento.

Questa procedura dettagliata illustra come implementare il completamento dell'istruzione per un set hardcoded di identificatori. Nelle implementazioni complete, il servizio di linguaggio e la documentazione del linguaggio sono responsabili della fornitura di tale contenuto.

Creare un progetto MEF

Per creare un progetto MEF

  1. Creare un progetto VSIX C#. (In Finestra di dialogo Nuovo progetto , selezionare Visual C# / Estendibilità e quindi progetto VSIX. Assegnare alla soluzione CompletionTestil nome .

  2. Aggiungere un modello di elemento classificatore dell'editor al progetto. Per altre informazioni, vedere Creare un'estensione con un modello di elemento dell'editor.

  3. Eliminare i file di classe esistenti.

  4. Aggiungere i riferimenti seguenti al progetto e assicurarsi che CopyLocal sia impostato su 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

Implementare l'origine di completamento

L'origine di completamento è responsabile della raccolta del set di identificatori e dell'aggiunta del contenuto alla finestra di completamento quando un utente digita un trigger di completamento, ad esempio le prime lettere di un identificatore. In questo esempio, gli identificatori e le relative descrizioni sono hardcoded nel AugmentCompletionSession metodo . Nella maggior parte degli usi reali si userà il parser della lingua per ottenere i token per popolare l'elenco di completamento.

Per implementare l'origine di completamento

  1. Aggiungere un file di classe e assegnargli il nome TestCompletionSource.

  2. Aggiungere queste importazioni:

    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. Modificare la dichiarazione di classe per in TestCompletionSource modo che implementi ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Aggiungere campi privati per il provider di origine, il buffer di testo e un elenco di Completion oggetti (che corrispondono agli identificatori che faranno parte della sessione di completamento):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Aggiungere un costruttore che imposta il provider di origine e il buffer. La TestCompletionSourceProvider classe è definita nei passaggi successivi:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implementare il AugmentCompletionSession metodo aggiungendo un set di completamento contenente i completamenti che si desidera fornire nel contesto. Ogni set di completamento contiene un set di Completion completamenti e corrisponde a una scheda della finestra di completamento. Nei progetti Visual Basic le schede della finestra di completamento sono denominate Common and All.) Il FindTokenSpanAtPosition metodo viene definito nel passaggio successivo.

    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. Il metodo seguente viene usato per trovare la parola corrente dalla posizione del cursore:

    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. Implementare il Dispose() metodo :

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

Implementare il provider di origine di completamento

Il provider di origine di completamento è la parte del componente MEF che crea un'istanza dell'origine di completamento.

Per implementare il provider di origine di completamento

  1. Aggiungere una classe denominata TestCompletionSourceProvider che implementa ICompletionSourceProvider. Esportare questa classe con un ContentTypeAttribute "testo non crittografato" e un NameAttribute "completamento del test".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Importare un ITextStructureNavigatorSelectorServiceoggetto , che trova la parola corrente nell'origine di completamento.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implementare il TryCreateCompletionSource metodo per creare un'istanza dell'origine di completamento.

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

Implementare il provider del gestore dei comandi di completamento

Il provider del gestore dei comandi di completamento è derivato da un IVsTextViewCreationListeneroggetto , che rimane in ascolto di un evento di creazione della visualizzazione testo e converte la visualizzazione da un IVsTextViewoggetto , che consente l'aggiunta del comando alla catena di comandi della shell di Visual Studio, in un oggetto ITextView. Poiché questa classe è un'esportazione MEF, è anche possibile usarla per importare i servizi richiesti dal gestore dei comandi stesso.

Per implementare il provider del gestore dei comandi di completamento

  1. Aggiungere un file denominato TestCompletionCommandHandler.

  2. Aggiungere queste direttive 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. Aggiungere una classe denominata TestCompletionHandlerProvider che implementa IVsTextViewCreationListener. Esportare questa classe con un NameAttribute "gestore di completamento del token", un ContentTypeAttribute "testo non crittografato" e un TextViewRoleAttribute di Editable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Importare , che consente la IVsEditorAdaptersFactoryServiceconversione da un IVsTextView oggetto a un ITextViewoggetto , un ICompletionBrokere un oggetto SVsServiceProvider che consente l'accesso ai servizi standard di Visual Studio.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implementare il VsTextViewCreated metodo per creare un'istanza del gestore dei comandi.

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

Implementare il gestore dei comandi di completamento

Poiché il completamento delle istruzioni viene attivato dalle sequenze di tasti, è necessario implementare l'interfaccia IOleCommandTarget per ricevere ed elaborare le sequenze di tasti che attivano, eseguono il commit e ignorano la sessione di completamento.

Per implementare il gestore dei comandi di completamento

  1. Aggiungere una classe denominata TestCompletionCommandHandler che implementa IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Aggiungere campi privati per il gestore dei comandi successivo (a cui si passa il comando), la visualizzazione testo, il provider del gestore comandi (che consente l'accesso a vari servizi) e una sessione di completamento:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Aggiungere un costruttore che imposta la visualizzazione testo e i campi del provider e aggiunge il comando alla catena di comandi:

    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. Implementare il QueryStatus metodo passando il comando lungo:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implementa il metodo Exec. Quando questo metodo riceve una sequenza di tasti, deve eseguire una di queste operazioni:

    • Consentire la scrittura del carattere nel buffer e quindi attivare o filtrare il completamento. I caratteri di stampa eseguono questa operazione.

    • Eseguire il commit del completamento, ma non consentire la scrittura del carattere nel buffer. (Spazi vuoti, Tabulazione e Invio eseguire questa operazione quando viene visualizzata una sessione di completamento.

    • Consente di passare il comando al gestore successivo. (Tutti gli altri comandi.

      Poiché questo metodo può visualizzare l'interfaccia utente, chiamare IsInAutomationFunction per assicurarsi che non venga chiamato in un contesto di automazione:

      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. Questo codice è un metodo privato che attiva la sessione di completamento:

    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'esempio seguente è un metodo privato che annulla la sottoscrizione all'evento Dismissed :

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

Compilare e testare il codice

Per testare questo codice, compilare la soluzione CompletionTest ed eseguirla nell'istanza sperimentale.

Per compilare e testare la soluzione CompletionTest

  1. Compilare la soluzione.

  2. Quando si esegue questo progetto nel debugger, viene avviata una seconda istanza di Visual Studio.

  3. Creare un file di testo e digitare un testo che includa la parola "add".

  4. Quando si digita prima "a" e quindi "d", dovrebbe essere visualizzato un elenco che contiene "addizione" e "adattamento". Si noti che l'aggiunta è selezionata. Quando si digita un altro "d", l'elenco deve contenere solo "addizione", che è ora selezionato. È possibile eseguire il commit di "addizione" premendo la barra spaziatrice, tabulazione o invio oppure ignorando l'elenco digitando ESC o qualsiasi altro tasto.