IntelliJ IDEAプラグイン開発。 パート4

最後に、この部分で、語彙と構文の分析、PSI(プログラム構造インターフェース)、スタブ(スタブ)のパルプに到達しました。 前のパーツ: 1、2、3


IntelliJ IDEAはJava IDEであるだけでなく、あらゆる言語の開発ツールを構築するための強力なプラットフォームです。 IDEAの機能のほとんどは、言語に依存しないプログラミングと言語固有のプログラミングの2つの部分で構成されています。 したがって、言語の機能をサポートするのにそれほど労力は必要ありません。特定の部分を実装するだけでよく、言語に依存しない部分はプラットフォームによって提供されます。 さらに、IDEAは、ツールキットの開発に必要な独自の機能を実装できる強力なフレームワークを提供します。



ファイルタイプの登録



特定の言語プラグインを開発する最初のステップは、それに関連付けられたファイルタイプを登録することです。 通常、IDEAは名前(拡張子)に従ってファイルの種類を決定します。

特定の言語ファイルタイプは、LanguageFileTypeから継承されたクラスで、Languageクラスのインスタンスをその親コン​​ストラクターに渡します。 ファイルタイプを登録するには、拡張ポイントcom.intellij.fileTypeFactoryに登録されているFileTypeFactoryインターフェイスの実装を提供する必要があります。

<extensions defaultExtensionNs="com.intellij"><fileTypeFactory implementation="com.intellij.lang.properties.PropertiesFileTypeFactory"/></extensions>
      
      





プロパティプラグインのLanguageFileTypeクラスの実装例。



登録を確認するには、ユーザー定義のファイルタイプに関連付けられた拡張子を持つファイルの横に表示されるアイコンが、getIcon()メソッドで定義されたアイコンと一致することを確認します。



字句解析器の実装



字句解析プログラム(字句解析プログラム)は、ファイルの内容をトークンのシーケンスに分割する方法を決定します。 レクサーは、構文の強調表示からコード分析機能まで、言語プラグインのほぼすべての機能の基盤として機能します。 レクサーAPIは、Lexerインターフェイスで定義されます。

IDEAは3つの主要なコンテキストでレクサーを呼び出し、プラグインはそれぞれの実装を提供する必要があります。



構文の強調表示に使用されるレクサーは、ファイルの変更された部分のみを処理するために増分的に呼び出すことができます。 他の場合、ファイル全体、または異なるタイプのファイルに組み込まれた完全な言語構成要素を処理するために、レクサーが呼び出されます。



インクリメンタルに呼び出されるレクサーは、現在の状態、つまり ファイル内の各位置に対応するコンテキスト。 構文の強調表示の重要な要件は、状態を通常の番号(Lexer.getState()メソッドから返される)で表すことです。 この状態は、ファイルの途中で字句解析を続行する必要がある場合に、処理のためにフラグメントの開始オフセットとともにLexer.start()メソッドに渡されます。 他のコンテキストのレクサーは、単に0を返す場合があります。



特定のプログラミング言語の字句解析プログラムの作成を簡単にするために、JFlexなどの字句解析ジェネレータを使用できます。 IDEAには、JFlexレクサーをIDEA字句APIに適合させるアダプタークラス(FlexLexerおよびFlexAdapter)が含まれています。 Intellij IDEA Community Editionのソースコードには、JFlex 1.4.1の修正バージョンと、FlexAdapterと互換性のあるレクサーの開発に使用できるレクサープリセットファイルが含まれています。 JFlexの修正バージョンは、新しいコマンドラインオプション--charat



提供します。これは、生成されたコードを修正してIDEA(文字の配列ではなくCharSequenceを必要とする)で動作するようにします。

JFlexを使用したレクサーの開発を容易にするために、構文の強調表示やその他の便利な機能を提供するプラグインがあります。



例:プロパティプラグインのレクサー



JFlexベースのレクサーを含むレクサーは、トークン間のギャップなしでファイル全体を解析する必要があることを覚えておく必要があります。 無効な文字が見つかった場合、そのような場合に予約されているトークンのタイプTokenType.BAD_CHARACTER



割り当てる必要があります。 また、レクサーは分析が終了するまで作業を中断しないでください。



IDEAトークンタイプは、IElementTypeクラスのインスタンスとして定義されます。 ほとんどの言語に共通するいくつかのタイプのトークンは、TokenTypeインターフェースで定義されています。 カスタムプラグインは、レクサー実装で再利用する必要があります。 プラグインは、他のタイプのトークンをIElementTypeクラスの自己作成オブジェクトに関連付ける必要があります。 レクサーが対応するトークンを解析するたびに、IElementTypeの同じインスタンスが返されます。



例:プロパティ言語で使用されるトークンの種類



