Esiste un modo per eseguire una funzione hook solo una volta?


15

Il contesto

Sto usando l' after-make-frame-functionshook per caricare correttamente i temi in una configurazione client / server emacs . In particolare questo è lo snippet di codice che utilizzo per farlo (basato su questa risposta 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)))

Il problema

Quando emacsclient -c/tviene avviata una nuova sessione, l'hook viene eseguito non solo nel nuovo frame, ma in tutti i frame esistenti precedenti (altre sessioni emacsclient ) e produce un effetto visivo molto fastidioso (i temi vengono caricati di nuovo in tutti quei frame) . Ancora peggio, nei terminali i client hanno già aperto il colore del tema che si incasina completamente. Ovviamente ciò accade solo sui client emacs collegati allo stesso server emacs. Il motivo di questo comportamento è chiaro, l'hook viene eseguito sul server e tutti i suoi client ne sono interessati.

La domanda

Esiste un modo per eseguire questa funzione una sola volta o ottenere lo stesso risultato senza usare l'hook?


Una soluzione parziale

Ora ho questo codice, grazie alla risposta di @ Drew. Ma ha ancora un problema, una volta avviata una sessione client nel terminale, la GUI non carica correttamente i temi e viceversa. Dopo molti test, mi sono reso conto che il comportamento dipende da quale emacsclient inizia per primo, e dopo aver scartato varie cose, penso che sia forse correlato alla tavolozza dei colori che viene caricata. Se ricarichi manualmente il tema funziona tutto bene e questo è il motivo per cui questo comportamento non appare quando la funzione viene chiamata dall'hook ogni volta come nella mia configurazione iniziale.

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

La soluzione finale

Finalmente ho un codice totalmente funzionante che risolve il comportamento visto nella soluzione parziale, per raggiungere questo obiettivo eseguo la funzione una volta per modalità (terminale o gui) quando viene quindi avviato emacsclient pertinente per la prima volta, quindi rimuovo la funzione dal gancio perché è non serve più. Ora sono felice! :) Grazie ancora @Drew!

Il codice:

(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
Ho modificato il titolo come suggerito. Per favore, sentiti libero di tornare indietro se non era quello che intendevi originariamente.
Malabarba,

Va bene @Malabarba! Concordo con @drew
joe di castro il

Risposte:


11

Immagino che tu non stia davvero cercando un modo per " eseguire il hook solo una volta ". Immagino che tu stia cercando un modo per eseguire quella particolare funzione una sola volta, ogni volta che viene eseguito l'hook.

La risposta convenzionale e semplice a questa domanda è che la tua funzione si rimuova dal gancio, dopo aver eseguito l'azione che desideri una volta sola. In altre parole, utilizzare add-hookin un contesto in cui si sa che la funzione deve essere eseguita quando viene eseguito l'hook e fare in modo che la funzione stessa si rimuova dall'hook, dopo che la funzione ha fatto la sua cosa.

Se sto indovinando correttamente ciò che vuoi davvero, ti preghiamo di considerare di modificare la tua domanda in qualcosa del tipo " Esiste un modo per eseguire una funzione hook solo una volta?

Ecco un esempio, dalla libreria standard 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))

Sì, può funzionare in questo modo e suppongo che sarebbe una soluzione meno problematica e soggetta a errori. Grazie.
joe di castro,

3
+1. Non sarebbe male avere un esempio a 3 righe di una funzione che si rimuove dal gancio.
Malabarba,

Finalmente ho una soluzione di lavoro totale parzialmente basata sulla tua risposta (alla fine mi sono reso conto che avrei potuto dotarla senza rimuovere la funzione dal gancio). Grazie mille!
joe di castro,

3

Ecco una macro che puoi usare invece di add-hook(non ampiamente testata):

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

Nota: make-symbolcrea un simbolo non modificato con il nome indicato. Ho incluso a #nel nome per contrassegnare il simbolo come alquanto insolito, nel caso in cui lo si incontri mentre si guarda una variabile hook.


Non funziona per me, genera questo errore:(void-function gensym)
joe di castro,

@joedicastro Ah, sì, è dal clpacchetto. Scusatemi - dimentico che non tutti lo usano. Puoi usare (make-symbol "#once")invece. Aggiornerò la risposta.
Harald Hanche-Olsen,

1
Ho provato di nuovo, e non ho funzionato per me, e onestamente, dato che avevo una parziale soluzione operativa di Drew, cerco invece quel percorso più promettente. Grazie comunque per la tua risposta!
joe di castro,

@joedicastro: Questa è la tua decisione, ovviamente, e in effetti la soluzione di Drew funzionerà. Ed è il modo convenzionale per farlo. Lo svantaggio principale è la necessità di codificare il nome del hook nella funzione hook, rendendo difficile il riutilizzo della funzione in più hook, se necessario. Inoltre, se ti ritrovi a copiare la soluzione per utilizzarla in un contesto diverso, devi ricordare di modificare anche il nome dell'hook. La mia soluzione rompe la dipendenza, permettendoti di riutilizzare i pezzi e spostarli a piacimento. Sono curioso di sapere perché non funziona per te, ma se ...
Harald Hanche-Olsen,

... ma se preferisci non prenderti il ​​tempo per arrivare a fondo, capisco perfettamente. La vita è breve - vai con ciò che funziona per te.
Harald Hanche-Olsen,

0

puoi creare una funzione iper gen-onceper trasferire una normale funzione a una funzione che può essere eseguita una sola volta:

(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

quindi, utilizzare (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.