연습: 서명 도움말 표시

서명 도움말(매개 변수 정보라고도 함)은 사용자가 매개 변수 목록 시작 문자(일반적으로 여는 괄호)를 입력할 때 도구 설명에 메서드의 서명을 표시합니다. 매개 변수와 매개 변수 구분 기호(일반적으로 쉼표)를 입력하면 도구 설명이 업데이트되어 다음 매개 변수를 굵게 표시합니다. 언어 서비스의 컨텍스트에서 사용자 고유의 파일 이름 확장명 및 콘텐츠 형식을 정의하고 해당 형식에 대해서만 서명 도움말을 표시하거나, 기존 콘텐츠 형식(예: "텍스트")에 대한 서명 도움말을 표시하는 식으로 서명 도움말을 정의할 수 있습니다. 이 연습에서는 "텍스트" 콘텐츠 형식에 대해 서명 도움말을 표시하는 방법을 보여 줍니다.

서명 도움말은 일반적으로 "("(여는 괄호)와 같은 특정 문자를 입력하여 트리거되고 ")"(닫는 괄호) 같은 다른 문자를 입력하여 해제됩니다. 문자 입력으로 트리거되는 IntelliSense 기능은 키 입력에 대한 명령 처리기(IOleCommandTarget 인터페이스) 및 IVsTextViewCreationListener 인터페이스를 구현하는 처리기 공급자를 사용하여 구현할 수 있습니다. 서명 도움말에 참여하는 서명 목록인 서명 도움말 원본을 만들려면 ISignatureHelpSource 인터페이스와, ISignatureHelpSourceProvider 인터페이스를 실행하는 원본 공급자를 구현합니다. 공급자는 MEF(Managed Extensibility Framework) 구성 요소 파트이며 원본 및 컨트롤러 클래스 내보내기와 서비스 및 브로커 가져오기를 담당합니다. 예를 들어 ITextStructureNavigatorSelectorService를 통해 텍스트 버퍼에서 탐색할 수 있고 ISignatureHelpBroker는 서명 도움말 세션을 트리거합니다.

이 연습에서는 하드 코딩된 식별자 집합에 대한 서명 도움말을 설정하는 방법을 보여 줍니다. 전체 구현에서 언어는 해당 콘텐츠를 제공해야 합니다.

MEF 프로젝트 만들기

MEF 프로젝트를 만들려면

  1. C# VSIX 프로젝트를 만듭니다. (새 프로젝트 대화 상자에서 Visual C#/확장성, VSIX 프로젝트를 차례로 선택합니다.) 솔루션 이름을 SignatureHelpTest로 지정합니다.

  2. 프로젝트에 편집기 분류자 항목 템플릿을 추가합니다. 자세한 내용은 편집기 항목 템플릿을 사용하여 확장 만들기를 참조하세요.

  3. 기존 클래스 파일을 삭제합니다.

  4. 프로젝트에 다음 참조를 추가하고 CopyLocalfalse로 설정되었는지 확인합니다.

    Microsoft.VisualStudio.Editor

    Microsoft.VisualStudio.Language.Intellisense

    Microsoft.VisualStudio.OLE.Interop

    Microsoft.VisualStudio.Shell.14.0

    Microsoft.VisualStudio.TextManager.Interop

서명 도움말 서명 및 매개 변수 구현

서명 도움말 원본은 각각 IParameter를 구현하는 매개 변수를 포함하는 ISignature 구현 서명을 기반으로 합니다. 전체 구현에서 이 정보는 언어 설명서에서 가져오지만 이 예제에서는 서명이 하드 코딩됩니다.

