Walkthrough: Displaying Signature Help

 

For the latest documentation on Visual Studio 2017 RC, see Visual Studio 2017 RC Documentation.

Signature Help (also known as Parameter Info) displays the signature of a method in a tooltip when a user types the parameter list start character (typically an opening parenthesis). As a parameter and parameter separator (typically a comma) are typed, the tooltip is updated to show the next parameter in bold. You can define Signature Help in the context of a language service, or you can define your own file name extension and content type and display Signature Help for just that type, or you can display Signature Help for an existing content type (for example, "text"). This walkthrough shows how to display Signature Help for the "text" content type.

Signature Help is typically triggered by typing a specific character, for example, "(" (opening parenthesis), and dismissed by typing another character, for example, ")" (closing parenthesis). IntelliSense features that are triggered by typing a character can be implemented by using a command handler for the keystrokes (the IOleCommandTarget interface) and a handler provider that implements the IVsTextViewCreationListener interface. To create the Signature Help source, which is the list of signatures that participate in Signature Help, implement the ISignatureHelpSource interface and a source provider that implements the ISignatureHelpSourceProvider interface. The providers are Managed Extensibility Framework (MEF) component parts, and are responsible for exporting the source and controller classes and importing services and brokers, for example, the ITextStructureNavigatorSelectorService, which lets you navigate in the text buffer, and the ISignatureHelpBroker, which triggers the Signature Help session.

This walkthrough shows how to implement Signature Help for a hard-coded set of identifiers. In full implementations, the language is responsible for providing that content.

Starting in Visual Studio 2015, you do not install the Visual Studio SDK from the download center. It is included as an optional feature in Visual Studio setup. You can also install the VS SDK later on. For more information, see Installing the Visual Studio SDK.

To create a MEF project

  1. Create a C# VSIX project. (In the New Project dialog, select Visual C# / Extensibility, then VSIX Project.) Name the solution SignatureHelpTest.

  2. Add an Editor Classifier item template to the project. For more information, see Creating an Extension with an Editor Item Template.

  3. Delete the existing class files.

  4. Add the following references to the project, and make sure CopyLocal is set to false:

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.14.0

    Microsoft.VisualStudio.TextManager.Interop

The Signature Help source is based on signatures that implement ISignature, each of which contains parameters that implement IParameter. In a full implementation, this information would be obtained from the language documentation, but in this example, the signatures are hard-coded.

