[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
GNU Emacs で `kill' コマンドを使ってバッファからテキストをカットないしは 切り取った (clip した) 場合、常にその情報はリストとして保持されており、 それをヤンク (yank) コマンドで取り出すことが出来る。
(Emacs で特に何らかの実体を破壊しないプロセスに対して `kill' とい う言葉を使うのは、不幸な歴史的出来事である。これには `clip' という単語の 方がずっとふさわしい。これこそ kill コマンドがやっていることに他ならない からである。このコマンドはバッファからテキストを `clip' し、それをまた取 り出すことが出来るような置場所に移しているのだ。私はしばしば Emacs のソー スの中に出てくるあらゆる `kill' を `clip' に、あらゆる `killed' を `clipped' に置き換えたい衝動にかられる。)
リストへのテキストの保存 8.1 zap-to-char
テキストをある文字まで削除する 8.2 kill-region
リージョンの削除 8.3 delete-region
: ちょっと脱線して C の話を脱線: C で書かれた関数について 8.4 defvar
を用いた変数の初期化変数に初期値を与える 8.5 copy-region-as-kill
テキストの複写の定義 8.6 復習 cons と search-forward の復習 8.7 検索についての練習問題
テキストがバッファから切り取られた場合、そのテキストはあるリストに保存され る。次にまたテキストを切り取りると、そのテキストもまたこのリストの中に続けて 保存される。従ってリストは次のようになる。
("a piece of text" "last piece") |
関数 cons
を使えば、以下のように、このリストにテキストを加えるこ
とが出来る。
(cons "another piece" '("a piece of text" "last piece")) |
このS式を評価すると、次の三つの要素を持つリストがエコー領域に表示される はずだ。
("another piece" "a piece of text" "last piece") |
関数 car
や nthcdr
を使うと、この中からどの部分でも取り出
すことが出来る。例えば次に挙げるコードでは nthcdr 1 ...
が最
初の要素を除いたリストを返し、car
がその残りのリストの中の最初の
要素---元々のリストでは二番目の要素---を返す。
(car (nthcdr 1 '("another piece" "a piece of text" "last piece"))) => "a piece of text" |
勿論、実際に Emacs で使われている関数はこれよりももっと複雑である。テキ ストをカットしたり取り出したりするためのコードは Emacs があなたが取り出 したいテキストがどれか分るように書かれていなければならない。それが一番目 にあろうと二番目にあろうと三番目にあろうと、あるいはもっと他の場所にあっ てもである。また、もしリストの最後まで来た場合、そこで終わりとはせずに再 度最初の要素に戻らなくてはなるまい。
テキストの部分を保存しているリストは kill リング (kill ring)
と呼ばれる。この章では少しずつ kill リングについて話していくことにする。
そのために、まず zap-to-char
関数がどのように働くかを見ることで、
kill リングの使い方を見ていきたいと思う。山の頂上を目指す前にまず手頃な丘
に登ってみるわけである。
実際にテキストがどのようにバッファから切り取られ、あるいは取り出されるの かについては、次の章で説明する。Yanking Text Back, 参照。
zap-to-char
zap-to-char
関数は、GNU Emacs の version 18 と version 19 とでは
異なる書き方がされている。version 19 での実装の方が単純で、動作も少々異
なる。まずは version 19 用に書かれた方を見て、その後に version 18 の方を
見ることにしよう。
インタラクティブ関数 zap-to-char
の Emacs version 19 用の実装では、
カーソルの位置 (つまりポイントの位置) から次に指定した文字が現れるまでの、
その文字も含めた領域のテキストを取り去る。zap-to-char
によって取
り除かれたテキストは、kill リングに置かれる。これは C-y
(yank
) とタイプすることで取り出すことが出来る。もし、このコマンド
が引数と共に呼び出されたなら、その回数だけ指定された文字が出てくるまでの
間のテキストを取り去る。従って、もしカーソルが次の文の先頭にあり、特定の
文字が `s' だったとすると、`Thus' が取り去られることになる。
Thus, if the cursor were at the beginning of this sentence and the character were `s', `Thus' would be removed.
(訳註:日本語の文章では zap-to-char
は使えないので、止むなく英文
のままにした。)
またもし引数が2であれば、`Thus, if the curs' が取り去られる。ここで も最後の `s' が含まれていることに注目しよう。
Emacs version 18 での実装では、指定した文字までのその文字を含まな い領域を取り去るようになっている。従って、先のパラグラフでの例では、 `s' は取り除かれない。
更に Emacs の version 18 での実装では、もし指定した文字が現れなかった場 合にはバッファの最後までカーソルが移動した。しかし、version 19 の方では 単にエラーを返すだけである。(従って、テキストを取り除いたりもしない。)
どれだけのテキストを取り去るかを決定するために、どちらのバージョンの場合 でも検索関数を使っている。検索はもっぱらテキストを扱うコードの中で使われ る。そして、削除コマンドだけでなく検索関数にも注目するのは意味のあること である。
以下が version 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
式三つの部分からなるインタラクティブ式 8.1.2 zap-to-char
の本体部分ちょっと復習 8.1.3 関数 search-forward
文字列検索の仕方 8.1.4 関数 progn
8.1.5 zap-to-char
についての総括point
とsearch-forward
を使う8.1.6 Version 18 での実装 version 18 での実装
interactive
式
zap-to-char
コマンド内のインタラクティブ式は、次の通りである。
(interactive "*p\ncZap to char: ") |
引用符の中の部分は、"*p\ncZap to char: "
であり、三つのことを指
定している。一番目の部分は単なるアスタリスク `*' で、バッファが
read-only だったら警告のためにエラーを出すためのものである。つまり、
read-only なバッファで zap-to-char
を使おうとしてもテキストを取り
除くことは出来ず、代わりに "buffer is read-only" というメッセージを受
け取ることになることを意味する。端末によってはビープ音もなる。
*p\ncZap to char: "
の二番目の部分は `p' である。この部分
は改行 `\n' で終っている。`p' はこの関数の最初の引数として、処
理された前置引数の値が渡されることを意味する。前置引数は C-u に続
いてある数を、もしくは M- に続いてある数をタイプすることで渡される。
もし、この関数が前置引数なしで呼び出された場合、1が引数として渡される。
*p\ncZap to char: "
の三番目の部分は、`cZap to char: '
である。この部分の最初の小文字の `c' は interactive
がプロン
プトを出して、文字の入力を要求することを指示するものである。プロンプトは
`c' の後に続くもので、この場合は文字列 `Zap to char: '
である。(コロンの後の空白は、単に見栄えをよくするためのものである。)
結局これがすることは、正しい型の zap-to-char
の引数を用意し、ユー
ザーに対してプロンプトを出すということである。
zap-to-char
の本体部分
zap-to-char
関数の本体部分は、現在のカーソルの位置から指定した文
字までの間の領域のテキストを削除するコードを含んでいる。その最初の部分は
次の通りである。
(kill-region (point) ... |
(point)
は現在のカーソルの位置である。
この後には、progn
を使った式が続いている。progn
の本体部分
では、search-forward
と point
を呼び出している。
まず search-forward
を学んでからの方が progn
の働きを理解
しやすいので、先にこちらを見て、次に progn
について述べることにし
よう。
search-forward
search-forward
関数は、zap-to-char
の中で、削除する範囲の
端を定める文字 (以下 zapped-to-character と書く) の位置を定めるのに使わ
れている。もし検索が成功すると、search-forward
は目的の文字列の最
後の文字のすぐ後にポイントを移動する。(今の場合、目的の文字列はちょうど
一文字である。) もし検索が後方に向かうものであれば、
search-forward
は目的の文字列の最初の一文字のすぐ前にポイントを移
動する。また、search-forward
自身は値として t
を返す。(従っ
て、ポイントの移動は副作用である。)
zap-to-char
の中で search-forward
は次のように使われている。
(search-forward (char-to-string char) nil nil arg) |
関数 search-forward
は四つの引数を取る。
今回の場合は、たまたま zap-to-char
に渡される引数は単独の文字であ
る。計算機の設計上、Lisp インタプリタは単独の文字を一文字からなる文字列
とは区別して扱う。計算機の中では単独の文字は一文字からなる文字列とは別の
電気的なフォーマットを持っているのである。(単独の文字はたいてい計算機の
中で1バイトで記録されている。(訳註:勿論日本語とかでは事情は別である。)
しかし、文字列の方は長いものもあれば短いものもあるので、計算機の方でそれ
に備えなければならないのである。) search-forward
は文字列を検索す
るものなので、zap-to-char
関数に引数として渡された文字を計算機の
内部で別のフォーマットに変換しないといけない。でないと、
search-forward
は検索に失敗してしまう。この変換をするために、
char-to-string
という関数が使われている。
nil
である。
nil
を返すか---を伝えるものである。 三番目の引
数として nil
が指定されていると、検索に失敗した場合にはエラーを出
して警告する。
search-forward
の四番目の引数は、繰り返しの回数---つまり検索対象
の文字列が何回現れるのを調べるか---を指定するものである。この引数は省略
可能であり、もしこの引数なしで関数が呼び出された場合、この引数として1が
渡される。引数が負であれば、検索は後方に向かってなされる。
テンプレートで書くと、search-forward
式は次のようになる。
(search-forward "目的とする文字列" 検索の範囲の限界 検索に失敗した時の動作 繰り返しの階数) |
次に progn
を見ることにしよう。
progn
progn
は引数を各々順番に評価していき、最後の式の値を返すよう
な関数である。それ以前のS式はその副作用のためだけに評価され、これらが返
す値は無視される。
progn
式のテンプレートは極めて単純である。
(progn 本体...) |
zap-to-char
の中では、progn
式は二つのことを行う。まず、ポ
イントをちょうど正しい位置に置くこと。次に、ポイントの位置を返して、
kill-region
がどこまでテキストを削除すればよいかを教えることである。
progn
の最初の引数は search-forward
である。
search-forward
は検索する文字列を見つけた場合、その文字列
の最後の文字のすぐ後にポイントを移す。(今の場合、検索文字列は一文字
である。) ただし、もし検索が後方方向なら最初の一文字の直前の位置にポイン
トを移す。ポイントの移動は副作用である。
二番目の、そして最後の progn
の引数は (point)
である。この
S式はポイントの位置を返す。今の場合なら、これは search-forward
によって移動されたポイントの位置である。この値が progn
式の値とし
て返され、kill-region
に kill-region
の二番目の引数として
渡される。
zap-to-char
についての総括
さて、以上で search-forward
と progn
の働きを見てきた。こ
こで zap-to-char
全体がどのように動作するかを見ることにしよう。
zap-to-char
コマンドが与えられた時の kill-region
の最初の
引数はカーソルの位置---その時点でのポイントの値---である。また
progn
の中で検索コマンドによってポイントが zapped-to-character の
すぐ後に移され、point
によってその位置の値が返される。
kill-region
関数はこれら二つの値の最初の値をリージョンの始まりに、
二番目の値をリージョンの終わりに指定して、そのリージョンを削除する。
progn
関数が必要なのは、kill-region
関数の引数が二つである
からである。仮に search-forward
式と point
式が続けて二つ
の引数として書かれていたとしたら、うまく動作してはくれない。progn
は kill-region
にとって一つの引数であり、kill-region
が第
二引数として必要な一つの値を返すのである。
zap-to-char
の version 18 での実装は version 19 での実装とはちょっ
とばかり違っている。こちらも zapped-to-character までのテキストを削除す
るのだが、それには zapped-to-character 自身は含めない。また、指定された
文字列が見つからない場合にはバッファの最後まで削除してしまう。
違いは kill-region
の第二引数にある。Version 19 での実装では次の
ようになっていた。
(progn (search-forward (char-to-string char) nil nil arg) (point)) |
しかし version 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 zapped-to-character まで移動出来たらその位置にポイントを移動し ポイントを正確な位置に移動し直してその位置を返す そうでなければバッファの最後まで行き、その位置を返す) |
if
式を評価することで、kill-region
の二番目の引数が指定さ
れる。最初の引数としてはポイントの位置が与えられるので、これらから、
kill-region
はポイントから zap する位置までのテキストを削除出来る。
search-forward
が副作用としてどのようにポイントを移動するかについ
ては、既に説明したのだった。search-forward
はもし検索に成功したな
ら t
を返す。失敗した場合に nil
を返すか、エラーメッセージ
を出すかは search-forward
の三番目の引数による。今の場合三番目の
引数は t
なので、検索に失敗すると nil
が返る。後で見るよう
に、nil
が返される場合のコードの扱いは簡単である。
zap-to-char
の version 18 での実装では、検索は if
式の真偽
テストの部分で検索のためのS式が評価されることによって起動される。もし検
索に成功すれば Emacs は if
式の then-part を評価する。一方、もし
検索に失敗すれば、Emacs は else-part の方を評価する。
if
式の中で検索が成功すれば、progn
式が実行される---つまり
プログラムとして走らされる。
前にも言ったように、progn
は引数を順に評価していき、その最後のS
式の値を返す関数である。それ以前のS式は単に副作用だけのために評価され、
それらが返す値は捨てられる。
上で説明したように、このバージョンの zap-to-char
では
progn
式は search-forward
関数が検索する文字を見つけた場合
に実行される。progn
式は二つのことを実行しなければならない。一つ
はポイントを正しい位置に移動することであり、もう一つはその位置を返すこと
により kill-region
にどこまで削除すればよいかを伝えることである。
progn
が使われる理由は search-forward
が検索文字列を見
つけた場合に、目的の文字列の最後の文字の直後までポイントを移動してし
まうことにある。(今の場合は目標となる文字列は一つの文字である。) ま
た、検索が後方方向の場合は、search-forward
はポイントを目的の
文字列の最初の文字の直前に置いてしまう。
しかし、このバージョンの zap-to-char
は検索した文字自身は削除しな
いようになっている。例えば zap-to-char
が `z' という文字まで
のテキストを削除する場合、このバージョンのものでは `z' という文字は
削除してはいけない。そこで、ポイントをその文字を削除しないような所まで移
動しなければならないというわけである。
progn
式の本体
progn
式の本体
progn
式の本体は二つのS式からなっている。異なる部分間の区切を明
確にするために間を空けて、更にコメントを加えると、progn
式は次の
ようになる。
(progn (goto-char ; |
この progn
式がやることは次の通りである。もし検索が前方ならば、
(つまり arg
が正の数なら) Emacs は検索した文字の直後にポイントを
移動している。そこで一文字分前に戻ることで、その文字を対象範囲から除く。
この場合、progn
式の中のS式は次のように読める。(goto-char
(1- (point)))
。これはポイントの位置を一文字分だけ戻す。(1-
とい
う関数は引数から1を引く関数である。同様に 1+
は引数に1を足す。)一
方、もし zap-to-char
の引数が負ならば、検索は後方に向かって行われ
る。if
がこれを検知し、この場合は (goto-char (1+ (point)))
というS式が実行されることになる。(繰り返しになるが、1+
は引数に1
を加える関数である。)
progn
の二番目の、そして最後の引数は、(point)
というS式で
ある。このS式は progn
の最初の引数によって移動したポイントの位置を
返す。この値はこのS式を含む if
式によって返され、
kill-region
の第二引数として渡される。
手短にまとめると、この関数の働きは以下の通りである。kill-region
の最初の引数は zap-to-char
コマンドが与えられた時のカーソルの位置
---つまりその時点でのポイントの位置---である。検索に成功すれば検索を行う
関数がそこまでポイントを移動する。そして progn
式によって、指定し
た文字が削除する領域に入らないようにポイントの位置をずらしてから、その位
置を返す。そして、kill-region
関数がそのリージョンを削除するとい
うわけである。
最後に、if
式の else-part で、指定した文字が見つからなかった場合
の対処をしている。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 ; 失敗したら |
これを見ればわかるように、version 19 での実装は、version 18 での実装より もやることがちょっと少ないが、その分コードはずっと単純になっていたのであ る。
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
を
使っているところである。これらの関数については以降のセクションで説明する。
delete-region
: ちょっと脱線して C の話を
zap-to-char
コマンドは kill-region
関数を使っている。そし
て、これはこれでまた別の二つの関数 copy-region-as-kill
及び
delete-region
を利用している。copy-region-as-kill
につい
てはこの後のセクションでも説明するが、リージョンを kill-ring にコピーし
てヤンク出来るようにするものである。(copy-region-as-kill
, 参照。)
delete-region
関数は、リージョンの中身を消去してしまうもので、こ
の場合、それを取り戻すことは出来ない。
これまでに議論してきたコードとは異なり、delete-region
は Emacs
Lisp では書かれていない。これは C で書かれており、GNU Emacs system のプ
リミティブの一つである。これは大変単純なものなので、ちょっとの間 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
という単語から始まっていることを指摘しておく。ここで
DEFUN
という単語を選んだのは、このコードが Lisp における
defun
と同じ目的を持っているためである。単語 DEFUN
の後に
続く括弧の中に7つの部分がある。
delete-region
である。
Fdelete_region
である。便宜上、
`F' から始まっている。C では名前にハイフンを使わないので、代わりに
下線 (underscore) が使われている。
"r"
である。これは、この関数の引数がそのバッファのリージョンの
最初と最後の位置であることを示している。この場合はプロンプトはない。
次に形式的なそのオブジェクトの種類の宣言と一緒にパラメータが来て、そして、
このマクロの本体部分と呼ばれる部分が来る。delete-region
の場合は、
本体は次の三行からなる。
validate_region (&b, &e); del_range (XINT (b), XINT (e)); return Qnil; |
最初の関数 validate_region
は、リージョンの始まりと終わりとして渡
された値が正しい型で、適切な範囲かどうかをチェックしている。二番目の関数、
del_range
は、実際にテキストを削除する関数である。もしこの関数が
エラーを起こさず仕事を終えたら、三行目がそのことを示すために Qnil
を返す。
del_range
は複雑な関数なので、立ち入らないことにする。これはバッ
ファを更新するなどの働きをする。しかしながら、del_range
に渡され
る二つの引数には注目しておいた方がいいだろう。XINT(b)
と
XINT(e)
の二つである。C 言語として見る限り、b
と
e
は削除するリージョンの始まりと終わりを表わす二つの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 はおしなべて大変単純な構造をしている。しかし、そのような
動作をさせるために、裏では極めて複雑な処理をやっているというわけである。
defvar
を用いた変数の初期化
delete-region
関数と違って、copy-region-as-kill
関数は
Emacs Lisp で書かれている。これはバッファのリージョンを複写して
kill-ring
と呼ばれる変数に保存するものである。このセクションでは
この変数がどのようにして生成され初期化されるのかを説明する。
(ここでも、kill-ring
という言葉は誤用と言えるだろう。バッファから
切り取られたテキストは取り戻すことが出来るのである。これは、死体の輪なんか
ではなく、復活出来るテキストの輪なのだ。)
Emacs Lisp では、kill-ring
のような変数は defvar
という特
殊形式によって生成され、初期値を与えられる。この名前は "define
variable" から来ている。
defvar
という特殊形式は、変数に値を設定する点で setq
と似ている。setq
と違う点は二つある。一つ目は、その変数がまだ値を
持っていない場合にのみ値を設定するということである。既に値がある場合には、
defvar
はその値を上書きしたりはしない。二つ目は defvar
に
は説明文字列があるということである。
現在の変数の値は、どんな値であれ、describe-variable
関数を使って
見ることが出来る。これは普通は C-h v をタイプすることで呼び出せる。
C-h v とタイプして、プロンプトが出た所で kill-ring
(と改行)
を入力すると、現在の kill リングの中身がどうなっているか見ることが出来る。
これは、滅茶苦茶多いこともある! 逆に、もし今回 Emacs を起動してからこの
文書を読むだけで他には何もしていなければ、中には何もないはずだ。
また、`*Help*' バッファの最後に kill-ring
の説明がなされてい
ることも見てとれる。
Documentation: List of killed text sequences. |
この kill リングは defvar
を使って次のように定義されている。
(defvar kill-ring nil "List of killed text sequences.") |
この変数定義の中では、この変数は初期値 nil
を与えられている。これ
はもっともなことである。何もセーブしないうちから、ヤンク (yank) コマンド
で何かを取り出したいとは思わないだろう。説明文字列は defun
の
説明文字列と同じように書かれている。
defun
のものと同様、最初の行はそれだけで完全な文になっているべき
だ。何故なら apropos
のような幾つかのコマンドは、最初の一行しか
表示しないからである。また、その後に続く行はインデントすべきではない。そ
うしないと C-h v
(describe-variable
) を使って見た場合に見
栄えがよくない。
大抵の変数は Emacs の内部のものである。が、幾つか edit-options
コ
マンドを使って簡単に設定出来るようになっているものもある。(ただし、ここ
での設定はその時のセッション限りのものである。その設定を永続的に使用した
い場合は、`.emacs' ファイルを使う。詳しくは Your `.emacs' File, を参照のこと。)
簡単に設定出来る変数は、Emacs の中で、説明文字列の一 桁目にアスタリスク `*' を付けることで他の変数と区別されている。
例えば、
(defvar line-number-mode nil "*Non-nil means display line number in mode line.") |
これは edit-options
コマンドを使って line-number-mode
の値
を変化させることが出来ることを示している。
勿論、setq
式を使って line-number-mode
の値を変化させるこ
とも出来る。例えば次のようにすればよい。
(setq line-number-mode t) |
Using setq
, 参照。
copy-region-as-kill
copy-region-as-kill
関数はバッファからリージョンのテキストを複写
して、kill-ring
と呼ばれる変数の中に保存する。
もし、kill-region
コマンドを前回の kill-region
コマンドの
直後に呼び出したとすると、Emacs は新しく複写したテキストを以前複写したテ
キストに付加 (append) する。これは、もしそのテキストをヤンクした場合、以
前複写した分と今回複写した分を合わせた全体を戻すことになるということを意
味する。一方、もし copy-region-as-kill
に先立って他のコマンドを使
用した場合には、複写したテキストは kill リングの新しい別の部分に、それま
でに複写したものと区別されて保存される。
以下が version 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) ;; then-part: 新しくコピーされたテキストを ;; 前回コピーされたものと結合 (kill-append (buffer-substring beg end) (< end beg)) ;; else-part: 新しくコピーされたテキストを新たな要素として ;; kill リングに追加し、必要なら kill リングを短くする。 (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"
によってインタラクティ
ブ宣言がなされている。つまり、二つの引数は各々リージョンの始まりと終わり
を示すものである。もしこの文書をきちんと最初から読んできたなら、この部分
を理解するのは殆どルーティーンワークにすぎないだろう。
この関数の本体は、if
式で始まっている。この式でやっているのは二つ
の状況を区別することである。つまり、このコマンドがkill-region
コ
マンドが呼び出された直後に実行されたかどうかの判断である。もし直後に実行
されたなら、新しく複写されるリージョンは以前複写された部分に付加される。
そうでない場合は、kill リングの先頭に、以前複写されたものとは区別されて
保存される。
この関数定義の最後の二行は、二つの setq
式である。一つは変数
this-command
を kill-region
に、もう一つは変数
kill-ring-yank-pointer
を kill リングに設定する。
この copy-region-as-kill
の本体部分は詳しく見る価値がある。
8.5.1 copy-region-as-kill
の本体部分
copy-region-as-kill
の本体部分
copy-region-as-kill
関数は、二行以上の kill を一つの項目につなげ
るように書かれている。もし、それらを kill リングからヤンクして取り出すと、
一箇所にまとめて取り出される。更に、現在のカーソルの位置から前方 (つまり
テキストの終わりの方) に向かって kill すると、その部分は以前 kill したテ
キストの後に付け加えられ、後方 (つまりテキストの始まりの方) に向かって
kill すると、以前 kill したテキストの前に付け加えられる。こうしてテキス
トの部分は正しい順序に保たれることになる。
この関数は、現在、及びその一つ前に Emacs が実行したコマンドを憶えておく
ために二つの変数 this-command
と last-command
を使っている。
通常、Emacs はどんな関数を実行している場合でも this-command
の値
を現在実行中のコマンドの値 (今の場合なら copy-region-as-kill
) に
セットしている。しかしながら、copy-region-as-kill
の場合は違う。
この関数は this-command
の値を kill-region
にセットする。
これは copy-region-as-kill
を呼び出している関数の名前である。
copy-region-as-kill
の本体部分の最初のパートでは、if
式で
last-command
が kill-region
かどうかを判定している。もしそ
うであれば、kill-append
関数を使って今回複写したテキストを kill
リングの最初の要素 (つまり CAR) のテキストと結合する。一方、もしも
last-command
の値が kill-region
で無ければ
copy-region-as-kill
関数は kill リングに新しい要素を付け加えること
になる。
if
式は次の通りである。これは eq
を使っているが、この関数
はここで初めて出てきたものである。
(if (eq last-command 'kill-region) ;; then-part (kill-append (buffer-substring beg end) (< end beg)) |
eq
関数は、最初の引数が二番目の引数と同じ Lisp オブジェクトかどう
かを判定するものである。eq
関数は equal
関数と、等しいかど
うかの判定をするという点では似ているが、異なる名前を持つものが、計算機の
内部で実際に同じオブジェクトかどうかを判定するのかそうでないか、という点
で異なっている。eq
の方は同じオブジェクトの場合でないと真を返さな
いが、equal
関数の方は二つの式が同じ構造でありかつ同じ内容であり
さえすれば真を返す。
(訳註:ここはちょっと分り難いかもしれない。詳しくは section `Equality Predicates' in The GNU Emacs Lisp Reference Manual, を参照。なお、 リストはどのように実装されているか, も参考 になる。)
関数 kill-append
copy-region-as-kill
の else-part
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
関数は concat
を
使って新しいテキストを kill リングの CAR と結合している。新しい方の
テキストを前に持ってくるか、後に持ってくるかは if
式の結果による。
(if before-p ; if-part (concat string (car kill-ring)) ; then-part (concat (car kill-ring) string)) ; else-part |
もし、今回切り取ったリージョンが直前のコマンドで切り取ったテキストよりも
前にあれば、今回切り取ったものは以前保存したものの前につなげるべきである
し、逆に、今回切り取ったテキストが以前切り取ったテキストの後に続くものな
ら、以前保存したものの後につなげるべきである。if
式は今回保存する
テキストを以前保存しておいたテキストの前につけるか後につけるかを
before-p
という述語で判断している。
シンボル before-p
は kill-append
の引数の名前の一つである。
kill-append
関数が評価されると、これは実際の引数を評価して返され
た値にバインドされる。今の場合であれば、(< end beg)
の値がバイン
ドされる。このS式は、今回のコマンドで切り取られたテキストが直前のコマン
ドで切り取られたテキストの前に属しているか、それとも後に属しているかを、
直接判定しているわけではない。この式がやっているのは変数 end
の値
が変数 beg
の値よりも小さいかどうかの判定だけである。もし小さけれ
ば、それはユーザがおそらくはバッファの先頭に向かっているということを意味
する。というわけで、この場合は以前のテキストの前に今回のテキストを追加す
るようになっている。一方、もし end
の値の方が beg
の値より
も大きかった場合、今回のテキストは以前のテキストの後尾に追加される。
新しく保存したテキストが以前保存したテキストの前に追加される場合は、その 新しいテキストを前にして古いテキストと結合される。
(concat string (car kill-ring)) |
逆に、テキストが後に追加される場合は、新しいテキストを古いテキストの後に して結合する。
(concat (car kill-ring) string)) |
この関数の動作を理解するためには、concat
関数の復習から始めなけれ
ばならないだろう。concat
関数は二つのテキスト文字列を一つにつ
なげるものである。結果も文字列になる。例えば次のような感じである。
(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
関数が理解出来るようになる。これは、kill リン
グの中身を修正するものである。kill リングはリストであり、各々の要素は保
存されたテキストである。setcar
関数が実際にこのリストの最初の要素
を置き換える役目を果たしている。そのために、まず concat
で kill リ
ングの最初の要素 (kill リングの CAR) と新しいテキストを結合し、元の
要素をその新しく出来た要素で置き換えている。新しく保存するテキストを元々
のテキストの前に結合するか後に結合するかは、それが古いテキストを切り取っ
た場所の前にあるか後にあるかによる。結合された結果が kill リングの新しい
最初の要素となる。
ついでながら、私の現在の kill リングの最初の方を挙げておこう。
("concatenating together" "saved text" "element" ... |
copy-region-as-kill
の else-part
さて、copy-region-as-kill
の説明に戻ろう。
もし直前のコマンドが kill-region
でなかったとすると、この場合は
kill-append
ではなく、次のS式の else-part が呼ばれることになる。
(if 真偽テスト テストが真だった時の動作 ;; else-part (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))) |
else-part の setq
の行では、kill リングの新しい値として、古い
kill リングの値に新しく削除した文字列を加えた結果をセットしている。
これがどういうことをやっているかを簡単な例で見てみることにしよう。
(setq example-list '("here is a clause" "another clause")) |
このS式を C-x C-e で評価すると、example-list
が評価出来る
ようになる。その結果は次の通り。
example-list => ("here is a clause" "another clause") |
さて、次のS式を評価することで、このリストに新しい要素を付け加えてみよう。
(setq example-list (cons "a third clause" example-list)) |
そうして再度評価すると、次の値が返されるのが分る。
example-list => ("a third clause" "here is a clause" "another clause") |
かくして、三番目の要素が cons
によってリストに加えられた。
この関数での setq
や cons
も上と同じことをやっているのであ
る。違うのは、リージョンのテキストを取り出して cons
に渡すために、
buffer-substring
を使っていることである。その行をもう一度抜き出し
ておく。
(setq kill-ring (cons (buffer-substring beg end) kill-ring)) |
copy-region-as-kill
の else-part の次の部分は、もう一つの if 式で
ある。この if
式は kill リングが長くなりすぎるのを防ぐものである。
この部分は以下のようになっている。
(if (> (length kill-ring) kill-ring-max) (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))) |
このコードはまず kill リングの長さが最大許容範囲にを越えていないかどうか
チェックしている。最大許容範囲とは kill-ring-max
の値 (デフォルト
では 30) のことである。もしこの長さがそれより長ければ、このコードは kill
リングの最後の要素を nil
にセットする。そのために二つの関数
nthcdr
と setcdr
を利用している。
先に setcdr
の方を見ることにする。(setcdr
,
参照。) これはリストの CDR を設定するものである。setcar
が
リストの CAR を設定するのと同様だ。しかしながら、この場合
setcdr
は kill リング全体の cdr
を設定するのではない。
nthcdr
関数は繰り返しリストの CDR を取ることで動作する。つま
り、CDR の CDR を取り、そのまた CDR を取り ... という
操作を N 回繰り返して、その値を返すのである。
もし四つの要素のリストがあって、それを(最初の)三つの要素のリストにしたい
場合、二回 CDR を取って出来たリストの CDR を nil
に設定
すればよい。このようにして、リストを短く出来るのである。
以上のことは、下の三つのS式を順に評価していくことで理解出来る。まずは
trees
の値を (maple oak pine birch)
にセットし、次にその二
番目の 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
式そのものの値は nil
になる。これは、CDR
を nil
にセットしたからである。)
繰り返しのために nthcdr
関数を使って、copy-region-as-kill
に対し kill リングの最大許容範囲よりも1少ない回数だけ CDR を取り、
最後に返された CDR の値(これは kill リングの最後の要素である)を
nil
にセットする。こうして kill リングが長くなり過ぎることを防い
でいるのである。
copy-region-as-kill
関数の次の行は
|
である。この行は内側のS式にも外側の 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
はポインタ (`pointer') とは呼ばれてい
るけれども、実際は kill-ring と同じような変数である。ただし、このような
名前がついているのは、この変数の使われ方がよく分るようにするためである。
この変数は yank
や yank-pop
などの関数で使用される。
(テキストのヤンク, 参照。)
この辺りの話をするにはやはり、バッファから切り取ったテキストを取り戻すた めのコマンド---ヤンクコマンドについて話さなければなるまい。が、その話をす る前に、リストが計算機の内部でどのように扱われるかを説明しておいた方が良 いだろう。そうすることで、「ポインタ」といったミステリアスな言葉を明確に 理解出来るようになるだろう。
この辺りで導入した関数についての簡単なまとめを載せておく。
car
cdr
car
はリストの最初の要素を返す。cdr
はリストの二番目以降の
要素からなるリストを返す。
例)
(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
は最初の引数を二番目の引数の前に加えることで新たなリストを作
成する。
例)
(cons 1 '(2 3 4)) => (1 2 3 4) |
nthcdr
cdr
を取った結果を返す。言わば `残り' の
`残り' である。
例)
(nthcdr 3 '(1 2 3 4 5 6 7)) => (4 5 6 7) |
setcar
setcdr
setcar
はリストの最初の要素を置き換える。setcdr
はリストの
二番目以降の要素を置き換える。
例)
(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
引数は次の四つ
nil
を返すかエラーメッセージを出すか
を指定。
kill-region
delete-region
copy-region-as-kill
kill-region
はバッファのポイントとマークの間のテキストを切り取って
それを kill リングに保存する。このテキストはヤンクによって取り出すことが
出来る。
delete-region
はポイントとマークの間のテキストをバッファから削除
して捨ててしまう。復活させることは出来ない。
copy-region-as-kill
はポイントとマークの間のテキストを kill リング
に複写する。これはヤンクによって取り出すことが出来る。この関数はバッファ
からテキストを切り取ったり削除したりはしない。
search-forward
とい
う名前を使ってはいけない。そうしてしまうと、元の Emacs で定義された現在
の search-forward
を上書きしてしまう。例えば test-search
という名前を使いなさい。
copy-region-as-kill
はもはや
this-command
に値をセットしない。この変更で、どのような影響が出る
か? また、何故このような変更が行われたと思うか。
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |