endaaman.com

2019-04-10

Tips

ヤンク履歴管理する仕組みを作った

Vimのヤンクはレジスタ領域に保持され、その名の通りVimにとっての一次記憶の領域であり、作業者の海馬の拡張領域とも言える。非常に重要な機能の一つであるが、これまで上手く活用できていなかったので少し気の利いたものを考えてみた。

用語整理

  • ヤンク ... yyでお馴染みのVimにおけるコピーのこと
  • レジスタ ... ヤンクしたものが保持される領域。09 azに加えて+ * / など特殊なものをある
  • クリップボード ... OS上のコピペで使う領域。ざっくりと「クリップボード」と「プライマリ」の2種類ある(後述)

OSのクリップボードについての基礎知識

私がLinuxしか持ってないのでLinuxを前提とする。まず先にその辺を説明。Linuxで他のOSと同様にブラウザなどでテキスト選択して<Ctrl> + Cを押すとそのテキスト片は「クリップボード」に保持される。<Ctrl> + Vでペーストできる、みんなが知ってるクリップボードである。

問題はもう片方の「プライマリ」である。こいつはテキスト選択しただけで、その選択部分が保持される領域である。昔はX orgにはこれしかなかったが、時代を経て”死に機能”になってしまったものである。これを出力するときにわかりやすいのはターミナルである。基本的に「マウスのホイールクリック」で出力できるはずである。

Vimのクリップボード拡張

Vimにはクリップボード拡張があり、これによりOSのクリップボードとVimのレジスタを連携させることができる。

拡張なし

set clipboard=

SSHとかでGUIなしのサーバーで触るときはこの状態が多いかもしれない。ヤンクした内容は "" (無名レジスタ)に入り、ペースト時も "" から貼り付けられる。

unnamedplus

set clipboard=unnamedplus

これがLinuxでは「クリップボード」との連携を意味し、ヤンクした内容は"+に保持される。"+はOSの「クリップボード」と共有された状態になっていて、ブラウザなどで<Ctrl> + Cを押すと、その内容が"+にも入ってくる。逆にlet @+ = 'hoge'と書き込めば、他のアプリで<Ctrl> + Vをおせばhogeを貼り付けられる。

unnamed

set clipboard=unnamed

これはLinuxでは「プライマリ」との連携を意味する。ヤンクした内容は "* に保持される。なのでヤンクしたものをターミナルで中クリックすればその内容をターミナルに貼り付けたりできる。

ややこしいことに、macOSやWindowsではこっちがOSのクリップボードと連携している。なので.vimrcを書くときはOSを判別して

if has('win32') || has('win64') || has('mac')
  set clipboard=unnamed
else
  set clipboard=unnamedplus
endif

というように書くのが良い。

ヤンク履歴を管理する

下のような仕様を考える

  • ヤンクしたものを"+"a"b"c"d→……とずらす
  • 改行のみ、空文字は無視する
  • "wyy のようにレジスタを指定した場合も適切に動く

実装するとこんな感じ

function! ShiftRegister() abort
  if &clipboard =~? 'unnamedplus'
    silent let l:clipboard = getreg('+')
  elseif &clipboard =~? 'unnamed'
    let l:clipboard = getreg('*')
  else
    let l:clipboard = getreg('"')
  endif
  if l:clipboard ==# '' || l:clipboard ==# "\<NL>"
    return
  endif
  " 番号のレジスタだと削除時に使われるのでアルファベットの前半を使う
  let l:list= ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
  if @a != l:clipboard
    let l:i = len(l:list) - 1
    while l:i > -1
      call setreg(l:list[l:i], (l:i == 0 ? l:clipboard : getreg(l:list[l:i - 1])), 'v')
      let l:i -= 1
    endwhile
  endif
endfunction

function! RegisterPrefix(key) abort
  call ShiftRegister()
  " `"wyy` のようにレジスタを指定するとv:registerには`'w'`が入ってくる
  return '"' . v:register . a:key
endfunction

" 基本操作を書き換える
noremap <silent> <expr> y RegisterPrefix('y')
noremap <silent> <expr> d RegisterPrefix('d')
noremap <silent> <expr> c RegisterPrefix('c')
noremap <silent> <expr> Y RegisterPrefix('Y')
noremap <silent> <expr> D RegisterPrefix('D')
noremap <silent> <expr> C RegisterPrefix('C')
nnoremap <silent> <expr> yy RegisterPrefix('yy')
nnoremap <silent> <expr> dd RegisterPrefix('dd')

" リターンで行をコピーできるようにする
nnoremap <silent> <expr> <C-m> RegisterPrefix('yy')
vnoremap <silent> <expr> <C-m> RegisterPrefix('y')
onoremap <silent> <expr> <C-m> RegisterPrefix('y')

こんな感じのを追加。割と便利。


©2024 endaaman.com