チュートリアル: QuickInfo ツールヒントの表示

QuickInfo は、ユーザーがメソッド名の上にポインターを移動したときにメソッドのシグネチャと説明を表示する、IntelliSense 機能です。 QuickInfo のような言語ベースの機能は、QuickInfo の説明の提供対象となる識別子を定義した後で、コンテンツを表示するためのツールヒントを作成することによって実装できます。 言語サービスのコンテキストで QuickInfo を定義することも、独自のファイル名拡張子とコンテンツ タイプを定義し、そのタイプだけのために QuickInfo を表示することもできます。または、既存のコンテンツ タイプ ("text" など) のために QuickInfo を表示することもできます。 このチュートリアルでは、"text" コンテンツ タイプのために QuickInfo を表示する方法を示します。

このチュートリアルの QuickInfo の例では、ユーザーがポインターをメソッド名の上に移動したときにツールヒントを表示します。 この設計では、以下の 4 つのインターフェイスを実装する必要があります。

  • ソース インターフェイス

  • ソース プロバイダー インターフェイス

  • コントローラー インターフェイス

  • コントローラー プロバイダー インターフェイス

    ソースとコントローラーのプロバイダーは、Managed Extensibility Framework (MEF) コンポーネント パーツであり、ソースとコントローラーのクラスをエクスポートしたり、ツールヒントのテキスト バッファーを作成する ITextBufferFactoryService や、QuickInfo セッションをトリガーする IQuickInfoBroker などのサービスやブローカーをインポートしたりする役割を担います。

    この例では QuickInfo ソースで、ハードコーディングされた、メソッド名と説明のリストを使用していますが、完全な実装では、言語サービスと言語ドキュメントが、そのコンテンツを提供する役割を担います。

MEF プロジェクトを作成する

MEF プロジェクトを作成するには

  1. C# VSIX プロジェクトを作成します。 ([新しいプロジェクト] ダイアログで、[Visual C#]、[拡張機能][VSIX プロジェクト] の順に選択します。) ソリューションに QuickInfoTest という名前を付けます。

  2. プロジェクトに、[エディター分類子] 項目テンプレートを追加します。 詳細については、「エディター項目テンプレートを使用して拡張機能を作成する」を参照してください。

  3. 既存のクラス ファイルを削除します。

QuickInfo ソースを実装する

QuickInfo ソースでは、識別子とその説明のセットを収集し、いずれかの識別子が検出されたときに、ツールヒントのテキスト バッファーにコンテンツを追加します。 この例では、識別子とその説明が、ソース コンストラクターに追加されたところです。

