Spec-Zone.ru › Elisp

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

Из предыдущих подразделов мы уже знаем, что 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, изменив их порядок или добавив действия, которых там нет, но которые лучше соответствуют привычкам пользователя при редактировании. Однако его также можно использовать для более существенного изменения поведения по умолчанию.

Рассмотрим пользователя, который обычно предпочитает отображать буферы в другом фрейме. Такой пользователь может настроить Emacs следующим образом:

(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 будет полностью проигнорирован.

Обратите внимание, что в нашей настройке display-buffer-alist мы не стали указывать запись списка действий reusable-frames. 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 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-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*")))

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

Copyright © 1990-1996, 1998-2026 Free Software Foundation, Inc.
Licensed under the GNU GPL license.
https://www.gnu.org/software/emacs/manual/html_node/elisp/Precedence-of-Action-Functions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API