エディターでテキストを操作する
拡張機能コードは、さまざまなエントリ ポイント (ユーザーが Visual Studio と対話するときに発生する状況) に応答して実行するように構成できます。 現在、エディターの拡張機能では、リスナー、EditorExtensibility サービス オブジェクト、コマンドの 3 つのエントリ ポイントがサポートされています。
イベント リスナーは、エディター ウィンドウで特定のアクションが発生したときにトリガーされます。これは、TextView
によってコードで表されます。 たとえば、ユーザーがエディターに何かを入力すると、TextViewChanged
イベントが発生します。 エディター ウィンドウを開いたり閉じたりすると、TextViewOpened
イベントと TextViewClosed
イベントが発生します。
エディター サービス オブジェクトは、EditorExtensibility
クラスのインスタンスであり、テキスト編集の実行など、リアルタイムのエディター機能を公開します。
コマンド は、メニュー、コンテキスト メニュー、またはツール バーに配置できる項目をクリックすることで、ユーザーによって開始されます。
テキスト ビュー リスナーを追加する
リスナーには、ITextViewChangedListener と ITextViewOpenClosedListener の 2 種類があります。 これらのリスナーを組み合わせて使用することで、テキストエディターの開閉や変更を観察することができます。
次に、ExtensionPart 基本クラスと ITextViewChangedListener
、ITextViewOpenClosedListener
、またはその両方を実装する新しいクラスを作成し、VisualStudioContribution 属性を追加します。
次に、ITextViewChangedListener および ITextViewOpenClosedListenerが必要とする TextViewExtensionConfiguration プロパティを実装し、C# ファイルを編集する際にリスナーが適用されるようにします。
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") },
};
他のプログラミング言語とファイルの種類に使用できるドキュメントの種類については、この記事 の後半記載されています。また、必要に応じてカスタム ファイルの種類を定義することもできます。
両方のリスナーを実装することにした場合、完成したクラス宣言は次のようになります。
[VisualStudioContribution]
public sealed class TextViewOperationListener :
ExtensionPart, // This is the extension part base class containing infrastructure necessary to use VS services.
ITextViewOpenClosedListener, // Indicates this part listens for text view lifetime events.
ITextViewChangedListener // Indicates this part listens to text view changes.
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
// Indicates this part should only light up in C# files.
AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") },
};
...
ITextViewOpenClosedListener と ITextViewChangedListener はどちらも TextViewExtensionConfiguration プロパティを宣言しているため、その構成は両方のリスナーに適用されます。
拡張機能を実行すると、次の内容が表示されます。
- ITextViewOpenClosedListener.TextViewOpenedAsync は、ユーザーがテキスト ビューを開いた際にいつでも呼び出されます。
- ITextViewOpenClosedListener.TextViewClosedAsync は、ユーザーがテキストビューを閉じたときにいつでも呼び出されます。
- ITextViewChangedListener.TextViewChangedAsync、ユーザーがテキスト ビューで表示されるテキスト ドキュメントにテキストを変更するたびに呼び出されます。
これらの各メソッドには、ユーザーがアクションを呼び出したときのテキスト ビューとテキスト ドキュメントの状態を含む ITextViewSnapshot と、IDE が保留中のアクションを取り消すときに IsCancellationRequested == true
する CancellationToken が渡されます。
拡張機能が関連するタイミングを定義する
通常、拡張機能はサポートされている特定のドキュメントの種類とシナリオにのみ関連するため、その適用性を明確に定義することが重要です。 AppliesTo 構成()を使用することで、拡張機能の適用範囲を明確に定義することができます。 拡張子でサポートされているコード言語などのファイルの種類を指定するか、ファイル名またはパスに基づいてパターンに一致することで、拡張機能の適用性をさらに調整できます。
AppliesTo 構成でプログラミング言語を指定する
AppliesTo 構成は、拡張機能をアクティブ化するプログラミング言語のシナリオを示します。 AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") }
として記述されます。ドキュメントの種類は、Visual Studio に組み込まれている言語の既知の名前か、Visual Studio 拡張機能で定義されたカスタムです。
一部の既知のドキュメントの種類を次の表に示します。
文書タイプ | 説明 |
---|---|
"CSharp" | C# |
"C/C++" | C、C++、ヘッダー、IDL |
"TypeScript" | TypeScript と JavaScript の型言語。 |
"HTML" | HTML |
"JSON" | JSON |
"text" | "text" から派生する "code" の階層的子孫を含むテキスト ファイル。 |
"code" | C、C++、C# など。 |
DocumentTypes は階層型です。 つまり、C# と C++ はどちらも "コード" から派生するため、"コード" を宣言すると、すべてのコード言語、C#、C、C++ などの拡張機能がアクティブになります。
新しいドキュメントの種類を定義する
カスタム コード言語をサポートするなど、新しいドキュメントの種類を定義するには、拡張プロジェクト内の任意のクラスに静的 DocumentTypeConfiguration プロパティを追加し、プロパティに VisualStudioContribution
属性を付けます。
DocumentTypeConfiguration
では、新しいドキュメントの種類を定義し、1 つ以上の他のドキュメントの種類を継承することを指定し、ファイルの種類を識別するために使用される 1 つ以上のファイル拡張子を指定できます。
using Microsoft.VisualStudio.Extensibility.Editor;
internal static class MyDocumentTypes
{
[VisualStudioContribution]
internal static DocumentTypeConfiguration MarkdownDocumentType => new("markdown")
{
FileExtensions = new[] { ".md", ".mdk", ".markdown" },
BaseDocumentType = DocumentType.KnownValues.Text,
};
}
ドキュメントの種類の定義は、従来の Visual Studio 拡張機能によって提供されるコンテンツ タイプ定義とマージされます。これにより、追加のファイル拡張子を既存のドキュメントの種類にマップできます。
ドキュメント セレクター
DocumentFilter.FromDocumentType に加えて、DocumentFilter.FromGlobPattern を すると、ドキュメントのファイル パスが glob (ワイルドカード) パターンと一致する場合にのみ拡張機能をアクティブにすることで、拡張機能の適用性をさらに制限できます。
[VisualStudioContribution]
public sealed class TextViewOperationListener
: ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[]
{
DocumentFilter.FromDocumentType("CSharp"),
DocumentFilter.FromGlobPattern("**/tests/*.cs"),
},
};
[VisualStudioContribution]
public sealed class TextViewOperationListener
: ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[]
{
DocumentFilter.FromDocumentType(MyDocumentTypes.MarkdownDocumentType),
DocumentFilter.FromGlobPattern("docs/*.md", relativePath: true),
},
};
pattern
パラメーターは、ドキュメントの絶対パスに一致する glob パターンを表します。
Glob パターンには、次の構文を使用できます。
*
はパスセグメント内の0個以上の文字と一致させるために使用されます。?
: パス セグメント内の 1 文字と一致します**
: 任意の数 (0 を含む) のパス セグメントと一致します- 条件をグループ化する
{}
(たとえば、**/*.{ts,js}
はすべての TypeScript ファイルと JavaScript ファイルに一致します) - パス セグメントで一致する文字の範囲を宣言する
[]
(たとえば、example.0
、example.1
、... で一致するexample.[0-9]
など)。 [!...]
: パス セグメントで一致する文字の範囲を否定します (たとえば、example.[!0-9]
はexample.a
やexample.b
とは一致しますが、example.0
とは一致しません)
バックスラッシュ (\
) は glob パターン内では無効です。 glob パターンを作成するときは、バックスラッシュをスラッシュに変換します。
アクセス エディターの機能
エディター拡張機能クラスは、ExtensionPartから継承します。 ExtensionPart
クラスは、Extensibility プロパティを公開します。 このプロパティを使用すると、EditorExtensibility オブジェクトのインスタンスを要求できます。 このオブジェクトを使用して、編集などのリアルタイム エディター機能にアクセスできます。
EditorExtensibility editorService = this.Extensibility.Editor();
コマンド内のエディターの状態にアクセスする
各 Command
の ExecuteCommandAsync()
には、コマンドが呼び出されたときの IDE の状態のスナップショットを含む IClientContext
が渡されます。 アクティブ ドキュメントには、ITextViewSnapshot
インターフェイスを介してアクセスできます。EditorExtensibility
オブジェクトから取得するには、次の非同期メソッド GetActiveTextViewAsync
呼び出します。
using ITextViewSnapshot textView = await this.Extensibility.Editor().GetActiveTextViewAsync(clientContext, cancellationToken);
ITextViewSnapshot
したら、エディターの状態にアクセスできます。 ITextViewSnapshot
は、ある時点でのエディターの状態を変更できないビューであるため、編集を行うには、Editor オブジェクト モデルの他のインターフェイス 使用する必要があります。