summaryrefslogtreecommitdiff
path: root/lisp/appearance/modeline.el
blob: 9dde6c6eba4b930f2a06da7b043498246540c971 (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
;;; 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)))

(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"))
                        (t
                         (concat "%b [%m] [%*] Ln: %l/"
                            (number-to-string (1+ (count-lines 1 (point-max))))
                            " Col: %c")))
                  (cond ((string= (system-name) "Upsilon")
                         (format "%s | %s"
                                 (mode-line-time)
                                 battery-mode-line-string))
                        (t (mode-line-time)))))))

© 2015 - 2026 Jakob L. Kreuze