51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

ConvertX插件,是如何实现的

# 前言 {#前言}

我曾经写过一个插件叫做ConvertX,这个插件做的事情就是对代码中的字符串、时间格式、JSON等进行转换,提高工作的效率。在本篇博文中,我会介绍如何去写一个这样的插件,代码地址已经在博文中贴出,本文只展示核心的一些代码。

如果对IDEA开发不太熟的,可以看这个系列的上一篇文章,介绍了如何创建IDEA的开发环境并开始开发。

本篇博客涉及的代码已在github开源:https://github.com/OliverLiy/converterX (opens new window)

# 项目结构 {#项目结构}

项目结构如下:

action中是每一个选择项对应的类。

constant中存储了常量类型。

enums中存储三种转换的枚举类型。

executor中的是具体的执行器代码,实现逻辑的核心就在这里。

process中只有一个实现代码中替换文本的处理类。

strategy中是不同功能的策略实现。

# 字符串的转换 {#字符串的转换}

我以字符串的转换作为例子,日期时间的转换以及JSON的转换,实现逻辑都是一样的。

这个功能要实现的内容是这样的,选中一个字符串后,右键选择String Converter后,会在屏幕中间弹出一个列表框,就像下面这样

然后根据选择的内容不同,可以将字符串转换成对应个格式,比如驼峰命令,下划线命名等。

要实现这个功能,首先需要新建一个Action,点击之后弹出上面这个列表框,其中:

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import org.jetbrains.annotations.NotNull;
import top.codeease.idea.plugin.enums.StringConverterTypeEnum;
import top.codeease.idea.plugin.exectuor.StringPopupExecutor;

import java.util.List;
import java.util.Objects;

public class StringConverterAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent event) {
        Editor editor = (Editor) event.getDataContext().getData("editor");
        Project project = event.getProject();

        // 创建要展示的列表数据
        List<String> typeNameList = StringConverterTypeEnum.getTypeNameList();

        // 创建列表弹出窗口
        ListPopup listPopup = JBPopupFactory.getInstance()
                .createListPopup(new StringPopupExecutor("String Converter",typeNameList,editor,project));

        // 在屏幕中间显示列表弹出窗口
        listPopup.showCenteredInCurrentWindow(Objects.requireNonNull(event.getProject()));
    }
}

在上面的这段代码中,通过new一个StringPopupExecutor创建了弹出式的列表框,所有的实现代码逻辑都是在这个StringPopupExecutor对象中。 接下来看看StringPopupExecutor的实现,这个类继承了BaseListPopupStep类,其中onChosen方法中实现了对选中字符串的处理操作。原理就是先取出选中的值,然后按转换类型进行字符串替换,最后将代码中选中的字符串进行替换。

import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import top.codeease.idea.plugin.enums.StringConverterTypeEnum;
import top.codeease.idea.plugin.process.ReplaceProcess;
import top.codeease.idea.plugin.strategy.stringStrategy.StringConverterStrategy;

import javax.swing.*;
import java.util.List;


public class StringPopupExecutor extends BaseListPopupStep<String> {

    private Editor editor;
    private Project project;

    public StringPopupExecutor(@NotNull String title, @NotNull List<String> values, Editor editor, Project project) {
        super(title, values);
        this.editor = editor;
        this.project = project;
    }

    @Override
    @Nullable
    public PopupStep onChosen(@Nullable String selectedValue, boolean finalChoice) {
        // 处理选中的值
        if (StringUtils.isNotBlank(selectedValue)) {
            String selectedText = editor.getSelectionModel().getSelectedText();
            if (StringUtils.isNotBlank(selectedText)){
                StringConverterStrategy strategyInstance = StringConverterTypeEnum.getStrategyInstance(selectedValue);
                ReplaceProcess.replaceText(strategyInstance.execute(selectedText), editor, project);
            }
        }
        // 如果是最终选择,则关闭弹出窗口
        return finalChoice ? PopupStep.FINAL_CHOICE : super.onChosen(selectedValue, finalChoice);
    }

    @Override
    public boolean hasSubstep(@Nullable String selectedValue) {
        // 在这里可以定义是否有子步骤
        return false;
    }

    @Nullable
    @Override
    public String getTextFor(String value) {
        // 返回列表项的显示文本
        return value;
    }

    @Nullable
    @Override
    public Icon getIconFor(String value) {
        // 返回列表项的图标,如果不需要图标,则返回 null
        return null;
    }
}

因为String字符串有很多的转换类型,我在这里采用了策略模式,根据不同的类型执行不同的实现,下面是策略接口和驼峰的实现类。

public interface StringConverterStrategy {

