最简单的情况下,一个框架始终只容纳一个单独的窗口,可用于显示缓冲区。因此,最后一次调用的 display-buffer 总会成功将其缓冲区放置在该窗口中。
由于只使用这样的框架并不实用,Emacs 默认允许更复杂的框架布局,由框架尺寸默认值以及 split-height-threshold 和 split-width-threshold 选项控制。此时显示一个尚未在框架中出现的缓冲区,要么分割框架上的唯一窗口,要么复用已有的两个窗口之一。
一旦用户自定义了这些阈值,或手动修改了框架布局,默认行为就会失效。当使用非 nil 的 action 参数调用 display-buffer,或用户自定义了前面小节提到的任一选项时,默认行为同样会失效。由于可用的显示动作繁多,最终产生的框架布局也多种多样,熟练掌握 display-buffer 很容易变成一件令人沮丧的事情。
但是,放弃使用缓冲区显示函数,退回到手动分割、删除窗口的方式也并非良策。缓冲区显示函数为 Lisp 程序和用户提供了协调不同需求的框架,而单纯分割删除窗口并没有对应的机制。缓冲区显示函数还能在后续移除缓冲区时,至少部分恢复框架的布局 (see 退出窗口)。
下面我们给出若干指导原则,以缓解上述困扰,避免缓冲区在框架的多个窗口之间“消失不见”。
编写显示动作有时很麻烦,因为必须把动作函数和动作关联列表塞进同一个大列表中(历史原因导致 display-buffer 无法为它们提供独立参数)。记住下面几种基本形式会很有帮助:
'(nil (inhibit-same-window . t))
只指定动作关联列表条目,不指定动作函数。它唯一的作用是阻止其他地方指定的 display-buffer-same-window 在当前窗口显示缓冲区,参见上一小节最后一个示例。
'(display-buffer-below-selected)
与之相反,这段代码只指定一个动作函数,动作关联列表为空。 要合并以上两种效果,可以写成:
'(display-buffer-below-selected (inhibit-same-window . t))
要再添加一个动作函数,可以写成:
'((display-buffer-below-selected display-buffer-at-bottom) (inhibit-same-window . t))
要再添加一个关联列表条目,可以写成:
'((display-buffer-below-selected display-buffer-at-bottom) (inhibit-same-window . t) (window-height . fit-window-to-buffer))
最后这种形式可以作为 display-buffer 的 action 参数使用,示例如下:
(display-buffer (get-buffer-create "*foo*") '((display-buffer-below-selected display-buffer-at-bottom) (inhibit-same-window . t) (window-height . fit-window-to-buffer)))
在自定义 display-buffer-alist 时可以这样使用:
(setopt
display-buffer-alist
'(("\\*foo\\*"
(display-buffer-below-selected display-buffer-at-bottom)
(inhibit-same-window . t)
(window-height . fit-window-to-buffer))))
要为第二个缓冲区添加自定义规则,可以继续写成:
(setopt
display-buffer-alist
'(("\\*foo\\*"
(display-buffer-below-selected display-buffer-at-bottom)
(inhibit-same-window . t)
(window-height . fit-window-to-buffer))
("\\*bar\\*"
(display-buffer-reuse-window display-buffer-pop-up-frame)
(reusable-frames . visible))))
display-buffer-alist 和 display-buffer-base-action 是用户选项,Lisp 程序绝不应该设置或重新绑定它们。而 display-buffer-overriding-action 则是为应用程序保留的,应用程序极少使用,即便使用也必须极为谨慎。
旧版 display-buffer 经常导致用户和应用程序在 pop-up-frames、pop-up-windows 等用户选项上产生冲突 (see 显示缓冲区的附加选项)。这也是重新设计 display-buffer 的主要原因之一:提供一套清晰的规则,明确用户和应用程序各自的权限。
Lisp 程序必须做好准备:用户的自定义设置可能会让缓冲区以意料之外的方式显示。程序在后续行为中绝不应该假设缓冲区一定按照 display-buffer 的 action 参数所要求的方式精确显示。
用户也不应对任意缓冲区的显示施加过多、过严苛的限制,否则可能会丢失特定用途下的显示特性。假设某个 Lisp 程序用于在两个窗口中并排对比缓冲区的不同版本,而用户的 display-buffer-alist 规则强制所有此类缓冲区必须在选中窗口内部或下方显示,那么该程序就很难通过 display-buffer 搭建出期望的窗口布局。
若要指定通用缓冲区的显示偏好,用户应自定义 display-buffer-base-action。上一小节已经给出偏好多框架用户的设置示例。display-buffer-alist 则应专门用于对特定缓冲区指定特定显示方式。
通常来说,无论用户还是 Lisp 程序员,都应优先考虑复用已经显示目标缓冲区的窗口。上一小节已经说明,如果没有正确做到这一点,即使已有框架显示该缓冲区,display-buffer 仍可能不断弹出新框架。只有少数情况不适合复用窗口,例如需要在该窗口显示缓冲区的另一部分内容时。
因此,display-buffer-reuse-window 是一个应尽可能多用的动作函数,无论是在 action 参数中还是自定义设置里。action 参数中的 inhibit-same-window 条目通常可以处理最常见的例外情况—避免复用当前选中的窗口。
对于使用多框架的用户来说这很简单:显示缓冲区的框架会自动前置并获得焦点,除非 inhibit-switch-frame 条目禁止这一行为。但对单框架用户来说,这一需求会困难得多。尤其是 display-buffer-pop-up-window 和 display-buffer-use-some-window,它们会分割或使用看似随机的窗口(通常是最大或最近最少使用的窗口),容易分散用户注意力。
因此一些 Lisp 程序会尝试选择框架底部的窗口,例如在迷你缓冲区附近显示缓冲区,方便用户对新窗口相关内容做出回应。对于非输入相关的操作,display-buffer-below-selected 往往更合适,因为选中窗口通常已经处于用户的关注范围内。
很多应用程序会在窗口切换环境中调用 display-buffer,例如在 with-selected-window 内部,或使用带非 nil norecord 参数的 select-window。这通常是个坏主意,因为在这种切换环境中选中的窗口,往往不是最终呈现给用户的选中窗口。
例如,如果用户添加了 inhibit-same-window 条目,它会阻止在切换环境内选中的窗口显示,而不是阻止最终配置中的选中窗口。即使没有添加该条目,行为也可能异常。
在只有一个活动窗口的框架中执行以下代码:
(progn (split-window) (display-buffer "*Messages*"))
会在底部显示 *Messages* 缓冲区,并保持另一个窗口为选中状态。 执行下一段代码:
(with-selected-window (split-window) (display-buffer "*Messages*"))
则会在顶部窗口显示 *Messages* 并选中该窗口,这通常不是 display-buffer 应有的行为。
另一方面,执行下面这段代码:
(progn (split-window) (pop-to-buffer "*Messages*"))
会正确选中 *Messages* 缓冲区, 但下一段代码:
(progn
(split-window)
(with-selected-window (selected-window)
(pop-to-buffer "*Messages*")))
则不会达到预期效果。
同时,像 display-buffer-use-some-window 和 display-buffer-use-least-recent-window 这类依赖选中窗口拥有最高使用时间的动作函数,也可能无法按预期选择窗口。
因此,如果应用程序需要使用窗口切换,应尽量将 display-buffer 调用推迟到窗口切换结束之后。