官方插件开发文档

前端人开发intellij插件,一边哭一边学java…

API

PSI

PSI(Program Structure Interface) 文件是表示一个文件的内容作为在一个特定的编程语言元素的层次结构的结构的根。

PsiFile 类是所有PSI文件共同的基类, 在特定语言中的文件通常使用它的子类表示。比如,PsiJavaFile 类表示一个java文件, XmlFile 类表示xml文件。

VirtualFile 以及 Document的应用作用域不同(即使打开多个projects,仍然只有一个对应的实例),PSI的作用域是项目级(多个projects中同时打开同一个文件,会有多个实例)。

如何获取PSI文件?
  • e.getData(PlatformDataKeys.VIRTUAL_FILE),如果是多选,使用e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY)

  • LocalFileSystem.getInstance().findFileByIoFile()

  • psiFile.getVirtualFile(),如果PSI FILE仅存在内存中,则返回空

  • FileDocumentManager.getInstance().getFile()

监听文件改变

PsiTreeChangeListener
BulkFileListener 批量文件监听

1.添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
beforeChildrenChange

beforeChildAddition

beforeChildAddition

beforeChildAddition

childAdded

childAdded

childAdded

childrenChanged
2.删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
beforeChildrenChange

beforeChildRemoval

beforeChildRemoval

beforeChildRemoval

childRemoved

childRemoved

childRemoved

childrenChanged
3.修改名称

propertyChanged
childrenChanged

1
2
3
4
System.out.println("------"+e.getPropertyName());
// getPropertyName == fileName
System.out.println("e.getOldValue()"+e.getOldValue());
System.out.println("e.getNewValue()"+e.getNewValue());
1
<psi.treeChangeListener implementation="com.theblind.test5"/>
监听文件打开

FileEditorManagerListener

1
2
3
4
<projectListeners>
<listener class="com.theblind.MyFileEditorManagerListener"
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"/>
</projectListeners>

配置

右键菜单
1
2
3
4
5
6
7
<!-- plugin.xml -->
<actions>
<action id="com.hunliji.MainAction" class="com.hunliji.MainAction" text="yapi2ts" icon="/icon/logo.png">
<!--右键菜单-->
<add-to-group group-id="EditorPopupMenu" anchor="last" />
</action>
</actions>
tools工具栏
1
2
3
4
5
6
7
<!-- plugin.xml -->
<actions>
<action id="com.hunliji.MainAction" class="com.hunliji.MainAction" text="yapi2ts" icon="/icon/logo.png">
<!--tool工具栏-->
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
</actions>
快捷键
1
2
3
4
5
6
7
8
9
10
<!-- plugin.xml -->
<actions>
<action id="com.hunliji.MainAction" class="com.hunliji.MainAction" text="yapi2ts" icon="/icon/logo.png">
<!--快捷键-->
<keyboard-shortcut first-keystroke="control alt A" second-keystroke="C" keymap="$default"/>

<!--ctrl + N (generate快捷生成)-->
<!--<add-to-group group-id="GenerateGroup" anchor="last" />-->
</action>
</actions>
action分组
1
2
3
4
5
6
7
8
9
10
11
12
13
<actions>
<group id="" text="yapi2ts"
popup="true" icon=" ">
<add-to-group group-id="ProjectViewPopupMenu" anchor="after" relative-to-action="ReplaceInPath"/>
</group>

<action class=""
id="" description=""
icon=""
text="Basic Class">
<add-to-group group-id="Halo Tools"/>
</action>
</actions>

获取选中文本

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PopupAction extends AnAction {

@Override
public void actionPerformed(AnActionEvent e) {
//获取当前编辑器对象
Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
//获取选择的数据模型
SelectionModel selectionModel = editor.getSelectionModel();
//获取当前选择的文本
String selectedText = selectionModel.getSelectedText();
System.out.println(selectedText);
}
}

用户选择文档生成目录

1
2
3
4
5
6
7
8
9
10
createBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
VirtualFile virtualFile = FileChooser.chooseFile(FileChooserDescriptorFactory.createSingleFolderDescriptor(), project, project.getBaseDir());
if(virtualFile!=null){
String path = virtualFile.getPath();
System.out.println(path);
}
}
});

行标记