서명 도움말 서명 및 매개 변수를 구현하려면

  1. 클래스 파일을 추가하고 이름을 SignatureHelpSource로 지정합니다.

  2. 다음 가져오기를 추가합니다.

    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. IParameter를 구현하는 TestParameter라는 클래스를 추가합니다.

    internal class TestParameter : IParameter
    
  4. 모든 속성을 설정하는 생성자를 추가합니다.

    public TestParameter(string documentation, Span locus, string name, ISignature signature)
    {
        Documentation = documentation;
        Locus = locus;
        Name = name;
        Signature = signature;
    }
    
  5. 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. ISignature를 구현하는 TestSignature라는 클래스를 추가합니다.

    internal class TestSignature : ISignature
    
  7. 일부 프라이빗 필드를 추가합니다.

    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. 필드를 설정하고 Changed 이벤트를 구독하는 생성자를 추가합니다.

    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. CurrentParameterChanged 이벤트를 선언합니다. 이 이벤트는 사용자가 서명의 매개 변수 중 하나를 채울 때 발생합니다.

    public event EventHandler<CurrentParameterChangedEventArgs> CurrentParameterChanged;
    
  10. CurrentParameter 속성 값이 변경될 때 CurrentParameterChanged 이벤트를 발생시키는 속성을 구현합니다.

    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. CurrentParameterChanged 이벤트를 발생시키는 메서드를 추가합니다.

    private void RaiseCurrentParameterChanged(IParameter prevCurrentParameter, IParameter newCurrentParameter)
    {
        EventHandler<CurrentParameterChangedEventArgs> tempHandler = this.CurrentParameterChanged;
        if (tempHandler != null)
        {
            tempHandler(this, new CurrentParameterChangedEventArgs(prevCurrentParameter, newCurrentParameter));
        }
    }
    
  12. ApplicableToSpan의 쉼표 수를 서명의 쉼표 수와 비교하여 현재 매개 변수를 계산하는 메서드를 추가합니다.

    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. ComputeCurrentParameter() 메서드를 호출하는 Changed 이벤트에 대한 이벤트 처리기를 추가합니다.

    internal void OnSubjectBufferChanged(object sender, TextContentChangedEventArgs e)
    {
        this.ComputeCurrentParameter();
    }
    
  14. ApplicableToSpan 속성을 구현합니다. 이 속성은 서명이 적용되는 버퍼의 텍스트 범위에 해당하는 ITrackingSpan을 보유합니다.

    public ITrackingSpan ApplicableToSpan
    {
        get { return (m_applicableToSpan); }
        internal set { m_applicableToSpan = value; }
    }
    
  15. 다른 매개 변수를 구현합니다.

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

서명 도움말 원본 구현

서명 도움말 원본은 정보를 제공하는 서명 집합입니다.

서명 도움말 원본을 구현하려면

  1. ISignatureHelpSource를 구현하는 TestSignatureHelpSource라는 클래스를 추가합니다.

    internal class TestSignatureHelpSource : ISignatureHelpSource
    
  2. 텍스트 버퍼에 참조를 추가합니다.

    private ITextBuffer m_textBuffer;
    
  3. 텍스트 버퍼 및 서명 도움말 원본 공급자를 설정하는 생성자를 추가합니다.

    public TestSignatureHelpSource(ITextBuffer textBuffer)
    {
        m_textBuffer = textBuffer;
    }
    
  4. AugmentSignatureHelpSession 메서드를 구현합니다. 이 예제에서는 서명이 하드 코딩되지만 전체 구현에서 이 정보는 언어 설명서에서 가져옵니다.

    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. 도우미 메서드 CreateSignature()는 그림에만 있습니다.

    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. GetBestMatch 메서드를 구현합니다. 이 예제에는 각각 두 개의 매개 변수가 있는 두 개의 서명만 있습니다. 따라서 이 메서드는 필요하지 않습니다. 둘 이상의 서명 도움말 원본을 사용할 수 있는 전체 구현에서 이 메서드는 우선 순위가 가장 높은 서명 도움말 원본이 일치하는 서명을 제공할 수 있는지 여부를 결정하는 데 사용됩니다. 이렇게 할 수 없는 경우 메서드는 null을 반환하고 다음으로 우선 순위가 가장 높은 원본에 일치 항목을 제공하라는 메시지가 표시됩니다.

    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. Dispose() 메서드를 구현합니다.

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

서명 도움말 원본 공급자 구현

서명 도움말 원본 공급자는 MEF(Managed Extensibility Framework) 구성 요소 부분을 내보내고 서명 도움말 원본을 인스턴스화하는 역할을 담당합니다.

