Zenn
💥

macOSのVimで日本語入力の誤爆を防ぐ karabiner不使用版

2025/03/22に公開

IMEが有効になったままだとVimのノーマルモード操作で突っかかる

「日本語入力がオンのままVimを操作しようとして失敗する」


日本語入力状態のまま j を入力してしまった例

別にVimが悪いわけではなくIMEが入力をインターセプトする仕組みになっていることが原因なのですが、我々日本人がVimを使ったときにはよく発生しがちです。

Karabiner-Elementsを使うのが一般的

この問題を解決するため、macOS向けで多く提案されているのがKarabiner-Elementsを使用した方法です。

https://karabiner-elements.pqrs.org/

これは<esc>を押したときに同時に英数キーを送信するもので、これにより「Insert modeを抜けるとき、ついでにIMEをオフにする」ようになります。

この手法の問題点は<esc>がトリガーになっていることで、当然ですが<esc>を押さずノーマルモードへ移行する場合には日本語入力状態のままです。具体的には、以下のような場合が該当します。いずれもインサートモードを介しておらず、<esc>に英数キーが入っていても効果がありません。

  • 日本語を検索して<cr>で実行した(コマンドラインモード → ノーマルモード)
  • Vim外で日本語入力をしたあとVimにフォーカスを移した(Vim外 → ノーマルモード)

筆者は特に後者の問題に悩まされており、「ブラウザで何か日本語で検索→Vimに戻る→操作する→引っかかる→日本語入力のままだったことに気づく」という現象が多発していました。

そもそもmacOSはターミナルからキーコードを送信できるのでそれをsystem()で実行すれば良い

既存手法の問題は<esc>しか考慮されていないことでした。これを解決するため、「autocmdで英数キーを送信する」手法を考えました。

macOSではosascriptによってシステムの制御を行うことができます。

https://support.apple.com/ja-jp/guide/terminal/trml1003/mac

AppleScriptまたはJavaScriptでコードを書けます。個人的にはJavaScriptのほうが得意なので(あと短く書けるので)、この記事ではJavaScriptを使用します。

英数キーを送信するJSコードは以下のとおりです。

JavaScript
// 102 (0x66) は英数キーのキーコード
Application("System Events").keyCode(102)

これはシェルから直接実行できます。JSを文字列として渡すため、クォートinクォートになる点に注意が必要ですがこうなります。

shell oneliner
$ osascript -l JavaScript -e 'Application("System Events").keyCode(102)'

で、シェルワンライナーはVimからsystem()で呼び出すことができます。job機能を使えば非同期にもできますが、この程度なら同期的でも問題ないでしょう。シェルスクリプトを文字列として渡すため、クォートinクォートinクォートになる点に注意が必要ですがこうなります。

Vim script
call system("osascript -l JavaScript -e 'Application(`System Events`).keyCode(102)'")

https://vim-jp.org/vimdoc-ja/builtin.html#system()

あとはこれが望ましいタイミングで動くよう、autocmdに指定します。

Vim script
if has('mac')
  autocmd FocusGained,InsertLeave,CmdlineLeave *
        \ silent call system("osascript -l JavaScript -e 'Application(`System Events`).keyCode(102)'")
endif

Neovimでlua設定を使っている場合は[[ ... ]]で文字列を書けるので少し楽です。

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のような名前をつけて保存します。

JavaScriptを実行
 function run(input, parameters) {
	
-	// Your script goes here
+	Application("System Events").keyCode(102);

 	return input;
 }

macではopen -a {パス}でアプリを実行できるので、先程のautocmdを書き換えます。Vim script版だけ載せます。

vim
 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

ログインするとコメントできます