怎么找到一个好名字idea插件开发

VFS简介

虚拟文件系统(VFS)是一个Intellij Platform组件,它封装了大部分对活动文件的处理操作,为了达成以下目的:
– 提供一个处理文件的通用API,而不关心文件的具体位置(无论文件位于磁盘上、归档文件中还是HTTP服务器上)
– 追踪文件变化,并且在检测到文件内容发生更改时能提供新旧两个版本的文件
– 建立文件在VFS和持久化存储之间的关联

为了达到后两个目的,VFS为用户磁盘上的内容管理了一个持久化的快照,快照只存储那些通过VFS API访问过的和被异步更新后发生变化了的文件。快照是应用程序级别而不是工程级别的,也就是说,如果一个文件被多个项目引用,它的内容只会在快照中存在一个副本。所有的VFS访问操作都要经过快照。

如果通过VFS API访问文件时该文件不存在于快照中,VFS将从磁盘中加载该文件并存入快照,然后从快照返回数据。快照中只存储那些可被直接访问的信息,例如一个文本文件的内容会被全部存入快照,而另一些不可被直接访问的内容例如jar文件等,VFS只会存储它的元数据,例如文件名、文件尺寸和时间戳等。快照这一特性意味着IDE中显示的文件系统和文件内容可能并不总是能匹配磁盘上的实际内容。在IDE中,有一个观察者进程会从文件系统中接收到文件更改的通知并通知IDE进行刷新操作,刷新操作基于文件的时间戳,如果文件内容被更改而时间戳维持不变的话IDE将不会更新该文件的内容到快照。

获取VirtualFile

从IOFile获取

假设我们已经有一个位于磁盘上的io.java文件,要将其添加到VFS,可通过如下操作进行:
– 创建一个File实例File ioFile = new File("./io.java")
– 将文件从磁盘载入VFSVritualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioFile)
– 刷新VFSvirtualFile.refresh(false, true)

其中refresh方法的两个参数分别为是否进行异步刷新和是否进行递归刷新。通过以上代码逻辑即可获得IOFile在VFS中的实例。

使用FileChooser获取

SDK中为我们提供了文件选择组件,通过简单的步骤可以直接获得一个VirtualFile实例:
– 创建一个FileChooserDescriptor实例,FileChooserDescriptor封装了一组对需要选择的目标文件的特征描述
– 通过FileChooser.chooseFile()获取VirtualFile实例

从URL获取

VirtualFileManager类提供了findFileByUrl()refreshAndFindFileByUrl()方法让我们可以从URL获取VirtualFile实例

从VFS获取

IDE所有的文件访问都要通过VFS,上述几种方式最终都是将文件添加到VFS的快照之后再提供访问。当文件已经被快照管理时,可以通过FilenameIndex.getFilesByName()获取到VirtualFile实例。

写入内容到VirtualFile

正如Android不允许在UI线程中进行耗时操作一样,Intellij Platform也不允许在主线程中进行实时的文件写入,而需要通过一个异步任务来进行。

