22.11 Выход

Нажатие C-g во время выполнения функции на Lisp заставляет Emacs выйти из того, что он делает. Это означает, что управление возвращается в самый внутренний активный цикл команд.

Нажатие C-g, пока цикл команд ожидает ввод с клавиатуры, не вызывает выход; оно действует как обычный символ ввода. В простейшем случае вы не можете отличить, потому что C-g обычно выполняет команду keyboard-quit, эффект которой — выход. Однако когда C-g следует за префиксным ключом, они объединяются в неопределённый ключ. Эффект состоит в отмене как префиксного ключа, так и любого префиксного аргумента.

В минибуфере C-g имеет другое определение: он прерывает работу минибуфера. Это означает, по сути, что он выходит из минибуфера и затем выполняет выход. (Простой выход вернул бы управление в цикл команд внутри минибуфера.) Причина, по которой C-g не выполняет выход напрямую, когда считыватель команд читает ввод, заключается в том, чтобы его значение можно было переопределить в минибуфере таким образом. C-g после префиксного ключа не переопределяется в минибуфере и оказывает своё обычное действие — отмену префиксного ключа и префиксного аргумента. Это также было бы невозможно, если бы C-g всегда выполнял выход напрямую.

Когда C-g действительно выполняет прямой выход, он делает это, устанавливая переменную quit-flag в t. Emacs проверяет эту переменную в подходящие моменты и выполняет выход, если она не равна nil. Установка quit-flag в любое значение, отличное от nil, таким образом вызывает выход.

На уровне кода C выход не может произойти где угодно; только в тех особых местах, которые проверяют quit-flag. Причина в том, что выход в других местах мог бы оставить несогласованность во внутреннем состоянии Emacs. Поскольку выход откладывается до безопасного места, выход не может привести к краху Emacs.

Некоторые функции, такие как read-key-sequence или read-quoted-char, полностью предотвращают выход, даже если они ожидают ввод. Вместо выхода C-g служит запрашиваемым вводом. В случае read-key-sequence это служит для обеспечения особого поведения C-g в цикле команд. В случае read-quoted-char это сделано для того, чтобы C-q можно было использовать для экранирования C-g.

Вы можете предотвратить выход на части функции Lisp, привязав переменную inhibit-quit к значению, отличному от nil. Тогда, хотя C-g по-прежнему устанавливает quit-flag в t как обычно, обычный результат этого — выход — предотвращается. В конечном счёте, inhibit-quit снова станет nil, например, когда его привязка будет развёрнута в конце формы let. В этот момент, если quit-flag всё ещё не равен nil, запрошенный выход происходит немедленно. Такое поведение идеально, когда вы хотите гарантировать, что выход не произойдёт внутри критического участка программы.

В некоторых функциях (таких как read-quoted-char) C-g обрабатывается особым образом, не связанным с выходом. Это делается путём чтения ввода с inhibit-quit, привязанным к t, и установкой quit-flag в nil до того, как inhibit-quit снова станет nil. Этот фрагмент из определения read-quoted-char показывает, как это делается; он также показывает, что обычный выход разрешён после первого символа ввода.

(defun read-quoted-char (&optional prompt)
  "…документация…"
  (let ((message-log-max nil) done (first t) (code 0) char)
    (while (not done)
      (let ((inhibit-quit first)
            …)
        (and prompt (message "%s-" prompt))
        (setq char (read-event))
        (if inhibit-quit (setq quit-flag nil)))
      …установить переменную code…)
    code))
Переменная: quit-flag

Если эта переменная не равна nil, то Emacs немедленно выполняет выход, если только inhibit-quit не равен nil. Нажатие C-g обычно устанавливает quit-flag в значение, отличное от nil, независимо от inhibit-quit.

Переменная: inhibit-quit

Эта переменная определяет, должен ли Emacs выполнять выход, когда quit-flag установлен в значение, отличное от nil. Если inhibit-quit не равен nil, то quit-flag не имеет особого эффекта.

Макрос: with-local-quit body…

Этот макрос выполняет формы body последовательно, но позволяет выход, по крайней мере локально, внутри body, даже если inhibit-quit был не равен nil вне этой конструкции. Он возвращает значение последней формы в body, если только не произошёл выход, в котором случае он возвращает nil.

Если inhibit-quit равен nil на входе в with-local-quit, он просто выполняет body, и установка quit-flag вызывает обычный выход. Однако если inhibit-quit не равен nil, так что обычный выход задерживается, ненулевое значение quit-flag вызывает особый вид локального выхода. Это завершает выполнение body и выходит из тела with-local-quit с quit-flag, всё ещё не равным nil, так что другой (обычный) выход произойдёт, как только это будет разрешено. Если quit-flag уже не равен nil в начале body, локальный выход происходит немедленно, и тело не выполняется вовсе.

Этот макрос в основном полезен в функциях, которые могут вызываться из таймеров, фильтров процессов, сторожей процессов, pre-command-hook, post-command-hook и других мест, где inhibit-quit обычно привязан к t.

Команда: keyboard-quit

Эта функция генерирует условие quit с помощью (signal 'quit nil). Это то же самое, что делает выход. (См. signal в Ошибки.)

Чтобы выйти без прерывания определения или выполнения макроса клавиатуры, вы можете сгенерировать условие minibuffer-quit. Оно имеет почти тот же эффект, что и условие quit, за исключением того, что обработка ошибок в цикле команд обрабатывает его без выхода из определения или выполнения макроса клавиатуры.

Вы можете указать символ, отличный от C-g, для использования при выходе. См. функцию set-input-mode в Режимы ввода.