電球は Visual Studio エディターのアイコンで、組み込みのコード アナライザーやコード リファクタリングによって特定された問題の修正など、一連のアクションを表示するために展開されます。
Visual C# エディターと Visual Basic エディターでは、.NET コンパイラ プラットフォーム ("Roslyn") を使用して、電球を自動的に表示するアクションを含む独自のコード アナライザーを記述してパッケージ化することもできます。 詳細については、以下を参照してください。
方法: C# 診断とコード修正 を記述する
方法: Visual Basic 診断とコード修正 を記述する
C++ などの他の言語では、その関数のスタブ実装を作成するための提案など、一部のクイック アクション用の電球も用意されています。
電球の外観を次に示します。 Visual Basic または Visual C# プロジェクトでは、変数名が無効な場合、その名前の下に赤い波線が表示されます。 無効な識別子の上にマウスを置くと、カーソルの近くに電球が表示されます。
電球
電球の下向き矢印をクリックすると、一連の推奨アクションと、選択したアクションのプレビューが表示されます。 この場合、アクションを実行した場合にコードに加えられた変更が表示されます。
電球を使用して、独自の推奨アクションを提供できます。 たとえば、左中かっこを新しい行に移動したり、前の行の末尾に移動したりするアクションを提供できます。 次の手順では、現在の単語に表示される電球を作成し、2 つの推奨アクションを示す方法を説明します。大文字に変換 と 小文字に変換。
Managed Extensibility Framework (MEF) プロジェクトを作成する
C# VSIX プロジェクトを作成します。 ([新しいプロジェクト] ダイアログで、[Visual C#]、[拡張機能]、[VSIX プロジェクト] の順に選択します。) ソリューションに
LightBulbTest
という名前を付けます。エディター分類子 項目テンプレートをプロジェクトに追加します。 詳細については、「エディター項目テンプレートを使用して拡張機能を作成する」を参照してください。
既存のクラス ファイルを削除します。
プロジェクトに次の参照を追加し、ローカルコピー を
False
に設定します。Microsoft.VisualStudio.Language.Intellisense
新しいクラス ファイルを追加し、LightBulbTest
名前を付けます。 ディレクティブを使用して以下を追加します。
using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; using System.Threading;
電球ソース プロバイダーを実装する
LightBulbTest.cs クラス ファイルで、LightBulbTest クラスを削除します。 TestSuggestedActionsSourceProvider という名前のクラスを ISuggestedActionsSourceProviderを実装する形で追加します。 Test Suggested Actions という名前を付けて、ContentTypeAttribute に "text" を設定して、それをエクスポートします。
[Export(typeof(ISuggestedActionsSourceProvider))] [Name("Test Suggested Actions")] [ContentType("text")] internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
ソース プロバイダー クラス内で、ITextStructureNavigatorSelectorService をインポートし、プロパティとして追加します。
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
CreateSuggestedActionsSource メソッドを実装して、ISuggestedActionsSource オブジェクトを返します。 ソースについては、次のセクションで説明します。
public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { if (textBuffer == null || textView == null) { return null; } return new TestSuggestedActionsSource(this, textView, textBuffer); }
ISuggestedActionSource を実装する
推奨されるアクション ソースは、提案されたアクションのセットを収集し、適切なコンテキストで追加する役割を担います。 この場合、コンテキストは現在の単語であり、推奨されるアクションは UpperCaseSuggestedAction および LowerCaseSuggestedActionです。これについては、次のセクションで説明します。
を実装する TestSuggestedActionsSource クラスを追加します。 internal class TestSuggestedActionsSource : ISuggestedActionsSource
推奨されるアクション ソース プロバイダー、テキスト バッファー、およびテキスト ビューのプライベートな読み取り専用フィールドを追加します。
private readonly TestSuggestedActionsSourceProvider m_factory; private readonly ITextBuffer m_textBuffer; private readonly ITextView m_textView;
プライベート フィールドを設定するコンストラクターを追加します。
public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer) { m_factory = testSuggestedActionsSourceProvider; m_textBuffer = textBuffer; m_textView = textView; }
現在カーソルの下にある単語を返すプライベート メソッドを追加します。 次のメソッドは、カーソルの現在の位置を調び、テキスト構造ナビゲーターに単語の範囲を確認します。 カーソルが単語の上にある場合、TextExtent は out パラメーターで返されます。それ以外の場合、
out
パラメーターはnull
され、メソッドはfalse
を返します。private bool TryGetWordUnderCaret(out TextExtent wordExtent) { ITextCaret caret = m_textView.Caret; SnapshotPoint point; if (caret.Position.BufferPosition > 0) { point = caret.Position.BufferPosition - 1; } else { wordExtent = default(TextExtent); return false; } ITextStructureNavigator navigator = m_factory.NavigatorService.GetTextStructureNavigator(m_textBuffer); wordExtent = navigator.GetExtentOfWord(point); return true; }
HasSuggestedActionsAsync メソッドを実装します。 エディターはこのメソッドを呼び出して、電球を表示するかどうかを調べます。 この呼び出しは、たとえば、カーソルが 1 行から別の行に移動したときや、エラー波線の上にマウス ポインターを置いたときによく行われます。 このメソッドが動作している間、他の UI 操作を引き継ぐために非同期です。 ほとんどの場合、このメソッドは現在の行の解析と分析を実行する必要があるため、処理に時間がかかる場合があります。
この実装では、TextExtent を非同期的に取得し、エクステントが空白以外のテキストを持っているかどうかなど、エクステントが重要かどうかを判断します。
public Task<bool> HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { TextExtent extent; if (TryGetWordUnderCaret(out extent)) { // don't display the action if the extent has whitespace return extent.IsSignificant; } return false; }); }
異なる ISuggestedAction オブジェクトを含む SuggestedActionSet オブジェクトの配列を返す GetSuggestedActions メソッドを実装します。 このメソッドは、電球が展開されるときに呼び出されます。
警告
HasSuggestedActionsAsync()
とGetSuggestedActions()
の実装が一貫していることを確認する必要があります。つまり、HasSuggestedActionsAsync()
がtrue
を返す場合は、GetSuggestedActions()
に表示するアクションが必要です。 多くの場合、HasSuggestedActionsAsync()
はGetSuggestedActions()
の直前に呼び出されますが、必ずしもそうであるとは限りません。 たとえば、ユーザーが (Ctrl +) キーを押して電球アクションを呼び出した場合、 のみが呼び出されます。 public IEnumerable<SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { TextExtent extent; if (TryGetWordUnderCaret(out extent) && extent.IsSignificant) { ITrackingSpan trackingSpan = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); var upperAction = new UpperCaseSuggestedAction(trackingSpan); var lowerAction = new LowerCaseSuggestedAction(trackingSpan); return new SuggestedActionSet[] { new SuggestedActionSet(new ISuggestedAction[] { upperAction, lowerAction }) }; } return Enumerable.Empty<SuggestedActionSet>(); }
SuggestedActionsChanged
イベントを定義します。public event EventHandler<EventArgs> SuggestedActionsChanged;
実装を完了するには、
Dispose()
メソッドとTryGetTelemetryId()
メソッドの実装を追加します。 テレメトリを実行したくないので、false
返して GUID をEmpty
に設定するだけです。public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample provider and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
電球アクションを実装する
プロジェクトで、Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll への参照を追加し、を"ローカルにコピー"、 を
False
に設定します。2 つのクラスを作成します。1 つ目の名前は
UpperCaseSuggestedAction
で、2 つ目はLowerCaseSuggestedAction
という名前です。 どちらのクラスも ISuggestedActionを実装します。internal class UpperCaseSuggestedAction : ISuggestedAction internal class LowerCaseSuggestedAction : ISuggestedAction
どちらのクラスも同じですが、一方は ToUpper を呼び出し、もう一方は ToLowerを呼び出します。 次の手順では大文字のアクション クラスのみを対象としていますが、両方のクラスを実装する必要があります。 小文字操作を実装するためのパターンとして、大文字操作を実装するための手順を使用します。
これらのクラスに次の using ディレクティブを追加します。
using Microsoft.VisualStudio.Imaging.Interop; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
プライベート フィールドのセットを宣言します。
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
フィールドを設定するコンストラクターを追加します。
public UpperCaseSuggestedAction(ITrackingSpan span) { m_span = span; m_snapshot = span.TextBuffer.CurrentSnapshot; m_upper = span.GetText(m_snapshot).ToUpper(); m_display = string.Format("Convert '{0}' to upper case", span.GetText(m_snapshot)); }
GetPreviewAsync メソッドを実装して、アクションのプレビューを表示します。
public Task<object> GetPreviewAsync(CancellationToken cancellationToken) { var textBlock = new TextBlock(); textBlock.Padding = new Thickness(5); textBlock.Inlines.Add(new Run() { Text = m_upper }); return Task.FromResult<object>(textBlock); }
空の SuggestedActionSet 列挙型を返すように、GetActionSetsAsync メソッドを実装します。
public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<SuggestedActionSet>>(null); }
次のようにプロパティを実装します。
public bool HasActionSets { get { return false; } } public string DisplayText { get { return m_display; } } public ImageMoniker IconMoniker { get { return default(ImageMoniker); } } public string IconAutomationText { get { return null; } } public string InputGestureText { get { return null; } } public bool HasPreview { get { return true; } }
スパン内のテキストを大文字に置き換えて、Invoke メソッドを実装します。
public void Invoke(CancellationToken cancellationToken) { m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper); }
警告
電球アイコンのアクションである Invoke メソッドでは、UI を表示することが想定されていません。 アクションで新しい UI (プレビューや選択ダイアログなど) が表示される場合は、Invoke メソッド内から直接 UI を表示するのではなく、Invokeから戻った後に UI を表示するようにスケジュールします。
実装を完了するには、
Dispose()
メソッドとTryGetTelemetryId()
メソッドを追加します。public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample action and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
LowerCaseSuggestedAction
に対しても同様の作業を行い、表示テキストを "Convert '{0}' to lower case" に変更し、呼び出しを ToUpper から ToLowerに変えることを忘れないでください。
コードをビルドしてテストする
このコードをテストするには、LightBulbTest ソリューションをビルドし、実験用インスタンスで実行します。
ソリューションをビルドします。
デバッガーでこのプロジェクトを実行すると、Visual Studio の 2 番目のインスタンスが開始されます。
テキスト ファイルを作成し、テキストを入力します。 テキストの左側に電球が表示されます。
電球をポイントします。 下矢印が表示されます。
電球をクリックすると、選択したアクションのプレビューと共に、2 つの推奨アクションが表示されます。
テスト電球
、TestLIghtBulbExpanded 展開 最初のアクションをクリックすると、現在の単語のすべてのテキストが大文字に変換されます。 2 番目のアクションをクリックすると、すべてのテキストが小文字に変換されます。