rcmdnk's blog
Last update

Amazon.co.jp: IME: In My Experience (English Edition) 電子書籍: Steve Reed: Kindleストア

Vimで日本語を使っているとノーマルモード移行時に日本語入力状態のままだと ちょっと面倒なことになります。

そこで自動的にノーマルモードでは日本語入力をオフにしたいわけですが、 macOS SierraだとキーマップアプリのKarabinerが使えないために 中々上手いこと出来ていませんでした。

今回久しぶりにいろいろ試して取り敢えずなんとか出来たのでその方法について。

前に試した方法等

Karabinerが使える環境ではKarabinerを使って ターミナルアプリ上でESCが押された際に IMEがオンの状態だとESCと同時にIMEをオフにする操作を送ることで 日本語入力状態でESC(またはCtrl-[)を押しても IMEがオフの状態でノーマルモードに入れる様にしていました。

Windowsでも同じようなことをAutoHotKeyを使って実現しています。

ただSierraではKarabinerがまだ使えない状態なので他の方法を探して、 以前はBetterTouchToolでESCにAppleScriptを割り当て、 そのAppleScriptの中でキーを送ることでなんとかしようとしました。

ですがタイムラグが出るのと押すタイミング等で効かない状態に陥ったりして 不安定だったので辞めていました。

この辺、基本的にはVim独自と言うより、 ターミナル環境、またはシステム全体でESCをラップして 入力切り替えのコマンドを送ろうとしています。

新たな方法

VimでESCを押した時にIME OFFコマンドも送る

今回試した方法はVimの中でインサートモード中にESCが押された時に IMEの操作を行ってみることです。

まず、実現するにはコマンドラインからIMEがオフに出来ると色々できそうなので 以下の様なAppleScriptを考えてみます。

ImeOff.scpt
1
2
3
4
tell application "System Events"
  -- 102: EISU
  key code 102
end tell

上で書いた以前試した方法の時のImeOffEsc.scptをシンプルにしたもので、 単にEISUを送るだけのスクリプトです。

これを実行するとどこでも常に英数入力状態になります。

英数キーのキーコードを使っていますが、USキーボードなど英数キーが無いキーボードを使っていても大丈夫です。

コマンドラインからでも

$ osascript ImeOff.scpt

とすればIMEが切り替わります。

取り敢えずコマンドラインからやってみると 特にタイムラグなどは気にならない感じです。

さらに、これをスクリプトを作らずに

$ osascript -e "tell application \"System Events\" to key code 102"

として呼ぶ事も出来ます。

これをVimの中で使うために以下の様な設定を.vimrcの中に加えます。

.vimrc
1
2
3
4
5
6
7
if has('mac')
  let g:imeoff = 'osascript -e "tell application \"System Events\" to key code 102"'
  augroup MyIMEGroup
    autocmd!
    autocmd InsertLeave * :call system(g:imeoff)
  augroup END
endif

まず、Mac専用なのでMacの場合だけ。

後はコマンドを用意して、autocmdInsertLeave時に呼ぶようにしています。

これで取り敢えず日本語入力中にESCなどで ノーマルモードに戻るとき、IMEをオフにしてくれます。

前の方法に比べると大分副作用が小さく、上手く機能しています。

タイムラグを減らす

これで取り敢えずESCでインサートモードを抜ける時に IMEを確実にオフに出来る様になりました。

ですが、やってみるとIMEが切り替わるまでにちょっと時間がかかり、 ESCを押した後にJなどで移動しようとすると 日本語入力が始まってしまいおかしな表示になり、 場合によってはIMEの切り替えが元にもどってしまったりします。

これはVimのtimeoutlenttimeoutlenというオプションの値に関係があります。

timeoutlenはキーマッピングでvl等、vを押した後にlを押す、みたいな マッピングがある時にvを押してから次の入力を待つ時間。

ttimeoutlenはエスケープシーケンスな入力、例えば 矢印上キー等の入力: OA (ESC+O+A)が来た時にそのシーケンスの入力を待つ時間です。

値はミリ秒設定でtimeoutlenが1000、ttimeoutlenが-1になっていますが、 ttimeoutlenがマイナスの場合はキーシーケンスの待ち時間はtimeoutlenのものを使います。

従って、ESCキーを上の様にマッピングすると、 デフォルトではtimeoutlenの1000ミリ秒(1秒)待ってから そのマップされたものが実行される形になります。

もともとのESCでも 1秒の待はあるのですが、ESCがマップされてない状態だと他のキーを入力した瞬間に キーシーケンスとは捉えられずにノーマルモードに戻ってそのキーのコマンドを実行するようになっています。

これに関連してちょっと謎なのは、Vimの中で矢印キーのキーを見たい時、 Ctrl-Vを押してから矢印キーを押すと上の様にOAが表示されるわけですが、 コマンドラインなんかだとになります。Oでなくて[。 実際、コマンドラインでは(ESC+[+Aというシグナルで上キーを入力出来、 一方VimではESC+O+Aをしても上手く上移動になりませんが、 矢印入力が上手く行かないときにこのキーに<Up>などをマップしたりすることがあります 1

ともかく、ESCを押した後の待時間を減らすには

set ttimeoutlen=1

みたいな設定をします。 timeoutlenの方は短くしてしまうと複数キーマップしたものが入力出来なくなるので 別途ttimeoutlenで設定すべきです。 こちらの方は短くしても特にカーソルキーでの入力などには問題はありません。

また、これらの設定はtimeoutttimeoutという設定項が無効になっていると そもそも待ち時間設定関係なく延々と待ち続ける様な設定になりますが、 デフォルトでtimeoutはオンになってるので敢えてオフにしてない限りは設定し直す必要はりません。

timeoutがキーマップ用、ttimeoutがキーシーケンス用ですが、 ttimeoutの方の設定に関わらずtimeoutが有効であれば常にキーシーケンスの方も タイムアウトが有効になる設定になってるので、両方有効にしたいのであればttimeoutの方を直接設定する必要はありません。

options - Vim日本語ドキュメント

もし、この設定をしても1秒の待ち時間が変わらない様であれば、 ESCに関するマッピングがある可能性があります。

:imap <ESC>

などしてマップされてるものが無いか確認して必要なければ.vimrcなどから削除します。 (プラグインなどで自動で設定されてる可能性もあります。)

最終的にはこんな感じ。

.vimrc
1
2
3
4
5
6
7
8
9
if has('mac')
  set ttimeoutlen=1
  let g:imeoff = 'osascript -e "tell application \"System Events\" to key code 102"'
  augroup MyIMEGroup
    autocmd!
    autocmd InsertLeave * :call system(g:imeoff)
  augroup END
  noremap <silent> <ESC> <ESC>:call system(g:imeoff)<CR>
endif

ノーマルモード/ビジュアルモード時にも間違えて日本語になってる場合に ESCで強制的に日本語になるようにnoremapの設定も加えてあります。

ただし、下にも書くようにもしノーマルモード時にカーソルキーを使って移動してる様な場合は 設定するとカーソルキーが使えなくなるので辞めてください。 もしくはちゃんとhjklを使ってください。

ttimeoutlenに関してはここに入れずに通常の設定として入れておいても問題ないかも。

その他色々試したこと

InsertLeaveを使う代わりにinoremapnoremapみたいに直接キーマッピングを 変更する方法もあるかと思います。

inoremap <silent> <ESC> <ESC>:call system(g:imeoff)<CR>

みたいな感じ。もしくは

inoremap <silent> <ESC> <C-o>:call system(g:imeoff)<CR><ESC>

やってみた感じはInsertLeaveと変わらない感じでした。

ただし、この様にESCをマップしてしまうと カーソルキーの入力がAとかの入力になってしまいます。

なのでカーソルキーを使う場合にはInserLeaveを使うしかありません。

また、

inoremap <silent> <C-j> <ESC>:call system(g:imeoff)<CR>

みたいにESC以外にマップする方法もありますが、 これをするとttimeoutlenの設定をしなくても即座にimeoffが呼ばれて良いです。

ttimeoutlenを1(もしくは0)に設定しても僅かにタイムラグがあります。 即座にjなどで移動しようとすると日本語が入力されてしまったりします。

なのでESCを使わずに他のキーでノーマルモードに戻る設定をしてたりすると それを上の様なマップに切り替えることでより快適に切り替えが出来るはずです。

Escape key alternatives in Vim Hacker News

よくあるのがjjとか、後はちょっと挙動の違うCtrl-CESCにしてしまうとかでしょうか。

私自身はCtrl-[を常にESC変わりに使って居ますが、 Vimの中ではこのキーはESCと全く同じ扱いなので 上記のttimeoutlen等が必要になります。

この方法を試すにあたって

Sierraになってから上にも書いたように一度試してだめだったわけですが、 その後も何度か試したりしています。

そんな中で、他の人も試してる人が居て、

Vim でインサートモードを抜けた時にインプットメソッドを切り替える - Nekostack

このポストを見てVimの中でやることを参考にしました。 ただ、ここで使われているswimと言うコマンドはGoogle日本語入力だと 上手く扱えなかったので断念。

コマンドラインから使える他の方法を探してた所、

hnakamur’s blog: Mac OS X 10.5以降でIMEを切り替えるコマンドラインツールを作りました

と言うちょっと古いものをみつけて試してみたところ、 SierraでもGoogle日本語入力の操作も一応出来ているようでした。

ただ、何故かターミナル上からコマンドを送るとIMEの表示は切り替わるのですが ターミナル上では日本語入力のままになり、さらに何故か一度他のアプリに切り替えてから すぐにターミナルに戻ると英数入力にきちんと変更されてる、という状態。

これだとVimのなかからコマンドを送っても上手く行かないので断念。

で、ちょっと自分の前のポストを見てたらAppleScriptで簡単に出来るな、ということで やってみた次第です。

まとめ

今回の方法は以前試した方法よりは上手くいっています。

ただ、ちょくちょく上手くimeoffが呼べずに日本語のまま残っていたり、 微妙なタイムラグが残っていてESCを押した直後に他のキーを押そうとすると 日本語が残ってしまったり変なキーが入力されたりしてしまうことがあります。

特に、インサートモードに入ってIMEをオンにして、そのまますぐにノーマルモードに戻ろうとすると 何故か必ず日本語のまま残ってしまいます。 ちょっとでも文字を書いてからだと大概上手く行くのですが。

でも無いよりマシかな、という程度にはなったのでしばらくは使っていこうと思います。

最近この設定無しでしばらく使ってきたので、日本語からノーマルモードに戻る時に すぐにIMEの切り替えキーを押す癖も付いてしまっていますが、 必要なければそれだけ速く打てることは打てるので。

後は

$ osascript -e "tell application \"System Events\" to key code 102"

のワンライナーで特に余計なものを入れずに IMEをOFFにできるのを覚えておくと色々使えそう。

追記: 2017/03/12

Google日本語入力+iTerm2限定になりますが、 もっと良い方法が見つかりました。

追記ここまで

Sponsored Links
Sponsored Links

« Firefox 52リリース: Vimperatorは無事 VimでNormalモード切り替え時にIMEをOFFにする、をMacでKarabiner無しで実現する 3 »

}