├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── ideavim_extension.iml └── src └── main ├── kotlin └── io │ └── github │ └── hadixlin │ └── iss │ ├── InputMethodAutoSwitcher.kt │ ├── InputMethodSwitcher.kt │ ├── KeepEnglishInNormalAndRestoreInInsertExtension.kt │ ├── KeepEnglishInNormalExtension.kt │ ├── SystemInputMethodSwitcher.kt │ ├── lin │ ├── LinFcitx5RimeSwitcher.kt │ ├── LinFcitxRemoteSwitcher.kt │ └── LinuxIbusSwitcher.kt │ ├── mac │ ├── MacInputMethodSwitcher.kt │ └── MacNative.kt │ └── win │ ├── WinInputMethodSwitcher.kt │ └── WinNative.kt └── resources ├── META-INF └── plugin.xml ├── darwin-aarch64 └── libinput-source-switcher.dylib └── darwin └── libinput-source-switcher.dylib /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Java template 3 | # Compiled class file 4 | *.class 5 | 6 | # BlueJ files 7 | *.ctxt 8 | 9 | # Mobile Tools for Java (J2ME) 10 | .mtj.tmp/ 11 | 12 | # Package Files # 13 | *.war 14 | *.ear 15 | *.tar.gz 16 | *.rar 17 | 18 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 19 | hs_err_pid* 20 | ### macOS template 21 | *.DS_Store 22 | .AppleDouble 23 | .LSOverride 24 | 25 | # Icon must end with two \r 26 | Icon 27 | 28 | 29 | # Thumbnails 30 | ._* 31 | 32 | # Files that might appear in the root of a volume 33 | .DocumentRevisions-V100 34 | .fseventsd 35 | .Spotlight-V100 36 | .TemporaryItems 37 | .Trashes 38 | .VolumeIcon.icns 39 | .com.apple.timemachine.donotpresent 40 | 41 | # Directories potentially created on remote AFP share 42 | .AppleDB 43 | .AppleDesktop 44 | Network Trash Folder 45 | Temporary Items 46 | .apdisk 47 | ### Gradle template 48 | .gradle 49 | /build/ 50 | 51 | # Ignore Gradle GUI config 52 | gradle-app.setting 53 | 54 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 55 | !gradle-wrapper.jar 56 | 57 | # Cache of project 58 | .gradletasknamecache 59 | 60 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 61 | # gradle/wrapper/gradle-wrapper.properties 62 | ### JetBrains template 63 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 64 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 65 | 66 | # User-specific stuff: 67 | .idea 68 | ## File-based project format: 69 | *.iws 70 | 71 | ## Plugin-specific files: 72 | 73 | # IntelliJ 74 | /out/ 75 | 76 | # mpeltonen/sbt-idea plugin 77 | .idea_modules/ 78 | 79 | # JIRA plugin 80 | atlassian-ide-plugin.xml 81 | 82 | # Crashlytics plugin (for Android Studio and IntelliJ) 83 | com_crashlytics_export_strings.xml 84 | crashlytics.properties 85 | crashlytics-build.properties 86 | fabric.properties 87 | 88 | # gradle 89 | gradlew* 90 | gradle/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IdeaVim扩展 2 | 3 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhadix-lin%2Fideavim_extension.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhadix-lin%2Fideavim_extension?ref=badge_shield) 4 | 5 | 本插件作为[IdeaVim](https://plugins.jetbrains.com/plugin/164)的扩展存在.暂时只有一个功能,就是在退出插入模式时可以切换回系统的英文输入法. 6 | 7 | 支持Windows,MacOS和Linux 8 | 9 | - Windows 需要开启英语美国键盘 10 | - MacOS 需要开启英语美国键盘或ABC键盘 11 | - Linux 需要使用fcitx输入法,通过fcitx-remote切换,或使用ibus输入引擎 12 | 13 | ## 构建/安装方法 14 | 15 | 本项目使用gradle进行管理. 16 | 17 | 执行如下命令进行构建 18 | 19 | ```shell 20 | gradle buildPlugin 21 | ``` 22 | 23 | 之后会生成 **build/distributions/IdeaVimExtension-\*.\*.\*.zip**. 24 | 25 | 通过idea的插件配置对话框选择 **install plugin from disk**即可安装该插件 26 | 27 | ## 使用 28 | 29 | 输入法自动切换功能**不会**默认启用. 30 | 31 | 编辑器中normal模式下输入下面的指令来启用自动切换输入法功能: 32 | 33 | * `:set keep-english-in-normal` 开启输入法自动切换功能 34 | * `:set keep-english-in-normal-and-restore-in-insert` 回到insert模式时恢复输入法 35 | * `:set nokeep-english-in-normal-and-restore-in-insert` 保留输入法自动切换功能,但是回到insert模式不恢复输入法 36 | * `:set nokeep-english-in-normal` 关闭输入法自动切换功能 37 | 38 | 也可以通过将`set keep-english-in-normal[-and-restore-in-insert]`加入到`~/.ideavimrc`文件中并重启IDE来启用插件功能 39 | 40 | 在macOS中,normal模式的输入法可以通过变量`keep_input_source_in_normal`来设置,仅支持在`~/.ideavimrc` 41 | 中使用.例如`let keep_input_source_in_normal="com.apple.keylayout.ABC"` 42 | 43 | 注:可以使用 [im-select](https://github.com/daipeihust/im-select) 来获取当前使用的`input source id`。比如:`ABC` 对应 `com.apple.keylayout.ABC`、`ABC Extended` 对应 `com.apple.keylayout.USExtended`。 44 | 45 | ideavim.rc中还可以通过以下几个变量控制插件行为: 46 | 47 | `let keep_input_source_in_insert=[input source id]` 设置insert模式使用到非英文输入法 48 | 49 | `let keep_input_source_in_normal=[input source id]` 设置normal模式使用到输入英文到输入法 50 | 51 | 注意:上面两个变量仅在windows和macOS中有效 52 | 53 | `let context_aware=1` 进入insert模式时根据上下文判断是否恢复输入法,0禁用,1启用 54 | 55 | Linux 下的 fcitx5-rime 可以设置 `let rime_ascii = 1` 在 normal 模式使用 Rime 的 ASCII Mode。 56 | 57 | 版本要求: 58 | - `fcitx5 > 5.0.20` 59 | - `fcitx5-rime > 5.0.8` 60 | 61 | ## 更新历史 62 | * 1.7.4 63 | 64 | 修正[issue] #121中提到的由于安装中文语言包导致无法自动切换输入法的问题 65 | 66 | * 1.7.3 67 | 68 | 兼容IdeaVIM 2.7.0 69 | 70 | * 1.7.0 71 | 72 | 支持 Linux 下的 fcitx5-rime 使用 Rime ASCII 模式切换输入法。 73 | 需要在ideavim.rc中设置`let rime_ascii = 1` 74 | 75 | - fcitx5 和 fcitx5-rime 版本要求: 76 | - `fcitx5 > 5.0.20` 77 | - `fcitx5-rime > 5.0.8` 78 | 79 | * 1.6.12 80 | 81 | 修正由于配置顺序导致无法恢复输入法的问题 82 | 83 | * 1.6.11 84 | 85 | 回退到1.6.8的编辑器焦点处理行为 86 | 87 | * 1.6.10 88 | 89 | 默认激活编辑器焦点事件切换输入法功能 90 | 91 | * 1.6.9 92 | 93 | 合并PR#113,当前编辑器失去焦点时恢复输入法 94 | 95 | * 1.6.8 96 | 97 | 修正问题[#111](https://github.com/hadix-lin/ideavim_extension/issues/111) 98 | 99 | * 1.6.7 100 | 101 | 兼容IdeaVIM 2.0.0,Idea-IC 2022.3.1+ 102 | 103 | * 1.6.6 104 | 105 | 兼容IdeaVIM 2.0.0,Idea-IC 2022.3.1+ 106 | 107 | * 1.6.6 108 | 109 | 兼容IdeaVIM 2.0.0,Idea-IC 2022.3+ 110 | context_aware默认设置为0,对不清楚用法对用户避免产生不稳定的输入发切换行为 111 | 112 | * 1.6.5 113 | 114 | 修正问题[#96](https://github.com/hadix-lin/ideavim_extension/issues/96)
115 | 使用独立线程池执行输入法切换动作,避免IDE卡顿 116 | 117 | * 1.6.4 118 | 119 | 修正问题[#95](https://github.com/hadix-lin/ideavim_extension/issues/95)
120 | 去除对独立线程池的使用,改为使用应用管理器执行输入法切换动作,减少资源消耗 121 | 122 | * 1.6.3 修正非预期情况下切换输入法的问题 123 | * 1.6.2 支持在ideavim.rc中通过以下三个变量控制插件行为: 124 | 125 | `let keep_input_source_in_insert=[input source id]` 设置insert模式使用到非英文输入法 126 | 127 | `let keep_input_source_in_normal=[input source id]` 设置normal模式使用到输入英文到输入法 128 | 129 | 注意:上面两个变量仅在windows和macOS中有效 130 | 131 | `let context_aware=1` 进入insert模式时根据上下文判断是否恢复输入法,0禁用,1启用 132 | 133 | 注意:仅在`set keep-english-in-normal-and-restore-in-insert`时有意义 134 | * 1.6.1 修正[#87](https://github.com/hadix-lin/ideavim_extension/issues/87) 135 | * 1.6.0 返回insert模式时,根据当前输入位置的字符是否为ASCII来决策是否恢复输入法 136 | * 1.5.2 兼容"IdeaVIM 1.10.0" 137 | * 1.5.0 支持Linux下的IBUS输入引擎,解决[#76](https://github.com/hadix-lin/ideavim_extension/issues/76) 138 | * 1.4.12 修正windows中的npe[问题](https://github.com/hadix-lin/ideavim_extension/issues/72) 139 | * 1.4.11 在macOS中,normal模式的输入法可以通过`keep_input_source_in_normal`来设置 140 | * 1.4.10 兼容"IdeaVIM 0.67" 141 | * 1.4.9 支持apple silicon 142 | * 1.4.8 兼容"IdeaVIM 0.61" 143 | * 1.4.7 在MacOS下支持,Unicode16进制输入法 144 | * 1.4.6 支持fcitx5 145 | * 1.4.5 兼容IdeaVim 0.56, Intellij IDEA 2020.1 146 | * 1.4.4 147 | 修正编辑器重新获取焦点时保存的输入法状态不正确的问题[#48](https://github.com/hadix-lin/ideavim_extension/issues/48) 148 | * 1.4.3 修正非预期的输入法回复问题 [#44](https://github.com/hadix-lin/ideavim_extension/issues/44) 149 | * 1.4.2 150 | 1. 根据vim指令执行后编辑器状态来判断是否需要恢复输入法 151 | 2. 修正某些情况下恢复输入法出错的问题 152 | * 1.4.1 修复某些插入命令无法恢复输入法的问题 153 | * 1.4.0 支持IdeaVim 0.54, 从这个版本起兼容性策略与IdeaVim保持一致 154 | * 1.3.9 支持IdeaVim 0.52 155 | * 1.3.8 支持自定义"Exit Insert Mode"动作的IDE快捷键 156 | * 1.3.7 修正在`~/.ideavimrc`中使用`set keep-english-in-normal-and-restore-in-insert`无效的问题 157 | * 1.3.6 默认启用输入法自动切换功能,无须使用`set xxx`命令 158 | * 1.3.5 避免抛出NoSuchFieldException 159 | * 1.3.4 修复某些插入命令无法恢复输入法的问题 160 | * 1.3.3 增加Linux下Fcitx输入法支持,ubuntu 18.04下测试通过,理论上支持所有`fcitx` 161 | * 1.3.2 修正自动切换输入法功能在旧版IDEA失效问题 162 | * 1.3.1 编辑器重新获得焦点时,如果编辑器处于NORMAL/VISUAL模式,保持输入法为英文状态 163 | * 1.3.0 使用kotlin重写插件 164 | * 1.2.3 利用异步操作避免IDE失去响应 165 | * 1.2.2 修正导致IDE崩溃的bug 166 | * 1.2.1 修正导致不能恢复到英文输入法的问题 167 | * 1.2 增加对Windows的支持, support Windows. 168 | * 1.1.5 增加对macOS的ABC键盘支持. support keylayout ABC. 169 | * 1.1.3 解决一个偶尔出现的空值异常问题. resolve a NPE problem which happen rarely; 170 | * 1.1.2 增加英文说明.append information in English. 171 | * 1.1.1 更改自动注册的按键映射为`:nnoremap a`保证在normal模式下按esc切换到英文输入法.并且执行一次默认操作 172 | * 1.1 自动注册按键映射`:nmap a`以保证normal模式下可以按esc切换到英文输入法. 添加回到insert模式恢复为原来的输入方式的能力 173 | * 1.0 首次发布,macOS下,退出插入模式可以自动切换到英文输入法 174 | 175 | ### 感谢 176 | 177 | [史荣久](https://github.com/trydofor) 贡献了支持linux下的fcitx输入请求的代码 178 | [yangxuanx](https://github.com/yangxuanx) 帮助进行linux环境下的测试 179 | [邓志宇](https://github.com/yuzhou721) 贡献了支持linux下ibus输入引擎的代码 180 | [随云](https://github.com/suiyun39) 修正了由于安装中文语言包无法自动切换输入法的问题 181 | [Goofy](https://github.com/abop) 添加了如何获取 input source id 的说明 182 | 183 | ## IdeaVimExtension 184 | 185 | The plugin is an extension of 'IdeaVim' , can switch to English input method in normal mode and restore input method in 186 | insert mode. 187 | 188 | ### How To Enable: 189 | 190 | Auto-switch feature is enabled by default 191 | 192 | You can also, In normal mode ,in an editor input :set keep-english-in-normal to enable the auto-switch 193 | feature. 194 | 195 | Use`:set keep-english-in-normal-and-restore-in-insert` instead, if you want to restore original input method after 196 | return insert mode. 197 | 198 | Or add the command to the file `~/.ideavimrc`. 199 | 200 | Use `:set nokeep-english-in-normal[-and-restore-in-insert]` to disable the auto-switch feature. 201 | 202 | ### Notice: 203 | 204 | The plugin supports **macOS** and **Windows**, support **Linux** via fcitx 205 | 206 | ### Thanks: 207 | 208 | [trydofor](https://github.com/trydofor) contributed code to support fcitx under linux 209 | [yangxuanx](https://github.com/yangxuanx) helps to test in linux environment 210 | [yuzhou721](https://github.com/yuzhou721) contributed code to support ibus under linux 211 | 212 | ## License 213 | 214 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fhadix-lin%2Fideavim_extension.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fhadix-lin%2Fideavim_extension?ref=badge_large) 215 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") version "1.8.10" 5 | id("org.jetbrains.intellij") version "1.17.2" 6 | } 7 | 8 | intellij { 9 | pluginName.set("IdeaVimExtension") 10 | version.set("2023.1.2") 11 | plugins.add("IdeaVIM:2.7.0") 12 | updateSinceUntilBuild.set(false) 13 | downloadSources.set(true) 14 | } 15 | 16 | tasks.withType { 17 | kotlinOptions.jvmTarget = "17" 18 | } 19 | 20 | dependencies { 21 | implementation(kotlin("stdlib")) 22 | } 23 | 24 | group = "io.github.hadix" 25 | version = "1.7.4" 26 | 27 | repositories { 28 | mavenCentral() 29 | } -------------------------------------------------------------------------------- /ideavim_extension.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/InputMethodAutoSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss 2 | 3 | import com.intellij.openapi.application.ApplicationManager 4 | import com.intellij.openapi.command.CommandEvent 5 | import com.intellij.openapi.command.CommandListener 6 | import com.intellij.openapi.editor.Editor 7 | import com.intellij.openapi.editor.EditorFactory 8 | import com.intellij.openapi.editor.ex.EditorEventMulticasterEx 9 | import com.intellij.openapi.editor.ex.FocusChangeListener 10 | import com.intellij.util.messages.MessageBusConnection 11 | import com.maddyhome.idea.vim.VimPlugin 12 | import com.maddyhome.idea.vim.command.CommandState 13 | import com.maddyhome.idea.vim.listener.VimInsertListener 14 | import org.apache.commons.lang3.CharUtils 15 | import org.apache.commons.lang3.StringUtils 16 | import java.lang.Long.MAX_VALUE 17 | import java.util.* 18 | import java.util.concurrent.ArrayBlockingQueue 19 | import java.util.concurrent.ThreadPoolExecutor 20 | import java.util.concurrent.TimeUnit 21 | import kotlin.math.max 22 | import kotlin.math.min 23 | 24 | object InputMethodAutoSwitcher { 25 | private val VIM_INSERT_EXIT_MODE_ACTION = arrayOf("Escape", "Esc", "VimInsertExitModeAction") 26 | 27 | private val EDITING_MODES = EnumSet.of( 28 | CommandState.Mode.INSERT, 29 | CommandState.Mode.REPLACE 30 | ) 31 | 32 | @Volatile 33 | var restoreInInsert: Boolean = false 34 | set(value) { 35 | field = value 36 | if (value) { 37 | registerVimInsertListener() 38 | } else { 39 | unregisterVimInsertListener() 40 | } 41 | } 42 | 43 | var contextAware: Boolean = false 44 | 45 | @Volatile 46 | var enabled: Boolean = false 47 | private set 48 | 49 | private var executor: ThreadPoolExecutor? = null 50 | 51 | private val switcher = SystemInputMethodSwitcher() 52 | 53 | private var messageBusConnection: MessageBusConnection? = null 54 | 55 | private val exitInsertModeListener = object : CommandListener { 56 | override fun beforeCommandFinished(commandEvent: CommandEvent) { 57 | val commandName = commandEvent.commandName 58 | if (StringUtils.isBlank(commandName)) { 59 | return 60 | } 61 | if (commandName in VIM_INSERT_EXIT_MODE_ACTION) { 62 | executor?.execute { switcher.storeCurrentThenSwitchToEnglish() } 63 | return 64 | } 65 | } 66 | } 67 | 68 | private val insertListener = object : VimInsertListener { 69 | override fun insertModeStarted(editor: Editor) { 70 | if (!editor.isInsertMode) { 71 | return 72 | } 73 | if (contextAware) { 74 | if (editor.document.charsSequence.isEmpty()) { 75 | return 76 | } 77 | val pos = editor.caretModel.primaryCaret.offset 78 | val chars = editor.document.charsSequence.subSequence( 79 | max(pos - 1, 0), 80 | min(pos + 1, editor.document.textLength - 1) 81 | ) 82 | if (chars.any { CharUtils.isAsciiPrintable(it) }) { 83 | return 84 | } 85 | } 86 | executor?.execute { switcher.restore() } 87 | } 88 | } 89 | 90 | fun enable() { 91 | if (enabled) { 92 | return 93 | } 94 | enabled = true 95 | if (executor?.isShutdown != false) { 96 | executor = ThreadPoolExecutor( 97 | 1, 1, 98 | MAX_VALUE, TimeUnit.DAYS, 99 | ArrayBlockingQueue(10), 100 | { r -> 101 | val thread = Thread(r, "ideavim_extension") 102 | thread.isDaemon = true 103 | thread.priority = Thread.MAX_PRIORITY 104 | thread 105 | }, 106 | ThreadPoolExecutor.DiscardPolicy() 107 | ) 108 | } 109 | registerExitInsertModeListener() 110 | registerFocusChangeListener() 111 | if (restoreInInsert) { 112 | registerVimInsertListener() 113 | } 114 | } 115 | 116 | private fun registerExitInsertModeListener() { 117 | messageBusConnection = ApplicationManager.getApplication().messageBus.connect() 118 | messageBusConnection?.subscribe(CommandListener.TOPIC, exitInsertModeListener) 119 | } 120 | 121 | private fun unregisterExitInsertModeListener() { 122 | messageBusConnection?.disconnect() 123 | } 124 | 125 | private fun registerVimInsertListener() { 126 | VimPlugin.getChange().addInsertListener(insertListener) 127 | } 128 | 129 | private fun unregisterVimInsertListener() { 130 | VimPlugin.getChange().removeInsertListener(insertListener) 131 | } 132 | 133 | private fun registerFocusChangeListener() { 134 | val eventMulticaster = 135 | EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx ?: return 136 | eventMulticaster.addFocusChangeListener(focusListener) {} 137 | } 138 | 139 | private val focusListener = object : FocusChangeListener { 140 | 141 | override fun focusLost(editor: Editor) { 142 | } 143 | 144 | override fun focusGained(editor: Editor) { 145 | if (!enabled || !VimPlugin.isEnabled()) { 146 | return 147 | } 148 | val state = CommandState.getInstance(editor) 149 | if (state.mode !in EDITING_MODES) { 150 | executor?.execute { switcher.switchToEnglish() } 151 | } 152 | } 153 | } 154 | 155 | 156 | fun disable() { 157 | if (!enabled) { 158 | return 159 | } 160 | unregisterVimInsertListener() 161 | unregisterExitInsertModeListener() 162 | executor?.shutdown() 163 | enabled = false 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/InputMethodSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss 2 | 3 | /** 4 | * 5 | * @author hadix.ly 6 | * @date 2018-12-23 7 | */ 8 | interface InputMethodSwitcher { 9 | 10 | /** 保存当前输入法,然后切换输入法到英文 */ 11 | fun storeCurrentThenSwitchToEnglish() 12 | 13 | /** 将系统恢复到上次调用 [storeCurrentThenSwitchToEnglish] 时保存的输入法 */ 14 | fun restore() 15 | 16 | /** 切换输入法到英文 */ 17 | fun switchToEnglish() 18 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/KeepEnglishInNormalAndRestoreInInsertExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss 2 | 3 | import com.maddyhome.idea.vim.VimPlugin 4 | import com.maddyhome.idea.vim.api.setToggleOption 5 | import com.maddyhome.idea.vim.ex.exExceptionMessage 6 | import com.maddyhome.idea.vim.extension.VimExtension 7 | import com.maddyhome.idea.vim.options.OptionAccessScope 8 | import com.maddyhome.idea.vim.options.ToggleOption 9 | 10 | 11 | /** 12 | * @author hadix 13 | * @date 31/03/2017 14 | */ 15 | class KeepEnglishInNormalAndRestoreInInsertExtension : VimExtension { 16 | 17 | override fun getName(): String { 18 | return NAME 19 | } 20 | 21 | override fun init() { 22 | InputMethodAutoSwitcher.restoreInInsert = true 23 | InputMethodAutoSwitcher.contextAware = 24 | VimPlugin.getVariableService().getGlobalVariableValue(CONTEXT_WARE)?.asBoolean() ?: true 25 | val optionGroup = VimPlugin.getOptionGroup() 26 | val option = 27 | (optionGroup.getOption(KeepEnglishInNormalExtension.NAME) 28 | ?: throw exExceptionMessage("option not found")) as ToggleOption 29 | optionGroup.setToggleOption(option, OptionAccessScope.GLOBAL(null)) 30 | } 31 | 32 | override fun dispose() { 33 | InputMethodAutoSwitcher.restoreInInsert = false 34 | } 35 | 36 | companion object { 37 | private const val CONTEXT_WARE = "context_aware" 38 | private const val NAME = "keep-english-in-normal-and-restore-in-insert" 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/KeepEnglishInNormalExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss 2 | 3 | import com.maddyhome.idea.vim.extension.VimExtension 4 | 5 | /** 6 | * @author hadix 7 | * @date 09/04/2017 8 | */ 9 | class KeepEnglishInNormalExtension : VimExtension { 10 | 11 | override fun getName(): String { 12 | return NAME 13 | } 14 | 15 | override fun init() { 16 | InputMethodAutoSwitcher.enable() 17 | } 18 | 19 | 20 | override fun dispose() { 21 | InputMethodAutoSwitcher.disable() 22 | } 23 | 24 | companion object { 25 | const val NAME = "keep-english-in-normal" 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/SystemInputMethodSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss 2 | 3 | import com.maddyhome.idea.vim.VimPlugin 4 | import io.github.hadixlin.iss.lin.LinFcitx5RimeSwitcher 5 | import io.github.hadixlin.iss.lin.LinFcitxRemoteSwitcher 6 | import io.github.hadixlin.iss.lin.LinuxIbusSwitcher 7 | import io.github.hadixlin.iss.mac.MacInputMethodSwitcher 8 | import io.github.hadixlin.iss.win.WinInputMethodSwitcher 9 | import org.apache.commons.lang3.SystemUtils 10 | import java.util.* 11 | import java.util.concurrent.TimeUnit 12 | 13 | 14 | /** Created by hadix on 26/12/2018. */ 15 | class SystemInputMethodSwitcher 16 | : InputMethodSwitcher { 17 | private val delegate: InputMethodSwitcher by lazy { 18 | 19 | //获取输入法设置的环境变量 20 | //当前系统环境变量判断 21 | val variableService = VimPlugin.getVariableService() 22 | val english = variableService.getGlobalVariableValue(INPUT_SOURCE_IN_NORMAL) 23 | val nonEnglish = variableService.getGlobalVariableValue(INPUT_SOURCE_IN_INSERT) 24 | return@lazy when { 25 | SystemUtils.IS_OS_WINDOWS -> { 26 | WinInputMethodSwitcher( 27 | english?.toVimNumber()?.value?.toLong(), nonEnglish?.toVimNumber()?.value?.toLong() 28 | ) 29 | } 30 | 31 | SystemUtils.IS_OS_MAC -> { 32 | if (english != null) { 33 | MacInputMethodSwitcher(english.asString(), nonEnglish = nonEnglish?.asString()) 34 | } else { 35 | MacInputMethodSwitcher(nonEnglish = nonEnglish?.asString()) 36 | } 37 | } 38 | 39 | SystemUtils.IS_OS_LINUX -> { 40 | //获取输入法设置的环境变量 41 | val qtInputMethod = System.getenv(QT_INPUT_METHOD) 42 | val gtkInputMethod = System.getenv(GTK_INPUT_METHOD) 43 | //当前系统环境变量判断 44 | if (isFcitx(qtInputMethod, gtkInputMethod)) { 45 | if (canUseRimeAscii()) { 46 | LinFcitx5RimeSwitcher() 47 | } else { 48 | LinFcitxRemoteSwitcher() 49 | } 50 | } else if (isIbus(qtInputMethod, gtkInputMethod)) { 51 | LinuxIbusSwitcher() 52 | } else { 53 | throw IllegalArgumentException("Not Support Current Input Method [${qtInputMethod ?: gtkInputMethod}], Only Support Linux(with fcitx and ibus)") 54 | } 55 | } 56 | 57 | else -> throw IllegalArgumentException("Not Support Current System OS, Only Support Windows, MacOS and Linux(with fcitx and ibus)") 58 | } 59 | } 60 | 61 | 62 | override fun switchToEnglish() { 63 | delegate.switchToEnglish() 64 | } 65 | 66 | override fun storeCurrentThenSwitchToEnglish() { 67 | delegate.storeCurrentThenSwitchToEnglish() 68 | } 69 | 70 | override fun restore() { 71 | delegate.restore() 72 | } 73 | 74 | companion object { 75 | private const val INPUT_SOURCE_IN_NORMAL = "keep_input_source_in_normal" 76 | private const val INPUT_SOURCE_IN_INSERT = "keep_input_source_in_insert" 77 | private const val INPUT_METHOD_FCITX = "fcitx" 78 | private const val INPUT_METHOD_FCITX5 = "fcitx5" 79 | private const val INPUT_METHOD_IBUS = "ibus" 80 | private const val QT_INPUT_METHOD = "QT_IM_MODULE" 81 | private const val GTK_INPUT_METHOD = "GTK_IM_MODULE" 82 | private const val RIME_ASCII_MODE = "rime_ascii" 83 | 84 | fun isFcitx(qtInputMethod: String?, gtkInputMethod: String?): Boolean { 85 | return qtInputMethod == INPUT_METHOD_FCITX 86 | || gtkInputMethod == INPUT_METHOD_FCITX 87 | || qtInputMethod == INPUT_METHOD_FCITX5 88 | || gtkInputMethod == INPUT_METHOD_FCITX5 89 | } 90 | 91 | fun canUseRimeAscii(): Boolean { 92 | val rimeAscii = VimPlugin.getVariableService().getGlobalVariableValue(RIME_ASCII_MODE)?.asBoolean() ?: false 93 | if (!rimeAscii) { 94 | return false 95 | } 96 | val cmd = arrayOf( 97 | "busctl", "--user", "call", "org.fcitx.Fcitx5", "/controller", 98 | "org.fcitx.Fcitx.Controller1", "CurrentInputMethod" 99 | ) 100 | val proc = Runtime.getRuntime().exec(cmd) 101 | proc.waitFor(3, TimeUnit.SECONDS) 102 | return Scanner(proc.inputStream).use { 103 | if (it.hasNextLine()) { 104 | val line = it.nextLine() 105 | line.startsWith("s") && line.contains("rime") 106 | } else { 107 | false 108 | } 109 | } 110 | } 111 | 112 | fun isIbus(qtInputMethod: String?, gtkInputMethod: String?): Boolean = 113 | qtInputMethod == INPUT_METHOD_IBUS || gtkInputMethod == INPUT_METHOD_IBUS 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/lin/LinFcitx5RimeSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.lin 2 | 3 | import io.github.hadixlin.iss.InputMethodSwitcher 4 | import java.util.* 5 | import java.util.concurrent.TimeUnit 6 | 7 | class LinFcitx5RimeSwitcher : InputMethodSwitcher { 8 | 9 | private var lastStatus = STATUS_ASCII 10 | 11 | override fun storeCurrentThenSwitchToEnglish() { 12 | val currentStatus = getFcitxRimeStatus() 13 | lastStatus = currentStatus 14 | if (currentStatus == STATUS_IME){ 15 | switchToEnglish() 16 | } 17 | } 18 | 19 | override fun restore() { 20 | if (lastStatus != STATUS_ASCII){ 21 | execFcitxRime(FCITX_RIME_IME) 22 | } 23 | } 24 | 25 | override fun switchToEnglish() { 26 | execFcitxRime(FCITX_RIME_ASCII) 27 | } 28 | 29 | companion object { 30 | private const val BUS_TYPE = "b" 31 | private const val STATUS_ASCII = "true" 32 | private const val STATUS_IME = "false" 33 | 34 | private val FCITX_RIME_IME: Array 35 | private val FCITX_RIME_ASCII: Array 36 | private val FCITX_RIME_STATUS: Array 37 | 38 | init { 39 | val busctlCall: Array 40 | 41 | busctlCall = arrayOf("busctl", 42 | "call", "--user", "org.fcitx.Fcitx5", 43 | "/rime", "org.fcitx.Fcitx.Rime1") 44 | 45 | FCITX_RIME_IME = busctlCall.plus(arrayOf("SetAsciiMode", BUS_TYPE, STATUS_IME)) // active input method 46 | FCITX_RIME_ASCII = busctlCall.plus(arrayOf("SetAsciiMode", BUS_TYPE, STATUS_ASCII)) //inactivate input method 47 | FCITX_RIME_STATUS = busctlCall.plus(arrayOf("IsAsciiMode")) //get fcitx status 48 | } 49 | 50 | private fun execFcitxRime(cmd: Array): Process { 51 | val proc = Runtime.getRuntime().exec(cmd) 52 | proc.waitFor(3, TimeUnit.SECONDS) 53 | return proc 54 | } 55 | 56 | private fun getFcitxRimeStatus(): String { 57 | val proc = execFcitxRime(FCITX_RIME_STATUS) 58 | return Scanner(proc.inputStream).use { 59 | if (it.hasNext()) { 60 | val firstToken = it.next() 61 | if (firstToken == BUS_TYPE && it.hasNextBoolean()){ 62 | "${it.nextBoolean()}" 63 | }else { 64 | STATUS_ASCII 65 | } 66 | } else { 67 | STATUS_ASCII 68 | } 69 | } 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/lin/LinFcitxRemoteSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.lin 2 | 3 | import io.github.hadixlin.iss.InputMethodSwitcher 4 | import java.util.* 5 | import java.util.concurrent.TimeUnit 6 | 7 | class LinFcitxRemoteSwitcher : InputMethodSwitcher { 8 | 9 | override fun storeCurrentThenSwitchToEnglish() { 10 | switchToEnglish() 11 | } 12 | 13 | override fun restore() { 14 | execFcitxRemote(FCITX_ACTIVE) 15 | } 16 | 17 | override fun switchToEnglish() { 18 | execFcitxRemote(FCITX_INACTIVE) 19 | } 20 | 21 | companion object { 22 | private const val STATUS_UNKNOWN = -1 23 | 24 | private val FCITX_ACTIVE: Array 25 | private val FCITX_INACTIVE: Array 26 | private val FCITX_STATUS: Array 27 | 28 | init { 29 | var fcitxRemote: String 30 | try { 31 | fcitxRemote = "fcitx-remote" 32 | execFcitxRemote(arrayOf(fcitxRemote)) 33 | } catch (e: Exception) { 34 | fcitxRemote = "fcitx5-remote" 35 | execFcitxRemote(arrayOf(fcitxRemote)) 36 | } 37 | 38 | FCITX_ACTIVE = arrayOf(fcitxRemote, "-o") // active input method 39 | FCITX_INACTIVE = arrayOf(fcitxRemote, "-c") //inactivate input method 40 | FCITX_STATUS = arrayOf(fcitxRemote) //get fcitx status 41 | } 42 | 43 | private fun execFcitxRemote(cmd: Array): Process { 44 | val proc = Runtime.getRuntime().exec(cmd) 45 | proc.waitFor(3, TimeUnit.SECONDS) 46 | return proc 47 | } 48 | 49 | @Suppress("unused") 50 | private fun getFcitxStatus(): Int { 51 | val proc = execFcitxRemote(FCITX_STATUS) 52 | return Scanner(proc.inputStream).use { 53 | if (it.hasNextInt()) { 54 | it.nextInt() 55 | } else { 56 | STATUS_UNKNOWN 57 | } 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/lin/LinuxIbusSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.lin 2 | 3 | import io.github.hadixlin.iss.InputMethodSwitcher 4 | import java.util.* 5 | import java.util.concurrent.TimeUnit 6 | 7 | class LinuxIbusSwitcher : InputMethodSwitcher { 8 | 9 | private var lastStatus: String = STATUS_ENGLISH 10 | override fun storeCurrentThenSwitchToEnglish() { 11 | val current = getStatus() 12 | if (current == STATUS_ENGLISH) { 13 | return 14 | } 15 | lastStatus = current 16 | switchToEnglish() 17 | } 18 | 19 | override fun restore() { 20 | if (lastStatus != STATUS_ENGLISH) { 21 | execIbusCmd(lastStatus) 22 | } 23 | } 24 | 25 | override fun switchToEnglish() { 26 | execIbusCmd(STATUS_ENGLISH) 27 | } 28 | 29 | companion object { 30 | private const val STATUS_ENGLISH = "xkb:us::eng" 31 | private fun execIbusCmd(im: String): Process { 32 | val cmd = arrayOf("ibus", "engine", im) 33 | val proc = Runtime.getRuntime().exec(cmd) 34 | proc.waitFor(3, TimeUnit.SECONDS) 35 | return proc 36 | } 37 | 38 | private fun getStatus(): String { 39 | val cmd = arrayOf("ibus", "engine") 40 | val proc = Runtime.getRuntime().exec(cmd) 41 | proc.waitFor(3, TimeUnit.SECONDS) 42 | return Scanner(proc.inputStream).use { 43 | if (it.hasNextLine()) { 44 | it.nextLine() 45 | } else { 46 | "" 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/mac/MacInputMethodSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.mac 2 | 3 | import io.github.hadixlin.iss.InputMethodSwitcher 4 | import io.github.hadixlin.iss.mac.MacNative.getCurrentInputSourceID 5 | import io.github.hadixlin.iss.mac.MacNative.switchInputSource 6 | import org.apache.commons.lang.StringUtils.EMPTY 7 | 8 | /** 9 | * @author hadix 10 | * @date 2018-12-23 11 | */ 12 | class MacInputMethodSwitcher( 13 | private vararg val english: String = ENGLISH_INPUT_SOURCE_CANDIDATE, 14 | private var nonEnglish: String? 15 | ) : InputMethodSwitcher { 16 | 17 | override fun storeCurrentThenSwitchToEnglish() { 18 | val current = getCurrentInputSourceID() 19 | if (ENGLISH_INPUT_SOURCE == current) { 20 | return 21 | } 22 | if (nonEnglish == null) { 23 | this.nonEnglish = current 24 | } 25 | switchToEnglish() 26 | } 27 | 28 | override fun switchToEnglish() { 29 | if (ENGLISH_INPUT_SOURCE.isNotBlank()) { 30 | if (switchInputSource(ENGLISH_INPUT_SOURCE) < 0) { 31 | // 系统英文输入法可能发生变更,导致无法切换到记录到英文输入法 32 | ENGLISH_INPUT_SOURCE = EMPTY 33 | } 34 | } else { 35 | for (englishInputSource in english) { 36 | if (switchInputSource(englishInputSource) < 0) { 37 | continue 38 | } else { 39 | ENGLISH_INPUT_SOURCE = englishInputSource 40 | break 41 | } 42 | } 43 | } 44 | } 45 | 46 | override fun restore() { 47 | val nonEnglish = this.nonEnglish ?: return 48 | switchInputSource(nonEnglish) 49 | } 50 | 51 | @Suppress("SpellCheckingInspection") 52 | companion object { 53 | private const val KEY_LAYOUT_US = "com.apple.keylayout.US" 54 | private const val KEY_LAYOUT_ABC = "com.apple.keylayout.ABC" 55 | private const val KEY_LAYOUT_UNICODEHEX = "com.apple.keylayout.UnicodeHexInput" 56 | private val ENGLISH_INPUT_SOURCE_CANDIDATE = arrayOf(KEY_LAYOUT_UNICODEHEX, KEY_LAYOUT_ABC, KEY_LAYOUT_US) 57 | private var ENGLISH_INPUT_SOURCE: String = "" 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/mac/MacNative.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.mac 2 | 3 | import com.sun.jna.Native 4 | 5 | /** Created by hadix on 30/03/2017. */ 6 | object MacNative { 7 | 8 | init { 9 | Native.register("input-source-switcher") 10 | } 11 | 12 | external fun getCurrentInputSourceID(): String 13 | 14 | external fun switchInputSource(inputSourceID: String): Int 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/win/WinInputMethodSwitcher.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.win 2 | 3 | import com.sun.jna.Pointer 4 | import com.sun.jna.platform.win32.WinDef 5 | import io.github.hadixlin.iss.InputMethodSwitcher 6 | 7 | class WinInputMethodSwitcher( 8 | english: Long?, private var nonEnglish: Long? 9 | ) : InputMethodSwitcher { 10 | 11 | private val english: Long = english ?: KEY_LAYOUT_US_DEFAULT 12 | override fun storeCurrentThenSwitchToEnglish() { 13 | val hwnd = WinNative.INSTANCE.GetForegroundWindow() ?: return 14 | val current = getCurrentInputSource(hwnd) 15 | if (current == english) { 16 | return 17 | } 18 | if (nonEnglish == null) { 19 | nonEnglish = current 20 | } 21 | switchToInputSource(hwnd, english) 22 | } 23 | 24 | private fun switchToInputSource(hwnd: WinDef.HWND, inputSourceId: Long) { 25 | WinNative.INSTANCE.PostMessage( 26 | hwnd, 27 | WM_INPUT_LANG_CHANGE_REQUEST, 28 | WinDef.WPARAM(0), 29 | WinDef.LPARAM(inputSourceId) 30 | ) 31 | } 32 | 33 | override fun restore() { 34 | val nonEnglish = this.nonEnglish ?: return 35 | val hwnd = WinNative.INSTANCE.GetForegroundWindow() ?: return 36 | switchToInputSource(hwnd, nonEnglish) 37 | } 38 | 39 | override fun switchToEnglish() { 40 | val hwnd = WinNative.INSTANCE.GetForegroundWindow() ?: return 41 | switchToInputSource(hwnd, english) 42 | } 43 | 44 | companion object { 45 | private const val KEY_LAYOUT_US_DEFAULT: Long = 0x4090409 46 | private const val WM_INPUT_LANG_CHANGE_REQUEST = 0x0050 47 | 48 | private fun getCurrentInputSource(hwnd: WinDef.HWND?): Long { 49 | val handle = hwnd ?: WinNative.INSTANCE.GetForegroundWindow() 50 | val pid = WinNative.INSTANCE.GetWindowThreadProcessId(handle, null) 51 | val hkl = WinNative.INSTANCE.GetKeyboardLayout(WinDef.DWORD(pid.toLong())) 52 | return Pointer.nativeValue(hkl.pointer) 53 | } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/hadixlin/iss/win/WinNative.kt: -------------------------------------------------------------------------------- 1 | package io.github.hadixlin.iss.win 2 | 3 | import com.sun.jna.Native 4 | import com.sun.jna.platform.win32.User32 5 | import com.sun.jna.platform.win32.WinDef 6 | import com.sun.jna.platform.win32.WinNT 7 | import com.sun.jna.win32.W32APIOptions 8 | 9 | /** 10 | * @author hadix 11 | */ 12 | interface WinNative : User32 { 13 | 14 | @Suppress("FunctionName") 15 | fun GetKeyboardLayout(pid: WinDef.DWORD): WinNT.HANDLE 16 | 17 | companion object { 18 | val INSTANCE = Native.load( 19 | "user32", 20 | WinNative::class.java, 21 | W32APIOptions.DEFAULT_OPTIONS 22 | ) as WinNative 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | IdeaVimExtension 3 | IdeaVimExtension 4 | 5 | 8 | hadix 9 | 10 | 11 | The plugin is an extension of 'IdeaVim' , can switch to English input method in normal mode and restore input method in insert mode.

13 |

How To Enable:

14 |

auto-switch feature is disabled by default

15 |

16 | enable the feature with the commands below, input in normal mode: 17 |

    18 |
  • :set keep-english-in-normal enable auto-switch feature
  • 19 |
  • :set keep-english-in-normal-and-restore-in-insert restore input method when return insert mode.
  • 20 |
  • :set nokeep-english-in-normal-and-restore-in-insert keep auto-switch feature, but doesn't restore input method when return insert mode
  • 21 |
  • :set nokeep-english-in-normal disable auto-switch feature
  • 22 |
23 | You can also add `set keep-english-in-normal[-and-restore-in-insert]` to the `~/.ideavimrc` file and restart IDE to enable the feature. 24 |
25 | 26 |

27 |

Notice:

28 |

The plugin support MacOS and Windows and Linux(with fcitx)

29 |

30 | MacOS need enable en_US OR ABC keyboard
31 | Windows need enable en_US keyboard
32 | Linux need fcitx-remote, or ibus
33 |

34 |

35 | Support for controlling plugin behavior in ideavim.rc via the following three variables:
36 | let keep_input_source_in_insert=[input source id] set input method for non-English used in insert mode
37 | let keep_input_source_in_normal=[input source id] set input method for English used in normal mode
38 | Note: The above two variables are only valid in windows and macOS
39 | let context_aware=1 When entering insert mode, judge whether to restore the input method according to the context, 0 to disable, 1 to enable
40 | Note: Only meaningful when set keep-english-in-normal-and-restore-in-insert
41 |

42 |

GitHub RepositorySource and Feedback

43 |
44 |

为IdeaVim插件增加自动切换为英文输入法的功能

45 |

输入法自动切换功能不会默认启用

46 |

47 | 编辑器中normal模式下输入输入下面的指令以启用自动切换输入法功能: 48 |

    49 |
  • :set keep-english-in-normal 开启输入法自动切换功能
  • 50 |
  • :set keep-english-in-normal-and-restore-in-insert 回到insert模式时恢复输入法
  • 51 |
  • :set nokeep-english-in-normal-and-restore-in-insert 保留输入法自动切换功能,但是回到insert模式不恢复输入法
  • 52 |
  • :set nokeep-english-in-normal 关闭输入法自动切换功能
  • 53 |
54 | 也可以通过将`set keep-english-in-normal[-and-restore-in-insert]`加入到`~/.ideavimrc`文件中并重启IDE来启用插件功能。 55 |

56 |

注意:支持MacOS和Windows, 通过fcitx-remote支持Linux

57 |

58 | MacOS需要开启英语美国键盘或ABC键盘
59 | Windows需要开启英语美国键盘
60 | Linux需要使用fcitx输入法,通过fcitx-remote切换,或使用ibus输入引擎
61 |

62 |

63 | 支持在ideavim.rc中通过以下三个变量控制插件行为:
64 | let keep_input_source_in_insert=[input source id] 设置insert模式使用的非英文输入法
65 | let keep_input_source_in_normal=[input source id] 设置normal模式使用的输入英文到输入法
66 | 注意:上面两个变量仅在windows和macOS中有效
67 | let context_aware=1 进入insert模式时根据上下文判断是否恢复输入法,0禁用,1启用
68 | 注意:仅在set keep-english-in-normal-and-restore-in-insert时有意义
69 |

70 |

71 |

 72 |       Linux 下的 fcitx5-rime 可以设置 `let rime_ascii = 1` 在 normal 模式使用 Rime 的 ASCII Mode。
 73 |       版本要求:
 74 |        - `fcitx5 > 5.0.20`
 75 |        - `fcitx5-rime > 5.0.8`
 76 |       
77 |

78 |

GitHub Repository: 源码和问题反馈

79 | ]]>
80 | 81 | 1.7.4
83 | Fixed the problem that the input method couldn't be switch automatically due to install the Chinese language pack mentioned in [issue] #121 84 |

