kill-new 関数 ¶バージョン22では、kill-new関数は次のようになっています。
(defun kill-new (string &optional replace yank-handler) "Make STRING the latest kill in the kill ring. Set `kill-ring-yank-pointer' to point to it. If `interprogram-cut-function' is non-nil, apply it to STRING. Optional second argument REPLACE non-nil means that STRING will replace the front of the kill ring, rather than being added to the list. ..."
(if (> (length string) 0)
(if yank-handler
(put-text-property 0 (length string)
'yank-handler yank-handler string))
(if yank-handler
(signal 'args-out-of-range
(list string "yank-handler specified for empty string"))))
(if (fboundp 'menu-bar-update-yank-menu)
(menu-bar-update-yank-menu string (and replace (car kill-ring))))
(if (and replace kill-ring)
(setcar kill-ring string)
(push string kill-ring)
(if (> (length kill-ring) kill-ring-max)
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
(setq kill-ring-yank-pointer kill-ring)
(if interprogram-cut-function
(funcall interprogram-cut-function string (not replace))))
(関数が対話型ではないことに注意してください。)
いつものように、この関数を部分ごとに見ていくことができます。
関数定義にはオプションの yank-handler
引数があり、これを指定すると、太字や斜体などテキストに追加されたプロパティをどのように処理すべきかを関数に伝えることができます。これについては割愛します。
ドキュメントの1行目は理にかなっている:
Make STRING the latest kill in the kill ring.
ひとまず、残りのドキュメントは飛ばしましょう。
また、冒頭の if 式と menu-bar-update-yank-menu
を含む数行のコードはひとまず飛ばしましょう。これらについては後ほど解説します。
重要な箇所は以下の通りです。
(if (and replace kill-ring)
;; then
(setcar kill-ring string)
;; else
(push string kill-ring)
(if (> (length kill-ring) kill-ring-max)
;; avoid overly long kill ring
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
(setq kill-ring-yank-pointer kill-ring)
(if interprogram-cut-function
(funcall interprogram-cut-function string (not replace))))
条件判定は (and replace kill-ring)
です。これは、キルリングに何らかのものが入っており、かつ変数 replace
が真であるという2つの条件が満たされたときに成立します。
kill-append 関数が replace
を true に設定し、かつキルリングに少なくとも1つのアイテムが存在する場合、setcar
式が実行されます:
(setcar kill-ring string)
setcar 関数は、実際には kill-ring リストの最初の要素を string
の値に変更します。最初の要素を置き換えるのです。
一方、キルリングが空であるか、replaceがfalseである場合は、条件のelse部分が実行されます:
(push string kill-ring)
push は第1引数を第2引数に代入します。これは旧来の
(setq kill-ring (cons string kill-ring))
あるいはより新しいもの
(add-to-list kill-ring string)
偽の場合、この式はまず、既存のキルリングの先頭に新しい要素として string
を付加することで、キルリングの新しいバージョンを構築します(これが push
の動作です)。次に、2番目の if 節を実行します。この2番目の if 節により、キルリングが長くなりすぎるのを防ぎます。
これら2つの表現を順番に見ていきましょう。
else部分のpush行は、キルリングの新しい値を、キル対象の文字列を既存のキルリングに加えた結果に設定します。
具体例を通して、これがどのように機能するのか見てみましょう。
まず、
(setq example-list '("here is a clause" "another clause"))
この式を C-x C-e で評価した後、example-list を評価してどのような値が返ってくるかを確認できます:
example-list
⇒ ("here is a clause" "another clause")
次に、以下の式を評価することで、このリストに新しい要素を追加できます。
(push "a third clause" example-list)
example-listを評価すると、その値は次のようになります:
example-list
⇒ ("a third clause" "here is a clause" "another clause")
したがって、3番目の節がpushによってリストに追加される。
次に、if
節の後半部分です。この式により、キルリングが長くなりすぎるのを防ぎます。内容は以下の通りです。
(if (> (length kill-ring) kill-ring-max)
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))
このコードは、キルリングの長さが最大許容長を超えているかどうかを判定します。これはkill-ring-max(デフォルトでは120)の値です。キルリングの長さが長すぎる場合、このコードはキルリングの最後の要素をnilに設定します。具体的には、nthcdrとsetcdrという2つの関数を用いてこれを行います。
先ほど setcdr について見ました(setcdr を参照)。これは、setcar がリストの CAR
を設定するのと同様に、リストの CDR を設定します。ただし、この場合、setcdr はキルリング全体の CDR
を設定するわけではありません。nthcdr 関数を用いて、キルリングの最後から2番目の要素の
CDR を設定するようにしています。つまり、最後から2番目の要素の CDR はキルリングの最後の要素であるため、結果としてキルリングの最後の要素が設定されることになります。
nthcdr 関数は、リストの CDR
を繰り返し取得することで動作します—CDR の CDR の CDR を取得し… これを N 回繰り返し、その結果を返します。 (nthcdr を参照してください。)
したがって、本来3要素であるべき4要素のリストがある場合、最後から2番目の要素のCDRをnilに設定することで、リストを短縮できます。(もし最後の要素をnil以外の値に設定した場合――これは可能です――、リストを短縮したことにはなりません。setcdrを参照してください。)
以下の3つの式を順に評価することで、短縮を確認できます。まずtreesの値を(maple oak pine birch)に設定し、次に2番目のCDRのCDRの値をnilに設定し、最後にtreesの値を求めます:
(setq trees (list 'maple 'oak 'pine 'birch))
⇒ (maple oak pine birch)
(setcdr (nthcdr 2 trees) nil)
⇒ nil
trees
⇒ (maple oak pine)
(setcdr 式が返す値は nil
となります。これは、CDR に設定されている値だからです。)
繰り返しになりますが、kill-new では、nthcdr 関数が CDR
に、キルリングの最大許容サイズより1小さい回数分の値を代入し、setcdr
がその要素(キルリング内の残りの要素となります)の CDR を nil に設定します。これにより、キルリングが長くなりすぎるのを防ぎます。
kill-new関数の最後から2番目の式は
(setq kill-ring-yank-pointer kill-ring)
kill-ring-yank-pointerは、kill-ringに設定されるグローバル変数です。
kill-ring-yank-pointerは「pointer」と呼ばれていますが、キルリングと同様に変数です。ただし、この名称は、人間がその変数の使われ方を理解しやすくするために付けられています。
さて、関数本体にある初期の式に戻りましょう。
(if (fboundp 'menu-bar-update-yank-menu)
(menu-bar-update-yank-menu string (and replace (car kill-ring))))
if 式から始まります
この場合、式はまず menu-bar-update-yank-menu
が関数として存在するかどうかを判定し、存在すればそれを呼び出します。fboundp
関数は、判定対象のシンボルの関数定義が void でない場合に true を返します。もしそのシンボルの関数定義が void
であれば、意図的にエラーを発生させたときと同様に、エラーメッセージが表示されます(エラーメッセージの生成を参照)。
その時点の部分には、第1要素が関数 and である式が含まれている。
and 特殊形式は、引数のいずれかが nil
の値を返すまで各引数を評価し、その場合は and 式が nil を返します。ただし、どの引数も nil
の値を返さない場合は、最後の引数を評価した結果が返されます。(このような値は nil
ではないため、Emacs Lisp では真とみなされます。)言い換えれば、and
式は、すべての引数が真である場合にのみ真の値を返します。(復習を参照してください。)
この式によって、menu-bar-update-yank-menu
の第2引数が真であるか否かが決まります。
menu-bar-update-yank-menuは、メニューバーの「編集」項目にある「選択して貼り付け」メニューを利用可能にする機能の一つです。マウスを使って保存済みのさまざまなテキストを確認し、その中から一つを選択して貼り付けることができます。
kill-new
関数の最後の式は、新たにコピーした文字列を、ウィンドウシステム上で動作する異なるプログラム間でコピー&ペーストを行うための既存の機能に追加します。例えば
X Windowing システムでは、x-select-text 関数が文字列を受け取り、X
が管理するメモリに格納します。この文字列を、Xterm などの別のプログラムに貼り付けることができます。
式は次のようになります。
(if interprogram-cut-function
(funcall interprogram-cut-function string (not replace))))
interprogram-cut-function が存在する場合、Emacs は funcall
を実行し、それがさらに第1引数を関数として呼び出し、残りの引数をそこに渡します。(ちなみに、私が見る限り、この if 式は、関数の前半部分にあるものと同様の and
式に置き換えられるでしょう。)
ウィンドウシステムやその他のプログラムについてはこれ以上触れませんが、これがGNU Emacsを他のプログラムと容易かつ良好に連携させるための仕組みであるということだけ述べておきます。
キルリングにテキストを配置するためのこのコード――既存の要素に連結する場合も、新しい要素として追加する場合も――は、バッファから切り出されたテキストを復元するためのコード、すなわちヤンクコマンドへとつながります。しかし、ヤンクコマンドについて解説する前に、コンピュータでリストがどのように実装されているかを学んでおくのがよいでしょう。そうすることで、「ポインタ」という用語の使い方といった謎が明らかになります。ですがその前に、一旦C言語の話に寄り道しましょう。