编写插件解决Jetbrains系列软件Ctrl + Alt + L 和搜狗输入法Ctrl切换语言的冲突
以下内容只针对Windows平台
问题
- 搜狗输入法提供了两种语言切换快捷键,一个是Shift一个是Ctrl,作为一个码农,Shift肯定是少不了会一直按的,拿它作为语言切换快捷键会存在一个问题,就是只要你稍微犹豫了一下,只按了Shift但是没按其它键,那么搜狗就会切语言了,然而这肯定并非尔之所愿,所谓的犹豫就会败北.所以我将搜狗默认的Shift切换语言改成Ctrl了.不过感觉周围好像就我一个人这个干,我就想知道选择Shift键作为语言切换的同学的是怎么忍受Shift天天没事乱切换语言的...
- 然后涉及第二个习惯,在使用IDEA/Android Studio过程中,我老是喜欢按下Ctrl + Alt + L去对齐全文代码,可能我已经习惯了整齐舒适的感觉.不过这个习惯经常遭来祸端,比如改了别人的代码,然后他提交的时候就冲突了.当然我会告诉他,你没对齐还怪我喽[手动滑稽]
基于以上两个习惯,冲突了.........
按下Ctrl + Alt + L会切换语言. WTF.
按下Ctrl + L或者其它键并不会切换语言,只要带上Ctrl + Alt就会切.所以这特么就是一个搜狗N年都没解决的Bug..........
可能还是因为使用Ctrl键作为语言切换的人太少了,没反馈过去.
能怎么办呢?搜狗不解决那只有自己解了.
解决办法
- 在搜狗改成Shift切换语言 : 去死!
- 修改IDEA的快捷键 : 有效,容易和其它快捷键重叠,试了好几个都发现有重叠,然后我尝试覆盖了一个Ctrl + L,有用,但是我特么已经习惯了Ctrl + Alt + L了..........每次都会习惯的用Ctrl + Alt + L,所以这个方案放弃了.
- 对搜狗屏蔽Alt : 感觉像是系统层才能实现的方式,太难了,告辞.而且搜狗也有一些Ctrl + Alt的快捷键,比如截图,所以屏蔽了也不好.
- 用一个程序监听按键消息,当发生Ctrl + ALt + * 的组合快捷键时,当所有按键弹起时再按一次Ctrl来将搜狗的语言恢复回去.
最终也就是最后一个方案是较为可行的.
实现方案
有了想法,如何实现呢?Java不能在后台检测按键,其必须要有焦点,所以写个Java程序在后台跑是不现实的;然后C/C++是可以实现后台监听按键的,但是作为一个菜鸡表示对C/C++不是很熟,而且一直跑的必要性不是很高;那么就试试写个IDEA的插件喽,本身只是在IDEA使用Ctrl + Alt + L才有的冲突,在IDEA中处理完就行了.
实现过程
了解插件的编写
IDEA插件的编写资料很少,尤其是中文资料更少,或者说不详细.特么的都是抄袭,点开几个网页内容都是一致的好烦,滥竽充数真没意思......你说你要是记笔记记在有道云不好吗?
找到一篇较为详细的中文资料,有兴趣可以了解下.
https://cloud.tencent.com/developer/article/1348741
创建工程
IDEA插件的创建方式有两种,一种是IntelliJ Platform Plugin一种是Gradle.
作为一个Android开发者对Gradle有着莫名的好感.所以,一开始是使用的Gradle,结果它第一次使用要下载依赖.......下了半天没啥动静....当我把插件写完了,它终于下载完了....
然后很无奈只好选择另一种方式创建了.
创建完了里面大概就一个plugin.xml,配置一些插件信息和注册一些组件,类似Android的AndroidManifest.xml.
此篇水文并不是教怎么写插件的,只是记录一个解决问题的过程,所以在此不对plugin.xml做说明,如果想了解插件编写可以先看下上面链接的文章.
创建组件
IDEA常用的组件应该是Action居多吧,它可以实现IDEA的菜单和快捷键调用,很方便,不过这里是一个监听操作,所以用不到Action.
这里我们需要做的是注册一个ApplicationComponent,这个组件可以实现在IDEA打开的时候就初始化.然后我们需要的正是这个效果 : 在IDEA打开时注册一个按键事件的监听回调.
简述其创建过程,如下.创建一个接口,继承于ApplicationComponent,举例:
package com.yxf.plugin;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.components.ApplicationComponent;
public interface Component extends ApplicationComponent {
}
Java 8有了接口默认实现,所以不需要实现其方法,真好.
然后编写一个类实现这个接口,举例:
package com.yxf.plugin;
public class FixSouGouConflictComponent implements Component {
@NotNull
@Override
public String getComponentName() {
return "FixSgC.FixSouGouConflictComponent";
}
@Override
public void initComponent() {
}
@Override
public void disposeComponent() {
}
获取名称,初始化,销毁,实现此三个接口即可.
然后别忘了将组件注册到plugin.xml中.
<actions>
<!-- Add your actions here -->
</actions>
<application-components>
<component>
<interface-class>com.yxf.plugin.Component</interface-class>
<implementation-class>com.yxf.plugin.FixSouGouConflictComponent</implementation-class>
</component>
</application-components>
<application-components>
这个标签默认没有,需要自己添加.
监听事件
然后如何实现按键事件的监听呢?中文网站没搜索到这部分内容,然后Google找到了一个线索,有个网友提到IdeEventQueue.addPostprocessor
可以实现.然后我针对addPostprocessor
搜索了下没发现啥.然后我想起了万能的GayHub,呸,GitHub.一搜索发现了一个Kotlin的例子,可以取出其中的KeyEvent.然后根据其方法修改实现:
private IdeEventQueue.EventDispatcher mDispatcher = awtEvent -> {
if (awtEvent instanceof KeyEvent) {
return onKeyEvent((KeyEvent) awtEvent);
}
return false;
};
@Override
public void initComponent() {
IdeEventQueue queue = IdeEventQueue.getInstance();
queue.addPostprocessor(mDispatcher, null);
}
private boolean onKeyEvent(KeyEvent event) {
//................
return false;
}
有了KeyEvent就容易很多了,然后很容易就可以实现针对Ctrl + Alt + [*]快捷键的监控,发生后再按下Ctrl键恢复语言.具体实现如下.
private Robot mRobot;
private Set<Integer> mKeyDownSet = new HashSet<Integer>();
private boolean mTriggered = false;
private boolean onKeyEvent(KeyEvent event) {
int keyCode = event.getKeyCode();
switch (event.getID()) {
case KeyEvent.KEY_PRESSED:
if (event.isControlDown() && event.isAltDown()) {
if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT) {
return false;
}
mKeyDownSet.add(keyCode);
mTriggered = true;
}
break;
case KeyEvent.KEY_RELEASED:
if (keyCode != KeyEvent.VK_CONTROL && keyCode != KeyEvent.VK_ALT) {
mKeyDownSet.remove(keyCode);
}
if (mTriggered && mKeyDownSet.size() == 0 && !event.isControlDown() && !event.isAltDown()) {
mTriggered = false;
if (mRobot != null) {
mRobot.keyPress(KeyEvent.VK_CONTROL);
mRobot.delay(50);
mRobot.keyRelease(KeyEvent.VK_CONTROL);
}
}
break;
}
return false;
}
其中Robot类是用于模拟鼠标和键盘的.
至此逻辑就完了,写一个简单的插件实际上就和创建一个Android的Activity差不多.不过要实现复杂的插件,就像你想创建一个Dialog但是没有文档一样难受.IDEA的插件编写文档也是特别少,其官方文档写的貌似也不怎么样,讲道理最好的学习方式真的就是去GitHub找源代码看........
添加依赖
完了吗?Naive,哪有这么简单,这样做出了的插件直接运行没问题,放在Android Studio中没问题,但是非Java系列的软件上就有问题了,比如Pycharm/Rider,这样直接做出了的插件放到Pycharm根本没用,但是特么也不报错,也没log..........
Google了一把没多少有效信息,看到了一个关于说引用了python模块无法在IDEA中使用的......然后怀疑是不是缺失了什么依赖.然后回去plugin.xml中寻找线索,发现这样一段注释
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
on how to target different products -->
<!-- uncomment to enable plugin in all products
<depends>com.intellij.modules.lang</depends>
-->
这是引导我如何对他们不同的产品做处理,哇,这正是所需要的.贴下网址
http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
其内容大致是说每个产品依赖了不同的模块,有些公共模块,有些是IDEA/Android Studio才有的有些Pycharm才有的,诸如此类.......然后如果插件需要通用的话需要申明需要的模块.这部分它官方文档也没给个例子,差评.
而且国内的IDEA插件开发资料基本上没有这个信息.......
为了找个例子,继续GitHub搜吧,搜索关键字com.intellij.modules
找出一堆.........发现很多Pycharm的插件,确实都有加一些依赖.然后我将其所有编辑器共有的依赖项都加上去了,如下
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
<idea-version since-build="173.0"/>
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
on how to target different products -->
<!-- uncomment to enable plugin in all products
<depends>com.intellij.modules.lang</depends>
-->
<depends>com.intellij.modules.platform</depends>
<depends>com.intellij.modules.lang</depends>
<depends>com.intellij.modules.vcs</depends>
<depends>com.intellij.modules.xml</depends>
<depends>com.intellij.modules.xdebugger</depends>
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
</extensions>
重新编译后放到Pycharm和Rider中运行正常,只要使用Ctrl + Alt + [*]的快捷键,切换了语言又会马上切换回来.
曲线救国成功\(^o^)/YES!
此插件已经上传至Jetbrains插件仓库,所以可以在仓库中直接搜索FixSgC
安装