Návod: Dokončení příkazu Display

Dokončování příkazů na základě jazyka můžete implementovat definováním identifikátorů, pro které chcete dokončit, a následným spuštěním relace dokončení. Dokončování příkazů můžete definovat v kontextu služby jazyka, definovat vlastní příponu názvu souboru a typ obsahu a pak zobrazit dokončení pouze pro tento typ. Nebo můžete aktivovat dokončování pro existující typ obsahu – například "prostý text". Tento názorný postup ukazuje, jak aktivovat dokončování příkazů pro typ obsahu prostého textu, což je typ obsahu textových souborů. Typ obsahu "text" je nadřazený všem ostatním typům obsahu, včetně kódu a souborů XML.

Dokončování příkazů se obvykle aktivuje zadáním určitých znaků– například zadáním začátku identifikátoru, jako je například using. Obvykle se zavře stisknutím mezerníku, tabulátoru nebo klávesy Enter pro potvrzení výběru. Funkce IntelliSense, které se aktivují při zadávání znaku, můžete implementovat pomocí obslužné rutiny příkazu pro stisknutí kláves ( IOleCommandTarget rozhraní) a zprostředkovatele obslužné rutiny, který implementuje IVsTextViewCreationListener rozhraní. Chcete-li vytvořit zdroj dokončení, což je seznam identifikátorů, které se účastní dokončení, implementujte ICompletionSource rozhraní a zprostředkovatele zdroje dokončení ( ICompletionSourceProvider rozhraní). Zprostředkovatelé jsou součásti mef (Managed Extensibility Framework). Zodpovídají za export tříd zdrojového a kontroleru a import služeb a zprostředkovatelů, ITextStructureNavigatorSelectorServicenapříklad za navigaci v textové vyrovnávací paměti a za ICompletionBrokeraktivaci relace dokončení.

Tento návod ukazuje, jak implementovat dokončování příkazů pro pevně zakódovanou sadu identifikátorů. V úplných implementacích zodpovídá za poskytování daného obsahu služba jazyka a dokumentace k jazyku.

Vytvoření projektu MEF

Vytvoření projektu MEF

  1. Vytvořte projekt VSIX jazyka C#. (V Dialogové okno Nový projekt , vyberte Visual C# / Rozšiřitelnost a pak projekt VSIX.) Pojmenujte řešení CompletionTest.

  2. Přidejte do projektu šablonu položky klasifikátoru editoru. Další informace najdete v tématu Vytvoření rozšíření pomocí šablony položky editoru.

  3. Odstraňte existující soubory třídy.

  4. Přidejte do projektu následující odkazy a ujistěte se, že je vlastnost CopyLocal nastavená na 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

Implementace zdroje dokončení

Zdroj dokončení zodpovídá za shromažďování sady identifikátorů a přidání obsahu do okna dokončení, když uživatel zadá trigger dokončení, například první písmena identifikátoru. V tomto příkladu jsou identifikátory a jejich popisy pevně zakódovány v AugmentCompletionSession metodě. Ve většině reálných použití byste pomocí analyzátoru jazyka získali tokeny k naplnění seznamu dokončení.

Implementace zdroje dokončení

  1. Přidejte soubor třídy a pojmenujte ho TestCompletionSource.

  2. Přidejte tyto importy:

    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. Upravte deklaraci třídy tak TestCompletionSource , aby implementovali ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Přidejte soukromá pole pro zprostředkovatele zdroje, vyrovnávací paměť textu a seznam Completion objektů (které odpovídají identifikátorům, které se budou účastnit relace dokončení):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Přidejte konstruktor, který nastaví zprostředkovatele zdroje a vyrovnávací paměť. Třída TestCompletionSourceProvider je definována v pozdějších krocích:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implementujte metodu AugmentCompletionSession přidáním sady dokončení, která obsahuje dokončení, které chcete poskytnout v kontextu. Každá sada dokončení obsahuje sadu Completion dokončení a odpovídá tabulátoru okna dokončení. (V projektech jazyka Visual Basic jsou karty okna dokončení pojmenované. Společné a všechny.) Metoda FindTokenSpanAtPosition je definována v dalším kroku.

    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. Následující metoda slouží k vyhledání aktuálního slova z pozice kurzoru:

    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. Implementujte metodu Dispose() :

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

Implementace zprostředkovatele zdroje dokončení

Zprostředkovatel zdroje dokončení je komponenta MEF, která vytvoří instanci zdroje dokončení.

