在开发前Q首先我们要做一些环境准备,q样你可以快速进入这个场景。首先你要安装好IntelliJ IDEAQ这里我推荐大家使用IntelliJ IDEA 7.0 M2Q你可以通过http://www.jetbrains.com/idea下蝲。IntelliJ IDEA安装完毕后,你需要安装一个插件开发包Q主要用于IntelliJ IDEA的插件开发?a >下蝲地址和IntelliJ IDEA相同?q个开发包包含以下内容QIntelliJ IDEA的Open API和源码,以及一些插件的源码QIntelliJ IDEA的插件源码都?http://svn.jetbrains.org/idea/TrunkQ这些都是你~写插g的很好样例)。当然没有这个插件开发包你可以开发插Ӟ不过我强烈推?a >下蝲q个开发包。开发部下蝲完毕后,你需要将其解压到IntelliJ IDEA的安装目录下Q这样IntelliJ IDEA的SDK创徏会很单。这里要注意一点,插g开发包的版本最好和IntelliJ IDEA的版本相对应。关于IntelliJ IDEA的SDK准备Q你可以参考这文章:建立IntelliJ IDEA插g开发环?/a> ? q里要有一个注意事:默认的情况下Q新创徏的SDKq会idea.jar包含到classpath中,׃IntelliJ IDEA的Open API不能完全满你需要的功能Q你的插件可能会用到IDEA未公布的APIQ所以这里徏议你查一下idea.jar文g是否已经被包含,如果没有被包含,请加入这个jar文g? 目创徏后,我们需要添加一个plugin module。只需打开"File" -> "Add Module"然后选择"plugin module"可以啦Q请module的SDK讄Z面我们创建的IDEA SDK。到q里插g开发的目创建完毕啦Q你可以开发你的插件啦?
接下来我们启动IntelliJ IDEA来创Z个IntelliJ IDEA SDKQ这是开发插件的基础。启动IDEAQ打开讄面板Q选择”Project Settings"Q在弹出的对话框中安装下图进行IntelliJ IDEA SDK讄Q?/p>
IntelliJ IDEA的SDK已经被创ZQ它是开发插件的基础包。在q里我相对这些jar文gq行一些描qͼ已方便我们以后的代码~写Q毕竟我们代码是要引用其他的开发包的,如果IDEA已经提供Q我们就没有必要在找W三方的jar包啦。在$IDEA_HOME/lib下有以下文g需要提一下:
commons-codec-1.3.jarQ这个是q行~码转换的开发包Q是Apache Commons一个比较重要的开发包Q?http://commons.apache.org/codec
commons-collections.jarQ?Java Collection扩展框架Q对Collection处理更加方便Q也是Apache Commons一个比较重要的开发包Q?http://commons.apache.org/collections/
j2ee.jar和javaee.jarQJ2EE开发包Q这个不用说啦; http://java.sun.com/javaee/5/docs/api/
jdom.jarQXML开发包Q?http://www.jdom.org
trove4j.jar: java.util.Collections的一个实玎ͼ更加快速、轻量; http://trove4j.sourceforge.net/
velocity.jar: Velocity模板引擎Q?http://velocity.apache.org
xmlrpc-2.0.jar: xml-rpc的开发包Q?http://ws.apache.org/xmlrpc/
xstream.jar: xml/object映射框架Q?http://xstream.codehaus.org/
了解一下这些开发包Q可能对后箋的开发有不少的帮助?
下面p我们开始创Z个项目ƈq行插g开发。首先我们要创徏一个项目,q里个h有一个徏议:建立一个空的项目,然后d多个plugin moduleQ这样在一个项目中可以包含多个plugin moduleQ主要的好处是你可以添加一些参考的插gQ这对编写一个新的插件会帮助不少?
当你看到q一章节Ӟ你估计会骂我鸡婆。IoCQ这个还要你来告诉我Q我用SpringFramework已经很久啦。但我还是要说一下。IDEA整个lgl构是基于PicoContainerQ?a >http://www.picocontainer.org)的,PicoContainer是一个高效的嵌入式的DI容器。如果你有时间的话,我徏议你?分钟览一下PicoContainerQ然后回到这文档来?
PicoContainer是有层次l构的,是一个container可以包含子containerQ子容器可以讉K父容器中的组Ӟ而各个子容器直接是独立的。在IDEA中,主要有三UcontainerQApplication, Project和ModuleQ分别包含不同的lg。application container包含多个project containerQproject container可以包含多个module containerQ如下图Q?
q样各个project container是独立的Q都可以讉Ka(chn)pplication container中的lgQmodule container也是独立的,可以讉K所属project container和application container中的lg。这个图是我们后面理解application component, project component, module component和extension point{等的基?
PicoContainer的组件注入主要有两种方式:构造注入和Setter注入Q但是在IDEA中,目前Setter注入q不支持Q全部是构造注入,关于构造注入,PicoContainer推荐最好用一个构造函敎ͼq点也在IDEA中需要明。如果你的组仉要引用其他的lg?a >资源Q你最好在lg的构造函C指定QPicoContainer会帮助你完成资源引用和初始化?
IDEA的这些容器中包含些什么? 当然首先是各UcomponentQ还有就是一些服务,容器中不仅仅是componentQ还有相关ؓ(f)lg服务的资源,在后面我们也会涉及到对容器中服务资源的讲q。?
如果讉Kq些容器中的lgQ在IDEA中,讉Ka(chn)pplication container中的lg可以通过ApplicationManager.getInstance().getComponent(Class T)来进行。通用获得project对象后,你可以访问project容器中的lgQ获取module对象后,你可以访问module容器的组件。有了容器后Q如何能获取指定的组Ӟ有以下几U方式: 1. lgIDQ组件提供的lg标识W号Q可以通过标识W来讉K。如果组件没有标识符P我们UC为匿名组件?2. lg的interfacecR如果一个组件的是通过interface向外服务的,那么我们可以通过interface来获取对应的lg。如?interface的实Cؓ(f)多个lgQ就会获得多个组件?
如果让我的组件被注册到这些容器中Q?在IDEA中,有三U组Ӟ Application ComponentQ?Project Component和Module Component。不用的lg需要承不同接口,分别为Application ComponentQ?ProjectComponent和ModuleComponentQ如果你的组件承了某一接口Q将会自动放|到某一容器中,不需要你手动L册?
lg既然要交l容器去理Q这q涉到生命周期的概念,对于Application Component来说QinitComponent负责初始化,disposeComponent负责资源清理。对ProjectComponent来说Q除了initComponent和disposeComponentQ还增加了projectOpened 和projectClosedQ这个意思还比较Ҏ(gu)理解Q就是一个挂钩(hookQ。组件一旦被Ȁz,开始发挥它的作用啦?
lg的行为可能需要设|,如设|不同的参数lg的特性就不一栗如何给lg讄参数Q当然可以让lg自ndQ去找一个文Ӟ当文件修改后重新加蝲。在IDEA中,你不需要这么做Q你只需让组件承Configurable接口QIDEA会将在设|面板中d一个设|选项Q让你设|这个组件的参数Q当然包括运行期的,q个好像和JMX相像?:)
lg的参数设|完毕啦Q当容器关闭后,lg带的q些参数需要保存在一个地方,q样当容器重新启动后Q组件仍然能向以前一样工作,不然你又得重新设|一下。同L(fng)道理Q你可以自己讑֮逻辑Q保存到某一个地方,然后在加载v来。如果IDEA提供了一?JDOMExternalizable接口Q只要实现接口ƈd量的代码,IDEA׃完成component的参C存和d的Q务。在最新的 IDEA 7.0中,采取了另外一U保存机Ӟq个我们会在后面q行说明?
讲到q里Q你可能会问Q有没有一U方面来声明ComponentQ?q是有的Q那是extension point。extension point是组件的化方式,它的主要功能是数据信息扩展,它们不需要承component接口Q同时也没有lg标识W号Q只需要在 plugin.xml声明可以,在声明的时候你需要指名是何种cd的组件。下面会有更详细的介l?
PicoContainer是IDEA基础Q因为我们编写的lg都是由容器初始化的,而且lg直接的相互依赖也是有容器完成Q所有了解一下PicoContainerq是很有必要的,Ҏ(gu)件编写和IDEA的机刉非常有好处?/p>
前面讲解了一下extions pointQ这里想再细化一下。Extension point的主要作用是数据信息扩展和事件监听,也就是一个插件注册了某一extension pointQ其他插件可以通过extension point插g提供数据信息或触发事仉辑Q从而达到媄响上一插g中的lg的一些行为。最典型的就是gotoSymbolContributorQ我们在各个插g中通过gotoSymbolContributor的声明,提供插g自己的symbol信息lIDEAQ这样在按下 Ctrl+Shift+Alt+NӞ插g提供的symbol信息׃被提C出来,当然你可以利用这U机制实现其他功能,监听也是一U实现。从用户的角度来看,是在某些方面,原先的插件功能增加啦。那么如何声明一个extension point呢。这个很单,只要建立一个Java IntegeraceQ然后在plugin.xmlq行什么就可以啦,代码如下Q?
<extensionPoint name="resourceBundleManager" interface="com.intellij.lang.properties.psi.ResourceBundleManager" area="IDEA_PROJECT"/>
前面说过Qextension point是组件的化方式,q里的area是指lg的类型,如果不指定就是ApplicationComponentQIDEA_PROJECT表示 ProjectComponentQMODULE_PROJECT表示ModuleComponent。声明完成后Q我们需要在插g中访?extension point去获取数据,代码如下Q?
Object[] extensions = Extensions.getExtensions("plugin_id.testExtPoint");
q里字符串中的plugin_id表示plugin的idQ在xml文g中)QtestExtPoint是extension point的name。还有一U就是提供ExtensionPointNameQ这个可以参考一下Open APIQ也非常单。这里返回一个数l,因ؓ(f)可能多个其他插g使用该extension point插g提供数据。接下来是在其他插件应用该extension point啦,三个步骤Q?
1 首先依赖该插Ӟ <depends>reliant_plugin_id</depends>
2 创徏extension point的interface的实玎ͼjava~码卛_
3 extension point引用声明Qxmlns的值就是所依赖的plugin的id。代码如下:
<extensions xmlns="reliant_plugin_id">
<testExtPoint implementation="com.foobar.test.impl.Extender"/>
</extensions>
通过q种方式Q可以实现插件直接的数据供给Q提C原有插件的功能Q一个好的插Ӟ如果能定义好一下扩展点Q方便其他插件进行扩充,是非常有益的?
事实上IDEA核心?yu)提供了非常多的extension pointQ这里你可以扩展IDEA的功能。关于这些扩展点的元信息Q请参考:$IDEA_HOME/lib/resources.jar文g?META-INF/plugin.xml文g?/p>
我们应该q入正题啦。Plugin的主要功能扩展IDE的功能,前面我们讲述了IDEA整体l构是基于容器的Q那么要扩展IDEA的功能,唯一的方法就是想容器中添加组Ӟ新添加的lg包含自n的一些功能,同时和其他组件进行交互(修改一些参数和Ҏ(gu)等Q,影响其他lg的行为,从而达到功能的扩展目的。那么一个插件中Q应该会包含application component, project component和module component。由于还要和用户q行交换Q插件还提供了actionQ也是和用戯行交换的操作Q所以插件的主要内容是component?action。这里顺便还聊一句,component是由容器理的,那么action可不可以也由容器理呢?q样在action中引?component更加方ѝ目前actionq不是由容器理的,q个主要是由历史原因军_的,不少action的代码还不能转移到容器中理Q不q?IDEA正在做一些工作,怿以后action也可以由容器q行理?
下面我们要开始插件编写啦。首先我们要讑֮一下插件的基本信息。插仉要有一个唯一标识W,有一个版本号Q便于升U)q有是适用的IDEA版本。这三项应该说是必需的,其他是插g的额外信息,如描qͼchangeLogQ作者等{,在plugin.xml讑֮可以啦?
完成讑֮后,我们需要向插g中添加内容啦。创建Component和Action非常单,只要通过new group可以创建。图例如下:
q里我们可能q要啰嗦一下,是关于plugin的目录结构。插件开发包中有一个plugin structure的html文档Q已l讲q的非常清楚Q这里只是重复一下。一个plugin通常包含plugin.xmlQ相关的class和引用的W三方jar文g。如何组l这些文Ӟ我推荐以下的l构Q插件目录下的lib文g夹保存第三方jar文gQ如果没有引用第三方jarQ可以没有该目录Q,classes目录包含插g的代码,META-INF包含 plugin.xml文gQ结构如下:
Maven实际上已l成为Java目理的规范,当然q里我们也希望IDEA的插件开发也能通过Maven理h。Mavenq不难,但是针对 IDEA的插仉目主要有以下问题Q可能导致管理有一定的隑ֺQ?. IDEAq不是都使用Javacq行代码~译。如果你使用了IDEA的UI DesignerQ那么你得用Javac2才能~译q些代码Q?2. 开发插仉要的IDEA的jar文g在repo1.maven.org/maven2中没有,你可能需要自p定repository的位|;3. IDEA的插仉要装配,q个可能是一些web目Qjar目不具有的。基于这些原因,我想l一个相Ҏ(gu)准的pom.xml文g和插仉目的目录l构Q目录结构如下图Q?
q个目录和标准Maven目是一致的Q不q有一点就是我们将plugin.xml文g攄在src/main/resources目录下,最好Ş成这L(fng)标准Q这对后l的plugin打包分发有帮助?
回到pom.xml文gQ其实只需注意一下,IDEA插g开发需要的jar包都?http://mevenide.codehaus.org/m2-repository/, 所以我们需要设|一下项目的repository的位|。由于还使用了codehaus的一些Maven插gQ所以还有设|一下plugin repository的ؓ(f)位置。下面就是设|build pluginQ能保证插g目中的代码能被正确~译Q主要就是IDEA的UI Designer的文件编译,其他的和标准的Maven~译选项一致。接下来是讄dependencyQ由于插件开发需要的jar包不都包括?IDEA SDK(前面我们讲述q?Q所有这些dependency的scope讄为provided卛_。如果你引用的是IntelliJ IDEA本n的jar包,那可能还有注意一点:׃IDEA的jar包包括正式版本号和编译版本号Q所以你可能q有ldependency讄 classifierQ这个值就是IDEA的编译版本号Q一个典型的dependency声明如下Q?
<dependency>
<groupId>com.intellij.idea</groupId>
<artifactId>openapi</artifactId>
<version>7.0</version>
<scope>provided</scope>
<classifier>${idea_build_number}</classifier>
</dependency>
因ؓ(f)牉|到插件要发布出去Q所以我们还是需要设|一下如何将插g打包。在Maven中这UC为AssemblyQ是通过Assembly plugin完成。我们只需要创建Assembly的描q文Ӟ然后在设|一下Assembly插g的配|,最后咨询mvn assembly:assembly可以啦。一个IDEA插g目典型的Assembly的描q文件如下所C:
<?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中出现的artifactQ如commons-httpclient需要能在pom.xml中解析得刎ͼ只要commons- httpclient处于dependency中就可以Q,q样我们可以将IDEA的插件打包成zip文gQ这样就可以发布Q现在你只要登录到 http://plugins.intellij.netQ然后上传这个zip文gQ你的发布就完成啦。 ?
个h对TDDq是比较推崇的,所以在没有q行开发前Q还是先介绍一下如何进行测试。在com.intellij.testFramework包下包含各种 TestCaseQ你可以q行相关的单元测试。下面我们先看一下IDEA是如何运行TestCase的。IntelliJ IDEA在运行IDEA的TestCaseӞ会加载当前编辑的插gQ这样就会模拟出一个IDEAq行的真实环境,q样你就可以q行各种试。在实际的开发中Q我们经怼PsiFileQVirtualFile{Javac,在plugin的内容组l方面,也主要是ActionQInspection {,IDEA的test framework都提供了q些TestCaseQ很方便地测试这些类和组件。IdeaTestCase、LightIdeaTestCase?PsiTestCase以及InspectionTestCase{都提供很便L(fng)方式来测试你~写的代码。当然IDEAq没有提供非常全面的试Ҏ(gu)来测试Q何代码,q个可能对TestCase设计~写要求会比较高Q应该绝大多数情况下Q你要自p得怎样ȝ写Unit Test。对于新的插件开发h员,个hq是TestCase加Debug合ƈ使用Q毕竟这斚w的知识我们还是比较欠~点。参考一下别人编写的 TestCase可能会提升我们编写TestCase的水q뀂如何用IDEA test framework提供的TestCaseQ这个很单,只要l承响应的TestCaseQ然后编写代码就可以啦,没有MҎ(gu)的要求。不要害怕,~写几个TestCaseQ你有感觉啦。最后说一句,IDETalk插g的Unit Test写的不错Q大家可以参考一下,而且IDETalk的作者Kirill Maximov也写了不是关于IDEA下Unit Test的文章?/p>
1 开发一个读写文件的ActionQ?
IDEA的设计思\是多U程dU程写的模式Q而且在AnAction中是不能q行写操作的Q如果你要在Action中进行写操作Q你需要创Z?Runnable对象Q然后交l?ApplicationManager.getApplication().runWriteAction(runnable)L行?
2 Editor ActionQ?
editor action主要用于操作当前~辑H口中的内容Q通常需要给editor action讄一个EditorWriteActionHandler来完成对editor的操作。EditorModificationUtil提供了不方法,可以加快开发。如果向惌取光标处的PsiElement对象Q需要设|PsiFile?getElementByOffset(offsetQ来获取。如果你惌行相关的插入操作Q你可能需要创建指定的PsiElementQ这个时?PsiElementFactory可能会帮你不忙Q你可以参考一下PsiElementFactory APIQ看是否有你需要的东西?
3 Intention Action:
intention action单地说就是意图操作,IDEA会根据光标所在的位置Q进行相x查,然后提示可以q行相关的操作。intention action需要承IntentionActionc,需要提供family name(ID标识)和description(昄名称)。在IntentionActioncMQisAvailable判断当前Intention Action是否有效Qinvoke表示你选择该intention action后执行的动作。PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); q个语句用来获取当前光标处的PsiElement。intention actionq需要注意的一点,是我们要在源码根目录徏立一个intentionDescriptions的目录,然后在依据family name建立一个子目录Q最后添加三个文Ӟdescription.html、before.xxx.templates?after.xxx.templateQxxx可以为某一U类型文件的扩展名。如下图所C:
最后,我们需要将q个actionq行注册Qaction通常要归属于某一cd。注册有两种方式Q代码和声明。代码ؓ(f)QIntentionManager.getInstance().registerIntentionAndMetaData(new FirstIntentionAction(), "category"); 声明方式需要在plugin.xml中指定,代码如下Q?
<intentionAction>
<className>com.foobar.FirstIntentionAction</className>
<category>category</category>
</intentionAction>
4 Inspection action创徏
inspection action是代码审查actionQ它可以查发C码潜在的错误。如果你留意一下右下角的侦探头像,他就是代码审查员Q负责调用各U?inspection actionQ完成对代码的审查。如果发现潜在的问题Q就会给你一个解x案,你可以选择该方案进行问题修复。在IDEA讄面板中的“Errors”选项Q其实就是inspection action的集合,目前IDEA 7.0大概包含800+个inspection actionQ审查代码的各个斚wQ对代码质量的提升有很大的帮助?
a归正传,如果~写一个这L(fng)action。首先我们创Z个新的inspection actionQ它需要实现LocalInspectionToolcRInspection action需要提供short nameQID标识Q,display nameQ显C名Q和group display nameQ所在的l名Q? q样inspection action可以更好地显C。和Intention Action一P我们需要在源码目录下徏立一个inspectionDescription的目录,然后依据short name创徏一个html文档Q将该inspection的功能进行描q。图例如下:
LocalInspectionTool默认是检查Java文g的,如果你想让LocalInspectionTool审查其他文gQ你需要重?LocalInspectionToolcȝbuildVisitorҎ(gu)Q来审查特定的类型的PsiElementQ你可以参考一?LocalInpsectionTool的源码。前面我们讲到inspection action会提供一个解决问题的Ҏ(gu)Q在IDEA中这叫QuickFix。当我们查到一个错误时Q我们需要创Z个问题描qͼ如果有必要的话,需要创Z个quick fix来完成问题修复。注册一个问题,通常提供q四个参敎ͼ可能存在错误的PsiElement、警告别、描q和quickfix。这个可以通过 InspectionManagerq行审查问题创徏?
目前Q我们的Inspection Action已经完成啦,如何注册inspection actionQ?和intention action的注册一P有两U方式:代码和extension point。代码方式:需要创Z个application componentQ然后承InspectionToolProviderQ只要实现InspectionToolProvider?getInspectionClasses()Ҏ(gu)卛_?extension pointQ同intention action不一L(fng)是,我们要创Z个类Q承InspectionToolProviderQ这个类不必要?ApplicationComponentQ然后实现InspectionToolProvider的getInspectionClasses()Q最后在plugin.xml文g中进行声明,如下Q?
<inspectionToolProvider implementation="com.intellij.psi.css.CssInspectionsLoader"/>
从理Z来说QIDEA是将inspectionToolProvider最Z个application component对待?
5. Annotator创徏
annotator思义标注Q就是给相关的PsiElement加上标识Q这个标识涉及到高亮昄、装订栏Q装订栏d图标标识Q等{,annotator主要用于标识一些信息。编写annotatorQ我们需要创Z个类Q承AnnotatorQ然后根据psiElement来判断是否要lelementd标识Q主要是和Annotation打交到。最后,我们需要在plugin.xml中进行annotatorxQ如下:
<annotator language="JAVA" annotatorClass="cn.org.intellij.webx.ScreenAnnotator"/>
6. 讄lg属性和状态保?
前面我们讲过如何保存lg的状态,在这里我们讲qC下IDEA 7.0中的实现Ҏ(gu)Q可能有点不一栗想要设|一个组件的状态,你需要承ConfigurableQ这样在讄面板中就会出C个选项Q你可以q行相关的操作。接下来我们需要将讄的状态保存v来,q是我们需要创建另外一个类Q专门用于保存设|,我们可以UC为Settingsc,q个Settings c需要PersistentStateComponentc,负责信息的保存和d。信息的d是通过一个@State的annotation来设|的。到q里Q我们就可以理解啦,一个类用于接收参数讑֮Q一个类用于参数保存和读取,׃该类包含相关参数Q所以它?yu)是一个ServiceQ通常参数进行封装,对外提供服务Q代码很单。接下来是在plugin.xml中进行声明,我们可以参考一下Ruby插g中的例子Q?
<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的描qͼ
@State(
name = YourPluginConfiguration.COMPONENT_NAME,
storages = {@Storage(id = "your_id", file = "$PROJECT_FILE$")}
)
在这个例子中我们使用?PROJECT_FILE$宏,你可以根据组件的cd不同讄不同的宏Q如下:
- 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+Nd扄或symbolQ如果你x展各个功能,如在struts中,查找某一个action的声明,q个时候我们需要扩展这一功能。这个时候我们只需创徏一个类Q让其承ChooseByNameContributorQ实?ChooseByNameContributor的方法就可以。接下来是在plugin.xml中进行声明,可以参考Struts, Ruby的插Ӟ
<gotoSymbolContributor implementation="org.jetbrains.plugins.ruby.ruby.gotoByName.RubySymbolContributor"/>
在IDEA中,我想Ҏ(gu)本的操作可能是q三个在发挥作用Q这三者都可以Ҏ(gu)件的内容q行更改?
Virtual File是IDEA的统一文gpȝQ就向Java的IO一P我们可以UC为VFS(虚拟文gpȝ)。有了Virtual FileQ我们不在需要和传统的文件打交到Q取而代之的是VFS。我们对VFS的各U操作,会映到传统的文件系l上。IDEA中的所有和文g相关的操作都是通过Virtual Fileq行Q这些操作和传统的文件操作差不多Q不q更加简单?
Document其实是Virtual File的内容的字符序列Q所以对Document的各U操作都是基于普通文本的。Document的可操作的方法ƈ不多Q主要是Document是基于字W序列的Q操作v来难度有点大。事实上我们对Document的用也比较?yu),通常都是一些信息的单获取?
Psi Fileq个是结构化的文件内容呈玎ͼq样我们通过操作l构化的对象Q从而达到操作文件内容的目的。这些结构化的对象通常通过一U编E语a来体玎ͼ?IDEA中,是Java对象Q这h们操作就更加单。这里我不知道这个例子是否合适,JDom是用Java对象来体现xml文档Q这里PsiFile 是用Java语言来体现各U文件内宏V讲到这里可能大家还没有体验PsiFile的好处,我们举一个例子来说明。现在有一个Java文gQ那么我们就可以构徏一个PsiJavaFile对象Q通过该对象,我们可以了解该java文g的一些信息,如package 名称Qimport语句列表Q包含的class。假设我们获取了PsiJavaFile的一个PsiClass对象Q我们就可以了解该Javacȝ各种信息Q如名称、注释和包含的函数等{。在q里我们可以更改PsiClass的名U、注释等{,q些修改马上׃反映到文件的内容中。试想一下,如果q一切通过文本分析完成Q那是多么复杂的工作,有了Psi FileQ这一切就单啦Q操作对象比操作文g内容要可靠简单的多。关于PSIQ请参考一些IntelliJ IDEA的插件开发文档,同时我们推荐Psi Viewerq个插gQ你可以对IDEA处理内容做更好地理解。如果你惛_出好的插件的话,你需要对PSI有较q理解Q虽然我写的很少Q但是它的重要性却相当高?/p>
其实q节不知道如何写Q章节的名称也怪怪的。我们都知道IDEA的代码提C、代码导航和代码重构非常强大Q这背后的是什么样的数据结构来支撑q些Ҏ(gu)。在 IDEA中,通过一UReference机制可以很多的事情兌h。回C一节所说的Q在IDEA中,我们Ҏ(gu)件的内容操作Q通常都是通过 PsiFile完成的。PsiFile中最的元素是PsiElementQ由于PSI的设计合理,所以可以将PsiElementq行兌Q这样可以实现很多的Ҏ(gu)。D一个例子来说吧?<bean id="personManager" class="com.foobar.PersonManagerImpl"/>q是一个简单xml元素Q但是在q个云素中,我们知道class的属性?XmlAttributeValue, PsiElemnt的子c?是和Java Classc进行关联的。如果我们将XmlAttributeValue和PsiClass(JavacȝPsil构cd)兌hQ那么我们就可以实现代码提示Q导航和重构Q当cd更改会更新xml的属性|Q这个关联的q程是Reference的实现。我们通过特定的Reference这两者关联进行实玎ͼ然后q行注册声明Q那么我们这U关联就建立hQ我们就会体会到其中的便捗IDEA包含特定的烦引结构,你可以想象一下,目中文件的内容都存在着一定的兌关系Q最后Ş成一个很大的|来l护q些兌。在IDEA启动Ӟ会花不少的时间来建立索引Q来形成q些兌关系Q相信大家在打开目都能体验到这一炏V如果徏立这些关联关p,有很多中实现方式Q主要是PsiReference和ReferenceProvidersRegistry?PsiReference思义是建立PsiElement直接的关联,ReferenceProvidersRegistry则完成这些关联的注册和管理。关于这些方面的内容写v来比较琐,如果你实C某一兌Q代码提C、导航、重构等都能很好的工作,对你的工作效率提升有很大的帮助?
前面介绍了基本原理,下面我们看一下如何实现常用的几种兌方式。我们前面讲qCPsiReferenceQ但是这里还有一?PsiReferenceProvider要介l一下,其实是对PsiReference的一个封装,因ؓ(f) ReferenceProvidersRegistryq行注册的时候,只接受PsiReferenceProvider?PsiReferenceProvider很简单,只需要将指定的PsiReference实例q回卛_?ReferenceProvidersRegistry的对象实例可以通过 ReferenceProvidersRegistry registry = ReferenceProvidersRegistry.getInstance(project); 接下来注册的代码可以在project component初始化的时候完成。下面让我们q行几个样例吧:
1. xml tag的属性值和某一PsiElementq行兌Q这U情形很常见Q如xmlTag的属性值和java class兌QxmlTag的属性值和java class field兌{等。如果实现这个关联呢。首先我们要扑ֈ指定的属性|通常我们通过三个参数可以确定: xml的namspaceQ?tag名称和属性名Q有了这三个|我们可以确定该属性|下面是和某一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);
如果你说Q这个xml没有namespaceQ你可能需要根据改xml的特征写一个filterQ完成定位的需要。你可以上q的namespaceFilter替换Z需要的filter卛_?
2. xml tag的文本值和某一PsiElement兌Q?׃IDEAq没有提供类?registerXmlAttributeValueReferenceProviderq样的函敎ͼq样xml tag的文本值就没法通过直接api的方式进行。IDEA提供了这L(fng)一个方式: registry.registerReferenceProvider(filter, XmlTag.class, psiReferenceProvider); 你只需要设定filter卛_。另外你可以通过registry.registerXmlTagReferenceProvider()也可以进行注册?
3. 字符串和某一PsiElement兌Q这U情形也很多Q如context.getBean("")Q和xml中的bean tag兌Q这里的字符串作为函数的参数Q有了实际的意义Q可能就会和某一个PsiElement兌。这个关联注册很单, registry.registerReferenceProvider(filter, PsiLiteralExpression.class, psiReferenceProvider); PsiLiteralExpression.class是文本的代表。这里要注意的是Q一定要讄好filterQ否则会很其他的代码带来问题。关于字W串和PsiElement兌q要注意一点就是PsiReference的isSoft一定要讄为falseQ因为IDEA中字W串的默认提C是 property keyQ还有是classpath中的路径Q如果isSoft为falseQ那么其他的提示不会出现?
上面的三个是比较常见的。说到这里,因ؓ(f)是要建立兌Q必定涉及到更加字符串查找指定的PsiElement。在IDEA中我们可以通过 PsiManagerQPsiShortNamesCache和PsiSearchHelper能完成不的工作。如果还有问题的话,请参?PsiElement和PsiRecursiveElementVisitor完成查找工作?/p>
q一章节可以说有实际的意义,毕竟xml在各U框架中的应用还是毕竟广的(管annotation已经替代部分xml的功能)。IntelliJ IDEA提供一个文档主要介l在IntelliJ IDEA下如何通过DOM方式q行xml操作Q?a >http://www.jetbrains.com/idea/documentation/dom-rp.htmlQ,q个章节可以说作Z文说明和补充。我不想q节和大家沟通,主要说的是一个步骤:
1. 创徏各种Dom Element及其直接关系Q这个在IDEA DOM的文档中有描q。这里我们说一下,根节炚w要承CommonDomModelRootElementQ普通节炚w要?CommonDomModelElementQ最好给每一个Dom Element创徏对应的实现类Q主要是Z扩展。对于实现类Q根节点需要实现RootBaseImplQ普通节点实现BaseImpl?
2. 首先我们创徏一个DOM File DescriptorQ进行Dom File注册Q让IDEA能在某一cd的xml和Dom直接q行映射。DomFileDescription提供一个isMyFile()Ҏ(gu)Q可以帮助我们确定xml文g是否是Dom要求的。接下来在plugin.xml中进行声明: <dom.fileDescription implementation="org.intellij.ibatis.IbatisConfigurationFileDescription"/>
3. 如果有必要的话,lDom Element创徏各种converterQ?
4. 创徏DomModel和DomModelFactoryQxml文g是提供基信息的基矻I如果我们惌问xml文g中的信息Q可以通过l一的接口去讉KQ这个就是DomModel和DomModelFactory。DomModel负责和Dom Element之间交互Q对外提供服务。而DomModelFactory则创建DomModelQDomModelFacotory能够处理各种情况Q准构建DomModelQ?
5. q样我们完成XML的基本处理。在实际的开发我们可能要参考这些类: DomManager, DomElement, DomUtil, q些c都在com.intellij.util.xml包下Q徏议看一下?
6. ȝ来说QDOM的操作要比之间操作XmlFile和XmlTag要简单很多,如果你的插g中牵涉到xml操作Q考虑一下IDEA DOM是非常有必要的?/p>
写这节的目的有两点:1. 开发中可能需要各U语aQ?2. IntelliJ IDEA中支持语a注入Q你~写的这一功能可能被应用到各种地方Q提升效率。由于Custom Language Plugin牉|到很多的内容Q如果你对这斚w感兴,可以先参考一?http://www.jetbrains.com/idea/plugins/developing_custom_language_plugins.htmlQ了解一下基本原理和自定义语a插g的功能。这里还要说一下就是关于JFlexQ通常你需要了解这个工P它是词法分析器,和IDEA能结合的很好Q同?IDEA也提供了JFlex PluginQ你可以q行JFlex相关的试验。关于这部分内容Q在后箋我还会更斎ͼ主要是想通过一个具体的例子来说明?/p>
如果你想在编辑窗口实C码提C,通常有三U方式: PsiReferenceQCompletionData和LookupManagerQ其中PsiReferenceQCompletionData是系l直接调用的Q而LookupManager需要你手动触发的。PsiReference前面已经介绍q,是通过PsiElement之间怺兌来进行的。CompletionData则是和某一U语a理hQ在调用代码提示时会触发q段代码Q从而达到提C的目的。LookupManager完全手动编码方式,是手动触发一个代码提C框Q只不过LookupManager帮你做了很多Q而不用关心很多的l节?/p>
在进行Inspection讲解之前Q让我看一下Inpsection的结构:
在此l构中,我们可以看到一个Inspection需要一个Visitor去访问某一起始点下的各个子PsiElementQ在遍历各个 PsiElement的过E中Q当发现问题时注册问题描qͼProblemDescriptionQ,如果该问题有对应的QuickFixQ则该问题描述和QuickFix兌h。Inspection的机制如下:
1. Inspection Manager调用某一Inspection来审查某一PsiElement
2. Inpsection会调用visitor去访问该PsiElement的各个子PsiElement
3. 在访问各个子PsiElementӞ如果发现了问题,q创建对应的问题描述Q如果该问题包含对应的QuickFixQ则q行兌
4. 当用戯用quickfixӞ触发quickfix的执?
在实际的~码中,我们看一下BaseInspection的结构:
通常一个Inspection只会x一U问题,Zq个原则Q所有错误提C应该是一L(fng)Q所以BaseInspection需要你提供一个统一的问题描q。对应同一个问题的解决Ҏ(gu)Q当然可能有一U或多种Q所以BaseInspection提供了buildFix和BuildFixesQ你只需要实C个即可。最后是Visitor的创建,BaseInspection引入了BaseInspectionVisitorQ顾名思义是专门?inspection做的的visitorQ包含了针对Inspecitond的常用方法。Visitor同时包含ProlemsHolder?Inspection对象Q这样在发现问题的时候,马上可以问题和该inspection对应的解x法关联v来。这里还有一?InspectionGadgetsFixQ这个类没有太多的解释,主要目的是d一些判定,是否可以q行QuickFix操作{。通过q种l构调整Q流E和代码q单了很多Q你创徏Inspection容易多啦?
同样的原理可用在Intention Action上,在Intention Action中,首先通过一个predicate来进行匹配判断,如果匚w后又专门的处理逻辑q行操作Q完成intention action?/p>
Q: 插g中的囄大小有哪些要求?
A: 在菜单栏中出现的囄要是?6x16的png囄Q在讄面板中出现的囄要求?8x48的png囄。这些图片通常都是要求透明的,如果你不知道怎么制作透明的png囄的话Q你可以通过ImageMagick提供的命令行行进行操? >convert -transparent white logo.png logo_1.png
Q: 如何创徏Live Template的自定义functionQ?
A: 在Open API里没有涉及到q个斚wQ你需要参考一下Macro和MacroFactoryQMacro是自定义函数的接口,MacroFactory完成注册Q你可以参考一下CapitalizeMacro的实玎ͼ在Web Service插g中也有例子?
Q: 如何讉K剪切板?
AQ?/b>你可以通过CopyPasteManagerq行讉KQ下面是一个想剪切板添加内容的例子?
CopyPasteManager copyPasteManager = CopyPasteManager.getInstance ();
copyPasteManager.setContents (new StringSelection ("the character string which we would like to copy appointment"));
q里我不惛_出几文档,q些文档只能起到扫盲的作用,如果你想开发插Ӟ从扫盲版毕业是远不够的。个人的Q多看别人的插gQ多看论坛的帖子Q多看APIQ多实践?
IntelliJ IDEA官方论坛Q?http://www.intellij.net/forums
Jira for Open APIQ?a >http://www.jetbrains.net/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10132&component=10366
插g的源码库Q?http://svn.jetbrains.org/idea/Trunk
现在不少目都在Google Code上安家啦Q你可以去Google Code上搜索关于IDEA的插Ӟ然后了解他h的想法和代码Q对自己的帮助会很大?/p>
先看看输出结果:
在看看原来的JavaBeanQ?/p>
处理l果是o人满意的。实现过E如下:
-mixed参数是指定按JavaBean中的变量名生成属性,q有些其它的参数Q可以控制大写和连字符L(fng)。另外由于我用的实体是JDO增强q的class文gQ所以classpathq需要加上JDO实现的包?/p>
q行的结果有少无奈Q因为对于JavaBean中Listq样的容器字D늱型,无法让它识别出对象的cdQ只能生成类?lt;!ELEMENT pension ANY>q样的描qͼ如果在一个什么配|文件中可以讄的话那就更好了?/p>
另外q有一个DTD解析工具Q可以解析DTD文gQ目前还不知道有什么其它用途,使用如下Ҏ(gu)可以解析后输出控制台Q?/p>
java -classpath d:/policy38/lib/dtdparser121.jar com.wutka.dtd.Tokenize code.dtd
资源Q?/p>
http://www.wutka.com/download.html
警告Q?最后一个参C用了不准的变量cd?varargs Ҏ(gu)的非 varargs 调用Q?br />[javac] 对于 varargs 调用Q应使用 java.lang.Object
[javac] 对于?varargs 调用Q应使用 java.lang.Object[]Q这样也可以抑制此警?/font>
E序是一L(fng)Q在jdk1.4下可以编译通过Q但?.5׃行。上|查了一下,解决办法Q?/p>