[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8. テキストのカットと保存

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Cutting%20&%20Storing%20Text"
"intro/テキストのカットと保存"へのコメント(無し)

GNU Emacsで「キル(kill)」コマンドでバッファからテキストをカットする (切り取る)と、それらはリストに保存され、 「ヤンク(yank)」コマンドで取り出せる。

(Emacsにおける単語「kill」は、実体の値を破壊しない処理を意味するが、 その用法は、歴史的な不運な偶然による。 もっと適切な用語は、キルコマンドの動作からすれば、 「clip(切り取る)」であろう。 バッファからテキストを切り取り、復元可能なように保存するからである。 Emacsのソースのすべての「kill」を「clip」に、 すべての「killed」を「clipped」に置き換えたくなる誘惑にしばしば駆られる。)

Storing Text in a List

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Storing%20Text"
"intro/StoringTextinaList"へのコメント(無し)

バッファからテキストを切る取ると、それはリストに保存される。 順に切り取ったテキストは順にリストに保存されるので、 リストはつぎのようになる。

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

関数carnthcdrを使えば、テキストの望みの断片を取り出せる。 たとえば、つぎのコードでは、 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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1 zap-to-char

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=zap-to-char"
"intro/zap-to-char"へのコメント(無し)

関数zap-to-charは、GNU Emacsの第18版と第19版とでは書き方が異なる。 第19版での実装のほうがいくぶん簡単であるが、動作も少々異なる。 第19版の関数を説明してから、第18版の関数を説明する。

Emacs第19版の対話的関数zap-to-charの実装では、 カーソル(つまりポイント)の位置から指定文字を含めたテキストを取りさる。 zap-to-charで取りさったテキストはキルリングに保存され、 C-yyank)とタイプするとキルリングから取り出せる。 コマンドに引数を与えると、指定文字のその回数までのテキストを取りさる。 たとえば、「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 and search-forward.
8.1.6 第18版の実装    The version 18 implementation.



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.1 interactive

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=zap-to-char%20interactive"
"intro/interactive式"へのコメント(無し)

