Vim/GVimで「日本語入力固定モード」を使用する


Vim/GVimの日本語入力や編集で一番問題になるのはIME(IM)のモード切替です。

WindowsではIMEの制御が行えますが、仕様的な問題で入力内容によっては余計なモード切替が頻繁に起こる事がありますし、Linuxに至っては IM制御の挙動はまちまちで、同じ OS上の Vim(非GUI)と GVimですら挙動が異なるため非常に混乱しやすくなっています。

個人的にはノーマルモードから挿入モードへ移行した時に、自動制御で日本語入力をオンにするかオフにするかを選択できるようにする「日本語入力固定モード」を導入する事で解決しました。
「日本語入力固定モード」を使用すると IME(IM)の自動制御を Vim(非GUI)と GVimだけでなく Windowsと Linuxのように OS自体が違う場合でも同じ挙動にして扱う事ができるので非常にわかりやすくなります。

またLinuxでは一部の IMに GVimのみですが挿入モードからノーマルモードへ移行した際には日本語入力が自動でオフになる「vi協調モード」という機能があります。
「日本語入力固定モード」を利用すると GVimだけでなく Vim(非GUI)との両方で、また IBus、SCIM、uimの全てで「vi協調モード」を使用可能にする事ができます。
IBus(1.3.9)自体の vi協調モードにはバグらしき動作がありますが、これを完全な動作とする事もできます。

その他の Vimで日本語を扱う場合の問題や設定等については「vim/gvimで日本語を使いやすくする」にまとめてあります。


最終更新

(2016/09/18)
  • Windows等の<C-^>で切替可能な場合に、単純なIMのOn/Offを追加しました。
    これによりVimの挿入モードでのみ有効なIM切替キーを設定可能になります。
    <C-^>でIMの切替が行える場合
ファイルは「インストール」からダウンロードしてください。




日本語入力固定モードとは

日本語を入力する、と言っても単に日本語で一言付け加えたいだけの時と日本語をメインに入力したい時との二種類があります。
「日本語入力固定モード」を利用すると、この二種類の場合それぞれに対応した IMEの自動制御を行う事が出来ます。

ソースコードに日本語で一言コメントを書くような場合には
  1. 挿入モードに入ってIME ON
  2. コメントを書く
  3. <ESC>で挿入モードを抜ける
  4. 再び挿入モードへ
  5. IMEは OFF のまま
というように自動的に IMEが OFFになります。
Windowsの場合でもいちいち IMEをオフにする必要はありません。
 
逆に日本語をがっつり入力したい場合は、<C-j>を「日本語入力固定モード」の切替キーにしていたとして
  1. 挿入モードに入って<C-j>でIME ON
  2. なにかを書く
  3. <ESC>で挿入モードを抜ける
  4. 再び挿入モードへ
  5. IMEは自動で ON になる
というように今度は IMEが自動制御で ONになります。
ノーマルモードで移動して、また日本語を入力したい場合に便利かと思います。
以降は <C-j> で「日本語入力固定モード」を切り替えない限り、挿入モードの IME初期状態が日本語入力 ON になります。
「日本語入力固定モード」が有効な間は <C-j> 以外で IMEをオフにしても次回の IMEの状態はオンから始まります。

つまりノーマルモードへ抜けた時の IMEの状態にかかわらず、挿入モードへ移行した時の IMEの初期状態は「日本語入力固定モード」で決定されるという事になります。

「日本語入力固定モード」の状態を表示する

なおバッファごとに「日本語入力固定モード」の状態を保持することも可能です。
日本語入力固定モードを制御する




現在「日本語入力固定モード」はWindows/Linux/Macで使用可能です。
なお unix系の場合はプラグイン(im_control.vim)の他に IM切替のための手段を用意しなければならない場合があります。

具体的なインストール方法については、使用する環境に合わせて以下から参照してください。

Windowsの場合

WindowsのGVimでは 「<C-^>でIMの切替」が行えますので「インストール」後に「<C-^>でIMの切替」へ進んで下さい。