1.7.3
85 | Compatible with "IdeaVIM 2.7.0"
86 |

1.7.0
87 |

 88 |        support using "Rime ASCII" mode to switch input method
 89 | 
 90 |        require set `let rime_ascii = 1` in ideavim.rc.
 91 |        - fcitx5 and fcitx5-rime version must follow:
 92 |             - `fcitx5 > 5.0.20`
 93 |             - `fcitx5-rime > 5.0.8`
 94 |       
95 |

1.6.12
96 | Fix the problem of being unable to restore the input method due to configuration order 97 |

1.6.11
98 | Fallback to editor focus handling behavior of 1.6.8 99 |

1.6.10
100 | default open focus auto 101 |

1.6.9
102 | merged PR#113, restore the input method when the current editor loses focus 103 |

1.6.8
104 | fix #111
105 |

1.6.7
106 | Compatible with IdeaVIM 2.0.0, Idea-IC 2022.3.1+
107 |

1.6.6
108 | Compatible with IdeaVIM 2.0.0, Idea-IC 2022.3+
109 | context_aware is set to 0 by default, avoiding unstable input method switching behaviors for users who are unclear about usage 110 |

1.6.5
111 | Fix #96
112 | Use an independent thread pool to perform input method switching to avoid IDE lag 113 |

1.6.4
114 | Fix #95
115 | Eliminate the use of independent thread pools, use ApplicationManager to perform input method switching action, and reduce resource consumption 116 |

1.6.3
117 | Fix the problem of switching input methods in unexpected situations 118 |

1.6.2
119 | Support for controlling plugin behavior in ideavim.rc via the following three variables:
120 | let keep_input_source_in_insert=[input source id] set input method for non-English used in insert mode
121 | let keep_input_source_in_normal=[input source id] set input method for English used in normal mode
122 | Note: The above two variables are only valid in windows and macOS
123 | let context_aware=1 When entering insert mode, judge whether to restore the input method according to the context, 0 to disable, 1 to enable
124 | Note: Only meaningful when set keep-english-in-normal-and-restore-in-insert
125 |

1.6.1
126 | Fix Issue #87
127 |

1.6.0
128 | When returning to insert mode, decide whether to restore the input method according to whether the character at the current input position is ASCII or not
129 |

130 |

1.5.2
131 | Compatible with "IdeaVIM 1.10.0"
132 |

133 |

1.5.0
134 | Support IBUS input engine under Linux, fix #76
135 |

136 |

1.4.12
137 | Fix npe Issue #72 in windows
138 |

139 |

1.4.11
140 | In macOS, the input method in normal mode can be set by keep_input_source_in_normal,but it is only supported in ~/.ideavimrc`.
141 | For example, let keep_input_source_in_normal="com .apple.keylayout.ABC" 142 |

143 |

1.4.10
144 | Compatible with "IdeaVIM 0.67"
145 |

146 |

1.4.9
147 | support apple silicon
148 |

149 |

1.4.8
150 | Compatible with "IdeaVIM 0.61"
151 |

152 |

1.4.7
153 | Support Unicode Hex Input Method in MacOS.
154 |

155 |

1.4.6
156 | Support fcitx5.
157 |

158 |

1.4.5
159 | Compatible with "IdeaVIM 0.56" and "Intellij IDEA 2020.1"
160 |

161 |

1.4.4
162 | Fix the [issue] #48 caused by incorrect input method state saved when the editor regains focus
163 |

164 |

1.4.3
165 | Fixed the unexpected input method restoration issue
166 |

167 |

1.4.2
168 | 1.Determine whether to restore the input method based on the state of the editor after the vim instruction is executed
169 | 2.Fix the problem of recovering input method in some cases
170 |

171 |

1.4.1
172 | fix the problem that some insert commands cannot restore input method
173 |

174 |

1.4.0
175 | support IdeaVim 0.54, keep consistent compatibility strategy with IdeaVim starting with this version.
176 |

177 |
178 | 179 |

1.7.4
180 | 修正[issue] #121中提到的由于安装中文语言包导致无法自动切换输入法的问题 181 |

1.7.3
182 | 兼容"IdeaVIM 2.7.0"
183 |

1.7.0
184 |

185 |        支持 Linux 下的 fcitx5-rime 使用 Rime ASCII 模式切换输入法。
186 |        需要在ideavim.rc中设置`let rime_ascii = 1`
187 |        - fcitx5 和 fcitx5-rime 版本要求:
188 |             - `fcitx5 > 5.0.20`
189 |             - `fcitx5-rime > 5.0.8`
190 |       
191 |

1.6.12
192 | 修正由于配置顺序导致无法恢复输入法的问题 193 |

1.6.11
194 | 回退到1.6.8的编辑器焦点处理行为 195 |

1.6.10
196 | 默认激活编辑器焦点事件切换输入法功能 197 |

1.6.9
198 | 合并PR#113,当前编辑器失去焦点时恢复输入法 199 |

1.6.8
200 | 修正问题 #111
201 |

1.6.7
202 | 兼容IdeaVIM 2.0.0,Idea-IC 2022.3.1+
203 |

1.6.6
204 | 兼容IdeaVIM 2.0.0,Idea-IC 2022.3+
205 | context_aware默认设置为0,对不清楚用法对用户避免产生不稳定的输入发切换行为 206 |

1.6.5
207 | 修正问题#96
208 | 使用独立线程池执行输入法切换动作,避免IDE卡顿 209 |

1.6.4
210 | 修正问题#95
211 | 去除对独立线程池的使用,改为使用应用管理器执行输入法切换动作,减少资源消耗 212 |

1.6.3
213 | 修正非预期情况下切换输入法的问题 214 |

1.6.2
215 | 支持在ideavim.rc中通过以下三个变量控制插件行为:
216 | let keep_input_source_in_insert=[input source id] 设置insert模式使用到非英文输入法
217 | let keep_input_source_in_normal=[input source id] 设置normal模式使用到输入英文到输入法
218 | 注意:上面两个变量仅在windows和macOS中有效
219 | let context_aware=1 进入insert模式时根据上下文判断是否恢复输入法,0禁用,1启用
220 | 注意:仅在set keep-english-in-normal-and-restore-in-insert时有意义
221 |

1.6.1
222 | 修正#87
223 |

1.6.0
224 | 返回insert模式时,根据当前输入位置的字符是否为ASCII来决策是否恢复输入法
225 |

226 |

1.5.2
227 | 兼容"IdeaVIM 1.10.0"
228 |

229 |

1.5.0
230 | 支持Linux下的IBUS输入引擎,解决#76
231 |

232 |

1.4.12
233 | 修正windows中的npe问题#72
234 |

235 |

1.4.11
236 | 在macOS中,normal模式的输入法可以通过keep_input_source_in_normal来设置,但仅支持在~/.ideavimrc`中使用.
237 | 例如let keep_input_source_in_normal="com.apple.keylayout.ABC"
238 |

239 |

1.4.10
240 | 兼容"IdeaVIM 0.67"
241 |

242 |

1.4.9
243 | 支持apple silicon
244 |

245 |

1.4.8
246 | 兼容"IdeaVIM 0.61"
247 |

248 |

1.4.7
249 | 在MacOS下支持,Unicode16进制输入法
250 |

251 |

1.4.6
252 | 支持fcitx5
253 |

254 |

1.4.5
255 | 兼容"IdeaVim 0.56"和"Intellij IDEA 2020.1"
256 |

257 |

1.4.4
258 | 修正编辑器重新获取焦点时保存的输入法状态不正确的问题
259 |

260 |

1.4.3
261 | 修正非预期的输入法恢复问题
262 |

263 |

1.4.2
264 | 1.根据vim指令执行后编辑器状态来判断是否需要恢复输入法
265 | 2.修正某些情况下恢复输入法出错的问题
266 |

267 |

1.4.1
268 | 修复某些插入命令无法恢复输入法的问题
269 |

270 |

1.4.0
271 | 支持IdeaVim 0.54, 从这个版本开始兼容性策略与IdeaVim保持一致
272 |

273 | ]]> 274 |
275 | 276 | 277 | 278 | com.intellij.modules.lang 279 | IdeaVIM 280 | 281 | 282 | 284 | 286 | 287 |
-------------------------------------------------------------------------------- /src/main/resources/darwin-aarch64/libinput-source-switcher.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadix-lin/ideavim_extension/3e87320e55c328adcc718b3653f1a65325d09f58/src/main/resources/darwin-aarch64/libinput-source-switcher.dylib -------------------------------------------------------------------------------- /src/main/resources/darwin/libinput-source-switcher.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hadix-lin/ideavim_extension/3e87320e55c328adcc718b3653f1a65325d09f58/src/main/resources/darwin/libinput-source-switcher.dylib --------------------------------------------------------------------------------