ApplicationManager.getApplication().invokeLater(new Runnable() { 
@Override
public void run() { new WriteCommandAction(project) { @Override protected void run(@NotNull Result result) throws Throwable { //writing to file } }.execute(); } }); 

上面是一个写入文件的示例,需要说明的是,WriteCommandAction也并不是实时写入的,如果有多个WriteCommandAction操作,可能会被合并到某一时间同时执行写操作,所以如果在一组WriteCommandAction中有对不同文件的写操作,而该文件在这组操作中是动态获取的,那么最好在写操作执行前先将WriteCommandAction与文件建立一个映射关系。

VFS就介绍到这里,下一篇文章将介绍如何操作工程中的目录、代码和其他资源文件

 

PSI简介

PSI(Program Structure Interface)是Intellij Platform中一个非常重要的概念,在IDE所管理的Project中,每个目录,Package,源代码和资源文件都会被抽象成相应的PSI对象。本文将以PsiDirectoryPsiJavaFileXmlFile为例介绍插件对文件目录、Java类和DOM对象的操作。

一些有用的方法

通用方法
  • FilenameIndex.getFilesByName()通过给定名称(不包含具体路径)搜索对应文件
  • ReferencesSearch.search()类似于IDE中的Find Usages操作
  • RefactoringFactory.createRename()重命名
  • FileContentUtil.reparseFiles()通过VirtualFile重建PSI
Java专用方法
  • ClassInheritorsSearch.search()搜索一个类的所有子类
  • JavaPsiFacade.findClass()通过类名查找类
  • PsiShortNamesCache.getInstance().getClassesByName()通过一个短名称(例如LogUtil)查找类
  • PsiClass.getSuperClass()查找一个类的直接父类
  • JavaPsiFacade.getInstance().findPackage()获取Java类所在的Package
  • OverridingMethodsSearch.search()查找被特定方法重写的方法

实例1

下面通过一个实例,使用PSI对象创建一个目录,并在目录中创建一个Java类。

创建目录
  1. 获取Project根目录
PsiDirectory baseDir = PsiDirectoryFactory.getInstance(project).createDirectory(project.getBaseDir());
  1. 递归查找要创建的目录
PsiDirectory subDir = baseDir.findSubdirectory(moduleName);
......
  1. 判断要创建的目录是否已存在
boolean isExist = subDir == null;
  1. 如不存在,创建新目录
if(!isExist) {
subDir = moduleDir.createSubdirectory(moduleName); 
}
创建Java类
  1. 创建PsiClass
PsiClass clazz = JavaDirectoryService.getInstance().createClass(subDir, className)
  1. 添加package字段
((PsiJavaFile) clazz.getContainingFile()).setPackageName(pkgName); 

package字段可通过文件路径解析或使用JavaPsiFacade获得。此处添加字段的操作必须在WriteCommandAction中异步进行。

实例2

接下来的实例演示如何操作DOM对象

操作从外部读入的DOM
  1. 获取XmlFile实例
    此处通过FileChooser引导用户从文件系统中选取一个xml文件
FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false); VirtualFile virtualFile = FileChooser.chooseFile(descriptor, project, null); if (virtualFile != null) { if (!virtualFile.isDirectory() && virtualFile.getName().endsWith("xml")) { xmlFile = (XmlFile) PsiManager.getInstance(project).findFile(virtualFile); } } 
  1. 遍历所有深度为1的Tag
XmlDocument document = xml.getDocument();
if (document != null) {
XmlTag rootTag = document.getRootTag(); if (rootTag != null) { XmlTag[] subTags = rootTag.getSubTags(); for (XmlTag tag : subTags) { //do something you want } } } 
生成XmlFile并写入文件
  1. 创建XmlFile
XmlFile xmlFile = (XmlFile) PsiFileFactory.getInstance(project).createFileFromText(xmlName, StdFileTypes.XML);
  1. 向XmlFile写入属性
XmlDocument document = xmlFile.getDocument();
if (document != null && document.getRootTag() != null) { XmlTag rootTag = document.getRootTag(); rootTag.getAttribute(attrName).setValue(attrValue);//set value for exists attr. rootTag.setAttribute(name,value);//add a new attr and setting value } 
  1. 写入文件
    写入操作同样需要在WriteCommandAction中异步进行。先判断目标文件是否已存在,如果存在则执行删除
PsiFile psiFile = directory.findFile(xmlName);
if (psiFile != null) {
psiFile.delete();
}directory.add(file);

PSI对象操作就介绍到这里,下篇将介绍PSI的进阶用法

 

本文是Intellij IDEA插件开发系列的最后一篇文章,将介绍PSI对象操作的进阶用法

使用模板

模板可以大大节省我们编写代码的工作量,在IDEA中,可以通过Preferences -> Editor -> File and Code Templates来查看和编辑已有的模板,或是添加新的模板。在模板中可以使用IDE提供的一些宏变量,常用的有NAMEPACKAGE_NAMEUSERDATE等,在新建模板时也可以使用自定义宏变量,自定义的宏变量将在创建文件时弹出一个输入框让用户对其赋值。

同样的,在插件开发中也可以使用文件模板,将公共部分代码进行抽离,减少创建PSI对象的复杂程度,下面将介绍模板的使用方法。

创建模板

在插件中模板文件必须存放于源文件目录的fileTemplates/internal目录内模板文件必须以${模板唯一名称}.${对应代码文件的扩展名}.ft命名,例如我们要创建一个java类的模板,命名可能为myTemplate.java.ft,模板文件内也可以使用宏变量,下面我们将创建一个java类通用的模板,包含package字段和类注释。

#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
/**
* Author: ${USER}
* Created on ${DATE}
*/
public class ${NAME} { } 
注册模板

在使用模板前必须先向插件系统注册改模板,编辑plugin.xml文件,在extensions节点加入模板注册信息。

<extensions defaultExtensionNs="com.intellij">
 <internalFileTemplate name="myTemplates"/> extensions> 

此处的模板名称必须是在IDE内唯一的名称,否则调用模板时会发生冲突。

使用模板

在创建JavaClass时可以调用该模板

PsiClass clazz = JavaDirectoryService.getInstance().createClass(directory, className, templateName);

以上就是模板使用的相关方法,so easy~

为PsiClass添加类导入、接口实现、成员变量和方法

生成PsiClass后我们可以为其添加一系列Java类的内容,下面介绍各种内容的添加方式,注意,所有的添加操作都必须异步进行。

添加类导入

要为PsiClass导入一个类,那我们必须搜索到这个类在哪,怎么搜索呢,分为两部曲
1. 创建SearchScope
SearchScope是个什么玩意呢,望文生义,它就是用来描述搜索范围的,在这里我们把搜索范围定义为整个Project,IDE会搜索Project内所有的类(包括引用的类库)来寻找目标。

GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
  1. 搜它丫的
    创建好SearchScope,使用它进行搜索
PsiClass[] psiClasses = PsiShortNamesCache.getInstance(project).getClassesByName(className, searchScope);

搜索完毕之后会返回一个PsiClass数组,包含所有类名匹配的PsiClass,接下来可以过滤出目标类并生成import,同样是两步
3. 生成PsiImportStatement

PsiImportStatement importStatement = psiElementFactory.createImportStatement(psiClass);
  1. 添加到PsiClass
((PsiJavaFile) clazz.getContainingFile()).getImportList().add(importStatement) 

齐活儿~

添加接口实现

添加接口实现同样需要先搜索接口类,搜索的套路还是和上面类导入的套路一样一样的,我们直接从生成接口字段PsiJavaCodeReferenceElement开始
1. 生成PsiJavaCodeReferenceElement

PsiJavaCodeReferenceElement ref = psiElementFactory.createClassReferenceElement(psiClasses);
  1. 添加到PsiClass
clazz.getImplementsList().add(ref);

需要一提的是添加接口实现声明之后接口类会被自动导入,是不是很Nice~

添加成员变量

有两个方式为PsiClass添加成员变量
1. 从字符串生成

PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiField field = factory.createFieldFromText("public int a = 0;", psiClass);

使用此种方式生成成员必须在字符串中显示指定成员的类型和修饰符,并且需要自行保证无语法错误。
2. 通过Factory生成

PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiField field = factory.createField("a",PsiType.INT);
field.getModifierList().setModifierProperty(PsiModifier.PUBLIC,true);

明显通过此种方式生成的Field更能保证代码质量,也更符合我们对代码进行面向对象操作的理念。

添加方法

Method同样有通过Text和通过Factory生成两种方式。
1. 通过字符串生成

PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiMethod method = factory.createMethodFromText("public String toString(){}",psiClass);
  1. 通过Factory生成
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiMethod method = factory.createField("getCount",PsiType.INT);
  1. 为Method添加注解
method.getModifierList().addAnnotation("Override")

同样的,可以为PsiMethod添加修饰符和方法体,添加方式和上文添加成员变量相同。

格式化代码
CodeStyleManager.getInstance(project).reformat(psiClass); 
在编辑器中打开
FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true);

注册自定义类型文件

在编写处理自定义语言或IDE未支持的文件类型时,需要自行注册文件类型到IDE,此处通过一个简单的例子来阐述。
SVG是基于XML,用于描述二维矢量图形的一种图形格式,在IDE中我们可以使用解析标准DOM的方式对其进行解析,通过两个简单步骤即可实现:
1. 添加自定义FileTypeFactory
此处需要新建一个继承了FileTypeFactory的类

public class SVGFileTypeFactory extends FileTypeFactory { @Override public void createFileTypes(FileTypeConsumer fileTypeConsumer) { fileTypeConsumer.consume(XmlFileType.INSTANCE,"svg"); } } 

2.注册FileTypeFactory
编辑plugin.xml文件,在extensions节点加入

<extensions defaultExtensionNs="com.intellij">
<fileTypeFactory implementation="com.guide.plugin.SVGFileTypeFactory"/> extensions> 

完成以上两个步骤之后就能在IDE中使用XmlFile来描述SVG文件。


Intellij IDEA 插件开发系列到这里就结束了,虽然这样属于黑科技的文章想必也不会有啥人看。

 

这一个系列的前面四篇文章对IntelliJ IDEA插件开发的一些基础和通用的方法做了介绍,本篇将会深入一步,从语言结构化支持的角度探究插件开发的相关技巧。
依据Wiki的介绍,编程语言是用来定义计算机程序的形式语言。它是一种被标准化的交流技巧,用来向计算机发出指令。编程语言的描述一般可以分为语法及语义。语法是说明编程语言中,哪些符号或文字的组合方式是正确的,语义则是对于编程的解释。
对语言中语法和语义的分析,就是编译原理里词法分析和语法分析的概念。本篇文章仅会简略介绍针对于完全未知的语言类型的支持,而将更多的重点放在对已有语言类型的继承和扩展支持上。
针对于插件开发者,推荐一个十分有用的插件PsiViewer,可以方便地在IDE内查看编辑器内的内容所对应的PsiElement类型,省去了很多查找文档的操作。

支持未知类型的语言

定义基础语言描述

IDE之所以能仅通过扩展名就能识别出源文件所使用的语言,就是因为IDE内定义的LanguageFileType为其提供了支持。

定义Language

简单继承Language类,在构造函数中传入语言ID和其他参数即可。

定义文件类型

继承LanguageFileType类,在实现的方法中分别返回源文件的名称、描述、默认扩展名和Icon即可。

定义FlieTypeFactory

继承FileTypeFactory类,在createFileTypes方法中注册语言,并在plugin.xml的extensions节点中通过fileTypeFactory标签注册Factory。

完成以上几步后IDE便能通过文件扩展名识别出源文件并为其应用图标。

定于词法和语法规范

定义Token和Element

此处定义的Token和Element分别对应于后续词法分析中的Token和IDE内部表示的PsiElement,继承IElementType,完成定义。

定义语法

IDEA支持通过BNF范式来定义语法,按照规则编写一个bnf文件,当语法被定义完成后,可以通过IDE生成PsiElement定义和PSI Parser。

定义Lexer

词法分析器定义了一个文件的内容是如何被分解为Token的,创建Lexer的一个简单的方式是使用JFlex。定义一个lexer规则文件然后通过IDE即可生成Lexer。
实现ParserDefinition接口,创建一个Parser定义,并在plugin.xml中的extensions节点中使用lang.parserDefinition标签注册该parser。

以上便是为语言做最基础支持的步骤,完成文件类型和语法、词法分析器注册后,IDE便能为自定义语言提供文件识别、关键字检查和语法检查等支持。下面结合对weex-language-support插件的开发过程,介绍对扩展了html语法的weex脚本所做的语法高亮、自动提示、Lint等其它一系列的支持的实现。

weex语言支持

识别weex脚本

由于weex脚本文件的结构和html类似,均包括

你可能感兴趣的:(怎么找到一个好名字idea插件开发)