Compartir vía


Tutorial: Mostrar la finalización de instrucciones

Para implementar la finalización de instrucciones basadas en lenguaje, defina los identificadores para los que desea proporcionar la finalización y, a continuación, desencadene una sesión de finalización. Puede definir la finalización de instrucciones en el contexto de un servicio de lenguaje, definir su propia extensión de nombre de archivo y tipo de contenido y, a continuación, mostrar la finalización solo para ese tipo. O bien, puede desencadenar la finalización de un tipo de contenido existente, por ejemplo, "texto sin formato". En este tutorial se muestra cómo desencadenar la finalización de instrucciones para el tipo de contenido "texto no cifrado", que es el tipo de contenido de los archivos de texto. El tipo de contenido "text" es el antecesor de todos los demás tipos de contenido, incluidos los archivos XML y de código.

Normalmente, la finalización de instrucciones se desencadena escribiendo determinados caracteres; por ejemplo, escribiendo el principio de un identificador como "using". Normalmente se descarta presionando la barra espaciadora, la pestaña o la tecla Entrar para confirmar una selección. Puede implementar las características de IntelliSense que se desencadenan al escribir un carácter mediante un controlador de comandos para las pulsaciones de tecla (la IOleCommandTarget interfaz) y un proveedor de controladores que implementa la IVsTextViewCreationListener interfaz. Para crear el origen de finalización, que es la lista de identificadores que participan en la finalización, implemente la ICompletionSource interfaz y un proveedor de origen de finalización (la ICompletionSourceProvider interfaz). Los proveedores son componentes de Managed Extensibility Framework (MEF). Son responsables de exportar las clases de origen y controlador e importar servicios y agentes, por ejemplo, , que ITextStructureNavigatorSelectorServicehabilita la navegación en el búfer de texto y , ICompletionBrokerque desencadena la sesión de finalización.

En este tutorial se muestra cómo implementar la finalización de instrucciones para un conjunto codificado de forma rígida de identificadores. En implementaciones completas, el servicio de lenguaje y la documentación del lenguaje son responsables de proporcionar ese contenido.

Crear un proyecto MEF

Para crear un nuevo proyecto de MEF

  1. Cree un proyecto VSIX de C#. (En Cuadro de diálogo Nuevo proyecto , seleccione Visual C# / Extensibilidad y, después , Proyecto VSIX). Asigne un nombre a la solución CompletionTest.

  2. Agregue una plantilla de elemento clasificador del editor al proyecto. Para obtener más información, vea Creación de una extensión con una plantilla de elemento de editor.

  3. Elimine los archivos de clase existentes.

  4. Agregue las siguientes referencias al proyecto y asegúrese de que CopyLocal está establecido falseen :

    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

Implementación del origen de finalización

El origen de finalización es responsable de recopilar el conjunto de identificadores y agregar el contenido a la ventana de finalización cuando un usuario escribe un desencadenador de finalización, como las primeras letras de un identificador. En este ejemplo, los identificadores y sus descripciones se codifican de forma rígida en el AugmentCompletionSession método . En la mayoría de los usos reales, usaría el analizador del idioma para obtener los tokens para rellenar la lista de finalización.

Para implementar el origen de finalización

  1. Agregue un archivo de clase y asígnele el nombre TestCompletionSource.

  2. Agregue estas importaciones:

    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. Modifique la declaración de clase para TestCompletionSource para que implemente ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Agregue campos privados para el proveedor de origen, el búfer de texto y una lista de Completion objetos (que corresponden a los identificadores que participarán en la sesión de finalización):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Agregue un constructor que establezca el proveedor de origen y el búfer. La TestCompletionSourceProvider clase se define en pasos posteriores:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implemente el AugmentCompletionSession método agregando un conjunto de finalización que contenga las finalizaciones que desea proporcionar en el contexto. Cada conjunto de finalización contiene un conjunto de Completion finalizaciones y corresponde a una pestaña de la ventana de finalización. (En los proyectos de Visual Basic, las pestañas de la ventana de finalización se denominan Común y Todo).) El FindTokenSpanAtPosition método se define en el paso siguiente.

    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. El método siguiente se usa para buscar la palabra actual desde la posición del cursor:

    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. Implemente el Dispose() método :

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