レクサーレベルで実装できる重要な機能は、ファイル内の言語の混合です(たとえば、テンプレートファイルにJavaコードの埋め込みフラグメント)。 言語がフラグメントの埋め込みをサポートしている場合、フラグメントの種類ごとに異なる「カメレオントークン」として定義する必要がありますが、トークンの種類はILazyParseableElementTypeインターフェイスを実装する必要があります。 フラグメントを解析するために、IDEAはILazyParseableElementType.parseContents()メソッドを呼び出すことにより、対応する言語のパーサーを呼び出します。



解析とPSI



IntelliJ IDEAでの解析は2つのステップで行われます。 1つ目は、プログラムの構造を定義する抽象構文ツリーです。 ASTNodeクラスのインスタンスによって表されるASTノードは、IDEA自体によって作成されます。 各ノードには、プラグインによって定義された(IElementType型のオブジェクトとして)関連付けられた要素タイプがあります。 ファイルを表す最上位のASTノードには、IFileElementTypeインターフェイスを実装する特別な要素タイプが必要です。



ASTノードには、基礎となるドキュメントのテキスト範囲への直接マッピングがあります(リーフノードはレクサーによって返される特定のトークンにマップされ、上位レベルのノードにはいくつかのトークンのフラグメントが含まれます)。

ASTノードで実行された操作(挿入、削除、並べ替えなど)は、基になるドキュメントのテキストへの変更としてすぐに反映されます。



2番目のステップでは、抽象的な構文ツリーに基づいてPSI(プログラム構造インターフェース)が作成され、特定の言語構造を操作するためのセマンティクスとメソッドが追加されます。 PSIノードは、PsiElementインターフェイスを実装するクラスによって表され、ParserDefinition.createElement()メソッドによって作成されます。 PSIツリーのルートノードは、PsiFileインターフェイスを実装し、ParserDefinition.createFile()メソッドで作成する必要があります。



例:プロパティプラグインのParserDefinition



PSI要素(PsiFileに基づくPsiFileBase、PsiElementに基づくASTWrapperPsiElement)を実装するための基本クラスは、IntelliJ IDEA自体によって提供されます。 内部実装に含まれています。 したがって、バージョン10.5以前のプラグインを開発する場合、idea.jarがクラスパスにあることを確認する必要があります。 新しいバージョン(11.0以降)では、クラスパスに自動的に追加されます。



パーサーの実装


IntelliJ IDEAは、既製のプログラミング言語(ANTLRなど)を使用してカスタムプラグインでパーサーを作成する機能を提供しません。 ただし、 Grammar-Kitプラグインを使用して、パーサーとPSIクラスを生成できます。 コードの生成に加えて、構文の編集、構文の強調表示、ナビゲーション、リファクタリングなどの他のオプションも提供します。



言語プラグインのParserDefinitionクラスのcreateParser()メソッドは、PsiParserインターフェイスを実装するパーサーを提供する必要があります。 パーサーは、PsiBuilderクラスのインスタンスを受け取ります。これは、レクサーからトークンのストリームを受け取り、中間AST表現を作成するために使用されます。 パーサーは、トークンが言語構文に一致しない場合でも、シーケンスの最後まで(PsiBuilder.getTokenType()がnullを返すまで)各トークンを処理する必要があります。



操作中に、パーサーはトークン(PsiBuilder.Markerクラスのオブジェクト)とレクサーから受け取ったトークンのペアを設定します。 トークンの各ペアは、抽象構文ツリーの各ノードに関連するトークンの範囲を定義します。 トークンのペアが別のペアにネストされている場合、それは外側のペアの子になります。



マーカーペア(およびそれに基づいて作成されたASTノード)の要素タイプは、エンドマーカーのインストール時に決定されます(PsiBuilder.Marker.done()メソッドが呼び出されました)。 終了マーカーを設定する前に開始マーカーをリセットすることもできます。 drop()メソッドは、1つの開始マーカーのみをリセットし、後で設定される残りには影響しません。 rollbackTo()メソッドは、初期マーカーとその後のすべてのセットをリセットし、レクサーの位置を開始マーカーの先頭に戻します。 これらのメソッドを使用して、解析中に先読みを実装できます。



PsiBuilder.marker.precede()メソッドは、次のトークンを読み取る前に特定の位置に必要なマーカーの数がわからない場合に、右から左への解析に役立ちます。 たとえば、バイナリ式a + b + cは((a + b)+ c)として解析される必要があります。 したがって、トークン「a」の位置には2つの開始マーカーが必要ですが、これはトークン「c」が読み取られるまでわかりません。 パーサーは、「b」に続く「+」トークンに到達すると、precede()を呼び出して開始マーカーを位置「a」に複製し、終了マーカーを「c」を超える位置に配置できます。



