开发手册

    • 当你看到这一章节时,你估计会骂我鸡婆。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

    • 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文件。java

    • 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就能够建立。图例以下:web

       

      这里咱们可能还要啰嗦一下,就是关于plugin的目录结构。插件开发包中有一个plugin structure的html文档,已经讲述的很是清楚,这里只是重复一下。一个plugin一般包含plugin.xml,相关的class和引用的第三方jar文件。如何组织这些文件,我推荐如下的结构:插件目录下的lib文件夹保存第三方jar文件(若是没有引用第三方jar,能够没有该目录),classes目录包含插件的代码,META-INF包含 plugin.xml文件,结构以下:apache

    • 使用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" 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文件,你的发布就完成啦。   编程

    • 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的文章。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"/>数据结构

    • 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"));

相关文章
相关标签/搜索