Freigeben über


Exemplarische Vorgehensweise: Anzeigen von Anweisungsvervollständigung

Sie können den Abschluss einer sprachbasierten Anweisung implementieren, indem Sie die Bezeichner definieren, für die Sie den Abschluss bereitstellen und dann eine Abschlusssitzung auslösen möchten. Sie können den Abschluss der Anweisung im Kontext eines Sprachdiensts definieren, ihre eigene Dateinamenerweiterung und den Inhaltstyp definieren und dann den Abschluss nur für diesen Typ anzeigen. Oder Sie können den Abschluss für einen vorhandenen Inhaltstyp auslösen, z. B. "Nur-Text". In dieser exemplarischen Vorgehensweise wird gezeigt, wie Der Abschluss der Anweisung für den Inhaltstyp "Nur-Text" ausgelöst wird, bei dem es sich um den Inhaltstyp von Textdateien handelt. Der Inhaltstyp "Text" ist das Vorgängerelement aller anderen Inhaltstypen, einschließlich Code- und XML-Dateien.

Der Abschluss der Anweisung wird in der Regel durch Eingabe bestimmter Zeichen ausgelöst, z. B. durch Eingabe des Anfangs eines Bezeichners wie "using". Sie wird in der Regel geschlossen, indem Sie die LEERTASTE, DIE TAB-TASTE oder die EINGABETASTE drücken, um eine Auswahl zu übernehmen. Sie können die IntelliSense-Features implementieren, die beim Eingeben eines Zeichens ausgelöst werden, indem Sie einen Befehlshandler für die Tastaturanschläge (die IOleCommandTarget Schnittstelle) und einen Handleranbieter verwenden, der die IVsTextViewCreationListener Schnittstelle implementiert. Um die Abschlussquelle zu erstellen, bei der es sich um die Liste der Bezeichner handelt, die am Abschluss teilnehmen, implementieren Sie die ICompletionSource Schnittstelle und einen Vervollständigungsquellenanbieter (die ICompletionSourceProvider Schnittstelle). Die Anbieter sind Komponententeile des Managed Extensibility Framework (MEF). Sie sind dafür verantwortlich, die Quell- und Controllerklassen zu exportieren und Dienste und Broker zu importieren, z. B. das ITextStructureNavigatorSelectorService, das die Navigation im Textpuffer ermöglicht, und die ICompletionBroker, die die Abschlusssitzung auslöst.

In dieser exemplarischen Vorgehensweise wird gezeigt, wie Sie den Abschluss der Anweisung für einen hartcodierten Satz von Bezeichnern implementieren. In vollständigen Implementierungen sind der Sprachdienst und die Sprachdokumentation für die Bereitstellung dieser Inhalte verantwortlich.

Erstellen eines MEF-Projekts

So erstellen Sie ein MEF-Projekt

  1. Erstellen Sie ein C#VSIX-Projekt. (Im Dialogfeld "Neues Projekt ", wählen Sie Visual C# / Erweiterbarkeit und dann VSIX-Projekt aus.) Benennen Sie die Lösung CompletionTest.

  2. Fügen Sie dem Projekt eine Elementvorlage für Editorklassifizierer hinzu. Weitere Informationen finden Sie unter Erstellen einer Erweiterung mit einer Editorelementvorlage.

  3. Löschen Sie die vorhandenen Klassendateien.

  4. Fügen Sie dem Projekt die folgenden Verweise hinzu, und stellen Sie sicher, dass CopyLocal auf :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

Implementieren der Abschlussquelle

Die Abschlussquelle ist für das Sammeln der Bezeichner und das Hinzufügen des Inhalts zum Abschlussfenster verantwortlich, wenn ein Benutzer einen Abschlusstrigger eingibt, z. B. die ersten Buchstaben eines Bezeichners. In diesem Beispiel werden die Bezeichner und ihre Beschreibungen in der AugmentCompletionSession Methode hartcodiert. In den meisten realen Anwendungen verwenden Sie den Parser Ihrer Sprache, um die Token abzurufen, um die Abschlussliste aufzufüllen.

So implementieren Sie die Abschlussquelle

  1. Fügen Sie eine Klassendatei hinzu, und nennen Sie sie TestCompletionSource.

  2. Fügen Sie diese Importe hinzu:

    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. Ändern Sie die Klassendeklaration so TestCompletionSource , dass sie folgendes implementiert ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Fügen Sie private Felder für den Quellanbieter, den Textpuffer und eine Liste von Completion Objekten hinzu (die den Bezeichnern entsprechen, die an der Abschlusssitzung teilnehmen):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Fügen Sie einen Konstruktor hinzu, der den Quellanbieter und -puffer festlegt. Die TestCompletionSourceProvider Klasse wird in späteren Schritten definiert:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implementieren Sie die AugmentCompletionSession Methode, indem Sie einen Vervollständigungssatz hinzufügen, der die Fertigstellungen enthält, die Sie im Kontext bereitstellen möchten. Jeder Abschlusssatz enthält eine Reihe von Completion Fertigstellungen und entspricht einer Registerkarte des Abschlussfensters. (In Visual Basic-Projekten werden die Fensterregisterkarten für den Abschluss benannt .Common und All.) Die FindTokenSpanAtPosition Methode wird im nächsten Schritt definiert.

    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. Die folgende Methode wird verwendet, um das aktuelle Wort von der Position des Cursors zu finden:

    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. Implementieren Sie die Dispose() Methode:

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

Implementieren des Anbieters der Fertigstellungsquelle