서명 도움말 원본 공급자를 구현하려면

  1. ISignatureHelpSourceProvider를 구현하는 이름이 TestSignatureHelpSourceProvider인 클래스를 추가하고 NameAttribute, “text” 특성의 ContentTypeAttribute, Before="default"의 OrderAttribute로 내보냅니다.

    [Export(typeof(ISignatureHelpSourceProvider))]
    [Name("Signature Help source")]
    [Order(Before = "default")]
    [ContentType("text")]
    internal class TestSignatureHelpSourceProvider : ISignatureHelpSourceProvider
    
  2. TestSignatureHelpSource를 인스턴스화하여 TryCreateSignatureHelpSource를 구현합니다.

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

명령 처리기 구현

서명 도움말은 일반적으로 여는 괄호 "(" 문자에 의해 트리거되고 닫는 괄호 ")" 문자에 의해 해제됩니다. 알려진 메서드 이름 앞에 오는 여는 괄호 문자를 받을 때 서명 도움말 세션을 트리거하고 닫는 괄호 문자를 받으면 세션을 해제할 수 있도록 IOleCommandTarget을 실행하여 이러한 키 입력을 처리할 수 있습니다.

명령 처리기를 구현하려면

  1. IOleCommandTarget를 구현하는 TestSignatureHelpCommand라는 클래스를 추가합니다.

    internal sealed class TestSignatureHelpCommandHandler : IOleCommandTarget
    
  2. IVsTextView 어댑터에 대한 프라이빗 필드(명령 처리기 체인에 명령 처리기를 추가할 수 있음), 텍스트 보기, 서명 도움말 브로커 및 세션, ITextStructureNavigator 및 다음 IOleCommandTarget을 추가합니다.

    IOleCommandTarget m_nextCommandHandler;
    ITextView m_textView;
    ISignatureHelpBroker m_broker;
    ISignatureHelpSession m_session;
    ITextStructureNavigator m_navigator;
    
  3. 생성자를 추가하여 이러한 필드를 초기화하고 명령 필터 체인에 명령 필터를 추가합니다.

    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. 명령 필터가 알려진 메서드 이름 중 하나에서 여는 괄호 "(" 문자를 받을 때 서명 도움말 세션을 트리거하고 세션이 활성 상태인 동안 닫는 괄호 ")" 문자를 받으면 세션을 해제하는 Exec 메서드를 구현합니다. 모든 경우에 명령이 전달됩니다.

    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. QueryStatus 메서드를 구현하여 항상 명령을 전달합니다.

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

서명 도움말 명령 공급자 구현

텍스트 뷰를 만들 때 명령 처리기를 인스턴스화하도록 IVsTextViewCreationListener를 구현하여 서명 도움말 명령을 제공할 수 있습니다.

서명 도움말 명령 공급자를 구현하려면

  1. IVsTextViewCreationListener를 구현하는 클래스 TestSignatureHelpController를 추가하고 NameAttribute, ContentTypeAttributeTextViewRoleAttribute로 내보냅니다.

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("Signature Help controller")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    [ContentType("text")]
    internal class TestSignatureHelpCommandProvider : IVsTextViewCreationListener
    
  2. IVsEditorAdaptersFactoryService(ITextView 가져오기에 사용, IVsTextView 개체의 경우), ITextStructureNavigatorSelectorService(현재 단어 찾기에 사용) 및 ISignatureHelpBroker(서명 도움말 세션 트리거)를 가져옵니다.

    [Import]
    internal IVsEditorAdaptersFactoryService AdapterService;
    
    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ISignatureHelpBroker SignatureHelpBroker;
    
  3. TestSignatureCommandHandler를 인스턴스화하여 VsTextViewCreated 메서드를 구현합니다.

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

코드 빌드 및 테스트

이 코드를 테스트하려면 SignatureHelpTest 솔루션을 빌드하고 실험적 인스턴스에서 실행합니다.

SignatureHelpTest 솔루션을 빌드하고 테스트하려면

  1. 솔루션을 빌드합니다.

  2. 디버거에서 이 프로젝트를 실행하면 Visual Studio의 두 번째 인스턴스가 시작됩니다.

  3. 텍스트 파일을 만들고 단어 "add"와 여는 괄호를 포함하는 일부 텍스트를 입력합니다.

  4. 여는 괄호를 입력한 후 add() 메서드에 대한 두 서명 목록을 표시하는 도구 설명이 표시됩니다.