Implementace zprostředkovatele zdroje dokončení

  1. Přidejte třídu s názvem TestCompletionSourceProvider , která implementuje ICompletionSourceProvider. Exportovat tuto třídu s " ContentTypeAttribute prostým textem" a NameAttribute "dokončení testu".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Naimportujte výraz ITextStructureNavigatorSelectorService, který najde aktuální slovo ve zdroji dokončení.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implementujte metodu TryCreateCompletionSource pro vytvoření instance zdroje dokončení.

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

Implementace zprostředkovatele obslužné rutiny příkazu dokončení

Zprostředkovatel obslužné rutiny příkazu dokončení je odvozen z objektu IVsTextViewCreationListener, který naslouchá události vytvoření textového zobrazení a převede zobrazení z objektu IVsTextView–, který umožňuje přidání příkazu do řetězce příkazů prostředí sady Visual Studio – do objektu ITextView. Vzhledem k tomu, že tato třída je export MEF, můžete ji použít také k importu služeb, které vyžaduje samotná obslužná rutina příkazu.

Implementace zprostředkovatele obslužné rutiny příkazu dokončení

  1. Přidejte soubor s názvem TestCompletionCommandHandler.

  2. Přidejte tyto direktivy 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. Přidejte třídu s názvem TestCompletionHandlerProvider , která implementuje IVsTextViewCreationListener. Export této třídy s "obslužnou rutinou NameAttribute dokončení tokenu", ContentTypeAttribute "plaintext" a a TextViewRoleAttribute z Editable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Naimportujte IVsEditorAdaptersFactoryService, který umožňuje převod z a IVsTextView ITextView, a ICompletionBroker, a, SVsServiceProvider který umožňuje přístup ke standardním službám sady Visual Studio.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implementujte metodu VsTextViewCreated pro vytvoření instance obslužné rutiny příkazu.

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

Implementace obslužné rutiny příkazu dokončení

Vzhledem k tomu, že dokončování příkazů je aktivováno pomocí klávesových úhozí, musíte implementovat IOleCommandTarget rozhraní pro příjem a zpracování klávesových úhozí, které trigger, potvrzení a zavře relace dokončení.

Implementace obslužné rutiny příkazu dokončení

  1. Přidejte třídu s názvem TestCompletionCommandHandler , která implementuje IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Přidejte soukromá pole pro další obslužnou rutinu příkazu (do které předáte příkaz), textové zobrazení, zprostředkovatele obslužné rutiny příkazu (který umožňuje přístup k různým službám) a relaci dokončení:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Přidejte konstruktor, který nastaví textové zobrazení a pole zprostředkovatele a přidá příkaz do řetězce příkazů:

    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. Implementujte metodu QueryStatus předáním příkazu společně:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implementujte metodu Exec . Když tato metoda obdrží stisknutí klávesy, musí provést jednu z těchto věcí:

    • Povolte zápis znaku do vyrovnávací paměti a pak aktivujte nebo filtrujte dokončení. (Tisk znaků provede.)

    • Potvrďte dokončení, ale nepovolujte zápis znaku do vyrovnávací paměti. (Prázdné znaky, Tab a Enter to udělat při zobrazení relace dokončení.)

    • Povolte předání příkazu další obslužné rutině. (Všechny ostatní příkazy.)

      Vzhledem k tomu, že tato metoda může zobrazit uživatelské rozhraní, voláním IsInAutomationFunction se ujistěte, že není volána v kontextu automatizace:

      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. Tento kód je privátní metoda, která aktivuje relaci dokončení:

    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. Dalším příkladem je soukromá metoda, která se odhlásí od odběru Dismissed události:

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

Sestavení a otestování kódu

Tento kód otestujete tak, že sestavíte řešení CompletionTest a spustíte ho v experimentální instanci.

Sestavení a otestování řešení CompletionTest

  1. Sestavte řešení.

  2. Když tento projekt spustíte v ladicím programu, spustí se druhá instance sady Visual Studio.

  3. Vytvořte textový soubor a zadejte nějaký text, který obsahuje slovo "přidat".

  4. Při psaní prvního "a" a "d" by se měl zobrazit seznam, který obsahuje "sčítání" a "adaptaci". Všimněte si, že je vybráno přidání. Když zadáte další "d", seznam by měl obsahovat pouze "sčítání", které je nyní vybráno. Přidání můžete potvrdit stisknutím mezerníku, tabulátoru nebo klávesy Enter nebo zavřením seznamu zadáním klávesy Esc nebo jiné klávesy.