[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
GNU Emacsで「キル(kill)」コマンドでバッファからテキストをカットする (切り取る)と、それらはリストに保存され、 「ヤンク(yank)」コマンドで取り出せる。
(Emacsにおける単語「kill」は、実体の値を破壊しない処理を意味するが、 その用法は、歴史的な不運な偶然による。 もっと適切な用語は、キルコマンドの動作からすれば、 「clip(切り取る)」であろう。 バッファからテキストを切り取り、復元可能なように保存するからである。 Emacsのソースのすべての「kill」を「clip」に、 すべての「killed」を「clipped」に置き換えたくなる誘惑にしばしば駆られる。)
バッファからテキストを切る取ると、それはリストに保存される。 順に切り取ったテキストは順にリストに保存されるので、 リストはつぎのようになる。
("a piece of text" "last piece") |
リストにテキストを追加するには、つぎのように関数cons
を使う。
(cons "another piece" '("a piece of text" "last piece")) |
この式を評価すると、エコー領域に3要素リストが表示される。
("another piece" "a piece of text" "last piece") |
関数car
とnthcdr
を使えば、テキストの望みの断片を取り出せる。 たとえば、つぎのコードでは、 nthcdr 1 ...
は先頭要素を除いたリストを返し、 car
はその先頭要素を返す。 つまり、もとのリストの第2要素である。
(car (nthcdr 1 '("another piece" "a piece of text" "last piece"))) => "a piece of text" |
もちろん、Emacsの実際の関数はこれより複雑である。 テキストを切り取り復元するコードは、何番目の要素を指定しようとも、 Emacsがリストの望みの要素を取り出すように書く必要がある。 さらに、リストの最後に達した場合には、何も返さないのではなく、 リストの先頭要素を返すようにすべきである。
テキストの断片を保持するリストをキルリング(kill ring)と呼ぶ。 本章では、キルリングについて説明し、まず、関数zap-to-char
の動作と その使い方を説明する。 この関数は、キルリングを操作する関数を起動する関数を使う(呼び出す)。 まずは、裾野を登ることにしよう。
本章以降では、バッファから切り取ったテキストをどのように取り出すかを説明する。 See 節 10. テキストの取り出し方。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
zap-to-char
zap-to-char
"へのコメント(無し)
関数zap-to-char
は、GNU Emacsの第18版と第19版とでは書き方が異なる。 第19版での実装のほうがいくぶん簡単であるが、動作も少々異なる。 第19版の関数を説明してから、第18版の関数を説明する。
Emacs第19版の対話的関数zap-to-char
の実装では、 カーソル(つまりポイント)の位置から指定文字を含めたテキストを取りさる。 zap-to-char
で取りさったテキストはキルリングに保存され、 C-y(yank
)とタイプするとキルリングから取り出せる。 コマンドに引数を与えると、指定文字のその回数までのテキストを取りさる。 たとえば、「Thus, if the cursor were at ...
」 の先頭にカーソルがあり、文字`s'を指定すると`Thus'を取りさる。 引数に2を与えると`cursor'の`s'までを含めて`Thus, if the curs'を 取りさる。
Emacs第18版の実装では、ポイントから指定文字の直前までを取りさる。 したがって、上の段落の例では、`s'は取りさられない。
さらに、第18版の実装では、指定文字がみつからない場合には、 バッファの最後まで行くが、第19版ではエラーになる(テキストも取りさらない)。
どれだけのテキストを取りさるかを決定するために、 どちらの版のzap-to-char
も探索関数を使う。 テキストを処理するコードでは検索は非常によく使われ、 削除コマンドと同じくらいに探索関数に注意を払うのも価値がある。
以下は、第19版での関数の実装の完全なテキストである。
(defun zap-to-char (arg char) ; version 19 implementation "Kill up to and including ARG'th occurrence of CHAR. Goes backward if ARG is negative; error if CHAR not found." (interactive "*p\ncZap to char: ") (kill-region (point) (progn (search-forward (char-to-string char) nil nil arg) (point)))) |
8.1.1 interactive
式A three part interactive expression. 8.1.2 zap-to-char
の本体A short overview. 8.1.3 関数 search-forward
How to search for a string. 8.1.4 関数 progn
The progn
function.8.1.5 zap-to-char
のまとめUsing point
andsearch-forward
.8.1.6 第18版の実装 The version 18 implementation.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
interactive
式interactive
式"へのコメント(無し)
コマンドzap-to-char
のinteractive
式はつぎのとおりである。
(interactive "*p\ncZap to char: ") |
"*p\ncZap to char: "
のように二重引用符に囲まれた引数があり、 3つの部分から成る。 最初の部分はもっとも簡単でアスタリスク`*'であり、 バッファが読み出し専用だった場合にエラーを発生させる。 つまり、読み出し専用バッファでzap-to-char
を使うと、 テキストを削除できずに、「Buffer is read-only(バッファは読み出し専用)」という エラーメッセージを受け取り、端末のベルも鳴る。
"*p\ncZap to char: "
の2番目の部分は`p'である。 この部分は改行`\n'で終わる。 `p'は、関数の第1引数には「処理した前置引数(processed prefix)」の 値を渡すことを意味する。 前置引数は、C-uに続けて数、あるいは、M-と数をタイプして渡す。 引数なしで対話的に関数を呼び出した場合には、この引数には1が渡される。
"*p\ncZap to char: "
の3番目の部分は `cZap to char:'である。 この部分では、小文字の`c'により、interactive
はプロンプトがあり、 引数は文字であることを期待する。 プロンプトは`c'のあとに続く文字列`Zap to char: 'である (コロンのうしろの空白は、見やすくするためである)。
これらにより、ユーザーに問い合わせてzap-to-char
へ渡す正しい型の引数を 準備する。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
zap-to-char
の本体zap-to-char
の本体"へのコメント(無し)
関数zap-to-char
の本体には、 カーソルの現在位置から指定文字を含んだテキストをキル(つまり、削除)する コードがある。 コードの最初の部分はつぎのとおりである。
(kill-region (point) ... |
(point)
はカーソルの現在位置である。
コードのつぎの部分は、progn
を使った式である。 progn
の本体は、search-forward
とpoint
の呼び出しから成る。
search-forward
を説明してからのほうが progn
の動作を理解しやすいので、 search-forward
を説明してからprogn
を説明する。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
search-forward
search-forward
"へのコメント(無し)
関数search-forward
は、zap-to-char
にて削除する指定文字を 探すために使われる。 この探索に成功すると、search-forward
は、探索文字列の最後の文字の 直後にポイントを置く (ここでは、探索文字列は1文字である)。 逆向きに探索した場合には、探索文字列の最初の文字の直前にポイントを置く。 さらに、search-forward
は、真としてt
を返す (したがって、ポイントの移動は「副作用」である)。
zap-to-char
では、関数search-forward
をつぎのように使う。
(search-forward (char-to-string char) nil nil arg) |
関数search-forward
は4つの引数を取る。
zap-to-char
に渡される引数は単独の文字である。 コンピュータの構成方法のために、Lispインタープリタは単独の文字と 文字列を区別する。 コンピュータ内部では、単独の文字は、1文字の文字列とは異なる電気的な形式となる (単独の文字は、コンピュータ内部では、しばしば、ちょうど1バイトで記録される。 一方、文字列は長かったり短かったりするので、コンピュータはそれに対応できる 必要がある)。 関数search-forward
は文字列を探すので、 関数zap-to-char
が引数として受け取った文字は、 コンピュータ内部では、ある形式から別の形式に変換する必要がある。 さもないと、関数search-forward
は失敗する。 関数char-to-string
を使って、この変換を行う。
nil
である。
nil
を返す。 第3引数にnil
を指定すると、探索に失敗すると関数はエラーを通知する。
search-forward
の第4引数は、繰り返し回数、 つまり、文字列の出現を何回探すかを指定する。 この引数は省略でき、繰り返し回数を指定しないと1である。 この引数が負の場合には、逆向きに探索する。search-forward
式の概略はつぎのとおりである。
(search-forward "探索文字列" 探索範囲 探索失敗時の動作 繰り返し回数) |
では、progn
を説明しよう。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
progn
progn
"へのコメント(無し)
progn
は、個々の引数を順番に評価して最後のものの値を返す関数である。 最後以外の式は、それらの副作用のためだけに評価される。 それらが返す値は捨てられる。
progn
式の雛型はとても簡単である。
(progn 本体...) |
zap-to-char
では、progn
式は2つのことを行う。 正しい位置にポイントを置くことと、 kill-region
がどこまでを削除するかがわかるようにポイントの位置を 返すことである。
progn
の第1引数はsearch-forward
である。 search-forward
は、文字列を探しあてると 検索文字列の最後の文字の直後にポイントを置く (ここでは、検索文字列は1文字長である)。 逆向きに検索した場合は、search-forward
は検索文字列の最初の文字の 直前にポイントを置く。 ポイントの移動は副作用である。
progn
の2番目で最後の引数は、式(point)
である。 この式はポイントの値を返し、 ここでは、search-forward
が移動した位置である。 progn
式がこの値を返し、kill-region
の第2引数として kill-region
に渡される。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
zap-to-char
のまとめzap-to-char
のまとめ"へのコメント(無し)
search-forward
とprogn
の動作がわかったので、 関数zap-to-char
全体としての動作を理解しよう。
kill-region
の第1引数は、コマンドzap-to-char
を与えたときの カーソルの位置、つまり、そのときのポイントの値である。 progn
の中で、探索関数が削除する文字の直後にポイントを移動し、 point
がその位置の値を返す。 関数kill-region
は、ポイントの2つの値を組み合わせて、 最初のものをリージョンの始まり、 あとのものをリージョンの終わりと解釈してリージョンを削除する。
式search-forward
とpoint
を2つ続けて余分な2つの引数として書くと、 2つの引数を取るコマンドkill-region
は失敗するので、 関数progn
が必要なのである。 progn
式は、kill-region
に対しては1つの引数となり、 kill-region
が第2引数に必要とする1つの値を返す。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
zap-to-char
の第18版での実装は第19版での実装と少々異なる。 指定文字の直前までを削除する。 また、指定文字がみつからないと、バッファの最後までを削除する。
この違いは、コマンドkill-region
の第2引数にある。 第19版の実装ではつぎのとおりであった。
(progn (search-forward (char-to-string char) nil nil arg) (point)) |
第18版の実装はつぎのとおりである。
(if (search-forward (char-to-string char) nil t arg) (progn (goto-char (if (> arg 0) (1- (point)) (1+ (point)))) (point)) (if (> arg 0) (point-max) (point-min))) |
このコードは相当複雑に見えるが、部分ごとに見れば理解できる。
最初の部分はつぎのとおりである。
(if (search-forward (char-to-string char) nil t arg) |
これはつぎのif
式に当てはめて考えられる。
(if 指定文字がみつかったなら、そこへポイントを移動する ポイントを修正し、その位置を返す さもなければ、バッファの最後に移動し、その位置を返す) |
if
式の評価は、kill-region
の第2引数になる。 第1引数はポイントなので、この処理により、kill-region
は ポイントから指定文字までのテキストを削除できるのである。
search-forward
が副作用としてポイントを移動することについては、 すでに説明した。 search-forward
が返す値は、探索に成功すればt
を、 さもなければ、search-forward
の第3引数の値に依存して nil
かエラーメッセージを返す。 ここでは、第3引数にt
を指定しているので、 探索に失敗すると関数はnil
を返す。 これから見るように、探索でnil
が返ったときの場合を処理する コードを書くのは簡単である。
zap-to-char
の第18版での実装では、 if
の判定条件として探索式を評価するので、探索が行われる。 探索に成功すると、Emacsはif
式の真の場合の動作を評価する。 一方、探索に失敗すると、Emacsはif
式の偽の場合の動作を評価する。
if
式では、探索に成功するとprogn
式が実行される。
すでに説明したように、関数progn
は、個々の引数を順番に評価し、 最後のものの値を返す。 それ以外の式は、それらの副作用のためだけに評価される。 それらの値は捨てられる。
zap-to-char
のこの版では、progn
式は、 関数search-forward
が文字を探しあてたときに実行される。 progn
式では2つのことを行う。 ポイントの位置を正すことと、kill-region
がどこまで削除するかを わかるようにポイントの位置を返すことである。
progn
のコードがある理由は、search-forward
が文字列を 探しあてたあとでは、探索文字列の最後の文字の直後にポイントを置くからである (ここでは、探索文字列は1文字長である)。 逆向きの探索では、search-forward
は探索文字列の最初の文字の 直前にポイントを置く。
しかし、関数zap-to-char
のこの版では、 指定文字を削除しないことになっている。 たとえば、zap-to-char
で`z'までのテキストを削除すると、 この版では`z'は削除しない。 そのため、指定文字が削除されないようにポイントを移動する必要がある。
progn
式の本体The body of the progn
expression
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
progn
式の本体progn
式の本体"へのコメント(無し)
progn
の本体には2つの式がある。 これを各部分がわかりやすいように輪郭を整えて注釈を加えるとつぎのようになる。
(progn (goto-char ; |
progn
式はつぎのことを行う。 (arg
が正で)末尾へ向けた探索の場合には、Emacsは探しあてた文字列の直後に ポイントを置く。 ポイントを1文字分戻せば、その文字を範囲から外せる。 つまり、progn
中の式は(goto-char (1- (point)))
と読める。 これは、ポイントを1文字分戻す (関数1-
は、1+
が引数に1を加えるように、引数から1を減じる)。 一方、zap-to-char
の引数が負の場合、 探索は先頭へ向けて行われる。 if
がこの場合を検出すると、式は(goto-char (1+ (point)))
と読める (関数1+
は引数に1を加える)。
progn
の2番目で最後の引数は、式(point)
である。 この式は、progn
の第1引数で移動したポイントの位置の値を返す。 この値はif
式の値として返され、kill-region
の第2引数として kill-region
に渡される。
まとめると、関数はつぎのように働く。 kill-region
の第1引数は、コマンドzap-to-char
を起動したときの カーソルの位置、つまり、そのときのポイントの値である。 続いて、探索関数は探索に成功するとポイントを移動する。 progn
式は、指定文字を削除しないようにポイントを移動し、 移動後のポイントの値を返す。 続いて、関数kill-region
がリージョンを削除する。
最後に、if
式の偽の場合の動作は、指定文字がみつからなかった場合を扱う。 関数zap-to-char
の引数が正で(あるいは指定してなくて) 指定文字がみつからない場合、 ポイントの現在位置からバッファの参照可能なリージョンの最後 (ナロイングしていなければバッファの最後)までのテキストすべてを削除する。 arg
が負で指定文字がみつからない場合、 参照可能なリージョンの先頭から削除する。 これを扱うコードは、つぎの単純なif
節である。
(if (> arg 0) (point-max) (point-min)) |
つまり、arg
が正の数ならばpoint-max
の値を、 さもなければpoint-min
の値を返す。
復習のために、kill-region
を起動するコードを注釈付きで示す。
(kill-region
(point) ; リージョンの始め
(if (search-forward
(char-to-string char) ; 探索文字列
nil ; 探索範囲:指定しない
t ; 失敗したら
|
以上でわかるように、第19版の実装は、第18版の実装より少々小さく、 より簡単である。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
kill-region
kill-region
"へのコメント(無し)
関数zap-to-char
は関数kill-region
を用いる。 この関数はとても簡単であり、説明文字列の一部を省略するとつぎのとおりである。
(defun kill-region (beg end) "Kill between point and mark. The text is deleted but saved in the kill ring." (interactive "*r") (copy-region-as-kill beg end) (delete-region beg end)) |
重要な点は、つぎの節で説明する関数delete-region
と copy-region-as-kill
を使っていることである。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
delete-region
:Cへ回り道delete-region
:Cへ回り道"へのコメント(無し)
コマンドzap-to-char
は関数kill-region
を使い、 それはさらにcopy-region-as-kill
とdelete-region
と いう2つの関数を使っている。 関数copy-region-as-kill
はつぎの節で説明するが、 リージョンのコピーをキルリングに保存して取り出せるようにする (See 節 8.5 copy-region-as-kill
)。
関数delete-region
はリージョンの内容を削除するが、 その内容を戻すことはできない。
これまでに説明したコードと異なり、delete-region
はEmacs Lispで 書かかれていない。 Cで書かかれており、GNU Emacsシステムの基本操作関数の1つである。 とても簡単なので、Lispから回り道して、ここで説明することにする。
Emacsのほとんどの基本操作関数と同様に、delete-region
は、 Cのマクロ、コードの雛型となるマクロを用いて書かれている。 マクロの最初の部分はつぎのとおりである。
DEFUN ("delete-region", Fdelete_region, Sdelete_region, 2, 2, "r", "Delete the text between point and mark.\n\ When called from a program, expects two arguments,\n\ character numbers specifying the stretch to be deleted.") |
マクロの書き換え処理の詳細を説明するつもりはないが、 このマクロは単語DEFUN
で始まることを指摘しておく。 Lispのdefun
と同じ目的を果たすコードなので、単語DEFUN
が選ばれた。 単語DEFUN
に続けて括弧の中には7つの部分がある。
delete-region
である。
Fdelete_region
である。 慣習的に`F'で始める。 Cでは名前としてハイフンを使えないので、かわりに下線を使う。
interactive
の引数と同じであり、 文字に続けてプロンプトがあってもよい。 ここでは、文字は"r"
であり、関数への2つの引数はバッファの リージョンの先頭と最後であることを示す。 この例では、プロンプトはない。
続いて、オブジェクトの種類を指定する文とともに仮引数があり、 さらに、マクロの「本体」とも呼ぶべきものが続く。 delete-region
の本体はつぎの3行から成る。
validate_region (&b, &e); del_range (XINT (b), XINT (e)); return Qnil; |
最初の関数validate_region
では、 リージョンの始めと終わりとして渡された値が 正しい型で正しい範囲にあるかどうかを調べる。 2番目の関数del_range
で、実際にテキストを削除する。 この関数の処理が正常に終了すると、 これを表すために3番目の行でQnil
を返す。
del_range
は複雑な関数なので、その中身は調べないことにする。 バッファを更新し、その他のことも行う。 しかし、del_range
に渡される2つの引数を調べることは価値がある。 これらは、XINT (b)
とXINT (e)
である。 言語Cに関する限り、b
とe
は、削除すべきバッファの先頭と最後を 表す2つの32ビット整数である。 しかし、Emacs Lispの他の数と同様に、32ビットの内の24ビットのみを使っている。 残りの8ビットは、情報の型の記録やその他の目的に利用される (ある種のマシンでは、6ビットのみを使う)。 ここでは、これらの数がバッファの位置を表すことを示すために8ビットを使う。 数の中のビットをこのように使うことをタグ(tag)と呼ぶ。 各32ビット整数で8ビットタグを利用すると、タグを利用しない場合に比べて 高速に動作するようにEmacsを書くことができる。 一方で、数は24ビットの範囲に制限されるため、Emacsのバッファは 約8メガバイトに制限される (コンパイルするまえに、ファイル`emacs/src/config.h'に VALBITS
とGCTYPEBITS
の定義を追加すれば、 バッファの最大サイズを増加できる。 Emacsディストリビューションに含まれるファイル `emacs/etc/FAQ'の注意書きを参照してほしい)。
`XINT'は、32ビット長のLispオブジェクトから24ビット整数を取り出す Cのマクロである。 他の目的に用いる8ビットは捨てられる。 したがって、del_range (XINT (b), XINT (e))
は、 b
で始まりe
で終わるリージョンを削除する。
Lispを書く人の観点からは、Emacsは非常に簡単であるが、 その背後には、すべてが正しく働くように、とても複雑なことが隠れている。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
defvar
による変数の初期化defvar
による変数の初期化"へのコメント(無し)
関数delete-region
と異なり、関数copy-region-as-kill
は Emacs Lispで書かれている。 バッファのリージョンのコピーを変数kill-ring
に保存する。 本節では、この変数の作成と初期化の方法を説明する。
(kill-ring
はふさわしくない名称であることを再度指摘しておく。 バッファから切り取ったテキストは戻すことができる。 キルリングは死体のリングではなく、復活できるテキストのリングである。)
Emacs Lispでは、kill-ring
のような変数は、 スペシャルフォームdefvar
を用いて作成し初期化する。 この名称は「define variable(変数を定義する)」からきている。
スペシャルフォームdefvar
は、変数の値を設定するという意味では setq
に似ている。 しかし、setq
とは2つの点で異なる。 まず、値を持っていない変数にのみ値を設定することである。 変数にすでに値があれば、defvar
は既存の値を書き換えない。 第二に、defvar
は説明文字列を有することである。
任意の変数の現在の値は、関数describe-variable
を使って調べることができ、 普通、C-h vとタイプすれば起動できる。 C-h vとタイプして問い合わせにkill-ring
(に続けてRET)と タイプすれば、今のキルリングに何が入っているかがわかるが、とても多量であろう。 一方、本書を読む以外の操作をEmacsで行っていなければ、キルリングには 何もないであろう。 バッファ`*Help*'の最後には、つぎのようなkill-ring
の 説明文字列があるはずである。
Documentation: List of killed text sequences. |
キルリングはつぎのようにdefvar
で定義してある。
(defvar kill-ring nil "List of killed text sequences.") |
この変数定義では、変数に初期値nil
を設定している。 何も保存していないときには、コマンドyank
で何も戻ってほしくないので、 この値には意味がある。 説明文字列は、defun
の説明文字列と同じである。 apropos
のようなある種のコマンドは説明文の最初の1行しか表示しないので、 defun
の説明文字列と同様に説明文の最初の行は完全な文にしておく。 また、C-h v(describe-variable
)で表示したときに 変にならないように、続く行は字下げしない。
ほとんどの変数はEmacsの内部用であるが、 コマンドedit-options
で設定するオプションであるものもある (これらの設定は、編集作業中でのみ有効である 恒久的に設定するには、ファイル`.emacs'を書く。 See 節 16. 個人用ファイル`.emacs')。
Emacsでは、設定可能な変数とそれ以外のものとは、 説明文字列の先頭のアスタリスク`*'で区別する。
たとえば、
(defvar line-number-mode nil "*Non-nil means display line number in mode line.") |
line-number-mode
の値は、コマンドedit-options
を使って 変更できることを意味する。
もちろん、line-number-mode
の値は、 つぎのようにsetq
式の内側に書いて評価しても変更できる。
(setq line-number-mode t) |
See 節 1.9.2 setq
の使い方。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
copy-region-as-kill
copy-region-as-kill
"へのコメント(無し)
関数copy-region-as-kill
は、バッファからリージョンのテキストをコピーし 変数kill-ring
に保存する。
コマンドkill-region
の直後にcopy-region-as-kill
を呼ぶと、 Emacsは、新たにコピーしたテキストを直前にコピーしたテキストに追加する。 つまり、そのテキストを復元すると、そのテキストと直前のテキストを まとめて得ることになる。 一方で、copy-region-as-kill
のまえに別のコマンドを実行した場合には、 この関数はテキストを独立した項目としてキルリングに保存する。
わかりやすいように整形して注釈を付加した第18版の copy-region-as-kill
の完全なテキストをつぎに示す。
(defun copy-region-as-kill (beg end) "Save the region as if killed, but don't kill it." (interactive "r") (if (eq last-command 'kill-region) ;; 真の場合の動作:新たにコピーしたテキストを ;; 直前にコピーしたテキストに追加する。 (kill-append (buffer-substring beg end) (< end beg)) ;; 偽の場合の動作:新たにコピーしたテキストを ;; 独立したものとしてキルリングに追加し、 ;; 必要ならばキルリングを短くする。 (setq kill-ring (cons (buffer-substring beg end) kill-ring)) (if (> (length kill-ring) kill-ring-max) (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))) (setq this-command 'kill-region) (setq kill-ring-yank-pointer kill-ring)) |
この関数も部分部分に分解できる。
(defun copy-region-as-kill (引数リスト) "説明文..." (interactive "r") 本体...) |
引数はbeg
とend
であり、 "r"
が指定された対話的関数であるので、 2つの引数はリージョンの始まりと終わりを参照する必要がある。 本書を始めから読んでいる読者ならば、関数のこの部分を理解するのは簡単であろう。
単語「kill」が通常の意味と異なることを思い出せば、 説明文の内容に混乱することもないであろう。
関数の本体はif
節で始まる。 この節で、2つの異なる場合を分ける。 コマンドkill-region
の直後に、このコマンドが実行されたかどうかを区別する。 そうならば、直前にコピーしたテキストに新たなリージョンを追加する。 さもなければ、直前の断片とは独立したテキストの断片として キルリングの先頭に挿入する。
関数の最後の2行は、2つのsetq
式である。 1つは、変数this-command
にkill-region
を設定し、 もう1つでは、変数kill-ring-yank-pointer
がキルリングを指すようにする。
copy-region-as-kill
の本体は詳しく説明する価値がある。
8.5.1 copy-region-as-kill
の本体The body of copy-region-as-kill
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
copy-region-as-kill
の本体copy-region-as-kill
の本体"へのコメント(無し)
関数copy-region-as-kill
は、 連続してキルしたテキストは1つの断片にまとめるように書かれている。 キルリングからテキストを取り出すと、1つの断片として得ることになる。 さらに、カーソルの現在位置から終わりに向けたキルでは 直前にコピーしたテキストの末尾に追加し、 先頭向けのコピーでは直前にコピーしたテキストの先頭に追加する。 このようにして、テキスト内の語順は正しく保たれる。
この関数では、現在と直前のEmacsコマンドを記録する2つの変数を用いる。 this-command
とlast-command
である。
通常、関数が実行されると、Emacsは、this-command
の値に 実行する関数(ここでは、copy-region-as-kill
)を設定する。 同時に、this-command
の直前の値をlast-command
に設定する。 しかし、コマンドcopy-region-as-kill
では違っていて、 this-command
の値にはkill-region
を設定する。 これは、copy-region-as-kill
を呼んだ関数の名前である。
関数copy-region-as-kill
の本体の始めの部分では、 last-command
の値がkill-region
かどうかを if
式で調べている。 そうならば、if
式の真の場合の動作が評価される。 そこでは、関数kill-append
を使って、 この呼び出しでコピーするテキストをキルリングの先頭要素(CAR)に連結する。 一方、last-command
の値がkill-region
でなければ、 関数copy-region-as-kill
は新たな要素をキルリングに追加する。
まだ説明していない関数eq
を用いているが、 if
式はつぎのように読める。
(if (eq last-command 'kill-region) ;; 真の場合の動作 (kill-append (buffer-substring beg end) (< end beg)) |
関数eq
は、第1引数が第2引数と同じLispオブジェクトかどうかを検査する。 関数eq
は、等しいかどうかを検査する関数equal
に似ているが、 2つの表現がコンピュータ内部で実際に同じオブジェクトかどうかを検査する。 equal
は、2つの式の構造と内容が同じかどうかを検査する。
関数 kill-append
The kill-append
functioncopy-region-as-kill
の偽の場合の動作The else-part of copy-region-as-kill
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
kill-append
kill-append
"へのコメント(無し)
関数kill-append
はつぎのとおりである。
(defun kill-append (string before-p) (setcar kill-ring (if before-p (concat string (car kill-ring)) (concat (car kill-ring) string)))) |
この関数も部分に分けて説明できる。 関数setcar
は、キルリングのCARに新たなテキストを連結するために concat
を使っている。 テキストを先頭に挿入するのか末尾に追加するのかは、 if
式の結果に依存する。
(if before-p ; 判定条件 (concat string (car kill-ring)) ; 真の場合の動作 (concat (car kill-ring) string)) ; 偽の場合の動作 |
直前のコマンドでキルしたリージョンの直前のリージョンをキルしたときには、 直前のキルで保存したテキストの先頭に挿入するべきである。 逆に、直前にキルしたリージョンの直後に続くテキストをキルした場合には、 直前のテキストの末尾に追加するべきである。 if
式では、新たに保存するテキストを直前に保存したテキストの先頭に 挿入するか末尾に追加するかを述語before-p
を用いて決めている。
シンボルbefore-p
は、kill-append
の引数の名前の1つである。 関数kill-append
が評価されると、実引数を評価した結果の値に束縛される。 ここでは、式(< end beg)
である。 この式では、このコマンドでキルしたテキストが直前のコマンドでキルしたテキストの まえにあるか、うしろにあるかを直接には決定しない。 変数end
の値が変数beg
の値より小さいかどうかのみを決定する。 そうであった場合には、バッファの先頭に向けてである可能性が高い。 すると、述語式(< end beg)
の評価結果は真となり、 テキストは直前のテキストの先頭に挿入される。 一方、変数end
の値が変数beg
の値より大きければ、 テキストは直前のテキストの末尾に追加される。
新たに保存するテキストを先頭に挿入するときには、 既存のテキストのまえに新たなテキストを連結する。
(concat string (car kill-ring)) |
テキストを追加する場合には、既存のテキストのうしろに連結する。
(concat (car kill-ring) string)) |
この動作を理解するには、まず、関数concat
を復習しておく必要がある。 関数concat
は、2つの文字列を繋げる。 結果も文字列である。 たとえば、
(concat "abc" "def") => "abcdef" (concat "new " (car '("first element" "second element"))) => "new first element" (concat (car '("first element" "second element")) " modified") => "first element modified" |
これでkill-append
の動作を理解でき、キルリングの内容を変更することが わかる。 キルリングはリストであり、各要素は保存したテキストである。 関数setcar
は、実際には、このリストの先頭要素を変更する。 それにはconcat
を用いて、 キルリングのもとの先頭要素(キルリングのCAR)を もとの保存されたテキストと新たに保存したテキストを連結したもので置き換える。 新たに保存するテキストは、バッファからそれを切り取った位置に依存して、 もとのテキストの先頭か末尾に追加される。 連結したものがキルリングの新たな先頭要素になる。
たとえば、筆者のキルリングの始めの部分はつぎのようになっている。
("concatenating together" "saved text" "element" ... |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
copy-region-as-kill
の偽の場合の動作copy-region-as-kill
の偽の場合の動作"へのコメント(無し)
さて、copy-region-as-kill
の説明に戻ろう。
直前のコマンドがkill-region
でない場合には、 kill-append
を呼ぶかわりに、つぎに示した偽の場合の動作を呼び出す。
(if 判定条件 真の場合の動作 ;; 偽の場合の動作 (setq kill-ring (cons (buffer-substring beg end) kill-ring)) (if (> (length kill-ring) kill-ring-max) (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))) |
偽の場合の動作のsetq
の行では、キルした文字列をもとのキルリングに 追加した結果をキルリングの新たな値に設定する。
つぎの例からこの動作を理解できる。
(setq example-list '("here is a clause" "another clause")) |
C-x C-eでこの式を評価してから、example-list
を評価すると つぎのような結果になる。
example-list => ("here is a clause" "another clause") |
このリストに新たな要素を追加するには、つぎの式を評価すればよい。
(setq example-list (cons "a third clause" example-list)) |
example-list
を評価すると、その値はつぎのとおりである。
example-list => ("a third clause" "here is a clause" "another clause") |
つまり、cons
で「the third clause」をリストに追加したのである。
以上は、関数内でsetq
とcons
とが行うことと同じであるが、 buffer-substring
を使ってテキストのリージョンのコピーを作り出して cons
に渡している。 その行を改めてつぎに記しておく。
(setq kill-ring (cons (buffer-substring beg end) kill-ring)) |
copy-region-as-kill
の偽の場合の動作のつぎの部分もif
節である。 このif
節は、キルリングが長くなり過ぎるのを防ぐ。 つぎのようになっている。
(if (> (length kill-ring) kill-ring-max) (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))) |
このコードでは、キルリングの長さが許された最大長よりも 大きいかどうかを検査する。 最大長はkill-ring-max
の値である(デフォルトは30)。 キルリングの長さが長すぎる場合には、キルリングの最後の要素をnil
に 設定する。 これには、2つの関数nthcdr
とsetcdr
を使う。
setcdr
についてはすでに説明した(see 節 7.5 setcdr
)。 setcar
がリストのCARを設定するように、 setcdr
はリストのCDRを設定する。 しかし、ここではsetcdr
はキルリング全体のcdr
を設定するのではない。 関数nthcdr
が使われていて、キルリングの最後の要素の直前のcdr
を 設定するのである。 つまり、最後の要素の直前のcdr
はキルリングの最後の要素であるから、 キルリングの最後の要素を設定することになる。
関数nthcdr
は、リストのCDRを繰り返し取るように動作する。 つまり、CDRのCDRのCDRの...のCDRを取る。 N回繰り返した結果を返す。
したがって、たとえば、4要素リストを3要素リストにするには、 最後の要素の直前のCDRをnil
にしてリストを短くすればよい。
つぎの3つの式を順に評価すれば、これを理解できるであろう。 まず、trees
の値として(maple oak pine birch)
を設定する。 つぎに、2つめのCDRのCDRをnil
にしてから、 trees
の値を見てみる。
(setq trees '(maple oak pine birch)) => (maple oak pine birch) (setcdr (nthcdr 2 trees) nil) => nil trees => (maple oak pine) |
(setcdr
式が返す値は、CDRをnil
に設定したので、 nil
である。)
copy-region-as-kill
では、関数nthcdr
は、 キルリングの許された最大長引く1回だけCDRを取り、 その要素(キルリングの残りの要素)のCDRにnil
を設定する。 これにより、キルリングが長くなり過ぎるのを防ぐ。
関数copy-region-as-kill
の最後の行の直前はつぎのとおりである。
|
この行は、if
式の内側でも外側でもなく、 copy-region-as-kill
が呼ばれるごとに評価される。 ここが、this-command
にkill-region
を設定する場所である。 すでに説明したように、つぎにコマンドが与えられると、 変数last-command
にはこの値が設定される。
関数copy-region-as-kill
の最後の行は、つぎのとおりである。
(setq kill-ring-yank-pointer kill-ring) |
kill-ring-yank-pointer
はグローバル変数であり、 kill-ring
と同じに設定される。
kill-ring-yank-pointer
はポインタと名前が付いているが、 キルリングと同じ変数である。 しかし、変数の使われ方が人間にわかりやすいように、この名称が選ばれた。 この変数は、yank
やyank-pop
などの関数で使われる (see 節 10. テキストの取り出し方)。
これで、バッファから切り取ったテキストを復元する ヤンクコマンドのコードの説明に進める。 しかし、ヤンクコマンドを説明するまえに、コンピュータでのリストの実装方法を 学んでおくのがよいであろう。 そうすれば、「ポインタ」などの用語の不可解さが明らかになる。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
これまでに説明した関数のいくつかを以下にまとめておく。
car
cdr
car
はリストの先頭要素を返す。 cdr
はリストの2番目以降の要素を返す。
たとえば、
(car '(1 2 3 4 5 6 7)) => 1 (cdr '(1 2 3 4 5 6 7)) => (2 3 4 5 6 7) |
cons
cons
は、第1引数を第2引数のまえに置いたリストを作る。
たとえば、
(cons 1 '(2 3 4)) => (1 2 3 4) |
nthcdr
cdr
を「n」回適用した結果を返す。
たとえば、
(nthcdr 3 '(1 2 3 4 5 6 7)) => (4 5 6 7) |
setcar
setcdr
setcar
はリストの先頭要素を変更する。 setcdr
はリストの2番目以降の要素を変更する。
たとえば、
(setq triple '(1 2 3)) (setcar triple '37) triple => (37 2 3) (setcdr triple '("foo" "bar")) triple => (37 "foo" "bar") |
progn
たとえば、
(progn 1 2 3 4) => 4 |
save-restriction
search-forward
4つの引数を取る。
nil
を返すかエラーメッセージを返すか指定する。 省略できる。
kill-region
delete-region
copy-region-as-kill
kill-region
は、ポイントとマークのあいだのテキストを バッファから切り取り、ヤンクで復元できるように、 キルリングにそのテキストを保存する。
delete-region
は、ポイントとマークのあいだのテキストを バッファから取りさり、破棄する。 復元することはできない。
copy-region-as-kill
はポイントとマークのあいだのテキストを キルリングにコピーし、キルリングからそのテキストを復元できるようにする。 この関数は、バッファからテキストを取りさったりしない。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
search-forward
を使わないこと。 さもないと、Emacsの既存のsearch-forward
を書き換えてしまう。 かわりに、test-search
のような名前を使う)。
copy-region-as-kill
はthis-command
を設定しない。 この変更の帰結はなんであろう? その意図は何であろう?[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |