Чи є спосіб запустити функцію гачка лише один раз?


15

Контекст

Я використовую after-make-frame-functionsгачок для належного завантаження тем у конфігурації клієнта / сервера emacs . Зокрема, це фрагмент коду, який я використовую, щоб зробити це (виходячи з цієї відповіді ТАК ):

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

Остаточне рішення

Нарешті, у мене є повністю працюючий код, який вирішує поведінку, бачену в частковому рішенні, для досягнення цього я запускаю функцію один раз за режимом (термінал або gui), коли потім вперше запускається відповідний 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
joe di castro

Відповіді:


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

Так, це може працювати таким чином, і я вважаю, що це було б менш проблематичним та схильним до помилок рішенням. Спасибі.
joe di castro

3
+1. Не завадило б мати 3-рядковий приклад функції, яка видаляє себе з гачка.
Малабарба

Нарешті, я маю загальне робоче рішення, яке частково базується на вашій відповіді (наприкінці я зрозумів, що можу це зробити, не знімаючи функції з гачка). Велике спасибі!
joe di castro

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)
joe di castro

@joedicastro Ага, так, це з clпакета. Вибачте з цього приводу - я забуваю, що не всі ним користуються. Ви можете використовувати (make-symbol "#once")замість цього. Я оновлю відповідь.
Харальд Ханш-Олсен

1
Я спробував ще раз, і мені це не вийшло, і, чесно кажучи, у мене було часткове робоче рішення від Дрю, я шукаю замість цього більш перспективний шлях. Все одно дякую за вашу відповідь!
joe di castro

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.