kill-new 函数 ¶在 22 版本中,kill-new 函数的定义如下:
(defun kill-new (string &optional replace yank-handler) "将 STRING 设为删除环中最新的删除内容。 设置 `kill-ring-yank-pointer' 指向该内容。 若 `interprogram-cut-function' 非 nil,将其应用于 STRING。 可选第二个参数 REPLACE 非 nil 表示 STRING 会替换删除环的头部, 而非添加到列表中。 ..."
(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 设为删除环中最新的删除内容。
我们暂时跳过文档字符串的其余部分。
同时,我们也先跳过开头的 if 表达式
以及涉及 menu-bar-update-yank-menu 的代码行。
我们会在下方进行说明。
关键代码行如下:
(if (and replace kill-ring)
;; then
(setcar kill-ring string)
;; else
(push string kill-ring)
(if (> (length kill-ring) kill-ring-max)
;; avoid overly long kill ring
(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)。
当两个条件同时满足时结果为真:删除环不为空,
且 replace 变量为真。
当 kill-append 函数将 replace 设为真,
且删除环中至少有一项内容时,会执行 setcar 表达式:
(setcar kill-ring string)
setcar 函数会将 kill-ring 列表的
第一个元素实际修改为 string 的值,直接替换第一个元素。
反之,如果删除环为空,或者 replace 为假, 则执行条件的 else 分支:
(push string kill-ring)
push 将第一个参数添加到第二个参数的头部。
它与较早的写法类似:
(setq kill-ring (cons string kill-ring))
也与较新的写法类似:
(add-to-list kill-ring string)
条件为假时,表达式首先会将 string
作为新元素添加到现有删除环头部,构造一个新的删除环
(这就是 push 的作用)。
随后执行第二个 if 语句,
用于防止删除环变得过长。
我们按顺序查看这两个表达式。
else 分支中的 push 语句
将待删除字符串添加到旧删除环中,
并将结果设为删除环的新值。
我们可以通过示例理解其工作方式。
首先执行:
(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 语句的第二部分。
该表达式用于限制删除环的最大长度,代码如下:
(if (> (length kill-ring) kill-ring-max)
(setcdr (nthcdr (1- kill-ring-max) kill-ring) nil))
代码会检查删除环长度是否超出允许的最大值,
该值由 kill-ring-max 指定(默认为 120)。
如果删除环过长,代码会将其最后一个元素设为 nil。
它通过两个函数实现:nthcdr 和 setcdr。
我们之前已经了解过 setcdr(see setcdr)。
它用于设置列表的 CDR,
就像 setcar 用于设置列表的 CAR。
不过在本例中,setcdr 并非设置整个删除环的 CDR;
nthcdr 函数使其作用于删除环倒数第二个元素的 CDR —
这意味着,由于倒数第二个元素的 CDR 就是删除环的最后一个元素,
该操作会设置删除环的最后一个元素。
nthcdr 函数会反复获取列表的 CDR —
对 CDR 的 CDR 再取 CDR …
重复执行 N 次后返回结果。
(See nthcdr.)
因此,如果我们有一个四元素列表,
但只需要保留三个元素,
就可以将倒数第二个元素的 CDR 设为 nil,
从而缩短列表。
(如果你将最后一个元素设为非 nil 的其他值,
列表并不会被缩短。See setcdr。)
你可以依次对下面三个表达式求值,直观观察列表缩短效果。
首先将 trees 设为 (maple oak pine birch),
然后将其第二个 CDR 的 CDR 设为 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 内容。)
再重申一次:在 kill-new 中,
nthcdr 会按删除环最大长度减一的次数获取 CDR,
然后 setcdr 将该元素的 CDR
(即删除环中后续的所有元素)设为 nil,
从而避免删除环无限增长。
kill-new 函数中倒数第二个表达式为:
(setq kill-ring-yank-pointer kill-ring)
kill-ring-yank-pointer 是一个全局变量,
被设置为指向 kill-ring。
尽管 kill-ring-yank-pointer 被称为“指针”,
它本质上和删除环一样都是变量。
不过这个命名是为了帮助人类理解其用途。
现在回到函数体开头的表达式:
(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 函数就会返回真。
如果该符号的函数定义为空,我们就会收到错误提示,
就像之前故意制造错误时一样(see Generate an Error Message)。
then 分支包含一个以 and 函数为首个元素的表达式。
and 特殊形式会依次对每个参数求值,
直到某个参数返回 nil,
此时整个 and 表达式返回 nil;
如果所有参数都不返回 nil,
则返回最后一个参数的求值结果。
(在 Emacs Lisp 中,非 nil 的值均视为真。)
换句话说,and 表达式只有在所有参数都为真时才返回真值。
(See 本章小结.)
该表达式用于确定
传递给 menu-bar-update-yank-menu 的第二个参数是否为真。
menu-bar-update-yank-menu 是支持菜单栏中
“编辑(Edit)”项里 “选择并粘贴(Select and Paste)” 菜单的函数之一;
你可以通过鼠标查看已保存的各段文本,
并选择一段进行粘贴。
kill-new 函数的最后一个表达式
会将新复制的字符串同步到窗口系统中
用于程序间复制粘贴的机制中。
例如在 X 窗口系统中,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 能够与其他程序良好协同工作的机制即可。
这段将文本加入删除环的代码(无论是拼接至现有元素还是作为新元素), 将引出用于取回从缓冲区剪切文本的代码 — 即取回命令。 不过在讨论取回命令之前, 最好先了解列表在计算机中的实现方式, 这能澄清 “指针(pointer)” 等术语的内在含义。 但在此之前,我们先穿插介绍一下 C 语言相关内容。