ただしVim(非GUI)では制御できないため「日本語入力固定モード」は使用できません。
単純に <ESC>でノーマルモードへ移行したときにIMをオフにしたいだけなら、特定アプリでのみキーをリマップ可能なキーマッパーを利用してVimのときだけ<ESC>に <ESC>+半角/全角キーを割り当てるという方法もあります。
Windowsでは「AutoHotkey」(「AutoHotkeyを流行らせるページ」)などが使えるようです。

余談ですがIME制御は imm32.dllで行うことが可能で、Vimからdll呼び出しも可能なので非GUIでもIM制御は原理的には可能です。

Macの場合

Macについては動作環境がないので直接確認はしていませんが、「<C-^>でIMの切替」が行える GVimでは動作するそうです。
インストール」後に「<C-^>でIMの切替」へ進んで下さい。

<C-^>で IMの切替が行えない場合は Linuxの「その他」(xvkbdを使用する例)を参考に外部コマンドで IMを制御することが出来れば使用することができます。
iTerm2+MacVim-Kaoriyaで使っている方はいるようです。
http://qiita.com/wakuworks/items/7c9c6ab60275fd865b02

単純に <ESC>でノーマルモードへ移行したときにIMをオフにしたいだけなら、特定のアプリのみキーをリマップ可能なキーマッパーを利用してVimのときだけ<ESC>に <ESC>+英数キーを割り当てるという方法もあります。
MacOSXでは「KeyRemap4MacBook」などが使えるようです。
IMによってはESCでIMをオフにするキー設定やVim協調モードがあるIMもあるようです。

Linuxの場合

Linuxで「日本語入力固定モード」を使用するためには、Vimから IMを制御できるようにする必要があります。
IMの ON/OFFがコマンドラインから制御できる手段があれば何でも良いのですが、ONとOFFが別のコマンドで制御可能な物が望ましい手段です。

1. <C-^>でIMの切替が行える場合

<C-^>でIMの切替が行える場合は「インストール」へ進んでください。

2. fcitx, IBus

fcitxまたはIBusを使用している場合は「インストール」へ進んでください。

3. その他

どちらでもない場合の手段として、今回はIMの機能にほとんど依存しない xvkbdを使用します。
IMに適当なホットキーを設定してやり、Vimから xvkbdを呼び出して仮想的にホットキーを入力することで IMを制御するという方法です。
(uim, SCIM, iiimfではxvkbdで動作するようです)

まずはインストールの前にxvkbdによるIM制御の準備を行います。
外部コマンドによるIM制御の準備

IMの ON/OFFにxvkbdでない別の制御方法を使いたい場合はプラグインをインストールした後に 「その他の方法で制御する」へ進みますが、xvkbdを使用する方法をみて参考にすると良いかもしれません。

単純に <ESC>でノーマルモードへ移行したときにIMをオフにしたいだけなら特定のアプリのみキーをリマップ可能なキーマッパーを利用して、 Vimのときだけ<ESC>に <ESC>+半角/全角キーを割り当てるという方法もあります。
Linuxでは「IronAHK」や「autokey」などが使えるようです。

androidの場合

androidの現状(Android 4.3)としてはIME制御を各IMEが独自に処理しているため、OSとして統一されたIME制御方法は存在しないようです。
インストール」でim_control.vimをインストールしたら「IMCtrl (制御用関数)の設定」を参考に使用しているIMEに合わせた設定を行ってください。

以下参考までにiWnnとWnn Keyboard lab用設定(.vimrcに追加)を書いておきます。
日本語入力固定モード自体の使用方法については「日本語入力固定モードの使い方」を参考にしてください。

" im_control.vim
inoremap <silent> <C-j> <C-r>=IMState('FixMode')<CR>
" android (Wnn Keyboard lab)
let IM_Keycode = 'KEYCODE_ZENKAKU_HANKAKU'
" android (iWnn)
let IM_Keycode = 'KEYCODE_GRAVE'

function
! IMCtrl(cmd)   let cmd = a:cmd   if cmd == 'On'     let res = system('input keyevent '.g:IM_Keycode.'&')   elseif cmd == 'Off'     " 実際には意味が無いので無効化     " let res = system('input keyevent KEYCODE_ESCAPE&')   elseif cmd == 'Toggle'   endif   return '' endfunction


