开发手册

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

    • 开发场景

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

转载于:https://www.cnblogs.com/liqiking/p/6822616.html

开发手册目录 │ Addison Wesley - JDBC API Tutorial and Reference 3rd Edition (2003).chm │ ajax教程.chm │ AngularJS 中文API参考手册.chm │ Bootstrap-中文-API.chm │ css2.chm │ CSS4.0中文参考手册.chm │ cssv3.4.0.chm │ DHTML_DOC_CN.chm │ DHTML手册.chm │ dom4j.chm │ DOM_help.chm │ DOM中文参考手册CHchm.chm │ DOM文档对象模型手册.chm │ DTD.chm │ EasyUI-API+1.3.2.chm │ Ext2.2API中文版.CHM │ Ext3.2中文API.CHM │ Hibernate3.2.chm │ Hibernate3.2API.chm │ html5参考手册.chm │ HTML入门与提高.CHM │ Html标签一览表.chm │ html语法教程.chm │ HTTP1.1.chm │ J2EE_1.5_API.CHM │ J2EE_1.6_API.chm │ Java+EE+6+API+Specifications.CHM │ JavaEE_API_5[1].0.chm │ JavaScript Professional Projects.chm │ JavaScript20.chm │ JavaScript中文手册.CHM │ JavaScript手册.chm │ JavaScript语言中文参考手册.chm │ java_ee_api_中英文对照版.chm │ java_ee_api_中英文对照版.chw │ jBPM 4.4 API.chm │ jdk 1.7_api_doc.CHM │ JDK_API_1_6_zh_CN.CHM │ JDK_API_1_6_zh_CN.chw │ jquery1.7 中文手册.chm │ JQuery_1.4_API.CHM │ jQuery文档.chm │ JSP API.chm │ Jsp帮助文档.chm │ JSP语法.chm │ Linux基础命令教程豪华版.chm │ Linux常用命令大全.chm │ lucene_3.6.1_API.CHM │ MySQL_5.1_zh.chm │ POI_3.8_API.CHM │ Servlet API[China].chm │ Servlet-API.chm │ servlet.chm │ Spring-Reference_zh_CN.chm │ spring2.5.5_API.chm │ Spring3.0.2-RELEASE-API.chm │ Struts2.chm │ struts2中文教程.chm │ struts2标签.chm │ tomcat5.5中文帮助文档.chm │ W3CSchool .chm │ W3CSchool.chm │ w3school完整版.CHM │ WebGL自修教程.chm │ XML+Schema官方教程(9loong中文版)修正版2009.04.chm │ XmlSchema标准参考手册.chm │ XPathTutorial.chm │ 样式表中文手册.chm │ 网页制作完全手册.chm │ 英语资料大全.chm └─hadoop api
Addison Wesley - JDBC API Tutorial and Reference 3rd Edition (2003).chm ajax教程.chm AngularJS 中文API参考手册.chm Bootstrap-中文-API.chm css2.chm CSS4.0中文参考手册.chm cssv3.4.0.chm DHTML手册.chm dom4j.chm DOM_help.chm DOM中文参考手册CHchm.chm DOM文档对象模型手册.chm DTD.chm EasyUI-API+1.3.2.chm Ext2.2API中文版.CHM Ext3.2中文API.CHM FILESLIST111.TXT Hibernate3.2.chm html5参考手册.chm HTML入门与提高.CHM Html标签一览表.chm html语法教程.chm HTTP1.1.chm J2EE_1.5_API.CHM J2EE_1.6_API.chm Java+EE+6+API+Specifications.CHM JavaEE_API_5[1].0.chm JavaScript Professional Projects.chm JavaScript20.chm JavaScript中文手册.CHM JavaScript手册.chm JavaScript语言中文参考手册.chm java_ee_api_中英文对照版.chm jBPM 4.4 API.chm jdk 1.7_api_doc.CHM JDK_API_1_6_zh_CN.CHM jquery1.7 中文手册.chm JQuery_1.4_API.CHM jQuery文档.chm JSP API.chm Jsp帮助文档.chm JSP语法.chm Linux基础命令教程豪华版.chm Linux常用命令大全.chm lucene_3.6.1_API.CHM MySQL_5.1_zh.chm POI_3.8_API.CHM R.BAT Servlet API[China].chm Servlet-API.chm servlet.chm Spring-Reference_zh_CN.chm spring2.5.5_API.chm Spring3.0.2-RELEASE-API.chm Struts2.chm struts2中文教程.chm struts2标签.chm tomcat5.5中文帮助文档.chm W3CSchool.chm w3school完整版.CHM WebGL自修教程.chm XML+Schema官方教程(9loong中文版)修正版2009.04.chm XmlSchema标准参考手册.chm XPathTutorial.chm 样式表中文手册.chm 英语资料大全.chm
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值