QuickInfo は、ユーザーがメソッド名の上にポインターを移動したときにメソッドのシグネチャと説明を表示する、IntelliSense 機能です。 QuickInfo のような言語ベースの機能は、QuickInfo の説明の提供対象となる識別子を定義した後で、コンテンツを表示するためのツールヒントを作成することによって実装できます。 言語サービスのコンテキストで QuickInfo を定義することも、独自のファイル名拡張子とコンテンツ タイプを定義し、そのタイプだけのために QuickInfo を表示することもできます。または、既存のコンテンツ タイプ ("text" など) のために QuickInfo を表示することもできます。 このチュートリアルでは、"text" コンテンツ タイプのために QuickInfo を表示する方法を示します。
このチュートリアルの QuickInfo の例では、ユーザーがポインターをメソッド名の上に移動したときにツールヒントを表示します。 この設計では、以下の 4 つのインターフェイスを実装する必要があります。
ソース インターフェイス
ソース プロバイダー インターフェイス
コントローラー インターフェイス
コントローラー プロバイダー インターフェイス
ソースとコントローラーのプロバイダーは、Managed Extensibility Framework (MEF) コンポーネント パーツであり、ソースとコントローラーのクラスをエクスポートしたり、ツールヒントのテキスト バッファーを作成する ITextBufferFactoryService や、QuickInfo セッションをトリガーする IQuickInfoBroker などのサービスやブローカーをインポートしたりする役割を担います。
この例では QuickInfo ソースで、ハードコーディングされた、メソッド名と説明のリストを使用していますが、完全な実装では、言語サービスと言語ドキュメントが、そのコンテンツを提供する役割を担います。
MEF プロジェクトを作成する
MEF プロジェクトを作成するには
C# VSIX プロジェクトを作成します。 ([新しいプロジェクト] ダイアログで、[Visual C#]、[拡張機能]、[VSIX プロジェクト] の順に選択します。) ソリューションに QuickInfoTest
という名前を付けます。
プロジェクトに、[エディター分類子] 項目テンプレートを追加します。 詳細については、「エディター項目テンプレートを使用して拡張機能を作成する」を参照してください。
既存のクラス ファイルを削除します。
QuickInfo ソースを実装する
QuickInfo ソースでは、識別子とその説明のセットを収集し、いずれかの識別子が検出されたときに、ツールヒントのテキスト バッファーにコンテンツを追加します。 この例では、識別子とその説明が、ソース コンストラクターに追加されたところです。
QuickInfo ソースを実装するには
クラス ファイルを追加し、その名前を TestQuickInfoSource
にします。
Microsoft.VisualStudio.Language.IntelliSense への参照を追加します。
次のインポートを追加します。
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;
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Collections.ObjectModel
Imports System.ComponentModel.Composition
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Operations
Imports Microsoft.VisualStudio.Text.Tagging
Imports Microsoft.VisualStudio.Utilities
IQuickInfoSource を実装するクラスを宣言し、その名前を TestQuickInfoSource
にします。
internal class TestQuickInfoSource : IQuickInfoSource
Friend Class TestQuickInfoSource
Implements IQuickInfoSource
QuickInfo ソース プロバイダー、テキスト バッファー、および一連のメソッド名とメソッド シグネチャのためのフィールドを追加します。 この例では、メソッド名とシグネチャが TestQuickInfoSource
コンストラクター内で初期化されています。
private TestQuickInfoSourceProvider m_provider;
private ITextBuffer m_subjectBuffer;
private Dictionary<string, string> m_dictionary;
Private m_provider As TestQuickInfoSourceProvider
Private m_subjectBuffer As ITextBuffer
Private m_dictionary As Dictionary(Of String, String)
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.");
}
Public Sub New(ByVal provider As TestQuickInfoSourceProvider, ByVal subjectBuffer As ITextBuffer)
m_provider = provider
m_subjectBuffer = subjectBuffer
'these are the method names and their descriptions
m_dictionary = New Dictionary(Of String, String)()
m_dictionary.Add("add", "int add(int firstInt, int secondInt)" & vbLf & "Adds one integer to another.")
m_dictionary.Add("subtract", "int subtract(int firstInt, int secondInt)" & vbLf & "Subtracts one integer from another.")
m_dictionary.Add("multiply", "int multiply(int firstInt, int secondInt)" & vbLf & "Multiplies one integer by another.")
m_dictionary.Add("divide", "int divide(int firstInt, int secondInt)" & vbLf & "Divides one integer by another.")
End Sub
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;
}
Public Sub AugmentQuickInfoSession(ByVal session As IQuickInfoSession, ByVal qiContent As IList(Of Object), ByRef applicableToSpan As ITrackingSpan) Implements IQuickInfoSource.AugmentQuickInfoSession
' Map the trigger point down to our buffer.
Dim subjectTriggerPoint As System.Nullable(Of SnapshotPoint) = session.GetTriggerPoint(m_subjectBuffer.CurrentSnapshot)
If Not subjectTriggerPoint.HasValue Then
applicableToSpan = Nothing
Exit Sub
End If
Dim currentSnapshot As ITextSnapshot = subjectTriggerPoint.Value.Snapshot
Dim querySpan As New SnapshotSpan(subjectTriggerPoint.Value, 0)
'look for occurrences of our QuickInfo words in the span
Dim navigator As ITextStructureNavigator = m_provider.NavigatorService.GetTextStructureNavigator(m_subjectBuffer)
Dim extent As TextExtent = navigator.GetExtentOfWord(subjectTriggerPoint.Value)
Dim searchText As String = extent.Span.GetText()
For Each key As String In m_dictionary.Keys
Dim foundIndex As Integer = searchText.IndexOf(key, StringComparison.CurrentCultureIgnoreCase)
If foundIndex > -1 Then
'applicableToSpan = currentSnapshot.CreateTrackingSpan(querySpan.Start.Add(foundIndex).Position, 9, SpanTrackingMode.EdgeInclusive)
applicableToSpan = currentSnapshot.CreateTrackingSpan(extent.Span.Start + foundIndex, key.Length, SpanTrackingMode.EdgeInclusive)
Dim value As String = ""
m_dictionary.TryGetValue(key, value)
If value IsNot Nothing Then
qiContent.Add(value)
Else
qiContent.Add("")
End If
Exit Sub
End If
Next
applicableToSpan = Nothing
End Sub
IQuickInfoSource では IDisposable を実装しているため、Dispose() メソッドも実装する必要があります。
private bool m_isDisposed;
public void Dispose()
{
if (!m_isDisposed)
{
GC.SuppressFinalize(this);
m_isDisposed = true;
}
}
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
QuickInfo ソース プロバイダーを実装する
QuickInfo ソースのプロバイダーは、主に、MEF コンポーネント パーツとして自身をエクスポートして、QuickInfo ソースをインスタンス化します。 これは MEF コンポーネント パーツであるため、その他の MEF コンポーネント パーツをインポートできます。
QuickInfo ソース プロバイダーを実装するには
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
<Export(GetType(IQuickInfoSourceProvider))> _
<Name("ToolTip QuickInfo Source")> _
<Order(Before:=" Default Quick Info Presenter")> _
<ContentType("text")> _
Friend Class TestQuickInfoSourceProvider
Implements IQuickInfoSourceProvider
ITextStructureNavigatorSelectorService と ITextBufferFactoryService の 2 つのエディター サービスを、TestQuickInfoSourceProvider
のプロパティとしてインポートします。
[Import]
internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
[Import]
internal ITextBufferFactoryService TextBufferFactoryService { get; set; }
Private _NavigatorService As ITextStructureNavigatorSelectorService
<Import()> _
Friend Property NavigatorService() As ITextStructureNavigatorSelectorService
Get
Return _NavigatorService
End Get
Set(ByVal value As ITextStructureNavigatorSelectorService)
_NavigatorService = value
End Set
End Property
Private _TextBufferFactoryService As ITextBufferFactoryService
<Import()> _
Friend Property TextBufferFactoryService() As ITextBufferFactoryService
Get
Return _TextBufferFactoryService
End Get
Set(ByVal value As ITextBufferFactoryService)
_TextBufferFactoryService = value
End Set
End Property
新しい TestQuickInfoSource
を返す TryCreateQuickInfoSource を実装します。
public IQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
{
return new TestQuickInfoSource(this, textBuffer);
}
Public Function TryCreateQuickInfoSource(ByVal textBuffer As ITextBuffer) As IQuickInfoSource Implements IQuickInfoSourceProvider.TryCreateQuickInfoSource
Return New TestQuickInfoSource(Me, textBuffer)
End Function
QuickInfo コントローラーを実装する
QuickInfo コントローラーでは、QuickInfo がいつ表示されるかを指定します。 この例では、いずれかのメソッド名に対応する単語の上にポインターが置かれたときに QuickInfo が表示されます。 QuickInfo コントローラーには、QuickInfo セッションをトリガーするマウス ホバー イベントのハンドラーを実装します。
QuickInfo コントローラーを実装するには
IIntellisenseController を実装するクラスを宣言し、その名前を TestQuickInfoController
にします。
internal class TestQuickInfoController : IIntellisenseController
Friend Class TestQuickInfoController
Implements IIntellisenseController
テキスト ビューのプライベート フィールド、そのテキスト ビューで表示されるテキスト バッファー、QuickInfo セッション、QuickInfo コントローラー プロバイダーを追加します。
private ITextView m_textView;
private IList<ITextBuffer> m_subjectBuffers;
private TestQuickInfoControllerProvider m_provider;
private IQuickInfoSession m_session;
Private m_textView As ITextView
Private m_subjectBuffers As IList(Of ITextBuffer)
Private m_provider As TestQuickInfoControllerProvider
Private m_session As IQuickInfoSession
フィールドを設定するコンストラクターを追加し、マウス ホバー イベントのハンドラーを追加ます。
internal TestQuickInfoController(ITextView textView, IList<ITextBuffer> subjectBuffers, TestQuickInfoControllerProvider provider)
{
m_textView = textView;
m_subjectBuffers = subjectBuffers;
m_provider = provider;
m_textView.MouseHover += this.OnTextViewMouseHover;
}
Friend Sub New(ByVal textView As ITextView, ByVal subjectBuffers As IList(Of ITextBuffer), ByVal provider As TestQuickInfoControllerProvider)
m_textView = textView
m_subjectBuffers = subjectBuffers
m_provider = provider
AddHandler m_textView.MouseHover, AddressOf Me.OnTextViewMouseHover
End Sub
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);
}
}
}
Private Sub OnTextViewMouseHover(ByVal sender As Object, ByVal e As MouseHoverEventArgs)
'find the mouse position by mapping down to the subject buffer
Dim point As System.Nullable(Of SnapshotPoint) = m_textView.BufferGraph.MapDownToFirstMatch(New SnapshotPoint(m_textView.TextSnapshot, e.Position), PointTrackingMode.Positive, Function(snapshot) m_subjectBuffers.Contains(snapshot.TextBuffer), PositionAffinity.Predecessor)
If point IsNot Nothing Then
Dim triggerPoint As ITrackingPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position, PointTrackingMode.Positive)
If Not m_provider.QuickInfoBroker.IsQuickInfoActive(m_textView) Then
m_session = m_provider.QuickInfoBroker.TriggerQuickInfo(m_textView, triggerPoint, True)
End If
End If
End Sub
コントローラーがテキスト ビューからデタッチされたときにマウス ホバー イベントのハンドラーが削除されるように Detach メソッドを実装します。
public void Detach(ITextView textView)
{
if (m_textView == textView)
{
m_textView.MouseHover -= this.OnTextViewMouseHover;
m_textView = null;
}
}
Public Sub Detach(ByVal textView As ITextView) Implements IIntellisenseController.Detach
If m_textView Is textView Then
AddHandler m_textView.MouseHover, AddressOf Me.OnTextViewMouseHover
m_textView = Nothing
End If
End Sub
この例では、ConnectSubjectBuffer メソッドと DisconnectSubjectBuffer メソッドを空のメソッドとして実装します。
public void ConnectSubjectBuffer(ITextBuffer subjectBuffer)
{
}
public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer)
{
}
Public Sub ConnectSubjectBuffer(ByVal subjectBuffer As ITextBuffer) Implements IIntellisenseController.ConnectSubjectBuffer
End Sub
Public Sub DisconnectSubjectBuffer(ByVal subjectBuffer As ITextBuffer) Implements IIntellisenseController.DisconnectSubjectBuffer
End Sub
QuickInfo コントローラー プロバイダーの実装
QuickInfo コントローラーのプロバイダーは、主に、MEF コンポーネント パーツとして自身をエクスポートして、QuickInfo コントローラーをインスタンス化します。 これは MEF コンポーネント パーツであるため、その他の MEF コンポーネント パーツをインポートできます。
QuickInfo コントローラー プロバイダーを実装するには
IIntellisenseControllerProvider を実装する TestQuickInfoControllerProvider
という名前のクラスを宣言し、NameAttribute として "ToolTip QuickInfo Controller" を、ContentTypeAttribute として "text" を指定して、それをエクスポートします。
[Export(typeof(IIntellisenseControllerProvider))]
[Name("ToolTip QuickInfo Controller")]
[ContentType("text")]
internal class TestQuickInfoControllerProvider : IIntellisenseControllerProvider
<Export(GetType(IIntellisenseControllerProvider))> _
<Name("ToolTip QuickInfo Controller")> _
<ContentType("text")> _
Friend Class TestQuickInfoControllerProvider
Implements IIntellisenseControllerProvider
IQuickInfoBroker をプロパティとしてインポートします。
[Import]
internal IQuickInfoBroker QuickInfoBroker { get; set; }
Private _QuickInfoBroker As IQuickInfoBroker
<Import()> _
Friend Property QuickInfoBroker() As IQuickInfoBroker
Get
Return _QuickInfoBroker
End Get
Set(ByVal value As IQuickInfoBroker)
_QuickInfoBroker = value
End Set
End Property
QuickInfo コントローラーをインスタンス化することで TryCreateIntellisenseController メソッドを実装します。
public IIntellisenseController TryCreateIntellisenseController(ITextView textView, IList<ITextBuffer> subjectBuffers)
{
return new TestQuickInfoController(textView, subjectBuffers, this);
}
Public Function TryCreateIntellisenseController(ByVal textView As ITextView, ByVal subjectBuffers As IList(Of ITextBuffer)) As IIntellisenseController Implements IIntellisenseControllerProvider.TryCreateIntellisenseController
Return New TestQuickInfoController(textView, subjectBuffers, Me)
End Function
コードをビルドしてテストする
このコードをテストするには、QuickInfoTest ソリューションをビルドし、それを実験用インスタンスで実行します。
QuickInfoTest ソリューションをビルドしてテストするには
ソリューションをビルドします。
デバッガーでこのプロジェクトを実行すると、Visual Studio の 2 つ目のインスタンスが起動されます。
テキスト ファイルを作成し、"add" と "subtract" という単語を含む何らかのテキストを入力します。
いずれかの "add" の位置にポインターを移動します。 add
メソッドのシグネチャと説明が表示されるはずです。
関連するコンテンツ