見ればわかるようにIME制御は[半角/全角]の押下イベントを発生させているだけです。
iWnnとWnn Keyboard labでは制御キーが違うので気をつけてください。
IMEオフを行えるキーはWnnに無いようなのですが、<ESC>でIMEオフになるので実用上は問題無いようです。

ただし、inputコマンドはJavaVMを起動するためワンテンポ遅れる感じに処理されてしまいます。
問題になるのはインサートモード移行時に日本語入力を有効化する場合ですが、気になるようならAndroid Terminal EmulatorにIME制御パッチを当てるなどで対処すると良いかもしれません。

個人的にはキーボードショートカットやアクションバーでのIME On/Off等と日本語処理関係の改変を加えた端末エミュレータ(改変版)でVimを動作させています。
エスケープシーケンスでIME制御可能なので高速に切替が行えます。
端末エミュレータ (日本語処理改変版)

「端末エミュレータ」でVimを動作させる方法については以下を参照してください。
androidでVim + howm

なお端末エミュレータで動作させている場合、クリップボードからの貼付けで改行が<C-j>として入力されるので、日本語入力固定モードの切替キーを<C-j>以外にするなどの対処が必要になります。

インストール

GitHubからダウンロードしたzipファイルを解凍してできたディレクトリにランタイムパスを通してください。
GitHub(Zip)
im_control.vim-master.zip

動作に必要なのはim_control.vimだけなのでim_control.vimをpluginディレクトリにコピーするだけでもかまいません。
https://github.com/fuenor/im_control.vim

GitHubからダウンロードしたzipファイルを解凍してランタイムパスを通す手順は以下のとおりです
  1. zipファイルを解凍します。
  2. ディレクトリ(im_control.vim-master)が作成されます。
  3. 解凍されたディレクトリを適当な場所にコピーして、ランタイムパスを通します。
c:\tempにim_control.vim-masterをコピーした場合は、_vimrc(Linuxは .vimrc) で次のようにパスを通します。
set runtimepath+=c:/temp/im_ctrl.vim-master


im_control.vimの設定

次に .vimrcへ設定を追加して 「日本語入力固定モード」と「vi協調モード」をを有効にします。

オプションの詳細は後述するとして、代表的な設定例から解説します。
動作がおかしいときは imactivatekeyなどIM関係オプションは設定しないでデフォルトのままにしてみてください。

設定が終わったら「日本語入力固定モードの使い方」を参考にして実際に入力してみてください。
オプションは次節以降にあります



必須ではありませんが、最初のうちは日本語入力固定モードの状態を表示可能にしておくと良いかもしれません。
「日本語入力固定モード」の状態を表示する

またバッファごとに「日本語入力固定モード」の状態を保持することも可能です。
日本語入力固定モードを制御する

<ESC>押下後の反応が遅い場合

Linux等で「日本語入力固定モード」を使用している場合<ESC>押下後の反応が遅いことがあります。
<ESC>押下後の反応が遅いとおもわれる場合はttimeoutlenを設定してみると改善されるかもしれません。
" <ESC>押下後のIM切替開始までの反応が遅い場合はttimeoutlenを短く設定してみてください(ミリ秒)
set
timeout timeoutlen=3000
ttimeoutlen=100
Vimのデフォルト設定では複数のキーマップが存在する/しないにかかわらずキー押下後に1000ミリ秒の待ち時間が存在します。

「日本語入力固定モード」の状態を表示する

コマンドラインの高さを 2 以上にしておくと切替時に切替後の「日本語入力固定モード」が表示されます。
set cmdheight=2

ステータス表示

ステータス行に「日本語入力固定モード」の状態を表示する

それ以外にも「日本語入力固定モード」がオンの場合、ステータス行にメッセージ表示することも可能です。


" 「日本語入力固定モード」がオンの場合、ステータス行にメッセージ表示
set statusline+=%{IMStatus('[日本語固定]')}

[日本語固定] のメッセージ部分は好みに書き換えてください。


