GNU screenからVimへのペーストを、インデント崩れを回避しつつ簡潔な操作で行う

# GNU screenからVimへのペーストを、インデント崩れを回避しつつ簡潔な操作で行う

[Vim Advent Calendar 2013](http://atnd.org/events/45072)の59日目(2014-01-28)の記事です。

GNU screenを使う目的の一つは、キーボード操作によるコピー&ペーストです。
GNU screenの他windowのシェル等でコピーした文字列をvimにペーストすることがよくあります。

が、GNU screen側の操作でペースト(`CTRL-O .`に設定してます)すると、vim側の'smartindent'が効いてインデントが崩れることがあります。

そうなった時は、以下の操作をしていましたが、繁雑なので、簡潔な操作で行う方法を調べてみました。

1. `Esc`でInsert modeを抜けて、
2. `u`でundoして、
3. `:se paste`して、
4. `i`でInsert modeに入って、
5. 再度GNU screenのペースト操作(`CTRL-O .`)をして、
6. `Esc`してInsert modeを抜けて、
7. `:se nopaste`する。

(なお、GNU screenのペースト操作でペーストする際は、vim側が日本語入力モードになっていたら、日本語入力モードをオフにする操作も必要になります。この点も解決したいところです。`:se paste`すれば問題ないですが。)

### 方法1: 'pastetoggle'を使用

```vim:.vimrc
set pastetoggle=<C-Q>
autocmd InsertLeave * set nopaste
```

1. Insert modeで、`CTRL-Q`により、set pasteして、
2. GNU screenのペースト操作
3. `Esc`してInsert modeを抜けると、set nopasteされる。

### 方法2: `:a!`や`:i!`を使用

`:a!`や`:i!`すればインデントは崩れない、という記述も見かけるのですが、手元ではなぜか駄目だったので`:se paste`を使っていました。

少し試してみたところ、`:set smartindent`だけしていると駄目で、`:set autoindent`も必要なようです。

1. `:a!`
2. GNU screenのペースト操作
3. `<CR>`で改行して、`.<CR>`で終了

終了方法がちょっと癖がある印象です。

### 方法3: Xのselectionを使う

vimで+xterm_clipboard featureが有効になっている場合、xterm内のvimでも、普通に`"*p`等の操作でXからのペーストが可能。

1. `"*p`

もしくは、Insert modeで、

1. `CTRL-R CTRL-O *`

ただし、GNU screenのペーストバッファをXのselectionに入れる設定や、操作(例えば以下の設定を~/.screenrcに入れて`CTRL-O y`)が必要。

```sh:.screenrc
bind y eval writebuf screen 'stuff "xsel -i -p < $HOME/tmp/screen-xchg; exit^M"'
```

X無しの場合(Windowsからsshでリモートに接続している場合等)は使用不可。

### 方法4: [fakeclip](http://www.vim.org/scripts/script.php?script_id=2098)を使用

Vim側で`"&p`すれば、GNU screenのペーストバッファからペースト可能。

1. `"&p`

tmuxやX、Mac OS X、Cygwinにも対応。Vim側操作で、各システムからのペーストや各システムへのyankが可能。

### 方法5: fakeclipと同様の自作script

(車輪の再発明をしてました。fakeclipを失念してて、既に同じものがありそうだと思って、「GNU screen vim paste」あたりで少しぐぐっても見つけられず、自分で書いてました。「tmux vim」でぐぐってfakeclipを発見。)

1. `gp`

fakeclipに比べて、以下の違いがあります。

* ペーストする文字列に改行が含まれていたら(最後が改行で終わっていなくても)、
  `:put`を使ってlinewiseにペースト。
  行の途中にいる時に複数行をペーストする場合、
  linewiseの方が使いやすい気がするので。
  (ただし、`"*p`はlinewiseではないので、整合性はなくなりますが。)
* screenへのyankは、レジスタ指定無しでyank操作をした後、
  screenのペーストバッファへのコピーを行う操作(`gy`)を行う形。
  これにより、screen側にyankするつもりだったのに、
  `"&`を付けずにyank操作をしてしまった場合に、
  `"&`を付けて再度yank操作をし直さなくても、
  `gy`を入力するだけで良くなります。
* screenへのyank時に、screenのreadbufが、「Slurped 28 characters into buffer」
  のようなメッセージを出すと少し目ざわりなので、出さないようにmsgwait 0を追加。

```vim:.vimrc
" GNU screenのペーストバッファの内容を、Vim側に読み込む。
" ~/.screenrcで以下の設定をしている前提
"   bufferfile $HOME/tmp/screen-xchg
let s:screenfile = '~/tmp/screen-xchg'
function! s:PasteFromScreen()
  silent !screen -X writebuf
  let s = system('cat ' . s:screenfile)
  call s:PasteStr(s)
endfunction
nnoremap <silent> gp :<C-U>call <SID>PasteFromScreen()<CR>

function! s:PasteStr(str)
  let save_reg = @@
  let @@ = a:str
  if stridx(a:str, "\n") >= 0
    put
    " ペースト後のカーソル位置を通常の`p`の場合と合わせる
    normal! '[
  else
    normal! p
  endif
  let @@ = save_reg
endfunction

function! s:YankToScreen()
  call writefile(split(@@, '\n', 1), expand(s:screenfile), 'b')
  " suppress message like "Slurped 28 characters into buffer"
  silent !screen -X eval 'msgwait 0' readbuf 'msgwait 1'
  "silent execute '!screen -X register . "$(cat ' . s:screenfile . ')"'
endfunction
nnoremap <silent> gy :<C-U>call <SID>YankToScreen()<CR>

if has('xterm_clipboard')
  " linewiseにしたいので
  nnoremap <silent> gP :<C-U>call <SID>PasteStr(@*)<CR>
  "nnoremap <silent> gP "*p
  nnoremap <silent> gY :<C-U>let @* = @@<CR>
else
  function! s:PasteFromX()
    let s = system('xsel -o -p')
    let s = iconv(s, 'utf-8', &encoding) " &encがeuc-jpの場合用
    call s:PasteStr(s)
  endfunction
  nnoremap <silent> gP :<C-U>call <SID>PasteFromX()<CR>
  nnoremap <silent> gY :<C-U>call system('xsel -i -p', @@)<CR>
endif
" XXX: vimが-X付きで起動されたかの判定もするなら以下。
"      もっと簡単に判定できるならいいけど、ここまでやらなくてもいい気が。
" function! s:has_xterm_clipboard()
"   if !has('xterm_clipboard')
"     return 0
"   endif
"   let expect = strftime('%c')
"   call system('xsel -i -p', expect)
"   let actual = @*
"   if actual ==# expect
"     return 1
"   endif
"   return 0
" endfunction
```

### おまけ: ペースト直後に、ペーストした複数行のインデントを調整

通常の`p`を使う場合でも、ペースト直後にインデントの増減をすることが多いのですが、`']`を打つのが少し面倒なので。

```vim:.vimrc
" paste直後に、pasteした複数行のインデントを増減
nnoremap <p <']
nnoremap >p >']
nnoremap =p =']
" pastetoggleや、`:a!`用。redoできるように、最初に先頭に移動
nnoremap <P '[<']
nnoremap >P '[>']
nnoremap =P '[=']
```

## gvim使用時も同じ操作ができるようにする

gvim使用時も同じ操作ができるように、~/.gvimrcに以下の設定を入れておきます。
(Windows上のgvim用でも同様に、~/_gvimrcに設定を入れておきます。)

```vim:.gvimrc
nnoremap <silent> gp "*p
nmap <silent> gP gp
nnoremap <silent> gy :<C-U>let @* = @@<CR>
nmap <silent> gY gy
```

## 参考:Bracketed Paste Mode
* [Bracketed Paste Mode(括弧付きの貼り付けモード)](http://qiita.com/kefir_/items/415a30930a80b9b42adb)
* [「vim からの制御シーケンスの使用例」をScreen上でも使えるようにする](http://sanrinsha.lolipop.jp/blog/2011/11/%E3%80%8Cvim-%E3%81%8B%E3%82%89%E3%81%AE%E5%88%B6%E5%BE%A1%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B9%E3%81%AE%E4%BD%BF%E7%94%A8%E4%BE%8B%E3%80%8D%E3%82%92screen%E4%B8%8A%E3%81%A7%E3%82%82%E4%BD%BF.html)

## 参考:GNU screenのペーストバッファ内容を、vimで編集した内容に更新する

普段は日本語入力IMを起動していなくて、lynxやコマンドラインで少しだけ日本語が入力したい場合、以下の操作をしていました。
(なお、lynxは、テキストフィールドでも、`CTRL-X e`でvimを起動して編集できるようになったので、以下の操作は不要。)

1. screenの別windowを作って
2. vimを起動して
3. vim内で日本語を入力して(tcvimeを使ってIM無しでvimだけで日本語入力)
4. 入力した日本語をscreenのコピー操作(対象文字列の選択操作も含む)でコピー
5. screenの元のwindowに切り替えて
6. screenのペースト操作でペースト
7. (その後、一時的にvimを起動したwindowの後始末)

繁雑なので、以下の手順でできるようにするシェルスクリプトです。
screenのペーストバッファ内容を、vimを起動して編集した内容に更新します。

1. screenの操作で、編集用vimを起動(`CTRL-O v`)
2. vim内で日本語を入力して
3. vimを終了(`:x`)。
   (終了により、vimで編集した内容がscreenのペーストバッファに入り、
   vim用のwindowも閉じられる。)
4. screenのペースト操作でペースト

~/.screenrcの設定:

```sh:.screenrc
bind v eval screen 'stuff "vs; exit^M"'
```

上で呼んでいる~/bin/vsの内容は以下。

```sh:~/bin/vs
#!/bin/sh
# edit file and copy to screen's buffer
cp /dev/null $HOME/tmp/.sv
$EDITOR $HOME/tmp/.sv
screen -X register . "`cat $HOME/tmp/.sv|nkf -e`"
```