IDEA插件开发心得

环境搭建

可参考:

https://github.com/yangfeng20/idea-plugin-dev-guide?tab=readme-ov-file

下面是执行上述过程中记录的一些问题。

gradle-bin下载失败

默认是从官方地址下载:

https\://services.gradle.org/distributions/gradle-8.2-bin.zip

可以改为国内镜像地址,修改gradle-wrapper.properties文件:

# old
# distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
# now
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip

org.jetbrains.intellij.gradle.plugin下载失败

报错信息:

Plugin Repositories (could not resolve plugin artifact 'org.jetbrains.intellij:org.jetbrains.intellij.gradle.plugin:1.17.3')

修改settings.gradle.kts,设置插件仓为国内镜像:

pluginManagement {
    repositories {
        maven("https://maven.aliyun.com/repository/gradle-plugin")
        maven("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/")
        gradlePluginPortal()
    }
}

Caused by: java.lang.NullPointerException: getHeaderField(“Location“) must not be null错误

网上的解决方法:启用离线模式运行,toggle offline mode。

但我把IDEA从2023.2.4升级到2024.2.0.1就没这个问题了。

Unable to load class ‘com.intellij.ant.InstrumentIdeaExtensions’.错误

暂时无法解决,网上说是把org.jetbrains.intellij升级到1.17就行,但我的就是1.17.3,始终不行。

后续把IDEA从2023.2.4升级到2024.2.0.1就没这个问题了。

如何查看IDEA和gradle的版本配套关系

https://www.jetbrains.com/legal/third-party-software/?product=iic&version=2023.2.4

IDEA与jdk的关系

2024.2+用jdk21,之前的用jdk17或jdk19。

避免下载gradle-8.X-src.zip

有时候,新工程会自动下载gradle src包,可启用离线模式运行:toggle offline mode来解决。

插件开发

开发具体功能的插件,取决于需求,这里不赘述。IDEA插件的gradle配置是支持java编译的,不一定非要用kotlin开发。

plugin.xml配置

插件需要配置plugin.xml,下面是一个例子:

        <action id="HelloWorldAction"
                        class="com.lee.idea_plugin_supplier.HelloWorldAction"
                        text="Hello World"
                        description="Show a greeting notification">
                    <add-to-group group-id="ToolsMenu" anchor="first"/>
                </action>

group-id是具体要添加到的主菜单标识,上例是Tools菜单。

单元测试

我所使用的gradle8.10,似乎只能支持junit4,支持不了junit5,一跑junit5就报错。所以只能引入junit4的依赖:

dependencies {
    ...

//    testImplementation("org.junit.jupiter:junit-jupiter-api:5.11.4")
//    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.4")
//    testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4")
    testCompileOnly("junit:junit:4.13.2")
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.12.0")
}

同时增加test任务:

tasks {
    named<Test>("test") {
        useJUnitPlatform()
    }
    ...
}

识别lombok

要在build.gradle.kts里增加依赖、注解处理器和io.freefair.lombok插件:

plugins {
    ...
    id("io.freefair.lombok") version "8.14"
}

dependencies {
    ...

    compileOnly("org.projectlombok:lombok:1.18.36")
    annotationProcessor("org.projectlombok:lombok:1.18.30")
}

若还有问题,在build.gradle.kts的tasks里增加annotationProcessorGeneratedSourcesDirectory(我这边未加也ok):

tasks {
    ...
    
    withType<JavaCompile> {
        ...
        options.annotationProcessorGeneratedSourcesDirectory =
            file("$buildDir/generated/sources/annotationProcessor/java/main")
    }
}

打印日志

用idea提供的日志类:

import com.intellij.openapi.diagnostic.Logger;

public class MyPlugin {

    private static final Logger LOG = Logger.getInstance(MyPlugin.class);
    
