summaryrefslogtreecommitdiff
path: root/lisp/appearance/modeline.el
blob: b598ccd2d1853f207b29bede06189aec345af356 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
;;; modeline.el -*- lexical-binding: t; -*-
;;;
;;; Author:  Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;; 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))))

© 2015 - 2026 Jakob L. Kreuze