第一步、环境准备

1、IDEA安装Plugin DevKit插件

image-20240831090042073

2、开启IDEA内部模式(使用UI检查器定位代码)

image-20240831090642654

输入idea.is.internal=true开启内部模式,重启后生效。

3、gradle安装配置

gradle下载地址:https://gradle.org/releases/

注意:gradle版本需兼容IDEA的版本,否则构建会报错

版本对应查询:https://www.jetbrains.com/legal/third-party-software/?product=IIU&version=2023.1

image-20240831102225447

image-20240831102308993

解压gradle压缩包到指定路径,然后配置环境变量

image-20240831102646025

image-20240831102717383

idea中配置本地gradle

image-20240831102837756

第二步、项目实战

1、背景

日常开发过程中,我们鼠标双击会选中整个单词,现在想实现按照驼峰选中,效果如图

image-20240831120036366

当然,idea中有开关可以控制,但每次都进行设置不太方便,现在开发一个简单插件,实现当同时按下ctrl+alt+win键时,双击鼠标则是驼峰选中,释放时是单词选中

image-20240831120237778

2、原理

开启IDEA内部模式(使用UI检查器定位代码)后,我们可以通过 ctrl+alt+鼠标左键点击对应菜单,可以获取到点击信息,然后根据关键类信息,到idea源码中查找到对应的代码,在插件开发中通过调用对应的api就可以修改配置信息。

image-20240831130831811

3、新建插件项目

image-20240831121215144

4、编辑build.gradle.kts配置文件内容

plugins {
    id("java")
    id("org.jetbrains.kotlin.jvm") version "1.7.20"
    id("org.jetbrains.intellij") version "1.13.1"
}

group = "com.fufeng"
version = "1.0-SNAPSHOT"

repositories {
    // 这里修改为阿里云的仓库地址
    //    mavenCentral()
    maven {
        url = uri("https://maven.aliyun.com/repository/public")
    }
}

// Configure Gradle IntelliJ Plugin
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {

//    version.set("2022.2.4")
//    type.set("IC") // Target IDE Platform
    // 使用localPath设置本地idea的安装路径,这样在运行时会新打开一个程序用于测试插件
    localPath.set("D:\\develop Tools\\JetBrains\\IntelliJ IDEA 2023.1")

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

tasks {
    // Set the JVM compatibility versions
    withType<JavaCompile> {
        sourceCompatibility = "11"
        targetCompatibility = "11"
        // 解决编译时中文报错
        options.encoding = "UTF-8"
    }
    // 添加以下内容,解决运行时控制台中文乱码
    withType<JavaExec> {
        jvmArgs = listOf("-Dfile.encoding=UTF-8", "-Dsun.stdout.encoding=UTF-8", "-Dsun.stderr.encoding=UTF-8")
    }
    withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions.jvmTarget = "11"
    }

    patchPluginXml {
        sinceBuild.set("222")
        untilBuild.set("232.*")
    }

    signPlugin {
        certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
        privateKey.set(System.getenv("PRIVATE_KEY"))
        password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
    }

    publishPlugin {
        token.set(System.getenv("PUBLISH_TOKEN"))
    }
}

重新构建配置文件

image-20240831123005790

若出现这个报错,解决方式把项目修改为离线状态即可

image-20240831133929609

打开 plugin.xml 文件发现爆红,这是因为不能使用默认的模版字符,修改一下就可以了

<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
    <!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. -->
    <!-- 插件唯一id -->
    <id>com.fufeng.SelectByCamel</id>

    <!-- Public plugin name should be written in Title Case.
         Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
    <!-- 插件名称,大驼峰格式,不能有plugin字符串 -->
    <name>SelectByCamel</name>

    <!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
    <!-- 插件下载页面,作者相关信息 -->
    <vendor email="123@qq.com" url="https://www.yourcompany.com">YourCompany1</vendor>

    <!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager.
         Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag.
         Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description -->
    <description><![CDATA[插件功能描述,需要大于40个字符,支持html。。。。。。。。。。。。。。。。。。。。。。。。。。。。]]></description>

    <!-- Product and plugin compatibility requirements.
         Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
    <depends>com.intellij.modules.platform</depends>

    <!-- Extension points defined by the plugin.
         Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html -->
    <extensions defaultExtensionNs="com.intellij">

    </extensions>
</idea-plugin>

kotlin目录名修改为java。选中kotlin目录,按shift+f6修改目录名为java。不修改后面可能会报错。

image-20240831122645398

5、实现开关切换的两种方式

5.1、创建动作Action
  • idea中的action概念可以理解为idea中的任意一个动作,点击了某个按钮,就会执行它绑定的Action类的代码。
  • 使用Plugin DevKit 创建一个Action。创建成功之后就会生成一个Action类并在plugin中注册一个action

查询Tools菜单详情:

image-20240831132317527

插件Action

