函数 window-prev-buffers 的返回值会自动改变吗?

Consult 有一个 issue

里面有人贴出了一段代码:

(defun consult-buffer-hack (&rest args)
    (if (eq consult--buffer-display #'switch-to-buffer)
        (let* ((window (selected-window))
               (orig-buffer (current-buffer))
               (orig-buffer-window-info (list orig-buffer (set-marker (make-marker) (window-start)) (point-marker)))
               (prev-buffers (window-prev-buffers window))
               (next-buffers (window-next-buffers window))
               (unwind (lambda ()

                         (print prev-buffers) ;; second print

                         (set-window-prev-buffers window prev-buffers)
                         (set-window-next-buffers window next-buffers))))

          (print prev-buffers) ;; first print

          (add-hook 'minibuffer-exit-hook unwind)
          (unwind-protect
              (apply args)
            (remove-hook 'minibuffer-exit-hook unwind))

          (setq new-buffer (window-buffer window))
          (unless (eq orig-buffer new-buffer)
            (setq tmp-prev-buffer (assoc-delete-all orig-buffer prev-buffers))
            (set-window-prev-buffers window (add-to-list 'tmp-prev-buffer orig-buffer-window-info))))
      (apply args)))

  (advice-add 'consult-buffer :around 'consult-buffer-hack)

我在里面增加了两个 print ,我发现运行 consult-buffer 后再随便进行一些搜索,然后再按 C-g 退出,两次 print 的内容会不一样。同一个变量,也没有什么地方显示改变它,为什么它的值会发生变化呢?

你的例子可简化为:

(let ((prev-buffers (window-prev-buffers)))
  (print (seq-take prev-buffers 3))
  (switch-to-buffer "*Messages*")
  (print (seq-take prev-buffers 3)) ;; <--- 结果已改变
  nil)
;; =>
;; (#<buffer *test*> (#<buffer *scratch*> #<marker at 2244 in *scratch*> #<marker at 2726 in *scratch*>) (#<buffer *test*> #<marker at 1 in *test*> #<marker at 1 in *test*>))
;; (#<buffer *test*> (#<buffer *test*> #<marker at 1 in *test*> #<marker at 1 in *test*>) (#<buffer *Messages*> #<marker at 1 in *Messages*> #<marker at 1 in *Messages*>))

很显然局部变量 prev-buffers 改变了。

(window-prev-buffers) 返回的其实是一个“引用”,你甚至可手动修改它:

(let ((prev-buffers (window-prev-buffers)))
  (print (car prev-buffers))
  (setcar prev-buffers (get-buffer-create "*test*"))
  (print (car (window-prev-buffers))) ;; <--- 结果已改变
  nil)
;; =>
;; (#<buffer emacs-china.org/t/window-prev-buffers/20925> #<marker at 1 in emacs-china.org/t/window-prev-buffers/20925> #<marker at 83 in emacs-china.org/t/window-prev-buffers/20925>)
;; #<buffer *test*>

我看了一下 issue 原帖,里边抱怨的其实是改变之后的 buffers 排序不符合预期。

原来如此,第一次了解到 Emacs 函数返回“引用”这样的概念。

window-prev-buffers 的文档也只写着是 ”The return value is a list“ 。

它的类型,返回的也是 cons 。

ELISP> (type-of (window-prev-buffers))
cons

那请问这种情况应该怎么获取到引用的值而不是引用,

直接 -copy 行不行呢?

(let ((prev-buffers (-copy (window-prev-buffers))))

还有怎样避免这样的 “陷阱” 呢?

list 是 cons 的一个特例:

(cons 1 (cons 2 (cons 3 nil))) ;; => (1 2 3)

判断 list 是否为空,用 consp 比 listp 靠谱:

(listp nil)      ;; => t
(consp nil)      ;; => nil
(listp t)        ;; => nil
(consp t)        ;; => nil
(listp '(1 2 3)) ;; => t
(consp '(1 2 3)) ;; => t
(consp '(1 . 2)) ;; => t
(listp '(1 . 2)) ;; => t

行。