summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarthik Chikmagalur <karthikchikmagalur@gmail.com>2025-07-31 11:35:52 -0700
committerKarthik Chikmagalur <karthikchikmagalur@gmail.com>2025-07-31 11:35:52 -0700
commit441734b52029707032bb0fac184ab687b687bcc8 (patch)
tree6400e51fa4c2d26f5fe57207bb9e4fa00d7ce442
parentd59ca149307182b20e9843db0dd1738e01504cf1 (diff)
timeout: Add functions for throttling and debouncing
* timeout.el (timeout-throttle, timeout-debounce): Add functions to generate new throttled and debounced versions of their argument functions, as opposed to advising them. Try to maintain the documentation and interactive specs of the argument functions.
-rw-r--r--timeout.el106
1 files changed, 106 insertions, 0 deletions
diff --git a/timeout.el b/timeout.el
index 2bf41b8..161955e 100644
--- a/timeout.el
+++ b/timeout.el
@@ -34,6 +34,19 @@
;;
;; To debounce a function FUNC to run after a delay of 0.3 seconds, run
;; (timeout-debounce! func 0.3)
+;;
+;; To create a new throttled or debounced version of FUNC instead, run
+;;
+;; (timeout-throttle func 2.0)
+;; (timeout-debounce func 0.3)
+;;
+;; You can bind this via fset or defalias:
+;;
+;; (defalias 'throttled-func (timeout-throttle func 2.0))
+;; (fset 'throttled-func (timeout-throttle func 2.0))
+;;
+;; The interactive spec and documentation of FUNC is carried over to the new
+;; function.
;;; Code:
(require 'nadvice)
@@ -118,5 +131,98 @@ function."
'((name . throttle)
(depth . -98)))))
+(defun timeout-throttle (func &optional throttle default)
+ "Return a throttled version of function FUNC.
+
+THROTTLE defaults to 1 second. DEFAULT is the immediate return
+value of the function when called."
+ (let ((throttle-timer nil)
+ (throttle (or throttle 1))
+ (result default))
+ (if (commandp func)
+ ;; INTERACTIVE version
+ (lambda (&rest args)
+ (:documentation
+ (concat
+ (documentation func)
+ (format "\n\nThrottle calls to this function by %f seconds" throttle)))
+ (interactive (advice-eval-interactive-spec
+ (cadr (interactive-form func))))
+ (if (and throttle-timer (timerp throttle-timer))
+ result
+ (setq result (apply func args))
+ (setq throttle-timer
+ (run-with-timer
+ throttle nil
+ (lambda ()
+ (cancel-timer throttle-timer)
+ (setq throttle-timer nil))))))
+ ;; NON-INTERACTIVE version
+ (lambda (&rest args)
+ (:documentation
+ (concat
+ (documentation func)
+ (format "\n\nThrottle calls to this function by %f seconds" throttle)))
+ (if (and throttle-timer (timerp throttle-timer))
+ result
+ (setq result (apply func args))
+ (setq throttle-timer
+ (run-with-timer
+ throttle nil
+ (lambda ()
+ (cancel-timer throttle-timer)
+ (setq throttle-timer nil)))))))))
+
+(defun timeout-debounce (func &optional delay default)
+ "Return a debounced version of function FUNC.
+
+DELAY defaults to 0.5 seconds. DEFAULT is the immediate return
+value of the function when called."
+ (let ((debounce-timer nil)
+ (delay (or delay 0.50)))
+ (if (commandp func)
+ ;; INTERACTIVE version
+ (lambda (&rest args)
+ (:documentation
+ (concat
+ (documentation func)
+ (format "\n\nDebounce calls to this function by %f seconds" delay)))
+ (interactive (advice-eval-interactive-spec
+ (cadr (interactive-form func))))
+ (if (timerp debounce-timer)
+ (timer-set-idle-time debounce-timer delay)
+ (prog1 default
+ (setq debounce-timer
+ (run-with-idle-timer
+ delay nil
+ (lambda (buf)
+ (cancel-timer debounce-timer)
+ (setq debounce-timer nil)
+ (if (buffer-live-p buf)
+ (with-current-buffer buf
+ (apply func args))
+ (apply func args)))
+ (current-buffer))))))
+ ;; NON-INTERACTIVE version
+ (lambda (&rest args)
+ (:documentation
+ (concat
+ (documentation func)
+ (format "\n\nDebounce calls to this function by %f seconds" delay)))
+ (if (timerp debounce-timer)
+ (timer-set-idle-time debounce-timer delay)
+ (prog1 default
+ (setq debounce-timer
+ (run-with-idle-timer
+ delay nil
+ (lambda (buf)
+ (cancel-timer debounce-timer)
+ (setq debounce-timer nil)
+ (if (buffer-live-p buf)
+ (with-current-buffer buf
+ (apply func args))
+ (apply func args)))
+ (current-buffer)))))))))
+
(provide 'timeout)
;;; timeout.el ends here