kill-new 函数

在版本22中,kill-new 函数的定义如下:

(defun kill-new (string &optional replace yank-handler)
  "将STRING设为kill环中的最新kill。
将`kill-ring-yank-pointer'设为指向它。

如果`interprogram-cut-function'非nil,则将其应用于STRING。可选的第二个参数REPLACE非nil表示STRING将替换kill环的最前面,而不是被添加到列表中。
…"
  (if (> (length string) 0)
      (if yank-handler
          (put-text-property 0 (length string)
                             'yank-handler yank-handler string))
    (if yank-handler
        (signal 'args-out-of-range
                (list string "yank-handler specified for empty string"))))
  (if (fboundp 'menu-bar-update-yank-menu)
      (menu-bar-update-yank-menu string (and replace (car kill-ring))))
  (if (and replace kill-ring)
      (setcar kill-ring string)
    (push string kill-ring)
    (if (> (length kill-ring) kill-ring-max)
        (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
  (setq kill-ring-yank-pointer kill-ring)
  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

(注意,此函数不是交互式的。)

和往常一样,我们可以将这个函数分为几个部分来看。

函数定义有一个可选的yank-handler参数,当调用时告诉函数如何处理添加到文本中的属性,比如粗体或斜体。我们将跳过这一点。

文档的第一行有意义:

将STRING设为kill环中的最新kill。

让我们暂时跳过文档的其余部分。

同样,让我们跳过初始的if表达式以及涉及menu-bar-update-yank-menu的那些行代码。我们将在下面解释它们。

关键的代码在这里:

  (if (and replace kill-ring)
      ;; 然后
      (setcar kill-ring string)
    ;; 否则
    (push string kill-ring)
    (if (> (length kill-ring) kill-ring-max)
        ;; 避免kill环过长
        (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))
  (setq kill-ring-yank-pointer kill-ring)
  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

条件测试是 (and replace kill-ring)。当满足两个条件时,这将为真:kill环中有内容,并且replace变量为真。

kill-append函数将replace设置为真时,且kill环中至少有一项时,将执行setcar表达式:

(setcar kill-ring string)

setcar函数实际上将kill-ring列表的第一个元素更改为string的值。它替换第一个元素。

另一方面,如果kill环为空,或者replace为假,则执行条件的else部分:

(push string kill-ring)

push将其第一个参数推入第二个参数。它类似于较旧的

(setq kill-ring (cons string kill-ring))

或者较新的

(add-to-list kill-ring string)

当为假时,表达式首先通过将要被杀死的字符串作为新元素添加到旧kill环中构造了新版本的kill环(这就是push的作用)。然后它执行第二个if子句。这第二个if子句防止kill环变得过长。

让我们按顺序查看这两个表达式。

push else-part的行将新的kill环的值设置为将要被杀死的字符串添加到旧kill环中得到的值。

通过以下示例,我们可以看到它是如何工作的。

首先,

(setq example-list '("here is a clause" "another clause"))

通过使用C-x C-e评估此表达式后,您可以评估example-list并查看其返回值:

example-list
     ⇒ ("here is a clause" "another clause")

现在,我们可以通过评估以下表达式将一个新元素添加到此列表中:

(push "a third clause" example-list)

当我们评估example-list时,我们发现其值为:

example-list
     ⇒ ("a third clause" "here is a clause" "another clause")

因此,通过push添加了第三个子句。

现在是if子句的第二部分。此表达式防止kill环变得过长。它看起来是这样的:

(if (> (length kill-ring) kill-ring-max)
    (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))

代码检查kill环的长度是否大于允许的最大长度。这是kill-ring-max(默认为120)的值。如果kill环的长度太长,则此代码将最后一个元素设置为nil。它通过使用两个函数nthcdrsetcdr来实现。

我们之前看过setcdr(see setcdr)。它设置列表的CDR,就像setcar设置列表的CAR一样。但是,在这种情况下,setcdr不会设置整个kill环的CDRnthcdr函数用于使其设置kill环的倒数第二个元素的CDR——这意味着由于倒数第二个元素的CDR是kill环的最后一个元素,它将设置kill环的最后一个元素。

nthcdr函数通过重复获取列表的CDR来工作——它获取CDRCDRCDR等等。它这样做N次并返回结果。(See nthcdr.)

因此,如果我们有一个应该是三个元素长的四个元素列表,我们可以将倒数第二个元素的CDR设置为nil,从而缩短列表。(如果将最后一个元素设置为除nil之外的其他值,您可以这样做,那么您将不会缩短列表。See setcdr.)

通过依次评估以下三个表达式,您可以看到缩短的效果。首先将trees的值设置为(maple oak pine birch),然后将其第二个CDRCDR设置为nil,然后找到trees的值:

(setq trees (list 'maple 'oak 'pine 'birch))
     ⇒ (maple oak pine birch)

(setcdr (nthcdr 2 trees) nil)
     ⇒ nil

trees
     ⇒ (maple oak pine)

(setcdr表达式返回的值为nil,因为它将CDR设置为nil。)

重申一下,在kill-new中,nthcdr函数取kill环的最大允许大小减一的次数,setcdr函数将其CDR设置为那个元素(这意味着由于倒数第二个元素的CDR是kill环的最后一个元素,它将设置kill环的最后一个元素)。这样可以防止kill环变得过长。

kill-new函数中倒数第二个表达式是

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

kill-ring-yank-pointer是一个全局变量,被设置为kill-ring

尽管kill-ring-yank-pointer被称为‘指针’,但它和kill环一样是一个变量。但是,为了帮助人们理解该变量的用法,选择了这个名称。

现在,回到函数体中的早期表达式:

  (if (fboundp 'menu-bar-update-yank-menu)
       (menu-bar-update-yank-menu string (and replace (car kill-ring))))

它以一个if表达式开始

在这种情况下,该表达式首先测试menu-bar-update-yank-menu是否存在作为函数,并且如果存在,则调用它。fboundp函数返回true,如果它测试的符号具有非空的函数定义。如果符号的函数定义为空,我们将收到错误消息,就像我们故意创建错误一样(see 生成错误消息)。

然后部分包含一个表达式,其第一个元素是and函数。

特殊形式 and 对其每个参数进行求值,直到其中一个参数返回值为 nil 为止,此时 and 表达式返回 nil;然而,如果没有任何参数返回 nil,则返回最后一个参数的求值结果。 (由于这样的值不是 nil,在Emacs Lisp中被视为真值。)换句话说,只有当所有参数都为真时,and 表达式才返回真值。(See 复习.)

该表达式确定了 menu-bar-update-yank-menu 的第二个参数是否为真。

menu-bar-update-yank-menu 是使得可以在菜单条的编辑项目的“选择和粘贴”菜单中使用的函数之一;使用鼠标,您可以查看已保存的各种文本片段,并选择一个片段进行粘贴。

kill-new 函数中的最后一个表达式将新复制的字符串添加到用于在窗口系统中运行的不同程序之间复制和粘贴的任何设施中。例如,在X Windowing系统中,x-select-text 函数将字符串存储在由X操作的内存中。您可以在另一个程序中粘贴该字符串,例如Xterm。

该表达式如下:

  (if interprogram-cut-function
      (funcall interprogram-cut-function string (not replace))))

如果存在 interprogram-cut-function,则Emacs执行 funcall,它又调用其第一个参数作为函数,并将其余参数传递给它。 (顺便说一下,就我所看到的,此 if 表达式可以被类似于函数第一部分的 and 表达式替代。)

我们不打算进一步讨论窗口系统和其他程序,只是注意到这是一种使GNU Emacs能够轻松而有效地与其他程序协同工作的机制。

这段代码用于将文本放入kill环中,可以是与现有元素连接,也可以是作为新元素。这引导我们进入了从缓冲区中删除文本的代码——yank命令。但在讨论yank命令之前,最好先了解计算机中列表是如何实现的。这将解释“指针”一词的使用。但在此之前,我们将离题讨论C。