掘金 后端 ( ) • 2024-04-21 17:54

theme: nico

好像许久没有在掘金更新过文章了。以前年中的总结的时候说好的大量输出文章也因为后面忙碌的工作耽误了。

最近也在学习一些开发IDEA的插件,看了下小傅哥的文章也受益匪浅,写程序这东西不能只看不练,正如所说脑子会了手还不会。最近也花了2个小时的时间写了一个我认为在开发的时候还可以提升效率的小插件,大家喜欢的话可以直接去IDEA的插件市场里面搜索XTC去下载。

想法

虽然我们现在的开始也一直在使用若依框架,但是有的时候因为需求的变更需要在数据库中加几个字段(数据库中的字段往往是使用的下划线命名方式,代码中一般使用的是驼峰)这个时候如果把字段名复制到代码中还得手动去修改字段名成驼峰的方式,这样子会比较麻烦,所以我就开发了一个下划线转驼峰的插件小程序。

tt_cE48cHnKW4.gif

安装方式

  • 你可以直接在IDEA中的Plugins中搜索XTC然后安装。
  • 也可以通过源码的方式自已打包后安装生成的jar包
  • 也可以去我的GitHub上下载发布的jar直接安装

在IDEA的Plugins中搜索的时候可能会因为IDEA版本的差异搜索不到的情况。插件本身是所有的IDEA版本都支持的。这样的话就可能你去下载jar包或源码自已编辑成Jar安装。

插件主页:https://plugins.jetbrains.com/plugin/24190-xtc

可以点击主页中的Source Code去我的GitHub下载

插件源码解读

对于IDEA的这种小插件的开发还是很简单的,前提是你需要有一个逻辑清晰的思路。插件开发的过程主要是继承一些IDEA本身自有的类做出扩展。然后在配置下你这做的这个功能显示到什么位置。

梳理插件的逻辑

  • 即然是做的一个粘贴的功能,这个功能的入口肯定是显示在打开文件的编辑窗口中,并且这个功能需要挂在右键菜单上。
  • 因为本插件的功能是把复制的下划线命名方式转换成驼峰的命名方式。所以这个菜单项的禁用与否是根据剪切版中是否有文本型的内容并且这个文本型的内容还得是下划线的命名方式才会启用
  • 粘贴的时候也不是将文本随便的粘贴,是需要把文本粘贴到当前光标的位置。并且粘贴完成后需要将光标移动到文本的末尾的位置方便后续的输入。

显示菜单及点击功能

在IDEA的插件的开发中,是通过resources/META-INF中的plugin.xml来配置显示在什么位置的。

先来看下plugin.xml的配置文件。

功能的点击事件的类是继承自AnAction,所以在这个配置文件中需要在action标签中来做出配置

<actions>
    <action id="PasteHandlerAction" class="com.cc.tools.paste.action.PasteHandlerAction" text="粘贴转驼峰" icon="/img/z_icon.jpg">
        <add-to-group group-id="EditorPopupMenu" anchor="first"/>
        <keyboard-shortcut first-keystroke="control K" keymap="$default"/>
    </action>
</actions>

这个里面的配置也是比较简单的。

  • 在action的属性中需要指定一个id,这个id想必就不用多说。
  • 还需要指定一个点击了这个菜单后回调哪一个类这个是放在class的属性中指定的。
  • 还需要一个菜单上显示的文字。包插一个icon这个icon并不是必须的。

这些的属性指定了,后面就须要想一下这个菜单应该显示在哪里。我需要将他显示在打开文件编辑器的右键菜单上,所以这里就需要将他加入到EditorPopupMenu组中,通过anhor来控制菜单显示的位置。

下面的keyboard-shortcut就没有什么好说的了,就是来设置下快捷键。

回调功能

点击菜单的回调功能也就是我们上面配置中在class属性设置的类路径。这个类需要继承AnAction类。重写它的actionPerformed方法。

比如你可以在这个方法中写一个打印的代码,来测试下有没有生效

@Override
public void actionPerformed(@NotNull AnActionEvent e) {
    System.out.print("Hello IDEA Plugin");
}

运行后不出意义当你点击了你新加菜单项的,他就会在控制台打印出这个字符串。

工具类的准备

在这个插件开发的过程中我也封装了一些常用的工具类,以后在写代码中也可以复用到。

剪切版工具类(CuttingBoardUtil)

判断剪切板中是否存在文本型的数据

/**
 * 判断剪切板中是否有文字数据
 * @return
 */
public static boolean checkClipboardForText(){
    //获取到系统剪切板
    Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
    Transferable contents = systemClipboard.getContents(null);
    if(contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)){
        return true;
    }
    return false;
}

获取剪切板中的数据:

/**
 * 获取剪切板中的数据
 * @return
 */
