1.背景与痛点

一张图说明。。

上面的问题不知道友友们遇到过没,反正我是经常遇到,f...

写代码免不了要写注释,但是注释的时候我们经常要在代码区和注释区进行切换,这个过程虽然看似简单但是偶尔也会出些差错。比如当前输入法是美国英文输入时要想切换到中文输入法就不能使用shift快捷键了,而是要通过 win+空格或者ctrl+shift来切换输入法。如果电脑安装了多个输入法还可能会因为在各种输入法之间的切换而浪费时间,而这部分时间理应用来摸鱼。

需求

编写一个插件能够自动识别代码上下文,判断coder将要进行的操作,自动切换输入法。比如当光标移到了代码区,程序将输入法切换为英文,当光标移动到注释区程序将输入法切换为中文。

下面的链接是一个大概的功能演示

演示-CSDN直播https://live.csdn.net/v/235592

技术可实现性分析

功能点可以拆分为:idea插件主体(搭框架),输入法切换,代码上下文判断。

通过查阅资料发现要实现以上功能需要先学会idea基本插件开发(没学过也能看懂本文),windows输入法管理原理(了解),idea插件开发中的PSI框架(使用某些API可以用来获取程序上下文),

实现的功能虽然很简单不过要考虑的细节其实还是比较多的,比如java doc注释和java行注释甚至是其他语言的注释,另外还要操作系统对输入法的管理方式,已及不同平台的适配。不过为了简单起见我们只适配Windows 64位操作系统和java代码,并且只实现基础功能。然后后续迭代完全可以靠大家的聪明才智了,比如加些人工智能啥的(俺不会哈哈)。

经过分析以及参照资料可以明确需求能够被实现,那么我们进入下一个步骤吧!

前置知识

由于案例涉及到了一些额外知识,包括windows编程,jni,PSI等等所以在这里统一简单解释一遍。

1. 输入法切换原理

参考博客:该博客大概介绍了一些函数,我们通过调用系统函数可以实现输入法切换。

WINDOWS下输入法中英文切换_fengbangyue的博客-CSDN博客_c++ 切换输入法这几天在做一个注册页面,其中有一个真实姓名字段要求必须输入中文,于是想能不能在该真实姓名编辑框得到焦点的时候就将输入法切换为中文输入法呢?于是就开始寻找前辈们的成功案例,果然功夫不费有新人,虽然费了很长时间。  开始是不知道如何着手,接下来找到了一个遍历系统安装输入法的C++类,并且可以设置激活任何一种已经安装的C++类,因此这就简单了,但是需要判断我们激活的输入法是不是中文输入法。下https://blog.csdn.net/fengbangyue/article/details/7346333

参考MSDN对相应函数的说明:具体如何实现输入法切换的可以看MSDN详细说明,因为我也没学习过windows编程相关技术,所以我刚开始的时候也是硬着头皮看,根据文档的介绍找到关键的几个函数,然后看相应的函数说明,参数和返回值等等。

ActivateKeyboardLayout (Windows CE 5.0) | Microsoft Docshttps://docs.microsoft.com/en-us/previous-versions/windows/embedded/aa452845(v=msdn.10)

因为插件使用java代码编写,而java代码运行在虚拟机上的所以java本身没有提供操作输入法相关的函数,因此我们要先把操作输入法的函数封装好,然后利用jni技术在java里面调用C函数,这样就实现了第一个技术点,切换输入法!

下面是代码展示,函数头和名字有点奇怪不用管这个后面会解释,主要就是调用了两个函数(这里实现切换方法不是唯一的,只是其中比较简单的一种)


#include "com_bully_myplugin_jni_InputManagerJni.h"
#include "Windows.h"JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2Chinese(JNIEnv *env, jobject obj) {//获取当前窗口HWND hwnd = GetForegroundWindow();//将输入法先改为英文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0409);//将输入法修改为中文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0804);
}JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2English(JNIEnv *env, jobject obj) {//获取当前窗口HWND hwnd = GetForegroundWindow();//将输入法先改为中文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0804);//将输入法修改为英文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0409);
}

