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

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

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.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.