PhpStormのリンク解決プラグインの作成(IntelliJ IDEA)

私はWebプログラマーとして働いており、PHPで記述し、Kohanaフレームワークを使用しています。 開発には、驚くべき、私の意見では、PhpStorm環境を使用します。



大規模であまりプロジェクトではないとき、プロジェクトをナビゲートし、プロジェクトツリーで特定のファイル(コントローラーまたはテンプレート)を検索するのに多くの時間を費やすことにいつも落ち込んでいました。 残念ながら、Ctrl + Shift + Nは必ずしも便利ではありません。



まず、コカノフスキービューに渡されたテンプレート名の上でCtrl + B(またはCtrl +クリック)を押してコントローラーファイルからナビゲートできるようにしたかったのです。







したがって、PhpStorm用の小さなプラグインを作成することにしました。これにより、作業が容易になり、ルーチンの一部が解放されます。







環境の準備



必要なもの:

-IntelliJ IDEA Community EditionまたはUltimate。

-JDK (PhpStormのビルド元のバージョンをダウンロードする必要があります。そうしないと、プラグインが起動しません。私の場合はJava 1.6です)。



IDEA プラグインを作成するためのドキュメントは非常に少ないため、 Intellij IDEAソースコードのコピーも取得して、視覚的なドキュメントとして使用することをお勧めします。



ツールのセットアップ:




Java SDKおよびIntelliJ IDEAプラグインSDKを構成する必要があります。

-IntelliJ IDEAを起動します

-[ファイル]メニュー項目を開きます| プロジェクト構造

-[SDK]タブを選択し、プラス記号をクリックして、JDKへのパスを選択します



-[プロジェクト]タブを選択します

-新規をクリックしてから、IntelliJ IDEAプラグインSDKをクリックし、開いたメニューで-PhpStormへのパスを選択します(IntelliJ IDEAでも可能ですが、PhpStormでプラグインをデバッグすることはできません)





また、PhpStormでプラグインをデバッグできるように、実行/デバッグ構成を作成する必要があります。



プロジェクトを作成する


ファイル| 新規プロジェクト:「ゼロから作成」を選択し、名前を入力し、プラグインモジュールのタイプを選択し、以前に設定したSDKを選択して、プロジェクトを作成します。



plugin.xmlファイルにpathosの著作権を追加します(これがなければ、何もありません!)



<name>KohanaStorm</name> <description>KohanaStorm framework integration for PhpStorm<br/> Authors: zenden2k@gmail.com </description> <version>0.1</version> <vendor url="http://zenden.ws/" email="zenden2k@gmail.com">zenden.ws</vendor> <idea-version since-build="8000"/>
      
      







プラグインをIDEAだけでなくPhpStormでも実行するには、次の依存関係をplugin.xmlに追加します。

 <depends>com.intellij.modules.platform</depends>
      
      





基本



各ファイルに対して、IntelliJ IDEAはPSIツリーを構築します。



PSI(プログラム構造インターフェース)は、特定のプログラミング言語の要素の階層としてファイルの内容を表す構造です。 PsiFileはすべてのPSIファイルの共通の親クラスであり、特定のプログラミング言語はPsiFileから継承されたクラスとして表されます。 たとえば、PsiJavaFileクラスはJavaファイルを表し、XmlFileクラスはXMLファイルを表します。 PSIツリーは、PSIビューアーツール([ツール]-> [PSI構造の表示])を使用して表示できます。



画像



プラグイン開発



そのため、View :: factory( 'template_name')でCtrl + B(またはCtrl + Click)でコントローラーファイルからテンプレートファイルに直接切り替えられるようにしたかったのです。







計画の実施方法





リンクを解決するには、次から継承された3つのクラスを作成する必要があります。



PsiReference-このインターフェイスを実装するオブジェクトはリンクです。 これには、親要素の位置(テキスト内の位置)とデータ(リンクテキスト)のデータが含まれ、後で「リンクを許可する」ことができます。 リンクはそれ自体を解決できる必要があります。 彼女のresolve()メソッドは、彼女が指す要素を見つけることができるはずです。



PsiReferenceProvider -PSIツリーの単一要素内のリンクを見つけるクラス。 PsiReferenceオブジェクトの配列を返します。



PsiReferenceContributor -PsiReferenceProviderをPSI要素のハンドラーとして登録するクラス。



1. PsiReferenceインターフェイスを実装する参照クラスMyReferenceを作成し、その中で次のメソッドをオーバーライドします




 public class MyReference implements PsiReference { @Override public String toString() { } public PsiElement getElement() { } public TextRange getRangeInElement() { return textRange; } public PsiElement handleElementRename(String newElementName) } public PsiElement bindToElement(PsiElement element) throws IncorrectOperationException { } public boolean isReferenceTo(PsiElement element) { return resolve() == element; } public Object[] getVariants() { return new Object[0]; } public boolean isSoft() { return false; } @Nullable public PsiElement resolve() { } @Override public String getCanonicalText() { } }
      
      







このクラスでは、resolve()メソッドが最も重要です。 その中で、リンクが指す要素を返さなければなりません。 この場合、phpファイルへのリンクを返しますが、一般的な場合、クラス、メソッド、変数など、その上にあるプシツリーまたは言語モデルの任意の要素を使用できます。



2. PsiReferenceProviderから継承したクラスを作成し、 getReferencesByElementメソッドをオーバーライドします。




 public class MyPsiReferenceProvider extends PsiReferenceProvider { @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull final ProcessingContext context) { } }
      
      







getReferencesByElementメソッドは、 渡されたPsiElement要素に含まれるリンクのリスト( PsiReference )を返す必要があります。 私たちの場合、1つのリンクのみが返されますが、一般的なケースでは複数あり、各リンクには対応するtextRange(psi要素のテキスト内のリンクの開始インデックスと終了インデックス)を含める必要があります



