후크 기능을 한 번만 실행할 수있는 방법이 있습니까?


15

문맥

emacs 클라이언트 / 서버 구성after-make-frame-functions 에서 테마를 올바르게로드하기 위해 후크를 사용하고 있습니다 . 특히 이것은 이것을 만드는 데 사용하는 코드 스 니펫입니다 (이 SO answer 기반 ).

 (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

@Malabarba는 괜찮습니다! @drew에 동의합니다
joe di castro

답변:


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 18시 25 분

3
+1. 후크에서 자신을 제거하는 함수의 3 줄 예를 갖는 것은 상처가되지 않습니다.
Malabarba

마지막으로 귀하의 답변에 부분적으로 기반을 둔 총 작업 솔루션이 있습니다 (결국에는 함수를 후크에서 제거하지 않고 점을 찍을 수 있음을 깨달았습니다). 대단히 감사합니다!
joe di castro

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주어진 이름으로 언 인턴 심볼을 만듭니다. 나는 포함 #후크 변수를 보면서 경우에 당신이 그것을 건너, 플래그 이름 다소 특이한로 기호에.


그것은 나를 위해 작동하지 않습니다, 그것은이 오류가 발생합니다 :(void-function gensym)
joe di castro

@ joedicastro 아, 그렇습니다 cl. 그것에 대해 죄송합니다 – 모두가 그것을 사용하지는 않습니다. (make-symbol "#once")대신 사용할 수 있습니다 . 답변을 업데이트하겠습니다.
Harald Hanche-Olsen

1
나는 다시 시도했지만 나를 위해 일하지 않았고 솔직히 Drew의 부분적인 작업 솔루션을 얻었으므로 더 유망한 길을 찾았습니다. 어쨌든 답변 주셔서 감사합니다!
joe di castro

@ joedicastro : 물론 당신의 결정이며, 실제로 Drew의 솔루션이 효과가 있습니다. 그리고 그것을하는 일반적인 방법입니다. 주요 단점은 후크 함수에서 후크 이름을 하드 코딩해야하기 때문에 필요한 경우 둘 이상의 후크에서 함수를 재사용하기가 어렵습니다. 또한 다른 컨텍스트에서 사용하기 위해 솔루션을 복사하는 경우 후크 이름도 편집해야합니다. 내 솔루션은 종속성을 깨뜨려 조각을 재사용하고 마음대로 움직일 수 있습니다. 왜 그것이 당신에게 효과가 없는지 궁금하지만… 만약
Harald Hanche-Olsen

… 그러나 시간을 허비하지 않으면 완전히 이해합니다. 인생은 짧다 – 당신을 위해 일하는 것과 함께 가십시오.
Harald Hanche-Olsen

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.