Czy jest jakiś sposób, aby uruchomić funkcję hook tylko raz?

15

Kontekst

Korzystam z after-make-frame-functionshaka, aby poprawnie załadować motywy w konfiguracji klient / serwer emacs . W szczególności jest to fragment kodu, którego używam do tego (na podstawie tej odpowiedzi 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)))

Problem

Po uruchomieniu nowej emacsclient -c/tsesji zaczep jest wykonywany nie tylko w nowej ramce, ale we wszystkich poprzednich istniejących ramkach (inne sesje emacsclient ) i daje bardzo irytujący efekt wizualny (motywy są ładowane ponownie we wszystkich tych ramkach) . Co gorsza, w terminalu klienci, którzy już otworzyli, kolor motywu jest całkowicie pomieszany. Oczywiście dzieje się tak tylko na klientach emacs podłączonych do tego samego serwera emacs. Przyczyna tego zachowania jest jasna, hak jest uruchamiany na serwerze i dotyczy to wszystkich jego klientów.

Pytanie

Czy jest jakiś sposób na wykonanie tej funkcji tylko raz lub uzyskanie tego samego wyniku bez użycia haka?


Częściowe rozwiązanie

Mam teraz ten kod, dzięki odpowiedzi @ Drew. Ale nadal ma problem, gdy rozpoczniesz sesję klienta w terminalu, GUI nie ładuje poprawnie motywów i odwrotnie. Po wielu testach zdałem sobie sprawę, że zachowanie zależy od tego, który emacsclient zaczyna się jako pierwszy, a po odrzuceniu różnych rzeczy myślę, że może być związany z załadowaną paletą kolorów. Jeśli przeładujesz ręcznie kompozycję, wszystko działa dobrze i dlatego to zachowanie nie pojawia się, gdy funkcja jest wywoływana przez hak za każdym razem, jak w mojej początkowej konfiguracji.

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

Ostateczne rozwiązanie

Wreszcie mam całkowicie działający kod, który rozwiązuje zachowanie widoczne w rozwiązaniu częściowym. Aby to osiągnąć, uruchamiam funkcję jeden raz w trybie (terminal lub GUI), kiedy po raz pierwszy uruchamiany jest odpowiedni emacsclient, a następnie usuwam funkcję z zaczepu, ponieważ jest nie jest już potrzebny. Teraz jestem szczęśliwy! :) Jeszcze raz dziękuję @Drew!

Kod:

(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)))
Joe di Castro
źródło
1
Zredagowałem tytuł zgodnie z sugestią. Proszę wycofać się, jeśli nie jest to pierwotnie to, co zamierzałeś.
Malabarba
W porządku @Malabarba! Zgadzam się z @drew
Di

Odpowiedzi:

11

Zgaduję, że tak naprawdę nie szukasz sposobu na „ wykonanie haka tylko raz ”. Zgaduję, że szukasz sposobu na wykonanie tej konkretnej funkcji tylko raz, za każdym razem, gdy uruchamiany jest hak.

Konwencjonalna i prosta odpowiedź na to pytanie polega na tym, że Twoja funkcja usuwa się z zaczepu po wykonaniu jednorazowego działania, które chcesz. Innymi słowy, użyj add-hookw kontekście, w którym wiesz, że funkcja powinna zostać wykonana po uruchomieniu haka, i niech sama funkcja zdejmie się z haka, gdy funkcja zrobi swoje.

Jeśli mam zgadywać prawidłowo, co naprawdę chcesz, to proszę rozważyć edycji pytanie coś takiego jak " Czy istnieje jakiś sposób, aby uruchomić hak funkcję tylko raz?

Oto przykład ze standardowej biblioteki 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))
Rysował
źródło
Tak, może działać w ten sposób i przypuszczam, że byłoby to mniej problematyczne i podatne na błędy rozwiązanie. Dzięki.
Joe di Castro
3
+1. Nie zaszkodzi mieć 3-liniowy przykład funkcji, która usuwa się z zaczepu.
Malabarba
Wreszcie mam kompletne działające rozwiązanie częściowo oparte na twojej odpowiedzi (na końcu zdałem sobie sprawę, że mogę to zrobić bez usuwania funkcji z zaczepu). Dziękuję Ci bardzo!
Joe di Castro,
3

Oto makro, którego możesz użyć zamiast add-hook(nie jest dokładnie testowany):

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

Uwaga: make-symboltworzy nienaruszony symbol o podanej nazwie. W #nazwie umieściłem symbol, aby oznaczyć symbol jako nieco nietypowy, na wypadek, gdybyś go spotkał patrząc na zmienną hook.

Harald Hanche-Olsen
źródło
To nie działa dla mnie, generuje ten błąd:(void-function gensym)
Joe Di Castro
@joedicastro Ach, tak, to jest z clpaczki. Przepraszam za to - zapominam, że nie wszyscy go używają. Zamiast tego możesz użyć (make-symbol "#once"). Zaktualizuję odpowiedź.
Harald Hanche-Olsen
1
Spróbowałem ponownie i nie pracowałem dla siebie. Szczerze mówiąc, ponieważ miałem częściowo działające rozwiązanie od Drew, szukam bardziej obiecującej ścieżki. W każdym razie dziękuję za odpowiedź!
Joe di Castro,
@joedicastro: To oczywiście twoja decyzja i rzeczywiście rozwiązanie Drew będzie działać. I to jest konwencjonalny sposób na zrobienie tego. Główną wadą jest konieczność zakodowania na stałe nazwy haka w funkcji haka, co utrudnia ponowne użycie funkcji w więcej niż jednym haku, jeśli to konieczne. Ponadto, jeśli kopiujesz rozwiązanie do użycia w innym kontekście, musisz pamiętać o edycji nazwy haka. Moje rozwiązanie przełamuje zależność, pozwalając ci na ponowne użycie elementów i przenoszenie ich do woli. Jestem ciekawy, dlaczego to nie działa, ale jeśli…
Harald Hanche-Olsen,
… Ale jeśli wolisz nie poświęcać czasu, aby dojść do sedna, rozumiem całkowicie. Życie jest krótkie - idź z tym, co Ci odpowiada.
Harald Hanche-Olsen,
0

możesz wykonać funkcję hiper, gen-onceaby przekształcić normalną funkcję w funkcję, która może działać tylko raz:

(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

następnie użyj (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

chendianbuji
źródło