vimのマーク機能をできるだけ活用してみる

Vim Advent Calendar 2012の151日目の記事になります。

今日は自分があまり使っていなかったマーク機能を改善した話をしたいと思います。

マーク機能とは

vimにはマーク機能というものがあります。
ソース中の行に対して見えない印をつけ、すぐに特定の場所に飛ぶことが出来る機能です。

操作方法を以下に書きます。
(注:カレントバッファ内マークについてのみの説明です、より詳細な説明は :h mark-motions を参照してください)

ma ... mz

でaからzのアルファベットに対応する行を記憶して

'a

もしくは

`a

で記憶した行に移動することが出来ます。
使いこなせればとても便利な機能なのですが、いくつかの問題があってほとんど死に機能となっている方もいるのではないでしょうか。

問題点

マーク機能には次の問題があるように思います。

  • 記憶した行をどのアルファベットに割り当てたかを覚えていないといけない
  • 現在どのアルファベットを割り当てているのかを覚えていないといけない

マークの情報は

:marks

コマンドを使うことで確認することが出来ますが、情報が少なく直感的ではありません。
行に設定されているマークを視覚化するプラギンもありますが、結局その行が見える範囲にないとマークがわからないので自分には合っていないように思います。

なんとかしてマーク機能を便利に活用する方法はないでしょうか。

解決1

vimには超有能なイケメンヘルプさんが居るので何かいいものが無いか聞いてみたところ、このような記述を発見しました。

]'			カーソルがある行から [count] 個先の小文字のマークがあ
			る行の最初の非空白文字へ移動します。
			{Vi にはありません}

]`			カーソル位置以降の [count] 個先の小文字のマークへ移動
			します。
			{Vi にはありません}

['			カーソルがある行から [count] 個前の小文字のマークがある
			行の最初の非空白文字へ移動します。
			{Vi にはありません}

[`			カーソル位置より [count] 個前の小文字のマークへ移動し
			ます。
			{Vi にはありません}

これは便利。
上記のキーマップを使うことで直前、直後のマークに移動できるため

  • 記憶した行をどのアルファベットに割り当てたかを覚えていないといけない

を解決することが出来そうです。

設定しているマークの数が多い場合は思い通りの位置に飛ぶのが面倒になりますが、一つのバッファに対してそこまでマークを酷使することもないのでまぁ問題ないでしょう。
またマーク毎に入力するキーマップを変えなくていいというのも良い感じです。

解決2

  • 現在どのアルファベットを割り当てているのかを覚えていないといけない

新しくマークを付けたい場合にまだ使ってないアルファベットがどれなのかを知らないとマークを上書きしてしまう可能性があります。
これについては下記のスクリプトを用意しました。

if !exists('g:markrement_char')
    let g:markrement_char = [
    \     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    \     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
    \ ]
endif
nnoremap <silent>m :<C-u>call <SID>AutoMarkrement()<CR>
function! s:AutoMarkrement()
    if !exists('b:markrement_pos')
        let b:markrement_pos = 0
    else
        let b:markrement_pos = (b:markrement_pos + 1) % len(g:markrement_char)
    endif
    execute 'mark' g:markrement_char[b:markrement_pos]
    echo 'marked' g:markrement_char[b:markrement_pos]
endfunction

mを押すことで現在位置に対して自動的にアルファベットを割り振ってくれるようになりました。
アルファベットを使い切った場合は先頭に戻ってループします。
スマートやで。

これでマークの問題は全て解決しました!

ついでに

それでは最終的な.vimrcを、の前に、、、、
更に別の問題が出て来ましたのでそれの対策を。

ヘルプには以下のように書かれていました。

小文字のマーク 'a から 'z まではマークのあるファイルがバッファリストに存在す
る限り覚えておかれます。もしファイルをバッファリストから削除するとそのファイ
ルに関するマーク一は全て失われます。またマークを含んでいる行を削除するとその
マークは消されます。

しかし自分の環境が特別なのかバッファを閉じてvimを再起動してもマーク情報は残ったままでした。
前回のマーク情報が残っているのはうざったいので消したいと思います。

カレントバッファのマーク情報を全て消すためには

:delmarks!

とすればいいので、バッファを開いた時点で削除するように自動コマンドを書きます。

autocmd BufReadPost * delmarks!

これでOK。

またバッファを開いた際に、最後に編集していた位置に戻る自動コマンド

autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g`\"" | endif

を設定している場合はdelmarks!の前に書くよう気をつけてください。

最終的に

使いやすさ等を考慮して自分のマーク設定は最終的に以下のようになりました。
マッピングについては、Vim-users.jp - Hack #59: 分かりやすいKey-mappingsを定義するを参考にしています。

" マーク設定 : {{{

" 基本マップ
nnoremap [Mark] <Nop>
nmap m [Mark]

" 現在位置をマーク
if !exists('g:markrement_char')
    let g:markrement_char = [
    \     'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    \     'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
    \ ]
endif
nnoremap <silent>[Mark]m :<C-u>call <SID>AutoMarkrement()<CR>
function! s:AutoMarkrement()
    if !exists('b:markrement_pos')
        let b:markrement_pos = 0
    else
        let b:markrement_pos = (b:markrement_pos + 1) % len(g:markrement_char)
    endif
    execute 'mark' g:markrement_char[b:markrement_pos]
    echo 'marked' g:markrement_char[b:markrement_pos]
endfunction

" 次/前のマーク
nnoremap [Mark]n ]`
nnoremap [Mark]p [`

" 一覧表示
nnoremap [Mark]l :<C-u>marks<CR>

" 前回終了位置に移動
autocmd MyAutoCmd BufReadPost * if line("'\"") > 0 && line("'\"") <= line('$') | exe 'normal g`"' | endif

" バッファ読み込み時にマークを初期化
autocmd MyAutoCmd BufReadPost * delmarks!

" }}}

これで快適マーク生活を送れるようになったでしょうかね。
しばらくはこれで様子を見たいと思います。

いまいちマークを使ってないなーという人は参考にしてみてください。