Udostępnij za pośrednictwem


Przewodnik: uzupełnianie instrukcji wyświetlania

Można zaimplementować uzupełnianie instrukcji opartej na języku, definiując identyfikatory, dla których chcesz zapewnić ukończenie, a następnie wyzwalając sesję ukończenia. Możesz zdefiniować uzupełnianie instrukcji w kontekście usługi językowej, zdefiniować rozszerzenie nazwy pliku i typ zawartości, a następnie wyświetlić uzupełnianie tylko dla tego typu. Możesz też wyzwolić uzupełnianie dla istniejącego typu zawartości — na przykład "zwykły tekst". W tym przewodniku pokazano, jak wyzwolić uzupełnianie instrukcji dla typu zawartości "zwykły tekst", który jest typem zawartości plików tekstowych. Typ zawartości "tekst" jest elementem nadrzędnym wszystkich innych typów zawartości, w tym kodu i plików XML.

Uzupełnianie instrukcji jest zwykle wyzwalane przez wpisanie niektórych znaków — na przykład przez wpisanie początku identyfikatora, takiego jak "using". Zazwyczaj jest on odrzucany przez naciśnięcie klawisza Spacja, Klawisz Tab lub Enter w celu zatwierdzenia zaznaczenia. Funkcje intelliSense wyzwalane podczas wpisywania znaku można zaimplementować przy użyciu procedury obsługi poleceń dla naciśnięć klawiszy ( IOleCommandTarget interfejsu) i dostawcy programu obsługi, który implementuje IVsTextViewCreationListener interfejs. Aby utworzyć źródło uzupełniania, czyli listę identyfikatorów, które uczestniczą w uzupełnianiu, zaimplementuj ICompletionSource interfejs i dostawcę źródła uzupełniania (interfejs).ICompletionSourceProvider Dostawcy są częściami składników Managed Extensibility Framework (MEF). Są one odpowiedzialne za eksportowanie klas źródłowych i kontrolerów oraz importowanie usług i brokerów — na przykład ITextStructureNavigatorSelectorService, który umożliwia nawigację w buforze tekstowym i ICompletionBroker, która wyzwala sesję ukończenia.

W tym przewodniku pokazano, jak zaimplementować uzupełnianie instrukcji dla ustalonego zestawu identyfikatorów. W pełnych implementacjach usługa językowa i dokumentacja języka są odpowiedzialne za dostarczanie tej zawartości.

Tworzenie projektu MEF

Aby utworzyć projekt MEF

  1. Utwórz projekt VSIX w języku C#. (W Okno dialogowe Nowy projekt , wybierz pozycję Visual C# / Rozszerzalność, a następnie projekt VSIX. Nadaj rozwiązaniu CompletionTestnazwę .

  2. Dodaj szablon elementu Klasyfikator edytora do projektu. Aby uzyskać więcej informacji, zobacz Tworzenie rozszerzenia za pomocą szablonu elementu edytora.

  3. Usuń istniejące pliki klas.

  4. Dodaj następujące odwołania do projektu i upewnij się, że właściwość CopyLocal jest ustawiona na falsewartość :

    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

Implementowanie źródła uzupełniania

Źródło uzupełniania jest odpowiedzialne za zbieranie zestawu identyfikatorów i dodawanie zawartości do okna uzupełniania, gdy użytkownik wpisze wyzwalacz ukończenia, na przykład pierwsze litery identyfikatora. W tym przykładzie identyfikatory i ich opisy są zakodowane w metodzie AugmentCompletionSession . W większości przypadków używanych w większości rzeczywistych analizator języka służy do pobierania tokenów w celu wypełnienia listy uzupełniania.

Aby zaimplementować źródło uzupełniania

  1. Dodaj plik klasy i nadaj mu TestCompletionSourcenazwę .

  2. Dodaj następujące 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. Zmodyfikuj deklarację klasy dla elementu TestCompletionSource , aby implementować ICompletionSourcepolecenie :

    internal class TestCompletionSource : ICompletionSource
    
  4. Dodaj pola prywatne dla dostawcy źródłowego, bufor tekstu i listę Completion obiektów (które odpowiadają identyfikatorom, które będą uczestniczyć w sesji ukończenia):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Dodaj konstruktor, który ustawia dostawcę źródła i bufor. Klasa jest zdefiniowana TestCompletionSourceProvider w kolejnych krokach:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Zaimplementuj metodę AugmentCompletionSession , dodając zestaw uzupełniania zawierający uzupełnienia, które chcesz podać w kontekście. Każdy zestaw uzupełniania zawiera zestaw Completion uzupełniania i odpowiada karcie okna ukończenia. (W projektach Visual Basic karty okien uzupełniania są nazwane Wspólne i wszystkie). Metoda jest zdefiniowana FindTokenSpanAtPosition w następnym 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. Następująca metoda służy do znajdowania bieżącego wyrazu z położenia kursora:

    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. Zaimplementuj metodę Dispose() :

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

Implementowanie dostawcy źródła uzupełniania

Dostawca źródła uzupełniania jest częścią składnika MEF, która tworzy wystąpienie źródła uzupełniania.

