Compartilhar via


Passo a passo: exibir preenchimento de declaração

Você pode implementar a conclusão de instrução baseada em idioma definindo os identificadores para os quais deseja fornecer conclusão e, em seguida, disparando uma sessão de conclusão. Você pode definir a conclusão da instrução no contexto de um serviço de idioma, definir sua própria extensão de nome de arquivo e tipo de conteúdo e, em seguida, exibir a conclusão apenas para esse tipo. Ou, você pode disparar a conclusão para um tipo de conteúdo existente — por exemplo, "texto sem formatação". Este passo a passo mostra como disparar a conclusão de instrução para o tipo de conteúdo "texto sem formatação", que é o tipo de conteúdo dos arquivos de texto. O tipo de conteúdo "texto" é o ancestral de todos os outros tipos de conteúdo, incluindo código e arquivos XML.

Normalmente, a conclusão da instrução é acionada digitando determinados caracteres, por exemplo, digitando o início de um identificador, como "usando". Normalmente, ele é descartado pressionando a tecla Barra de espaço, Tab ou Enter para confirmar uma seleção. Você pode implementar os recursos do IntelliSense que são acionados ao digitar um caractere 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 fonte de conclusão, que é a lista de identificadores que participam da conclusão, implemente a interface e um provedor de origem de conclusão (a ICompletionSourceICompletionSourceProvider interface). Os provedores são partes do componente MEF (Managed Extensibility Framework). Eles são responsáveis por exportar as classes de origem e controlador e importar serviços e brokers — por exemplo, o , que permite a navegação no buffer de texto e o ITextStructureNavigatorSelectorServiceICompletionBroker, que aciona a sessão de conclusão.

Este passo a passo mostra como implementar a conclusão de instrução para um conjunto embutido de identificadores embutido em código. Em implementações completas, o serviço de idioma e a documentação de idioma são responsáveis por fornecer esse conteúdo.

Criar 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 CompletionTest.

  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 certifique-se de que CopyLocal está definido como 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

Implementar a origem de conclusão

A fonte de conclusão é responsável por coletar o conjunto de identificadores e adicionar o conteúdo à janela de conclusão quando um usuário digita um gatilho de conclusão, como as primeiras letras de um identificador. Neste exemplo, os identificadores e suas descrições são embutidos no AugmentCompletionSession método. Na maioria dos usos do mundo real, você usaria o analisador do seu idioma para obter os tokens para preencher a lista de conclusão.

Para implementar a fonte de conclusão

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

  2. Adicione estas importações:

    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 a declaração de classe para TestCompletionSource que ela implemente ICompletionSource:

    internal class TestCompletionSource : ICompletionSource
    
  4. Adicione campos privados para o provedor de origem, o buffer de texto e uma lista de objetos (que correspondem aos identificadores que participarão da sessão de Completion conclusão):

    private TestCompletionSourceProvider m_sourceProvider;
    private ITextBuffer m_textBuffer;
    private List<Completion> m_compList;
    
  5. Adicione um construtor que defina o provedor de origem e o buffer. A TestCompletionSourceProvider classe é definida em etapas posteriores:

    public TestCompletionSource(TestCompletionSourceProvider sourceProvider, ITextBuffer textBuffer)
    {
        m_sourceProvider = sourceProvider;
        m_textBuffer = textBuffer;
    }
    
  6. Implemente o AugmentCompletionSession método adicionando um conjunto de conclusão que contém as conclusões que você deseja fornecer no contexto. Cada conjunto de conclusão contém um conjunto de conclusões e corresponde a uma guia da janela de Completion conclusão. (Em projetos do Visual Basic, as guias da janela de conclusão são nomeadas Comum e Tudo.) O FindTokenSpanAtPosition método é definido na próxima etapa.

    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. O método a seguir é usado para localizar a palavra atual a partir da posição do 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 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 de conclusão

O provedor de origem de conclusão é a parte do componente MEF que instancia a fonte de conclusão.

