Gibt es eine Möglichkeit, eine Hook-Funktion nur einmal auszuführen?

14

Der Kontext

Ich verwende den after-make-frame-functionsHaken, um die Themen in einer Emacs-Client / Server-Konfiguration richtig zu laden . Im Einzelnen ist dies das Code-Snippet, mit dem ich das mache (basierend auf dieser SO-Antwort ):

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

Das Problem

Wenn eine neue emacsclient -c/tSitzung gestartet wird, wird der Hook nicht nur in dem neuen Frame ausgeführt, sondern in allen vorherigen vorhandenen Frames (andere Emacsclient- Sitzungen), und es entsteht ein sehr ärgerlicher visueller Effekt (die Themes werden in all diesen Frames erneut geladen) . Schlimmer noch, in den bereits geöffneten Terminal-Clients wird das Thema Farbe komplett durcheinander gebracht. Dies geschieht natürlich nur auf den Emacs-Clients, die mit demselben Emacs-Server verbunden sind. Der Grund für dieses Verhalten ist klar, der Hook wird auf dem Server ausgeführt und alle seine Clients sind betroffen.

Die Frage

Gibt es eine Möglichkeit, diese Funktion nur einmal auszuführen oder dasselbe Ergebnis zu erhalten, ohne den Hook zu verwenden?


Eine Teillösung

Ich habe jetzt diesen Code, dank der Antwort von @ Drew. Wenn Sie jedoch eine Client-Sitzung im Terminal starten, wird die GUI die Designs nicht richtig laden und umgekehrt. Nach vielen Tests stellte ich fest, dass das Verhalten davon abhängt, welcher Emacsclient zuerst startet, und nach dem Verwerfen verschiedener Dinge denke ich, dass dies möglicherweise mit der geladenen Farbpalette zusammenhängt. Wenn Sie das Design manuell neu laden, funktioniert alles einwandfrei. Aus diesem Grund tritt dieses Verhalten nicht immer auf, wenn die Funktion wie in meiner ursprünglichen Konfiguration vom Hook aufgerufen wird.

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

Das finale Resultat

Schließlich habe ich voll funktionsfähigen Code, der das in der Teillösung beobachtete Verhalten behebt. Um dies zu erreichen, führe ich die Funktion einmal nach Modus (Terminal oder GUI) aus, wenn der betreffende Emacsclient zum ersten Mal gestartet wird, und entferne dann die Funktion aus dem Hook, weil nicht mehr benötigt. Jetzt bin ich glücklich! :) Nochmals vielen Dank @Drew!

Der Code:

(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
quelle
1
Ich habe den Titel wie vorgeschlagen bearbeitet. Bitte zögern Sie nicht, einen Rollback durchzuführen, wenn dies nicht das ist, was Sie ursprünglich gemeint haben.
Malabarba
Es ist in Ordnung, @Malabarba! Ich bin mit @drew
joe di castro

Antworten:

11

Ich vermute, dass Sie nicht wirklich nach einer Möglichkeit suchen, " den Hook nur einmal auszuführen ". Ich vermute, dass Sie nach einer Möglichkeit suchen, diese bestimmte Funktion nur einmal auszuführen, wenn der Hook ausgeführt wird.

Die konventionelle und einfache Antwort auf diese Frage lautet, dass Ihre Funktion sich selbst vom Haken löst, nachdem Sie die gewünschte einmalige Aktion ausgeführt haben. Mit anderen Worten, verwenden add-hookSie diese Option in einem Kontext, in dem Sie wissen, dass die Funktion ausgeführt werden soll, wenn der Hook ausgeführt wird, und lassen Sie die Funktion selbst aus dem Hook entfernen, nachdem die Funktion ihre Aufgabe erfüllt hat.

Wenn ich richtig einschätze, was Sie wirklich wollen, dann überlegen Sie bitte, ob Sie Ihre Frage auf etwas wie " Gibt es eine Möglichkeit, eine Hook- Funktion nur einmal auszuführen ?

Hier ist ein Beispiel aus der Standardbibliothek 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))
Drew
quelle
Ja, das kann so funktionieren, und ich nehme an, es wäre eine weniger problematische und fehleranfällige Lösung. Vielen Dank.
Joe di Castro
3
+1. Es würde nicht schaden, ein dreizeiliges Beispiel für eine Funktion zu haben, die sich vom Haken löst.
Malabarba
Schließlich habe ich eine vollständige funktionierende Lösung, die teilweise auf Ihrer Antwort basiert (am Ende wurde mir klar, dass ich sie punktieren kann, ohne die Funktion vom Haken zu nehmen). Vielen Dank!
Joe di Castro
2

Hier ist ein Makro, das Sie anstelle von add-hook(nicht ausführlich getestet) verwenden können:

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

Hinweis: make-symbolErstellt ein nicht internes Symbol mit dem angegebenen Namen. Ich habe ein #in den Namen eingefügt, um das Symbol als etwas ungewöhnlich zu kennzeichnen, falls Sie es beim Betrachten einer Hook-Variablen finden.

Harald Hanche-Olsen
quelle
Es funktioniert nicht für mich, es wirft diesen Fehler:(void-function gensym)
Joe di Castro
@joedicastro Ah, ja, das ist aus dem clPaket. Tut mir leid - ich vergesse, dass nicht jeder es benutzt. Sie können (make-symbol "#once")stattdessen verwenden. Ich werde die Antwort aktualisieren.
Harald Hanche-Olsen
1
Ich habe es erneut versucht und habe nicht für mich gearbeitet, und ehrlich gesagt, da ich eine teilweise funktionierende Lösung von Drew hatte, suche ich stattdessen diesen vielversprechenderen Weg. Trotzdem danke für deine Antwort!
Joe di Castro
@joedicastro: Das ist natürlich deine Entscheidung, und in der Tat wird Drews Lösung funktionieren. Und es ist die konventionelle Art, es zu tun. Der Hauptnachteil ist die Notwendigkeit, den Namen des Hakens in der Hakenfunktion fest zu codieren, was es schwierig macht, die Funktion in mehr als einem Haken wiederzuverwenden, falls dies erforderlich sein sollte. Wenn Sie feststellen, dass Sie die Lösung zur Verwendung in einem anderen Kontext kopieren, müssen Sie auch den Namen des Hooks bearbeiten. Meine Lösung unterbricht die Abhängigkeit, sodass Sie die Teile wiederverwenden und nach Belieben verschieben können. Ich bin gespannt, warum es bei Ihnen nicht funktioniert, aber wenn ...
Harald Hanche-Olsen
… Aber wenn Sie sich lieber nicht die Zeit nehmen, dem auf den Grund zu gehen, verstehe ich das vollkommen. Das Leben ist kurz - machen Sie mit, was für Sie funktioniert.
Harald Hanche-Olsen
0

Sie können eine Hyperfunktion gen-onceerstellen, um eine normale Funktion in eine Funktion umzuwandeln, die nur einmal ausgeführt werden kann:

(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

dann benutze (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

chendianbuji
quelle