    /**
     * 字符串转换
     * @param msg
     * @return
     */
    String execute(String msg);
}

public class CamelCaseStringConverter implements StringConverterStrategy {
    @Override
    public String execute(String msg) {

        ClassCamelStringConverter classCamelStringFormat = new ClassCamelStringConverter();
        StringBuilder result = new StringBuilder(classCamelStringFormat.execute(msg));
        // 将首字母改为小写
        if (!result.toString().isEmpty()){
            result.replace(0,1,String.valueOf(result.charAt(0)).toLowerCase());
        }
        return result.toString();
    }
}

通过一个枚举类实现了策略的选择:

public enum StringConverterTypeEnum {
    /**
     * 转大写
     */
    UPPER("Upper (CODEEASE)", UpperStringConverter.class),
    /**
     * 转小写
     */
    LOWER("Lower (codeease)", LowerStringConverter.class),
    /**
     * 转驼峰命名
     */
    CAMEL_CASE("Camel (codeEase)", CamelCaseStringConverter.class),
    /**
     * 类名驼峰
     */
    CLASS_CAMEL_CASE("ClassCamel (CodeEase)", ClassCamelStringConverter.class),
    /**
     * 转下划线大写
     */
    TO_UNDERLINE_UPPER("UnderlineUpper (CODE_EASE)", UnderlineUpperStringConverter.class),
    /**
     * 转下划线小写
     */
    TO_UNDERLINE_LOWER("UnderlineLower (code_ease)", UnderlineLowerStringConverter.class)

    ;
    private String typeName;
    private Class<? extends StringConverterStrategy> strategyClass;

    StringConverterTypeEnum(String typeName, Class<? extends StringConverterStrategy> strategyClass){
        this.typeName = typeName;
        this.strategyClass = strategyClass;
    }

    public String getTypeName() {
        return typeName;
    }

    public Class<? extends StringConverterStrategy> getStrategyClass() {
        return strategyClass;
    }

    /**
     * 获取全部的转换功能
     * @return
     */
    public static List<String> getTypeNameList(){
        List<String> typeNameList = new ArrayList<>();
        for (StringConverterTypeEnum value : StringConverterTypeEnum.values()) {
            typeNameList.add(value.getTypeName());
        }
        return typeNameList;
    }

    /**
     * 通过类型名称获取实例对象
     * @param typeName
     * @return
     */
    public static StringConverterStrategy getStrategyInstance(String typeName){
        for (StringConverterTypeEnum value : StringConverterTypeEnum.values()) {
            if (value.getTypeName().equals(typeName)){
                try {
                    Class<? extends StringConverterStrategy> strategyClass = value.getStrategyClass();
                    return strategyClass.getDeclaredConstructor().newInstance();
                }catch (Exception exception){
                    exception.printStackTrace();
                }

            }
        }
        return null;
    }

}

最后将这个类注册到plugin.xml文件中,使用group对三个转换按钮进行了分组,另外给每个Action定制了一个快捷键,快捷键也会同步显示在IDEA中。

<actions>
    <!-- Add your actions here -->
    <group id="ConverterX" text="ConverterX" description="ConverterX" popup="true" icon="/META-INF/logo.png">
        <add-to-group group-id="EditorPopupMenu" anchor="first"/>
        <action id="StringConverter" class="top.codeease.idea.plugin.action.StringConverterAction" text="String Converter"
                description="Convert strings to any type">
            <keyboard-shortcut keymap="$default" first-keystroke="ctrl shift S"/>
        </action>
        <action id="DateConverter" class="top.codeease.idea.plugin.action.DateConverterAction" text="Date Converter"
                description="Convert date to any type">
            <keyboard-shortcut keymap="$default" first-keystroke="ctrl shift D"/>
        </action>
        <action id="JsonConverter" class="top.codeease.idea.plugin.action.JsonConverterAction" text="Json Converter"
                description="Format and compress json">
            <keyboard-shortcut keymap="$default" first-keystroke="ctrl shift J"/>
        </action>
    </group>
</actions>

这个xml文件对应的效果是下面这样的:

这样一个字符串转换的逻辑就实现了,我定义了一个策略的接口,如果有更多的转换需求,只需要多写一个策略类的实现,然后在枚举类中增加一行就可以了。

看懂字符串的转换之后,日期时间和JSON的转换就很简单了,这一部分大家有兴趣的可以自己看代码。

# 总结 {#总结}

学完这个项目后,你应该对IDEA插件开发中的Action有了了解,基于Action已经可以实现很多的功能,这个系列的下一期我会结合图形化做一个插件。

赞(4)
未经允许不得转载:工具盒子 » ConvertX插件,是如何实现的