Say if I’ve a buffer, is there a package or hack to track time that’s spent typing/editing on this buffer (ignoring idle time)?

  • I think it might be possible to do this with the built-in type-break-mode, whose purpose is to schedule typing breaks for the user. Have a read through M-x find-library RET type-break RET or M-x customize-group RET type-break RET. – Basil Apr 26 '19 at 11:04
  • @Basil I think type-break-mode is a global thing. You cannot make it buffer-local. – Tobias Apr 26 '19 at 11:29
  • @Tobias Sure, but it might provide some machinery that can be reused; I was just speculating. – Basil Apr 26 '19 at 11:34

The minor mode buftime-mode defined below measures the active buffer time. By active buffer time I mean the time you are running commands interactively in the buffer.
The modeline displays the active buffer time in seconds as minor mode lighter in angular braces.

Note, that measuring the active buffer time is no replacement for work time clocking like org-clock-in of package org or timeclock-in of built-in package calendar.

For an instance if you stop typing to overthink what you have typed that is work-time but it is not included in the active buffer time.

(require 'type-break) ;; for `timep'

(defvar-local buftime-start nil
  "Time at which `buftime-timer' started.
The timer is started in `pre-command-hook'.")

(defvar-local buftime-time nil
  "Time during which the current buffer was active in seconds.")

(defvar-local buftime-timer nil
  "Timer stopping measurement of the active buffer time.")

(defvar-local buftime-cancelled-timer nil
  "Timer cons returned by `timer-cancel'.")

(defcustom buftime-idle-time 1
  "Idle time before `buftime-timer' fires."
  :type 'number
  :group 'buftime)

(defvar buftime-string ""
  "String showing the active time in current buffer.")

(defun buftime-update-string ()
  "Update `buftime-string' for `global-mode-string'."
  (if (timep buftime-time)
      (setq buftime-string (format-time-string "<%s>" buftime-time))
    (setq buftime-string ""))

(defun buftime-start ()
  "Start `buftime-timer' measuring active buffer time.
Use this function for `pre-command-hook'."
  (unless (eq this-command 'ignore)
    (unless buftime-timer
      (setq buftime-timer (timer-create))
      (timer-set-function buftime-timer #'buftime-stop))
    (let ((time (current-time)))
      (timer-set-time buftime-timer (timer-relative-time time buftime-idle-time))
      (unless (memq buftime-timer timer-list)
    (timer-activate buftime-timer))
      (unless buftime-start
    (setq buftime-start time)))

(defun buftime-stop ()
  "Timer function for `buftime-timer'.
Stops measurement for active buffer time."
  (when (timep buftime-start)
    (let ((timediff (time-subtract (current-time) buftime-start)))
      (setq buftime-time (if (timep buftime-time)
                 (time-add buftime-time timediff)
        buftime-start nil)
      ;; We do not need to cancel the timer since it does not repeat.

(define-minor-mode buftime-mode
  "Minor mode for measuring the active buffer time.
Active buffer time starts with any command running with the buffer being active.
It starts after the buffer was idle for buftime-idle-time seconds."
  :lighter (" " buftime-string)
  (if buftime-mode
    (add-hook 'pre-command-hook #'buftime-start nil t)
    (remove-hook 'pre-command-hook #'buftime-start t)))

The stuff is new and only coarsely tested with Emacs 26.1.


Depending on how automatic you want it, it may be difficult. You could consider using the clock-in/-out mechanism for org-mode, and that will let you track the time, and can handle idle time.

You may even be able to write a function for a hook that automatically clocks in/out when you switch buffers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.