public static String getClipboardForText(){
    //获取到系统剪切板
    try{
        Clipboard systemClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable contents = systemClipboard.getContents(null);
        if(contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)){
            return (String) contents.getTransferData(DataFlavor.stringFlavor);
        }
        return "";
    }catch (Exception e){
        return "";
    }
}

字符串工具类(StringUtils)

检查文本是否是下划线命名方式:

/**
 * 检查文本是否是下划线命名方式
 * @param text 待检查的文本
 * @return 如果是下划线命名方式返回true,否则返回false
 */
public static boolean isSnakeCase(String text) {
    // 检查字符串是否为空或长度为0
    if (text == null || text.isEmpty()) {
        return false;
    }

    // 判断字符串是否以字母或下划线开头,并且只包含字母、数字和下划线
    for (int i = 0; i < text.length(); i++) {
        char ch = text.charAt(i);
        if (i == 0 && !Character.isLetter(ch) && ch != '_') {
            return false;
        }
        if (!Character.isLetterOrDigit(ch) && ch != '_') {
            return false;
        }
    }

    return true;
}

将下划线的命名方法转换成驼峰命名方式:

/**
 * 将下划线的命名方法转换成驼峰命名方式
 * @param snakeCase 下划线命名方式
 * @return 返回转驼峰后的字符串
 */
public static String snakeCaseToCamelCase(String snakeCase) {
    StringBuilder camelCase = new StringBuilder();

    boolean nextUpperCase = false;
    for (int i = 0; i < snakeCase.length(); i++) {
        char currentChar = snakeCase.charAt(i);

        if (currentChar == '_') {
            nextUpperCase = true;
        } else {
            if (nextUpperCase) {
                camelCase.append(Character.toUpperCase(currentChar));
                nextUpperCase = false;
            } else {
                camelCase.append(Character.toLowerCase(currentChar));
            }
        }
    }
    return camelCase.toString();
}

代码开发

上面的工具类准备好后,就可以开发小插件了。

首先我们第一个问题是如何控制这个菜单项的启用或禁用。这里IDEA中的AnAction中也给我们准备好了方法AnAction#update重写这个方法后我们就可以通过这个方法来控制是否启用。

 @Override
public void update(@NotNull AnActionEvent e) {
    VirtualFile vf = e.getData(PlatformCoreDataKeys.VIRTUAL_FILE);
    /**
     * 如果在已打开的文件中并且剪切板存在数据才启用
     */
    if(vf != null && CuttingBoardUtil.checkClipboardForText()){
        e.getPresentation().setEnabled(true);
    }else{
        e.getPresentation().setEnabled(false);
    }
}

这里的逻辑也挺简单的,首先我们只有打开了一个文件的时候才会显示这个菜单项,

  • 所以这里的第一行代码如果返回的不是null就代表已经打开了一个文件。
  • 然后通过我们封装的工具类当前剪切板中是否存在文本型的数据。设置禁用或启用

然后就是重写actionPerformed,插件的粘贴逻辑都在这个方法中编写。

  • 通过工具类获取到剪切板的内容
  • 判断当前剪切板的内容是否是下划线的命名方式
  • 如果是下划线的命名方式,将内容转成驼峰插入到光标位置
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
    String clipboardForText = CuttingBoardUtil.getClipboardForText();
    //判断当前文本是否是下划线
    if(StringUtils.isSnakeCase(clipboardForText)){
        String camelCaseStr = StringUtils.snakeCaseToCamelCase(clipboardForText);
        Editor editor = e.getData(PlatformCoreDataKeys.EDITOR);
        if(editor != null){
            //获取到光标位置
            int cursorPosition = editor.getCaretModel().getOffset();
            insertTextToCursorPosition(camelCaseStr, editor, cursorPosition);
        }
    }
}

因为是需要将数据插入到当前的编辑器中,所以这里要获取到当前的编辑器对象,就是通过这行代码

Editor editor = e.getData(PlatformCoreDataKeys.EDITOR);

这个类还封装了一个方法,是用于插入文本内容的方法。

/**
 * 将文本插入到光标位置
 * @param camelCaseStr 待插入的文本
 * @param editor 编辑器实例
 * @param cursorPosition 光标位置
 */
private void insertTextToCursorPosition(String camelCaseStr, Editor editor, int cursorPosition) {

    Project project = editor.getProject();
    if(project != null){
        WriteCommandAction.runWriteCommandAction(project,()->{
            editor.getDocument().insertString(cursorPosition, camelCaseStr);
            editor.getCaretModel().moveToOffset(cursorPosition + camelCaseStr.length());
        });
    }
}

这里的WriteCommandAction.runWriteCommandAction表达式去网上找个啥用途就可以了,这个是IDEA的规则,要插入数据的话,只能把插入数据的逻辑写在这个lmbda表达式中。

上面的代码只是一些比较重要的代码。全部的代码可以去GitHub上下载:https://github.com/can888-gc/idea-XTC

本次的IDEA小插件文章就完结了~~~