Create a gist now

Instantly share code, notes, and snippets.

Vimでの日本語のカーソル移動の改善: 文節単位のWORD移動(W,E,B)プラグインと、句読点に移動するmap

日本語のカーソル移動の改善: 文節単位のWORD移動(W,E,B)プラグインと、句読点に移動するmap

Vim Advent Calendar 2012の102日目(2013-03-12)の記事です。

概要

  • 文節単位のWORD移動(W,E,B)plugin

    W,E,BでのWORD移動を文節単位にする jasegment.vimを作りました。 TinySegmenterを使って文節を区切っています。

  • 句読点に移動するmap

    f,tを使った「。、」への移動を、f<C-J>等にmapしておく設定例 (noremap <silent> f<C-J> f。)です。 また、f,t,r向けに、mapしていない日本語文字を1文字だけ入力するのは、 digraphs機能を使うのが楽だと思います(例:<C-K>woで「を」)。

背景

Vimの特徴として、カーソル移動や編集操作を、 ホームポジションからあまり指を動かさずに、 少ないキー入力で行える(CTRLキーとの同時押しが不要)ので、 快適に編集が行える点があります。

移動操作も、単なる上下左右(k,j,h,l)だけでなく、 単語単位(w,e,b,W,E,B)や指定した文字まで(f,F,f,T)など豊富にあるので、 適切な方法をその場に合わせて簡単に使い分けることができます。

さらに、移動操作を削除等の編集操作と組み合わせることで、 意図する編集を手早く実行可能な点も快適な操作感につながっています。

課題

英文のように単語間にスペースがある場合、 単語単位での移動や編集が容易なため、Vimの特徴が生かせます。 しかし、日本語文章での移動に関して以下の課題があります。

日本語はスペースでの分かち書きをしないので、wordやWORD移動がしづらい

日本語の場合は、wordは、漢字の連続やひらがなの連続のように、 同じ文字種が連続する間という形で扱われますが、 単語とは一致しないことがあります。

特に、WORD移動(W,E,B)は使いづらい機能でした。

  • &encodingがeuc-jpやcp932の場合は、 word移動(w,e,b)とWORD移動(W,E,B)で違いがないため、 W,E,Bの存在意味がなく、使い道がありませんでした。
  • &encodingがutf-8の場合は、スペースが無いと改行まで移動するため、 日本語文章での行内移動には使えませんでした。

WORD移動を日本語の文節単位にするjasegment.vimを作りました。

f,t,rで日本語文字の指定が面倒

f,tで日本語文字を指定して移動したいとき、 Input Methodを使う場合は、かな漢字変換の確定操作が必要で、 英数字を指定する場合に比べ、まどろっこしさがあります。

あるいは、kana keymapをsetして、lmapオンの状態であれば、 そのまま「、。」やひらがなが入力できますが、 lmapオフの状態だと、一度Insert modeに入ってlmapオンにする手間があります。

よく使う文字はmapしておく。あるいはdigraphやkeymapを使う。

解決方法

jasegment.vim

jasegment.vimは、 日本語文章でのWORD移動(W,E,B)を文節単位にするpluginスクリプトです。 TinySegmenterを使って文節を区切っています。

以下の特徴があります。

  • ひらがなが連続する中から、ひらがなで始まる文節を認識可能。 (文字種だけを見て、 漢字の後の連続するひらがなをWORDとみなす方法では不可能)
  • 英字とその後に連続するひらがなを、WORDとして扱う。

機能:

  • 通常移動(countも対応)の他に、Visual modeや、 dW/c2E/yB等のOperator-pending modeも対応。
  • text-objectsでWORD選択を行うaW/iWも、文節で選択するようにします。
  • カーソル行の文節開始位置に下線を表示するオプションあり。

下線表示スクリーンショット

文節区切りに関しては、以下を使っています。

  • 文節区切りは、 TinySegmenter をVimスクリプトに移植したものを使用。
  • 文節区切りの学習データは、 TinySegmenterMaker を使って、 KNBコーパス から、文節区切りを学習させたもの。

区切り例

jasegment.vimの文節区切り(knbc_bunsetuモデル):

Vimは | 最も | たくさんの | コンピュータ/OSで | 利用できる | テキストエディタです。

jasegment.vimの単語区切り(rwcpモデル):

Vim | は | 最も | たく | さん | の | コンピュータ | / | OS | で | 利用 | できる | テキストエディタ | です | 。

Vimのword:

Vim | は | 最 | もたくさんの | コンピュータ | / | OS | で | 利用 | できる | テキストエディタ | です | 。

VimのWORD(&encodingがeuc-jpやcp932の場合):

Vim | は | 最 | もたくさんの | コンピュータ | / OS | で | 利用 | できる | テキストエディタ | です | 。

VimのWORD(&encoding=utf-8の場合。改行までまるごと1つ):

Vimは最もたくさんのコンピュータ/OSで利用できるテキストエディタです。

matchit2.vim:

Vim | は | 最もたくさんの | コンピュータ | / | OS | で | 利用できる | テキストエディタ | です。

MyMoveWord.vim("。"までまるごと1つ):

Vimは最もたくさんのコンピュータ/OSで利用できるテキストエディタです。

motion_ja.vim:

Vim | は最もたくさんのコンピュータ | /OS | で利用できるテキストエディタです。

jvim3のWORD:

Vim | は最もたくさんのコンピュータ | /OS | で利用できるテキストエディタです | 。

EmacsのM-f(forward-word):

Vim | は | 最もたくさんの | コンピュータ | /OS | で | 利用できる | テキストエディタです | 。

f,t,rで日本語文字指定

