macOSのVimで日本語入力の誤爆を防ぐ karabiner不使用版
IMEが有効になったままだとVimのノーマルモード操作で突っかかる
「日本語入力がオンのままVimを操作しようとして失敗する」
日本語入力状態のまま j を入力してしまった例
別にVimが悪いわけではなくIMEが入力をインターセプトする仕組みになっていることが原因なのですが、我々日本人がVimを使ったときにはよく発生しがちです。
Karabiner-Elementsを使うのが一般的
この問題を解決するため、macOS向けで多く提案されているのがKarabiner-Elementsを使用した方法です。
これは<esc>
を押したときに同時に英数キーを送信するもので、これにより「Insert modeを抜けるとき、ついでにIMEをオフにする」ようになります。
この手法の問題点は<esc>
がトリガーになっていることで、当然ですが<esc>
を押さずノーマルモードへ移行する場合には日本語入力状態のままです。具体的には、以下のような場合が該当します。いずれもインサートモードを介しておらず、<esc>
に英数キーが入っていても効果がありません。
- 日本語を検索して
<cr>
で実行した(コマンドラインモード → ノーマルモード) - Vim外で日本語入力をしたあとVimにフォーカスを移した(Vim外 → ノーマルモード)
筆者は特に後者の問題に悩まされており、「ブラウザで何か日本語で検索→Vimに戻る→操作する→引っかかる→日本語入力のままだったことに気づく」という現象が多発していました。
system()
で実行すれば良い
そもそもmacOSはターミナルからキーコードを送信できるのでそれを既存手法の問題は<esc>
しか考慮されていないことでした。これを解決するため、「autocmdで英数キーを送信する」手法を考えました。
macOSではosascriptによってシステムの制御を行うことができます。
AppleScriptまたはJavaScriptでコードを書けます。個人的にはJavaScriptのほうが得意なので(あと短く書けるので)、この記事ではJavaScriptを使用します。
英数キーを送信するJSコードは以下のとおりです。
// 102 (0x66) は英数キーのキーコード
Application("System Events").keyCode(102)
これはシェルから直接実行できます。JSを文字列として渡すため、クォートinクォートになる点に注意が必要ですがこうなります。
$ osascript -l JavaScript -e 'Application("System Events").keyCode(102)'
で、シェルワンライナーはVimからsystem()
で呼び出すことができます。job機能を使えば非同期にもできますが、この程度なら同期的でも問題ないでしょう。シェルスクリプトを文字列として渡すため、クォートinクォートinクォートになる点に注意が必要ですがこうなります。
call system("osascript -l JavaScript -e 'Application(`System Events`).keyCode(102)'")
あとはこれが望ましいタイミングで動くよう、autocmdに指定します。
if has('mac')
autocmd FocusGained,InsertLeave,CmdlineLeave *
\ silent call system("osascript -l JavaScript -e 'Application(`System Events`).keyCode(102)'")
endif
Neovimでlua設定を使っている場合は[[ ... ]]
で文字列を書けるので少し楽です。
if vim.fn.has('mac') == 1 then
vim.api.nvim_create_autocmd({ 'FocusGained', 'InsertLeave', 'CmdlineLeave' }, {
pattern = '*',
callback = function()
vim.fn.system([[osascript -l JavaScript -e 'Application("System Events").keyCode(102)']])
end
})
end
上記の例はaugroupを考慮していないので適宜設定してください。また、複数環境で同じ設定を使ったときにバグらないようhas('mac')
を入れていますが、mac環境でしか使わないのであればこの判定も不要です。
osascriptでSystem Eventsにアクセスするにはmacのセキュリティ許可が必要になるので、初回の実行時に警告が表示されます。もし複数のターミナルアプリからVimを使う場合はアプリごとに許可が必要です。
初回の警告
アクセシビリティ設定
オートメーション設定
許可をしていないと失敗します。
execution error: Error: Error: エラーが起きました (-1743)
ターミナルにSystem Events全許可は脆弱性がマッハでは?
残念ながらmacOSのセキュリティ設定では「Vimから実行したときだけ許可」「英数キーの場合だけ許可」といった設定はできません。「このターミナルアプリはSystem Eventsの実行をすべて許可」となります。さすがにこれは範囲が広すぎて怖い。
アプリ単位でしか許可できないので、Automatorで専用のアプリを作成し、それを許可することにしました。
Automatorから「アプリケーション」を新規作成し…
Automator新規作成
左のアクション一覧から「JavaScriptを実行」を右のワークフロー部分へドラッグ。
アプリケーション作成
コードの// Your script goes here
を英数キー送信に書き換え、eisuu_key.app
のような名前をつけて保存します。
function run(input, parameters) {
- // Your script goes here
+ Application("System Events").keyCode(102);
return input;
}
macではopen -a {パス}
でアプリを実行できるので、先程のautocmdを書き換えます。Vim script版だけ載せます。
if has('mac')
autocmd FocusGained,InsertLeave,CmdlineLeave *
- \ silent call system("osascript -l JavaScript -e 'Application(`System Events`).keyCode(102)'")
+ \ silent call system("open -a /path/to/eisuu_key.app")
endif
やはりこちらも初回は警告が出るので、アクセシビリティ設定とオートメーション設定でeisuu_key.app
を許可してください。
初回の警告
こちらを有効にしたらターミナルアプリのセキュリティ設定は無効にして構いません。
所感
これでしばらく使っていましたが、これはこれで使いづらかったので やめました 。英数キー入力スクリプト実行時に0.5秒程度のブロッキングが発生するようで、その間に行った入力がすべて虚空に消えます。実際に英数キーをタイプした場合はそのような問題はないので、automatorが起き上がってjsを実行するのが遅いのかなと思っています。なんかうまい解決方法をご存じの方がいたら教えてください。
Discussion