C语言代码也很短的!

当然这是最直接的方法,更简单的还可以通过python脚本实现输入法的切换,但是其实本质都是一样的。

# 这是一个示例 Python 脚本。
import ctypesimport win32api
import win32gui
from win32con import WM_INPUTLANGCHANGEREQUESTdef set_english_inputer():# 0x0409为英文输入法的lid_hex的 中文一般为0x0804name=win32api.GetKeyboardLayoutName()state=win32api.GetKeyboardState()print("state",state)print("名字",name)hwnd = win32gui.GetForegroundWindow()title = win32gui.GetWindowText(hwnd)im_list = win32api.GetKeyboardLayoutList()im_list = list(map(hex, im_list))for d in im_list:print(d)result = win32api.SendMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0409)result = win32api.SendMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0804)print(result)if result == 0:print("英文输入法切换成功!")set_english_inputer()

由于java环境的原因,不论是使用python还是C我们都要解决如何在插件中调用其他语言执行的问题。

2.什么是Jni

上面提到了跨语言的问题,现在我们就来解决。

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1]  从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

简单说就是 它提供提供了一个标准,java只写函数声明,c里面写实现,比如java里面的hash()方法就是native方法。最后java用的是一个dll文件,这个文件将C语言写的函数都封装在了这个文件里面。java代码加载这个dll之后就可以同过java native方法调用里面的c函数。

jni编写步骤:

1. 编写带有native声明的方法的java类

2. 使用javac命令编译所编写的java类

3. 然后使用javah + java类名生成扩展名为h的头文件

4. 使用C/C++实现本地方法

5. 将C/C++编写的文件生成动态链接库

6. java里面加载dll动态链接库

7. 调用native函数

看着是不是步骤挺多的其实一步一步跟着走会很简单,不用担心,不过只有亲自走过才能真正学会哦!

详细代码和操作将会在下面介绍。关于jni技术想要详细了解的这里贴个百度百科。

JNI_百度百科JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。https://baike.baidu.com/item/JNI/9412164本文作者使用jni技术来实现在java环境中调用c/c++函数,当然如果是使用Python也是有其他解决方案的,不过这里不深入,提供一个链接吧!

https://www.jianshu.com/p/4281ce5e137fhttps://www.jianshu.com/p/4281ce5e137f

开始编码

1.搭建插件项目

参考博客:IDEA插件开发之环境搭建_sawi的博客-CSDN博客

关于项目创建这里就简单介绍了网上资料比较多。需要注意的是插件采用Gradel进行构建,所有确保你正确配置了Gradel。

第一步是创建插件项目,这里配置名称符合jdk版本,如果熟悉kotlion也可以选择使用kotlin语言开发。

这样就完成了插件项目的框架。是不是很简单!!!

下面是目录结构,自己的代码都写在src里面,然后resource里面的plugin.xml是idea自动创建的插件配置文件(类似于android的清单文件),以后会对他进行很多操作。

不过目前就是创建了一个java项目而已,接下来我们按照之前的思路继续分析。

2.输入法切换功能的实现

首先我们要实现输入法必需要在一个合适的时机自动切换,这个时机可能是键盘输入,光标移动,快捷键触发等等。那么简单起见我们先用快捷键实现一个输入法切换的功能,然后慢慢扩展。

看图。。我们在编辑器右键菜单显示2了个快捷键功能,但我们按下对应快捷键时就让程序切换到对应输入法。

首先在plugin.xml里面注册action和快捷键。其中class属性指定了action的处理类,这个类需要我们自己定义,add-to-group标签就是配置action菜单位置的,这里配置在了EditorPopupMenu也就是编辑器右键弹出菜单。快捷键时通过keyboard-shortcut标签配置的,注意的是快捷键不要和已经存在的快捷键冲突。

  <!-- action配置,按钮展示在哪里需要在这配置 --><actions><action id="transEn" class="com.bully.myplugin.listener.TransEnAction" text="切换英文输入"description="ctrl+shift+X 切换为英文"><add-to-group group-id="EditorPopupMenu" anchor="first"/><keyboard-shortcut keymap="$default" first-keystroke="ctrl shift X"/></action><action id="transCh" class="com.bully.myplugin.listener.TransChAction" text="切换中文输入"description="ctrl+shift+D 切换为中文"><add-to-group group-id="EditorPopupMenu" anchor="first"/><keyboard-shortcut keymap="$default" first-keystroke="ctrl shift D"/></action></actions>