im_control.vimがない環境でも .vimrcを変更しなくて済むように、.vimrc内でダミー関数も定義しておくと良いかもしれません。
" 「日本語入力固定モード」がオンの場合、ステータス行にメッセージ表示
set statusline+=%{IMStatus('[日本語固定]')}

" im_control.vimがない環境でもエラーを出さないためのダミー関数
function! IMStatus(...)
  return ''
endfunction

バッファごとに「日本語入力固定モード」を制御する

デフォルトではVim全体で「日本語入力固定モード」の制御状態が共有されますが、「日本語入力固定モード」の状態を各バッファごとに保持して個別制御することも可能です。
" 「日本語入力固定モード」の全バッファローカルモード
let IM_CtrlBufLocalMode = 1

全バッファではなく特定のバッファでのみ「日本語入力固定モード」の状態を個別制御したい場合はバッファローカルなオプションを設定してください。
" 「日本語入力固定モード」のバッファローカルモード
let b:IM_CtrlBufLocal = 1
(注意) 通常のオプションと違い b: が付いているので気をつけてください。


(例) 特定のファイルタイプや拡張子でバッファローカルな「日本語入力固定モード」制御を行う
" ファイルタイプがvim (.vimrcやvimスクリプトなど)なら個別制御
au FileType vim let b:IM_CtrlBufLocal = 1

" 拡張子が confなら個別制御

au BufRead,BufNewFile *.conf let b:IM_CtrlBufLocal = 1

以下は 独自スクリプト等でIMの状態や「日本語入力固定モード」を制御する際の参考にしてください。

IM/IMEの制御

IM/IME制御用の関数はIMCtrl()として定義されています。
各コマンドを実行して IM/IMEの状態制御可能です。

IM/IME制御
On  :call IMCtrl('On')
Off  :call IMCtrl('Off')
Toggle  :call IMCtrl('Toggle')

「日本語入力固定モード」の制御

「日本語入力固定モード」制御には変数を使用します。

「日本語入力固定モード」制御
On  :let IMState = 2
Off  :let IMState = 0

IMState = 2なら「日本語入力固定モード」切替キーで「日本語入力固定モード」をONにしたのと同じ事になります。

その他のオプション

Vimと GVimで動作を変えたい場合は has('gui_running')で切り分けてください。
" GVimとVim(非GUI)で「日本語入力固定モード」の設定値を切り替える設定例
let IM_CtrlMode = has('gui_running') ? 4 : 0
この例だとGVimなら IM_CtrlMode = 4、Vim(非GUI)なら IM_CtrlMode = 0 に設定されます。


「日本語入力固定モード」の動作設定

" 「日本語入力固定モード」の動作設定
let IM_CtrlMode = 1
ノーマルモードから挿入モードへの移行時に 「日本語入力固定モード」に応じて IMをOnにするための設定です。
やむを得ない場合を除き 0 ,1, 4, 5のいずれかで使用するようにしてください。

 IM_CtrlMode 
 
0  何もしない
1  IM On/Offが個別制御できる場合に設定します。
 独自の vi協調モードも動作します。
2  IM制御が Toggleしか使用できない場合に設定します。
 独自の vi協調モードは動作しません。
3  IM制御が Toggleしか使用できない場合に設定します。
 独自の vi協調モードは動作しませんが、挿入モードからノーマルモードへの移行時に「日本語入力固定モード」が Onだったら Toggleを実行します。
 これは 「日本語入力固定モード」が Onなら日本語メインで入力しているはずで、挿入モードでは日本語モードになっていることが多いだろう、という仮定から実装された "疑似vi協調モード" です。
4
 <C-^>で IM制御が行える場合に設定します。
 Windowsの場合は常にこの設定が使用されます。
5
 IBus 1.5以降では使用できません
 IBusを使用していてPythonで制御したい場合に設定します。
 設定の統一性を持たせるためのもので、起動後には IM_CtrlMode=1, IM_CtrlIBusPython=1 に再設定されます。
 「IBus + Python