To implement the Signature Help signatures and parameters

  1. Add a class file and name it SignatureHelpSource.

  2. Add the following imports.

    Imports System
    Imports System.Collections.Generic
    Imports System.Linq
    Imports System.Text
    Imports System.Collections.ObjectModel
    Imports System.ComponentModel.Composition
    Imports System.Runtime.InteropServices
    Imports Microsoft.VisualStudio.Language.Intellisense
    Imports Microsoft.VisualStudio.Text
    Imports Microsoft.VisualStudio.Text.Editor
    Imports Microsoft.VisualStudio.Utilities
    Imports Microsoft.VisualStudio.Editor
    Imports Microsoft.VisualStudio.Text.Operations
    Imports Microsoft.VisualStudio
    Imports Microsoft.VisualStudio.TextManager.Interop
    Imports Microsoft.VisualStudio.OLE.Interop
    

  3. Add a class named TestParameter that implements IParameter.

    Friend Class TestParameter
        Implements IParameter
    

  4. Add a constructor that sets all the properties.

        Public Sub New(ByVal documentation As String, ByVal locus As Span, ByVal name As String, ByVal signature As ISignature)
            Me.privateDocumentation = documentation
            Me.privateLocus = locus
            Me.privateName = name
            Me.privateSignature = signature
        End Sub
    

  5. Add the properties of IParameter.

        Private privateDocumentation As String
        ReadOnly Property Documentation() As String Implements IParameter.Documentation
            Get
                Return privateDocumentation
            End Get
    
        End Property
        Private privateLocus As Span
        ReadOnly Property Locus() As Span Implements IParameter.Locus
            Get
                Return privateLocus
            End Get
        End Property
        Private privateName As String
        ReadOnly Property Name() As String Implements IParameter.Name
            Get
                Return privateName
            End Get
        End Property
        Private privateSignature As ISignature
        ReadOnly Property Signature() As ISignature Implements IParameter.Signature
            Get
                Return privateSignature
            End Get
        End Property
        Private privatePrettyPrintedLocus As Span
        ReadOnly Property PrettyPrintedLocus() As Span Implements IParameter.PrettyPrintedLocus
            Get
                Return privatePrettyPrintedLocus
            End Get
        End Property
    

  6. Add a class named TestSignature that implements ISignature.

    Friend Class TestSignature
        Implements ISignature
    

  7. Add some private fields.

        Private m_subjectBuffer As ITextBuffer
        Private m_currentParameter As IParameter
        Private m_content As String
        Private m_documentation As String
        Friend m_applicableToSpan As ITrackingSpan
        Friend m_parameters As ReadOnlyCollection(Of IParameter)
        Private m_printContent As String
    

  8. Add a constructor that sets the fields and subscribes to the Changed event.

        Friend Sub New(ByVal subjectBuffer As ITextBuffer, ByVal content As String, ByVal doc As String, ByVal parameters As ReadOnlyCollection(Of IParameter))
            m_subjectBuffer = subjectBuffer
            m_content = content
            m_documentation = doc
            m_parameters = parameters
            AddHandler m_subjectBuffer.Changed, AddressOf OnSubjectBufferChanged
        End Sub
    

  9. Declare a CurrentParameterChanged event. This event is raised when the user fills in one of the parameters in the signature.

        Public Event CurrentParameterChanged As EventHandler(Of CurrentParameterChangedEventArgs) Implements ISignature.CurrentParameterChanged
    

  10. Implement the CurrentParameter property so that it raises the CurrentParameterChanged event when the property value is changed.

        ReadOnly Property CurrentParameter() As IParameter Implements ISignature.CurrentParameter
            Get
                Return m_currentParameter
            End Get
        End Property
    

  11. Add a method that raises the CurrentParameterChanged event.

        Private Sub RaiseCurrentParameterChanged(ByVal prevCurrentParameter As IParameter, ByVal newCurrentParameter As IParameter)
            Dim tempHandler As EventHandler(Of CurrentParameterChangedEventArgs) = Me.CurrentParameterChangedEvent
            If tempHandler IsNot Nothing Then
                tempHandler(Me, New CurrentParameterChangedEventArgs(prevCurrentParameter, newCurrentParameter))
            End If
        End Sub
    

  12. Add a method that computes the current parameter by comparing the number of commas in the ApplicableToSpan to the number of commas in the signature.

        Friend Sub ComputeCurrentParameter()
            If Parameters.Count = 0 Then
                Me.m_currentParameter = Nothing
                Return
            End If
    
            'the number of commas in the string is the index of the current parameter
            Dim sigText As String = ApplicableToSpan.GetText(m_subjectBuffer.CurrentSnapshot)
    
            Dim currentIndex As Integer = 0
            Dim commaCount As Integer = 0
            Do While currentIndex < sigText.Length
                Dim commaIndex As Integer = sigText.IndexOf(","c, currentIndex)
                If commaIndex = -1 Then
                    Exit Do
                End If
                commaCount += 1
                currentIndex = commaIndex + 1
            Loop
    
            If commaCount < Parameters.Count Then
                Me.m_currentParameter = Parameters(commaCount)
            Else
                'too many commas, so use the last parameter as the current one.
                Me.m_currentParameter = Parameters(Parameters.Count - 1)
            End If
        End Sub
    

  13. Add an event handler for the Changed event that calls the ComputeCurrentParameter() method.

        Friend Sub OnSubjectBufferChanged(ByVal sender As Object, ByVal e As TextContentChangedEventArgs)
            Me.ComputeCurrentParameter()
        End Sub
    

  14. Implement the ApplicableToSpan property. This property holds an ITrackingSpan that corresponds to the span of text in the buffer to which the signature applies.

        ReadOnly Property ApplicableToSpan() As ITrackingSpan Implements ISignature.ApplicableToSpan
            Get
                Return (m_applicableToSpan)
            End Get
        End Property
    

  15. Implement the other parameters.

        ReadOnly Property Content() As String Implements ISignature.Content
            Get
                Return (m_content)
            End Get
        End Property
    
        ReadOnly Property Documentation() As String Implements ISignature.Documentation
            Get
                Return (m_documentation)
            End Get
        End Property
    
        ReadOnly Property Parameters() As ReadOnlyCollection(Of IParameter) Implements ISignature.Parameters
            Get
                Return (m_parameters)
            End Get
        End Property
    
        ReadOnly Property PrettyPrintedContent() As String Implements ISignature.PrettyPrintedContent
            Get
                Return (m_printContent)
            End Get
        End Property
    