1
2
3
4
5
<extensions defaultExtensionNs="com.intellij">
<!--如下所示添加行标记扩展 -->
<codeInsight.lineMarkerProvider language="JAVA"
implementationClass="org.xujin.idea.right.linemarker.HaloLineMarker"/>
</extensions>
1
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
 public class HaloLineMarker implements LineMarkerProvider {
@Nullable
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement psiElement) {
LineMarkerInfo lineMarkerInfo= null;
try {
lineMarkerInfo = null;
String anno="org.springframework.boot.autoconfigure.SpringBootApplication";
if(!judgeHaveAnnotation(psiElement,anno)){
return lineMarkerInfo;
}
PsiClassImpl field = ((PsiClassImpl) psiElement);
PsiAnnotation psiAnnotation = field.getAnnotation(anno);
lineMarkerInfo = new LineMarkerInfo<>(psiAnnotation, psiAnnotation.getTextRange(), IconLoader.findIcon("/icons/right/HaloBasic.png"),
new FunctionTooltip("快速导航"),
new AppMgmtNavigationHandler(),
GutterIconRenderer.Alignment.LEFT);
} catch (Exception e) {
e.printStackTrace();
}
return lineMarkerInfo;
}
@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> list, @NotNull Collection<LineMarkerInfo> collection) {
}
private boolean judgeHaveAnnotation(@NotNull PsiElement psiElement, String anno) {
if (psiElement instanceof PsiClass) {
PsiClassImpl field = ((PsiClassImpl) psiElement);
PsiAnnotation psiAnnotation = field.getAnnotation(anno);
if (null != psiAnnotation) {
return true;
}
return false;
}
return false;
}
}

生成文档

1
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
79
80
81
82
public interface Processor{
public void process(SourceNoteData sourceNoteData) throws Exception;
}



编写Freemarker的抽象类
public abstract class AbstractFreeMarkerProcessor implements Processor{

protected abstract Template getTemplate() throws IOException, Exception;

protected abstract Object getModel(SourceNoteData sourceNoteData);

protected abstract Writer getWriter(SourceNoteData sourceNoteData) throws FileNotFoundException, Exception;


@Override
public final void process(SourceNoteData sourceNoteData) throws Exception{
Template template = getTemplate();
Object model = getModel(sourceNoteData);
Writer writer = getWriter(sourceNoteData);
template.process(model, writer);
}
}



编写MDFreeMarkProcessor继承AbstractFreeMarkerProcessor。实现抽象方法
public class MDFreeMarkProcessor extends AbstractFreeMarkerProcessor{
@Override
protected Template getTemplate() throws Exception{
//加载模板字符串
String templateString = UrlUtil.loadText(MDFreeMarkProcessor.class.getResource("/template/md.ftl"));
//创建模板配置
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
//创建字符串模板的导入器
StringTemplateLoader stringTemplateLoader=new StringTemplateLoader();
//导入字符串模板
stringTemplateLoader.putTemplate("MDTemplate",templateString);
configuration.setTemplateLoader(stringTemplateLoader);
//获取模板
return configuration.getTemplate("MDTemplate");
}

@Override
protected Object getModel(SourceNoteData sourceNoteData){
HashMap model = new HashMap();
model.put("topic",sourceNoteData.getNoteTopic());
model.put("noteList",sourceNoteData.getNoteDataList());
return model;
}

@Override
protected Writer getWriter(SourceNoteData sourceNoteData) throws Exception{
String filePath = sourceNoteData.getFilePath();
File file = new File(filePath);
return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),"utf-8"));
}

}



添加处理操作
createBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e){
VirtualFile virtualFile = FileChooser.chooseFile(FileChooserDescriptorFactory.createSingleFolderDescriptor(), project, project.getBaseDir());
if (virtualFile != null) {
String path = virtualFile.getPath();
String topic = topicEtf.getText();
String filePath = path + "/" + topic + ".md";
Processor processor = new MDFreeMarkProcessor();
try {
processor.process(new DefaultSourceNoteData(topic, filePath, DataCenter.NOTE_LIST));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});

粘贴

1
2
CopyPasteManager.getInstance().setContents(new StringSelection(xml));
CopyPasteManager.getInstance().setContents(new SimpleTransferable(table.toString(), DataFlavor.allHtmlFlavor));

界面

下拉框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private JComboBox<SelectedTypeModel> kind;

kind.addItem(seleType);

kind.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
SelectedTypeModel selectedTypeModel = (SelectedTypeModel) e.getItem();
switch (selectedTypeModel.getValue()) {
case HaloConstant.COMBOX_CONTROLLER:
NewRightContext.setClassType(HaloConstant.COMBOX_CONTROLLER);
break;
default:
NewRightContext.setClassType(null);
}![在这里插入图片描述](https://gitee.com/Lovxy/private-notes/raw/master/doc/show.gif#pic_center)

}
});

