Existe alguma maneira de executar uma função de gancho apenas uma vez?

15

O contexto

Estou usando o after-make-frame-functionsgancho para carregar corretamente os temas em uma configuração de cliente / servidor do emacs . Especificamente, este é o trecho de código que eu uso para fazer isso (com base nesta resposta do 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)))

O problema

Quando uma nova emacsclient -c/tsessão é iniciada, o gancho é executado não apenas no novo quadro, mas em todos os quadros existentes anteriores (outras sessões do emacsclient ) e produz um efeito visual muito irritante (os temas são carregados novamente em todos esses quadros) . Pior ainda, nos clientes do terminal já abertos, a cor do tema fica completamente bagunçada. Obviamente, isso acontece apenas nos clientes emacs conectados ao mesmo servidor emacs. O motivo desse comportamento é claro, o gancho é executado no servidor e todos os seus clientes são afetados.

A questão

Existe alguma maneira de executar essa função apenas uma vez ou obter o mesmo resultado sem usar o gancho?


Uma solução parcial

Agora tenho esse código, graças à resposta do @ Drew. Mas ainda tem um problema: depois de iniciar uma sessão do cliente no terminal, a GUI não carrega os temas corretamente e vice-versa. Depois de muitos testes, percebi que o comportamento depende de qual emacsclient é iniciado primeiro e, depois de descartar várias coisas, acho que talvez esteja relacionado à paleta de cores carregada. Se você recarregar manualmente, o tema funcionará bem e essa é a razão pela qual esse comportamento não aparece quando a função é chamada pelo gancho sempre, como na minha configuração 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)))

A solução final

Finalmente, tenho um código totalmente funcional que resolve o comportamento visto na solução parcial. Para conseguir isso, eu executo a função uma vez por modo (terminal ou gui), quando o emacsclient pertinente é iniciado pela primeira vez e, em seguida, remova a função do gancho, porque é não é mais necessário. Agora estou feliz! :) Mais uma vez obrigado @Drew!

O 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
fonte
1
Eu editei o título como sugerido. Sinta-se à vontade para reverter se não foi o que você originalmente quis dizer.
Malabarba 9/11
Está tudo bem, Malabarba! Eu concordo com @drew
joe di castro

Respostas:

11

Eu estou supondo que você realmente não está procurando uma maneira de " executar o gancho apenas uma vez ". Suponho que você esteja procurando uma maneira de executar essa função específica apenas uma vez, sempre que o gancho for executado.

A resposta convencional e simples a essa pergunta é que sua função se retire do gancho, depois de executar a ação única que você deseja. Em outras palavras, use add-hookem um contexto em que você saiba que a função deve ser executada quando o gancho for executado e faça com que a própria função se remova do gancho, depois que a função fizer sua coisa.

Se estou adivinhando corretamente o que você realmente deseja, considere editar sua pergunta para algo como " Existe alguma maneira de executar uma função de gancho apenas uma vez?

Aqui está um exemplo da biblioteca padrão 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))
Desenhou
fonte
Sim, pode funcionar dessa maneira e suponho que seria uma solução menos problemática e propensa a erros. Obrigado.
Joe di castro
3
+1. Não faria mal ter um exemplo de três linhas de uma função que se remove do gancho.
Malabarba
Finalmente, tenho uma solução de trabalho total parcialmente baseada em sua resposta (no final, percebi que poderia pontilhá-la sem remover a função do gancho). Muito obrigado!
Joe di castro
3

Aqui está uma macro que você pode usar em vez de add-hook(não amplamente testada):

(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-symbolcria um símbolo não interno com o nome fornecido. Eu incluí um #no nome para sinalizar o símbolo como algo incomum, caso você o encontre enquanto olha para uma variável de gancho.

Harald Hanche-Olsen
fonte
Ele não funciona para mim, ele lança este erro:(void-function gensym)
joe di castro
@joedicastro Ah, sim, isso é do clpacote. Desculpe por isso - esqueço que nem todo mundo usa. Você pode usar em seu (make-symbol "#once")lugar. Vou atualizar a resposta.
Harald Hanche-Olsen
1
Tentei novamente e não funcionou para mim e, honestamente, como tinha uma solução de trabalho parcial da Drew, busco esse caminho mais promissor. Obrigado mesmo assim pela resposta!
Joe di castro
@joedicastro: Essa é sua decisão, é claro, e de fato a solução de Drew funcionará. E é a maneira convencional de fazer isso. A principal desvantagem é a necessidade de codificar o nome do gancho na função gancho, dificultando a reutilização da função em mais de um gancho, se necessário. Além disso, se você estiver copiando a solução para uso em um contexto diferente, lembre-se de editar também o nome do gancho. Minha solução quebra a dependência, permitindo que você reutilize as peças e as mova à vontade. Estou curioso para saber por que ele não funciona para você, mas se ...
Harald Hanche-Olsen
... mas se você preferir não se apressar, entendo completamente. A vida é curta - siga o que funciona para você.
Harald Hanche-Olsen
0

você pode criar uma hiper função gen-oncepara transformar uma função normal em uma função que só pode ser executada uma 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

então use (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

chendianbuji
fonte