ok,现在注册了action类和快捷键接下来我们就对action进行实现。

看项目结构,我把输入法切换定义在了2个类里面,一个转换为中文,一个转换为英文。

我们看看TransEnAction里面的内容。

package com.bully.myplugin.listener;import com.bully.myplugin.jni.InputManagerJni;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKeys;public class TransEnAction extends AnAction {@Overridepublic void actionPerformed(AnActionEvent e) {System.out.println("快捷键切换为英文");InputManagerJni.getSingleton().any2English_1();}
}

代码比较短,简单分析下。首先我们的action类要继承AnAction类,然后重写actionPerformed方法,该方法会在快捷键触发后自动调用。回调方法传入了一个AnActionEvent参数,这个参数非常有用,可以拿到当前行为的各种信息,不过这里没有用到。

方法里面我们只写了一行代码 :InputManagerJni.getSingleton().any2English_1();该代码调用了一个输入法转换的方法,这个转换方法是通过jni方式实现的。接下来我们分析一下InputManager里面做了啥 。

package com.bully.myplugin.jni;import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.StringTokenizer;/*** @author LIXIN* @description 通过jni 调用win32 api 实现输入法切换* @date 2022/8/11 13:11*/
public class InputManagerJni {//1 -englishpublic static int status=1;public static int status0=1;//是否进行转换public static boolean doTrans=true;private static volatile InputManagerJni inputManagerJni = new InputManagerJni();public static Robot robot;static {try {robot = new Robot();} catch (AWTException e) {throw new RuntimeException(e);}try {String libpath = System.getProperty("java.library.path");if (libpath == null || libpath.length() == 0) {throw new RuntimeException("java.library.path is null");}String path = null;StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));if (st.hasMoreElements()) {path = st.nextToken();} else {throw new RuntimeException("can not split library path:" + libpath);}URL resource = InputManagerJni.class.getResource("/InputManager_64.dll");InputStream inputStream = resource.openStream();final File dllFile = new File(new File(path), "InputManager_64.dll");if (!dllFile.exists()) {FileOutputStream outputStream = new FileOutputStream(dllFile);byte[] array = new byte[8192];for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {outputStream.write(array, 0, i);}outputStream.close();}Runtime.getRuntime().addShutdownHook(new Thread() {public void run() {if (dllFile.exists()) {boolean delete = dllFile.delete();System.out.println("delete : " + delete);}}});} catch (Throwable e) {throw new RuntimeException("load jacob.dll error!", e);}System.loadLibrary("InputManager_64");}private InputManagerJni() {}public static InputManagerJni getSingleton() {if (inputManagerJni == null) {synchronized (InputManagerJni.class) {if (inputManagerJni == null) {inputManagerJni = new InputManagerJni();}}}return inputManagerJni;}public native void any2Chinese();/*** 转换为英文输入法*/public native void any2English();/*** 当前输入法 0-中文,1-英文,2-其他*/public native int state();/*** 获取输入法列表-保留*/public native String list();/*** 移除输入法 0-中文,1-英文,-保留*/public native String remove(int flag);/*** 转换为中文输入法*/public void any2Chinese_1(){if (!doTrans) return;status=0;any2Chinese();System.out.println("toChinese");}public void any2English_1() {if (!doTrans) return;any2English();status=1;System.out.println("toEnglish");}private void pressShift(){if (robot==null){try {robot=new Robot();} catch (AWTException e) {throw new RuntimeException(e);}}robot.keyPress(KeyEvent.VK_SHIFT);robot.keyRelease(KeyEvent.VK_SHIFT);}
}

可以看到其实InputManagerJni类里面只是声明了相应方法并未做实现。

 public native void any2Chinese();/*** 转换为英文输入法*/public native void any2English();

这时请回忆一下前置知识里面介绍的Jni部分内容,其实这里的native方法对应了下面头文件的函数声明。

/** Class:     com_bully_myplugin_jni_InputManagerJni* Method:    any2Chinese* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2Chinese(JNIEnv *, jobject);/** Class:     com_bully_myplugin_jni_InputManagerJni* Method:    any2English* Signature: ()V*/
JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2English(JNIEnv *, jobject);

这个头文件是通过是自动生成的。具体方法是:

1. 用javac将InputManagerJni.java文件编译为InputManagerJni.class。

2.用javah将InputManagerJni.class编译为com_bully_myplugin_jni_InputManagerJni.h(这个名称是自动生成的)

ok,现在我们通过java文件生成了头文件,接下来我们应该要编写函数实现了。这里我使用了Clion来进行C语言的编写,读者有需求可以使用自己习惯的ide进行编写。

首先看看Clion的基本配置,这是我的目录结构,使用Cmake进行构建。

提前配置的工具链

Cmake配置,这里有个Cmake选项(红色框框),如果你使用的是vs2022 可以这样写。其他vs版本可以网上查,都有规律这里表示我构建的是64位的动态链接库。

另外还有一点,因为我们要调用windows api所以安装vs的时候记得下载MFC

主目录下的 cmakeLists文件

cmake_minimum_required(VERSION 3.23)
project(InputManager)set(CMAKE_CXX_STANDARD 14)
find_package(JNI REQUIRED)include_directories(${JNI_INCLUDE_DIRS})include_directories(jni)
set(BUILD_USE_64BITS on)
add_subdirectory(src)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY D:/AllCode/Clion/InputManager/build)

src目录下的CmakeLists文件

project(InputManager)
add_library(InputManager SHARED  InputManager.c)

InputManager.c文件 这里面实现了切换的代码,

解释一下为啥转换中文要先调用转换英文的函数:因为比如百度输入法就是中文输入法,当我们按下shift键后输入的就是英文,这是如果想要切换为中文状态就不能直接调用函数了因为英文状态的百度输入法其实也是0x0804,所以要先转换为英文输入法,再转换为中文输入法。(这个方法是我自己想的,可能有其他方法但是我对windows函数不熟悉所以就这样将就了)。同理英文也是一样的。

//
// Created by LIXIN on 2022/8/10.
//#include "com_bully_myplugin_jni_InputManagerJni.h"
#include "Windows.h"JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2Chinese(JNIEnv *env, jobject obj) {//获取当前窗口HWND hwnd = GetForegroundWindow();//将输入法先改为英文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0409);//将输入法修改为中文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0804);
}JNIEXPORT void JNICALL Java_com_bully_myplugin_jni_InputManagerJni_any2English(JNIEnv *env, jobject obj) {//获取当前窗口HWND hwnd = GetForegroundWindow();//将输入法先改为中文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0804);//将输入法修改为英文SendMessageA(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, 0x0409);
}

这样代码就写完了,最后运行构建就会生成dll文件(这里是64位的)。(对Cmake不熟悉的可以直接搜教程网上很多,我也不是很懂就不讲了,反正里面坑挺多的)

回到IDEA,吧dll文件放到resource目录下面

先想想现在我们都干了啥:

我们定义了native方法,并且基于jni规则做了实现,最后把dll文件引入到了java项目resource目录里面,那么接下来我们就是要加载库了。

加载的代码很简单,只需要一行代码,但是这个loadLibary函数对参数是有要求的,它需要输入一个库文件的名字,并且这个文件名要在一下目录中

1)和jre相关的一些目录

2)程序当前目录

3)Windows目录

4)系统目录(system32)

5)系统环境变量path指定目录

而我们的文件是在resource里面所有不能直接这样处理。

 static{
....
....System.loadLibrary("InputManager_64");}

