[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

8. テキストの切り取りと保存

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")

関数 carnthcdr を使うと、この中からどの部分でも取り出 すことが出来る。例えば次に挙げるコードでは 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, 参照。


8.1 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 についての総括  pointsearch-forward を使う
8.1.6 Version 18 での実装  version 18 での実装


8.1.1 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 の引数を用意し、ユー ザーに対してプロンプトを出すということである。


8.1.2 zap-to-char の本体部分

zap-to-char 関数の本体部分は、現在のカーソルの位置から指定した文 字までの間の領域のテキストを削除するコードを含んでいる。その最初の部分は 次の通りである。

 
(kill-region (point) ...

(point) は現在のカーソルの位置である。

この後には、progn を使った式が続いている。progn の本体部分 では、search-forwardpoint を呼び出している。

まず search-forward を学んでからの方が progn の働きを理解 しやすいので、先にこちらを見て、次に progn について述べることにし よう。


8.1.3 関数 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 は四つの引数を取る。

  1. 最初の引数は、検索しようとするターゲットである。これは `"z"' のよう に文字列でなければならない。

    今回の場合は、たまたま zap-to-char に渡される引数は単独の文字であ る。計算機の設計上、Lisp インタプリタは単独の文字を一文字からなる文字列 とは区別して扱う。計算機の中では単独の文字は一文字からなる文字列とは別の 電気的なフォーマットを持っているのである。(単独の文字はたいてい計算機の 中で1バイトで記録されている。(訳註:勿論日本語とかでは事情は別である。) しかし、文字列の方は長いものもあれば短いものもあるので、計算機の方でそれ に備えなければならないのである。) search-forward は文字列を検索す るものなので、zap-to-char 関数に引数として渡された文字を計算機の 内部で別のフォーマットに変換しないといけない。でないと、 search-forward は検索に失敗してしまう。この変換をするために、 char-to-string という関数が使われている。

  2. 二番目は、検索の範囲を指定するためのものである。これはバッファ内の 位置として指定される。今の場合、検索はバッファの最後まで行うので、範囲を 制限したりはしない。従って、二番目の引数は nil である。

  3. 三番目は、この関数に検索が失敗し場合にどうするか---警告してエラー を表示するか、単に nil を返すか---を伝えるものである。 三番目の引 数として nil が指定されていると、検索に失敗した場合にはエラーを出 して警告する。

  4. search-forward の四番目の引数は、繰り返しの回数---つまり検索対象 の文字列が何回現れるのを調べるか---を指定するものである。この引数は省略 可能であり、もしこの引数なしで関数が呼び出された場合、この引数として1が 渡される。引数が負であれば、検索は後方に向かってなされる。

テンプレートで書くと、search-forward 式は次のようになる。

 
(search-forward "目的とする文字列"
                検索の範囲の限界
                検索に失敗した時の動作
                繰り返しの階数)

次に progn を見ることにしよう。


8.1.4 関数 progn

progn は引数を各々順番に評価していき、最後の式の値を返すよう な関数である。それ以前のS式はその副作用のためだけに評価され、これらが返 す値は無視される。

progn 式のテンプレートは極めて単純である。

 
(progn
  本体...)

zap-to-char の中では、progn 式は二つのことを行う。まず、ポ イントをちょうど正しい位置に置くこと。次に、ポイントの位置を返して、 kill-region がどこまでテキストを削除すればよいかを教えることである。

progn の最初の引数は search-forward である。 search-forward は検索する文字列を見つけた場合、その文字列 の最後の文字のすぐ後にポイントを移す。(今の場合、検索文字列は一文字 である。) ただし、もし検索が後方方向なら最初の一文字の直前の位置にポイン トを移す。ポイントの移動は副作用である。

二番目の、そして最後の progn の引数は (point) である。この S式はポイントの位置を返す。今の場合なら、これは search-forward によって移動されたポイントの位置である。この値が progn 式の値とし て返され、kill-regionkill-region の二番目の引数として 渡される。


8.1.5 zap-to-char についての総括

さて、以上で search-forwardprogn の働きを見てきた。こ こで zap-to-char 全体がどのように動作するかを見ることにしよう。

zap-to-char コマンドが与えられた時の kill-region の最初の 引数はカーソルの位置---その時点でのポイントの値---である。また progn の中で検索コマンドによってポイントが zapped-to-character の すぐ後に移され、point によってその位置の値が返される。 kill-region 関数はこれら二つの値の最初の値をリージョンの始まりに、 二番目の値をリージョンの終わりに指定して、そのリージョンを削除する。

progn 関数が必要なのは、kill-region 関数の引数が二つである からである。仮に search-forward 式と point 式が続けて二つ の引数として書かれていたとしたら、うまく動作してはくれない。prognkill-region にとって一つの引数であり、kill-region が第 二引数として必要な一つの値を返すのである。


8.1.6 Version 18 での実装

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 の最初の式。
        (if (> arg 0)       ; もし arg が正なら、
            (1- (point))    ;   一文字分戻り、
          (1+ (point))))    ;   そうでなければ一文字分進む。

  (point))                  ; progn の二番目の式。
                            ;   ポイントの位置を返す。

この 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                     ; 失敗したら nil を返す。
      arg)                  ; 繰り返しの回数
     (progn                 ; then-part
       (goto-char     
        (if (> arg 0)
            (1- (point))
          (1+ (point))))
       (point))
   
   (if (> arg 0)            ; else-part
       (point-max)
     (point-min))))

これを見ればわかるように、version 19 での実装は、version 18 での実装より もやることがちょっと少ないが、その分コードはずっと単純になっていたのであ る。


8.2 kill-region

zap-to-charkill-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-regioncopy-region-as-kill を 使っているところである。これらの関数については以降のセクションで説明する。


8.3 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 の場合は、 本体は次の三行からなる。

 
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 言語として見る限り、be は削除するリージョンの始まりと終わりを表わす二つの32ビットの整 数である。しかしながら、他の Emacs Lisp の数と同様、本来の数として使用さ れるのは32ビット中の最初の24ビットだけである。残りの8ビットは情報の種類 を保持したりするなど、他の目的のために使われる。(ある種の機械では、最初 のほんの6ビットしか使わなかったりもする。) 今の場合は、この8ビットはこれ らの数がバッファ内での位置を表すためのものであることを示すために用いられ る。このような目的のために使われる数は、tag と呼ばれる。このように 32ビット整数の中に8ビットのタグを埋め込むことで、そうしないよりも Emacs がずっと速く動作するようにすることが出来る。一方で、数を24ビットに制限し てしまうために、Emacs のバッファは約8メガバイトに制限されてしまう。(バッ ファの最大サイズは、コンパイルする前に `emacs/src/config.h' の中で VALBITSGCTYPEBITS を定義することでぐっと増やすことが出 来る。Emacs の配布に含まれる `emacs/etc/FAQ' を参照のこと。)

`XINT' は、32ビットの Lisp オブジェクトから 24ビットの数を取り出す C のマクロである。他の目的に使われる8ビットの部分は破棄される。かくし て、del_range (XINT (b), XINT (e)) は、開始位置 b か ら 終了位置 e までの間を消去することになる。Lisp を書く人の立場か ら見れば、Emacs はおしなべて大変単純な構造をしている。しかし、そのような 動作をさせるために、裏では極めて複雑な処理をやっているというわけである。


8.4 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, 参照。


8.5 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")
  本体...)

引数は begend であり、"r" によってインタラクティ ブ宣言がなされている。つまり、二つの引数は各々リージョンの始まりと終わり を示すものである。もしこの文書をきちんと最初から読んできたなら、この部分 を理解するのは殆どルーティーンワークにすぎないだろう。

この関数の本体は、if 式で始まっている。この式でやっているのは二つ の状況を区別することである。つまり、このコマンドがkill-region コ マンドが呼び出された直後に実行されたかどうかの判断である。もし直後に実行 されたなら、新しく複写されるリージョンは以前複写された部分に付加される。 そうでない場合は、kill リングの先頭に、以前複写されたものとは区別されて 保存される。

この関数定義の最後の二行は、二つの setq 式である。一つは変数 this-commandkill-region に、もう一つは変数 kill-ring-yank-pointer を kill リングに設定する。

この copy-region-as-kill の本体部分は詳しく見る価値がある。

8.5.1 copy-region-as-kill の本体部分  


8.5.1 copy-region-as-kill の本体部分

copy-region-as-kill 関数は、二行以上の kill を一つの項目につなげ るように書かれている。もし、それらを kill リングからヤンクして取り出すと、 一箇所にまとめて取り出される。更に、現在のカーソルの位置から前方 (つまり テキストの終わりの方) に向かって kill すると、その部分は以前 kill したテ キストの後に付け加えられ、後方 (つまりテキストの始まりの方) に向かって kill すると、以前 kill したテキストの前に付け加えられる。こうしてテキス トの部分は正しい順序に保たれることになる。

この関数は、現在、及びその一つ前に Emacs が実行したコマンドを憶えておく ために二つの変数 this-commandlast-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-commandkill-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-pkill-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 によってリストに加えられた。

この関数での setqcons も上と同じことをやっているのであ る。違うのは、リージョンのテキストを取り出して 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 にセットする。そのために二つの関数 nthcdrsetcdr を利用している。

先に 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 関数の次の行は

 
(setq this-command 'kill-region)

である。この行は内側のS式にも外側の if 式にも属していない。従っ て、copy-region-as-kill が呼ばれるたびに毎回評価される。ここの部 分で this-commandkill-region に設定されていることが分 るだろう。前に見たように、次のコマンドが与えられると、この値は last-command の値になる。

最後に copy-region-as-kill の最後の行を見てみよう。

 
(setq kill-ring-yank-pointer kill-ring)

kill-ring-yank-pointerkill-ring に設定されるグローバ ル変数である。

kill-ring-yank-pointer はポインタ (`pointer') とは呼ばれてい るけれども、実際は kill-ring と同じような変数である。ただし、このような 名前がついているのは、この変数の使われ方がよく分るようにするためである。 この変数は yankyank-pop などの関数で使用される。 (テキストのヤンク, 参照。)

この辺りの話をするにはやはり、バッファから切り取ったテキストを取り戻すた めのコマンド---ヤンクコマンドについて話さなければなるまい。が、その話をす る前に、リストが計算機の内部でどのように扱われるかを説明しておいた方が良 いだろう。そうすることで、「ポインタ」といったミステリアスな言葉を明確に 理解出来るようになるだろう。


8.6 復習

この辺りで導入した関数についての簡単なまとめを載せておく。

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
リストに対して `n' 回 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
文字列を検索し、もし文字列が見つかれば、そこにポイントを移動する。

引数は次の四つ

  1. 検索文字列。

  2. (省略可能) 検索範囲。

  3. (省略可能) 検索に失敗した時に nil を返すかエラーメッセージを出すか を指定。

  4. (省略可能) 検索を何回実行するかを指定。負数の場合は後方検索になる。

kill-region
delete-region
copy-region-as-kill

kill-region はバッファのポイントとマークの間のテキストを切り取って それを kill リングに保存する。このテキストはヤンクによって取り出すことが 出来る。

delete-region はポイントとマークの間のテキストをバッファから削除 して捨ててしまう。復活させることは出来ない。

copy-region-as-kill はポイントとマークの間のテキストを kill リング に複写する。これはヤンクによって取り出すことが出来る。この関数はバッファ からテキストを切り取ったり削除したりはしない。


8.7 検索についての練習問題


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Matsuda Shigeki on April, 10 2002 using texi2html