PsiBuilderのもう1つの重要な機能は、空白とコメントの保存です。 スペースおよびコメントとして処理されるトークンのタイプは、ParserDefinitionクラスのgetWhitespaceTokens()およびgetCommentTokens()メソッドで定義されます。 PsiBuilderは、PsiParserに渡されるシーケンス内のスペースおよびコメントトークンを自動的にスキップし、ASTノードのトークン範囲を調整して、先頭および末尾のスペースがノードに入らないようにします。



ParserDefinition.getCommentTokens()メソッドによって返されるトークンのセットは、TO DOアイテムの検索にも使用されます。

PSIツリーの構築プロセスをよりよく理解するために、簡単な式については、下図の次の図を参照できます。





ユーザープラグインでPSIを実装する真の方法はありません。 言語機能(エラー分析、リファクタリングなど)の実装に最も便利なPSI構造と一連のメソッドを選択できます。 ただし、使用の名前変更や検索などの機能のサポートを実装するために、言語プラグインで使用する必要がある基本的なインターフェイスが1つあります。 名前を変更できる、またはリンクを持つ各要素(クラス、メソッドなど)は、メソッドsetName()、getName()でPsiNamedElementインターフェイスを実装する必要があります。



PSIの実装と使用に使用できる多くの関数は、パッケージcom.intellij.psi.util、特にクラスPsiUtil、PsiTreeUtilにあります。



PSI実装のデバッグに非常に役立つツールの1つは、 PsiViewerプラグインです。 ユーザープラグインによって構築されたPSI構造、各要素のプロパティ、およびPSI要素に関連付けられたテキスト範囲の強調表示を表示できます。



インデックス付けとスタブ



IntelliJ IDEAインデックスフレームワークは、広範なコードベースで特定の要素(特定の単語や特定の名前のメソッドを含むファイルなど)をすばやく検索する方法を提供します。 開発者は、IDEA自体に組み込まれている既存のインデックスと独自のインデックスの両方を使用できます。



IDEAは、ファイルベースとスタブの2つの主要なインデックスタイプをサポートしています。 ファイルインデックスはファイルコンテンツの上に構築され、スタブインデックスはシリアル化されたスタブツリーに基づいて構築されます。 ソースファイルのスタブツリーは、PSIツリーのサブセットであり、コンパクトなバイナリ形式でシリアル化された外部から見える定義のみが含まれています。 ファイルインデックスを要求することにより、プラグインは指定された条件を満たすファイルで構成されるセットを受け取ります。次に、スタブインデックスはPSI要素で直接動作します。 したがって、言語プラグインの開発者はスタブインデックスを優先する必要があります。



ファイルインデックス


IntelliJ IDEAのファイルインデックスは、map / reduceアーキテクチャに基づいています。 各インデックスには、特定のタイプのキーと値があります。 キーは、インデックスからデータを抽出する操作で使用されます。たとえば、ワードインデックスでは、キーは単語を含む文字列です。 インデックスのキー値には任意の情報を使用できます。たとえば、ワードインデックスでは、ワードが配置されているコンテキスト(コード、リテラル、コメント)を定義するマスクになります。 最も単純な場合(データがどのファイルにあるかを判別する必要がある場合)、値はvoid型であり、インデックスに保存されません。



ファイルにインデックスを付けた後、インデックスはキーとその関連値のテーブルを返します。 特定のキーでインデックスにアクセスすると、このキーに関連付けられたファイルとデータのリストを返します。



ファイルインデックスの実装


理解を深めるために、ファイルインデックス、つまりUI Designerで使用されるフォーム境界インデックスのかなり単純な実装の例を示します。



特定の各インデックス実装はFileBasedIndexExtensionクラスを継承し、拡張ポイントで登録する必要があります。 . :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().










. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().









. :

getIndexer() - , /, ; getKeyDescriptor() - , . - EnumeratorStringDescriptor ( ); getValueExternalizer() - , ; getInputFilter() - ; getVersion() - . , .

, ScalarIndexExtension.



, , DataIndexer.map() , , - . , , .





FileBasedIndex, :

getAllKeys() processAllKeys() - , , . , , , . getValues() - , ( , ); getContainingFiles() - , ; processValues() - , .



, IDEA . , . , PsiSearchHelper. - FilenameIndex, . FileTypeIndex - .





, PSI-, .

PSI- AST (.. ), stub- ( ), .

Stub- ( , , ). , , , PSI- AST.



Bean- , PSI- (, , ..). .



, PSI- . , , , .



, , :

, StubElement (); (); , PSI- StubBasedPsiElement (); , PSI- StubBasedPsiElementBase (). : ASTNode, - ; , IStubElementType PSI- (). createPsi() createStub(), serialize() deserialize() ; , IStubElementType (); , PSI- , PSI- (: Property.getKey()).

, :

( ParserDefinition.getFileNodeType()) , IStubFileElementType; plugin.xml, , IElementType, ().












All Articles