このメソッドを開発する際の主な問題は、JetBrainsが言語API(この場合はPHP)へのプラグインアクセスを開かなかったことです。 しかし、ここでReflectionが助けになりました。 要素オブジェクトについて何を知っていますか? StringLiteralExpressionImplクラスのインスタンスでなければなりません。



  public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull final ProcessingContext context) { Project project = element.getProject(); PropertiesComponent properties = PropertiesComponent.getInstance(project); String kohanaAppDir = properties.getValue("kohanaAppPath", "application/"); VirtualFile appDir = project.getBaseDir().findFileByRelativePath(kohanaAppDir); if (appDir == null) { return PsiReference.EMPTY_ARRAY; } String className = element.getClass().getName(); Class elementClass = element.getClass(); // ,     StringLiteralExpressionImpl if (className.endsWith("StringLiteralExpressionImpl")) { try { //   getValueRange,    ,      Method method = elementClass.getMethod("getValueRange"); Object obj = method.invoke(element); TextRange textRange = (TextRange) obj; Class _PhpPsiElement = elementClass.getSuperclass().getSuperclass().getSuperclass(); //   getText,    PHP- Method phpPsiElementGetText = _PhpPsiElement.getMethod("getText"); Object obj2 = phpPsiElementGetText.invoke(element); String str = obj2.toString(); String uri = str.substring(textRange.getStartOffset(), textRange.getEndOffset()); int start = textRange.getStartOffset(); int len = textRange.getLength(); // ,     PHP- (  )   if (uri.endsWith(".tpl") || uri.startsWith("smarty:") || isViewFactoryCall(element)) { PsiReference ref = new MyReference(uri, element, new TextRange(start, start + len), project, appDir); return new PsiReference[]{ref}; } } catch (Exception e) { } } return PsiReference.EMPTY_ARRAY; }
      
      







PHPリテラルだけでなく、View :: factory()に渡された文字列でキャッチされたことを判断するために、リフレクションのマジックを再び使用します。



 public static boolean isViewFactoryCall(PsiElement element) { PsiElement prevEl = element.getParent(); String elClassName; if (prevEl != null) { elClassName = prevEl.getClass().getName(); } prevEl = prevEl.getParent(); if (prevEl != null) { elClassName = prevEl.getClass().getName(); if (elClassName.endsWith("MethodReferenceImpl")) { try { Method phpPsiElementGetName = prevEl.getClass().getMethod("getName"); String name = (String) phpPsiElementGetName.invoke(prevEl); if (name.toLowerCase().equals("factory")) { Method getClassReference = prevEl.getClass().getMethod("getClassReference"); Object classRef = getClassReference.invoke(prevEl); PrintElementClassDescription(classRef); String phpClassName = (String) phpPsiElementGetName.invoke(classRef); if (phpClassName.toLowerCase().equals("view")) { return true; } } } catch (Exception ex) { } } } return false; }
      
      







私たちが何を扱っているかをより明確にするために、画像:



このコードは、「factory」と呼ばれ、「view」クラスにあるメソッド呼び出し(MethodReference)に要素が実際にネストされていることを決定します。



3. PsiReferenceContributorから継承したクラスを作成し、次のメソッドをオーバーライドします。




  @Override public void registerReferenceProviders(PsiReferenceRegistrar registrar) { registrar.registerReferenceProvider(StandardPatterns.instanceOf(PsiElement.class), provider); }
      
      







クラスが行うことは、PsiReferenceProviderをレジストリに登録し、テンプレートを、適用するPsiElementのタイプ(サブクラス)に設定することだけです。 たとえば、必要なドキュメント要素がXML属性の値である場合、すべてがより単純になります。



  registrar.registerReferenceProvider(StandardPatterns.instanceOf(XmlAttributeValue.class), provider);
      
      







しかし、JetBrainsは言語API(この場合はPHP)へのアクセスを開かなかったため、絶対にすべてのPsiElement要素をサブスクライブし、この要素が必要かどうかを動的に判断する必要があります。



4. plugin.xmlファイルに貢献者を登録します。


  <extensions defaultExtensionNs="com.intellij"> <psi.referenceContributor implementation="MyPsiReferenceContributor"/> </extensions>
      
      







設定ページを作成する









phpstormには、プロジェクト関連とグローバルの2種類の設定があります。 プラグインの設定ページを作成するには、Configurableインターフェースを実装するKohanaStormSettingsPageクラスを作成します。 getDisplayNameメソッドは、PhpStorm設定リストに表示されるタブの名前を返す必要があります。 createComponentメソッドはフォームを返します。 applyメソッドでは、すべての設定を保存する必要があります。



 public class KohanaStormSettingsPage implements Configurable { private JTextField appPathTextField; private JCheckBox enableKohanaStorm; private JTextField secretKeyTextField; Project project; public KohanaStormSettingsPage(Project project) { this.project = project; } @Nls @Override public String getDisplayName() { return "KohanaStorm"; } @Override public JComponent createComponent() { JPanel panel = new JPanel(); panel.setLayout(new BoxLayout (panel, BoxLayout.Y_AXIS)); JPanel panel1 = new JPanel(); panel1.setLayout(new BoxLayout(panel1, BoxLayout.X_AXIS)); enableKohanaStorm = new JCheckBox("Enable Kohana Storm for this project"); ... PropertiesComponent properties = PropertiesComponent.getInstance(project); appPathTextField.setText(properties.getValue("kohanaAppPath", DefaultSettings.kohanaAppPath)); return panel; } @Override public void apply() throws ConfigurationException { PropertiesComponent properties = PropertiesComponent.getInstance(project); properties.setValue("kohanaAppPath", appPathTextField.getText()); properties.setValue("enableKohanaStorm", String.valueOf(enableKohanaStorm.isSelected()) ); properties.setValue("kohanaStormSecretKey", secretKeyTextField.getText()); } @Override public boolean isModified() { return true; } @Override public String getHelpTopic() { return null; } @Override public void disposeUIResources() { } @Override public void reset() { } }
      
      







plugin.xmlファイルに設定ページを登録します。



 <extensions defaultExtensionNs="com.intellij"> <psi.referenceContributor implementation="MyPsiReferenceContributor"/> <projectConfigurable implementation="KohanaStormSettingsPage"></projectConfigurable > </extensions>
      
      







(設定ページがグローバルである場合、applicationConfigurableを使用します)



設定保存


プラグインの設定を保存する最も簡単な方法は、PropertiesComponentクラスとsetValueおよびgetValueメソッドを使用することです。 より複雑な方法は 、ドキュメントに記載されています。



プラグインのインストール


プラグインの開発が完了したら、完了する必要があります

ビルド->展開用のプラグインを準備します。 その後、jarと呼ばれるファイルがプロジェクトフォルダに表示され、配布できます。

(ファイル->設定->プラグイン->ディスクからインストール)を実行してphpstormにインストールできます



プラグインとソースコードをダウンロードする



All Articles