通过前面几节我们已经知道,display-buffer 必须接收一组显示动作 (see 为显示缓冲区选择窗口) 才能显示缓冲区。在完全未定制的 Emacs 中,这些动作由 display-buffer-fallback-action 按以下优先级顺序指定:复用窗口、在当前框架弹出新窗口、使用之前显示过该缓冲区的窗口、使用任意窗口、弹出新框架。注意,display-buffer-fallback-action 中其余动作在未定制 Emacs 中无效。
考虑以下表达式:
(display-buffer (get-buffer-create "*foo*"))
在未定制 Emacs 的 *scratch* 缓冲区中执行该表达式,通常无法找到已显示 *foo* 的窗口,但会成功弹出新窗口。再次执行该表达式不会产生可见变化,因为 display-buffer 复用了已显示 *foo* 的窗口—该动作可用且在所有可用动作中优先级最高。
如果选中框架空间不足,弹出新窗口会失败。在未定制 Emacs 中,框架上已有两个窗口时通常会失败。例如,先按 C-x 1 再按 C-x 2,然后再次执行该表达式,*foo* 会出现在下方窗口,因为 display-buffer 使用了 “某个” 窗口。如果在按 C-x 2 之前先按 C-x o,则 *foo* 会显示在上方窗口,因为 “某个” 窗口指最近最少使用的窗口,而选中窗口只有在独占框架时才是最少使用的。
假设你没有按 C-x o,*foo* 显示在下方窗口。按 C-x o 进入该窗口,再按 C-x left,然后再次执行表达式,*foo* 会显示在同一个下方窗口中,因为该窗口之前已经显示过它,因此优先于其他窗口被选中。
到目前为止我们只观察了未定制 Emacs 的默认行为。要了解如何定制该行为,可以查看选项 display-buffer-base-action。它提供非常基础的定制,原则上会影响**所有**缓冲区的显示。你可以用它重新排列 display-buffer-fallback-action 中的动作顺序,或添加更符合编辑习惯的新动作。它也可以更彻底地改变默认行为。
假设有用户习惯在其他框架上显示缓冲区,可以进行如下设置:
(setopt display-buffer-base-action '((display-buffer-reuse-window display-buffer-pop-up-frame) (reusable-frames . 0)))
该设置会让 display-buffer 首先在可见或最小化框架上寻找显示该缓冲区的窗口,如果不存在则弹出新框架。在图形系统中,你可以在显示 *scratch* 的窗口中按 C-x 1,然后执行标准的 display-buffer 表达式,通常会创建一个新框架并将焦点移至该框架,其根窗口显示 *foo*。将该框架最小化后再次执行,display-buffer 会复用新框架上的窗口,通常会恢复框架并赋予焦点。
只有创建新框架失败时,display-buffer 才会应用 display-buffer-fallback-action 提供的动作,即再次尝试复用窗口、弹出新窗口等。一个简单的让创建框架失败的示例如下:
(let ((pop-up-frame-function 'ignore)) (display-buffer (get-buffer-create "*foo*")))
观察到它无法创建新框架并转而使用回退动作后,我们不再讨论该示例。
注意,display-buffer-reuse-window 在 display-buffer-base-action 的定制中看似多余,因为它已经属于 display-buffer-fallback-action,本应在那里被尝试。但实际会失效,因为 display-buffer-base-action 优先级更高,display-buffer-pop-up-frame 会优先生效。事实上下面的设置:
(setopt display-buffer-base-action '(display-buffer-pop-up-frame (reusable-frames . 0)))
会让 display-buffer **总是**弹出新框架,这通常不是用户想要的。
到目前为止我们只展示了 用户(users) 如何定制 display-buffer 的默认行为。下面看 应用程序(applications) 如何改变其行为。标准方式是使用 display-buffer 或其调用函数(如 pop-to-buffer)的 action 参数 (see 在窗口中切换到缓冲区)。
假设某个应用希望将 *foo* 优先显示在选中窗口下方,失败时则显示在框架底部,可以这样调用:
(display-buffer (get-buffer-create "*foo*") '((display-buffer-below-selected display-buffer-at-bottom)))
为观察该修改后的调用效果,先关闭所有显示 *foo* 的框架,在 *scratch* 窗口依次按 C-x 1、C-x 2,然后执行该表达式。display-buffer 会分割上方窗口并在新窗口显示 *foo*。如果在 C-x 2 之后按 C-x o,则会分割底部窗口。
如果在执行新表达式前,先将选中窗口缩到最小(例如执行 (fit-window-to-buffer)),则 display-buffer 无法分割选中窗口,转而分割框架根窗口,最终在框架底部显示 *foo*。
无论哪种情况,第二次执行该表达式都会复用已显示 *foo* 的窗口,因为 action 参数中的两个函数都会优先复用该窗口。
通过设置 action 参数,应用程序可以有效覆盖用户对 display-buffer-base-action 的任何定制。用户此时可以接受应用的选择,也可以通过自定义 display-buffer-alist 再次覆盖,例如:
(setopt
display-buffer-alist
'(("\\*foo\\*"
(display-buffer-reuse-window display-buffer-pop-up-frame))))
在没有任何窗口显示 *foo* 的配置下执行修改后的表达式,会在独立框架上显示 *foo*,完全忽略 display-buffer 的 action 参数。
注意我们在 display-buffer-alist 中没有指定 reusable-frames 动作列表条目。display-buffer 会使用找到的第一个条目,即 display-buffer-base-action 中指定的那个。如果希望使用不同设置,例如排除最小化框架上的窗口,则需要单独指定:
(setopt
display-buffer-alist
'(("\\*foo\\*"
(display-buffer-reuse-window display-buffer-pop-up-frame)
(reusable-frames . visible))))
测试后可以发现,只有当框架可见时,重复显示 *foo* 才会成功复用该框架。
上面的例子可能让人误以为用户定制 display-buffer-alist 只是为了覆盖应用程序指定的 action 参数。这种理解并不正确。display-buffer-alist 是用户控制特定缓冲区显示方式的标准选项,无论显示是否受 action 参数引导。
但我们可以合理地得出结论:定制 display-buffer-alist 与定制 display-buffer-base-action 主要有两点不同:它优先级更高,可以覆盖 display-buffer 的 action 参数;并且可以精确指定受影响的缓冲区。事实上,对 *foo* 的定制不会影响其他缓冲区的显示。例如:
(display-buffer (get-buffer-create "*bar*"))
仍然只受 display-buffer-base-action 和 display-buffer-fallback-action 控制。
我们可以在此结束示例,但 Lisp 程序仍有办法覆盖用户对 display-buffer-alist 的任何定制。这就是变量 display-buffer-overriding-action,可以在调用 display-buffer 时绑定它:
(let ((display-buffer-overriding-action
'((display-buffer-same-window))))
(display-buffer
(get-buffer-create "*foo*")
'((display-buffer-below-selected display-buffer-at-bottom))))
执行该表达式通常会在选中窗口显示 *foo*,无论 action 参数和用户定制如何。实际应用程序通常不会同时提供 action 参数,这里只是为了说明它会被覆盖。
观察在我们给出的定制下,display-buffer 为显示 *foo* 尝试的动作函数列表会很有启发。该列表(含注释说明各条目的添加方)如下:
(display-buffer-same-window ;; `display-buffer-overriding-action' display-buffer-reuse-window ;; `display-buffer-alist' display-buffer-pop-up-frame display-buffer-below-selected ;; ACTION argument display-buffer-at-bottom display-buffer-reuse-window ;; `display-buffer-base-action' display-buffer-pop-up-frame display-buffer--maybe-same-window ;; `display-buffer-fallback-action' display-buffer-reuse-window display-buffer--maybe-pop-up-frame-or-window display-buffer-in-previous-window display-buffer-use-some-window display-buffer-pop-up-frame)
注意这些内部函数中,display-buffer--maybe-same-window 实际上会被忽略,而 display-buffer--maybe-pop-up-frame-or-window 实际会运行 display-buffer-pop-up-window。
每次函数调用传入的动作列表为:
((reusable-frames . visible) (reusable-frames . 0))
这表明我们使用了上面第二个 display-buffer-alist 配置,覆盖了 display-buffer-base-action 中的设置。假设用户写成:
(setopt
display-buffer-alist
'(("\\*foo\\*"
(display-buffer-reuse-window display-buffer-pop-up-frame)
(inhibit-same-window . t)
(reusable-frames . visible))))
此时 inhibit-same-window 条目会使 display-buffer-overriding-action 中的 display-buffer-same-window 失效,display-buffer 会在其他框架上显示 *foo*。为让 display-buffer-overriding-action 更健壮,应用程序需要同时指定对应的 inhibit-same-window 条目,例如:
(let ((display-buffer-overriding-action
'(display-buffer-same-window (inhibit-same-window . nil))))
(display-buffer (get-buffer-create "*foo*")))
最后这个例子说明:虽然动作函数的优先级顺序固定(如 为显示缓冲区选择窗口 所述),但优先级较低的显示动作指定的动作列表条目,仍可能影响优先级更高的显示动作的执行。