Intellij platform本质是对不同的开发语言提供支持,举例来说我们也可以用notebook.app来开发java代码,但效率上可能没有可比性。因为Intellij idea提供了很多语言特定功能(例如语法高亮显示和代码分析)。很多插件本质上都是效率插件,围绕编程语言或环境而设。
在Intellij体系中在实现这类高级功能其核心是PSI。PSI比较复杂,如果同学们系统学习过JAVA-AST可能会有直观的感觉,PSI可以说有过之而无不及,相应的功能也更强大。掌握PSI的最好方法就是从头创造一个新的语言,所以从本章开始笔者会用大量实验带大家一起开发一个属于自己的编程语言。
建议安装并启用Grammar-Kit(PSI生成器)和PsiViewer(PSI查看器)这两个插件
一、语言和文件类型
IntelliJ IDEA 维护一个文件类型列表,每个文件类型将一种语言服务与一个或多个文件名模式相关联。开发自定义语言插件的第一步是注册与该语言关联的文件类型。IDE 通常通过查看文件名或扩展名来确定文件的类型。自定义语言文件类型是派生自LanguageFileType的类,它将Language子类传递给其基类构造函数。
1、注册LanguageType
LanguageFileType通过com.intellij.fileType扩展点注册。同时要注意配置中的属性language与LanguageFileType.getLanguage()值相同,属性name值需要与FileType.getName()的值相同。比如以下示例代码:
<fileType
name="Properties"
language="Properties"
extensions="properties"
fieldName="INSTANCE"
implementationClass="com.intellij.lang.properties.PropertiesFileType"/>
其它属性说明:
- extensions: 文件扩展名,格式为分号分隔的扩展名列表,没有
.
前缀; - fileNames/fileNamesCaseInsensitive:硬编码文件名,分号分隔的(不区分大小写)文件名列表;
- patterns:文件名模式规则,以分号分隔的模式列表 (
*
and?
),比如*.java; - hashBangs:分号分隔的 hash bang 模式列表,比如#!*java*;
public final class PropertiesFileType extends LanguageFileType {
public static final LanguageFileType INSTANCE = new PropertiesFileType();
public static final String DEFAULT_EXTENSION = "properties";
public static final String DOT_DEFAULT_EXTENSION = "."+DEFAULT_EXTENSION;
public static final Charset PROPERTIES_DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
private PropertiesFileType() {
super(PropertiesLanguage.INSTANCE);
}
@Override
public @NotNull String getName() {
return "Properties";
}
@Override
public @NotNull String getDescription() {
return PropertiesBundle.message("filetype.properties.description");
}
@Override
public @NotNull String getDefaultExtension() {
return DEFAULT_EXTENSION;
}
@Override
public Icon getIcon() {
return IconManager.getInstance().getPlatformIcon(PlatformIcons.PropertiesFileType);
}
@Override
public String getCharset(@NotNull VirtualFile file, byte @NotNull [] content) {
Charset charset = EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file);
if (charset == null) {
charset = PROPERTIES_DEFAULT_CHARSET;
}
if (EncodingRegistry.getInstance().isNative2Ascii(file)) {
charset = Native2AsciiCharset.wrap(charset);
}
return charset.name();
}
}
二、实现示例
1、定义Language
public class SimpleLanguage extends Language {
public static final SimpleLanguage INSTANCE = new SimpleLanguage();
private SimpleLanguage() {
super("Simple");
}
}
2、定义Icon
图标文件放在了/resource/icons/jar-gray.png下面
public class SimpleIcons {
public static final Icon FILE = IconLoader.getIcon("/icons/jar-gray.png", SimpleIcons.class);
}
3、定义FileType
public class SimpleFileType extends LanguageFileType {
public static final SimpleFileType INSTANCE = new SimpleFileType();
private SimpleFileType() {
super(SimpleLanguage.INSTANCE);
}
@NotNull
@Override
public String getName() {
return "Simple File";
}
@NotNull
@Override
public String getDescription() {
return "Simple language file";
}
@NotNull
@Override
public String getDefaultExtension() {
return "simple";
}
@Nullable
@Override
public Icon getIcon() {
return SimpleIcons.FILE;
}
}
4、注册FileType
extensions属性设置了文件后续,但配置时不需要配置.,即.simple会简写成simple。同时需要注意这个配置中的相关值要与类中的相关值一样。
<extensions defaultExtensionNs="com.intellij">
<fileType
name="Simple File"
implementationClass="org.intellij.sdk.language.SimpleFileType"
fieldName="INSTANCE"
language="Simple"
extensions="simple"/>
</extensions>
5、测试运行
要验证文件类型是否已正确注册,可以运行插件并验证是否可以正确显示LanguageFileType.getIcon()方法设置的图标,查看位置Settings | Editor | File Types:
第二种验证方式是可以在 File | New | file(因暂时还没实现把新语言添加到菜单中),创建一个以.simple为后缀的文件,会显示如下图所示: