Tutorial: Mostrar ayuda de firma

La Ayuda de firma (también conocida como Información de parámetros) muestra la firma de un método en una información sobre herramientas cuando un usuario escribe el carácter de inicio de la lista de parámetros (normalmente un paréntesis de apertura). Como parámetro y separador de parámetros (normalmente una coma) se escriben, la información sobre herramientas se actualiza para mostrar el siguiente parámetro en negrita. Puede definir la Ayuda de firma de las maneras siguientes: en el contexto de un servicio de lenguaje, defina su propia extensión de nombre de archivo y tipo de contenido y muestre la Ayuda de firma solo para ese tipo, o muestre la Ayuda de firma para un tipo de contenido existente (por ejemplo, "texto"). En este tutorial se muestra cómo mostrar la Ayuda de firma para el tipo de contenido "texto".

La Ayuda de firma se desencadena normalmente escribiendo un carácter específico, por ejemplo, "(" (paréntesis de apertura) y descartado escribiendo otro carácter, por ejemplo, ")" (paréntesis de cierre). Las características de IntelliSense que se desencadenan escribiendo un carácter se pueden implementar 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 la Ayuda de firma, que es la lista de firmas que participan en la Ayuda de firma, implemente la ISignatureHelpSource interfaz y un proveedor de origen que ejecute la ISignatureHelpSourceProvider interfaz. Los proveedores son componentes de Managed Extensibility Framework (MEF) y son responsables de exportar las clases de origen y controlador e importar servicios y agentes, por ejemplo, , que ITextStructureNavigatorSelectorServicele permite navegar en el búfer de texto y , ISignatureHelpBrokerque desencadena la sesión de Ayuda de firma.

En este tutorial se muestra cómo configurar la Ayuda de firma para un conjunto codificado de identificadores de forma rígida. En implementaciones completas, el lenguaje es responsable de proporcionar ese contenido.

Creación de 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 SignatureHelpTest.

  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.14.0

    Microsoft.VisualStudio.TextManager.Interop

Implementar firmas y parámetros de la Ayuda de firma

El origen de la Ayuda de firma se basa en firmas que implementan ISignature, cada una de las cuales contiene parámetros que implementan IParameter. En una implementación completa, esta información se obtendría de la documentación del lenguaje, pero en este ejemplo, las firmas están codificadas de forma rígida.

Para implementar las firmas y parámetros de la Ayuda de firma

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

  2. Agregue las importaciones siguientes.

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using System.Runtime.InteropServices;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.VisualStudio.Editor;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.OLE.Interop;
    
  3. Agregue una clase denominada TestParameter que implemente IParameter.

    internal class TestParameter : IParameter
    
  4. Agregue un constructor que establezca todas las propiedades.

    public TestParameter(string documentation, Span locus, string name, ISignature signature)
    {
        Documentation = documentation;
        Locus = locus;
        Name = name;
        Signature = signature;
    }
    
  5. Agregue las propiedades de IParameter.

    public string Documentation { get; private set; }
    public Span Locus { get; private set; }
    public string Name { get; private set; }
    public ISignature Signature { get; private set; }
    public Span PrettyPrintedLocus { get; private set; }
    
  6. Agregue una clase denominada TestSignature que implemente ISignature.

    internal class TestSignature : ISignature
    
  7. Agregue algunos campos privados.

    private ITextBuffer m_subjectBuffer;
    private IParameter m_currentParameter;
    private string m_content;
    private string m_documentation;
    private ITrackingSpan m_applicableToSpan;
    private ReadOnlyCollection<IParameter> m_parameters;
    private string m_printContent;
    
  8. Agregue un constructor que establezca los campos y se suscriba al Changed evento.

    internal TestSignature(ITextBuffer subjectBuffer, string content, string doc, ReadOnlyCollection<IParameter> parameters)
    {
        m_subjectBuffer = subjectBuffer;
        m_content = content;
        m_documentation = doc;
        m_parameters = parameters;
        m_subjectBuffer.Changed += new EventHandler<TextContentChangedEventArgs>(OnSubjectBufferChanged);
    }
    
  9. Declare un CurrentParameterChanged evento. Este evento se genera cuando el usuario rellena uno de los parámetros de la firma.

    public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged;
    
  10. Implemente la CurrentParameter propiedad para que genere el CurrentParameterChanged evento cuando se cambie el valor de la propiedad.

    public IParameter CurrentParameter
    {
        get { return m_currentParameter; }
        internal set
        {
            if (m_currentParameter != value)
            {
                IParameter prevCurrentParameter = m_currentParameter;
                m_currentParameter = value;
                this.RaiseCurrentParameterChanged(prevCurrentParameter, m_currentParameter);
            }
        }
    }
    
  11. Agregue un método que genere el CurrentParameterChanged evento.

    private void RaiseCurrentParameterChanged(IParameter prevCurrentParameter, IParameter newCurrentParameter)
    {
        EventHandler<CurrentParameterChangedEventArgs> tempHandler = this.CurrentParameterChanged;
        if (tempHandler != null)
        {
            tempHandler(this, new CurrentParameterChangedEventArgs(prevCurrentParameter, newCurrentParameter));
        }
    }
    
  12. Agregue un método que calcule el parámetro actual comparando el número de comas en con ApplicableToSpan el número de comas de la firma.

    internal void ComputeCurrentParameter()
    {
        if (Parameters.Count == 0)
        {
            this.CurrentParameter = null;
            return;
        }
    
        //the number of commas in the string is the index of the current parameter
        string sigText = ApplicableToSpan.GetText(m_subjectBuffer.CurrentSnapshot);
    
        int currentIndex = 0;
        int commaCount = 0;
        while (currentIndex < sigText.Length)
        {
            int commaIndex = sigText.IndexOf(',', currentIndex);
            if (commaIndex == -1)
            {
                break;
            }
            commaCount++;
            currentIndex = commaIndex + 1;
        }
    
        if (commaCount < Parameters.Count)
        {
            this.CurrentParameter = Parameters[commaCount];
        }
        else
        {
            //too many commas, so use the last parameter as the current one.
            this.CurrentParameter = Parameters[Parameters.Count - 1];
        }
    }
    
  13. Agregue un controlador de eventos para el Changed evento que llama al ComputeCurrentParameter() método .

    internal void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
    {
        this.ComputeCurrentParameter();
    }
    
  14. Implemente la propiedad ApplicableToSpan. Esta propiedad contiene un ITrackingSpan que corresponde al intervalo de texto del búfer al que se aplica la firma.

    public ITrackingSpan ApplicableToSpan
    {
        get { return (m_applicableToSpan); }
        internal set { m_applicableToSpan = value; }
    }
    
  15. Implemente los demás parámetros.

    public string Content
    {
        get { return (m_content); }
        internal set { m_content = value; }
    }
    
    public string Documentation
    {
        get { return (m_documentation); }
        internal set { m_documentation = value; }
    }
    
    public ReadOnlyCollection<IParameter> Parameters
    {
        get { return (m_parameters); }
        internal set { m_parameters = value; }
    }
    
    public string PrettyPrintedContent
    {
        get { return (m_printContent); }
        internal set { m_printContent = value; }
    }
    

Implementación del origen de la Ayuda de firma

El origen de la Ayuda de firma es el conjunto de firmas para las que se proporciona información.

Para implementar el origen de la Ayuda de firma

  1. Agregue una clase denominada TestSignatureHelpSource que implemente ISignatureHelpSource.

    internal class TestSignatureHelpSource : ISignatureHelpSource
    
  2. Agregue una referencia al búfer de texto.

    private ITextBuffer m_textBuffer;
    
  3. Agregue un constructor que establezca el búfer de texto y el proveedor de origen de la Ayuda de firma.

    public TestSignatureHelpSource(ITextBuffer textBuffer)
    {
        m_textBuffer = textBuffer;
    }
    
  4. Implemente el método AugmentSignatureHelpSession. En este ejemplo, las firmas están codificadas de forma rígida, pero en una implementación completa obtendrá esta información de la documentación del lenguaje.

    public void AugmentSignatureHelpSession(ISignatureHelpSession session, IList<ISignature> signatures)
    {
        ITextSnapshot snapshot = m_textBuffer.CurrentSnapshot;
        int position = session.GetTriggerPoint(m_textBuffer).GetPosition(snapshot);
    
        ITrackingSpan applicableToSpan = m_textBuffer.CurrentSnapshot.CreateTrackingSpan(
         new Span(position, 0), SpanTrackingMode.EdgeInclusive, 0);
    
        signatures.Add(CreateSignature(m_textBuffer, "add(int firstInt, int secondInt)", "Documentation for adding integers.", applicableToSpan));
        signatures.Add(CreateSignature(m_textBuffer, "add(double firstDouble, double secondDouble)", "Documentation for adding doubles.", applicableToSpan));
    
    }
    
  5. El método CreateSignature() auxiliar se proporciona solo para la ilustración.

    private TestSignature CreateSignature(ITextBuffer textBuffer, string methodSig, string methodDoc, ITrackingSpan span)
    {
        TestSignature sig = new TestSignature(textBuffer, methodSig, methodDoc, null);
        textBuffer.Changed += new EventHandler<TextContentChangedEventArgs>(sig.OnSubjectBufferChanged);
    
        //find the parameters in the method signature (expect methodname(one, two)
        string[] pars = methodSig.Split(new char[] { '(', ',', ')' });
        List<IParameter> paramList = new List<IParameter>();
    
        int locusSearchStart = 0;
        for (int i = 1; i < pars.Length; i++)
        {
            string param = pars[i].Trim();
    
            if (string.IsNullOrEmpty(param))
                continue;
    
            //find where this parameter is located in the method signature
            int locusStart = methodSig.IndexOf(param, locusSearchStart);
            if (locusStart >= 0)
            {
                Span locus = new Span(locusStart, param.Length);
                locusSearchStart = locusStart + param.Length;
                paramList.Add(new TestParameter("Documentation for the parameter.", locus, param, sig));
            }
        }
    
        sig.Parameters = new ReadOnlyCollection<IParameter>(paramList);
        sig.ApplicableToSpan = span;
        sig.ComputeCurrentParameter();
        return sig;
    }
    
  6. Implemente el método GetBestMatch. En este ejemplo, solo hay dos firmas, cada una de las cuales tiene dos parámetros. Por lo tanto, este método no es necesario. En una implementación más completa, en la que hay disponible más de un origen de ayuda de firma, este método se usa para decidir si el origen de ayuda de firma de prioridad más alta puede proporcionar una firma coincidente. Si no es posible, el método devuelve null y se pide al origen de prioridad más alta que proporcione una coincidencia.

    public ISignature GetBestMatch(ISignatureHelpSession session)
    {
        if (session.Signatures.Count > 0)
        {
            ITrackingSpan applicableToSpan = session.Signatures[0].ApplicableToSpan;
            string text = applicableToSpan.GetText(applicableToSpan.TextBuffer.CurrentSnapshot);
    
            if (text.Trim().Equals("add"))  //get only "add" 
                return session.Signatures[0];
        }
        return null;
    }
    
  7. 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 la Ayuda de firma

El proveedor de origen de la Ayuda de firma es responsable de exportar la parte del componente Managed Extensibility Framework (MEF) y para crear instancias del origen de la Ayuda de firma.

Para implementar el proveedor de origen de la Ayuda de firma

  1. Agregue una clase denominada TestSignatureHelpSourceProvider que implemente ISignatureHelpSourceProvidery expórtela con un NameAttribute, un ContentTypeAttribute de "texto" y un OrderAttribute valor de Before="default".

    [Export(typeof(ISignatureHelpSourceProvider))]
    [Name("Signature Help source")]
    [Order(Before = "default")]
    [ContentType("text")]
    internal class TestSignatureHelpSourceProvider : ISignatureHelpSourceProvider
    
  2. Implemente TryCreateSignatureHelpSource mediante la creación de instancias de TestSignatureHelpSource.

    public ISignatureHelpSource TryCreateSignatureHelpSource(ITextBuffer textBuffer)
    {
        return new TestSignatureHelpSource(textBuffer);
    }
    

Implementación del controlador de comandos

La Ayuda de firma normalmente se desencadena mediante un carácter de paréntesis de apertura "(" y descartado por un carácter de paréntesis de cierre ")". Puede controlar estas pulsaciones de tecla ejecutando para IOleCommandTarget que desencadene una sesión de Ayuda de firma cuando reciba un carácter de paréntesis de apertura precedido por un nombre de método conocido y descarta la sesión cuando recibe un carácter de paréntesis de cierre.

Para implementar el controlador de comandos

  1. Agregue una clase denominada TestSignatureHelpCommand que implemente IOleCommandTarget.

    internal sealed class TestSignatureHelpCommandHandler : IOleCommandTarget
    
  2. Agregue campos privados para el IVsTextView adaptador (que le permite agregar el controlador de comandos a la cadena de controladores de comandos), la vista de texto, el agente de ayuda de firma y la sesión, un ITextStructureNavigatory el siguiente IOleCommandTarget.

    IOleCommandTarget m_nextCommandHandler;
    ITextView m_textView;
    ISignatureHelpBroker m_broker;
    ISignatureHelpSession m_session;
    ITextStructureNavigator m_navigator;
    
  3. Agregue un constructor para inicializar estos campos y para agregar el filtro de comandos a la cadena de filtros de comandos.

    internal TestSignatureHelpCommandHandler(IVsTextView textViewAdapter, ITextView textView, ITextStructureNavigator nav, ISignatureHelpBroker broker)
    {
        this.m_textView = textView;
        this.m_broker = broker;
        this.m_navigator = nav;
    
        //add this to the filter chain
        textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
    }
    
  4. Implemente el Exec método para desencadenar la sesión de ayuda de firma cuando el filtro de comandos recibe un paréntesis de apertura "(" carácter después de uno de los nombres de método conocidos y para descartar la sesión cuando recibe un carácter de paréntesis de cierre ")" mientras la sesión sigue activa. En cada caso, el comando se reenvía.

    public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
    {
        char typedChar = char.MinValue;
    
        if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
        {
            typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
            if (typedChar.Equals('('))
            {
                //move the point back so it's in the preceding word
                SnapshotPoint point = m_textView.Caret.Position.BufferPosition - 1;
                TextExtent extent = m_navigator.GetExtentOfWord(point);
                string word = extent.Span.GetText();
                if (word.Equals("add"))
                    m_session = m_broker.TriggerSignatureHelp(m_textView);
    
            }
            else if (typedChar.Equals(')') && m_session != null)
            {
                m_session.Dismiss();
                m_session = null;
            }
        }
        return m_nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
    }
    
  5. Implemente el QueryStatus método para que siempre reenvíe el comando.

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    

Implementación del proveedor de comandos de la Ayuda de firma

Puede proporcionar el comando Ayuda de firma implementando para IVsTextViewCreationListener crear una instancia del controlador de comandos cuando se crea la vista de texto.

Para implementar el proveedor de comandos de la Ayuda de firma

  1. Agregue una clase denominada TestSignatureHelpController que implementa IVsTextViewCreationListener y exporte con , NameAttributeContentTypeAttributey TextViewRoleAttribute.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("Signature Help controller")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    [ContentType("text")]
    internal class TestSignatureHelpCommandProvider : IVsTextViewCreationListener
    
  2. IVsEditorAdaptersFactoryService Importe (usado para obtener , ITextViewdado el IVsTextView objeto ), ITextStructureNavigatorSelectorService (que se usa para buscar la palabra actual) y el ISignatureHelpBroker objeto (para desencadenar la sesión de Ayuda de firma).

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService;
    
    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ISignatureHelpBroker SignatureHelpBroker;
    
  3. Implemente el método mediante la VsTextViewCreated creación de instancias de TestSignatureCommandHandler.

    public void VsTextViewCreated(IVsTextView textViewAdapter)
    {
        ITextView textView = AdapterService.GetWpfTextView(textViewAdapter);
        if (textView == null)
            return;
    
        textView.Properties.GetOrCreateSingletonProperty(
             () => new TestSignatureHelpCommandHandler(textViewAdapter,
                textView,
                NavigatorService.GetTextStructureNavigator(textView.TextBuffer),
                SignatureHelpBroker));
    }
    

Compilación y prueba del código

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

Para compilar y probar la solución SignatureHelpTest

  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" más un paréntesis de apertura.

  4. Después de escribir el paréntesis de apertura, debería ver una información sobre herramientas que muestra una lista de las dos firmas para el add() método .