5

As part of my custom mode-line, I've included a feature that displays a countdown timer (timers are created using the chronos package) when it recognises that there is one running.

Works great, but the problem I have is that the mode-line only refreshes when I enter keypresses into Emacs. It will not update in the background (say I work in the browser for a bit but Emacs is still visible).

Is there any good way to force the mode-line to redisplay each second, so that the timer display stays up to date?

Screenshot of mode-line Screenshot

Relevant code This is appended to the format-mode-line list:

(if (>= (length chronos--timers-list) 2)
    (list " timer|" (propertize (my-timer-display) 'face 'my-red-face) "| ")
  "")

The my-timer-display decides whether or not to show the timer. I think this might be the best place to get Emacs to start forcing redisplays.

(defun my-timer-display ()
  "Return the lowest timer, avoiding the --now-- time, in the chronos timers list"
  (if (string= (chronos--message (nth 0 (chronos--sort-by-expiry))) chronos-now-message)
      (my-print-timer 1)
    (my-print-timer 0)))

The my-print-timer function referenced just returns the xth timer in the chronos timers list. I won't include it as I don't think it's relevant to the question.

Drew
  • 75,699
  • 9
  • 109
  • 225
jamesmaj
  • 341
  • 1
  • 8

2 Answers2

3

I try not to use standard timers that repeat because they can affect performance while typing or scrolling. An idle-timer only fires once each time an idle occurs. Therefore, I would suggest setting up a system that runs an idle timer while Emacs has focus and a standard timer when Emacs loses focus. Since that is outside the scope of the question, I will leave that to the O.P. to investigate ...

To try out the following example, start with emacs -Q, open two or more windows and evaluate the following two lines of code. E.g., place both lines of code in the *scratch* buffer and type M-x eval-buffer.

The extra set of parentheses used in the example as part of the mode-line-format is not necessary for this example, but is helpful to remind us that we can include additional components of the mode-line (as elements of the list) if so desired.

Note that this example uses the optional argument for force-mode-line-update to operate on all visible windows.

(setq-default mode-line-format '((:eval (format-time-string "%m/%d/%Y @ %1I:%M:%S %p"))))

(run-with-timer 0 1 #'(lambda () (force-mode-line-update t)))
lawlist
  • 18,826
  • 5
  • 37
  • 118
  • After learning about timer.el from this answer decided to implement a solution like so: ` (progn` ` (cancel-function-timers 'force-mode-line-update)` ` (run-at-time 0.5 nil '(lambda () (force-mode-line-update t)))` ` (list " timer|" (propertize (my-timer-display) 'face 'my-red-face) "| "))` This fits in the true condition for the `mode-line-format` list. – jamesmaj Jan 02 '19 at 04:58
  • Unsure on stackoverflow ettiquite. Should I mark this as the answer, or should I create my own detailed answer and give credit to this answer? – jamesmaj Jan 02 '19 at 05:06
  • I will not be saddened if you write-up your own alternative answer. If you think that the question title and/or body need to be clarified so that this thread helps future forum searches, then please feel free to do so. – lawlist Jan 02 '19 at 05:07
2

Thanks to @lawlist for directing me to timer.el. A solution I implemented uses timer.el's run-at-time.

I replaced the format-line-list item with:

(if (< (length chronos--timers-list) 2)
    ""
  (cancel-function-timers 'force-mode-line-update)
  (run-at-time 0.5 nil '(lambda () (force-mode-line-update t)))
  (list " timer|" (propertize (my-timer-display) 'face 'my-red-face) "| ")))

This means that if a timer is running, I ask Emacs to update the mode-line in another 0.5s. This sets a timer. To prevent timers building up if I trigger multiple mode-line refreshes within the 0.5s interval I delete any previous timers first (cancel-function-timers).

I found that if I used a 1s interval, it would sometimes skip a second if I was typing in Emacs. Reducing to 0.5s makes it act/feel smoother.

Drew
  • 75,699
  • 9
  • 109
  • 225
jamesmaj
  • 341
  • 1
  • 8