doomemacs/modules/ui/modeline/+light.el
Henrik Lissner f236d27d51
ui/modeline: add +light feature
This will one day replace the module.
2019-12-13 20:07:41 -05:00

476 lines
18 KiB
EmacsLisp

;;; ui/modeline/+default.el -*- lexical-binding: t; -*-
(defvar +modeline-height 33)
(defvar +modeline-bar-width 3)
;; This is a slimmed down version of `doom-modeline' that manipulates
;; `mode-line-format' directly. Its purpose is to be a *significantly* lighter
;; modeline for doom. Upstream has generalized and grown too much so I've
;; returned to the roots.
;;
;; TODO Refactor me
(defface +modeline-success-highlight '((t (:inherit mode-line-highlight)))
"TODO")
(defvar +modeline--redisplayed-p nil)
(defadvice! modeline-recalculate-height-a (&optional _force &rest _ignored)
:before '(fit-window-to-buffer resize-temp-buffer-window)
(unless +modeline--redisplayed-p
(setq-local +modeline--redisplayed-p t)
(redisplay t)))
;;; `active'
(defvar selected-window (selected-window))
(defun active () (eq (selected-window) selected-window))
(add-hook! 'pre-redisplay-functions
(defun set-selected-window (&rest _)
"Set the variable `selected-window' appropriately."
(let ((win (selected-window)))
(unless (minibuffer-window-active-p win)
(setq selected-window (frame-selected-window))))))
;;; Helpers
(defun +modeline--make-xpm (color width height)
"Create an XPM bitmap via COLOR, WIDTH and HEIGHT. Inspired by `powerline''s `pl/+modeline--make-xpm'."
(propertize
" " 'display
(let ((data (make-list height (make-list width 1)))
(color (or color "None")))
(ignore-errors
(create-image
(concat
(format "/* XPM */\nstatic char * percent[] = {\n\"%i %i 2 1\",\n\". c %s\",\n\" c %s\","
(length (car data))
(length data)
color
color)
(apply #'concat
(cl-loop with idx = 0
with len = (length data)
for dl in data
do (cl-incf idx)
collect
(concat "\""
(cl-loop for d in dl
if (= d 0) collect (string-to-char " ")
else collect (string-to-char "."))
(if (eq idx len) "\"};" "\",\n")))))
'xpm t :ascent 'center)))))
(defun +modeline-format-icon (icon label &optional face help-echo voffset)
(propertize (concat (all-the-icons-material
icon
:face face
:height 1.1
:v-adjust (or voffset -0.225))
(propertize label 'face face))
'help-echo help-echo))
;;
;;; Segments
;;; `+modeline-bar'
(defvar +modeline-bar "")
(defvar +modeline-inactive-bar "")
(put '+modeline-bar 'risky-local-variable t)
(put '+modeline-inactive-bar 'risky-local-variable t)
(defface +modeline-bar '((t (:inherit highlight)))
"The face used for the left-most bar on the mode-line of an active window.")
(defface +modeline-bar-inactive '((t (:inherit mode-line-inactive)))
"The face used for the left-most bar on the mode-line of an inactive window.")
(add-hook! 'doom-load-theme-hook
(defun +modeline-refresh-bars-h ()
(let ((width (or +modeline-bar-width 1)))
(setq +modeline-bar
(+modeline--make-xpm (if +modeline-bar-width (face-background '+modeline-bar nil 'inherit))
width (max +modeline-height (frame-char-height)))
+modeline-inactive-bar
(+modeline--make-xpm (if +modeline-bar-width (face-background '+modeline-bar-inactive nil 'inherit))
width (max +modeline-height (frame-char-height)))))))
(defvar +modeline--old-height nil)
(defun +modeline-adjust-height-h ()
(unless +modeline--old-height
(setq +modeline--old-height +modeline-height))
(let ((default-height +modeline--old-height)
(scale (or (frame-parameter nil 'font-scale) 0)))
(if (> scale 0)
(let* ((font-size (string-to-number
(aref (doom--font-name (frame-parameter nil 'font)
(selected-frame))
xlfd-regexp-pixelsize-subnum)))
(scale (frame-parameter nil 'font-scale)))
(setq +modeline-height (+ default-height (* scale doom-font-increment))))
(setq +modeline-height default-height))
(setq +modeline-bar (+modeline--make-xpm nil 1 +modeline-height))))
(add-hook 'doom-change-font-size-hook #'+modeline-adjust-height-h)
;;; `+modeline-matches'
(progn
(use-package! anzu
:after-call isearch-mode
:config
;; anzu and evil-anzu expose current/total state that can be displayed in the
;; mode-line.
(defun doom-modeline-fix-anzu-count (positions here)
"Calulate anzu counts via POSITIONS and HERE."
(cl-loop for (start . end) in positions
collect t into before
when (and (>= here start) (<= here end))
return (length before)
finally return 0))
(advice-add #'anzu--where-is-here :override #'doom-modeline-fix-anzu-count)
(setq anzu-cons-mode-line-p nil) ; manage modeline segment ourselves
;; Ensure anzu state is cleared when searches & iedit are done
(add-hook 'isearch-mode-end-hook #'anzu--reset-status t)
(add-hook 'iedit-mode-end-hook #'anzu--reset-status)
(advice-add #'evil-force-normal-state :before #'anzu--reset-status)
;; Fix matches segment mirroring across all buffers
(mapc #'make-variable-buffer-local
'(anzu--total-matched anzu--current-position anzu--state
anzu--cached-count anzu--cached-positions anzu--last-command
anzu--last-isearch-string anzu--overflow-p)))
(use-package! evil-anzu
:when (featurep! :editor evil)
:after-call (evil-ex-start-search evil-ex-start-word-search evil-ex-search-activate-highlight))
(defun +modeline--anzu ()
"Show the match index and total number thereof.
Requires `anzu', also `evil-anzu' if using `evil-mode' for compatibility with
`evil-search'."
(when (and (bound-and-true-p anzu--state)
(not (bound-and-true-p iedit-mode)))
(propertize
(let ((here anzu--current-position)
(total anzu--total-matched))
(cond ((eq anzu--state 'replace-query)
(format " %d replace " anzu--cached-count))
((eq anzu--state 'replace)
(format " %d/%d " here total))
(anzu--overflow-p
(format " %s+ " total))
(t
(format " %s/%d " here total))))
'face (if (active) 'mode-line-highlight))))
(defun +modeline--evil-substitute ()
"Show number of matches for evil-ex substitutions and highlights in real time."
(when (and (bound-and-true-p evil-local-mode)
(or (assq 'evil-ex-substitute evil-ex-active-highlights-alist)
(assq 'evil-ex-global-match evil-ex-active-highlights-alist)
(assq 'evil-ex-buffer-match evil-ex-active-highlights-alist)))
(propertize
(let ((range (if evil-ex-range
(cons (car evil-ex-range) (cadr evil-ex-range))
(cons (line-beginning-position) (line-end-position))))
(pattern (car-safe (evil-delimited-arguments evil-ex-argument 2))))
(if pattern
(format " %s matches " (how-many pattern (car range) (cdr range)))
" - "))
'face (if (active) 'mode-line-highlight))))
(defun +mode-line--multiple-cursors ()
"Show the number of multiple cursors."
(when (bound-and-true-p evil-mc-cursor-list)
(let ((count (length evil-mc-cursor-list)))
(when (> count 0)
(let ((face (cond ((not (active)) 'mode-line-inactive)
(evil-mc-frozen 'mode-line-highlight)
('+modeline-success-highlight))))
(concat (propertize " " 'face face)
(all-the-icons-faicon "i-cursor" :face face :v-adjust -0.0575)
(propertize " " 'face `(:inherit (variable-pitch ,face)))
(propertize (format "%d " count)
'face face)))))))
(defun mode-line--overlay< (a b)
"Sort overlay A and B."
(< (overlay-start a) (overlay-start b)))
(defun +modeline--iedit ()
"Show the number of iedit regions matches + what match you're on."
(when (and (bound-and-true-p iedit-mode)
(bound-and-true-p iedit-occurrences-overlays))
(propertize
(let ((this-oc (or (let ((inhibit-message t))
(iedit-find-current-occurrence-overlay))
(save-excursion
(iedit-prev-occurrence)
(iedit-find-current-occurrence-overlay))))
(length (length iedit-occurrences-overlays)))
(format " %s/%d "
(if this-oc
(- length
(length (memq this-oc (sort (append iedit-occurrences-overlays nil)
#'mode-line--overlay<)))
-1)
"-")
length))
'face (if (active) 'mode-line-highlight))))
(defun +modeline--macro-recording ()
"Display current Emacs or evil macro being recorded."
(when (and (active)
(or defining-kbd-macro
executing-kbd-macro))
(let ((sep (propertize " " 'face 'mode-line-highlight)))
(concat sep
(propertize (if (bound-and-true-p evil-this-macro)
(char-to-string evil-this-macro)
"Macro")
'face 'mode-line-highlight)
sep
(all-the-icons-octicon "triangle-right"
:face 'mode-line-highlight
:v-adjust -0.05)
sep))))
(defvar +modeline-matches
'(:eval
(let ((meta (concat (+modeline--macro-recording)
(+modeline--anzu)
(+modeline--evil-substitute)
(+modeline--iedit)
(+mode-line--multiple-cursors))))
(or (and (not (equal meta "")) meta)
" %I "))))
(put '+modeline-matches 'risky-local-variable t))
;;; `+modeline-modes'
(defvar +modeline-modes ; remove minor modes
'(""
(:propertize mode-name
face bold
mouse-face mode-line-highlight)
mode-line-process
"%n"
"%]"
" "))
;;; `+modeline-buffer-identification'
(defconst +modeline-buffer-identification ; slightly more informative buffer id
'((:eval
(propertize
(let ((buffer-file-name (buffer-file-name (buffer-base-buffer))))
(or (when buffer-file-name
(if-let (project (doom-project-root buffer-file-name))
(let ((filename (or buffer-file-truename (file-truename buffer-file-name))))
(file-relative-name filename (concat project "..")))))
"%b"))
'face (cond ((buffer-modified-p)
'(error bold mode-line-buffer-id))
((active)
'mode-line-buffer-id))
'help-echo buffer-file-name))
(buffer-read-only (:propertize " RO" face warning))))
;;; `+modeline-position'
(defvar +modeline-position '(" %l:%C %p "))
;;; `+modeline-checker'
(defvar-local +modeline-checker nil
"Displays color-coded error status in the current buffer with pretty
icons.")
(put '+modeline-checker 'risky-local-variable t)
(defun +modeline-checker-update (&optional status)
"Update flycheck text via STATUS."
(setq +modeline-checker
(pcase status
(`finished
(if flycheck-current-errors
(let-alist (flycheck-count-errors flycheck-current-errors)
(let ((error (or .error 0))
(warning (or .warning 0))
(info (or .info 0)))
(+modeline-format-icon "do_not_disturb_alt"
(number-to-string (+ error warning info))
(cond ((> error 0) 'error)
((> warning 0) 'warning)
('success))
(format "Errors: %d, Warnings: %d, Debug: %d"
error
warning
info))))
(+modeline-format-icon "check" "" 'success)))
(`running (+modeline-format-icon "access_time" "*" 'font-lock-comment-face "Running..."))
(`errored (+modeline-format-icon "sim_card_alert" "!" 'error "Errored!"))
(`interrupted (+modeline-format-icon "pause" "!" 'font-lock-comment-face "Interrupted"))
(`suspicious (+modeline-format-icon "priority_high" "!" 'error "Suspicious")))))
(add-hook 'flycheck-status-changed-functions #'+modeline-checker-update)
(add-hook 'flycheck-mode-hook #'+modeline-checker-update)
;;; `+modeline-selection-info'
(defsubst doom-modeline-column (pos)
"Get the column of the position `POS'."
(save-excursion (goto-char pos)
(current-column)))
(defun add-selection-segment ()
(add-to-list '+modeline-format-left '+modeline-selection-info 'append))
(defun remove-selection-segment ()
(delq! '+modeline-selection-info +modeline-format-left))
(if (featurep 'evil)
(progn
(add-hook 'evil-visual-state-entry-hook #'add-selection-segment)
(add-hook 'evil-visual-state-exit-hook #'remove-selection-segment))
(add-hook 'activate-mark-hook #'add-selection-segment)
(add-hook 'deactivate-mark-hook #'remove-selection-segment))
(defvar +modeline-selection-info
'(:eval
(when (or mark-active
(and (bound-and-true-p evil-local-mode)
(eq evil-state 'visual)))
(cl-destructuring-bind (beg . end)
(if (boundp 'evil-local-mode)
(cons evil-visual-beginning evil-visual-end)
(cons (region-beginning) (region-end)))
(propertize
(let ((lines (count-lines beg (min end (point-max)))))
(concat " "
(cond ((or (bound-and-true-p rectangle-mark-mode)
(and (bound-and-true-p evil-visual-selection)
(eq 'block evil-visual-selection)))
(let ((cols (abs (- (doom-modeline-column end)
(doom-modeline-column beg)))))
(format "%dx%dB" lines cols)))
((and (bound-and-true-p evil-visual-selection)
(eq evil-visual-selection 'line))
(format "%dL" lines))
((> lines 1)
(format "%dC %dL" (- end beg) lines))
((format "%dC" (- end beg))))
(when (derived-mode-p 'text-mode)
(format " %dW" (count-words beg end)))
" "))
'face (if (active) 'success)))))
"Information about the current selection, such as how many characters and
lines are selected, or the NxM dimensions of a block selection.")
(put '+modeline-selection-info 'risky-local-variable t)
;;; `+modeline-encoding'
(defconst +modeline-encoding
'(:eval
(concat (pcase (coding-system-eol-type buffer-file-coding-system)
(0 " LF ")
(1 " RLF ")
(2 " CR "))
(let ((sys (coding-system-plist buffer-file-coding-system)))
(if (memq (plist-get sys :category)
'(coding-category-undecided coding-category-utf-8))
"UTF-8"
(upcase (symbol-name (plist-get sys :name)))))
" ")))
(put '+modeline-encoding 'risky-local-variable t)
;;
;;; Setup
(defvar-local +modeline-format-left nil)
(put '+modeline-format-left 'risky-local-variable t)
(defvar-local +modeline-format-right nil)
(put '+modeline-format-right 'risky-local-variable t)
(setq-default
+modeline-format-left
'(""
+modeline-matches
" "
+modeline-buffer-identification
+modeline-position)
+modeline-format-right
`(""
mode-line-misc-info
+modeline-modes
(vc-mode (" "
,(all-the-icons-octicon "git-branch" :v-adjust 0.0)
vc-mode " "))
" "
+modeline-encoding
(+modeline-checker ("" +modeline-checker " ")))
;;
mode-line-format
'(""
+modeline-bar
+modeline-format-left
(:eval
(propertize
" "
'display
`((space :align-to (- (+ right right-fringe right-margin)
,(string-width
(format-mode-line '("" +modeline-format-right))))))))
+modeline-format-right))
(with-current-buffer "*Messages*"
(setq mode-line-format (default-value 'mode-line-format)))
;;
;;; Other modelines
(defun set-project-modeline ()
(setq +modeline-format-left
`(" "
,(all-the-icons-octicon
"file-directory"
:face 'bold
:v-adjust -0.05
:height 1.25)
(:propertize (" " (:eval (abbreviate-file-name default-directory)))
face bold))
+modeline-format-right
'("" +modeline-modes)))
(defun set-special-modeline ()
(setq +modeline-format-left
'(""
+modeline-matches
" "
+modeline-buffer-identification)
+modeline-format-right
'("" +modeline-modes)))
(defun set-pdf-modeline ()) ; TODO `set-pdf-modeline'
;;
;;; Bootstrap
(size-indication-mode +1) ; filesize in modeline
(add-hook '+doom-dashboard-mode-hook #'set-project-modeline)
(add-hook 'doom-load-theme-hook #'+modeline-refresh-bars-h)
;; Other modes
(defun set-modeline-in-magit ()
(if (eq major-mode 'magit-status-mode)
(set-project-modeline)
(hide-mode-line-mode)))
(add-hook 'magit-mode-hook #'set-modeline-in-magit)
(add-hook 'special-mode-hook #'set-special-modeline)
(add-hook 'image-mode-hook #'set-special-modeline)
(add-hook 'circe-mode-hook #'set-special-modeline)
(add-hook 'pdf-tools-enabled-hook #'set-pdf-modeline)