The Signature Help source is the set of signatures for which you provide information.

To implement the Signature Help source

  1. Add a class named TestSignatureHelpSource that implements ISignatureHelpSource.

    Friend Class TestSignatureHelpSource
        Implements ISignatureHelpSource
    

  2. Add a reference to the text buffer.

        Private m_textBuffer As ITextBuffer
    

  3. Add a constructor that sets the text buffer and the Signature Help source provider.

        Public Sub New(ByVal textBuffer As ITextBuffer)
            m_textBuffer = textBuffer
        End Sub
    

  4. Implement the AugmentSignatureHelpSession method. In this example, the signatures are hard-coded, but in a full implementation you would get this information from the language documentation.

        Public Sub AugmentSignatureHelpSession(ByVal session As ISignatureHelpSession, ByVal signatures As IList(Of ISignature)) Implements ISignatureHelpSource.AugmentSignatureHelpSession
            Dim snapshot As ITextSnapshot = m_textBuffer.CurrentSnapshot
            Dim position As Integer = session.GetTriggerPoint(m_textBuffer).GetPosition(snapshot)
    
            Dim applicableToSpan As ITrackingSpan = 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))
        End Sub
    

  5. The helper method CreateSignature() is provided just for illustration.

        Private Function CreateSignature(ByVal textBuffer As ITextBuffer, ByVal methodSig As String, ByVal methodDoc As String, ByVal span As ITrackingSpan) As TestSignature
            Dim sig As New TestSignature(textBuffer, methodSig, methodDoc, Nothing)
            AddHandler textBuffer.Changed, AddressOf sig.OnSubjectBufferChanged
    
            'find the parameters in the method signature (expect methodname(one, two)
            Dim pars() As String = methodSig.Split(New Char() {"("c, ","c, ")"c})
            Dim paramList As New List(Of IParameter)()
    
            Dim locusSearchStart As Integer = 0
            For i As Integer = 1 To pars.Length - 1
                Dim param As String = pars(i).Trim()
    
                If String.IsNullOrEmpty(param) Then
                    Continue For
                End If
    
                'find where this parameter is located in the method signature
                Dim locusStart As Integer = methodSig.IndexOf(param, locusSearchStart)
                If locusStart >= 0 Then
                    Dim locus As New Span(locusStart, param.Length)
                    locusSearchStart = locusStart + param.Length
                    paramList.Add(New TestParameter("Documentation for the parameter.", locus, param, sig))
                End If
            Next i
    
            sig.m_Parameters = New ReadOnlyCollection(Of IParameter)(paramList)
            sig.m_ApplicableToSpan = span
            sig.ComputeCurrentParameter()
            Return sig
        End Function
    

  6. Implement the GetBestMatch method. In this example, there are just two signatures, each of which has two parameters. Therefore, this method is not required. In a fuller implementation, in which more than one Signature Help source is available, this method is used to decide whether the highest-priority Signature Help source can supply a matching signature. If it cannot, the method returns null and the next-highest-priority source is asked to supply a match.

        Public Function GetBestMatch(ByVal session As ISignatureHelpSession) As ISignature Implements ISignatureHelpSource.GetBestMatch
            If session.Signatures.Count > 0 Then
                Dim applicableToSpan As ITrackingSpan = session.Signatures(0).ApplicableToSpan
                Dim text As String = applicableToSpan.GetText(applicableToSpan.TextBuffer.CurrentSnapshot)
    
                If text.Trim().Equals("add") Then 'get only "add"
                    Return session.Signatures(0)
                End If
            End If
            Return Nothing
        End Function
    

  7. Implement the Dispose() method:

        Private m_isDisposed As Boolean
    
        Public Sub Dispose() Implements IDisposable.Dispose
            If Not m_isDisposed Then
                GC.SuppressFinalize(Me)
                m_isDisposed = True
            End If
        End Sub
    

