29.13.5 Приоритет функций действий

Из предыдущих подразделов мы уже знаем, что в display-buffer необходимо передать ряд действий отображения (см. Выбор окна для отображения буфера), чтобы отобразить буфер. В полностью ненастроенном Emacs эти действия задаются display-buffer-fallback-action в следующем порядке приоритета: повторно использовать окно, открыть новое окно в том же фрейме, использовать окно, ранее показывавшее буфер, использовать некоторое окно и открыть новый фрейм. (Обратите внимание, что остальные действия, указанные в display-buffer-fallback-action, в ненастроенном Emacs недействительны).

Рассмотрим следующую форму:

(display-buffer (get-buffer-create "*foo*"))

Вычисление этой формы в буфере *scratch* ненастроенного сеанса Emacs обычно не сможет повторно использовать окно, которое уже показывает *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* в том же нижнем окне, потому что это окно ранее уже показывало *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 сначала попытаться найти окно, показывающее буфер на видимом или свёрнутом в иконку фрейме, а если такого фрейма нет, открыть новый фрейм. Вы можете наблюдать это поведение в графической системе, нажав C-x 1 в окне, показывающем *scratch*, и вычислив нашу каноническую форму 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-fallback-action, в тот момент display-buffer-pop-up-frame уже выиграл бы гонку. Фактически, это:

(setopt
 display-buffer-base-action
 '(display-buffer-pop-up-frame (reusable-frames . 0)))

заставило бы display-buffer всегда открывать новый фрейм, что, вероятно, не соответствует желанию нашего пользователя.

До сих пор мы показывали, как пользователи могут настраивать поведение display-buffer по умолчанию. Теперь посмотрим, как приложения могут изменить ход выполнения display-buffer. Канонический способ сделать это — использовать аргумент action display-buffer или функции, которая его вызывает, например, pop-to-buffer (см. Переключение на буфер в окне).

Предположим, приложение хочет отобразить *foo* предпочтительно под выбранным окном (чтобы сразу привлечь внимание пользователя к новому окну) или, если это не удастся, в окне в нижней части фрейма. Оно может сделать это с помощью такого вызова:

(display-buffer
 (get-buffer-create "*foo*")
 '((display-buffer-below-selected display-buffer-at-bottom)))

Чтобы увидеть, как работает эта новая, изменённая форма, удалите любой фрейм, показывающий *foo*, нажмите C-x 1, затем C-x 2 в окне, показывающем *scratch*, и затем вычислите эту форму. display-buffer должно разделить верхнее окно и показать *foo* в новом окне. Альтернативно, если после C-x 2 вы нажали C-x o, display-buffer разделило бы окно внизу.

Предположим теперь, что перед вычислением новой формы вы сделали выбранное окно настолько маленьким, насколько это возможно, например, вычислив форму (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* отобразится на отдельном фрейме, полностью игнорируя аргумент action display-buffer.

Обратите внимание, что мы не позаботились указать запись alist действий reusable-frames в нашей спецификации display-buffer-alist. display-buffer всегда берёт первую найденную — в нашем случае ту, что указана display-buffer-base-action. Если бы мы хотели использовать другую спецификацию, например, исключить свёрнутые в иконку фреймы, показывающие *foo*, из списка повторно используемых, нам пришлось бы указать это отдельно:

(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 в двух основных аспектах: она сильнее, поскольку переопределяет аргумент action display-buffer, и она позволяет вам явно указывать затрагиваемые буферы. Фактически, отображение других буферов никак не затрагивается настройкой для *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
 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.

Alist действий, передаваемый при каждом вызове функции:

((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))))

В этом случае запись alist inhibit-same-window успешно аннулирует спецификацию display-buffer-same-window из display-buffer-overriding-action, и 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*")))

Этот последний пример показывает, что хотя порядок приоритета функций действий фиксирован, как описано в Выбор окна для отображения буфера, запись alist действий, указанная действием отображения, занимающим более низкое место в этом порядке, может повлиять на выполнение действия отображения с более высоким приоритетом.