image-20240831123134849

image-20240831123430471

手动切换驼峰选中开关:SelectByCamelAction:

package com.fufeng.selectcamel;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;

public class SelectCamelWordsAction extends AnAction {

    @Override
    public void actionPerformed(AnActionEvent e) {
        System.out.println("hello plugin");

        // TODO: insert action logic here

        // 获取到编辑器设置
        EditorSettingsExternalizable editorSettingsExternalizable = EditorSettingsExternalizable.getInstance();
        // 对之前的状态取反
        editorSettingsExternalizable.setCamelWords(!editorSettingsExternalizable.isCamelWords());
    }
}

5.2、监听器实现

监听快捷键切换驼峰选中开关:MyKeyListener

在自定义的事件分发器中监听shift+ctrl+win按下,开启驼峰选中。win键抬起,关闭驼峰选中。

package com.fufeng.selectcamel;

import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.wm.IdeFrame;

import java.awt.*;
import java.awt.event.KeyEvent;

/**
 * @ClassName: MyKeyListener
 * @Description:
 * @Author: 扶风
 * @Date: 2024/8/31 11:13
 */
public class MyKeyListener implements ApplicationActivationListener {

    private final KeyEventDispatcher dispatcher = new MyKeyEventDispatcher();

    @Override
    public void applicationActivated(@NotNull IdeFrame ideFrame) {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(dispatcher);
    }

    @Override
    public void applicationDeactivated(@NotNull IdeFrame ideFrame) {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(dispatcher);
    }

    private static class MyKeyEventDispatcher implements KeyEventDispatcher {
        // 分发器逻辑
        private boolean shiftPressed = false;
        private boolean ctrlPressed = false;
        private boolean winPressed = false;

        private final EditorSettingsExternalizable editorSettingsExternalizable = EditorSettingsExternalizable.getInstance();

        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            int keyCode = e.getKeyCode();
            int id = e.getID();

            if (id == KeyEvent.KEY_PRESSED) {
                switch (keyCode) {
                    case KeyEvent.VK_SHIFT:
                        shiftPressed = true;
                        break;
                    case KeyEvent.VK_CONTROL:
                        ctrlPressed = true;
                        break;
                    case KeyEvent.VK_WINDOWS:
                        winPressed = true;
                        break;
                }

                if (shiftPressed && ctrlPressed && winPressed) {
                    // 开启驼峰选中
                    System.out.println("Shift + Ctrl + Win 按下!");
                    editorSettingsExternalizable.setCamelWords(true);
                }
            } else if (id == KeyEvent.KEY_RELEASED) {
                switch (keyCode) {
                    case KeyEvent.VK_WINDOWS:
                        if (shiftPressed && ctrlPressed) {
                            // 关闭驼峰选中
                            System.out.println("Win 键抬起!");
                            editorSettingsExternalizable.setCamelWords(false);
                        }
                        winPressed = false;
                        break;
                    case KeyEvent.VK_SHIFT:
                        shiftPressed = false;
                        break;
                    case KeyEvent.VK_CONTROL:
                        ctrlPressed = false;
                        break;
                }
            }
            return false; // 继续分发事件
        }
    }
}

在plugin.xml中注册应用激活时监听,并绑定到我们的监听器中。

    <applicationListeners>
        <listener class="com.fufeng.selectcamel.MyKeyListener" topic="com.intellij.openapi.application.ApplicationActivationListener"/>
    </applicationListeners>

至此开发完成!

第三步、打包导入

image-20240831132643104

构建完成后会生成一个jar包,在插件管理里面从本地导入这个jar即可使用

image-20240831134550774

idea版本兼容性问题

  • idea-version参考: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html#idea-plugin__idea-version
  • 内部版本号映射参考:https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html#earlier-versions
  • 修改build.gradle.kts中的patchPluginXml,它用来不是兼容的idea版本。该值会覆盖plugin.xml中的idea-versin标签,所有可以直接把兼容版本写在这里。
patchPluginXml {
    // 最低版本
    sinceBuild.set("232")
    // 最高版本
    untilBuild.set("242.*")
}
<idea-version since-build="232" until-build="242.*"/>

jetbrains.com/docs/intellij/build-number-ranges.html#earlier-versions

  • 修改build.gradle.kts中的patchPluginXml,它用来不是兼容的idea版本。该值会覆盖plugin.xml中的idea-versin标签,所有可以直接把兼容版本写在这里。
patchPluginXml {
    // 最低版本
    sinceBuild.set("232")
    // 最高版本
    untilBuild.set("242.*")
}
<idea-version since-build="232" until-build="242.*"/>

  • 要做到多版本兼容,就是要将最低版本设置的尽可能的小,但是同时需要你设置的版本有你插件当前使用的api。例如applicationListeners就在193.0之后发布,我的最低版本就只能设置193.0。最大版本不设置。
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