Para implementar o provedor de origem de conclusão

  1. Adicione uma classe chamada TestCompletionSourceProvider que implementa ICompletionSourceProvidero . Exporte essa classe com um de "texto sem formatação" e um ContentTypeAttributeNameAttribute de "conclusão de teste".

    [Export(typeof(ICompletionSourceProvider))]
    [ContentType("plaintext")]
    [Name("token completion")]
    internal class TestCompletionSourceProvider : ICompletionSourceProvider
    
  2. Importe um ITextStructureNavigatorSelectorService, que localiza a palavra atual na fonte de conclusão.

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  3. Implemente o TryCreateCompletionSource método para instanciar a origem de conclusão.

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

Implementar o provedor de manipulador de comandos de conclusão

O provedor do manipulador de comandos de conclusão é derivado de um , que escuta um evento de criação de modo de exibição de texto e converte o modo de exibição de um — que permite a adição do comando à cadeia de comandos do shell do Visual Studio — em um IVsTextViewCreationListenerIVsTextViewITextView. Como essa classe é uma exportação MEF, você também pode usá-la para importar os serviços que são exigidos pelo próprio manipulador de comandos.

Para implementar o provedor de manipulador de comandos de conclusão

  1. Adicione um arquivo chamado TestCompletionCommandHandler.

  2. Adicione estes usando diretivas:

    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. Adicione uma classe chamada TestCompletionHandlerProvider que implementa IVsTextViewCreationListenero . Exporte essa classe com um de "manipulador de conclusão de token", um de "texto sem formatação" e um NameAttributeContentTypeAttributeTextViewRoleAttribute de .Editable

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("token completion handler")]
    [ContentType("plaintext")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    internal class TestCompletionHandlerProvider : IVsTextViewCreationListener
    
  4. Importe o , que permite a conversão de um para um , um e um SVsServiceProviderIVsTextViewITextViewICompletionBrokerque permite o IVsEditorAdaptersFactoryServiceacesso aos serviços padrão do Visual Studio.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService = null;
    [Import]
    internal ICompletionBroker CompletionBroker { get; set; }
    [Import]
    internal SVsServiceProvider ServiceProvider { get; set; }
    
  5. Implemente o método para instanciar o VsTextViewCreated manipulador 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);
    }
    

Implementar o manipulador de comandos de conclusão

Como a conclusão da instrução é acionada por pressionamentos de tecla, você deve implementar a interface para receber e processar os pressionamentos de tecla que disparam, confirmam e descartam a IOleCommandTarget sessão de conclusão.

Para implementar o manipulador de comandos de conclusão

  1. Adicione uma classe chamada TestCompletionCommandHandler que implementa IOleCommandTarget:

    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  2. Adicione campos particulares para o próximo manipulador de comandos (para o qual você passa o comando), o modo de exibição de texto, o provedor do manipulador de comandos (que permite o acesso a vários serviços) e uma sessão de conclusão:

    private IOleCommandTarget m_nextCommandHandler;
    private ITextView m_textView;
    private TestCompletionHandlerProvider m_provider;
    private ICompletionSession m_session;
    
  3. Adicione um construtor que defina o modo de exibição de texto e os campos do provedor e adicione o comando à cadeia 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 o método passando o QueryStatus comando junto:

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  5. Implementar o método de Exec . Quando esse método recebe um pressionamento de tecla, ele deve fazer uma destas coisas:

    • Permita que o caractere seja gravado no buffer e, em seguida, dispare ou filtre a conclusão. (A impressão de caracteres faz isso.)

    • Confirme a conclusão, mas não permita que o caractere seja gravado no buffer. (Espaço em branco, Tab e Enter fazem isso quando uma sessão de conclusão é exibida.)

    • Permita que o comando seja passado para o próximo manipulador. (Todos os outros comandos.)

      Como esse método pode exibir a interface do usuário, chame IsInAutomationFunction para certificar-se de que ele não é chamado em um contexto de automação:

      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. Esse código é um método privado que dispara a sessão de conclusão:

    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. O próximo exemplo é um método privado que cancela a inscrição do Dismissed evento:

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

Compilar e testar o código

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

Para criar e testar a solução CompletionTest

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

  4. Ao digitar primeiro "a" e depois "d", uma lista que contém "adição" e "adaptação" deve aparecer. Observe que a adição está selecionada. Quando você digita outro "d", a lista deve conter apenas "adição", que agora está selecionada. Você pode confirmar "adição" pressionando a tecla Barra de espaço, Tab ou Enter, ou descartar a lista digitando Esc ou qualquer outra tecla.