f,t,rで1文字だけ「。、」などの日本語文字を入力するのは、 digraphs機能を使うのが楽だと思います (例:f<C-K>haで「は」に移動して、r<C-K>gaで「が」に置換)。 Vimデフォルトのdigraphsにはひらがな・カタカナ・一部記号が含まれています。

ただ、「。」を入力するために<C-K>._と打つ必要があり、 よく使う場合は少し長いので、f<C-J>等にmapしておく設定例です。 (+kaoriya版の場合は、「。」か「、」のいずれかに移動するのであれば、()キーを使うのが早いですが)

その他、逆方向に検索して見つかった「。、」の後に 改行を入れるためのmap例も含みます。

" 日本語向け設定: Jで行をつなげた時に日本語の場合はスペースを入れない
set formatoptions+=Mm
" 。、に移動(f<C-K>._ を打つのは少し長いので)。cf<C-J>等の使い方も可。
function! s:MapFT(key, char)
  for cmd in ['f', 'F', 't', 'T']
    execute 'noremap <silent> ' . cmd . a:key . ' ' . cmd . a:char
  endfor
endfunction
call <SID>MapFT('<C-J>', '')
call <SID>MapFT('<C-U>', '')
" 前/次の「。、」の後に改行を挿入する
nnoremap <silent> f<C-H> fa<CR><Esc>
nnoremap <silent> f<C-L> fa<CR><Esc>
nnoremap <silent> F<C-H> F。a<CR><Esc>
nnoremap <silent> F<C-L> F、a<CR><Esc>
nnoremap <silent> f<C-M> :call search('[、。]')<CR>a<CR><Esc>
nnoremap <silent> F<C-M> :call search('[、。]', 'b')<CR>a<CR><Esc>
" Insert modeで「。、」の後に改行を入れる。長い行を折り返すため。
inoremap <C-U> <NOP>
inoremap <silent> <C-U><C-H> <C-G>u<C-O>:call <SID>AddNewline('')<CR>
inoremap <silent> <C-U><C-L> <C-G>u<C-O>:call <SID>AddNewline('')<CR>
inoremap <silent> <C-U><C-M> <C-G>u<C-O>:call <SID>AddNewline('[、。]')<CR>
function! s:AddNewline(ch)
  if search(a:ch . '\zs.', 'b', line('.')) > 0
    let pos = col("'^")
    let len = col('.') - 1
    execute "normal! i\<CR>\<Esc>"
    call cursor(0, pos - len)
  endif
endfunction

(なお、digraphs以外の方法として、kana keymapをsetしていれば、 f,t,rの後にそのままkana keymapに従って日本語文字入力可能ですが、 lmapオフの状態(iminsert=0)だと、 一度Insert modeに入ってlmapオンにする必要があるため、 少し使いにくい面があります)

digraph定義の変更

「あ」を入力するdigraphはa5ですが、 もっと打ちやすいaa等にしたい場合の設定例は以下です (12354は「あ」のUnicodeコードポイント)。

digraph aa 12354

このとき、digraph定義で使うUnicodeコードポイントは、 該当文字にカーソルを合わせて、:asciiもしくはgaで調べられます (&encodingがutf-8である必要あり)。

あるいは、日本語文字で書いておいて、Unicodeコードポイントに置換するには、

digraph aa あ

と書いた行で以下のコマンド(最後の文字をUnicodeコードポイントに置換)を実行 (&encodingがutf-8である必要あり)。

:s/\(.\)$/\=char2nr(submatch(1))/

tcodeやtutcode等の漢字直接入力keymapをdigraphに変換

tcodeやtutcode等の漢字直接入力keymapを使っている場合、 漢字に対応する2打鍵を:digraphsで登録しておくと便利です。 (なお、tutcodeの3打鍵以上の文字を入力したい場合は、 digraphsに登録できないのでkeymapを使う必要があります)

tcodeやtutcode keymapから、 digraph定義を生成するための編集操作例です。 (&encodingがutf-8である必要あり)。

:1,/^loadkeymap/d
:g/^"/d                              " コメント行を削除
:%s/<Space>/ /g                      " digraphではスペースもそのまま指定
:g/^\S\{3,}/d                        " 3打鍵以上の定義を削除
:%s/\(.\)$/\=char2nr(submatch(1))/   " 漢字をUnicodeコードポイントに変換
:%j
:s/^/digraph /

参考: 日本語での移動を改善する同様のスクリプト

  • matchit2.vim

    「漢字とその後の連続するひらがな」をひとまとまりとして移動。

  • MyMoveWord.vim

    指定した日本語セパレータ(。、など)でW,B,Eのカーソル移動を一旦停止。

  • motion_ja.vim

    E,W,Bでの移動量を、e,w,bよりも大きくするためのスクリプト。 jvim3と同様に「。、」や英数字との境目まで移動 (ただし、&encoding=utf-8の場合は移動量が多すぎ)。

蛇足

(ひらがなが連続する場合、文節区切りがぱっと見でわからないことがあるので、 文節開始位置に下線を表示するオプションを付けましたが、 ぱっと見での区切りのわかりやすさを考えると、 漢字の連続+ひらがなの連続をWORDとみなす方が使いやすいのかも。)

(または、普通の文節に対して、かな漢字変換のしやすいIME文節が使われるように、 Vimで日本語編集のしやすい編集単位(Vim文節?)を検討・評価して、 text-objectとして扱えるようにするのがいいのかも(textobj-wiwのように)。)

(さらに、Vim文節の切り出しは、まともな文になっていなくて、 細かく変更される文字列から行う必要があるので、正確にやるのは大変そう)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment