Edited at

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

More than 5 years have passed since last update.


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

Vim Advent Calendar 2013の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'を使用


.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)が必要。


.screenrc

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


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


方法4: fakeclipを使用

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を追加。


.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を使う場合でも、ペースト直後にインデントの増減をすることが多いのですが、']を打つのが少し面倒なので。


.vimrc

" paste直後に、pasteした複数行のインデントを増減

nnoremap <p <']
nnoremap >p >']
nnoremap =p =']
" pastetoggleや、`:a!`用。redoできるように、最初に先頭に移動
nnoremap <P '[<']
nnoremap >P '[>']
nnoremap =P '[=']


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

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

(Windows上のgvim用でも同様に、~/_gvimrcに設定を入れておきます。)


.gvimrc

nnoremap <silent> gp "*p

nmap <silent> gP gp
nnoremap <silent> gy :<C-U>let @* = @@<CR>
nmap <silent> gY gy


参考:Bracketed Paste Mode


参考: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の設定:


.screenrc

bind v eval screen 'stuff "vs; exit^M"'


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


~/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`"