精品伊人久久大香线蕉,开心久久婷婷综合中文字幕,杏田冲梨,人妻无码aⅴ不卡中文字幕

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
IntelliJ IDEA插件開發手冊
  • 開發準備


       在開發前,首先我們要做一些環境準備,這樣你可以快速進入這個場景。首先你要安裝好IntelliJ IDEA,這里我推薦大家使用IntelliJ IDEA 7.0 M2,你可以通過http://www.jetbrains.com/idea下載。IntelliJ IDEA安裝完畢后,你需要安裝一個插件開發包,主要用于IntelliJ IDEA的插件開發。下載地址和IntelliJ IDEA相同。 這個開發包包含以下內容:IntelliJ IDEA的Open API和源碼,以及一些插件的源碼(IntelliJ IDEA的插件源碼都在 http://svn.jetbrains.org/idea/Trunk,這些都是你編寫插件的很好樣例)。當然沒有這個插件開發包你可以開發插件,不過我強烈推薦下載這個開發包。開發部下載完畢后,你需要將其解壓到IntelliJ IDEA的安裝目錄下,這樣IntelliJ IDEA的SDK創建會很簡單。這里要注意一點,插件開發包的版本最好和IntelliJ IDEA的版本相對應。關于IntelliJ IDEA的SDK準備,你可以參考這篇文章:建立IntelliJ IDEA插件開發環境
       接下來我們啟動IntelliJ IDEA來創建一個IntelliJ IDEA SDK,這是開發插件的基礎。啟動IDEA,打開設置面板,選擇”Project Settings",在彈出的對話框中安裝下圖進行IntelliJ IDEA SDK設置:

     

    這里要有一個注意事項:默認的情況下,新創建的SDK并會將idea.jar包含到classpath中,由于IntelliJ IDEA的Open API不能完全滿足你需要的功能,你的插件可能會用到IDEA未公布的API,所以這里建議你檢查一下idea.jar文件是否已經被包含,如果沒有被包含,請加入這個jar文件。
        IntelliJ IDEA的SDK已經被創建了,它是開發插件的基礎包。在這里我相對這些jar文件進行一些描述,已方便我們以后的代碼編寫,畢竟我們代碼是要引用其他的開發包的,如果IDEA已經提供,我們就沒有必要在找第三方的jar包啦。在$IDEA_HOME/lib下有以下文件需要提一下:
          commons-codec-1.3.jar:這個是進行編碼轉換的開發包,是Apache Commons一個比較重要的開發包; http://commons.apache.org/codec
          commons-collections.jar: Java Collection擴展框架,對Collection處理更加方便,也是Apache Commons一個比較重要的開發包;  http://commons.apache.org/collections/
          j2ee.jar和javaee.jar:J2EE開發包,這個不用說啦; http://java.sun.com/javaee/5/docs/api/
          jdom.jar:XML開發包; http://www.jdom.org
          trove4j.jar: java.util.Collections的一個實現,更加快速、輕量; http://trove4j.sourceforge.net/
          velocity.jar: Velocity模板引擎; http://velocity.apache.org
          xmlrpc-2.0.jar: xml-rpc的開發包; http://ws.apache.org/xmlrpc/
          xstream.jar: xml/object映射框架; http://xstream.codehaus.org/
       了解一下這些開發包,可能對后續的開發有不少的幫助。
       下面就讓我們開始創建一個項目并進行插件開發。首先我們要創建一個項目,這里個人有一個建議:建立一個空的項目,然后添加多個plugin module,這樣在一個項目中可以包含多個plugin module,主要的好處就是你可以添加一些參考的插件,這對編寫一個新的插件會幫助不少。

    項目創建后,我們需要添加一個plugin module。只需打開"File" -> "Add Module"然后選擇"plugin module"就可以啦,請將module的SDK設置為上面我們創建的IDEA SDK。到這里插件開發的項目就創建完畢啦,你可以開發你的插件啦。

  • IntelliJ IDEA的IoC介紹


        當你看到這一章節時,你估計會罵我雞婆。IoC,這個還要你來告訴我,我用SpringFramework已經很久啦。但我還是要說一下。IDEA整個組件結構是基于PicoContainer(http://www.picocontainer.org)的,PicoContainer是一個高效的嵌入式的DI容器。如果你有時間的話,我建議你花5分鐘瀏覽一下PicoContainer,然后回到這篇文檔來。
        PicoContainer是有層次結構的,就是一個container可以包含子container,子容器可以訪問父容器中的組件,而各個子容器直接是獨立的。在IDEA中,主要有三種container:Application, Project和Module,分別包含不同的組件。application container包含多個project container,project container可以包含多個module container,如下圖:

        這樣各個project container是獨立的,都可以訪問application container中的組件;module container也是獨立的,可以訪問所屬project container和application container中的組件。這個圖是我們后面理解application component, project component, module component和extension point等等的基礎。
        PicoContainer的組件注入主要有兩種方式:構造注入和Setter注入,但是在IDEA中,目前Setter注入還不支持,全部是構造注入,關于構造注入,PicoContainer推薦最好使用一個構造函數,這點也在IDEA中需要明確。如果你的組件需要引用其他的組件或資源,你最好在組件的構造函數中指定,PicoContainer會幫助你完成資源引用和初始化。
       IDEA的這些容器中包含些什么? 當然首先是各種component,還有就是一些服務,容器中不僅僅是component,還有相關為組件服務的資源,在后面我們也會涉及到對容器中服務資源的講述。 
       如果訪問這些容器中的組件?在IDEA中,訪問application container中的組件可以通過ApplicationManager.getInstance().getComponent(Class T)來進行。通用獲得project對象后,你可以訪問project容器中的組件;獲取module對象后,你可以訪問module容器的組件。有了容器后,如何能獲取指定的組件?有以下幾種方式: 1. 組件ID,組件提供的組件標識符號,可以通過標識符來訪問。如果組件沒有標識符號,我們稱之為匿名組件。 2. 組件的interface類。如果一個組件的是通過interface向外服務的,那么我們可以通過interface來獲取對應的組件。如果 interface的實現為多個組件,就會獲得多個組件。
       如果讓我的組件被注冊到這些容器中? 在IDEA中,有三種組件: Application Component, Project Component和Module Component。不用的組件需要繼承不同接口,分別為Application Component, ProjectComponent和ModuleComponent,如果你的組件繼承了某一接口,將會自動放置到某一容器中,不需要你手動去注冊。
       組件既然要交給容器去管理,這就牽涉到生命周期的概念,對于Application Component來說,initComponent負責初始化,disposeComponent負責資源清理。對ProjectComponent來說,除了initComponent和disposeComponent,還增加了projectOpened 和projectClosed,這個意思還比較容易理解,就是一個掛鉤(hook)。組件一旦被激活,就開始發揮它的作用啦。
      組件的行為可能需要設置,如設置不同的參數組件的特性就不一樣。如何給組件設置參數?當然可以讓組件自身去做,去找一個文件,當文件修改后重新加載。在IDEA中,你不需要這么做,你只需讓組件繼承Configurable接口,IDEA會將在設置面板中添加一個設置選項,讓你設置這個組件的參數,當然包括運行期的,這個好像和JMX相像。 :)
      組件的參數設置完畢啦,當容器關閉后,組件帶的這些參數需要保存在一個地方,這樣當容器重新啟動后,組件仍然能向以前一樣工作,不然你又得重新設置一下。同樣的道理,你可以自己設定邏輯,保存到某一個地方,然后在加載起來。如果IDEA提供了一個 JDOMExternalizable接口,只要實現接口并添加少量的代碼,IDEA就會完成component的參數保存和讀取的任務。在最新的 IDEA 7.0中,采取了另外一種保存機制,這個我們會在后面進行說明。
       講到這里,你可能會問,有沒有一種方面來聲明Component? 這是有的,那就是extension point。extension point是組件的簡化方式,它的主要功能是數據信息擴展,它們不需要繼承component接口,同時也沒有組件標識符號,只需要在 plugin.xml聲明就可以,在聲明的時候你需要指名是何種類型的組件。下面會有更詳細的介紹。
      PicoContainer是IDEA基礎,因為我們編寫的組件都是由容器初始化的,而且組件直接的相互依賴也是有容器完成,所有了解一下PicoContainer還是很有必要的,對插件編寫和IDEA的機制都非常有好處。

  • Extension Points


       前面講解了一下extions point,這里想再細化一下。Extension point的主要作用是數據信息擴展和事件監聽,也就是一個插件注冊了某一extension point,其他插件可以通過extension point為該插件提供數據信息或觸發事件邏輯,從而達到影響上一插件中的組件的一些行為。最典型的就是gotoSymbolContributor,我們在各個插件中通過gotoSymbolContributor的聲明,提供插件自己的symbol信息給IDEA,這樣在按下 Ctrl+Shift+Alt+N時,插件提供的symbol信息就會被提示出來,當然你可以利用這種機制實現其他功能,監聽也是一種實現。從用戶的角度來看,就是在某些方面,原先的插件功能增加啦。那么如何聲明一個extension point呢。這個很簡單,只要建立一個Java Integerace,然后在plugin.xml進行什么就可以啦,代碼如下:
              <extensionPoint name="resourceBundleManager" interface="com.intellij.lang.properties.psi.ResourceBundleManager" area="IDEA_PROJECT"/>    
       前面說過,extension point是組件的簡化方式,這里的area是指組件的類型,如果不指定就是ApplicationComponent,IDEA_PROJECT表示 ProjectComponent,MODULE_PROJECT表示ModuleComponent。聲明完成后,我們需要在插件中訪問 extension point去獲取數據,代碼如下:
              Object[] extensions = Extensions.getExtensions("plugin_id.testExtPoint");
       這里字符串中的plugin_id表示plugin的id(在xml文件中),testExtPoint就是extension point的name。還有一種就是提供ExtensionPointName,這個可以參考一下Open API,也非常簡單。這里返回一個數組,因為可能多個其他插件使用該extension point為該插件提供數據。接下來就是在其他插件應用該extension point啦,三個步驟:
         1 首先依賴該插件:  <depends>reliant_plugin_id</depends>
         2 創建extension point的interface的實現,java編碼即可
         3 extension point引用聲明,xmlns的值就是所依賴的plugin的id。代碼如下:
                 <extensions xmlns="reliant_plugin_id">    
                        <testExtPoint implementation="com.foobar.test.impl.Extender"/>
                </extensions>
       通過這種方式,可以實現插件直接的數據供給,提示原有插件的功能,一個好的插件,如果能定義好一下擴展點,方便其他插件進行擴充,將是非常有益的。
       事實上IDEA核心就提供了非常多的extension point,這里你可以擴展IDEA的功能。關于這些擴展點的元信息,請參考:$IDEA_HOME/lib/resources.jar文件的/META-INF/plugin.xml文件。

  • Plugin的結構介紹


        我們應該進入正題啦。Plugin的主要功能擴展IDE的功能,前面我們講述了IDEA整體結構是基于容器的,那么要擴展IDEA的功能,唯一的方法就是想容器中添加組件,新添加的組件包含自身的一些功能,同時和其他組件進行交互(修改一些參數和特性等),影響其他組件的行為,從而達到功能的擴展目的。那么一個插件中,應該會包含application component, project component和module component。由于還要和用戶進行交換,插件還提供了action,也就是和用戶進行交換的操作,所以插件的主要內容就是component和 action。這里順便還聊一句,component是由容器管理的,那么action可不可以也由容器管理呢?這樣在action中引用 component將更加方便。目前action還不是由容器管理的,這個主要是由歷史原因決定的,不少action的代碼還不能轉移到容器中管理,不過 IDEA正在做一些工作,相信以后action也可以由容器進行管理。
       下面我們要開始插件編寫啦。首先我們要設定一下插件的基本信息。插件需要有一個唯一標識符,有一個版本號(便于升級)還有就是適用的IDEA版本。這三項應該說是必需的,其他就是插件的額外信息,如描述,changeLog,作者等等,在plugin.xml設定就可以啦。
       完成設定后,我們就需要向插件中添加內容啦。創建Component和Action非常簡單,只要通過new group就可以創建。圖例如下:

     

    這里我們可能還要啰嗦一下,就是關于plugin的目錄結構。插件開發包中有一個plugin structure的html文檔,已經講述的非常清楚,這里只是重復一下。一個plugin通常包含plugin.xml,相關的class和引用的第三方jar文件。如何組織這些文件,我推薦以下的結構:插件目錄下的lib文件夾保存第三方jar文件(如果沒有引用第三方jar,可以沒有該目錄),classes目錄包含插件的代碼,META-INF包含 plugin.xml文件,結構如下:

  • 使用Maven管理插件項目


         Maven實際上已經成為Java項目管理的規范,當然這里我們也希望IDEA的插件開發也能通過Maven管理起來。Maven并不難,但是針對 IDEA的插件項目主要有以下問題,可能導致管理有一定的難度:1. IDEA并不是都使用Javac進行代碼編譯。如果你使用了IDEA的UI Designer,那么你得使用Javac2才能編譯這些代碼; 2. 開發插件需要的IDEA的jar文件在repo1.maven.org/maven2中沒有,你可能需要自己設定repository的位置;3. IDEA的插件需要裝配,這個可能是一些web項目,jar項目不具有的。基于這些原因,我想給一個相對標準的pom.xml文件和插件項目的目錄結構,目錄結構如下圖: 

    這個目錄和標準Maven項目是一致的,不過有一點就是我們將plugin.xml文件放置在src/main/resources目錄下,最好形成這樣的標準,這對后續的plugin打包分發有幫助。
        回到pom.xml文件,其實只需注意一下,IDEA插件開發需要的jar包都在 http://mevenide.codehaus.org/m2-repository/, 所以我們需要設置一下項目的repository的位置。由于還使用了codehaus的一些Maven插件,所以還有設置一下plugin repository的為位置。下面就是設置build plugin,能保證插件項目中的代碼能被正確編譯,主要就是IDEA的UI Designer的文件編譯,其他的和標準的Maven編譯選項一致。接下來就是設置dependency,由于插件開發需要的jar包不少都包括在 IDEA SDK(前面我們講述過),所有這些dependency的scope設置為provided即可。如果你引用的是IntelliJ IDEA本身的jar包,那可能還有注意一點:由于IDEA的jar包包括正式版本號和編譯版本號,所以你可能還有給dependency設置 classifier,這個值就是IDEA的編譯版本號,一個典型的dependency聲明如下:
           <dependency>
                <groupId>com.intellij.idea</groupId>
                <artifactId>openapi</artifactId>
                <version>7.0</version>
                <scope>provided</scope>
                <classifier>${idea_build_number}</classifier>
            </dependency>
        因為牽涉到插件要發布出去,所以我們還是需要設置一下如何將插件打包。在Maven中這稱之為Assembly,是通過Assembly plugin完成。我們只需要創建Assembly的描述文件,然后在設置一下Assembly插件的配置,最后咨詢mvn assembly:assembly就可以啦。一個IDEA插件項目典型的Assembly的描述文件如下所示:
        <?xml version="1.0" encoding="utf-8" ?>
        <assembly  xmlns="http://maven.apache.org/xsd/assembly        <id/>
            <formats>
               <format>zip</format>
            </formats>
            <includeBaseDirectory>false</includeBaseDirectory>
            <dependencySets>
                <dependencySet>
                <outputDirectory>/gmail-plugin/lib</outputDirectory>
                <unpack>false</unpack>
                <scope>runtime</scope>
                <includes>
                    <include>org.intellij:gmail-plugin</include>
                    <include>net.sf:jgmail</include>
                    <include>commons-httpclient:commons-httpclient</include>
                    <include>commons-logging:commons-logging</include>
                </includes>
            </dependencySet>
         </dependencySets>
      </assembly>        
         在Assembly中出現的artifact,如commons-httpclient需要能在pom.xml中解析得到(只要commons- httpclient處于dependency中就可以),這樣我們就可以將IDEA的插件打包成zip文件,這樣就可以發布,現在你只要將登錄到
    http://plugins.intellij.net,然后上傳這個zip文件,你的發布就完成啦。   

  • IntelliJ IDEA TestCase


        個人對TDD還是比較推崇的,所以在沒有進行開發前,還是先介紹一下如何進行測試。在com.intellij.testFramework包下包含各種 TestCase,你可以進行相關的單元測試。下面我們先看一下IDEA是如何運行TestCase的。IntelliJ IDEA在運行IDEA的TestCase時,會加載當前編輯的插件,這樣就會模擬出一個IDEA運行的真實環境,這樣你就可以進行各種測試。在實際的開發中,我們經常會PsiFile,VirtualFile等Java類,在plugin的內容組織方面,也主要是Action,Inspection 等,IDEA的test framework都提供了這些TestCase,很方便地測試這些類和組件。IdeaTestCase、LightIdeaTestCase、 PsiTestCase以及InspectionTestCase等都提供很便捷的方式來測試你編寫的代碼。當然IDEA還沒有提供非常全面的測試方案來測試任何代碼,這個可能對TestCase設計編寫要求會比較高,應該絕大多數情況下,你要自己覺得怎樣去編寫Unit Test。對于新的插件開發人員,個人建議還是TestCase加Debug合并使用,畢竟這方面的知識我們還是比較欠缺點。參考一下別人編寫的 TestCase可能會提升我們編寫TestCase的水平。如何使用IDEA test framework提供的TestCase,這個很簡單,只要繼承響應的TestCase,然后編寫代碼就可以啦,沒有任何特殊的要求。不要害怕,編寫幾個TestCase,你就有感覺啦。最后說一句,IDETalk插件的Unit Test寫的不錯,大家可以參考一下,而且IDETalk的作者Kirill Maximov也寫了不是關于IDEA下Unit Test的文章。

  • 開發場景


       1 開發一個讀寫文件的Action:
              IDEA的設計思路是多線程讀單線程寫的模式,而且在AnAction中是不能進行寫操作的,如果你要在Action中進行寫操作,你需要創建一個 Runnable對象,然后交給 ApplicationManager.getApplication().runWriteAction(runnable)去執行。
       2 Editor Action:
              editor action主要用于操作當前編輯窗口中的內容,通常需要給editor action設置一個EditorWriteActionHandler來完成對editor的操作。EditorModificationUtil提供了不少方法,可以加快開發。如果向想獲取光標處的PsiElement對象,需要設置PsiFile的 getElementByOffset(offset)來獲取。如果你想進行相關的插入操作,你可能需要創建指定的PsiElement,這個時候 PsiElementFactory可能會幫你不少忙,你可以參考一下PsiElementFactory API,看是否有你需要的東西。
      3 Intention Action: 
              intention action簡單地說就是意圖操作,IDEA會根據光標所在的位置,進行相關檢查,然后提示可以進行相關的操作。intention action需要繼承IntentionAction類,需要提供family name(ID標識)和description(顯示名稱)。在IntentionAction類中,isAvailable判斷當前Intention Action是否有效,invoke表示你選擇該intention action后執行的動作。PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); 這個語句用來獲取當前光標處的PsiElement。intention action還需要注意的一點,就是我們要在源碼根目錄建立一個intentionDescriptions的目錄,然后在依據family name建立一個子目錄,最后添加三個文件:description.html、before.xxx.templates和 after.xxx.template,xxx可以為某一種類型文件的擴展名。如下圖所示:

    最后,我們需要將這個action進行注冊,action通常要歸屬于某一類別。注冊有兩種方式:代碼和聲明。代碼為:IntentionManager.getInstance().registerIntentionAndMetaData(new FirstIntentionAction(), "category"); 聲明方式需要在plugin.xml中指定,代碼如下:
           <intentionAction>
              <className>com.foobar.FirstIntentionAction</className>
              <category>category</category>
           </intentionAction>
      4  Inspection action創建
           inspection action就是代碼審查action,它可以檢查發現代碼潛在的錯誤。如果你留意一下右下角的偵探頭像,他就是代碼審查員,負責調用各種 inspection action,完成對代碼的審查。如果發現潛在的問題,就會給你一個解決方案,你可以選擇該方案進行問題修復。在IDEA設置面板中的“Errors”選項,其實就是inspection action的集合,目前IDEA 7.0大概包含800+個inspection action,審查代碼的各個方面,對代碼質量的提升有很大的幫助。
           言歸正傳,如果編寫一個這樣的action。首先我們創建一個新的inspection action,它需要實現LocalInspectionTool類。Inspection action需要提供short name(ID標識),display name(顯示名)和group display name(所在的組名), 這樣inspection action可以更好地顯示。和Intention Action一樣,我們需要在源碼目錄下建立一個inspectionDescription的目錄,然后依據short name創建一個html文檔,將該inspection的功能進行描述。圖例如下:

        LocalInspectionTool默認是檢查Java文件的,如果你想讓LocalInspectionTool審查其他文件,你需要重寫 LocalInspectionTool類的buildVisitor方法,來審查特定的類型的PsiElement,你可以參考一下 LocalInpsectionTool的源碼。前面我們講到inspection action會提供一個解決問題的方案,在IDEA中這叫QuickFix。當我們檢查到一個錯誤時,我們需要創建一個問題描述,如果有必要的話,需要創建一個quick fix來完成問題修復。注冊一個問題,通常提供這四個參數:可能存在錯誤的PsiElement、警告級別、描述和quickfix。這個可以通過 InspectionManager進行審查問題創建。
       目前,我們的Inspection Action已經完成啦,如何注冊inspection action? 和intention action的注冊一樣,有兩種方式:代碼和extension point。代碼方式:需要創建一個application component,然后繼承InspectionToolProvider,只要實現InspectionToolProvider的 getInspectionClasses()方法即可。 extension point:同intention action不一樣的是,我們要創建一個類,繼承InspectionToolProvider,這個類不必要繼承 ApplicationComponent,然后實現InspectionToolProvider的getInspectionClasses(),最后在plugin.xml文件中進行聲明,如下:
       <inspectionToolProvider implementation="com.intellij.psi.css.CssInspectionsLoader"/> 
    從理論上來說,IDEA是將inspectionToolProvider最為一個application component對待。
    5. Annotator創建
         annotator顧名思義標注,就是給相關的PsiElement加上標識,這個標識涉及到高亮顯示、裝訂欄(裝訂欄添加圖標標識)等等,annotator主要用于標識一些信息。編寫annotator,我們需要創建一個類,繼承Annotator,然后根據psiElement來判斷是否要給element添加標識,主要是和Annotation打交到。最后,我們需要在plugin.xml中進行annotator申明,如下:
             <annotator language="JAVA" annotatorClass="cn.org.intellij.webx.ScreenAnnotator"/>
    6. 設置組件屬性和狀態保存
         前面我們講過如何保存組件的狀態,在這里我們講述一下IDEA 7.0中的實現方法,可能有點不一樣。想要設置一個組件的狀態,你需要繼承Configurable,這樣在設置面板中就會出現一個選項,你可以進行相關的操作。接下來我們需要將設置的狀態保存起來,這是我們需要創建另外一個類,專門用于保存設置,我們可以稱之為Settings類,這個Settings 類需要PersistentStateComponent類,負責信息的保存和讀取。信息的讀取是通過一個@State的annotation來設置的。到這里,我們就可以理解啦,一個類用于接收參數設定,一個類用于參數保存和讀取,由于該類包含相關參數,所以它就是一個Service,通常將參數進行封裝,對外提供服務,代碼很簡單。接下來就是在plugin.xml中進行聲明,我們可以參考一下Ruby插件中的例子:
        <projectConfigurable implementation="com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable"/>
        <projectService serviceInterface="org.jetbrains.plugins.ruby.settings.RProjectSettings" serviceImplementation="org.jetbrains.plugins.ruby.settings.RProjectSettings"/>
    下面是@State的描述:
        @State(
        name = YourPluginConfiguration.COMPONENT_NAME,
        storages = {@Storage(id = "your_id", file = "$PROJECT_FILE$")}
        )
       在這個例子中我們使用了$PROJECT_FILE$宏,你可以根據組件的類型不同設置不同的宏,如下:
       - application-level components (ApplicationComponent): $APP_CONFIG$, $OPTIONS$;
       - project-level components (ProjectComponent): $PROJECT_FILE$, $WORKSPACE_FILE$, $PROJECT_CONFIG_DIR$;
       - module-level components (ModuleComponent): $MODULE_FILE$
    7. gotoClassContributor
        在IDEA中,我們通常會按下Ctrl+N或Ctrl+All+Shift+N去尋找類或symbol,如果你想擴展各個功能,如在struts中,查找某一個action的聲明,這個時候我們需要擴展這一功能。這個時候我們只需創建一個類,讓其繼承ChooseByNameContributor,實現 ChooseByNameContributor的方法就可以。接下來就是在plugin.xml中進行聲明,可以參考Struts, Ruby的插件:
            <gotoSymbolContributor implementation="org.jetbrains.plugins.ruby.ruby.gotoByName.RubySymbolContributor"/>

  • Virtual File, Document 和Psi File


         在IDEA中,我想對文本的操作可能就是這三個在發揮作用,這三者都可以對文件的內容進行更改。
         Virtual File是IDEA的統一文件系統,就向Java的IO一樣,我們可以稱之為VFS(虛擬文件系統)。有了Virtual File,我們不在需要和傳統的文件打交到,取而代之的是VFS。我們對VFS的各種操作,會映射到傳統的文件系統上。IDEA中的所有和文件相關的操作都是通過Virtual File進行,這些操作和傳統的文件操作差不多,不過更加簡單。
         Document其實就是Virtual File的內容的字符序列,所以對Document的各種操作都是基于普通文本的。Document的可操作的方法并不多,主要是Document是基于字符序列的,操作起來難度有點大。事實上我們對Document的使用也比較少,通常都是一些信息的簡單獲取。
         Psi File這個是結構化的文件內容呈現,這樣我們通過操作結構化的對象,從而達到操作文件內容的目的。這些結構化的對象通常通過一種編程語言來體現,在 IDEA中,就是Java對象,這樣我們操作就更加簡單。這里我不知道這個例子是否合適,JDom是用Java對象來體現xml文檔,這里PsiFile 就是用Java語言來體現各種文件內容。講到這里可能大家還沒有體驗PsiFile的好處,我們舉一個例子來說明。現在有一個Java文件,那么我們就可以構建一個PsiJavaFile對象,通過該對象,我們可以了解該java文件的一些信息,如package 名稱,import語句列表,包含的class。假設我們獲取了PsiJavaFile的一個PsiClass對象,我們就可以了解該Java類的各種信息,如名稱、注釋和包含的函數等等。在這里我們可以更改PsiClass的名稱、注釋等等,這些修改馬上就會反映到文件的內容中。試想一下,如果這一切通過文本分析完成,那將是多么復雜的工作,有了Psi File,這一切就簡單啦,操作對象比操作文件內容要可靠簡單的多。關于PSI,請參考一些IntelliJ IDEA的插件開發文檔,同時我們推薦Psi Viewer這個插件,你可以對IDEA處理內容做更好地理解。如果你想寫出好的插件的話,你需要對PSI有較深的理解,雖然我寫的很少,但是它的重要性卻相當高。

  • 數據關聯結構


       其實這節不知道如何寫?章節的名稱也怪怪的。我們都知道IDEA的代碼提示、代碼導航和代碼重構非常強大,這背后的是什么樣的數據結構來支撐這些特性。在 IDEA中,通過一種Reference機制可以將很多的事情關聯起來。回到上一節所說的,在IDEA中,我們對文件的內容操作,通常都是通過 PsiFile完成的。PsiFile中最小的元素就是PsiElement,由于PSI的設計合理,所以可以將PsiElement進行關聯,這樣可以實現很多的特性。舉一個例子來說吧。 <bean id="personManager" class="com.foobar.PersonManagerImpl"/>這是一個簡單xml元素,但是在這個云素中,我們知道class的屬性值(XmlAttributeValue, PsiElemnt的子類)是和Java Class類進行關聯的。如果我們將將XmlAttributeValue和PsiClass(Java類的Psi結構類型)關聯起來,那么我們就可以實現代碼提示,導航和重構(當類名更改會更新xml的屬性值),這個關聯的過程就是Reference的實現。我們通過特定的Reference將這兩者關聯進行實現,然后進行注冊聲明,那么我們這種關聯就建立起來,我們就會體會到其中的便捷。IDEA包含特定的索引結構,你可以想象一下,項目中文件的內容都存在著一定的關聯關系,最后形成一個很大的網來維護這些關聯。在IDEA啟動時,會花不少的時間來建立索引,來形成這些關聯關系,相信大家在打開項目都能體驗到這一點。如果建立這些關聯關系,有很多中實現方式,主要是PsiReference和ReferenceProvidersRegistry。 PsiReference顧名思義就是建立PsiElement直接的關聯,ReferenceProvidersRegistry則完成這些關聯的注冊和管理。關于這些方面的內容寫起來比較瑣碎,如果你實現了某一關聯,代碼提示、導航、重構等都能很好的工作,對你的工作效率提升有很大的幫助。
       前面介紹了基本原理,下面我們看一下如何實現常用的幾種關聯方式。我們前面講述了PsiReference,但是這里還有一個 PsiReferenceProvider要介紹一下,其實就是對PsiReference的一個封裝,因為 ReferenceProvidersRegistry進行注冊的時候,只接受PsiReferenceProvider。 PsiReferenceProvider很簡單,只需要將指定的PsiReference實例返回即可。 ReferenceProvidersRegistry的對象實例可以通過 ReferenceProvidersRegistry registry = ReferenceProvidersRegistry.getInstance(project); 接下來注冊的代碼可以在project component初始化的時候完成。下面讓我們進行幾個樣例吧:
       1. xml tag的屬性值和某一PsiElement進行關聯:這種情形很常見,如xmlTag的屬性值和java class關聯,xmlTag的屬性值和java class field關聯等等。如果實現這個關聯呢。首先我們要找到指定的屬性值,通常我們通過三個參數就可以確定: xml的namspace, tag名稱和屬性名,有了這三個值,我們就可以確定該屬性值,下面就是和某一reference provider關聯。代碼如下:
          String[] attributeNames=...;
          String tagName=...;
          NamespaceFilter namespaceFilter=...;
          PsiReferenceProvider referenceProvider=...; 
         registry.registerXmlAttributeValueReferenceProvider(attributeNames, new ScopeFilter(new ParentElementFilter(new AndFilter(new ClassFilter(XmlTag.class), new AndFilter(new OrFilter(new TextFilter(tagName)), namespaceFilter)), 2)), referenceProvider);
         如果你說,這個xml沒有namespace,你可能需要根據改xml的特征寫一個filter,完成定位的需要。你可以將上述的namespaceFilter替換為你需要的filter即可。
       2. xml tag的文本值和某一PsiElement關聯: 由于IDEA并沒有提供類似 registerXmlAttributeValueReferenceProvider這樣的函數,這樣xml tag的文本值就沒法通過直接api的方式進行。IDEA提供了這樣的一個方式: registry.registerReferenceProvider(filter, XmlTag.class, psiReferenceProvider); 你只需要設定filter即可。另外你可以通過registry.registerXmlTagReferenceProvider()也可以進行注冊。
      3. 字符串和某一PsiElement關聯:這種情形也很多,如context.getBean(""),和xml中的bean tag關聯,這里的字符串作為函數的參數,有了實際的意義,可能就會和某一個PsiElement關聯。這個關聯注冊很簡單, registry.registerReferenceProvider(filter, PsiLiteralExpression.class, psiReferenceProvider);  PsiLiteralExpression.class是文本的代表。這里要注意的是,一定要設置好filter,否則會很其他的代碼帶來問題。關于字符串和PsiElement關聯還要注意一點就是PsiReference的isSoft一定要設置為false,因為IDEA中字符串的默認提示是 property key,還有是classpath中的路徑,如果isSoft為false,那么其他的提示將不會出現。
       上面的三個是比較常見的。說到這里,因為是要建立關聯,必定涉及到更加字符串查找指定的PsiElement。在IDEA中我們可以通過 PsiManager,PsiShortNamesCache和PsiSearchHelper能完成不少的工作。如果還有問題的話,請參考 PsiElement和PsiRecursiveElementVisitor完成查找工作。

  • 基于xml的框架插件開發指南


         這一章節可以說有實際的意義,畢竟xml在各種框架中的應用還是畢竟廣的(盡管annotation已經替代部分xml的功能)。IntelliJ IDEA提供一個文檔主要介紹在IntelliJ IDEA下如何通過DOM方式進行xml操作(http://www.jetbrains.com/idea/documentation/dom-rp.html),這個章節可以說作為中文說明和補充。我不想就細節和大家溝通,主要說的是一個步驟:
        1. 創建各種Dom Element及其直接關系,這個在IDEA DOM的文檔中有描述。這里我們說一下,根節點需要繼承CommonDomModelRootElement,普通節點需要繼承 CommonDomModelElement,最好給每一個Dom Element創建對應的實現類,主要是為了擴展。對于實現類,根節點需要實現RootBaseImpl,普通節點實現BaseImpl。
        2. 首先我們創建一個DOM File Descriptor,進行Dom File注冊,讓IDEA能在某一類型的xml和Dom直接進行映射。DomFileDescription提供一個isMyFile()方法,可以幫助我們確定xml文件是否是Dom要求的。接下來在plugin.xml中進行聲明: <dom.fileDescription implementation="org.intellij.ibatis.IbatisConfigurationFileDescription"/>
        3. 如果有必要的話,給Dom Element創建各種converter;
        4. 創建DomModel和DomModelFactory:xml文件是提供基礎信息的基石,如果我們想訪問xml文件中的信息,可以通過統一的接口去訪問,這個就是DomModel和DomModelFactory。DomModel負責和Dom Element之間交互,對外提供服務。而DomModelFactory則創建DomModel,DomModelFacotory能夠處理各種情況,準確構建DomModel;
        5. 這樣我們就完成XML的基本處理。在實際的開發我們可能要參考這些類: DomManager, DomElement, DomUtil, 這些類都在com.intellij.util.xml包下,建議看一下。
        6. 總的來說,DOM的操作要比之間操作XmlFile和XmlTag要簡單很多,如果你的插件中牽涉到xml操作,考慮一下IDEA DOM是非常有必要的。

  • Custom Language Plugin


      寫這節的目的有兩點:1. 開發中可能需要各種語言; 2. IntelliJ IDEA中支持語言注入,你編寫的這一功能可能被應用到各種地方,提升效率。由于Custom Language Plugin牽涉到很多的內容,如果你對這方面感興趣,可以先參考一下 http://www.jetbrains.com/idea/plugins/developing_custom_language_plugins.html,了解一下基本原理和自定義語言插件的功能。這里還要說一下就是關于JFlex,通常你需要了解這個工具,它是詞法分析器,和IDEA能結合的很好,同時 IDEA也提供了JFlex Plugin,你可以進行JFlex相關的試驗。關于這部分內容,在后續我還會更新,主要是想通過一個具體的例子來說明。

  • 代碼提示相關


       如果你想在編輯窗口實現代碼提示,通常有三種方式: PsiReference,CompletionData和LookupManager,其中PsiReference,CompletionData是系統直接調用的,而LookupManager需要你手動觸發的。PsiReference前面已經介紹過,就是通過PsiElement之間相互關聯來進行的。CompletionData則是和某一種語言管理起來,在調用代碼提示時會觸發這段代碼,從而達到提示的目的。LookupManager就完全手動編碼方式,就是手動觸發一個代碼提示框,只不過LookupManager幫你做了很多,而不用關心很多的細節。

  • Inspection Action和Intention Action編寫注意事項


       在進行Inspection講解之前,讓我看一下Inpsection的結構:


    在此結構中,我們可以看到一個Inspection需要一個Visitor去訪問某一起始點下的各個子PsiElement,在遍歷各個 PsiElement的過程中,當發現問題時注冊問題描述(ProblemDescription),如果該問題有對應的QuickFix,則將該問題描述和QuickFix關聯起來。Inspection的機制如下:
       1. Inspection Manager調用某一Inspection來審查某一PsiElement
       2. Inpsection會調用visitor去訪問該PsiElement的各個子PsiElement
       3. 在訪問各個子PsiElement時,如果發現了問題,這創建對應的問題描述,如果該問題包含對應的QuickFix,則進行關聯
       4. 當用戶調用quickfix時,觸發quickfix的執行
    在實際的編碼中,我們看一下BaseInspection的結構:

    通常一個Inspection只會關注一種問題,基于這個原則,所有錯誤提示應該是一樣的,所以BaseInspection需要你提供一個統一的問題描述。對應同一個問題的解決方法,當然可能有一種或多種,所以BaseInspection提供了buildFix和BuildFixes,你只需要實現一個即可。最后是Visitor的創建,BaseInspection引入了BaseInspectionVisitor,顧名思義就是專門為 inspection做的的visitor,包含了針對Inspecitond的常用方法。Visitor同時包含ProlemsHolder和 Inspection對象,這樣在發現問題的時候,馬上可以將問題和該inspection對應的解決方法關聯起來。這里還有一個 InspectionGadgetsFix,這個類沒有太多的解釋,主要目的就是去做一些判定,是否可以進行QuickFix操作等。通過這種結構調整,流程和代碼就簡單了很多,你創建Inspection就容易多啦。
    同樣的原理可用在Intention Action上,在Intention Action中,首先通過一個predicate來進行匹配判斷,如果匹配后又專門的處理邏輯進行操作,完成intention action。

  • FAQ


    Q: 插件中的圖片大小有哪些要求?
    A: 在菜單欄中出現的圖片要是是16x16的png圖片;在設置面板中出現的圖片要求是48x48的png圖片。這些圖片通常都是要求透明的,如果你不知道怎么制作透明的png圖片的話,你可以通過ImageMagick提供的命令行行進行操作: >convert  -transparent white logo.png logo_1.png
    Q: 如何創建Live Template的自定義function?
    A: 在Open API里沒有涉及到這個方面,你需要參考一下Macro和MacroFactory,Macro是自定義函數的接口,MacroFactory完成注冊,你可以參考一下CapitalizeMacro的實現,在Web Service插件中也有例子。
    Q: 如何訪問剪切板?
    A:你可以通過CopyPasteManager進行訪問,下面是一個想剪切板添加內容的例子。
    CopyPasteManager copyPasteManager = CopyPasteManager.getInstance ();
    copyPasteManager.setContents (new StringSelection ("the character string which we would like to copy appointment"));

  • 參考資源


      這里我不想列出幾篇文檔,這些文檔只能起到掃盲的作用,如果你想開發插件,從掃盲版畢業是遠不夠的。個人的建議,多看別人的插件,多看論壇的帖子,多看API,多實踐。
      IntelliJ IDEA官方論壇: http://www.intellij.net/forums
      Jira for Open API:http://www.jetbrains.net/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10132&component=10366
      插件的源碼庫: http://svn.jetbrains.org/idea/Trunk
      現在不少項目都在Google Code上安家啦,你可以去Google Code上搜索關于IDEA的插件,然后了解他人的想法和代碼,對自己的幫助會很大。

  • 本站僅提供存儲服務,所有內容均由用戶發布,如發現有害或侵權內容,請點擊舉報
    打開APP,閱讀全文并永久保存 查看更多類似文章
    猜你喜歡
    類似文章
    intelliJ 默認.ignore
    Android Studio插件整理
    開發屬于自己的第一款 IDEA 插件!
    Tigase開發筆記
    Android Studio與其IntelliJ IDEA相比,其差異與利弊主要有哪些
    干貨|IntelliJ IDEA插件開發
    更多類似文章 >>
    生活服務
    分享 收藏 導長圖 關注 下載文章
    綁定賬號成功
    后續可登錄賬號暢享VIP特權!
    如果VIP功能使用有故障,
    可點擊這里聯系客服!

    聯系客服

    主站蜘蛛池模板: 绥中县| 盐池县| 石泉县| 巴南区| 车致| 额敏县| 双江| 健康| 阳信县| 府谷县| 沂南县| 红桥区| 北安市| 于都县| 北宁市| 定边县| 犍为县| 北票市| 黄大仙区| 鄂伦春自治旗| 广水市| 中江县| 嘉定区| 隆回县| 盘锦市| 扶绥县| 宁德市| 娄烦县| 钟山县| 奉节县| 顺昌县| 改则县| 扬州市| 麻栗坡县| 德令哈市| 乌鲁木齐市| 临西县| 望江县| 洞口县| 吴旗县| 临颍县|