Implementación del proveedor de origen de finalización

El proveedor de origen de finalización es la parte del componente MEF que crea una instancia del origen de finalización.

Para implementar el proveedor de origen de finalización

  1. Agregue una clase denominada TestCompletionSourceProvider que implemente ICompletionSourceProvider. Exporte esta clase con un ContentTypeAttribute de "texto sin formato" y un NameAttribute de "finalización de prueba".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Importe un , ITextStructureNavigatorSelectorServiceque encuentra la palabra actual en el origen de finalización.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implemente el TryCreateCompletionSource método para crear una instancia del origen de finalización.

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

Implementación del proveedor de controladores de comandos de finalización

El proveedor del controlador de comandos de finalización se deriva de un IVsTextViewCreationListener, que escucha un evento de creación de vistas de texto y convierte la vista de , IVsTextViewque permite agregar el comando a la cadena de comandos del shell de Visual Studio, a un ITextView. Dado que esta clase es una exportación MEF, también puede usarla para importar los servicios que necesita el propio controlador de comandos.

Para implementar el proveedor de controladores de comandos de finalización

  1. Agregue un archivo denominado TestCompletionCommandHandler.

  2. Agregue estas directivas 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. Agregue una clase denominada TestCompletionHandlerProvider que implemente IVsTextViewCreationListener. Exporte esta clase con un NameAttribute de "controlador de finalización de tokens", un ContentTypeAttribute de "texto sin formato" y un TextViewRoleAttribute de Editable.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Importe , que habilita la IVsEditorAdaptersFactoryServiceconversión de a IVsTextView , ITextViewa ICompletionBrokery que SVsServiceProvider permite el acceso a los servicios estándar de Visual Studio.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implemente el VsTextViewCreated método para crear una instancia del controlador de comandos.

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

Implementación del controlador de comandos de finalización

Dado que las pulsaciones de tecla desencadenan la finalización de instrucciones, debe implementar la IOleCommandTarget interfaz para recibir y procesar las pulsaciones de tecla que desencadenan, confirman y descartan la sesión de finalización.

Para implementar el controlador de comandos de finalización

  1. Agregue una clase denominada TestCompletionCommandHandler que implemente IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Agregue campos privados para el siguiente controlador de comandos (al que se pasa el comando), la vista de texto, el proveedor del controlador de comandos (que permite el acceso a varios servicios) y una sesión de finalización:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Agregue un constructor que establezca la vista de texto y los campos del proveedor y agregue el comando a la cadena de comandos:

    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. Implemente el QueryStatus método pasando el comando a lo largo de:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implemente el método Exec. Cuando este método recibe una pulsación de tecla, debe realizar una de estas acciones:

    • Permita que el carácter se escriba en el búfer y, a continuación, desencadene o filtre la finalización. (Los caracteres de impresión lo hacen).

    • Confirme la finalización, pero no permita que el carácter se escriba en el búfer. (Espacio en blanco, Tab y Entrar hacen esto cuando se muestra una sesión de finalización).

    • Permitir que el comando se pase al siguiente controlador. (Todos los demás comandos).

      Dado que este método puede mostrar la interfaz de usuario, llame IsInAutomationFunction a para asegurarse de que no se llama en un contexto de automatización:

      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. Este código es un método privado que desencadena la sesión de finalización:

    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. El ejemplo siguiente es un método privado que cancela la suscripción del Dismissed evento:

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

Compilación y prueba del código

Para probar este código, compile la solución CompletionTest y ejecútela en la instancia experimental.

Para compilar y probar la solución CompletionTest

  1. Compile la solución.

  2. Al ejecutar este proyecto en el depurador, se inicia una segunda instancia de Visual Studio.

  3. Cree un archivo de texto y escriba algún texto que incluya la palabra "add".

  4. Al escribir primero "a" y luego "d", debería aparecer una lista que contenga "suma" y "adaptación". Observe que la adición está seleccionada. Al escribir otro "d", la lista solo debe contener "suma", que ahora está seleccionada. Puede confirmar la "adición" presionando la barra espaciadora, la pestaña o la tecla Entrar , o descartar la lista escribiendo Esc o cualquier otra tecla.