有什么方法只能运行一次挂钩函数吗?


15

上下文

我正在使用after-make-frame-functions挂钩在emacs客户端/服务器配置中正确加载主题。具体来说,这是我用来创建代码段(基于此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)))

问题

emacsclient -c/t开始新的会话时,不仅在新框架中,而且还在所有先前存在的框架(其他emacsclient会话)中执行挂钩,这会产生非常烦人的视觉效果(在所有那些框架中再次加载主题)。更糟糕的是,在终端中已经打开的客户端的主题颜色就完全混乱了。显然,这仅在连接到同一emacs服务器的emacs客户端上发生。出现这种现象的原因很明显,该挂钩在服务器上运行,并且其所有客户端都受到影响。

问题

有什么方法可以只执行一次此功能,也可以不使用钩子而获得相同的结果吗?


部分解决方案

由于@Drew的回答,我现在有了这段代码。但是仍然存在问题,一旦在终端中启动客户端会话,GUI将无法正确加载主题,反之亦然。经过大量测试,我意识到其行为取决于首先启动哪个emacsclient,并且丢弃各种东西后,我认为它可能与所加载的调色板有关。如果您手动重新加载主题,那么一切都可以正常工作,这就是为什么每次在钩子上调用函数时都不会出现此行为的原因,就像在我的初始配置中一样。

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

最终的解决方案

最后,我有完全有效的代码来解决部分解决方案中出现的行为,为实现此目的,我在首次启动相关emacsclient时通过模式(终端或gui)运行了一次功能,然后从挂钩中删除了该功能,因为不再需要了。现在,我很高兴!:)再次感谢@Drew!

代码:

(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
我已经按照建议编辑了标题。请回滚,如果它不是您的初衷。
马拉巴巴

@Malabarba很好!我同意@drew
joe di castro

Answers:


11

我猜您不是在真正地寻找一种“ 只执行一次钩子 ”的方法。我猜想您正在寻找一种方法,无论何时运行该挂钩,该函数只能执行一次。

传统的,简单的,回答问题是你的函数本身从钩去掉,执行所需的一次性动作之后。换句话说add-hook,在上下文中使用,您知道应该在运行挂钩时执行该函数,并让函数在完成其操作后从挂钩中将其自身删除。

如果我正确地猜测了您真正想要的是什么,那么请考虑将您的问题编辑为“ 是否有办法只运行一次挂钩 函数

这是来自标准库的示例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))

是的,它可以这种方式工作,我想这将是一个问题较少且易于出错的解决方案。谢谢。
joe di castro 2014年

3
+1。具有将其自身从钩子中删除的函数的3行示例无害。
马拉巴巴

最终,我有一个总的解决方案,部分基于您的答案(最后,我意识到我可以将其加点而不用从钩子中删除该函数)。非常感谢你!
2014年

3

这是您可以代替add-hook(未经广泛测试)的宏:

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

注意:make-symbol创建具有给定名称的未隔离符号。我#在名称中添加了一个符号,以将符号标记为不寻常,以防您在查看hook变量时遇到它。


它对我不起作用,它引发以下错误:(void-function gensym)
joe di castro 2014年

@joedicastro啊,是的,那是从cl包裹里拿出来的。抱歉–我忘了并不是每个人都使用它。您可以(make-symbol "#once")改用。我将更新答案。
Harald Hanche-Olsen 2014年

1
我又试了一次,但对我没有用,老实说,由于我得到了Drew的部分解决方案,因此我寻求一条更有希望的道路。无论如何,谢谢您的回答!
joe di castro 2014年

@joedicastro:当然,这是您的决定,而且Drew的解决方案确实有效。这是传统的方法。主要缺点是需要在挂钩函数中用硬编码挂钩的名称,如果需要的话,很难在多个挂钩中重用该函数。另外,如果您发现自己要复制解决方案以在其他环境中使用,则必须记住还要编辑挂钩的名称。我的解决方案打破了依赖关系,让您重复使用各个部分并随意移动它们。我想知道为什么它对您不起作用,但是如果……
Harald Hanche-Olsen 2014年

…但是,如果您不想花时间深入了解,我完全理解。人生苦短–选择适合自己的产品。
Harald Hanche-Olsen 2014年

0

您可以使一个超函数gen-once将普通函数转换为只能运行一次的函数:

(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

然后,使用 (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.