6 fcitxを使用している場合に設定します。
 設定の統一性を持たせるためのもので、起動後には IM_CtrlMode=1に再設定されます。
 「fcitx


vi協調モード

vi協調モードは挿入モードからノーマルモードへの移行時にIMを自動でオフにする機能です。
IM自体に vi協調モードがある場合は 「日本語入力固定モード」独自の vi協調モード処理をスキップさせるため IM_vi_CooperativeMode に 0 を設定してください。
必要なら VimとGVimで設定を変更すると良いでしょう。
" GVimの時だけ「日本語入力固定モード」の vi協調モードを無効化
let IM_vi_CooperativeMode = has('gui_running') ? 0 : 1

 IM_vi_CooperativeMode 
 
0  何もしない
1  「日本語入力固定モード」独自の vi協調モードが有効になります。

<ESC>に直接マップする

<C-^>以外でIM制御を行なっている場合は 「日本語入力固定モード」のvi協調モードをオフにして <ESC>キーに直接IMオフの関数をマップする方法もあります。

" 「日本語入力固定モード」のvi協調モードを無効化
let IM_vi_CooperativeMode = 0
" 挿入モード終了時にIME状態を保存しない

inoremap <silent> <ESC> <ESC>
:IMCtrl('Off')<CR>
inoremap <silent> <C-[> <ESC>:IMCtrl('Off')<CR>

" <ESC>押下後のIM切替開始までの反応が遅い場合はttimeoutlenを短く設定してみてください(ミリ秒)
set
timeout timeoutlen=3000
ttimeoutlen=100
<ESC>にIMオフの機能もマップしておくと / コマンドなどで日本語検索した後<ESC>を押すことによってIMをオフに切替可能なので便利かもしれません。


同期呼び出しと非同期呼び出し

(「<C-^>でIMの切替が行える場合」では関係ありません)

環境によりますが GVimでは「日本語入力固定モード」をオンにしている時のノーマルモードで iiiiiiiiと高速に入力してみると最初の数文字が変換されない、という現象が起きる事があります。
これは挿入モード移行時の IM有効化が間に合わないため、挿入モード移行直後に入力された文字の一部が変換されずに直接入力されてしまうためです。
この現象は処理が高速な今時のマシンではあまり問題にはならないようですので、さほど気にする必要はないかもしれません。

この問題を原理的に回避するには IMの有効化を同期処理として実行する必要がありますが、GVimの場合には IM自体の vi協調モードとバッティングして動作しない事があるためデフォルトでは非同期呼び出しになっています。
逆に Vim(非GUI)のデフォルトは同期呼び出しですが、それぞれ正常動作しない時は呼び出し方法を変更してください。
" 制御スクリプトを同期処理で呼び出し
let IM_CtrlAsync = ''

" 制御スクリプトを非同期で呼び出し
let IM_CtrlAsync = '&'
uimや SCIMの場合は同期処理として呼び出すと良いかもしれません。
なおIMCtrl()を自分で定義している場合は本オプションは影響しません。


「日本語入力固定モード」切替時のオートトグル

<C-j>等の「日本語入力固定モード」切替キーで「日本語入力固定モード」の切替を行うとIMを現在の「日本語入力固定モード」と同じ状態に設定します。

しかし IMの個別制御ができない IM_CtrlMode = 2 や IM_CtrlMode = 3 の場合は当然切り替えができません。
このような場合は「日本語入力固定モード」の切替を行うとIMのトグル処理を行うことができます。
「日本語入力固定モード」の切替を行うときは現在の状態とトグル処理をさせたい場合が多いので、有効かもしれません。
"  IM制御がToggleしか行えない時「日本語入力固定モード」を切り替えると Toggleも実行する
let IM_JpFixModeAutoToggle = 1

fcitx

fcitxでfcitx-remoteが使える場合は以下の設定を.vimrcに追加することで使用可能です。
" fcitx

" 「日本語入力固定モード」の動作設定
let IM_CtrlMode = 6
" 「日本語入力固定モード」切替キー
inoremap <silent> <C-j> <C-r>=IMState('FixMode')<CR>

" <ESC>押下後のIM切替開始までの反応が遅い場合はttimeoutlenを短く設定してみてください(ミリ秒)
set
timeout timeoutlen=3000
ttimeoutlen=100


IM_CtrlMode = 6は内部的に以下のように設定されます。
必要なら明示的に指定して適宜変更してください。
" 「日本語入力固定モード」
let IM_CtrlMode = 1
" 「日本語入力固定モード」のvi協調モード

let IM_vi_CooperativeMode = 1

""""""""""""""""""""""""""""""
" 日本語入力固定モードの制御関数(fcitx)
""""""""""""""""""""""""""""""
function! IMCtrl(cmd)
  let cmd = a:cmd
  if cmd == 'On'
    let res = system('fcitx-remote -o > /dev/null 2>&1')
  elseif cmd == 'Off'
    let res = system('fcitx-remote -c > /dev/null 2>&1')
  elseif cmd == 'Toggle'
    let res = system('fcitx-remote -t > /dev/null 2>&1')
  endif
  return ''
endfunction

必須というわけではありませんが、「同期呼び出しと非同期呼び出し」も読んでおいた方がよいかもしれません。

uim

IM制御に「外部コマンドによるIM制御の準備」が必要です。

IM のvi協調モードは GVimでのみ有効、IMの On/Off は個別制御可能という場合の設定例です。
<C-j>で「日本語入力固定モード」を切り替えると IMも現在の「日本語入力固定モード」と対応したモードに切り替わります。

   vi協調モード  IMの On/Off 個別制御
 Vim
×
 GVim

" uim
" 「日本語入力固定モード」切替キー
inoremap <silent> <C-j> <C-r>=IMState('FixMode')<CR>
" GVimの時だけ「日本語入力固定モード」のvi協調モードを無効化
let IM_vi_CooperativeMode = has('gui_running') ? 0 : 1

" <ESC>押下後のIM切替開始までの反応が遅い場合はttimeoutlenを短く設定してみてください(ミリ秒)
set
timeout timeoutlen=3000
ttimeoutlen=100

uim自身の vi協調モードを無効化している場合は GVimでも IM_vi_CooperativeMode = 1 に設定してください。
xvkbd用のキーマップを変更している場合やIM制御に独自の方法を使用する場合は「IMCtrl (制御用関数)の設定」も参照して下さい。

必須というわけではありませんが「同期呼び出しと非同期呼び出し」も読んでおいた方がよいかもしれません。

以上の設定で「日本語入力固定モード」が使用可能になります。
日本語入力固定モードの使い方

SCIM

IM制御に「外部コマンドによるIM制御の準備」が必要です。

IM のvi協調モードは無効、IMの On/Off は個別制御可能という場合の設定例です。
<C-j>で「日本語入力固定モード」を切り替えると IMも現在の「日本語入力固定モード」と対応したモードに切り替わります。

   vi協調モード  IMの On/Off 個別制御
 Vim
×
 GVim ×

" SCIM
" 「日本語入力固定モード」切替キー
inoremap <silent> <C-j> <C-r>=IMState('FixMode')<CR>

" <ESC>押下後のIM切替開始までの反応が遅い場合はttimeoutlenを短く設定してみてください(ミリ秒)
set
timeout timeoutlen=3000
ttimeoutlen=100

xvkbd用のキーマップを変更している場合やIM制御に独自の方法を使用する場合は「IMCtrl (制御用関数)の設定」も参照して下さい。

必須というわけではありませんが「同期呼び出しと非同期呼び出し」も読んでおいた方がよいかもしれません。

以上の設定で「日本語入力固定モード」が使用可能になります。
日本語入力固定モードの使い方

その他の方法で IM制御する

まずはなんらかのIM制御処理を組み込む必要があります。
IMをON/OFFできれば何でも構わないので、xvkbdによる「外部コマンドによるIM制御の準備」を参考にして「IMCtrl (制御用関数)の設定」を行なってください。

それが終わったら uim, SCIMの設定例を参考にオプション設定を行います。
  • IM自体にvi協調モードがある場合
    uim
  • IM自体にvi協調モードが無い場合
    SCIM