チュートリアル: シグネチャ ヘルプを表示する

シグネチャ ヘルプ ("パラメーター ヒント" とも呼ばれます) には、ユーザーがパラメーター リストの開始文字 (通常は始めかっこ) を入力したときに、ツールヒントにメソッドのシグネチャを表示します。 パラメーターおよびパラメーターの区切り記号 (通常はコンマ) が入力されると、ツールヒントが更新され、次のパラメーターが太字で表示されます。 シグネチャ ヘルプは、次の方法で定義できます。言語サービスのコンテキストでは、独自のファイル名拡張子とコンテンツ タイプを定義し、その種類のみのシグネチャ ヘルプを表示するか、既存のコンテンツ タイプ (たとえば、"text") のシグネチャ ヘルプを表示します。 このチュートリアルでは、"text" コンテンツ タイプのシグネチャ ヘルプを表示する方法を示します。

シグネチャ ヘルプは、通常、"(" (始めかっこ) などの特定の文字を入力するとトリガーされ、")" (終わりかっこ) などの別の文字を入力すると破棄されます。 文字を入力するとトリガーされる IntelliSense 機能は、キーストローク (IOleCommandTarget インターフェイス) のコマンド ハンドラーと、IVsTextViewCreationListener インターフェイスを実装するハンドラー プロバイダーを使用して実装できます。 シグネチャ ヘルプに参加するシグネチャの一覧であるシグネチャ ヘルプ ソースを作成するには、ISignatureHelpSource インターフェイスと、ISignatureHelpSourceProvider インターフェイスを実行するソース プロバイダーを実装します。 プロバイダーは、Managed Extensibility Framework (MEF) コンポーネント パーツであり、ソースとコントローラーのクラスをエクスポートしたり、テキスト バッファー内を移動できるようにする 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

シグネチャ ヘルプのシグネチャとパラメーターを実装する

シグネチャ ヘルプ ソースは、ISignature を実装するシグネチャに基づいています。そのシグネチャそれぞれには、IParameter を実装するパラメーターが含まれています。 完全な実装では、この情報は言語ドキュメントから取得されますが、この例では、シグネチャはハードコーディングされています。

シグネチャ ヘルプのシグネチャとパラメーターを実装するには

  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. プロパティ値が変更されたときに CurrentParameterChanged イベントが発生するように、CurrentParameter プロパティを実装します。

    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 メソッドを実装します。 この例では、2 つのシグネチャとそのそれぞれに 2 つのパラメーターがあります。 したがって、このメソッドは必要ありません。 複数のシグネチャ ヘルプ ソースを使用できる完全な実装では、このメソッドは、優先度が最も高いシグネチャ ヘルプ ソースで、一致するシグネチャを提供できるかどうかを判断するために使用されます。 できない場合は、メソッドから 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;
        }
    }
    

シグネチャ ヘルプ ソース プロバイダーを実装する

シグネチャ ヘルプ ソース プロバイダーは、Managed Extensibility Framework (MEF) コンポーネント パーツのエクスポートと、シグネチャ ヘルプ ソースのインスタンス化を担当します。

シグネチャ ヘルプ ソース プロバイダーを実装するには

  1. ISignatureHelpSourceProvider を実装する、TestSignatureHelpSourceProvider という名前のクラスを追加し、NameAttributeContentTypeAttribute として "text"、OrderAttribute として Before="default" を使用してこれをエクスポートします。

    [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 という名前のクラスを追加し、NameAttributeContentTypeAttributeTextViewRoleAttribute を使用してエクスポートします。

    [Export(typeof(IVsTextViewCreationListener))]
    [Name("Signature Help controller")]
    [TextViewRole(PredefinedTextViewRoles.Editable)]
    [ContentType("text")]
    internal class TestSignatureHelpCommandProvider : IVsTextViewCreationListener
    
  2. (ITextView を取得するために使用され、IVsTextView オブジェクトが与えられる) IVsEditorAdaptersFactoryService、(現在の単語を検索するために使用される) 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 の 2 つ目のインスタンスが起動されます。

  3. テキスト ファイルを作成し、"add" という単語と始めかっこを含むテキストを入力します。

  4. 始めかっこを入力した後、add() メソッドの 2 つのシグネチャの一覧を示すツールヒントが表示されます。