summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/timer.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/emacs-lisp/timer.el')
-rw-r--r--lisp/emacs-lisp/timer.el586
1 files changed, 586 insertions, 0 deletions
diff --git a/lisp/emacs-lisp/timer.el b/lisp/emacs-lisp/timer.el
new file mode 100644
index 00000000000..b25a040a96c
--- /dev/null
+++ b/lisp/emacs-lisp/timer.el
@@ -0,0 +1,586 @@
+;;; timer.el --- run a function with args at some time in future -*- lexical-binding: t -*-
+
+;; Copyright (C) 1996, 2001-2022 Free Software Foundation, Inc.
+
+;; Maintainer: emacs-devel@gnu.org
+;; Package: emacs
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This package gives you the capability to run Emacs Lisp commands at
+;; specified times in the future, either as one-shots or periodically.
+
+;;; Code:
+
+(eval-when-compile (require 'cl-lib))
+
+;; If you change this structure, you also have to change `timerp'
+;; (below) and decode_timer in keyboard.c.
+(cl-defstruct (timer
+ (:constructor nil)
+ (:copier nil)
+ (:constructor timer--create ())
+ (:type vector)
+ (:conc-name timer--))
+ ;; nil if the timer is active (waiting to be triggered),
+ ;; non-nil if it is inactive ("already triggered", in theory).
+ (triggered t)
+ ;; Time of next trigger: for normal timers, absolute time, for idle timers,
+ ;; time relative to idle-start.
+ high-seconds low-seconds usecs
+ ;; For normal timers, time between repetitions, or nil. For idle timers,
+ ;; non-nil iff repeated.
+ repeat-delay
+ function args ;What to do when triggered.
+ idle-delay ;If non-nil, this is an idle-timer.
+ psecs
+ ;; A timer may be created with t as the TIME, which means that we
+ ;; want to run at specific integral multiples of `repeat-delay'. We
+ ;; then have to recompute this (because the machine may have gone to
+ ;; sleep, etc).
+ integral-multiple)
+
+(defun timer-create ()
+ ;; BEWARE: This is not an eta-redex, because `timer--create' is inlinable
+ ;; whereas `timer-create' should not be because we don't want to
+ ;; hardcode the shape of timers in other .elc files.
+ (timer--create))
+
+(defun timerp (object)
+ "Return t if OBJECT is a timer."
+ (and (vectorp object)
+ ;; Timers are now ten elements, but old .elc code may have
+ ;; shorter versions of `timer-create'.
+ (<= 9 (length object) 10)))
+
+(defsubst timer--check (timer)
+ (or (timerp timer) (signal 'wrong-type-argument (list #'timerp timer))))
+
+(defun timer--time-setter (timer time)
+ (timer--check timer)
+ (let ((lt (time-convert time 'list)))
+ (setf (timer--high-seconds timer) (nth 0 lt))
+ (setf (timer--low-seconds timer) (nth 1 lt))
+ (setf (timer--usecs timer) (nth 2 lt))
+ (setf (timer--psecs timer) (nth 3 lt))
+ time))
+
+;; Pseudo field `time'.
+(defun timer--time (timer)
+ (declare (gv-setter timer--time-setter))
+ (list (timer--high-seconds timer)
+ (timer--low-seconds timer)
+ (timer--usecs timer)
+ (timer--psecs timer)))
+
+(defun timer-set-time (timer time &optional delta)
+ "Set the trigger time of TIMER to TIME.
+TIME must be a Lisp time value.
+If optional third argument DELTA is a positive number, make the timer
+fire repeatedly that many seconds apart."
+ (setf (timer--time timer) time)
+ (setf (timer--repeat-delay timer) (and (numberp delta) (> delta 0) delta))
+ timer)
+
+(defun timer-set-idle-time (timer secs &optional repeat)
+ ;; FIXME: Merge with timer-set-time.
+ "Set the trigger idle time of TIMER to SECS.
+SECS may be an integer, floating point number, or the internal
+time format returned by, e.g., `current-idle-time'.
+If optional third argument REPEAT is non-nil, make the timer
+fire each time Emacs is idle for that many seconds."
+ (setf (timer--time timer) secs)
+ (setf (timer--repeat-delay timer) repeat)
+ timer)
+
+(defun timer-next-integral-multiple-of-time (time secs)
+ "Yield the next value after TIME that is an integral multiple of SECS.
+More precisely, the next value, after TIME, that is an integral multiple
+of SECS seconds since the epoch. SECS may be a fraction."
+ (let* ((ticks-hz (time-convert time t))
+ (ticks (car ticks-hz))
+ (hz (cdr ticks-hz))
+ trunc-s-ticks)
+ (while (let ((s-ticks (* secs hz)))
+ (setq trunc-s-ticks (truncate s-ticks))
+ (/= s-ticks trunc-s-ticks))
+ (setq ticks (ash ticks 1))
+ (setq hz (ash hz 1)))
+ (let ((more-ticks (+ ticks trunc-s-ticks)))
+ (time-convert (cons (- more-ticks (% more-ticks trunc-s-ticks)) hz) t))))
+
+(defun timer-relative-time (time secs &optional usecs psecs)
+ "Advance TIME by SECS seconds.
+
+Optionally also advance it by USECS microseconds and PSECS
+picoseconds.
+
+SECS may be either an integer or a floating point number."
+ (let ((delta secs))
+ (if (or usecs psecs)
+ (setq delta (time-add delta (list 0 0 (or usecs 0) (or psecs 0)))))
+ (time-add time delta)))
+
+(defun timer--time-less-p (t1 t2)
+ "Say whether time value T1 is less than time value T2."
+ (time-less-p (timer--time t1) (timer--time t2)))
+
+(defun timer-inc-time (timer secs &optional usecs psecs)
+ "Increment the time set in TIMER by SECS seconds.
+
+Optionally also increment it by USECS microseconds, and PSECS
+picoseconds. If USECS or PSECS are omitted, they are treated as
+zero.
+
+SECS may be a fraction."
+ (setf (timer--time timer)
+ (timer-relative-time (timer--time timer) secs usecs psecs)))
+
+(defun timer-set-function (timer function &optional args)
+ "Make TIMER call FUNCTION with optional ARGS when triggering."
+ (timer--check timer)
+ (setf (timer--function timer) function)
+ (setf (timer--args timer) args)
+ timer)
+
+(defun timer--activate (timer &optional triggered-p reuse-cell idle)
+ (let ((timers (if idle timer-idle-list timer-list))
+ last)
+ (cond
+ ((not (and (timerp timer)
+ (integerp (timer--high-seconds timer))
+ (integerp (timer--low-seconds timer))
+ (integerp (timer--usecs timer))
+ (integerp (timer--psecs timer))
+ (timer--function timer)))
+ (error "Invalid or uninitialized timer"))
+ ;; FIXME: This is not reliable because `idle-delay' is only set late,
+ ;; by `timer-activate-when-idle' :-(
+ ;;((not (eq (not idle)
+ ;; (not (timer--idle-delay timer))))
+ ;; (error "idle arg %S out of sync with idle-delay field of timer: %S"
+ ;; idle timer))
+ ((memq timer timers)
+ (error "Timer already activated"))
+ (t
+ ;; Skip all timers to trigger before the new one.
+ (while (and timers (timer--time-less-p (car timers) timer))
+ (setq last timers
+ timers (cdr timers)))
+ (if reuse-cell
+ (progn
+ (setcar reuse-cell timer)
+ (setcdr reuse-cell timers))
+ (setq reuse-cell (cons timer timers)))
+ ;; Insert new timer after last which possibly means in front of queue.
+ (setf (cond (last (cdr last))
+ (idle timer-idle-list)
+ (t timer-list))
+ reuse-cell)
+ (setf (timer--triggered timer) triggered-p)
+ (setf (timer--idle-delay timer) idle)
+ nil))))
+
+(defun timer-activate (timer &optional triggered-p reuse-cell)
+ "Insert TIMER into `timer-list'.
+If TRIGGERED-P is t, make TIMER inactive (put it on the list, but
+mark it as already triggered). To remove it, use `cancel-timer'.
+
+REUSE-CELL, if non-nil, is a cons cell to reuse when inserting
+TIMER into `timer-list' (usually a cell removed from that list by
+`cancel-timer-internal'; using this reduces consing for repeat
+timers). If nil, allocate a new cell."
+ (timer--activate timer triggered-p reuse-cell nil))
+
+(defun timer-activate-when-idle (timer &optional dont-wait reuse-cell)
+ "Insert TIMER into `timer-idle-list'.
+This arranges to activate TIMER whenever Emacs is next idle.
+If optional argument DONT-WAIT is non-nil, set TIMER to activate
+immediately \(see below), or at the right time, if Emacs is
+already idle.
+
+REUSE-CELL, if non-nil, is a cons cell to reuse when inserting
+TIMER into `timer-idle-list' (usually a cell removed from that
+list by `cancel-timer-internal'; using this reduces consing for
+repeat timers). If nil, allocate a new cell.
+
+Using non-nil DONT-WAIT is not recommended when activating an
+idle timer from an idle timer handler, if the timer being
+activated has an idleness time that is smaller or equal to
+the time of the current timer. That's because the activated
+timer will fire right away."
+ (timer--activate timer (not dont-wait) reuse-cell 'idle))
+
+(defalias 'disable-timeout #'cancel-timer)
+
+(defun cancel-timer (timer)
+ "Remove TIMER from the list of active timers."
+ (timer--check timer)
+ (setq timer-list (delq timer timer-list))
+ (setq timer-idle-list (delq timer timer-idle-list))
+ nil)
+
+(defun cancel-timer-internal (timer)
+ "Remove TIMER from the list of active timers or idle timers.
+Only to be used in this file. It returns the cons cell
+that was removed from the timer list."
+ (let ((cell1 (memq timer timer-list))
+ (cell2 (memq timer timer-idle-list)))
+ (if cell1
+ (setq timer-list (delq timer timer-list)))
+ (if cell2
+ (setq timer-idle-list (delq timer timer-idle-list)))
+ (or cell1 cell2)))
+
+(defun cancel-function-timers (function)
+ "Cancel all timers which would run FUNCTION.
+This affects ordinary timers such as are scheduled by `run-at-time',
+and idle timers such as are scheduled by `run-with-idle-timer'."
+ (interactive "aCancel timers of function: ")
+ (dolist (timer timer-list)
+ (if (eq (timer--function timer) function)
+ (setq timer-list (delq timer timer-list))))
+ (dolist (timer timer-idle-list)
+ (if (eq (timer--function timer) function)
+ (setq timer-idle-list (delq timer timer-idle-list)))))
+
+;; Record the last few events, for debugging.
+(defvar timer-event-last nil
+ "Last timer that was run.")
+(defvar timer-event-last-1 nil
+ "Next-to-last timer that was run.")
+(defvar timer-event-last-2 nil
+ "Third-to-last timer that was run.")
+
+(defcustom timer-max-repeats 10
+ "Maximum number of times to repeat a timer, if many repeats are delayed.
+Timer invocations can be delayed because Emacs is suspended or busy,
+or because the system's time changes. If such an occurrence makes it
+appear that many invocations are overdue, this variable controls
+how many will really happen."
+ :type 'integer
+ :group 'internal)
+
+(defun timer-until (timer time)
+ "Calculate number of seconds from when TIMER will run, until TIME.
+TIMER is a timer, and stands for the time when its next repeat is scheduled.
+TIME is a Lisp time value."
+ (float-time (time-subtract time (timer--time timer))))
+
+(defun timer-event-handler (timer)
+ "Call the handler for the timer TIMER.
+This function is called, by name, directly by the C code."
+ (setq timer-event-last-2 timer-event-last-1)
+ (setq timer-event-last-1 timer-event-last)
+ (setq timer-event-last timer)
+ (let ((inhibit-quit t))
+ (timer--check timer)
+ (let ((retrigger nil)
+ (cell
+ ;; Delete from queue. Record the cons cell that was used.
+ (cancel-timer-internal timer)))
+ ;; If `cell' is nil, it means the timer was already canceled, so we
+ ;; shouldn't be running it at all. This can happen for example with the
+ ;; following scenario (bug#17392):
+ ;; - we run timers, starting with A (and remembering the rest as (B C)).
+ ;; - A runs and a does a sit-for.
+ ;; - during sit-for we run timer D which cancels timer B.
+ ;; - timer A finally finishes, so we move on to timers B and C.
+ (when cell
+ ;; Re-schedule if requested.
+ (if (timer--repeat-delay timer)
+ (if (timer--idle-delay timer)
+ (timer-activate-when-idle timer nil cell)
+ (timer-inc-time timer (timer--repeat-delay timer) 0)
+ ;; If real time has jumped forward,
+ ;; perhaps because Emacs was suspended for a long time,
+ ;; limit how many times things get repeated.
+ (if (and (numberp timer-max-repeats)
+ (time-less-p (timer--time timer) nil))
+ (let ((repeats (/ (timer-until timer nil)
+ (timer--repeat-delay timer))))
+ (if (> repeats timer-max-repeats)
+ (timer-inc-time timer (* (timer--repeat-delay timer)
+ repeats)))))
+ ;; If we want integral multiples, we have to recompute
+ ;; the repetition.
+ (when (and (> (length timer) 9) ; Backwards compatible.
+ (timer--integral-multiple timer)
+ (not (timer--idle-delay timer)))
+ (setf (timer--time timer)
+ (timer-next-integral-multiple-of-time
+ nil (timer--repeat-delay timer))))
+ ;; Place it back on the timer-list before running
+ ;; timer--function, so it can cancel-timer itself.
+ (timer-activate timer t cell)
+ (setq retrigger t)))
+ ;; Run handler.
+ (condition-case-unless-debug err
+ ;; Timer functions should not change the current buffer.
+ ;; If they do, all kinds of nasty surprises can happen,
+ ;; and it can be hellish to track down their source.
+ (save-current-buffer
+ (apply (timer--function timer) (timer--args timer)))
+ (error (message "Error running timer%s: %S"
+ (if (symbolp (timer--function timer))
+ (format-message " `%s'" (timer--function timer))
+ "")
+ err)))
+ (when (and retrigger
+ ;; If the timer's been canceled, don't "retrigger" it
+ ;; since it might still be in the copy of timer-list kept
+ ;; by keyboard.c:timer_check (bug#14156).
+ (memq timer timer-list))
+ (setf (timer--triggered timer) nil))))))
+
+;; This function is incompatible with the one in levents.el.
+(defun timeout-event-p (event)
+ "Non-nil if EVENT is a timeout event."
+ (and (listp event) (eq (car event) 'timer-event)))
+
+
+(declare-function diary-entry-time "diary-lib" (s))
+
+(defun run-at-time (time repeat function &rest args)
+ "Perform an action at time TIME.
+Repeat the action every REPEAT seconds, if REPEAT is non-nil.
+REPEAT may be an integer or floating point number.
+TIME should be one of:
+
+- a string giving today's time like \"11:23pm\"
+ (the acceptable formats are HHMM, H:MM, HH:MM, HHam, HHAM,
+ HHpm, HHPM, HH:MMam, HH:MMAM, HH:MMpm, or HH:MMPM;
+ a period `.' can be used instead of a colon `:' to separate
+ the hour and minute parts);
+
+- a string giving a relative time like \"90\" or \"2 hours 35 minutes\"
+ (the acceptable forms are a number of seconds without units
+ or some combination of values using units in `timer-duration-words');
+
+- nil, meaning now;
+
+- a number of seconds from now;
+
+- a value from `encode-time';
+
+- or t (with non-nil REPEAT) meaning the next integral multiple
+ of REPEAT. This is handy when you want the function to run at
+ a certain \"round\" number. For instance, (run-at-time t 60 ...)
+ will run at 11:04:00, 11:05:00, etc.
+
+The action is to call FUNCTION with arguments ARGS.
+
+This function returns a timer object which you can use in
+`cancel-timer'."
+ (interactive "sRun at time: \nNRepeat interval: \naFunction: ")
+
+ (when (and repeat
+ (numberp repeat)
+ (< repeat 0))
+ (error "Invalid repetition interval"))
+
+ (let ((timer (timer-create)))
+ ;; Special case: nil means "now" and is useful when repeating.
+ (unless time
+ (setq time (current-time)))
+
+ ;; Special case: t means the next integral multiple of REPEAT.
+ (when (and (eq time t) repeat)
+ (setq time (timer-next-integral-multiple-of-time nil repeat))
+ (setf (timer--integral-multiple timer) t))
+
+ ;; Handle numbers as relative times in seconds.
+ (when (numberp time)
+ (setq time (timer-relative-time nil time)))
+
+ ;; Handle relative times like "2 hours 35 minutes".
+ (when (stringp time)
+ (when-let ((secs (timer-duration time)))
+ (setq time (timer-relative-time nil secs))))
+
+ ;; Handle "11:23pm" and the like. Interpret it as meaning today
+ ;; which admittedly is rather stupid if we have passed that time
+ ;; already. (Though only Emacs hackers hack Emacs at that time.)
+ (when (stringp time)
+ (require 'diary-lib)
+ (let ((hhmm (diary-entry-time time))
+ (now (decode-time)))
+ (when (>= hhmm 0)
+ (setq time (encode-time 0 (% hhmm 100) (/ hhmm 100)
+ (decoded-time-day now)
+ (decoded-time-month now)
+ (decoded-time-year now)
+ (decoded-time-zone now))))))
+
+ (timer-set-time timer time repeat)
+ (timer-set-function timer function args)
+ (timer-activate timer)
+ timer))
+
+(defun run-with-timer (secs repeat function &rest args)
+ "Perform an action after a delay of SECS seconds.
+Repeat the action every REPEAT seconds, if REPEAT is non-nil.
+SECS and REPEAT may be integers or floating point numbers.
+The action is to call FUNCTION with arguments ARGS.
+
+This function returns a timer object which you can use in `cancel-timer'."
+ (interactive "sRun after delay (seconds): \nNRepeat interval: \naFunction: ")
+ (apply #'run-at-time secs repeat function args))
+
+(defun add-timeout (secs function object &optional repeat)
+ "Add a timer to run SECS seconds from now, to call FUNCTION on OBJECT.
+If REPEAT is non-nil, repeat the timer every REPEAT seconds.
+
+This function returns a timer object which you can use in `cancel-timer'.
+This function is for compatibility; see also `run-with-timer'."
+ (run-with-timer secs repeat function object))
+
+(defun 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.
+SECS may be an integer, a floating point number, or the internal
+time format returned by, e.g., `current-idle-time'.
+If Emacs is currently idle, and has been idle for N seconds (N < SECS),
+then it will call FUNCTION in SECS - N seconds from now. Using
+SECS <= N is not recommended if this function is invoked from an idle
+timer, because FUNCTION will then be called immediately.
+
+If REPEAT is non-nil, do the action each time Emacs has been idle for
+exactly SECS seconds (that is, only once for each time Emacs becomes idle).
+
+This function returns a timer object which you can use in `cancel-timer'."
+ (interactive
+ (list (read-from-minibuffer "Run after idle (seconds): " nil nil t)
+ (y-or-n-p "Repeat each time Emacs is idle? ")
+ (intern (completing-read "Function: " obarray #'fboundp t))))
+ (let ((timer (timer-create)))
+ (timer-set-function timer function args)
+ (timer-set-idle-time timer secs repeat)
+ (timer-activate-when-idle timer t)
+ timer))
+
+(defvar with-timeout-timers nil
+ "List of all timers used by currently pending `with-timeout' calls.")
+
+(defmacro with-timeout (list &rest body)
+ "Run BODY, but if it doesn't finish in SECONDS seconds, give up.
+If we give up, we run the TIMEOUT-FORMS and return the value of the last one.
+The timeout is checked whenever Emacs waits for some kind of external
+event (such as keyboard input, input from subprocesses, or a certain time);
+if the program loops without waiting in any way, the timeout will not
+be detected.
+\n(fn (SECONDS TIMEOUT-FORMS...) BODY)"
+ (declare (indent 1) (debug ((form body) body)))
+ (let ((seconds (car list))
+ (timeout-forms (cdr list))
+ (timeout (make-symbol "timeout")))
+ `(let ((-with-timeout-value-
+ (catch ',timeout
+ (let* ((-with-timeout-timer-
+ (run-with-timer ,seconds nil
+ (lambda () (throw ',timeout ',timeout))))
+ (with-timeout-timers
+ (cons -with-timeout-timer- with-timeout-timers)))
+ (unwind-protect
+ (progn ,@body)
+ (cancel-timer -with-timeout-timer-))))))
+ ;; It is tempting to avoid the `if' altogether and instead run
+ ;; timeout-forms in the timer, just before throwing `timeout'.
+ ;; But that would mean that timeout-forms are run in the deeper
+ ;; dynamic context of the timer, with inhibit-quit set etc...
+ (if (eq -with-timeout-value- ',timeout)
+ (progn ,@timeout-forms)
+ -with-timeout-value-))))
+
+(defun with-timeout-suspend ()
+ "Stop the clock for `with-timeout'. Used by debuggers.
+The idea is that the time you spend in the debugger should not
+count against these timeouts.
+
+The value is a list that the debugger can pass to `with-timeout-unsuspend'
+when it exits, to make these timers start counting again."
+ (mapcar (lambda (timer)
+ (cancel-timer timer)
+ (list timer (time-subtract (timer--time timer) nil)))
+ with-timeout-timers))
+
+(defun with-timeout-unsuspend (timer-spec-list)
+ "Restart the clock for `with-timeout'.
+The argument should be a value previously returned by `with-timeout-suspend'."
+ (dolist (elt timer-spec-list)
+ (let ((timer (car elt))
+ (delay (cadr elt)))
+ (timer-set-time timer (time-add nil delay))
+ (timer-activate timer))))
+
+(defun y-or-n-p-with-timeout (prompt seconds default-value)
+ "Like (y-or-n-p PROMPT), with a timeout.
+If the user does not answer after SECONDS seconds, return DEFAULT-VALUE."
+ (with-timeout (seconds default-value)
+ (y-or-n-p prompt)))
+
+(defconst timer-duration-words
+ (list (cons "microsec" 0.000001)
+ (cons "microsecond" 0.000001)
+ (cons "millisec" 0.001)
+ (cons "millisecond" 0.001)
+ (cons "sec" 1)
+ (cons "second" 1)
+ (cons "min" 60)
+ (cons "minute" 60)
+ (cons "hour" (* 60 60))
+ (cons "day" (* 24 60 60))
+ (cons "week" (* 7 24 60 60))
+ (cons "fortnight" (* 14 24 60 60))
+ (cons "month" (* 30 24 60 60)) ; Approximation
+ (cons "year" (* 365.25 24 60 60)) ; Approximation
+ )
+ "Alist mapping temporal words to durations in seconds.")
+
+(defun timer-duration (string)
+ "Return number of seconds specified by STRING, or nil if parsing fails."
+ (let ((secs 0)
+ (start 0)
+ (case-fold-search t))
+ (while (string-match
+ "[ \t]*\\([0-9.]+\\)?[ \t]*\\([a-z]+[a-rt-z]\\)s?[ \t]*"
+ string start)
+ (let ((count (if (match-beginning 1)
+ (string-to-number (match-string 1 string))
+ 1))
+ (itemsize (cdr (assoc (match-string 2 string)
+ timer-duration-words))))
+ (if itemsize
+ (setq start (match-end 0)
+ secs (+ secs (* count itemsize)))
+ (setq secs nil
+ start (length string)))))
+ (if (= start (length string))
+ secs
+ (if (string-match-p "\\`[0-9.]+\\'" string)
+ (string-to-number string)))))
+
+(defun internal-timer-start-idle ()
+ "Mark all idle-time timers as once again candidates for running."
+ (dolist (timer timer-idle-list)
+ (if (timerp timer) ;; FIXME: Why test?
+ (setf (timer--triggered timer) nil))))
+
+(provide 'timer)
+
+;;; timer.el ends here