Der Anbieter für die Vervollständigungsquelle ist der MEF-Komponententeil, der die Abschlussquelle instanziiert.

So implementieren Sie den Anbieter für die Fertigstellungsquelle

  1. Fügen Sie eine Klasse mit dem Namen TestCompletionSourceProvider hinzu, die implementiert ICompletionSourceProviderwird. Exportieren Sie diese Klasse mit " ContentTypeAttribute Nur-Text" und einem NameAttribute "Testabschluss".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Importieren Sie ein ITextStructureNavigatorSelectorService, das das aktuelle Wort in der Abschlussquelle findet.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implementieren Sie die TryCreateCompletionSource Methode, um die Abschlussquelle zu instanziieren.

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

Implementieren des Anbieters des Abschlussbefehlshandlers

Der Handleranbieter für den Abschlussbefehl wird von einem IVsTextViewCreationListenerEreignis abgeleitet, das auf ein Textansichtserstellungsereignis lauscht und die Ansicht aus einer IVsTextViewKonvertiert, die das Hinzufügen des Befehls zur Befehlskette der Visual Studio-Shell ermöglicht, in eine ITextView. Da es sich bei dieser Klasse um einen MEF-Export handelt, können Sie sie auch verwenden, um die Dienste zu importieren, die vom Befehlshandler selbst benötigt werden.

So implementieren Sie den Anbieter des Abschlussbefehlshandlers

  1. Fügen Sie eine Datei mit dem Namen TestCompletionCommandHandlerhinzu.

  2. Fügen Sie die folgenden using-Direktiven hinzu:

    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. Fügen Sie eine Klasse mit dem Namen TestCompletionHandlerProvider hinzu, die implementiert IVsTextViewCreationListenerwird. Exportieren Sie diese Klasse mit einem NameAttribute "Token-Vervollständigungshandler", einem ContentTypeAttribute "Nur-Text" und einem TextViewRoleAttribute von Editable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Importieren Sie die IVsEditorAdaptersFactoryService, die Konvertierung von einer in eine ITextViewIVsTextView , a ICompletionBrokerund eineSVsServiceProvider, die den Zugriff auf standard Visual Studio-Dienste ermöglicht.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implementieren Sie die VsTextViewCreated Methode zum Instanziieren des Befehlshandlers.

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

Implementieren des Abschlussbefehlshandlers

Da der Abschluss der Anweisung durch Tastaturanschläge ausgelöst wird, müssen Sie die IOleCommandTarget Schnittstelle implementieren, um die Tastaturanschläge zu empfangen und zu verarbeiten, die die Abschlusssitzung auslösen, commiten und schließen.

So implementieren Sie den Abschlussbefehlshandler

  1. Fügen Sie eine Klasse mit dem Namen TestCompletionCommandHandler hinzu, die folgendes implementiert IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Fügen Sie private Felder für den nächsten Befehlshandler (an den Sie den Befehl übergeben), die Textansicht, den Befehlshandleranbieter (der Zugriff auf verschiedene Dienste ermöglicht) und eine Abschlusssitzung hinzu:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Fügen Sie einen Konstruktor hinzu, der die Textansicht und die Anbieterfelder festlegt, und fügt den Befehl der Befehlskette hinzu:

    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. Implementieren Sie die QueryStatus Methode, indem Sie den Befehl übergeben:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implementieren Sie die Exec-Methode. Wenn diese Methode einen Tastenanschlag empfängt, muss sie eine der folgenden Aktionen ausführen:

    • Zulassen, dass das Zeichen in den Puffer geschrieben wird, und lösen oder filtern Sie den Abschluss aus. (Drucken von Zeichen tun dies.)

    • Übernehmen Sie den Abschluss, lassen Sie jedoch nicht zu, dass das Zeichen in den Puffer geschrieben wird. (Leerzeichen, Drücken Sie die TAB-TASTE, und geben Sie die EINGABETASTE ein, wenn eine Abschlusssitzung angezeigt wird.)

    • Zulassen, dass der Befehl an den nächsten Handler übergeben wird. (Alle anderen Befehle.)

      Da diese Methode die Benutzeroberfläche möglicherweise anzeigt, rufen Sie auf IsInAutomationFunction , um sicherzustellen, dass sie nicht in einem Automatisierungskontext aufgerufen wird:

      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. Dieser Code ist eine private Methode, die die Abschlusssitzung auslöst:

    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. Das nächste Beispiel ist eine private Methode, die das Dismissed Ereignis abbestellt:

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

Erstellen und Testen des Codes

Um diesen Code zu testen, erstellen Sie die CompletionTest-Lösung, und führen Sie sie in der experimentellen Instanz aus.

So erstellen und testen Sie die CompletionTest-Lösung

  1. Erstellen Sie die Projektmappe.

  2. Wenn Sie dieses Projekt im Debugger ausführen, wird eine zweite Instanz von Visual Studio gestartet.

  3. Erstellen Sie eine Textdatei, und geben Sie Text ein, der das Wort "hinzufügen" enthält.

  4. Während Sie zuerst "a" und dann "d" eingeben, sollte eine Liste mit "Addition" und "Anpassung" angezeigt werden. Beachten Sie, dass das Hinzufügen ausgewählt ist. Wenn Sie einen weiteren "d" eingeben, sollte die Liste nur "Addition" enthalten, das jetzt ausgewählt ist. Sie können "Addition" ausführen, indem Sie die LEERTASTE, TAB- oder EINGABETASTE drücken oder die Liste schließen, indem Sie ESC oder eine beliebige andere Taste eingeben.