ConvertX插件,是如何实现的
作者:鱼仔
博客首页: https://codeease.top
公众号:Java鱼仔
# 前言
我曾经写过一个插件叫做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()));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在上面的这段代码中,通过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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
因为String字符串有很多的转换类型,我在这里采用了策略模式,根据不同的类型执行不同的实现,下面是策略接口和驼峰的实现类。
public interface StringConverterStrategy {
/**
* 字符串转换
* @param msg
* @return
*/
String execute(String msg);
}
2
3
4
5
6
7
8
9
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
通过一个枚举类实现了策略的选择:
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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
最后将这个类注册到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>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
这个xml文件对应的效果是下面这样的:
这样一个字符串转换的逻辑就实现了,我定义了一个策略的接口,如果有更多的转换需求,只需要多写一个策略类的实现,然后在枚举类中增加一行就可以了。
看懂字符串的转换之后,日期时间和JSON的转换就很简单了,这一部分大家有兴趣的可以自己看代码。
# 总结
学完这个项目后,你应该对IDEA插件开发中的Action有了了解,基于Action已经可以实现很多的功能,这个系列的下一期我会结合图形化做一个插件。