The Signature Help source provider is responsible for exporting the Managed Extensibility Framework (MEF) component part and for instantiating the Signature Help source.

To implement the Signature Help source provider

  1. Add a class named TestSignatureHelpSourceProvider that implements ISignatureHelpSourceProvider, and export it with a NameAttribute, a ContentTypeAttribute of "text", and an OrderAttribute of Before="default".

    <Export(GetType(ISignatureHelpSourceProvider)), Name("Signature Help source"), Order(Before:="default"), ContentType("text")>
    Friend Class TestSignatureHelpSourceProvider
        Implements ISignatureHelpSourceProvider
    

  2. Implement TryCreateSignatureHelpSource by instantiating the TestSignatureHelpSource.

        Public Function TryCreateSignatureHelpSource(ByVal textBuffer As ITextBuffer) As ISignatureHelpSource Implements ISignatureHelpSourceProvider.TryCreateSignatureHelpSource
            Return New TestSignatureHelpSource(textBuffer)
        End Function
    

Signature Help is typically triggered by a ( character and dismissed by a ) character. You can handle these keystrokes by implementing a IOleCommandTarget so that it triggers a Signature Help session when it receives a ( character preceded by a known method name, and dismisses the session when it receives a ) character.

To implement the command handler

  1. Add a class named TestSignatureHelpCommand that implements IOleCommandTarget.

    Friend NotInheritable Class TestSignatureHelpCommandHandler
        Implements IOleCommandTarget
    

  2. Add private fields for the IVsTextView adapter (which lets you add the command handler to the chain of command handlers), the text view, the Signature Help broker and session, a ITextStructureNavigator, and the next IOleCommandTarget.

        Private m_nextCommandHandler As IOleCommandTarget
        Private m_textView As ITextView
        Private m_broker As ISignatureHelpBroker
        Private m_session As ISignatureHelpSession
        Private m_navigator As ITextStructureNavigator
    

  3. Add a constructor to initialize these fields and to add the command filter to the chain of command filters.

        Friend Sub New(ByVal textViewAdapter As IVsTextView, ByVal textView As ITextView, ByVal nav As ITextStructureNavigator, ByVal broker As ISignatureHelpBroker)
            Me.m_textView = textView
            Me.m_broker = broker
            Me.m_navigator = nav
    
            'add this to the filter chain
            textViewAdapter.AddCommandFilter(Me, m_nextCommandHandler)
        End Sub
    

  4. Implement the Exec method to trigger the Signature Help session when the command filter receives a ( character after one of the known method names, and to dismiss the session when it receives a ) character while the session is still active. In every case, the command is forwarded.

        Public Function Exec(ByRef pguidCmdGroup As Guid, ByVal nCmdID As UInteger, ByVal nCmdexecopt As UInteger, ByVal pvaIn As IntPtr, ByVal pvaOut As IntPtr) As Integer Implements IOleCommandTarget.Exec
            Dim typedChar As Char = Char.MinValue
    
            If pguidCmdGroup = VSConstants.VSStd2K AndAlso nCmdID = CUInt(VSConstants.VSStd2KCmdID.TYPECHAR) Then
                typedChar = CChar(ChrW(CUShort(Marshal.GetObjectForNativeVariant(pvaIn))))
                If typedChar.Equals("("c) Then
                    'move the point back so it's in the preceding word
                    Dim point As SnapshotPoint = m_textView.Caret.Position.BufferPosition - 1
                    Dim extent As TextExtent = m_navigator.GetExtentOfWord(point)
                    Dim word As String = extent.Span.GetText()
                    If word.Equals("add") Then
                        m_session = m_broker.TriggerSignatureHelp(m_textView)
                    End If
    
                ElseIf typedChar.Equals(")"c) AndAlso m_session IsNot Nothing Then
                    m_session.Dismiss()
                    m_session = Nothing
                End If
            End If
            Return m_nextCommandHandler.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut)
        End Function
    

  5. Implement the QueryStatus method so that it always forwards the command.

        Public Function QueryStatus(ByRef pguidCmdGroup As Guid, ByVal cCmds As UInteger, ByVal prgCmds() As OLECMD, ByVal pCmdText As IntPtr) As Integer Implements IOleCommandTarget.QueryStatus
            Return m_nextCommandHandler.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText)
        End Function
    

