B.3 yank-pop

理解 yankcurrent-kill 之后,你就知道该如何分析 yank-pop 函数了。为节省篇幅省略文档字符串,代码如下:

(defun yank-pop (&optional arg)
  "..."
  (interactive "*p")
  (if (not (eq last-command 'yank))
      (error "Previous command was not a yank"))
  (setq this-command 'yank)
  (unless arg (setq arg 1))
  (let ((inhibit-read-only t)
        (before (< (point) (mark t))))
    (if before
        (funcall (or yank-undo-function 'delete-region) (point) (mark t))
      (funcall (or yank-undo-function 'delete-region) (mark t) (point)))
    (setq yank-undo-function nil)
    (set-marker (mark-marker) (point) (current-buffer))
    (insert-for-yank (current-kill arg))
    ;; 尽可能将窗口起始位置恢复到粘贴命令执行时的状态
    (set-window-start (selected-window) yank-window-start t)
    (if before
        ;; 效果类似交换光标与标记,但不激活标记
        ;; 即便命令循环会因插入文本而取消标记激活,
        ;; 避免激活依然更简洁    
        (goto-char (prog1 (mark t)
                     (set-marker (mark-marker)
                                 (point)
                                 (current-buffer))))))
  nil)

该函数为交互式函数,使用小写 ‘p’ 前缀参数,前缀参数会被处理并传入函数。该命令只能在之前执行过粘贴操作后使用,否则会抛出错误提示。该检查使用了 last-command 变量,其值由 yank 设置,相关内容会在其他地方讨论。(See copy-region-as-kill。)

let 子句会根据光标在标记之前还是之后,将 before 变量设为真或假,随后删除光标与标记之间的区域。该区域正是上一次粘贴插入的内容,即将被替换的文本。

funcall 将首个参数作为函数执行,并将剩余参数传入。首个参数是 or 表达式的返回值,剩余两个参数是上一次 yank 命令设置的光标与标记位置。

还有更多细节,但这已是最难理解的部分。