Apakah ada cara untuk menjalankan fungsi hook hanya sekali?

15

Isi

Saya menggunakan after-make-frame-functionshook untuk memuat tema dengan benar dalam konfigurasi klien / server emacs . Khususnya ini adalah potongan kode yang saya gunakan untuk membuatnya (berdasarkan jawaban SO ini ):

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

Masalah

Ketika emacsclient -c/tsesi baru dimulai, kait dieksekusi tidak hanya di frame baru, tetapi di semua frame yang ada sebelumnya ( sesi emacsclient lainnya ) dan itu membuat efek visual yang sangat menjengkelkan (tema dimuat lagi di semua frame itu) . Lebih buruk lagi, di terminal klien sudah membuka warna tema menjadi benar-benar kacau. Jelas itu hanya terjadi pada klien emacs yang terhubung ke server emacs yang sama. Alasan untuk perilaku ini jelas, hook dijalankan di server dan semua kliennya terpengaruh.

Pertanyaan

Apakah ada cara untuk menjalankan fungsi ini hanya sekali atau mendapatkan hasil yang sama tanpa menggunakan hook?


Solusi parsial

Saya sekarang memiliki kode ini, terima kasih atas jawaban @ Drew. Tetapi masih memiliki masalah, setelah Anda memulai sesi klien di terminal, GUI tidak memuat tema dengan benar dan sebaliknya. Setelah banyak tes, saya menyadari bahwa perilakunya tergantung pada emacsclient yang dimulai pertama, dan setelah membuang berbagai hal, saya pikir itu mungkin terkait dengan palet warna yang dimuat. Jika Anda memuat ulang secara manual tema semua berfungsi dengan baik dan itulah alasan mengapa perilaku ini tidak muncul ketika fungsi dipanggil oleh hook setiap kali seperti pada konfigurasi awal saya.

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

Solusi terakhir

Akhirnya saya memiliki kode yang benar-benar berfungsi yang memecahkan perilaku yang terlihat dalam solusi parsial, untuk mencapai ini saya menjalankan fungsi sekali dengan mode (terminal atau gui) ketika kemudian emacsclient yang bersangkutan dimulai untuk pertama kali, kemudian hapus fungsi dari hook karena tidak dibutuhkan lagi. Sekarang aku bahagia! :) Terima kasih lagi @Drew!

Kode:

(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)))
joe di castro
sumber
1
Saya telah mengedit judul seperti yang disarankan. Tolong, jangan ragu untuk mengembalikan jika bukan apa yang Anda maksudkan semula.
Malabarba
Tidak apa-apa @Malabarba! Saya setuju dengan @drew
joe di castro

Jawaban:

11

Saya menduga Anda tidak benar - benar mencari cara untuk " menjalankan hook hanya sekali ". Saya menduga Anda mencari cara untuk menjalankan fungsi tertentu hanya sekali, setiap kali kait dijalankan.

Konvensional, dan sederhana, jawaban yang dimaksud adalah untuk fungsi Anda untuk menghapus diri dari hook, setelah melakukan aksi satu kali yang Anda inginkan. Dengan kata lain, gunakan add-hookdalam konteks di mana Anda tahu bahwa fungsi tersebut harus dieksekusi ketika hook dijalankan, dan biarkan fungsi itu sendiri menghapus dirinya sendiri dari hook, setelah fungsi melakukan hal tersebut.

Jika saya menebak dengan benar tentang apa yang benar-benar Anda inginkan, maka silakan pertimbangkan untuk mengedit pertanyaan Anda untuk sesuatu seperti " Apakah ada cara untuk menjalankan fungsi hook hanya sekali?

Berikut ini adalah contoh, dari perpustakaan standar 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))
Drew
sumber
Ya, itu bisa bekerja dengan cara itu, dan saya kira itu akan menjadi solusi yang kurang bermasalah dan rawan kesalahan. Terima kasih.
joe di castro
3
+1. Tidak ada salahnya memiliki contoh 3-baris dari suatu fungsi yang menghilangkan dirinya sendiri dari hook.
Malabarba
Akhirnya saya memiliki solusi kerja total yang sebagian didasarkan pada jawaban Anda (pada akhirnya saya menyadari bahwa saya bisa dot itu tanpa menghapus fungsi dari hook). Terima kasih banyak!
joe di castro
3

Berikut ini adalah makro yang dapat Anda gunakan sebagai gantinya add-hook(tidak diuji secara luas):

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

Catatan: make-symbolmembuat simbol yang tidak diinginkan dengan nama yang diberikan. Saya menyertakan #nama untuk menandai simbol sebagai sesuatu yang tidak biasa, kalau-kalau Anda menemukan itu sambil melihat variabel kait.

Harald Hanche-Olsen
sumber
Ini tidak berfungsi untuk saya, itu melempar kesalahan ini:(void-function gensym)
joe di castro
@joedicastro Ah, ya, itu dari clpaket. Maaf tentang itu - saya lupa bahwa tidak semua orang menggunakannya. Anda bisa menggunakannya (make-symbol "#once"). Saya akan memperbarui jawabannya.
Harald Hanche-Olsen
1
Saya mencoba lagi, dan tidak bekerja untuk saya, dan jujur ​​karena saya punya solusi kerja parsial dari Drew, saya mencari jalan yang lebih menjanjikan itu. Terima kasih atas jawaban Anda!
joe di castro
@joedicastro: Itu keputusan Anda, tentu saja, dan memang solusi Drew akan berhasil. Dan itu adalah cara konvensional untuk melakukannya. Kelemahan utama adalah kebutuhan untuk membuat kode nama kait dalam fungsi kait, sehingga sulit untuk menggunakan kembali fungsi di lebih dari satu kait, jika itu harus dibutuhkan. Plus, jika Anda menemukan diri Anda menyalin solusi untuk digunakan dalam konteks yang berbeda, Anda harus ingat untuk juga mengedit nama hook. Solusi saya memecah ketergantungan, membiarkan Anda menggunakan kembali potongan-potongan dan memindahkannya sesuka hati. Saya ingin tahu mengapa itu tidak berhasil untuk Anda, tetapi jika ...
Harald Hanche-Olsen
... tetapi jika Anda lebih suka tidak meluangkan waktu untuk menyelesaikannya, saya mengerti sepenuhnya. Hidup ini singkat - ikuti apa yang cocok untuk Anda.
Harald Hanche-Olsen
0

Anda dapat membuat fungsi hiper gen-onceuntuk mentransformasikan fungsi normal ke fungsi yang hanya bisa berjalan sekali:

(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

lalu, gunakan (add-hook 'xxx-mode-hook (gen-once your-function-need-to-run-once))

chendianbuji
sumber