    private int runNativeCmd(String cmd, @NotNull ProgressIndicator indicator) throws Exception 	{
        if (Strings.isNullOrEmpty(cmd)) {
            LOG.warn("cmd is null,skip");
            return 0;
        }
        ...
    }
        

日志会打印到idea.log文件里,如果是开发阶段,会打印到沙箱日志里,路径为:

<你的开发目录>/build/idea-sandbox/system/log/idea.log

如果是实际运行时,windows会打印到

C:\Users\<用户名>\AppData\Roaming\JetBrains\<product><version>\log

linux会打印到:

~/.config/JetBrains/<product><version>/log

gradle相关知识

gradle kotlin dsl

同ruby一样,kotlin有一些有趣的语法糖,从而支持像写dsl那样写kotlin代码。下面是这方面的一些介绍。

  1. 当 lambda 表达式是函数的最后一个参数时,需将其放在大括号里调用。比如:
plugins {
    id("java")
}

这里的plugins就是一个kotlin函数,它的定义如下:

public final fun plugins(block: org.gradle.kotlin.dsl.PluginDependenciesSpecScope.() -> kotlin.Unit): kotlin.Unit { /* compiled code */ }

它只接受一个lambda参数,此处是id(“java”),放在大括号里。

  1. 赋值语句等于setter:
maven {
        url = uri("xxx")
    }

这里,url = xxx,实际上等价于调用maven.setUrl(xxx)

gradle task

一些常用的gradle task如下,它们都是Task类实例:

...
> Task :jar UP-TO-DATE
> Task :instrumentedJar UP-TO-DATE
> Task :prepareTestingSandbox UP-TO-DATE
> Task :prepareSandbox UP-TO-DATE
> Task :buildSearchableOptions UP-TO-DATE
> Task :jarSearchableOptions UP-TO-DATE
> Task :buildPlugin UP-TO-DATE
> Task :assemble UP-TO-DATE
> Task :test UP-TO-DATE
> Task :check UP-TO-DATE
> Task :build UP-TO-DATE

像buildPlugin就是对IDEA插件进行zip打包的task,buildPlugin是任务名,BuildPluginTask是其任务类型,该类型继承自Zip任务类型。要查看所有任务的名称及类型,可在build.gradle.kts里这么写:

tasks.forEach { task ->
    println("${task.name} -> ${task.javaClass.simpleName}")
}

强行指定依赖版本

比如依赖路径里有commons-io的多个版本,可以在build.gradle.kts里强行指定一个版本2.16.1,这样所有依赖路径的版本都会指向2.16.1

dependencies {
    ...
    implementation("commons-io:commons-io:2.16.1")

指定包为provided

使用compileOnly函数,等价于maven的provided:

compileOnly("org.slf4j:slf4j-api:1.7.36")

排掉某个依赖

例如排掉某个包里的slf4j依赖:

implementation("com.alibaba:easyexcel:4.0.3") {
        exclude(group = "org.slf4j", module = "slf4j-api")
    }

插件打包发布

idea插件最后会打包为一个zip,安装到IDEA时,会解压到IDEA的插件目录下,这个目录在windows下一般是:

C:\Users\<用户名>\AppData\Roaming\JetBrains\<product><version>\plugins

zip内只有一个lib目录,包含所有的jar包。如果不希望把三方文件放到jar包里,比如我有一个a.exe,想放到与lib平齐的bin目录,最后再统一打包为一个zip,可以这么做:

named<BuildPluginTask>("buildPlugin") {
        // 添加自定义文件夹到zip文件的bin目录下
        from("../../custom") {
            into("bin")
        }
    }

如前所述, buildPlugin是一个Zip类任务,可以在配置里新增from项,额外将某个custom目录复制到zip的bin目录里。注意:这里的配置项是增量的,不影响原有的zip打包。

IDEA API

一些基本概念需要了解。

VirtualFile和PsiFile类

  • VirtualFile(应用级):
    • 适用于需要对文件进行低级操作的场景,如读取文件内容、重命名文件等。
    • 适用于处理文件系统级别的操作,而不涉及文件的具体内容。
  • PsiFile(项目级):
    • 适用于需要对文件内容进行高级操作的场景,如代码分析、导航、重构等。
    • 适用于处理特定编程语言的文件,如 Java 文件或 XML 文件。

总结来说,VirtualFile 更适合处理文件系统级别的操作,而 PsiFile 更适合处理文件内容的高级操作。在开发插件时,根据具体需求选择合适的文件表示方式。

开发问题

不同IDEA版本间的兼容性

这个是需要逐个版本的测试的,可在build.gradle.kts里替换版本号,加以测试:

intellij {
    version.set("2023.2.4") //替换这里的版本号
    type.set("IC") // Target IDE Platform

    plugins.set(listOf(/* Plugin Dependencies */))
}

若不同的IDEA版本间有兼容性问题,可尝试调整三方依赖的版本来解决。

LinkageError错误

运行插件时报这样的异常:

java.lang.LinkageError: loader constraint violation: when resolving method 'org.slf4j.ILoggerFactory org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()' the class loader com.intellij.ide.plugins.cl.PluginClassLoader @763963c1 of the current class, org/slf4j/LoggerFactory, and the class loader com.intellij.util.lang.PathClassLoader @2ac1fdc4 for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature (org.slf4j.LoggerFactory is in unnamed module of loader com.intellij.ide.plugins.cl.PluginClassLoader @763963c1, parent loader 'bootstrap'; org.slf4j.impl.StaticLoggerBinder is in unnamed module of loader com.intellij.util.lang.PathClassLoader @2ac1fdc4)
	at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:423)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
	at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
	at com.alibaba.excel.ExcelReader.<clinit>(ExcelReader.java:21)

当同一个类文件被不同类加载器加载到内存中生成的类实例发生交互时,就会出现“LinkageError”异常。

这里牵涉到两个类加载器:PathClassLoader和PluginClassLoader,前者用于加载IDEA核心类和JDK通用类,后者用于加载单个插件的类,以做到插件间、插件与IDEA间相互隔离。

解决方法是将我们自己插件里引入的slf4j-api包排掉(参考https://bigzuo.github.io/2017/03/19/java-LinkageError-loader-constraint-violation-error/ )但有一点我不太明白,既然PluginClassLoader的目的就是为了插件与IDEA核心类的隔离,为何又出现两个加载器加载的同一个类一起运行的情况呢?

### 使用 IntelliJ IDEA 提升开发效率的方法 #### 掌握高效使用技巧 为了最大化利用 IntelliJ IDEA 的潜力,开发者应深入学习并实践该 IDE 中的各种高级特性。这包括但不限于快捷键的熟练运用、项目结构的有效管理以及调试工具的灵活应用[^1]。 #### 安装合适插件增强功能 IntelliJ IDEA 支持广泛的第三方插件,这些插件能够极大地扩展其基础能力。例如: - **版本控制系统集成**:Git、SVN 等插件让源码管理和协作更加顺畅。 - **构建工具支持**:Maven 和 Gradle 插件简化了项目的依赖管理和自动化构建过程。 - **代码质量和风格检查器**:像阿里巴巴 Java 编程指南这样的插件可以帮助团队成员保持一致的编码标准,减少潜在错误的发生几率[^3]。 ```xml <!-- Maven pom.xml 文件片段 --> <build> <plugins> <!-- 添加阿里云 CodeStyle 插件配置 --> <plugin> <groupId>com.alibaba</groupId> <artifactId>p3c-maven-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <goals> <goal>checkstyle</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` #### 利用内置特性和外部资源相结合 除了上述提到的功能外,还可以探索更多官方文档和技术社区分享的最佳实践经验来进一步优化个人的工作流。比如定期查看 JetBrains 官方博客了解最新更新;加入相关论坛交流心得等都是不错的选择[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值