vim 中文輸入解決方案
難道說 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 提供編輯的模塊,更多的主要功能還在開發中 :pHappy vimming!
推薦閱讀:
※Stack Overflow:幫助一百萬開發者退出 Vim
※vim多人編輯是一種什麼樣的體驗
※為什麼很多人認為編輯器比 IDE 更酷?
※如何提高右手小拇指打字的靈活性?
※linux下終端操作有什麼不好?