通知

1
2
3
4
5
6
7
8
NotificationGroup notificationGroup = new NotificationGroup("testid", NotificationDisplayType.BALLOON, false);

/**
* content : 通知内容
* type :通知的类型,warning,info,error
*/
Notification notification = notificationGroup.createNotification("测试通知", MessageType.INFO);
Notifications.Bus.notify(notification);

非模态框式通知

1
2
3
NotificationGroup notificationGroup = new NotificationGroup("notificationGroup", NotificationDisplayType.BALLOON, true);
Notification notification = notificationGroup.createNotification("notification",NotificationType.ERROR);
Notifications.Bus.notify(notification);

高版本不兼容NotificationGroup 直接采用 new Notification

1
Notification notification = new Notification("PNGroup", "Private Notes Message", content, type);

其中,NotificationDisplayType可以是以下几种:

  • NONE:无弹框,不展示
  • BALLOON:自动消失
  • STICKY_BALLOON:用户点击关闭按钮消失
  • TOOL_WINDOW:实例效果同STICKY_BALLOON

Toast

1
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
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.awt.RelativePoint;

import javax.swing.*;

public class Toast {

/**
* Display simple notification of given type
*
* @param jComponent
* @param type
* @param text
*/
public static void make(JComponent jComponent, MessageType type, String text) {
JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(text, type, null)
.setFadeoutTime(7500)
.createBalloon()
.show(RelativePoint.getCenterOf(jComponent), Balloon.Position.above);
}

/**
* Display simple notification of given type
*
* @param project
* @param type
* @param text
*/
public static void make(Project project, MessageType type, String text) {

StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);

JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(text, type, null)
.setFadeoutTime(7500)
.createBalloon()
.show(RelativePoint.getCenterOf(statusBar.getComponent()), Balloon.Position.atRight);
}
}

持久化

ApplicationSettingsConfigurable是settins配置页面

1
2
3
4
5
6
7
8
<!-- plugin.xml -->
<extensions defaultExtensionNs="com.intellij">
<applicationConfigurable parentId="tools" instance="com.hunliji.components.ApplicationSettingsConfigurable"
id="com.hunliji.components.ApplicationSettingsConfigurable" displayName="Yapi Settings"/>
<!-- applicationService 这个是插件配置文件的持久化 -->
<applicationService serviceInterface="com.hunliji.config.ConfigPersistence"
serviceImplementation="com.hunliji.config.ConfigPersistence"/>
</extensions>
1
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
// ConfigPersistence
/**
* 配置持久化
*/
@State(name = "yapi2ts", storages = {@Storage(value = "yapi2ts.xml")})
public class ConfigPersistence implements PersistentStateComponent<MyPersistenceConfiguration> {
private MyPersistenceConfiguration config;

@Nullable
@Override
public MyPersistenceConfiguration getState() {
if (config == null) {
config = new MyPersistenceConfiguration();
List<ConfigDTO> configDTOS = new ArrayList<>();
config.setConfigs(configDTOS);
}
return config;
}

public static ConfigPersistence getInstance() {
return ApplicationManager.getApplication().getService(ConfigPersistence.class);
}

@Override
public void loadState(@NotNull MyPersistenceConfiguration state) {
XmlSerializerUtil.copyBean(state, Objects.requireNonNull(getState()));
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public class MyPersistenceConfiguration {
private List<ConfigDTO> configs;

public List<ConfigDTO> getConfigs() {
return configs;
}

public void setConfigs(List<ConfigDTO> configs) {
this.configs = configs;
}
}

实战

开发了一个idea 小插件,可以在编辑器里面输入yapi文档接口详情的链接自动生成所需要的请求体ts类型或者返回数据的ts类型,提高前端开发者的工具效率。

安装

Using IDE built-in plugin system:

Settings/Preferences > Plugins > Marketplace > Search for "yapi2ts" >
Install Plugin

使用前注意

!!!务必先配置项目token: settings -> tools -> yapi2ts

使用

  1. 配置yapi文档项目的token

  2. 在需要生成类型文件的js/ts/jsx/tsx文件中打开tools -> yapi2ts 或者 右键文件打开菜单,选中yapi2ts打开工具弹窗

  3. 输入接口详情链接地址,选择生成请求体类型还是返回数据类型