看看下面这段代码,我对resource里面的文件做了一下处理。如果java.library.path里面没有这个文件,那么我们就把它写进去。

 static {try {String libpath = System.getProperty("java.library.path");if (libpath == null || libpath.length() == 0) {throw new RuntimeException("java.library.path is null");}String path = null;StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));if (st.hasMoreElements()) {path = st.nextToken();} else {throw new RuntimeException("can not split library path:" + libpath);}URL resource = InputManagerJni.class.getResource("/InputManager_64.dll");InputStream inputStream = resource.openStream();final File dllFile = new File(new File(path), "InputManager_64.dll");if (!dllFile.exists()) {FileOutputStream outputStream = new FileOutputStream(dllFile);byte[] array = new byte[8192];for (int i = inputStream.read(array); i != -1; i = inputStream.read(array)) {outputStream.write(array, 0, i);}outputStream.close();}Runtime.getRuntime().addShutdownHook(new Thread() {public void run() {if (dllFile.exists()) {boolean delete = dllFile.delete();System.out.println("delete : " + delete);}}});} catch (Throwable e) {throw new RuntimeException("load jacob.dll error!", e);}System.loadLibrary("InputManager_64");}

这样我们的第一个功能就做完了。经过测试,没啥问题。

3.检测代码上下文切换输入法

之前我们实现了根据快捷键切换输入法,现在我们要根据代码上下文来动态的切换了。就像开头的视频那样。

首先我们了解一个东西:PSI,这个是框架提供的一套API,代码分析会用到。因为这里只涉及到分析是不是注释代码,所以比较简单,要深入了解还得看看官方文档, 这里提供一个博客地址。

编写一个IDEA插件之:使用PSI分析Java代码 - 腾讯云开发者社区-腾讯云PSI是Program Structure Interface的缩写,即程序结构接口。https://cloud.tencent.com/developer/article/1731496直接上代码吧!

看看这个先,MyProjectManagerListener.java 这个类负责监听项目的一下变化,比如项目打开后,我们注册了一些文档变化的监听(这个并没有使用),光标移动的监听等等。还让输入法变成了英文。这个类学

这个类需要在Plugin.xml文件里面进行注册

下面这个是CareListener的一个实现类,他在上面的ProjectManager类里面进行了注册。当光标移动时会回调里面的caretPositionChanged方法,然后我们再去做光标处相应的代码分析。

package com.bully.myplugin.listener;import com.bully.myplugin.JudgeService;
import com.bully.myplugin.Process;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.editor.event.CaretListener;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import org.jetbrains.annotations.NotNull;public class PfcCaretListener implements CaretListener{int CaretIndex;@Overridepublic void caretPositionChanged(@NotNull CaretEvent event) {Process.caretPositionChanged(event);}}

现在我们能够监听光标变化了但是我们通过CaretEvent对象其实拿不到输入的字符和PSIFile对象,也就是是我们并不能预测用户输入和获取代码上下文。当然我们也可以通过各种if判断来确定当前光标是否处于注释下。不过这里使用Psi的api更简单。

其实要解决上面的问题我们只需要能够监听键盘输入就行了

首先在plugin.xml里面注册监听类

然后实现监听类,这里继承了TypedHandlerDelegate也就是当编辑器检测到输入字符后会首先调用该类进行处理,然后再写入docment里面;

这里面涉及的关系有 Edior(编辑器) Docment(文档,就是你写的代码) Caret(光标)。

我们要做的就是当编辑器输入的时候拿到字符进行判断,比如是不是中文啥的,如果是中文并且是没有在注释环境,也没有在表达式里面,那么这个输入可能是用户想要进行注释。这时我们要在光标前面给文档插入 "//",或者其他注释符号。

注意: 可能有人想直接监听文档(Docment)对象的改变,然后获取改变的值进行判断,其实这样是有问题的,因为文档监听器(DocmentListener)是用来监听变化的而不是用来产生变化的,否则这会引发一个嵌套的修改异常。

