LoginSignup
11
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-01-28

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`"
11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10