當你看到這一章節時,你估計會罵我雞婆。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,以下圖:
css
這樣各個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的機制都很是有好處。html
前面講解了一下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文件。java
咱們應該進入正題啦。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就能夠建立。圖例以下:web
這裏咱們可能還要囉嗦一下,就是關於plugin的目錄結構。插件開發包中有一個plugin structure的html文檔,已經講述的很是清楚,這裏只是重複一下。一個plugin一般包含plugin.xml,相關的class和引用的第三方jar文件。如何組織這些文件,我推薦如下的結構:插件目錄下的lib文件夾保存第三方jar文件(若是沒有引用第三方jar,能夠沒有該目錄),classes目錄包含插件的代碼,META-INF包含 plugin.xml文件,結構以下:apache
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" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/xsd/assemblyhttp://maven.apache.org/xsd/assembly-1.1.0-SNAPSHOT.xsd">
<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文件,你的發佈就完成啦。 編程
我的對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的文章。api
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的功能進行描述。圖例以下:
ruby
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"/>數據結構
在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在各類框架中的應用仍是畢竟廣的(儘管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是很是有必要的。
寫這節的目的有兩點: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講解以前,讓我看一下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。
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"));