2

I would like to config emacs to run c-c c-k command after edit text happen and delay 3 secs. if edit happen within 3 secs, then no need to run the command. if edit happened then after 3 secs not edit happen, I need to run c-c c-k command.

How can I write my init script to get such result?

Tyler
  • 21,719
  • 1
  • 52
  • 92
lucky1928
  • 1,622
  • 8
  • 28

2 Answers2

4

The function for doing this is called run-with-idle-timer. From the help for that function:

(run-with-idle-timer SECS REPEAT FUNCTION &rest ARGS)

Perform an action the next time Emacs is idle for SECS seconds. The action is to call FUNCTION with arguments ARGS.

See the full help with C-h f run-with-idle-timer, and the relevant manual section is (elisp) Idle Timers.

For your issue, you probably want something like:

(run-with-idle-timer 3 t #'my-function)

Where my-function is the name of the function called by C-c C-k. By default, C-c C-k isn't bound to anything, so I don't know what this does on your Emacs, it must be a custom configuration you have added.

Note that this will run my-function once every time Emacs is idle for more than 3 seconds. You only want it to run when the buffer has been edited. You can accomplish this by writing a wrapper that only calls my-function when buffer-edited-p returns t:

(defun my-idle-function ()
  (if (buffer-edited-p) (my-function))) 

And then something like this:

(run-with-idle-timer 3 t #'my-idle-function)
Tyler
  • 21,719
  • 1
  • 52
  • 92
  • Awesome, exactly what I want. I am using skewer-mode to edit javascript. I wish this take effect only for skewer mode. and how can I know which function c-c c-k called? – lucky1928 May 01 '22 at 15:38
  • With a buffer in `skewer-mode`, say `C-h c C-c C-k`, or (which is the same thing) `M-x describe-key-briefly RET C-c C-k`. – NickD May 01 '22 at 19:03
  • You accepted this answer; if it works OK for you in casual usage, great! But idle timers are not per-buffer. If you see problems (e.g. your skewer function runs on whatever buffer happens to be current) I could post a more complicated but more reliable approach as another answer. – Greg Hendershott May 02 '22 at 16:03
2

As I commented on the accepted answer, it might work fine for you in casual use -- in which case, that's great, and you can ignore this answer!


What I think you need to do, for it to be most reliable, is unfortunately a little more complicated: Use an after-change hook to detect edits, and have that set an idle timer (passing it the buffer to use).

;; The basic plan here:
;;
;; 1. Use an after-change-hook, which is called only after the buffer
;; changes.
;;
;; 2. That sets a 3 second timer, after which the C-c C-k skewer
;; command is called.

(defvar-local my/idle-timer nil
  "Buffer-local variable for idle timer.")

(defun my/skewer-after-change-function (_beg _end _len)
  "A value for `after-change-functions' hook."
  ;; Cancel any existing idle timer
  (when (timerp my/idle-timer)
    (cancel-timer my/idle-timer))
  ;; Set a fresh timer for 3 seconds from now, to run once only. If it
  ;; expires, it will call `my/run-skewer-function', passing the value
  ;; of `current-buffer' in effect right now.
  (setq my/idle-timer
        (run-with-idle-timer 3   ;seconds
                             nil ;no repeat
                             #'my/run-skewer-function
                             (current-buffer))))

(defun my/run-skewer-function (buffer)
  "A function to be called from our idle timer.

Mainly it just checks the buffer is live, and makes it current before
calling the skewer command."
  (when (buffer-live-p buffer)
    (with-current-buffer buffer
      ;; Here call whatever command is bound to C-c C-k in skewer-mdoe
      (the-C-c-C-k-skewer-function)))) ;name TBD

(defun my/add-skewer-hook ()
  (add-hook 'after-change-functions
            #'my/skewer-after-change-function
            nil
            t)) ;LOCAL is true; important

(add-hook 'skewer-mode #'my/add-skewer-hook)

Note: I named the variable and functions using a my/ prefix so that hopefully they won't collide with anything else in your init file or Emacs.

Greg Hendershott
  • 1,483
  • 12
  • 17
  • Many thanks, when I try it, I got a error: Error running timer `my/run-skewer-function': (void-function buffer-livep) – lucky1928 May 02 '22 at 17:23
  • Sorry about that typo, it should be `buffer-live-p`; I'll update the code in the answer. – Greg Hendershott May 02 '22 at 17:58
  • By the way, the provenance of this example code is [part of my Racket Mode package](https://github.com/greghendershott/racket-mode/blob/master/racket-xp.el#L839-L884). Although that's tested, it addresses some other concerns, so I tried to simplify and adapt it for this answer. – Greg Hendershott May 02 '22 at 18:04