;;; modeline.el -*- lexical-binding: t; -*- ;;; ;;; Author: Jakob L. Kreuze ;;; URL: https://git.sr.ht/~jakob/.emacs.d/ ;;; ;;; My custom modeline for Emacs, which seeks to fit the dual role of a ;;; functional status indicator for Emacs, as well as a "bar" in the sense of a ;;; traditional tiling window manager. ;;; ;; My implementation of a clock for the modeline. The modeline is refreshed by a ;; timer to ensure that it doesn't go too stale, as it could theoretically go ;; several minutes without being updated. (defun mode-line-time () (format-time-string "%Y-%m-%d %H:%M")) (let ((timer (run-at-time t 60.0 #'ignore))) (timer-set-function timer (lambda () (force-mode-line-update)))) ;; For some time, I was using my own implementation of a modeline battery ;; monitor. However, the constant calls to `battery-linux-sysfs' were ;; interfering with the way that EXWM handles input focus, and I've since moved ;; towards reusing `display-battery-mode'. Because strings in `mode-line-format' ;; can contain control sequences, the percent symbol has to be escaped twice -- ;; once for `battery-format', and once for `mode-line-format'. ;; ;; Obviously, my tower lacks a battery, so I only enable this on my laptop. (expand-when (string= (system-name) "Upsilon") (require 'battery) (display-battery-mode +1) (setq battery-mode-line-format "%L: %t, %p%%%%")) (defun maybe-elide (s &optional max-length) "Truncate `S' to `MAX-LENGTH'. Return a string derived from `S', possible elided, that is at most 1MAX-LENGTH characters long." (let ((max-length (or max-length 30))) (if (> (length s) max-length) (concat (substring s 0 (- max-length 4)) " ...") s))) (defun escape-mode-line-construct (s) "Sanitize `S' for use in `mode-line-format'. Ensure that no part of S will be interpreted as a modeline format specifier." (replace-regexp-in-string (regexp-quote "%") "%%" s t t)) (defun mode-line-render (left right) "Return a string of `window-width' length containing LEFT, and RIGHT aligned respectively." (let* ((right (format-mode-line right)) (left (maybe-elide (format-mode-line left) (- (window-width) (length right) 1))) (padding (- (window-width) (length left) (length right) 1)) (padding (if (wholenump padding) (make-string padding ?\ )))) (if padding (escape-mode-line-construct (concat " " left padding right)) (concat " " left right)))) (defun remove-unicode (s) "Remove non-ASCII or non-printable ASCII characters from `S'." (apply #'string (seq-filter #'(lambda (c) (and (>= c ? ) (<= c ?~))) s))) (defvar mode-line-time-tracking nil) (defvar mode-line-time-tracking-counter -1) (defvar mode-line-time-tracking-timer nil) (defun mode-line-time-tracking-update () (setq mode-line-time-tracking-counter (+ 1 mode-line-time-tracking-counter)) (setq mode-line-time-tracking (format "[%.2d:%.2d] %s" (floor (/ mode-line-time-tracking-counter 60)) (floor (% mode-line-time-tracking-counter 60)) org-clock-heading))) (defun mode-line-time-tracking-enable () (setq mode-line-time-tracking-counter -1) (setq mode-line-time-tracking-timer (run-at-time nil 60 #'mode-line-time-tracking-update))) (defun mode-line-time-tracking-disable () (setq mode-line-time-tracking "") (cancel-timer mode-line-time-tracking-timer)) (add-hook 'org-clock-in-hook #'mode-line-time-tracking-enable) (add-hook 'org-clock-out-hook #'mode-line-time-tracking-disable) (setq-default mode-line-format '((:eval (mode-line-render (cond ((eq major-mode 'exwm-mode) (concat "%b: " (remove-unicode exwm-title))) ((eq major-mode 'pdf-view-mode) (concat "%b [" (number-to-string (pdf-view-current-page)) "/" (number-to-string (pdf-cache-number-of-pages)) "]")) ((and (eq major-mode 'shell-mode) (boundp 'shell-title)) (concat "%b [%m: " shell-title "] [%*] Ln: %l/" (number-to-string (1+ (count-lines 1 (point-max)))) " Col: %c")) ((and (eq major-mode 'vterm-mode) (boundp 'vterm-buffer-titles) (gethash (current-buffer) vterm-buffer-titles)) (concat "%b [%m: " (gethash (current-buffer) vterm-buffer-titles) "] [%*] Ln: %l/" (number-to-string (1+ (count-lines 1 (point-max)))) " Col: %c")) ((and (eq major-mode 'eww-mode) (boundp 'eww-data)) (concat (plist-get eww-data :title) " [%m] [%*] Ln: %l/" (number-to-string (1+ (count-lines 1 (point-max)))) " Col: %c")) (t (concat "%b [%m] [%*] Ln: %l/" (number-to-string (1+ (count-lines 1 (point-max)))) " Col: %c"))) mode-line-time-tracking))))