コマンドzap-to-charinteractive式はつぎのとおりである。

 
(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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.2 zap-to-charの本体

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=zap-to-char%20body"
"intro/zap-to-charの本体"へのコメント(無し)

関数zap-to-charの本体には、 カーソルの現在位置から指定文字を含んだテキストをキル(つまり、削除)する コードがある。 コードの最初の部分はつぎのとおりである。

 
(kill-region (point) ...

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

コードのつぎの部分は、prognを使った式である。 prognの本体は、search-forwardpointの呼び出しから成る。

search-forwardを説明してからのほうが prognの動作を理解しやすいので、 search-forwardを説明してからprognを説明する。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.3 関数search-forward

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=search-forward"
"intro/関数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つの引数を取る。

  1. 第1引数は探す対象となるものであり、 `"z"'のような文字列である必要がある。

    zap-to-charに渡される引数は単独の文字である。 コンピュータの構成方法のために、Lispインタープリタは単独の文字と 文字列を区別する。 コンピュータ内部では、単独の文字は、1文字の文字列とは異なる電気的な形式となる (単独の文字は、コンピュータ内部では、しばしば、ちょうど1バイトで記録される。 一方、文字列は長かったり短かったりするので、コンピュータはそれに対応できる 必要がある)。 関数search-forwardは文字列を探すので、 関数zap-to-charが引数として受け取った文字は、 コンピュータ内部では、ある形式から別の形式に変換する必要がある。 さもないと、関数search-forwardは失敗する。 関数char-to-stringを使って、この変換を行う。

  2. 第2引数は探索範囲を限定し、バッファ内の位置を指定する。 この場合、バッファの最後まで探索してよいので、 探索範囲を限定せず、第2引数はnilである。

  3. 第3引数は、探索に失敗した場合にどうするかを指定する。 エラーを通知する(かつ、メッセージを表示する)か、nilを返す。 第3引数にnilを指定すると、探索に失敗すると関数はエラーを通知する。

  4. search-forwardの第4引数は、繰り返し回数、 つまり、文字列の出現を何回探すかを指定する。 この引数は省略でき、繰り返し回数を指定しないと1である。 この引数が負の場合には、逆向きに探索する。

search-forward式の概略はつぎのとおりである。

 
(search-forward "探索文字列"
                探索範囲
                探索失敗時の動作
                繰り返し回数)

では、prognを説明しよう。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.4 関数progn

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=progn"
"intro/関数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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.5 zap-to-charのまとめ

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=Summing%20up%20zap-to-char"
"intro/zap-to-charのまとめ"へのコメント(無し)

search-forwardprognの動作がわかったので、 関数zap-to-char全体としての動作を理解しよう。

kill-regionの第1引数は、コマンドzap-to-charを与えたときの カーソルの位置、つまり、そのときのポイントの値である。 prognの中で、探索関数が削除する文字の直後にポイントを移動し、 pointがその位置の値を返す。 関数kill-regionは、ポイントの2つの値を組み合わせて、 最初のものをリージョンの始まり、 あとのものをリージョンの終わりと解釈してリージョンを削除する。

search-forwardpointを2つ続けて余分な2つの引数として書くと、 2つの引数を取るコマンドkill-regionは失敗するので、 関数prognが必要なのである。 progn式は、kill-regionに対しては1つの引数となり、 kill-regionが第2引数に必要とする1つの値を返す。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.1.6 第18版の実装

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=v-18-zap-to-char"
"intro/第18版の実装"へのコメント(無し)

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式の本体

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=progn%20body"
"intro/progn式の本体"へのコメント(無し)

prognの本体には2つの式がある。 これを各部分がわかりやすいように輪郭を整えて注釈を加えるとつぎのようになる。

 
(progn 

  (goto-char                ; prognの最初の式
        (if (> arg 0)       ; argが正ならば、
            (1- (point))    ;   1文字分戻る;
          (1+ (point))))    ;   さもなければ、1文字分進める。

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

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                     ; 失敗したらnilを返す
      arg)                  ; 繰り返し回数
     (progn                 ; 真の場合の動作
       (goto-char     
        (if (> arg 0)
            (1- (point))
          (1+ (point))))
       (point))
   
   (if (> arg 0)            ; 偽の場合の動作
       (point-max)
     (point-min))))

以上でわかるように、第19版の実装は、第18版の実装より少々小さく、 より簡単である。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.2 kill-region

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=kill-region"
"intro/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-regioncopy-region-as-killを使っていることである。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.3 delete-region:Cへ回り道

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=delete-region"
"intro/delete-region:Cへ回り道"へのコメント(無し)

コマンドzap-to-charは関数kill-regionを使い、 それはさらにcopy-region-as-killdelete-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の本体はつぎの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に関する限り、beは、削除すべきバッファの先頭と最後を 表す2つの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は非常に簡単であるが、 その背後には、すべてが正しく働くように、とても複雑なことが隠れている。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.4 defvarによる変数の初期化

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=defvar"
"intro/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 vdescribe-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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.5 copy-region-as-kill

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=copy-region-as-kill"
"intro/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")
  本体...)

引数はbegendであり、 "r"が指定された対話的関数であるので、 2つの引数はリージョンの始まりと終わりを参照する必要がある。 本書を始めから読んでいる読者ならば、関数のこの部分を理解するのは簡単であろう。

単語「kill」が通常の意味と異なることを思い出せば、 説明文の内容に混乱することもないであろう。

関数の本体はif節で始まる。 この節で、2つの異なる場合を分ける。 コマンドkill-regionの直後に、このコマンドが実行されたかどうかを区別する。 そうならば、直前にコピーしたテキストに新たなリージョンを追加する。 さもなければ、直前の断片とは独立したテキストの断片として キルリングの先頭に挿入する。

関数の最後の2行は、2つのsetq式である。 1つは、変数this-commandkill-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 ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.5.1 copy-region-as-killの本体

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=copy-region-as-kill%20body"
"intro/copy-region-as-killの本体"へのコメント(無し)

関数copy-region-as-killは、 連続してキルしたテキストは1つの断片にまとめるように書かれている。 キルリングからテキストを取り出すと、1つの断片として得ることになる。 さらに、カーソルの現在位置から終わりに向けたキルでは 直前にコピーしたテキストの末尾に追加し、 先頭向けのコピーでは直前にコピーしたテキストの先頭に追加する。 このようにして、テキスト内の語順は正しく保たれる。

この関数では、現在と直前のEmacsコマンドを記録する2つの変数を用いる。 this-commandlast-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 function
copy-region-as-killの偽の場合の動作    The else-part of copy-region-as-kill



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

関数kill-append

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=kill-append%20function"
"intro/関数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の偽の場合の動作

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=copy-region-as-kill%20else-part"
"intro/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」をリストに追加したのである。

以上は、関数内でsetqconsとが行うことと同じであるが、 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つの関数nthcdrsetcdrを使う。

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の最後の行の直前はつぎのとおりである。

 
(setq this-command 'kill-region)

この行は、if式の内側でも外側でもなく、 copy-region-as-killが呼ばれるごとに評価される。 ここが、this-commandkill-regionを設定する場所である。 すでに説明したように、つぎにコマンドが与えられると、 変数last-commandにはこの値が設定される。

関数copy-region-as-killの最後の行は、つぎのとおりである。

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

kill-ring-yank-pointerはグローバル変数であり、 kill-ringと同じに設定される。

kill-ring-yank-pointerはポインタと名前が付いているが、 キルリングと同じ変数である。 しかし、変数の使われ方が人間にわかりやすいように、この名称が選ばれた。 この変数は、yankyank-popなどの関数で使われる (see 節 10. テキストの取り出し方)。

これで、バッファから切り取ったテキストを復元する ヤンクコマンドのコードの説明に進める。 しかし、ヤンクコマンドを説明するまえに、コンピュータでのリストの実装方法を 学んでおくのがよいであろう。 そうすれば、「ポインタ」などの用語の不可解さが明らかになる。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.6 復 習

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=cons%20&%20search-fwd%20Review"
"intro/復 習"へのコメント(無し)

これまでに説明した関数のいくつかを以下にまとめておく。

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

  1. 探すべき文字列。

  2. 探索範囲の制限。 省略できる。

  3. 探索に失敗した場合にnilを返すかエラーメッセージを返すか指定する。 省略できる。

  4. 探索を何回行うかを指定する。 省略できる。 負の場合には、逆向きに(先頭へ向けて)探索する。

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

kill-regionは、ポイントとマークのあいだのテキストを バッファから切り取り、ヤンクで復元できるように、 キルリングにそのテキストを保存する。

delete-regionは、ポイントとマークのあいだのテキストを バッファから取りさり、破棄する。 復元することはできない。

copy-region-as-killはポイントとマークのあいだのテキストを キルリングにコピーし、キルリングからそのテキストを復元できるようにする。 この関数は、バッファからテキストを取りさったりしない。



[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [表紙] [目次] [索引] [検索] [上端 / 下端] [?]

8.7 探索の演習問題

URL="https://bookshelf.jp/cgi-bin/goto.cgi?file=eintro&node=search%20Exercises"
"intro/探索の演習問題"へのコメント(無し)


[ << ] [ >> ]           [表紙] [目次] [索引] [検索] [上端 / 下端] [?]