¿Hay alguna forma de ejecutar una función de enlace solo una vez?

15

El contexto

Estoy usando el after-make-frame-functionsgancho para cargar correctamente los temas en una configuración de cliente / servidor de emacs . Específicamente, este es el fragmento de código que uso para hacer eso (basado en esta respuesta 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)))

El problema

Cuando emacsclient -c/tse inicia una nueva sesión, el gancho se ejecuta no solo en el nuevo cuadro, sino en todos los cuadros anteriores existentes (otras sesiones de emacsclient ) y produce un efecto visual muy molesto (los temas se cargan nuevamente en todos esos cuadros) . Peor aún, en la terminal los clientes que ya abrieron el color del tema se desordenaron por completo. Obviamente, eso solo ocurre en los clientes de emacs conectados al mismo servidor de emacs. La razón de este comportamiento es clara, el enlace se ejecuta en el servidor y todos sus clientes se ven afectados.

La pregunta

¿Hay alguna forma de ejecutar esta función solo una vez u obtener el mismo resultado sin usar el gancho?


Una solución parcial

Ahora tengo este código, gracias a la respuesta de @ Drew. Pero aún tiene un problema, una vez que inicia una sesión de cliente en el terminal, la GUI no carga los temas correctamente y viceversa. Después de muchas pruebas, me di cuenta de que el comportamiento depende de qué cliente electrónico comienza primero, y después de descartar varias cosas, creo que tal vez esté relacionado con la paleta de colores que se carga. Si vuelve a cargar manualmente el tema, todo funciona bien y esa es la razón por la cual este comportamiento no aparece cuando el gancho llama a la función cada vez como en mi configuración inicial.

(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 solución definitiva

Finalmente, tengo un código totalmente funcional que resuelve el comportamiento visto en la solución parcial, para lograr esto ejecuto la función una vez por modo (terminal o gui) cuando se inicia emacsclient pertinente por primera vez, luego elimino la función del gancho porque es No se necesita más. ¡Ahora soy feliz! :) Gracias de nuevo @Drew!

El código:

(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
fuente
1
He editado el título como se sugiere. Por favor, siéntase libre de retroceder si no es lo que originalmente quiso decir.
Malabarba
¡Está bien @Malabarba! Estoy de acuerdo con @drew
joe di castro

Respuestas:

11

Supongo que realmente no estás buscando una forma de " ejecutar el gancho solo una vez ". Supongo que está buscando una manera de ejecutar esa función en particular solo una vez, cada vez que se ejecuta el gancho.

La respuesta convencional y simple a esa pregunta es que su función se retire del gancho, después de llevar a cabo la acción única que desea. En otras palabras, úselo add-hooken un contexto en el que sepa que la función debe ejecutarse cuando se ejecuta el gancho, y que la función misma se elimine del gancho, después de que la función haga lo suyo.

Si estoy adivinando correctamente lo que realmente quiere, considere editar su pregunta a algo como " ¿Hay alguna forma de ejecutar una función de enlace solo una vez?

Aquí hay un ejemplo, de la biblioteca estándar 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))
Dibujó
fuente
Sí, puede funcionar de esa manera, y supongo que sería una solución menos problemática y propensa a errores. Gracias.
joe di castro
3
+1. No estaría de más tener un ejemplo de 3 líneas de una función que se elimina del gancho.
Malabarba
Finalmente, tengo una solución de trabajo total parcialmente basada en su respuesta (al final me di cuenta de que podía puntearla sin quitar la función del gancho). ¡Muchas gracias!
joe di castro
3

Aquí hay una macro que puede usar en lugar de add-hook(no ampliamente probada):

(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 símbolo no intercalado con el nombre dado. Incluí un #en el nombre para marcar el símbolo como algo inusual, en caso de que lo encuentre mientras mira una variable de gancho.

Harald Hanche-Olsen
fuente
No funciona para mí, arroja este error:(void-function gensym)
joe di castro
@joedicastro Ah, sí, eso es del clpaquete. Lo siento, olvido que no todos lo usan. Puedes usar (make-symbol "#once")en su lugar. Actualizaré la respuesta.
Harald Hanche-Olsen
1
Lo intenté nuevamente, y no funcionó para mí, y sinceramente, ya que tenía una solución de trabajo parcial de Drew, busco ese camino más prometedor. Gracias de todos modos por tu respuesta!
joe di castro
@joedicastro: Esa es su decisión, por supuesto, y de hecho la solución de Drew funcionará. Y es la forma convencional de hacerlo. El principal inconveniente es la necesidad de codificar el nombre del gancho en la función de gancho, lo que dificulta la reutilización de la función en más de un gancho, si fuera necesario. Además, si se encuentra copiando la solución para usarla en un contexto diferente, debe recordar editar también el nombre del gancho. Mi solución rompe la dependencia, permitiéndole reutilizar las piezas y moverlas a voluntad. Tengo curiosidad por saber por qué no funciona para usted, pero si ...
Harald Hanche-Olsen
... pero si prefieres no tomarte el tiempo para llegar al fondo de eso, lo entiendo completamente. La vida es corta: ve con lo que funciona para ti.
Harald Hanche-Olsen
0

puedes hacer una hiperfunción gen-oncepara transmitir una función normal a una función que solo podría ejecutarse una vez:

(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

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

chendianbuji
fuente