QuickInfo ソースを実装するには

  1. クラス ファイルを追加し、その名前を TestQuickInfoSourceにします。

  2. Microsoft.VisualStudio.Language.IntelliSense への参照を追加します。

  3. 次のインポートを追加します。

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Operations;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    
  4. IQuickInfoSource を実装するクラスを宣言し、その名前を TestQuickInfoSource にします。

    internal class TestQuickInfoSource : IQuickInfoSource
    
  5. QuickInfo ソース プロバイダー、テキスト バッファー、および一連のメソッド名とメソッド シグネチャのためのフィールドを追加します。 この例では、メソッド名とシグネチャが TestQuickInfoSource コンストラクター内で初期化されています。

    private TestQuickInfoSourceProvider m_provider;
    private ITextBuffer m_subjectBuffer;
    private Dictionary<string, string> m_dictionary;
    
  6. QuickInfo ソース プロバイダーとテキスト バッファーを設定するコンストラクターを追加し、メソッド名、メソッドのシグネチャ、メソッドの説明のセットを指定します。

    public TestQuickInfoSource(TestQuickInfoSourceProvider provider, ITextBuffer subjectBuffer)
    {
        m_provider = provider;
        m_subjectBuffer = subjectBuffer;
    
        //these are the method names and their descriptions
        m_dictionary = new Dictionary<string, string>();
        m_dictionary.Add("add", "int add(int firstInt, int secondInt)\nAdds one integer to another.");
        m_dictionary.Add("subtract", "int subtract(int firstInt, int secondInt)\nSubtracts one integer from another.");
        m_dictionary.Add("multiply", "int multiply(int firstInt, int secondInt)\nMultiplies one integer by another.");
        m_dictionary.Add("divide", "int divide(int firstInt, int secondInt)\nDivides one integer by another.");
    }
    
  7. AugmentQuickInfoSession メソッドを実装します。 この例のメソッドでは、現在の単語を検索するか、カーソルが行またはテキスト バッファーの末尾にある場合は、前の単語を検索します。 単語がメソッド名の 1 つである場合、そのメソッド名の説明が QuickInfo コンテンツに追加されます。

    public void AugmentQuickInfoSession(IQuickInfoSession session, IList<object> qiContent, out ITrackingSpan applicableToSpan)
    {
        // Map the trigger point down to our buffer.
        SnapshotPoint? subjectTriggerPoint = session.GetTriggerPoint(m_subjectBuffer.CurrentSnapshot);
        if (!subjectTriggerPoint.HasValue)
        {
            applicableToSpan = null;
            return;
        }
    
        ITextSnapshot currentSnapshot = subjectTriggerPoint.Value.Snapshot;
        SnapshotSpan querySpan = new SnapshotSpan(subjectTriggerPoint.Value, 0);
    
        //look for occurrences of our QuickInfo words in the span
        ITextStructureNavigator navigator = m_provider.NavigatorService.GetTextStructureNavigator(m_subjectBuffer);
        TextExtent extent = navigator.GetExtentOfWord(subjectTriggerPoint.Value);
        string searchText = extent.Span.GetText();
    
        foreach (string key in m_dictionary.Keys)
        {
            int foundIndex = searchText.IndexOf(key, StringComparison.CurrentCultureIgnoreCase);
            if (foundIndex > -1)
            {
                applicableToSpan = currentSnapshot.CreateTrackingSpan
                    (
                    //querySpan.Start.Add(foundIndex).Position, 9, SpanTrackingMode.EdgeInclusive
                                            extent.Span.Start + foundIndex, key.Length, SpanTrackingMode.EdgeInclusive
                    );
    
                string value;
                m_dictionary.TryGetValue(key, out value);
                if (value != null)
                    qiContent.Add(value);
                else
                    qiContent.Add("");
    
                return;
            }
        }
    
        applicableToSpan = null;
    }
    
  8. IQuickInfoSource では IDisposable を実装しているため、Dispose() メソッドも実装する必要があります。

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

QuickInfo ソース プロバイダーを実装する

QuickInfo ソースのプロバイダーは、主に、MEF コンポーネント パーツとして自身をエクスポートして、QuickInfo ソースをインスタンス化します。 これは MEF コンポーネント パーツであるため、その他の MEF コンポーネント パーツをインポートできます。

QuickInfo ソース プロバイダーを実装するには

  1. IQuickInfoSourceProvider を実装している TestQuickInfoSourceProvider という名前の QuickInfo ソース プロバイダーを宣言し、NameAttribute として "ToolTip QuickInfo Source"、OrderAttribute として Before = "default"、ContentTypeAttribute として "text" を指定してそれをエクスポートします。

    [Export(typeof(IQuickInfoSourceProvider))]
    [Name("ToolTip QuickInfo Source")]
    [Order(Before = "Default Quick Info Presenter")]
    [ContentType("text")]
    internal class TestQuickInfoSourceProvider : IQuickInfoSourceProvider
    
  2. ITextStructureNavigatorSelectorServiceITextBufferFactoryService の 2 つのエディター サービスを、TestQuickInfoSourceProvider のプロパティとしてインポートします。

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
    [Import]
    internal ITextBufferFactoryService TextBufferFactoryService { get; set; }
    
  3. 新しい TestQuickInfoSource を返す TryCreateQuickInfoSource を実装します。

    public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
    {
        return new TestQuickInfoSource(this, textBuffer);
    }
    

QuickInfo コントローラーを実装する

QuickInfo コントローラーでは、QuickInfo がいつ表示されるかを指定します。 この例では、いずれかのメソッド名に対応する単語の上にポインターが置かれたときに QuickInfo が表示されます。 QuickInfo コントローラーには、QuickInfo セッションをトリガーするマウス ホバー イベントのハンドラーを実装します。

