フック関数を一度だけ実行する方法はありますか?


14

コンテキスト

emacsクライアント/サーバー構成でafter-make-frame-functionsテーマを適切にロードするためにフックを使用しています。具体的には、これを作成するために使用するコードスニペットです(この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)で関数を1回実行し、関数をフックから削除しますもう必要ありません。今、私は幸せです!:) @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
提案どおりにタイトルを編集しました。本来の意味ではない場合は、気軽にロールバックしてください。
マラバルバ14年

@Malabarbaは大丈夫です!私は@drewに同意します-joe
di castro

回答:


11

フックを1回だけ実行する」方法を本当に探しているのではないと思います。フックが実行されるたびに、その特定の関数を1回だけ実行する方法を探していると思います。

その質問に対する従来の簡単な答えは、必要な1回限りのアクションを実行した後、関数がフックから自身を削除することです。言い換えれば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))

はい、それはそのように機能することができ、私はそれがそれほど問題がなく、エラーを起こしやすい解決策になると思います。ありがとう。
ジョー・ディ・カストロ14年

3
+1。フックから自身を削除する関数の3行の例を使用しても害はありません。
マラバルバ14年

最後に、あなたの答えに部分的に基づいた完全な実用的なソリューションがあります(最後に、フックから関数を削除せずにドットを付けることができることに気付きました)。どうもありがとうございました!
ジョー・ディ・カストロ14年

2

代わりに使用できるマクロは次の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指定された名前でインターンされていないシンボルを作成します。#フック変数を見ているときにシンボルに出くわす場合に備えて、シンボルをやや珍しいものとしてフラグするために名前にを含めました。


それは私のために動作しません、それはこのエラーがスローされます:(void-function gensym)
ジョー・ディ・カストロ

@joedicastroあ、はい、それはclパッケージからです。申し訳ありませんが、誰もが使用しているわけではないことを忘れています。(make-symbol "#once")代わりに使用できます。答えを更新します。
ハラルドハンシュオルセン14年

1
やり直しましたが、うまくいきませんでした。ドリューから部分的な解決策があったので、正直に言って、より有望な道を探しています。とにかく答えてくれてありがとう!
ジョー・ディ・カストロ14年

@joedicastro:それはもちろんあなたの決定であり、確かにドリューのソリューションは機能します。そして、それはそれを行う従来の方法です。主な欠点は、フック関数にフックの名前をハードコーディングする必要があることです。必要な場合、複数のフックで関数を再利用するのが難しくなります。さらに、別のコンテキストで使用するためにソリューションをコピーしている場合は、フックの名前も編集することを忘れないでください。私のソリューションは依存関係を解消し、ピースを再利用して自由に移動できるようにします。私はそれはあなたのために動作しない理由として、好奇心ですが、もし...
ハラルドHanche-オルセン

…しかし、もしあなたがその底に着くのに時間をかけたくないなら、私は完全に理解しています。人生は短いです-あなたのために働くもので行く。
ハラルドハンチェオルセン14年

0

ハイパー関数gen-onceを作成して、通常の関数を1回しか実行できない関数に変換できます。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.