Có cách nào để chạy một hàm hook chỉ một lần không?


14

Bối cảnh

Tôi đang sử dụng after-make-frame-functionshook để tải đúng các chủ đề trong cấu hình máy khách / máy chủ emacs . Cụ thể đây là đoạn mã mà tôi sử dụng để thực hiện điều đó (dựa trên câu trả lời SO này ):

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

Vấn đề

Khi một emacsclient -c/tphiên mới được bắt đầu, hook không chỉ được thực hiện trong khung mới, mà trong tất cả các khung tồn tại trước đó (các phiên emacsclient khác ) và nó tạo ra hiệu ứng hình ảnh rất khó chịu (các chủ đề được tải lại trong tất cả các khung đó) . Tệ hơn nữa, trong các thiết bị đầu cuối, các máy khách đã mở màu chủ đề bị rối hoàn toàn. Rõ ràng điều đó chỉ xảy ra trên các máy khách emacs được kết nối với cùng một máy chủ emacs. Lý do cho hành vi này là rõ ràng, hook được chạy trên máy chủ và tất cả các máy khách của nó bị ảnh hưởng.

Câu hỏi

Có cách nào để thực hiện chức năng này chỉ một lần hoặc nhận được kết quả tương tự mà không cần sử dụng hook không?


Một giải pháp một phần

Bây giờ tôi có mã này, nhờ câu trả lời của @ Drew. Nhưng vẫn có một vấn đề, một khi bạn bắt đầu một phiên máy khách trong thiết bị đầu cuối, GUI không tải các chủ đề đúng cách và ngược lại. Sau rất nhiều thử nghiệm, tôi nhận ra rằng hành vi phụ thuộc vào việc emacsclient bắt đầu trước và sau khi loại bỏ những thứ khác nhau, tôi nghĩ rằng nó có thể liên quan đến bảng màu được tải. Nếu bạn tải lại thủ công thì chủ đề đều hoạt động tốt và đó là lý do tại sao hành vi này không xuất hiện khi hàm được gọi bởi hook mỗi lần như trong cấu hình ban đầu của tôi.

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

Giải pháp cuối cùng

Cuối cùng tôi có mã làm việc hoàn toàn để giải quyết hành vi được thấy trong giải pháp một phần, để đạt được điều này, tôi chạy chức năng một lần theo chế độ (terminal hoặc gui) khi lần đầu tiên emacsclient thích hợp được khởi động, sau đó loại bỏ hàm khỏi hook vì không cần thiết nữa. Bây giơ tôi hạnh phuc! :) Cảm ơn một lần nữa @Drew!

Mật mã:

(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
Tôi đã chỉnh sửa tiêu đề như đề xuất. Xin vui lòng, thoải mái để quay trở lại nếu nó không phải là những gì bạn có ý nghĩa ban đầu.
Malabarba

Không sao đâu @Malabarba! Tôi đồng ý với @drew
joe di castro

Câu trả lời:


11

Tôi đoán rằng bạn không thực sự tìm cách " thực hiện hook chỉ một lần ". Tôi đoán rằng bạn đang tìm cách để thực hiện chức năng cụ thể đó chỉ một lần, bất cứ khi nào hook được chạy.

Câu trả lời thông thường và đơn giản cho câu hỏi đó là để chức năng của bạn tự loại bỏ khỏi móc, sau khi thực hiện hành động một lần mà bạn muốn. Nói cách khác, sử dụng add-hooktrong ngữ cảnh mà bạn biết rằng hàm sẽ được thực thi khi hook được chạy và có chức năng tự loại bỏ khỏi hook, sau khi hàm thực hiện công việc của nó.

Nếu tôi đoán đúng về những gì bạn thực sự muốn, thì vui lòng xem xét việc chỉnh sửa câu hỏi của bạn thành một câu như " Có cách nào để chạy chức năng hook chỉ một lần không?

Đây là một ví dụ, từ thư viện tiêu chuẩn 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))

Vâng, nó có thể hoạt động theo cách đó, và tôi cho rằng đó sẽ là một giải pháp ít vấn đề và dễ bị lỗi hơn. Cảm ơn.
joe di castro

3
+1. Sẽ không đau khi có một ví dụ 3 dòng về chức năng tự loại bỏ khỏi móc.
Malabarba

Cuối cùng tôi có một giải pháp làm việc tổng thể một phần dựa trên câu trả lời của bạn (cuối cùng tôi nhận ra rằng tôi có thể chấm nó mà không cần loại bỏ chức năng khỏi hook). Cảm ơn rât nhiều!
joe di castro

2

Đây là một macro mà bạn có thể sử dụng thay vì add-hook(không được thử nghiệm rộng rãi):

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

Lưu ý: make-symboltạo biểu tượng không liên kết với tên đã cho. Tôi đã bao gồm một #cái tên để gắn cờ biểu tượng là hơi bất thường, trong trường hợp bạn bắt gặp nó trong khi nhìn vào một biến hook.


Nó không hoạt động với tôi, nó ném lỗi này:(void-function gensym)
joe di castro

@joeesastro À, đúng rồi, đó là từ clgói. Xin lỗi về điều đó - tôi quên rằng không phải ai cũng sử dụng nó. Bạn có thể sử dụng (make-symbol "#once")thay thế. Tôi sẽ cập nhật câu trả lời.
Harald Hanche-Olsen

1
Tôi đã thử lại, và không làm việc cho tôi, và thành thật mà nói khi tôi có một giải pháp làm việc một phần từ Drew, thay vào đó tôi tìm kiếm con đường hứa hẹn hơn. Dù sao cũng cảm ơn bạn đã trả lời!
joe di castro

@joeesastro: Tất nhiên, đó là quyết định của bạn và thực sự giải pháp của Drew sẽ có hiệu quả. Và đó là cách thông thường để làm điều đó. Hạn chế chính là cần mã cứng tên của hook trong hàm hook, khiến cho việc sử dụng lại hàm này trong nhiều hook trở nên khó khăn, nếu điều đó là cần thiết. Ngoài ra, nếu bạn thấy mình sao chép giải pháp để sử dụng trong một ngữ cảnh khác, bạn cũng phải nhớ chỉnh sửa tên của hook. Giải pháp của tôi phá vỡ sự phụ thuộc, cho phép bạn tái sử dụng các mảnh và di chuyển chúng xung quanh theo ý muốn. Tôi tò mò về lý do tại sao nó không hiệu quả với bạn, nhưng nếu thì
Harald Hanche-Olsen

Nếu tôi không dành thời gian để đi đến tận cùng của điều đó, tôi hoàn toàn hiểu. Cuộc sống là ngắn ngủi - đi với những gì làm việc cho bạn.
Harald Hanche-Olsen

0

bạn có thể tạo một hàm siêu gen-onceđể chuyển một hàm bình thường sang một hàm chỉ có thể chạy một lần:

(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

sau đó sử dụng (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.