日本語でf
、t
、T
、F
(以後f
)を使って移動、削除、ヤンクするとき、f
を押してから一旦IMEをONにしないと使えません。めんどくさい……。
これでは誰も日本語でf
なんて使いませんよね。
日本語編集をやりやすくする方法としては、インサートモードでの横移動のキーバインドを設定したり、)
と(
やW
、B
、E
の移動を使いやすくするプラグインの導入などがあります。人によってはこれらで十分かもしれません。
しかし、それでもやっぱりせっかく用意されているデフォルトのf
を有効活用しないのはもったいないと思い、Vim scriptの勉強も兼ねてこのプラグインを作成しました(つまりクオリティは保証しません)。
出来るようになること
.
で.
と。
に対応
※ .vimrc
でキーリストを以下のように設定
let g:ftjpn_key_list = [
\ ['.', '。']
\ ]
配列の先頭の文字がキーです。上のように設定すれば、.
が.
と。
を担当するようになります。
こんには世界。ハローワールドですね!
f.
→。
まで移動df.
→。
まで削除cf.
→。
まで削除して挿入モードに入るvf.
→。
まで選択yf.
→。
までヤンク
※ F
、t
、T
もそれぞれキーに対応した動作をします。
もちろん.
の機能は失いません。Hello World.
のような行で使えば .
は.
として認識されます。
うーん、便利。
オプション設定
さらにキーを増やす
let g:ftjpn_key_list = [
\ ['.', '。', '.']
\ [',', '、', ',']
\ ['g', 'が']
\ ['w', 'を']
\ ['(', '(', ')']
\ [';', '!', '?']
\ ]
このように設定を増やしていけば、半角の,
で全角の、
、g
でが
に対して操作出来るようになります。
キーは1対1のペアである必要はなく、;
に!
と?
を担わせることも可能です。ただし、あまり増やしすぎると邪魔になることもあります。
これで、今まで対象の文字が全角であるがゆえに躊躇していたコマンドを惜しげもなく連発出来るようになるでしょう。
f
そのものを変えたい場合
f
を独自に設定したい場合は.vimrc
に以下のように記述します。
f
を<leader>f
にマッピング
let g:ftjpn_no_defalut_key_mappings = 1
let g:ftjpn_f = '<leader>f'
let g:ftjpn_F = '<leader>F'
let g:ftjpn_t = '<leader>t'
let g:ftjpn_T = '<leader>T'
半角同士でもOK
let g:ftjpn_key_list = [
\ [';', '^', '$', '*', '#', '~', '%']
\ ]
記号を打つのはシフトキーを押さなくてはいけないのでめんどくさいです。上のように設定すれば;
キーだけで複数の記号に対応できます。
ただし、狙った場所に1発で到着できなくなるケースが出てきます。
var str = '${str1}文字列結合${str2}'; // Template literal の活用
上の例で行頭から;
に飛ぼうとすると、$
で引っかかってしまいます。3f;
と打っても駄目です。最寄りが$
な時点で数えるのは$
だけになり、この例の場合2f;
までしか受け付けてくれません。
このようなことがあるので闇雲に対応する文字を増やすのは考えものです。多くの場合;
は行末で使われるので移動する上であまり影響はないかも知れませんが。
df
やcf
のことも考えると更にややこしくなってしまうので、半角同士のグループは設定しないというのも手だと思います。
問題点。英語と日本が混ざった時
こんにちは世界。I am Japanese.
このようなとき、行頭で f.
を実行するとこんにちは世界。
の。
に飛びます。
2f.
としてもI am Japanese.
の.
に飛ぶとは出来ません。悲しいです。
解決策は2つ。1つはf.
f.
と2回連続して打って移動。もう1つはf
を押してから1秒待って.
を押すことです(待機時間終了後に実行する)。そうすればプラグインとしての機能ではなく、デフォルトのf
として動作するので。
が無視されます。
このようなケースはほとんどないとはいえ、ちょっと苦しいので改善策を模索中です。
このプラグインがやっていること
- 現在行のカーソル位置から前方(後方)の文字列を取得
.
と。
を比較して近い方を取得- コマンド実行
.
と。
で近いほうを選ぶ
" 一部の記号を検索用に正規表現の形にする関数
function! s:ConvertRegex(char) abort
if a:char ==# '.'
return '\.'
elseif a:char ==# '*'
return '\*'
elseif a:char ==# '^'
return '\^'
elseif a:char ==# '$'
return '\$'
elseif a:char ==# '['
return '\['
elseif a:char ==# '~'
return '\~'
else
return a:char
endif
endfunction
" f, t 前方検索で利用する char の選定
function! s:SetForwardChar(pattern) abort
" 現在のカーソル位置から行末までの文字列を取得する
let col = col('.') - 1
let line = getline('.')[col:]
" 文字の位置を比較するときに使う辞書
let dict = {}
" 受け取った配列から1つずつ文字を取り出し、現在行の中にあるかチェック
for char in a:pattern
" 一部の文字は正規表現の形にする必要がある
let keyword = s:ConvertRegex(char)
let matchcol = match(line, keyword, 1, 1)
" 文字(key)と位置(value)の辞書に値をセット
if matchcol > 0
let dict[char] = matchcol
endif
endfor
if len(dict) == 0
" 何も見つからなかったら何もしない
return ''
else
" 最も近い位置のcolを数値で取得
let min_col = min(dict)
for [key, value] in items(dict)
if value ==# min_col
" 最も近いcolの値(min_col)と辞書の値(value)が一致した場合
" keyを正規表現から普通の形に戻して返す
return s:RevertRegex(key)
endif
endfor
endif
endfunction
function! ftjpn#Jfmove(pattern) abort
exe "silent normal! " . v:count1 . "f" . s:SetForwardChar(a:pattern)
endfunction
nnoremap <silent> f. :<C-u>call ftjpn#Jfmove('['.', '。']')<CR>
文字を取得するまでは getline() や col()、match() などを使っていますが、最終的に実行されるコマンドはデフォルトのものです。
f.
と打ったらカーソルより前方の文字の中から.と。
を検索開始。
先に。
が見つかったらf。
を実行。
先に.
が見つかったらf.
を実行。
たったこれだけです。
ドットリピートの対応
最終的に実行されるのがデフォルトのコマンドならドットリピートも自然と出来るだろうと思っていたのですが無理でした。
f
は;
と,
で繰り返し動作するので問題ないのですが、df
やcf
のリピートが上手くいかないのです。
function! s:hoge()
if 「.」の場合
execute "normal! f."
else if 「。」の場合
execute "normal! f。"
endfunction
onoremap f. :<C-u>call <SID>hoge()
上のように記述するとdf.
やcf.
をドットリピート出来ません。ノーマルコマンドを実行しているだけなのになぜ?と頭を悩ませていたのですが、<expr>
を使うことでこの問題を解決できました。
<expr>でドットリピートが可能になる
function! s:hoge()
if 「.」の場合
return "f."
else if 「。」の場合
return "f。"
endfunction
onoremap <expr> f. <SID>hoge()
複雑なことは出来ないようなのですが、デフォルト機能をちょっと拡張したいときに便利です。
まとめ。普通のVimを逸脱せずに日本語対応できた、と思う。確信はない
これで新たにキーマッピングを増やすことなく全角文字に対応する環境を用意できたはずです。
もっと便利にする方法はあると思いますが、あくまでもデフォルトに寄り添った形で実現したかったのでこれでよしとします。
諸々何かしら穴があるだろうとは思いますが、とりあえず期待通りのものは完成しました。
使いつつ改善していこうと思います。
今回のプラグイン作成で、getline()やcol()、match()、search()のような定番の関数の使い方や引数の並び、意味を少し理解できました。
このあたりの関数の意味がわかるとヘルプを読むのも苦痛でなくなり、むしろ楽しくなってきます。
やはり一度何か作ってみるものだなと、月並みな感想で締めくくりたいと思います。
所感 ~補足の与太話~
Vimで日本語を編集する際、f
とt
がうまく使えないのでなんとか楽にしたいと思っていました。いちいちIMEをONとOFFを切り替えるのは辛すぎます。
結局、w
を連発して行き過ぎたらh
で戻る、みたいなちまちまとしたカーソル移動を繰り返すことで対処していました。
インサートモードの長期滞在を前提にコントロールキーを使って移動するキーバインドを設定してみたり、f
移動のために。
や、
を新たにキーマッピングする(
しかし、手数が増える方法はめんどくさいので結局使わなくなります(なりました。コントロールやシフトを使ったらそれはもはや一手じゃなく二手だと思ってしまう派です)。
そして最終的に「そもそも英語のときと同じように操作出来るのが一番いいんじゃないか?」という結論に至りました。
同じ Vim を使っているのに、英語でプログラムを書くときと日本語の文章を書くときで操作が大きく異るのはなんか違う気がしたからです。
それに高速でテキストを編集する凄腕 Vimmer になるためには、日本語を使っているときも Vim ならではの操作を多用して修行するのが王道だと思います。きっと。
日本語文書の編集時でもノーマルモード滞在が快適なのは非常にいいもんです。