標籤:

vim 中文輸入解決方案

做為一個 vimer,想必你也有有過這樣的遭遇:在 normal 模式下輸入 a 想進入插入模式,結果卻發現當前輸入法處於中文輸入狀態,於是進入了尷尬的組合輸入狀態:

停頓半秒之後你敲了下 delete 然後切回英文輸入按下 a 進入插入模塊,再切換回中文狀態開始正常的中文輸入。

難道說 vim 不能更聰明些嗎?

當然可以,首先要介紹下 smartim 這個插件,它可以在離開插入狀態記錄同時切換到默認輸入法,然後在下次進入插入模式後自動切換為上次插入狀態使用的輸入法。我們還可以做的更好一些:按鍵時判定輸入狀態,如果是 normal 模塊並且處於輸入法狀態,則將按鍵值直接發送給 vim 同時阻止原來的輸入法生效,這樣我們就能做到保持中文輸入法同時直接進入插入模式

我使用了基於 electron 和 neovim 提供的 RPC 調用實現的 neoclide-client 這個模塊來實現這個功能。

監控當前系統輸入法

進行系統調用獲取輸入法是有時間消耗的,如果每次 normal 模式按鍵都去獲取則必然導致輸入的延遲,首先想到的做法是監聽 input 元素的 compositionstart 和 compositionend 來判定輸入法狀態,然而這種辦法並不可行,因為 compositionstart 事件實在 keyDown 之後才會觸發,此時輸入法已經開始起作用了,而我們必須在 keyDown 時獲取到當前的輸入法狀態。所以需要系統提供對應的介面,keyboard-layout 這個模塊為我們提供了一個監聽介面,只需要簡單調用就可以做到同步輸入法:

let keyboardLayout = nnKeyboardLayout.observeCurrentKeyboardLayout(layout => {n keyboardLayout = layoutn const ev = new CustomEvent(layoutChange, {n detail: layoutn })n window.dispatchEvent(ev) // 便於其它模塊監聽n})nnexport function imeRunning() {n return keyboardLayout && keyboardLayout !== com.apple.keylayout.USn}n

僅做了針對 Mac 的處理

設置系統輸入法

因為沒找到可用的 node 模塊,所以我做了 imselect 這個使用了一點 Object-C 的 node 原生模塊。

export function defaultIM() {n if (keyboardLayout && keyboardLayout !== com.apple.keylayout.US) {n imselect.selectMethod()n return truen }n return falsen}n

在 onKeyDown 事件使用的代碼:

if (mode == normal && imeRunning() &&n !ctrlKey &&n !metaKey &&n !altKey) {n event.preventDefault()n if ([a, A, i, I, o, O].indexOf(event.key) === -1) {n setImmediate(() => defaultIM())n }n this.inputToNeovim(event.key, event)n returnn }n

監聽 vim 模式變化

我們希望 vim 在 normal 模式下總是自動切換到系統的輸入法,然而 vim 僅提供了 InsertLeave,並沒有的 CmdlineLeave 事件讓我們監聽,譬如說我們使用 / 搜索中文,回到 normal 模式還會是中文輸入法狀態。neovim 提供的 RPC 介面也不會給我們返回 cmdline 這個狀態,所以我暫時只能對 neovim 的源碼做一點修改:

diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.cnindex 56b41f1..9178538 100644n--- a/src/nvim/api/ui.cn+++ b/src/nvim/api/ui.cn@@ -271,6 +271,8 @@ static void remote_ui_mode_change(UI *ui, int mode)n ADD(args, STRING_OBJ(cstr_to_string("insert")));n } else if (mode == REPLACE) {n ADD(args, STRING_OBJ(cstr_to_string("replace")));n+ } else if (mode == CMDLINE) {n+ ADD(args, STRING_OBJ(cstr_to_string("cmdline")));n } else {n assert(mode == NORMAL);n ADD(args, STRING_OBJ(cstr_to_string("normal")));nd然iff --git a/src/nvim/ui.c b/src/nvim/ui.cnindex eb50041..3e31b90 100644n--- a/src/nvim/ui.cn+++ b/src/nvim/ui.cn@@ -537,6 +537,8 @@ static void ui_mode_change(void)n mode = REPLACE;n else if (State & INSERT)n mode = INSERT;n+ else if (State & CMDLINE)n+ mode = CMDLINE;n elsen mode = NORMAL;n UI_CALL(mode_change, mode);n

然後監聽 RPC 傳來的 mode_change 事件即可。

更新: 這部分代碼已經合併到 neovim 的 master 分支上了

Focus 事件監聽

Mac 提供了針對應用的輸入法記錄功能,可以自動還原 app 之前的輸入法狀態,建議開啟。

記錄搜索模式輸入法

通過在 onKeyDown 中我們判定 event.key (這是個比較新的 API, 很多瀏覽器並不支持) 為 『?』 或者 『/』 同時模式為 normal 可以判定 vim 即將進入搜索模式,監聽 mode_change 可在模式變為其它模式時保存當前輸入法,下次進入後自動復原。

p.on(mode_change, mode => {n const {searching} = proxyn const curMode = proxy.moden if (mode != cmdline && searching) {n util.saveCommandIm()n store.dispatch(A.toggleSearch(false))n }n if (curMode != insert && mode == normal) {n // works with smartimn util.defaultIM()n }n if (mode == cmdline && searching) {n util.selectCommandIm()n }n store.dispatch(A.changeMode(mode))n })n

醒目顏色提醒

我們還需要更加便捷的知道當前所處的輸入法狀態,所以我使用了貼心的黃色滑鼠背景:

neoclide-client 這個模塊儘管已經完成,但它實際只是為了neoclide 提供編輯的模塊,更多的主要功能還在開發中 :p

Happy vimming!


推薦閱讀:

Stack Overflow:幫助一百萬開發者退出 Vim
vim多人編輯是一種什麼樣的體驗
為什麼很多人認為編輯器比 IDE 更酷?
如何提高右手小拇指打字的靈活性?
linux下終端操作有什麼不好?

TAG:NeoVim | Vim |