kill-append 函数

kill-append 函数定义如下:

(defun kill-append (string before-p &optional yank-handler)
  "将 STRING 追加到删除环最新删除内容的末尾。
若 BEFORE-P 非 nil,则将 STRING 添加到前方。
... "
  (let* ((cur (car kill-ring)))
    (kill-new (if before-p (concat string cur) (concat cur string))
              (or (= (length cur) 0)
                  (equal yank-handler
                         (get-text-property 0 'yank-handler cur)))
              yank-handler)))

kill-append 的逻辑相当直观。 它使用了 kill-new 函数,我们稍后会详细说明。

(此外,该函数还提供了可选参数 yank-handler; 调用时该参数用于告知函数如何处理 文本附加的属性,如粗体、斜体等。)

它使用 let* 将删除环第一个元素赋值给 cur。 (我不确定此处为何不用 let; 表达式中只设置了一个变量。 这或许是一个不引发问题的小错误?)

看一下作为 kill-new 参数之一的条件表达式: 它使用 concat 将新文本与删除环的 CAR 拼接。 是向前添加还是向后追加,取决于 if 表达式:

(if before-p                            ; if-part
    (concat string cur)                 ; then-part
  (concat cur string))                  ; else-part

如果本次删除的选区位于上一次删除的选区之前, 就应该添加到之前保存的内容前面; 反之,如果本次删除的文本在后方, 就追加到之前文本的后面。 该 if 表达式依靠谓词 before-p 决定新保存的文本放在已有文本之前还是之后。

符号 before-pkill-append 的其中一个参数名。当 kill-append 被求值时, 它会绑定到实际参数的求值结果。 在本例中,该表达式为 (< end beg)。 该表达式并不直接判断本次删除的文本 位于上一次删除文本的前方还是后方, 而是判断变量 end 的值是否小于 beg。 如果成立,通常意味着用户正向缓冲区开头移动。 同时,谓词表达式 (< end beg) 的结果为真, 文本会被添加到之前文本的前面。 反之,如果 end 大于 beg, 文本就会追加到已有文本之后。

当新保存的文本需要向前添加时, 新文本字符串会拼接在旧文本之前:

(concat string cur)

但如果是向后追加,则拼接在旧文本之后:

(concat cur 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-append 调用 kill-new, 而后者内部又使用了 setcar 函数。