Compartilhar via


Demonstra Passo a passo: Ajuda da Assinatura de Vídeo

A Ajuda de Assinatura (também conhecida como Informações de Parâmetro) exibe a assinatura de um método em uma dica de ferramenta quando um usuário digita o caractere de início da lista de parâmetros (normalmente um parêntese de abertura). À medida que um parâmetro e um separador de parâmetros (normalmente uma vírgula) são digitados, a dica de ferramenta é atualizada para mostrar o próximo parâmetro em negrito. Você pode definir a Ajuda de Assinatura das seguintes maneiras: no contexto de um serviço de idioma, defina sua própria extensão de nome de arquivo e tipo de conteúdo e exiba a Ajuda de Assinatura apenas para esse tipo ou exiba a Ajuda de Assinatura para um tipo de conteúdo existente (por exemplo, "texto"). Este passo a passo mostra como exibir a Ajuda de assinatura para o tipo de conteúdo "texto".

A Ajuda de Assinatura é normalmente acionada digitando um caractere específico, por exemplo, "(" (parêntese de abertura), e descartada digitando outro caractere, por exemplo, ")" (parêntese de fechamento). Os recursos do IntelliSense que são acionados digitando um caractere podem ser implementados usando um manipulador de comandos para os pressionamentos de tecla (a interface) e um provedor de manipulador que implementa a IOleCommandTargetIVsTextViewCreationListener interface. Para criar a origem da Ajuda de Assinatura, que é a lista de assinaturas que participam da Ajuda de Assinatura, implemente a interface e um provedor de origem que execute a ISignatureHelpSourceISignatureHelpSourceProvider interface. Os provedores são partes do componente MEF (Managed Extensibility Framework) e são responsáveis por exportar as classes de origem e controlador e importar serviços e agentes, por exemplo, o , que permite navegar no buffer de texto e o ITextStructureNavigatorSelectorServiceISignatureHelpBroker, que aciona a sessão de Ajuda de Assinatura.

Este passo a passo mostra como configurar a Ajuda de assinatura para um conjunto codificado de identificadores. Em implementações completas, a linguagem é responsável por fornecer esse conteúdo.

Criando um projeto MEF

Para criar um projeto MEF

  1. Crie um projeto C# VSIX. (No Caixa de diálogo Novo Projeto, selecione Visual C# / Extensibilidade e, em seguida, Projeto VSIX.) Nomeie a solução SignatureHelpTest.

  2. Adicione um modelo de item Editor Classificador ao projeto. Para obter mais informações, consulte Criar uma extensão com um modelo de item do editor.

  3. Exclua os arquivos de classe existentes.

  4. Adicione as seguintes referências ao projeto e verifique se CopyLocal está definido como false:

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.14.0

    Microsoft.VisualStudio.TextManager.Interop

Implementar assinaturas e parâmetros da Ajuda de Assinatura

A origem da Ajuda de Assinatura é baseada em assinaturas que implementam , cada uma das quais contém parâmetros que implementam ISignatureIParametero . Em uma implementação completa, essas informações seriam obtidas da documentação do idioma, mas neste exemplo, as assinaturas são codificadas.

Para implementar as assinaturas e os parâmetros da Ajuda da Assinatura

  1. Adicione um arquivo de classe e nomeie-o SignatureHelpSource.

  2. Adicione as seguintes importações.

    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. Adicione uma classe chamada TestParameter que implementa IParametero .

    internal class TestParameter : IParameter
    
  4. Adicione um construtor que defina todas as propriedades.

    public TestParameter(string documentation, Span locus, string name, ISignature signature)
    {
        Documentation = documentation;
        Locus = locus;
        Name = name;
        Signature = signature;
    }
    
  5. Adicione as propriedades 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. Adicione uma classe chamada TestSignature que implementa ISignatureo .

    internal class TestSignature : ISignature
    
  7. Adicione alguns 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. Adicione um construtor que defina os campos e se inscreva no 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 um CurrentParameterChanged evento. Esse evento é gerado quando o usuário preenche um dos parâmetros na assinatura.

    public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged;
    
  10. Implemente a propriedade para que ela gere CurrentParameter o evento quando o CurrentParameterChanged valor da propriedade for alterado.

    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. Adicione um método que gera o CurrentParameterChanged evento.

    private void RaiseCurrentParameterChanged(IParameter prevCurrentParameter, IParameter newCurrentParameter)
    {
        EventHandler<CurrentParameterChangedEventArgs> tempHandler = this.CurrentParameterChanged;
        if (tempHandler != null)
        {
            tempHandler(this, new CurrentParameterChangedEventArgs(prevCurrentParameter, newCurrentParameter));
        }
    }
    
  12. Adicione um método que calcule o parâmetro atual comparando o número de vírgulas no com ApplicableToSpan o número de vírgulas na assinatura.

    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. Adicione um manipulador de eventos para o evento que chama o ChangedComputeCurrentParameter() método.

    internal void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
    {
        this.ComputeCurrentParameter();
    }
    
  14. Implemente a propriedade ApplicableToSpan. Essa propriedade contém um ITrackingSpan que corresponde à extensão do texto no buffer ao qual a assinatura se aplica.

    public ITrackingSpan ApplicableToSpan
    {
        get { return (m_applicableToSpan); }
        internal set { m_applicableToSpan = value; }
    }
    
  15. Implemente os outros 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; }
    }
    

Implementar a fonte da Ajuda de Assinatura

A fonte da Ajuda de Assinatura é o conjunto de assinaturas para o qual você fornece informações.

