Есть ли способ запустить функцию ловушки только один раз?

15

Контекст

Я использую after-make-frame-functionsхук для правильной загрузки тем в конфигурации клиент / сервер emacs . В частности, это фрагмент кода, который я использую для этого (на основе этого SO ответа ):

 (if (daemonp)
      (add-hook 'after-make-frame-functions
          (lambda (frame)
              (select-frame frame)
              (load-theme 'monokai t)
              ;; setup the smart-mode-line and its theme
              (sml/setup))) 
      (progn (load-theme 'monokai t)
             (sml/setup)))

Проблема

Когда начинается новый emacsclient -c/tсеанс, ловушка выполняется не только в новом кадре, но и во всех предыдущих существующих кадрах (другие сеансы emacsclient ), и это создает очень раздражающий визуальный эффект (темы снова загружаются во все эти кадры) . Хуже того, в терминале клиенты уже открыли тему, цвет которой полностью испортился. Очевидно, что это происходит только на клиентах emacs, подключенных к тому же серверу emacs. Причина такого поведения ясна: ловушка запускается на сервере и затрагиваются все его клиенты.

Вопрос

Есть ли способ выполнить эту функцию только один раз или получить тот же результат без использования ловушки?


Частичное решение

Теперь у меня есть этот код, благодаря ответу @ Drew. Но все же есть проблема: после запуска клиентского сеанса в терминале графический интерфейс не загружает темы должным образом и наоборот. После многих испытаний я понял, что поведение зависит от того, какой emacsclient запускается первым, и после отбрасывания различных вещей, я думаю, что это может быть связано с загруженной цветовой палитрой. Если вы перезагружаете вручную тему, все работает нормально, и это причина, почему это поведение не появляется, когда функция вызывается ловушкой каждый раз, как в моей начальной конфигурации.

(defun emacsclient-setup-theme-function (frame)
  (progn
    (select-frame frame)
    (load-theme 'monokai t)
    ;; setup the smart-mode-line and its theme
    (sml/setup)
    (remove-hook 'after-make-frame-functions 'emacsclient-setup-theme-function)))

(if (daemonp)
    (add-hook 'after-make-frame-functions 'emacsclient-setup-theme-function)
    (progn (load-theme 'monokai t)
           (sml/setup)))

Окончательное решение

Наконец, у меня есть полностью рабочий код, который решает поведение, наблюдаемое в частичном решении, для достижения этого я запускаю функцию один раз в режиме (терминал или графический интерфейс), когда затем запускается соответствующий emacsclient, затем удаляю функцию из ловушки, потому что больше не нужно. Теперь я счастлив! :) Еще раз спасибо @Drew!

Код:

(setq myGraphicModeHash (make-hash-table :test 'equal :size 2))
(puthash "gui" t myGraphicModeHash)
(puthash "term" t myGraphicModeHash)

(defun emacsclient-setup-theme-function (frame)
  (let ((gui (gethash "gui" myGraphicModeHash))
        (ter (gethash "term" myGraphicModeHash)))
    (progn
      (select-frame frame)
      (when (or gui ter) 
        (progn
          (load-theme 'monokai t)
          ;; setup the smart-mode-line and its theme
          (sml/setup)
          (sml/apply-theme 'dark)
          (if (display-graphic-p)
              (puthash "gui" nil myGraphicModeHash)
            (puthash "term" nil myGraphicModeHash))))
      (when (not (and gui ter))
        (remove-hook 'after-make-frame-functions 'emacsclient-setup-theme-function)))))

(if (daemonp)
    (add-hook 'after-make-frame-functions 'emacsclient-setup-theme-function)
  (progn (load-theme 'monokai t)
         (sml/setup)))
Джо ди Кастро
источник
1
Я отредактировал название в соответствии с предложением. Пожалуйста, не стесняйтесь откатить, если это не то, что вы изначально имели в виду.
Малабарба
Все в порядке @Malabarba! Я согласен с @drew
Джо ди Кастро

Ответы:

11

Я предполагаю, что вы на самом деле не ищете способ « выполнить хук только один раз ». Я предполагаю, что вы ищете способ выполнить эту конкретную функцию только один раз, когда запускается ловушка.

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

Если я правильно о том, что вы действительно хотите, то , пожалуйста , рассмотреть вопрос редактирования , чтобы что - то вроде угадывания " Есть ли способ запустить крючок функцию только один раз?

Вот пример из стандартной библиотеки facemenu.el:

(defun facemenu-set-self-insert-face (face)
  "Arrange for the next self-inserted char to have face `face'."
  (setq facemenu-self-insert-data (cons face this-command))
  (add-hook 'post-self-insert-hook 'facemenu-post-self-insert-function))

(defun facemenu-post-self-insert-function ()
  (when (and (car facemenu-self-insert-data)
             (eq last-command (cdr facemenu-self-insert-data)))
    (put-text-property (1- (point)) (point)
                       'face (car facemenu-self-insert-data))
    (setq facemenu-self-insert-data nil))
  (remove-hook 'post-self-insert-hook 'facemenu-post-self-insert-function))
Нарисовалась
источник
Да, это может работать таким образом, и я полагаю, что это будет менее проблематичным и подверженным ошибкам решением. Благодарю.
Джо ди Кастро
3
+1. Не мешало бы иметь трехстрочный пример функции, которая удаляет себя из ловушки.
Малабарба
Наконец, у меня есть полностью рабочее решение, частично основанное на вашем ответе (в конце я понял, что могу расставить точки без снятия функции с крючка). Большое спасибо!
Джо ди Кастро
3

Вот макрос, который вы можете использовать вместо add-hook(не всесторонне протестированный):

(defmacro add-hook-run-once (hook function &optional append local)
  "Like add-hook, but remove the hook after it is called"
  (let ((sym (make-symbol "#once")))
    `(progn
       (defun ,sym ()
         (remove-hook ,hook ',sym ,local)
         (funcall ,function))
       (add-hook ,hook ',sym ,append ,local))))

Примечание: make-symbolсоздает непостоянный символ с заданным именем. Я включил #в имя символ, чтобы пометить символ как несколько необычный, на случай, если вы встретите его, глядя на переменную хука.

Харальд Ханче-Олсен
источник
Это не работает для меня, он выдает эту ошибку:(void-function gensym)
Джо ди Кастро
@joedicastro Ах, да, это из clпакета. Извините за это - я забыл, что не все используют это. Вы можете использовать (make-symbol "#once")вместо этого. Я обновлю ответ.
Харальд Ханче-Олсен
1
Я попробовал еще раз, и у меня ничего не вышло, и, честно говоря, поскольку у меня было частичное рабочее решение от Дрю, я вместо этого ищу более многообещающий путь. В любом случае спасибо за ответ!
Джо ди Кастро
@joedicastro: Это ваше решение, конечно, и действительно решение Дрю будет работать. И это обычный способ сделать это. Основным недостатком является необходимость жесткого кодирования имени ловушки в функции ловушки, что затрудняет повторное использование функции в нескольких ловушках, если это необходимо. Плюс, если вы обнаружите, что копируете решение для использования в другом контексте, вы должны помнить, что нужно также отредактировать имя хука. Мое решение разрушает зависимость, позволяя вам повторно использовать части и перемещать их по желанию. Мне любопытно, почему это не работает для вас, но если…
Харальд Ханче-Олсен
... но если вы не хотите тратить время на то, чтобы докопаться до сути, я полностью понимаю. Жизнь коротка - иди с тем, что работает для вас.
Харальд Ханче-Олсен
0

Вы можете создать гиперфункцию gen-onceдля преобразования нормальной функции в функцию, которая может выполняться только один раз:

(setq lexical-binding t)

(defun gen-once (fn)
  (let ((done nil))
    (lambda () (if (not done)
                   (progn (setq done t)
                          (funcall fn))))))
(setq test (gen-once (lambda () (message "runs"))))

;;;;---following is test----;;;;
((lambda () (funcall test))) ;; first time, message "runs"
((lambda () (funcall test))) ;; nil
((lambda () (funcall test))) ;; nil

затем используйте (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

chendianbuji
источник