QuickInfo コントローラーを実装するには

  1. IIntellisenseController を実装するクラスを宣言し、その名前を TestQuickInfoController にします。

    internal class TestQuickInfoController : IIntellisenseController
    
  2. テキスト ビューのプライベート フィールド、そのテキスト ビューで表示されるテキスト バッファー、QuickInfo セッション、QuickInfo コントローラー プロバイダーを追加します。

    private ITextView m_textView;
    private IList<ITextBuffer> m_subjectBuffers;
    private TestQuickInfoControllerProvider m_provider;
    private IQuickInfoSession m_session;
    
  3. フィールドを設定するコンストラクターを追加し、マウス ホバー イベントのハンドラーを追加ます。

    internal TestQuickInfoController(ITextView textView, IList<ITextBuffer> subjectBuffers, TestQuickInfoControllerProvider provider)
    {
        m_textView = textView;
        m_subjectBuffers = subjectBuffers;
        m_provider = provider;
    
        m_textView.MouseHover += this.OnTextViewMouseHover;
    }
    
  4. QuickInfo セッションをトリガーするマウス ホバー イベントのハンドラーを追加します。

    private void OnTextViewMouseHover(object sender, MouseHoverEventArgs e)
    {
        //find the mouse position by mapping down to the subject buffer
        SnapshotPoint? point = m_textView.BufferGraph.MapDownToFirstMatch
             (new SnapshotPoint(m_textView.TextSnapshot, e.Position),
            PointTrackingMode.Positive,
            snapshot => m_subjectBuffers.Contains(snapshot.TextBuffer),
            PositionAffinity.Predecessor);
    
        if (point != null)
        {
            ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position,
            PointTrackingMode.Positive);
    
            if (!m_provider.QuickInfoBroker.IsQuickInfoActive(m_textView))
            {
                m_session = m_provider.QuickInfoBroker.TriggerQuickInfo(m_textView, triggerPoint, true);
            }
        }
    }
    
  5. コントローラーがテキスト ビューからデタッチされたときにマウス ホバー イベントのハンドラーが削除されるように Detach メソッドを実装します。

    public void Detach(ITextView textView)
    {
        if (m_textView == textView)
        {
            m_textView.MouseHover -= this.OnTextViewMouseHover;
            m_textView = null;
        }
    }
    
  6. この例では、ConnectSubjectBuffer メソッドと DisconnectSubjectBuffer メソッドを空のメソッドとして実装します。

    public void ConnectSubjectBuffer(ITextBuffer subjectBuffer)
    {
    }
    
    public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer)
    {
    }
    

QuickInfo コントローラー プロバイダーの実装

QuickInfo コントローラーのプロバイダーは、主に、MEF コンポーネント パーツとして自身をエクスポートして、QuickInfo コントローラーをインスタンス化します。 これは MEF コンポーネント パーツであるため、その他の MEF コンポーネント パーツをインポートできます。

QuickInfo コントローラー プロバイダーを実装するには

  1. IIntellisenseControllerProvider を実装する TestQuickInfoControllerProvider という名前のクラスを宣言し、NameAttribute として "ToolTip QuickInfo Controller" を、ContentTypeAttribute として "text" を指定して、それをエクスポートします。

    [Export(typeof(IIntellisenseControllerProvider))]
    [Name("ToolTip QuickInfo Controller")]
    [ContentType("text")]
    internal class TestQuickInfoControllerProvider : IIntellisenseControllerProvider
    
  2. IQuickInfoBroker をプロパティとしてインポートします。

    [Import]
    internal IQuickInfoBroker QuickInfoBroker { get; set; }
    
  3. QuickInfo コントローラーをインスタンス化することで TryCreateIntellisenseController メソッドを実装します。

    public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
    {
        return new TestQuickInfoController(textView, subjectBuffers, this);
    }
    

コードをビルドしてテストする

このコードをテストするには、QuickInfoTest ソリューションをビルドし、それを実験用インスタンスで実行します。

QuickInfoTest ソリューションをビルドしてテストするには

  1. ソリューションをビルドします。

  2. デバッガーでこのプロジェクトを実行すると、Visual Studio の 2 つ目のインスタンスが起動されます。

  3. テキスト ファイルを作成し、"add" と "subtract" という単語を含む何らかのテキストを入力します。

  4. いずれかの "add" の位置にポインターを移動します。 add メソッドのシグネチャと説明が表示されるはずです。