package com.bully.myplugin;import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;/*** This is a custom {@link TypedHandlerDelegate} that handles actions activated keystrokes in the editor.* The execute method inserts a fixed string at Offset 0 of the document.* Document changes are made in the context of a write action.*/
class MyTypedHandler extends TypedHandlerDelegate {@Overridepublic @NotNull Result beforeCharTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, @NotNull FileType fileType) {
//        JudgeService.beforeJudge(c,project,editor,fileType);if (fileType instanceof JavaFileType) {Process.process(String.valueOf(c), editor, file);}return Result.CONTINUE;}@NotNull@Overridepublic Result charTyped(char c, @NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
//        JudgeService.judgement(c,project,editor,file);
//        new Process().process(String.valueOf(c),editor,file);return Result.STOP;}}

最后是Process类,这个类做了具体的判断逻辑。这段代码比较长,但是思路其实比较简单,代码太丑了不想说,应该是能看明白的,不过由于我自己也是第一次写插件,很多都不会,然后资料啥的也比较少,所以哇这个bug还是超级多的,只能说勉强能用。

package com.bully.myplugin;import com.bully.myplugin.jni.InputManagerJni;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.CaretEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.javadoc.PsiDocToken;
import com.intellij.psi.tree.IElementType;public class Process {static Editor mEditor;static PsiFile mPsiFile;static PsiElement mPsiElement;static Document mDocument;static Project mProject;static int offset;static int lineNumber;static String mInputChar;static int activeTimes = 0;static boolean processed = false;static int deleteLine = -1;public static void caretPositionChanged(CaretEvent event) {if (processed) {return;}mEditor = event.getEditor();if (mPsiFile == null) return;init(mInputChar, mEditor, mPsiFile);if (mPsiFile == null || mPsiElement == null) return;//添加注释if (!isNeedTrans()) {return;}if (getLineText().isBlank()) {if (isNewLine(event)) {InputManagerJni.getSingleton().any2English();}}if (isNewLine(event)) {activeTimes = 0;}if (activeTimes != 0) {return;}if (isComment()) {InputManagerJni.getSingleton().any2Chinese_1();if (mInputChar != null) activeTimes++;} else {InputManagerJni.getSingleton().any2English_1();if (mInputChar != null) activeTimes++;}}static void process(String inputChar, Editor editor, PsiFile psiFile) {processed = false;InputManagerJni.doTrans = true;mInputChar=inputChar;//初始化成员init(inputChar, editor, psiFile);boolean containChinese = Util.isContainChinese(inputChar);if (containChinese) {InputManagerJni.doTrans = false;InputManagerJni.status0 = InputManagerJni.status;InputManagerJni.status = 0;//添加行注释if (!(mPsiElement instanceof PsiIdentifier)) {mPsiElement = psiFile.findElementAt(offset - 1);if (mPsiElement == null) {return;}}if (mPsiElement.getParent() instanceof PsiErrorElement) {int startOffsetInParent = mPsiElement.getTextOffset();mDocument.insertString(startOffsetInParent, "//");processed = true;return;}if ((mPsiElement instanceof PsiIdentifier)) {int startOffsetInParent = mPsiElement.getTextOffset();PsiElement parent = mPsiElement.getParent();if (parent.getNextSibling() instanceof PsiErrorElement) {mDocument.insertString(startOffsetInParent, "//");processed = true;return;}if (parent instanceof PsiJavaCodeReferenceElement) {if (parent.getParent().getNextSibling() instanceof PsiErrorElement) {mDocument.insertString(startOffsetInParent, "//");processed = true;}}}} else {InputManagerJni.status0 = InputManagerJni.status;InputManagerJni.status = 1;InputManagerJni.doTrans = true;}}private static void init(String inputChar, Editor editor, PsiFile psiFile) {mEditor = editor;mPsiFile = psiFile;mDocument = editor.getDocument();mProject = editor.getProject();CaretModel caretModel = editor.getCaretModel();offset = caretModel.getOffset();lineNumber = mDocument.getLineNumber(offset);mPsiElement = mPsiFile.findElementAt(offset);}private static boolean isComment() {return isLineEndCommented() || isMutiLineComment();}private static boolean isNeedTrans() {if (mPsiElement == null) {return false;}//是表达式if (mPsiElement.getContext() instanceof PsiLiteralExpressionImpl) {return false;}if (mPsiElement instanceof PsiJavaToken) {IElementType tokenType = ((PsiJavaToken) mPsiElement).getTokenType();return !(tokenType.getDebugName().equals("STRING_LITERAL"));}return true;}private static String getLineText() {int start = mDocument.getLineStartOffset(lineNumber);String line = mDocument.getText(TextRange.from(start, offset - start));return line.replaceAll("\t", " ").trim();}//判断当前上下文是否属于行注释环境private static boolean isLineEndCommented() {String lineText = getLineText();if (lineText.isBlank() || mDocument.getLineStartOffset(lineNumber) == offset) {return false;}if (lineText.endsWith("//") && !lineText.endsWith("*//")) {return true;}if (lineText.startsWith("//")) {return true;}int lineStartOffset = mDocument.getLineStartOffset(lineNumber);for (int i = offset - 1; i >= lineStartOffset; i--) {if (mPsiElement instanceof PsiComment) {return mPsiElement.getTextOffset() != offset;} else {mPsiElement = mPsiFile.findElementAt(i);}}return false;}//是否是多行注释,包括doc注释private static boolean isMutiLineComment() {if (getLineText().startsWith("*/")) {return false;}String lineText = getLineText();if (lineText.isBlank() || mDocument.getLineStartOffset(lineNumber) == offset) {return false;}if (mPsiElement instanceof PsiDocComment || mPsiElement.getContext() instanceof PsiDocComment) {return true;}if (mPsiElement instanceof PsiDocTag || mPsiElement.getContext() instanceof PsiDocTag) {return true;}if (mPsiElement instanceof PsiDocToken || mPsiElement.getContext() instanceof PsiDocToken) {return true;}if (mPsiElement instanceof PsiComment) {IElementType tokenType = ((PsiComment) mPsiElement).getTokenType();return tokenType.getDebugName().equals("C_STYLE_COMMENT");}PsiElement context = mPsiElement.getContext();if (context instanceof PsiComment) {IElementType tokenType = ((PsiComment) context).getTokenType();return tokenType.getDebugName().equals("C_STYLE_COMMENT");}return false;}//是否是新行private static boolean isNewLine(CaretEvent event) {int oldLine = event.getOldPosition().line;//不管这样是的int newLine = event.getNewPosition().line;return oldLine != newLine;}
}

最后就是打包了,执行相应gradel脚本就行,具体百度,很简单,打包后就可以安装到你的ide上了。

注意:上面那段代码,有很多逻辑不合理的地方,大家看的时候注意分辨,我写了2万多个字了实在不想写了,有问题评论区找大神,如果我会的话我也会回答的,不回答就是不会捏。

然后源代码嘞本来是不想发的,因为是个半成品,bug太多,怕大家笑话。不过想想我写的虽然菜但是肯定有比我更菜的同学,那么就发出来吧,仅供参考。

源代码好像是jdk15的。

github地址:GitHub - lixin-hub/AutoNotes: 根据代码上下文自动切换输入法,自动生成注释根据代码上下文自动切换输入法,自动生成注释. Contribute to lixin-hub/AutoNotes development by creating an account on GitHub.https://github.com/lixin-hub/AutoNotes.git

在IDEA开发一个自动输入法切换插件相关推荐

  1. 如何添加MySQL插件_如何开发一个自定义的MySQL插件

    MySQL自带了很多插件,比如半同步插件.审计插件.密码验证插件等等,甚至MySQL存储引擎也是以插件方式实现的.MySQL开放的插件接口,为开发者开发自定义插件提供了便利.本文将介绍如何快速开发一个 ...

  2. 超详细带你入门开发一个超实用的浏览器插件

    相信大家平时在电脑上逛掘金.知乎网站时,肯定有看到过下面超级烦人的跳转拦截确认页面 虽然这种拦截的初衷是好的,但是我相信大家平时肯定不会因为有了这个拦截提醒页面,就会对即将打开的网站安全性提高自己的警 ...

  3. 使用 Vue3 + vite + elementUI 开发一个 Utools Markdown 编辑器插件

    文章目录 目的 开发文档整理 基础工具的集成 初始化项目 框架引入 按需引用和 SASS 引入验证 utools 开发配置 调试和打包插件 功能实现 依赖库的安装调用 布局实现 Editor.vue ...

  4. vue3 开发一个图片预览插件

    vue3 的插件开发和vue2思路类似但是写法却迥异.主要变化在vue3没有了extend构造器. 这次我们通过一个图片预览插件,贯通整个vue3插件开发的过程. 1 在src下新建lplugins文 ...

  5. 从头开发一个BurpSuite数据收集插件

    一段时间没写公众号了,最近写了个 burpsuite 数据收集的插件,于是想出一篇从头编写一个 burpsuite 插件的教程. ​ 这个插件的目的收集 burpsuite 请求中的数据,如请求中的子 ...

  6. iOS逆向之旅---在iPhone上开发微信自动跳一跳插件

    感谢「杜泽旭」同学投稿,也欢迎广大安全逆向爱好者积极投稿,分享不仅是一种精神也是一种快乐,本文涉及到的iOS应用开发安全逆向问题可以去小密圈咨询他,包括微信,抖音等各种问题! 现在已有的实现方法基本是 ...

  7. km之路--010 jquery 002 开发一个 手风琴/折叠面板 插件

    目标描述 我想要的插件是类似jquery-ui的accordion插件 我想要的功能是这样的: 1. 此插件是否响应式应该是可选的,也就是宽度和高度是否自动自动填充父容器div 2. 可以自定义左边的 ...

  8. webpack原理篇(六十二):实战开发一个自动合成雪碧图的loader

    说明 玩转 webpack 学习笔记 支持的语法 对样式里面图片引用后面加 __sprite 进行图片合并 如何将两张图片合成一张图片? 使用 spritesmith https://github.c ...

  9. 开发一个自动点击工具

    一个人点鼠标总是很麻烦,尤其是这个时间很长的时候.所有就考虑要做一个模拟鼠标点击的工具. 1.准备 打开vs,新建一个窗口程序. 上面依次选择, 1.程序的类型:窗口程序 2.新程序的名字和命名空间 ...

最新文章

  1. Android开发之自定义Toast(带详细注释)
  2. Reporting service个人使用经验
  3. [转]Loadrunner Error code 10053 Tomcat 连接器(connector)优化
  4. CodeForces 845C Two TVs
  5. 【数据分析】数据分析基础:SQL重要知识点梳理!
  6. Workaround for Search for my account in MyAppointment
  7. android按钮控件常见问题,Android的基本控件和Activity的应用总结
  8. Linux 上 安装 nginx、 阿里云服务器上安装 nginx
  9. 【计算机网络】—— 差错控制(检错编码)
  10. 解决SSM项目下静态资源(img、js、css)无法引用的问题
  11. 针对每个团队提出的意见和建议
  12. linux rsh配置 A主机无需密码登录B主机
  13. Hadoop生态圈-Azkaban实现文件上传到hdfs并执行MR数据清洗
  14. 笨办法学R编程(5)
  15. html 新浪微博分享申请,新浪微博API申请流程详解
  16. fullavatareditor 富头像上传编辑器
  17. C++ VS2017 编译调用 gflags
  18. Python 让书法作品和 PIL 库来一场美丽的邂逅
  19. Python-直线检测-提取邮票编码
  20. 关于航空障碍灯的介绍

热门文章

  1. 人工智能与深度神经网络,人工智能深度神经网络
  2. 中文字符 简体繁体相互转换
  3. 警惕,老外也诈骗!!
  4. MATHTYPE安装出现问题:无法打开要写入的文件;MathType打开word时“安全警告,宏已被禁用”;mathtype与AXmath不能同时使用
  5. usercity 小程序_微信小程序API 用户信息
  6. 是时候选择NewSQL数据库了
  7. Matlab机器人工具箱(3-4):五自由度机械臂(计算力矩控制方法与roblocks)
  8. Basemap绘制地图
  9. 识字水平测试软件,3000字良心测评,市面上最火的3款识字App,这款最便宜好用...
  10. mfs文件服务器,MFS分布式文件系统搭建