มีวิธีใดบ้างที่จะเรียกใช้ฟังก์ชั่น hook ได้เพียงครั้งเดียว?


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 เดียวกัน สาเหตุของพฤติกรรมนี้ชัดเจน hook ถูกเรียกใช้บนเซิร์ฟเวอร์และไคลเอ็นต์ทั้งหมดได้รับผลกระทบ

คำถาม

มีวิธีการใช้งานฟังก์ชั่นนี้เพียงครั้งเดียวหรือได้รับผลลัพธ์เดียวกันโดยไม่ต้องใช้เบ็ดหรือไม่?


ทางออกบางส่วน

ฉันมีรหัสนี้แล้วขอบคุณคำตอบของ @ Drew แต่ยังคงมีปัญหาเมื่อคุณเริ่มเซสชันไคลเอ็นต์ในเทอร์มินัล GUI ไม่โหลดธีมอย่างถูกต้องและในทางกลับกัน หลังจากการทดสอบหลายครั้งฉันรู้ว่าพฤติกรรมขึ้นอยู่กับว่า emacsclient เริ่มก่อนและหลังจากทิ้งสิ่งต่าง ๆ ฉันคิดว่ามันอาจเกี่ยวข้องกับจานสีที่บรรจุอยู่ หากคุณโหลดธีมเองใหม่ทั้งหมดจะทำงานได้ดีและนั่นเป็นสาเหตุที่พฤติกรรมนี้ไม่ปรากฏเมื่อมีการเรียกใช้ฟังก์ชันโดย hook ทุกครั้งเช่นเดียวกับในการกำหนดค่าเริ่มต้นของฉัน

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

ทางออกสุดท้าย

ในที่สุดฉันมีรหัสการทำงานทั้งหมดที่แก้พฤติกรรมที่เห็นในการแก้ปัญหาบางส่วนเพื่อให้บรรลุนี้ฉันเรียกใช้ฟังก์ชั่นครั้งเดียวโดยโหมด (terminal หรือกุย) เมื่อนั้น emacsclient ที่เกี่ยวข้องจะเริ่มขึ้นเป็นครั้งแรกแล้วเอาฟังก์ชั่น ไม่จำเป็นอีกต่อไป ตอนนี้ฉันมีความสุข! :) ขอขอบคุณอีกครั้ง @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ในบริบทที่คุณรู้ว่าควรจะเรียกใช้ฟังก์ชันเมื่อ hook ถูกเรียกใช้และให้ฟังก์ชันนั้นลบตัวเองออกจาก hook หลังจากฟังก์ชันทำสิ่งนั้น

หากฉันเดาได้อย่างถูกต้องเกี่ยวกับสิ่งที่คุณต้องการจริงๆโปรดลองแก้ไขคำถามของคุณเป็นแบบ " มีวิธีใดบ้างที่จะเรียกใช้ ฟังก์ชั่น 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

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)
โจดิ castro

@joedicastro อ่าใช่นั่นมาจากclแพ็คเกจ ขออภัยด้วย - ฉันลืมว่าไม่ใช่ทุกคนที่ใช้ คุณสามารถใช้(make-symbol "#once")แทน ฉันจะอัปเดตคำตอบ
Harald Hanche-Olsen

1
ฉันลองอีกครั้งและไม่ได้ผลสำหรับฉันและโดยสุจริตเนื่องจากฉันมีวิธีแก้ปัญหาการทำงานบางส่วนจาก Drew ฉันจึงหาเส้นทางที่มีแนวโน้มมากกว่านี้แทน ขอบคุณสำหรับคำตอบของคุณ!
joe di castro

@joedicastro: นั่นเป็นการตัดสินใจของคุณและแน่นอนว่าทางออกของ Drew จะใช้ได้จริง และเป็นวิธีการดั้งเดิมที่จะทำ ข้อเสียเปรียบหลักคือความต้องการรหัสฮาร์ดชื่อของ hook ในฟังก์ชั่น hook ทำให้ยากที่จะนำฟังก์ชั่นนี้มาใช้ซ้ำอีกครั้งใน hook มากกว่าหนึ่งตัวหากจำเป็น นอกจากนี้หากคุณพบว่าตัวเองกำลังคัดลอกโซลูชันเพื่อใช้ในบริบทที่แตกต่างคุณต้องจำไว้ว่าต้องแก้ไขชื่อของ hook ด้วย โซลูชันของฉันแบ่งการพึ่งพาอาศัยกันโดยให้คุณนำชิ้นส่วนเหล่านั้นกลับมาใช้ใหม่และเคลื่อนย้ายไปตามต้องการ ผมอยากรู้ว่าทำไมมันไม่ทำงานสำหรับคุณ แต่ถ้า ...
แฮรัลด์ Hanche-โอลเซ่น

… แต่ถ้าคุณไม่อยากใช้เวลาให้ถึงจุดต่ำสุดฉันก็เข้าใจอย่างถ่องแท้ ชีวิตนั้นสั้น - ไปกับสิ่งที่เหมาะกับคุณ
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.