You can provide the Signature Help command by implementing the IVsTextViewCreationListener to instantiate the command handler when the text view is created.

To implement the Signature Help command provider

  1. Add a class named TestSignatureHelpController that implements IVsTextViewCreationListener and export it with the NameAttribute, ContentTypeAttribute, and TextViewRoleAttribute.

    <Export(GetType(IVsTextViewCreationListener)), Name("Signature Help controller"), TextViewRole(PredefinedTextViewRoles.Editable), ContentType("text")>
    Friend Class TestSignatureHelpCommandProvider
        Implements IVsTextViewCreationListener
    

  2. Import the IVsEditorAdaptersFactoryService (used to get the ITextView, given the IVsTextView object), the ITextStructureNavigatorSelectorService (used to find the current word), and the ISignatureHelpBroker (to trigger the Signature Help session).

        <Import()>
        Friend AdapterService As IVsEditorAdaptersFactoryService
    
        <Import()>
        Friend Property NavigatorService() As ITextStructureNavigatorSelectorService
    
        <Import()>
        Friend SignatureHelpBroker As ISignatureHelpBroker
    

  3. Implement the VsTextViewCreated method by instantiating the TestSignatureCommandHandler.

        Public Sub VsTextViewCreated(ByVal textViewAdapter As IVsTextView) Implements IVsTextViewCreationListener.VsTextViewCreated
            Dim textView As ITextView = AdapterService.GetWpfTextView(textViewAdapter)
            If textView Is Nothing Then
                Return
            End If
    
            textView.Properties.GetOrCreateSingletonProperty(Function() New TestSignatureHelpCommandHandler(textViewAdapter, textView, NavigatorService.GetTextStructureNavigator(textView.TextBuffer), SignatureHelpBroker))
        End Sub
    

To test this code, build the SignatureHelpTest solution and run it in the experimental instance.

To build and test the SignatureHelpTest solution

  1. Build the solution.

  2. When you run this project in the debugger, a second instance of Visual Studio is instantiated.

  3. Create a text file and type some text that includes the word "add" plus an opening parenthesis.

  4. After you type the opening parenthesis, you should see a tooltip that displays a list of the two signatures for the add() method.

Walkthrough: Linking a Content Type to a File Name Extension

Show: