読者です 読者をやめる 読者になる 読者になる

えぇ... ただ置いていただくだけで結構です

tech vim

言葉通り長くなりそうなので,まずここで tl;dr 的に.

今回の作業をもって,こんな設定を加えることにしました.;

xnoremap <expr> p 'pgv"'.v:register.'ygv<esc>'

見ての通り p コマンド,つまり「貼り付け*1」の操作に関するものです.

選択した範囲へ貼り付け*2た時にやられちゃう,クリップボードの書き換えをしないようにしています.
とは言え実はこれ,見掛けだけの話で,書き換えと言う処理は行われていなかったりするのですが.

簡単に分解しますと.

step key description
1 p 選択した範囲へ貼り付け
2 gv #1 で貼り付けた内容を選択
3 "v:registery 選択した範囲の内容をコピー
4 gv<esc> 最後にもう一度範囲選択し解除

つまりやっていることは,"書き換えを回避させる" ではなく,何でもない,"再度クリップボードにコピー" して書き変えていないように見せている,と言うだけ.

ここで最終的に参考とした情報はこちら.;

» Vim: how to paste over without overwriting register

また上述参照先にもありますが,「v:register ってイミフ」とか,「ごく一般的な使い方で十分」とかなら恐らく単純に,{rhs} で v:register なくても良いのかも.;

xnoremap p pgvy

と言う事でして.今回は,今まであった「何となく」であったレジスタ registers というものが「なるほど」に変わった事もあって,その辺り含め以下にメモ.

Smashed filter protects lens....Paper / SGPhotography77



キッカケは,全く別ルートで目にしたこちらで触れられていた《10.xやsではヤンクしない》にある記述でした.

「なるほど」と思ったのと同時に,"_ と言うものがあって,それが消去専用レジスタ Black hole registerと言うもので,自分の場合「"c" コマンドなんかもそうだな~」等と色々試行錯誤しているうちに,「もしや?」と思ったのです.

"p" についても,こちらの表現借りれば,元の,つまり貼り付け前の内容でレジスタが汚されるの困るな,と常々思っていたことを.

これも,どうにかできないものか.

対象とする操作

何てことない,ごくごくフツーの操作です.*3
今更取り立てて説明するものでもない程.

"或る部分の内容を,別の部分へ上書きで貼り付け (置き換え) る"

「使いたい或る部分の内容を選択してコピー*4なりカット,そして置き換え先となる別の部分を選択し,貼り付け*5.」

普通ですとこれ以上の話は無いのですが,vim はここに,何となく面倒な風変りな振る舞いを持ち合わせておりまして,故に少し補足を加えることとなります.

それは「貼り付け」た時の,クリップボード*6対する振る舞いです.

その「貼り付け」は,クリップボードを「置き換え先と」して選択していた内容に書き換える

「選択し,貼り付け」ると,vim ではクリップボードの内容を「置き換え先と」して選択した内容を格納し,書き換えるのです.
「コピーなりカット」で格納した内容ではなく.

おそらく普通の感覚ですと,クリップボードの内容は「コピーなりカット」した内容そのまま,だと思います.ところが vim の場合は違ったりします.

でも,あって欲しいのはやっぱりそれじゃない

これは自身が vim の意図を,つまり今回取り上げたようなケースにおいて,貼り付け前の内容の再利用をデフォとして提供している意図を理解できていない,という所が問題なのは分かっているのですが,ここはやりいつもの慣れ親しんだ感じの使用感であって欲しいわけです.

貼り付け前の内容が "じゃない" から別の内容に置き換えたのに,その "じゃない" 方がクリップボードに納まっている,と言うのはどうしても気持ち悪かったりするし,そして忘れた時に、この仕様が原因でひょっこり顔を出して来る思わぬ結果に意外な程ストレスを感じたりするのです.

ここはやはりクリップボードにあるのは自分の意図で納めた方の内容であって欲しい.

そんなこんなで結果,以下のような設定を vimrc に置くことにしたわけです.冒頭でも触れましたが改めて.;

xnoremap <expr> p 'pgv"'.v:register.'ygv<esc>'

{rhs} の 'pgv"'.v:register.'ygv<esc>' の捉え方で分解するとこうなります."+" で繋ぐ書き方で,;

p + gv + "v:registery + gv

簡単な翻訳をすると,;

貼り付けて + 貼り付けた領域を選択して + それをコピーして + カーソル位置調整

そうです,実はこれ,書き換える振る舞いそのものを制御しているのではなく,"p" による書き換えはそのままやりすごし,やらせちゃって(笑,後で貼り付けた内容をコピーしクリップボードに上塗りする形で,あたかもそうであったかのように見せかけているだけ,だったりするのです.

イメージ掴み難い場合は,一度実際にゆっくり操作してみるとより分かり易いと思います.そして各ステップの途中で :reg 何か適宜挟んで,レジストリの内容がどうなっているのかを確認しながら進めると良いかも,です.;

  1. visual モード入って v,選択し~,
  2. それをヤンク y して~,
    → ここで一応 :reg なんかをしてみると良いかも.;
  3. 貼り付け先となるところへ移動してまた visual モード v で選択し~,
  4. 貼り付け p
    → ここでも一応 :reg してみると良いかと.;
    • たぶん "" の内容が,3 で選択した,貼り付け前の内容になっているかと.
    • もしかしたら,"- または "1 の内容も同様に貼り付け前の内容にかと.おそらく "切り取った",と言うことなのだと思います.
  5. ここで gv 叩いて~,
    あら不思議,4 で貼り付けたものが再度選択されます.
  6. ヤンク y
    2 同様,"""0 に格納されます.
    そうです.こうして,レジストリ (クリップボード) の中身は,ヤンク (コピー) なり削除 (カット,切り取り) した内容のまま,となっているのです.というより,そうしたのです,と表現した方が正しいでしょうか.それは "かなり無理矢理" ではあるのですが.
  7. そして再び gv 叩いて <esc>
    これは特に深い意味はありません.事後処理です.

    問題はカーソルの位置.

    6 でそのまま終わると,カーソルの位置が対象の範囲の開始地点になります.個人的にこれがちょっと面倒でして,自分としては終わった時のカーソルは当該範囲の終点にあって欲しい,と思って定義しています.これも全然エレガントではありませんが(恥,こんなことしか思いつきませんでした.

このアイディアは,こちらがベースになっています.;

» Vim: how to paste over without overwriting register

で,これで知った事メモ.

gv は《プットしたテキストが選択される

なのだそうです.こんなんあるの,知りませんでした.*7

ヤンク y,削除 d/xレジスタの関係

この辺りが上手く纏まっているように思えたので,必要箇所のみ抜粋.

table. レジスタの種類 (一部)

register register name description 俺々 notes
"" 無名レジスタ (Unnamed register) 最後に用いられたregisterを指す 直近のすべて
"0 番号付きレジスタ 0 (Numbered register 0) yankしたときに用いられるregister ヤンク
"1 番号付きレジスタ 1 (Numbered register 1) delete,changeの際に用いられるregister 削除,行単位
"2 番号付きレジスタ 2 (Numbered register 2) delete,changeの際,旧"1の値が入る その 1 つ前
"3 番号付きレジスタ 3 (Numbered register 3) delete,changeの際,旧"2の値が入る その 2 つ前
... 番号付きレジスタ .. (Numbered register ..) "4から"9まで同じ ... 順々スライド
"- 小削除用レジスタ (Small delete register) 行以下のdelete内容が入る 削除,行未満
"_ 消去専用レジスタ (Black hole register) 何も起こらない 何も残らない

個人的にはとても助けられました.この整理法知って開眼.感謝.

v:register

v:register expands to the last register name used in a normal mode command.

もちろんヘルプ :h v:register を一番最初に当たったのですが,なぁーに言ってんのか良く分からなくて(笑

改めて冒頭参照先の上の一行を見てやっとこさ合点がいく.

v:register は,直近で使われたレジスタの名前.

:echo v:register で確かめてみると "*" と返ってくる.

'clipboard' に"unnamed" か "unnamedplus" が含まれているときはデフォルトはそれぞれ '' か '+' になる

なるほど.

ちなみにレジスタの名前ではなく,さらにその中身みたい場合は,:echo getreg(v:register))

使うマップコマンドは xnoremap

vnoremap ではない.「visual モードだから "v"」かと思いきや,そう言うものじゃないらしいです.

visual モードでのマップだったら,自分の場合は,"v" ではなく "x" になるようです.あくまで "自分の場合は" です.

今更になってやっとこさ(笑 ハッキリと理解できました.

改めて考えることになったキッカケは,この一文でした.;

And you'd use xnoremap instead of vnoremap, according to the docs.

これを目にして docs,つまりヘルプに当たったのです.

"v" だとビジュアルモードと選択モード両方で機能する.

でも簡単に言ってしまいますと,あくまで自分の場合なのですが,選択モードの説明読むと,このモードに入ることって無いように思えるのです.マウスって使わないですし.

NOTE: 選択モードで印字可能文字にマップを定義するとユーザーの混乱を招くかもしれません.印字可能文字に対しては明示的に :xmap と :smap を使い分けるのがいいでしょう.

と言う事で,xnoremap なのです.

<expr>

これもちょくちょく目にはしながらも,今までヘルプ読んでも良く意味が分からなくて(笑,「ま,いいや」の "言われた通り" 的に,コピペでやり過ごしていた事柄のひとつでした.

そして今回,マップの {rhs} の中のピリオド . が目に入り,文字列がピリオドで繋がっていると言う事が分かった時,ヘルプで言っている事がやっと理解できたのです.*8

マップや短縮入力を定義するときに "<expr>" 引数を指定すると,引数が式 (スクリプト) として扱われます.マップが実行されたときに,式が評価され,その値が {rhs}として使われます.

via. :h map-

今回の xnoremap <expr> p 'pgv"'.v:register.'ygv<esc>' ですと.;

なるほど.

つか《"<expr>" 引数を指定すると,引数が》と "引数" ってのが連続重複して現れて,何をどう指しているのか分かり難いし,《引数が式として》ってのもイメージ出来なくて腑に落ちないし,《式が評価され,その値が》って評価と値の関係分からないし...今となっては,"意味するもの-されるもの"*9 の結び付け方がイメージ出来るようになって,その時のコンガラガッタ思考回路が遠のいて,その "分からなかったこと" が逆に理解できなくなりつつありますけど(笑

その他にも,方法はあります

調べている中で,この他にも幾つかの方法はあるようです.

ここからは,それらについて触れていきたいと思います.

どうしてこんな結構面倒なことをするかというと,そんな試行錯誤の結果,どうしてこの xnoremap <expr> p 'pgv"'.v:register.'ygv<esc>' に落ち着いたか,その意思決定過程を記録しておきたいとの考えあってのことです.

なぜこの設定を採用するに至ったか.この判断基準を挙げ,必要に応じて判断にあたってインパクトのあったポイントを付け加えながら箇条書きにしますと.;

  1. きちんと動く.当然の話ですが.
  2. 思った通りの結果を返してくれる.
    → 貼り付けの位置がズレたりしない.
  3. いつもの感覚の操作で.
    → ヤンク y でも,削除 x/d でも,いつもの p の操作で.勿論,修飾キーを使うなんてのもナシ.
  4. シンプルで分かり易い.
    → 長いスクリプトまで頑張らなくちゃならないんだったら,諦める.そのくらいでいい(笑

至極当然のことを書いていると言えば,そうなのですが.

方法1: xnoremap p "_dP

これが一番最初に見つけ,実際試した設定でした.ところがこの方法は,自分に合いませんでした.

参考としたリソースはここです.;

» vim ヤンクとクリップボードまわり - ソフトウェアとハードウェアの境界
» Replace a word with yanked text - Vim Tips Wiki - Wikia

分解しますと,こうなるか思います.;

"_d + P

消去専用レジスタ "_ を使っています.

やっていることは,「貼り付ける対象として選択した範囲を,消去専用レジスタ "_ を指定して削除し,そこに貼り付け P を行う」,となります.

これは本当の意味で "元の内容での書き換えを回避させる" を実現したものと言えるのではないでしょうか.今回の設定のような見せかけとは異なって.

しかし,先ほども言いましたが,ここではこの方法を採用しませんでした.

理由は,P にあります.

P は貼り付けをカーソルの前に対して行うのですが,これが原因であるケースにおいて,自分にとっては,なのですが,都合の悪い動きをしてしまうのです.

貼り付け対象となる範囲の両端が,何らかの文字であれば問題はないのですが,範囲の

対象となる範囲の両端が何らかの文字列であれば全く問題ないのですが,その範囲の前がスペースかタブで,かつ終点が行末だと,貼り付けの位置が 1 文字分前になってしまうのです.

ちょっと分かり難いかとは思います.

こんな箇所があったとして,この "put_data" の部分に別の内容を上書き貼り付けたいとします.

label1:  put_data
label2:  data2

これで "put_data" を選択し貼り付けを行うと,こんな感じでズレてしまうのです.

label1: data1
label2:  data2

なぜこうなるか想像できますか? できますよね.理解できない場合は,"_d + P を 1 ステップづつゆっくりやってみると理解できると思います.ポイントは d した時のカーソルの位置です.

ちなみに望んでいる結果は,;

label1:  data1
label2:  data2

これは先述したポイントの 2 です.ちょっとした所ではあるのですが,個人的にこのようなケースは良くあるので,これ以外に他の方法も探ってみることになったのです.

方法2: xnoremap p pgvy

実はこの方法を見つけた時,これで決めようと思ってました.

参照先は勿論こちら.;

» Vim: how to paste over without overwriting register

考え方はとても簡単.

何も分かっていなかった時の自分にとっては,理解し易かったので,すんなりと導入を決めました.

Use the following:

xnoremapppgvy

this will reselect and re-yank any text that is pasted in visual mode.

via. [Vim: how to paste over without overwriting register][03]

これで数時間? 数十分? ほど過ごしたのですが,終わった後のカーソルの位置が範囲の始点に来てしまうのが気になって気になって,再び他の方法も試してみる事に.

これは先のポイント 2 の問題でリトライ,と.

方法3: vnoremap p "0p

これは最も素直な考え方ではないでしょうか.

これも決める一歩手前までいったものでした.

参考にしたのはこちら.;

» vimでヴィジュアルモードの連続貼り付け - Qiita
» vimの連続コピペできない問題 - Qiita

この "0

前出「レジスタの種類」 でも触れてますが,これはヤンク専用のレジスタとなります.

貼り付けによって,貼り付け先範囲の内容が格納されるのは削除 d? x? 扱いですので小削除用レジスタ "- はじめ,どれでも使う無名レジスタ "",設定によっては "* を使うので,"0 は汚されていないわけです.

つまりヤンク (コピー) した時点の内容,そのまま,だったりするのです.

ヤンクされた文字列は 0 というレジスタにも自動的に入ります.そして,削除などでは 0 レジスタは使われません.

ですので,vnoremap p "0p とすれば良いっ! と.これで一件落着!

と,思いきや,これもこれで惜しいところがあるのです.

そう,これはヤンク (コピー) だと良いのですが,削除 (カット/切り取り) だと上手くいかないのです.

削除 (カット/切り取り) して貼り付けると,その内容は直近のヤンク (コピー) のものだったりするのです(笑

人によっては終章キー,control キーを混ぜて <C-p> とした報告もあるのですが,ここでは違うのです.

こちらも同様にポイント 2 の要因で破棄.

で結果,冒頭のような設定を採用するに至ったのです.というお話.

ということで

上述冒頭にあげたような設定に至ったのです,というお話.

勿論,今後使っているうちに色々気付いて,もしかしたら「これじゃだめじゃん」となるかもしれません.

が,当面はこれで過ごしてみようと思います.

もし,何か抜けているとかな盲点や,問題がありましたら是非ご指摘,ご指南頂けると嬉しいです.な.

やっぱ書きましたね(笑

はい,おしまい.

*1:vim の世界では「プット」と呼ぶようです.この "p" と言うのは,一般的にあるような「貼り付け(ペースト) paste」の "p" ではなく,「プット put」の "p" なのかと,恐らくですが.

*2:正確にはレジスタと言われます.

*3:恐縮な話,自分みたいな vim 弱ゆとり(いや自分だけでしょうが笑)にも成る丈伝わるよう vim 界ネイティブな表現は避け,誤解がない程度に平たい表現を選択しながら進めたいと考えています.vim 原理主義の方々にはキッ! と思われるような記述が度々出てくるかと思いますが,ここは見逃してください.

*4:vim ではヤンク (yank) と表現しますかね.

*5:vim ではプット (put) と言いますね.

*6:vim ではレジスタに,となるかと.

*7:よく「こういう仕様があると良いな」と思いついたな,と.これを知ってますます,今回採りあげたケースの "貼り付け前の元の内容をレジスタへ格納" という仕様に込められた本当の意味が気になってくるのです.

*8:ピリオドって文字列の連結だよな.<pgv"> と <v:register> と <y> を繋げている.<v:register> は変数で実際の中身は <∗> のはずなので,<pgv"∗y> と書いたのとと同じはず ...「あ!」.

*9:懐かしいw