Aby zaimplementować dostawcę źródła uzupełniania

  1. Dodaj klasę o nazwie TestCompletionSourceProvider , która implementuje ICompletionSourceProviderelement . Wyeksportuj tę klasę ContentTypeAttribute za pomocą "zwykłego tekstu" i NameAttribute "ukończenia testu".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Zaimportuj element ITextStructureNavigatorSelectorService, który znajduje bieżące słowo w źródle uzupełniania.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Zaimplementuj metodę , TryCreateCompletionSource aby utworzyć wystąpienie źródła uzupełniania.

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

Implementowanie dostawcy obsługi poleceń uzupełniania

Dostawca programu obsługi poleceń uzupełniania pochodzi z IVsTextViewCreationListenerelementu , który nasłuchuje zdarzenia tworzenia widoku tekstu i konwertuje widok z IVsTextViewelementu , co umożliwia dodanie polecenia do łańcucha poleceń powłoki programu Visual Studio do elementu ITextView. Ponieważ ta klasa jest eksportem MEF, można go również użyć do zaimportowania usług wymaganych przez samą procedurę obsługi poleceń.

Aby zaimplementować dostawcę obsługi poleceń uzupełniania

  1. Dodaj plik o nazwie TestCompletionCommandHandler.

  2. Dodaj następujące dyrektywy 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. Dodaj klasę o nazwie TestCompletionHandlerProvider , która implementuje IVsTextViewCreationListenerelement . Wyeksportuj tę klasę NameAttribute za pomocą "procedury obsługi uzupełniania tokenu", " ContentTypeAttribute zwykłego tekstu" i klasy TextViewRoleAttributeEditable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Zaimportuj element IVsEditorAdaptersFactoryService, który umożliwia konwersję z IVsTextView elementu na ITextView, a ICompletionBrokeri , SVsServiceProvider który umożliwia dostęp do standardowych usług programu Visual Studio.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Zaimplementuj metodę , VsTextViewCreated aby utworzyć wystąpienie programu obsługi poleceń.

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

Implementowanie procedury obsługi poleceń uzupełniania

Ponieważ uzupełnianie instrukcji jest wyzwalane przez naciśnięcia klawiszy, należy zaimplementować IOleCommandTarget interfejs w celu odbierania i przetwarzania naciśnięć klawiszy wyzwalających, zatwierdzających i odrzucających sesję uzupełniania.

Aby zaimplementować procedurę obsługi poleceń uzupełniania

  1. Dodaj klasę o nazwie TestCompletionCommandHandler , która implementuje IOleCommandTargetpolecenie :

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Dodaj pola prywatne dla następnej procedury obsługi poleceń (do której przekazujesz polecenie), widoku tekstu, dostawcy programu obsługi poleceń (który umożliwia dostęp do różnych usług) i sesji ukończenia:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Dodaj konstruktor, który ustawia widok tekstowy i pola dostawcy, a następnie dodaje polecenie do łańcucha poleceń:

    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. Zaimplementuj metodę QueryStatus , przekazując polecenie wzdłuż:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Zaimplementuj metodę Exec . Gdy ta metoda odbiera naciśnięcie klawisza, musi wykonać jedną z następujących czynności:

    • Zezwalaj na zapisanie znaku w buforze, a następnie wyzwalanie lub uzupełnianie filtru. (To robią znaki drukowania).

    • Zatwierdź ukończenie, ale nie zezwalaj na zapisanie znaku w buforze. (Biały znak, Karta i wprowadź to po wyświetleniu sesji ukończenia).

    • Zezwalaj na przekazywanie polecenia do następnego programu obsługi. (Wszystkie inne polecenia).

      Ponieważ ta metoda może wyświetlać interfejs użytkownika, wywołaj metodę IsInAutomationFunction , aby upewnić się, że nie jest wywoływana w kontekście automatyzacji:

      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. Ten kod jest prywatną metodą, która wyzwala sesję ukończenia:

    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. Następnym przykładem jest metoda prywatna, która anuluje Dismissed subskrypcję zdarzenia:

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

Kompilowanie i testowanie kodu

Aby przetestować ten kod, skompiluj rozwiązanie CompletionTest i uruchom je w wystąpieniu eksperymentalnym.

Aby skompilować i przetestować rozwiązanie CompletionTest

  1. Stwórz rozwiązanie.

  2. Po uruchomieniu tego projektu w debugerze zostanie uruchomione drugie wystąpienie programu Visual Studio.

  3. Utwórz plik tekstowy i wpisz tekst zawierający wyraz "add".

  4. Podczas wpisywania najpierw "a", a następnie "d", powinna pojawić się lista zawierająca "dodawanie" i "adaptacja". Zwróć uwagę, że zaznaczono dodanie. Po wpiseniu innego ciągu "d" lista powinna zawierać tylko wartość "add", która jest teraz zaznaczona. Możesz zatwierdzić "dodawanie", naciskając klawisz Spacja, Kartę lub Enter albo odrzucić listę, wpisując Klawisz Esc lub dowolny inny klawisz.