summaryrefslogtreecommitdiffstats
path: root/emacs/user-lisp/modeline.el
blob: 627584cfaac76f010797ca4fe8d65cb0c9213639 (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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
;; -*- lexical-binding: t; -*-

(require 'cl-lib)
(require 'subr-x)

(defvar my/ml-icons t
  "Non-nil uses Nerd Font glyphs, nil falls back to ASCII.")

;; Transparent background, white text
(set-face-attribute 'mode-line nil
                    :background "unspecified-bg" :foreground "white"
                    :box nil :inverse-video nil :underline nil :overline nil)
(set-face-attribute 'mode-line-inactive nil
                    :background "unspecified-bg" :foreground "brightblack"
                    :box nil :inverse-video nil :underline nil :overline nil)

;;;; git diff counts (+added ~modified -removed), refreshed on open and save
(defvar-local my/ml-diff-stat nil)

(defun my/ml-diff-update ()
  (setq my/ml-diff-stat nil)
  (when (and buffer-file-name
             (not (file-remote-p buffer-file-name))
             (eq (vc-backend buffer-file-name) 'Git))
    (let ((file buffer-file-name)
          (default-directory (file-name-directory buffer-file-name))
          (a 0) (m 0) (r 0))
      (with-temp-buffer
        (when (eq 0 (process-file "git" nil t nil "diff" "-U0" "--no-color"
                                  "--no-ext-diff" "--" file))
          (goto-char (point-min))
          (while (re-search-forward
                  "^@@ -[0-9]+\\(?:,\\([0-9]+\\)\\)? \\+[0-9]+\\(?:,\\([0-9]+\\)\\)? @@"
                  nil t)
            (let ((o (if (match-string 1) (string-to-number (match-string 1)) 1))
                  (n (if (match-string 2) (string-to-number (match-string 2)) 1)))
              (cond ((= o 0) (cl-incf a n))
                    ((= n 0) (cl-incf r o))
                    (t (cl-incf m (min o n))
                       (cl-incf a (max 0 (- n o)))
                       (cl-incf r (max 0 (- o n)))))))))
      (setq my/ml-diff-stat (list a m r)))))

(add-hook 'find-file-hook #'my/ml-diff-update)
(add-hook 'after-save-hook #'my/ml-diff-update)
(add-hook 'after-revert-hook #'my/ml-diff-update)

;;;; components
(defun my/ml-state ()
  (if (bound-and-true-p evil-local-mode) evil-state 'emacs))

(defun my/ml-block (text)
  (propertize text 'face '(:foreground "black" :background "white" :weight bold)))

(defun my/ml-mode ()
  (my/ml-block
   (format " %s "
           (pcase (my/ml-state)
             ('normal "NORMAL")
             ('insert "INSERT")
             ('visual (pcase (bound-and-true-p evil-visual-selection)
                        ('line "V-LINE") ('block "V-BLOCK") (_ "VISUAL")))
             ('replace "REPLACE")
             ('operator "O-PENDING")
             ('motion "MOTION")
             (_ "EMACS")))))

(defun my/ml-branch ()
  (when (and vc-mode (stringp vc-mode))
    (concat (if my/ml-icons "\ue0a0 " "")
            (replace-regexp-in-string "\\`[ ]*[A-Za-z]+[-:@!?]" "" vc-mode))))

(defun my/ml-diff ()
  (when my/ml-diff-stat
    (pcase-let ((`(,a ,m ,r) my/ml-diff-stat))
      (let ((parts (delq nil (list (and (> a 0) (format "+%d" a))
                                   (and (> m 0) (format "~%d" m))
                                   (and (> r 0) (format "-%d" r))))))
        (when parts (string-join parts " "))))))

(defun my/ml-diag ()
  (when (bound-and-true-p flymake-mode)
    (let ((e 0) (w 0) (i 0))
      (dolist (d (flymake-diagnostics))
        (let ((ty (flymake-diagnostic-type d)))
          (cond ((memq ty '(:error eglot-error))     (cl-incf e))
                ((memq ty '(:warning eglot-warning)) (cl-incf w))
                ((memq ty '(:note eglot-note))       (cl-incf i)))))
      (let ((parts (delq nil
                         (list (and (> e 0) (format "%s%d" (if my/ml-icons "\U000F015A " "E:") e))
                               (and (> w 0) (format "%s%d" (if my/ml-icons "\U000F002A " "W:") w))
                               (and (> i 0) (format "%s%d" (if my/ml-icons "\U000F02FD " "I:") i))))))
        (when parts (string-join parts " "))))))

(defun my/ml-file ()
  (concat (buffer-name)
          (cond (buffer-read-only " [-]")
                ((buffer-modified-p) " [+]")
                (t ""))))

(defun my/ml-encoding ()
  (replace-regexp-in-string "-\\(unix\\|dos\\|mac\\)\\'" ""
                            (symbol-name (or buffer-file-coding-system 'utf-8))))

(defun my/ml-eol ()
  (pcase (coding-system-eol-type (or buffer-file-coding-system 'utf-8-unix))
    (1 (if my/ml-icons "\ue70f" "dos"))
    (2 (if my/ml-icons "\uf179" "mac"))
    (_ (if my/ml-icons "\uf17c" "unix"))))

(defun my/ml-filetype ()
  (string-remove-suffix "-mode" (symbol-name major-mode)))

(defun my/ml-loc (block)
  (let ((s (format " %s:%-2d " (format-mode-line "%3l") (1+ (current-column)))))
    (if block (my/ml-block s) s)))

(defun my/ml-section (items)
  (let ((items (cl-remove-if (lambda (s) (or (null s) (string-empty-p s))) items)))
    (if items (concat " " (string-join items " | ") " ") "")))

;;;; assembly
(defun my/ml-format ()
  (let* ((active (mode-line-window-selected-p))
         (left (if active
                   (concat (my/ml-mode)
                           (my/ml-section (list (my/ml-branch) (my/ml-diff) (my/ml-diag)))
                           (my/ml-section (list (my/ml-file))))
                 (my/ml-section (list (my/ml-file)))))
         (right (if active
                    (concat (my/ml-section (list (my/ml-encoding) (my/ml-eol) (my/ml-filetype)))
                            (my/ml-loc t))
                  (my/ml-loc nil)))
         (fill (propertize " " 'display
                           `((space :align-to (- right ,(string-width right)))))))
    (concat left fill right)))

(setq-default mode-line-format '((:eval (my/ml-format))))

(provide 'modeline)