Para implementar a fonte da Ajuda de Assinatura

  1. Adicione uma classe chamada TestSignatureHelpSource que implementa ISignatureHelpSourceo .

    internal class TestSignatureHelpSource : ISignatureHelpSource
    
  2. Adicione uma referência ao buffer de texto.

    private ITextBuffer m_textBuffer;
    
  3. Adicione um construtor que defina o buffer de texto e o provedor de origem da Ajuda de Assinatura.

    public TestSignatureHelpSource(ITextBuffer textBuffer)
    {
        m_textBuffer = textBuffer;
    }
    
  4. Implementar o método de AugmentSignatureHelpSession . Neste exemplo, as assinaturas são codificadas, mas em uma implementação completa você obteria essas informações da documentação do idioma.

    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. O método CreateSignature() auxiliar é fornecido apenas para ilustração.

    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. Implementar o método de GetBestMatch . Neste exemplo, há apenas duas assinaturas, cada uma com dois parâmetros. Portanto, esse método não é necessário. Em uma implementação mais completa, na qual mais de uma fonte da Ajuda de Assinatura está disponível, esse método é usado para decidir se a fonte da Ajuda de Assinatura de prioridade mais alta pode fornecer uma assinatura correspondente. Se não for possível, o método retornará null e a próxima fonte de prioridade mais alta será solicitada a fornecer uma correspondência.

    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 o Dispose() método:

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

Implementar o provedor de origem da Ajuda de Assinatura

O provedor de origem da Ajuda de Assinatura é responsável por exportar a parte do componente MEF (Managed Extensibility Framework) e por instanciar a fonte da Ajuda de Assinatura.

Para implementar o provedor de origem da Ajuda de Assinatura

  1. Adicione uma classe chamada TestSignatureHelpSourceProvider que implementa ISignatureHelpSourceProvidero e exporte-a com um , a ContentTypeAttribute de "text" e um NameAttributeOrderAttribute de Before="default".

    [Export(typeof(ISignatureHelpSourceProvider))]
    [Name("Signature Help source")]
    [Order(Before = "default")]
    [ContentType("text")]
    internal class TestSignatureHelpSourceProvider : ISignatureHelpSourceProvider
    
  2. Implemente TryCreateSignatureHelpSource instanciando o TestSignatureHelpSource.

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

Implementar o manipulador de comandos

A Ajuda de Assinatura é normalmente acionada por um caractere "(" de parêntese de abertura e descartada por um caractere de parêntese de fechamento ")". Você pode manipular esses pressionamentos de tecla executando um para que ele acione uma sessão de Ajuda de Assinatura quando ele recebe um caractere de parêntese de abertura precedido por um nome de método conhecido e descarta a sessão quando recebe um IOleCommandTarget caractere de parêntese de fechamento.

Para implementar o manipulador de comandos

  1. Adicione uma classe chamada TestSignatureHelpCommand que implementa IOleCommandTargeto .

    internal sealed class TestSignatureHelpCommandHandler : IOleCommandTarget
    
  2. Adicione campos particulares para o adaptador (que permite adicionar o manipulador de comandos à cadeia de manipuladores de comandos), o modo de exibição de texto, o agente e a sessão da Ajuda de Assinatura, um ITextStructureNavigatore o IVsTextView próximo IOleCommandTarget.

    IOleCommandTarget m_nextCommandHandler;
    ITextView m_textView;
    ISignatureHelpBroker m_broker;
    ISignatureHelpSession m_session;
    ITextStructureNavigator m_navigator;
    
  3. Adicione um construtor para inicializar esses campos e adicionar o filtro de comando à cadeia de filtros de comando.

    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 o método para disparar a sessão de Ajuda de Assinatura quando o filtro de comando receber um caractere de parêntese de abertura "(" após um dos nomes de método conhecidos e para descartar a sessão quando receber um caractere de parêntese Exec de fechamento ")" enquanto a sessão ainda estiver ativa. Em todos os casos, o comando é encaminhado.

    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 o método para que ele sempre encaminhe o QueryStatus comando.

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

Implementar o provedor de comandos Ajuda de Assinatura

Você pode fornecer o comando Ajuda de Assinatura implementando o para instanciar o manipulador de comandos quando o IVsTextViewCreationListener modo de exibição de texto for criado.

Para implementar o provedor de comandos Ajuda de Assinatura

  1. Adicione uma classe chamada TestSignatureHelpController que implementa IVsTextViewCreationListener e exporta-a com o NameAttribute, ContentTypeAttributee TextViewRoleAttribute.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("Signature Help controller")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    [ContentType("text")]
    internal class TestSignatureHelpCommandProvider : IVsTextViewCreationListener
    
  2. Importe o (usado para obter o , dado o objeto), o (usado para localizar a palavra atual) e o IVsEditorAdaptersFactoryServiceITextStructureNavigatorSelectorServiceITextViewIVsTextViewISignatureHelpBroker (para acionar a sessão da Ajuda de Assinatura).

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService;
    
    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ISignatureHelpBroker SignatureHelpBroker;
    
  3. Implemente o método instanciando o VsTextViewCreatedTestSignatureCommandHandler.

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

Compilar e testar o código

Para testar esse código, crie a solução SignatureHelpTest e execute-a na instância experimental.

Para criar e testar a solução SignatureHelpTest

  1. Compile a solução.

  2. Quando você executa esse projeto no depurador, uma segunda instância do Visual Studio é iniciada.

  3. Crie um arquivo de texto e digite algum texto que inclua a palavra "adicionar" mais um parêntese de abertura.

  4. Depois de digitar o parêntese de abertura, você verá uma dica de ferramenta que exibe uma lista das duas assinaturas do add() método.