Emacs Configuration

Table of Contents

1 Summary

I've really been wanting to have a nicely formatted emacs config file and this is my attempt at it.

2 Required Magic

2.1 Lexical Binding

;;; -*- lexical-binding: t -*-
;;; DO NOT EDIT THIS FILE DIRECTLY
;;; EDIT ~init.org~ instead

2.2 The Magical Glue

The following auto compiles the emacs-lisp within the init.org file. Simply run `org-babel-tangle` to make it RAIN!

;; (setq byte-compile-warnings nil)
(defun tangle-init ()
  "If the current buffer is 'init.org' the code-blocks are tangled, and the tangled file is compiled."
  (when (equal (buffer-file-name)
               (expand-file-name (concat user-emacs-directory "init.org")))
    ;; Avoid running hooks when tangling.
    (let ((prog-mode-hook nil))
      (org-babel-tangle)
      (byte-compile-file (concat user-emacs-directory "init.el")))))

(add-hook 'after-save-hook 'tangle-init)

3 Config

3.1 Packages

(require 'package)

(defvar my-packages
  '(all-the-icons
    anzu
    base16-theme
    better-defaults
    company
    company-go
    counsel
    counsel-projectile
    dash-at-point
    diminish
    dockerfile-mode
    doom-themes
    ein
    eldoc-eval
    elpy
    expand-region
    fic-mode
    gitignore-mode
    go-mode
    go-playground
    gorepl-mode
    flycheck
    iedit
    ivy
    ivy-hydra
    json-mode
    magit
    material-theme
    multiple-cursors
    projectile
    py-autopep8
    rainbow-delimiters
    shrink-path
    tide
    typescript-mode
    use-package
    web-mode
    which-key))

(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/"))
(add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/"))

(when (not package-archive-contents)
  (package-refresh-contents))
(package-initialize)

(dolist (p my-packages)
  (when (not (package-installed-p p))
    (package-install p)))

3.2 Better Defaults

(require 'better-defaults)

;; Instead of the annoying giant warning icon, just flash the modeline.
;; (this happens when you do something like C-g)
(setq ring-bell-function
      (lambda ()
        (let ((orig-fg (face-foreground 'mode-line)))
          (set-face-foreground 'mode-line "#F2804F")
          (run-with-idle-timer 0.1 nil
                               (lambda (fg) (set-face-foreground 'mode-line fg))
                               orig-fg))))

(defun set-frame-size-according-to-resolution ()
  "Set the Emacs window size on startup."
  (interactive)
  (if window-system
      (progn
        ;; WIDTH
        (if (> (x-display-pixel-width) 1280)
            ;; Large Screen (only show 120 cols)
            (add-to-list 'default-frame-alist (cons 'width 240))
          ;; Small Screen (fill window)
          (add-to-list 'default-frame-alist (cons 'width (/ (x-display-pixel-width) (frame-char-width)))))

        ;; HEIGHT
        (if (> (x-display-pixel-height) 1080)
            ;; Large Screen (only fill half screen)
            (add-to-list 'default-frame-alist (cons 'height (/ (/ (x-display-pixel-height) 2)
                                                               (frame-char-height))))
          ;; Small Screen (fill window)
          (add-to-list 'default-frame-alist (cons 'height (/ (x-display-pixel-height) (frame-char-height)))))
        )))

(set-frame-size-according-to-resolution)

(defun window-px-width ()
  "Get the width of the Emacs window in pixels."
  (interactive)
  (* (* (window-total-width) 2.874) (frame-char-width)))

(defun window-px-left-pos ()
  "Calculate the left position of the Emacs window."
  (interactive)
  (/ (- (x-display-pixel-width) (window-px-width)) 2))


(add-to-list 'default-frame-alist (cons 'top 0))
(add-to-list 'default-frame-alist (cons 'left 1000))

3.3 Splash Screen

(setq inhibit-splash-screen nil
      fancy-splash-image "~/.emacs.d/public/emacs-logo.png"
      fancy-splash-image-file "~/.emacs.d/public/emacs-logo.png")

3.4 Basic Customization

(defvar backup-dir (expand-file-name "~/.emacs.d/backup/"))
(defvar autosave-dir (expand-file-name "~/.emacs.d/autosave/"))

(setq initial-scratch-message nil
      backup-directory-alist (list (cons ".*" backup-dir))
      auto-save-list-file-prefix autosave-dir
      auto-save-file-name-transforms `((".*" ,autosave-dir t)))

(menu-bar-mode 0)
(scroll-bar-mode 0)
(tool-bar-mode 0)


;; (load-theme 'doom-city-lights t)
;; (load-theme 'doom-dracula t)
;; (load-theme 'doom-nord t)
(load-theme 'doom-one t)
;; (load-theme 'doom-spacegrey t)
;; (load-theme 'base16-ocean t)
(load-theme 'base16-onedark t)
(global-linum-mode t)
(global-auto-revert-mode t)

(defalias 'yes-or-no-p 'y-or-n-p)

3.5 Tools

3.5.1 General

(require 'which-key)
(which-key-setup-minibuffer)
(which-key-mode)

(require 'fic-mode)
(add-hook 'js-mode-hook 'fic-mode)

3.5.2 Company

(require 'company)
(add-hook 'after-init-hook 'global-company-mode)

(setq company-dabbrev-downcase nil)
(setq company-idle-delay 0.1)

3.5.3 Diminish

(require 'diminish)
(diminish 'auto-revert-mode)
(eval-after-load "company" '(diminish 'company-mode))
(eval-after-load "counsel" '(diminish 'counsel-mode))
(eval-after-load "elpy" '(diminish 'elpy-mode))
(eval-after-load "go-mode" '(diminish 'go-mode))
(eval-after-load "go-playground" '(diminish 'go-playground-mode))
(eval-after-load "gorepl-mode" '(diminish 'gorepl-mode))
(eval-after-load "flycheck" '(diminish 'flycheck-mode))
(eval-after-load "ivy" '(diminish 'ivy-mode))
(eval-after-load "projectile" '(diminish 'projectile-mode))
(eval-after-load "which-key" '(diminish 'which-key-mode))

3.5.4 Dired

(defun dired-mode-setup ()
  "Will run as hook for `dired-mode'."
  (dired-hide-details-mode 1))
(add-hook 'dired-mode-hook 'dired-mode-setup)

3.5.5 Ivy

(require 'ivy-hydra)
(require 'ivy)
(require 'swiper)

(ivy-mode 1)
(counsel-mode)
(setq ivy-use-virtual-buffers t
      enable-recursive-minibuffers t
      ivy-height 25
      ivy-initial-inputs-alist nil
      ivy-extra-directories nil)

(global-set-key (kbd "C-s") 'swiper)
(global-set-key (kbd "C-c C-r") 'ivy-resume)
(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "C-x C-f") 'counsel-find-file)
(global-set-key (kbd "C-c g") 'counsel-git)
(global-set-key (kbd "C-c j") 'counsel-git-grep)
(global-set-key (kbd "C-c k") 'counsel-ag)
(define-key minibuffer-local-map (kbd "C-r") 'counsel-minibuffer-history)

(defun ivy-open-current-typed-path ()
  (interactive)
  (when ivy--directory
    (let* ((dir ivy--directory)
           (text-typed ivy-text)
           (path (concat dir text-typed)))
      (delete-minibuffer-contents)
      (ivy--done path))))

(define-key ivy-minibuffer-map (kbd "<return>") 'ivy-alt-done)
(define-key ivy-minibuffer-map (kbd "C-f") 'ivy-open-current-typed-path)

3.5.6 Magit

(require 'magit)
(global-set-key (kbd "C-x g") 'magit-status)
(global-set-key (kbd "C-c g") 'magit-status)
(setq magit-completing-read-function 'ivy-completing-read)

3.5.7 Projectile

(require 'projectile)
(require 'counsel-projectile)

(projectile-mode)
(setq projectile-mode-line '(:eval (format " %s" (projectile-project-name)))
      projectile-remember-window-configs t
      projectile-completion-system 'ivy)
(counsel-projectile-mode)

3.6 Development Specific

3.6.1 General

(require 'rainbow-delimiters)
(global-flycheck-mode)

(add-hook 'before-save-hook 'delete-trailing-whitespace)
(add-hook 'prog-mode-hook 'rainbow-delimiters-mode)

(setq-default indent-tabs-mode nil
              tab-width 4)
(defvaralias 'c-basic-offset 'tab-width)
(defvaralias 'cperl-indent-level 'tab-width)

(electric-pair-mode 1)
(show-paren-mode 1)

(require 'dockerfile-mode)
(add-to-list 'auto-mode-alist '("Dockerfile*\\'" . dockerfile-mode))

(require 'gitignore-mode)
(add-to-list 'auto-mode-alist '("gitignore\\'" . gitignore-mode))

(require 'json-mode)
(add-to-list 'auto-mode-alist '("\\.json\\'" . json-mode))

(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))

3.6.2 Python

(elpy-enable)
(setq python-shell-interpreter "jupyter"
      python-shell-interpreter-args "console --simple-prompt")

(when (require 'flycheck nil t)
  (setq elpy-modules (delq 'elpy-module-flymake elpy-modules))
  (add-hook 'elpy-mode-hook 'flycheck-mode))

(require 'py-autopep8)
(setq py-autopep8-options '("--ignore=E501"))
(add-hook 'elpy-mode-hook 'py-autopep8-enable-on-save)

3.6.3 Go

(require 'go-mode)
(require 'go-playground)
(require 'gorepl-mode)
(require 'company-go)

(add-to-list 'auto-mode-alist '("\\.go\\'" . go-mode))
(add-hook 'go-mode-hook (lambda ()
                          (add-hook 'before-save-hook 'gofmt-before-save)
                          (local-set-key (kbd "M-.") 'godef-jump)
                          (local-set-key (kbd "M-,") 'pop-tag-mark)
                          (local-set-key (kbd "C-c C-c") (lambda ()
                                                           (interactive)
                                                           (ansi-term)
                                                           (comint-send-string "*ansi-term*" "make\n")))
                          (set (make-local-variable 'company-backends) '(company-go))
                          (setq company-tooltip-limit 20
                                company-echo-delay 0
                                company-begin-commands '(self-insert-command))
                          (gorepl-mode)))
(defun set-exec-path-from-shell-PATH ()
  (let ((path-from-shell (replace-regexp-in-string
                          "[ \t\n]*$"
                          ""
                          (shell-command-to-string "$SHELL --login -i -c 'echo $PATH'"))))
    (setenv "PATH" path-from-shell)
    (setq eshell-path-env path-from-shell)
    (setq exec-path (split-string path-from-shell path-separator))))

(when window-system (set-exec-path-from-shell-PATH))

(setenv "GOPATH" "/Users/leviolson/go")
(add-to-list 'exec-path "/Users/leviolson/go/bin")

3.6.4 TypeScript

(defun setup-tide-mode ()
  "Tide setup function."
  (interactive)
  (tide-setup)
  (flycheck-mode +1)
  (setq flycheck-check-syntax-automatically '(save mode-enabled))
  (eldoc-mode +1)
  (tide-hl-identifier-mode +1)
  (company-mode +1))

;; aligns annotation to the right hand side
(setq company-tooltip-align-annotations t)

;; formats the buffer before saving
(add-hook 'before-save-hook 'tide-format-before-save)

(add-hook 'typescript-mode-hook #'setup-tide-mode)

(require 'typescript-mode)
(require 'tide)

(add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode))
(add-hook 'typescript-mode-hook
          '(lambda ()
             (set (make-local-variable 'company-backends) '(company-tide))
             (setq company-tooltip-limit 20
                   company-echo-delay 0
                   company-begin-commands '(self-insert-command)
                   tide-format-options '(:insertSpaceAfterFunctionKeywordForAnonymousFunctions t :placeOpenBraceOnNewLineForFunctions nil))
             (tide-setup)))
3.6.4.1 TSX
(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.tsx\\'" . web-mode))
(add-hook 'web-mode-hook
          (lambda ()
            (when (string-equal "tsx" (file-name-extension buffer-file-name))
              (setup-tide-mode))))
;; enable typescript-tslint checker
(flycheck-add-mode 'typescript-tslint 'web-mode)
3.6.4.2 JSX
(require 'web-mode)
(add-to-list 'auto-mode-alist '("\\.jsx\\'" . web-mode))
(add-hook 'web-mode-hook
          (lambda ()
            (when (string-equal "jsx" (file-name-extension buffer-file-name))
              (setup-tide-mode))))
;; configure jsx-tide checker to run after your default jsx checker
(flycheck-add-mode 'javascript-eslint 'web-mode)
(flycheck-add-next-checker 'javascript-eslint 'jsx-tide 'append)

3.6.5 Org

(org-babel-do-load-languages
 'org-babel-load-languages
 '((js . t)
   (shell . t)
   (emacs-lisp . t)))

(defvar org-src-tab-acts-natively)
(setq org-src-tab-acts-natively t)
;; (setenv "NODE_PATH"
;;          (getenv "NODE_PATH"))

(defvar org-confirm-babel-evaluate)

(defun my-org-confirm-babel-evaluate (lang body)
  "Execute certain languages without confirming.
      Takes LANG to allow and BODY to execute."
  (not (or (string= lang "js")
           (string= lang "restclient")
           (string= lang "emacs-lisp")
           (string= lang "shell"))))
(setq org-confirm-babel-evaluate #'my-org-confirm-babel-evaluate)
(add-to-list 'org-structure-template-alist
             (list "e" (concat "#+BEGIN_SRC emacs-lisp :results silent\n"
                               "\n"
                               "#+END_SRC")))
(add-to-list 'org-structure-template-alist
             (list "j" (concat "#+BEGIN_SRC js :cmd \"babel-node\"\n"
                               "\n"
                               "#+END_SRC")))
(add-to-list 'org-structure-template-alist
             (list "r" (concat "#+BEGIN_SRC restclient :results raw\n"
                               "\n"
                               "#+END_SRC")))

3.7 Functions

(defun find-user-init-file ()
  "Edit the `~/.emacs.d/init.org' file."
  (interactive)
  (find-file "~/.emacs.d/init.org"))

(defun load-user-init-file ()
  "LO: Reload the `~/.emacs.d/init.elc' file."
  (interactive)
  (load-file "~/.emacs.d/init.elc"))

(defun jump-to-symbol-internal (&optional backwardp)
  "Jumps to the next symbol near the point if such a symbol exists.  If BACKWARDP is non-nil it jumps backward."
  (let* ((point (point))
         (bounds (find-tag-default-bounds))
         (beg (car bounds)) (end (cdr bounds))
         (str (isearch-symbol-regexp (find-tag-default)))
         (search (if backwardp 'search-backward-regexp
                   'search-forward-regexp)))
    (goto-char (if backwardp beg end))
    (funcall search str nil t)
    (cond ((<= beg (point) end) (goto-char point))
          (backwardp (forward-char (- point beg)))
          (t  (backward-char (- end point))))))

(defun jump-to-previous-like-this ()
  "Jumps to the previous occurrence of the symbol at point."
  (interactive)
  (jump-to-symbol-internal t))

(defun jump-to-next-like-this ()
  "Jumps to the next occurrence of the symbol at point."
  (interactive)
  (jump-to-symbol-internal))

(defun match-paren (arg)
  "Go to the matching paren if on a paren; otherwise insert ARG (a literal % sign)."
  (interactive "p")
  (cond ((looking-at "\\s(") (forward-list 1))
        ((looking-back "\\s(" 2) (backward-char 1) (forward-list 1))
        ((looking-at "\\s)") (forward-char 1) (backward-list 1))
        ((looking-back "\\s)" 2) (backward-list 1))
        (t (self-insert-command (or arg 1)))))

(defun kill-this-buffer-unless-scratch ()
  "Works like `kill-this-buffer' unless the current buffer is the *scratch* buffer.  In which case the buffer content is deleted and the buffer is buried."
  (interactive)
  (if (not (string= (buffer-name) "*scratch*"))
      (kill-this-buffer)
    (delete-region (point-min) (point-max))
    (switch-to-buffer (other-buffer))
    (bury-buffer "*scratch*")))

(defun delete-backward-sentence ()
  "LO: Delete to the beginning of the sentence/line."
  (interactive)
  (delete-region (point) (progn (backward-sentence) (point))))

(defun delete-backward-to-boundary (arg)
  "LO: Delete backward to the previous word boundary.  With ARG, do this many times."
  (interactive "p")
  (let ((a (point))
        (b (progn
             (backward-word arg)
             (forward-word)
             (point))))
    (if (< a b)
        (delete-region a (progn (backward-word arg) (point)))
      (if (= a b)
          (delete-region a (progn (backward-word arg) (point)))
        (delete-region a b)))))

(defun comment-or-uncomment-region-or-line ()
  "Comments or uncomments the region or the current line if there's no active region."
  (interactive)
  (let (beg end)
    (if (region-active-p)
        (setq beg (region-beginning) end (region-end))
      (setq beg (line-beginning-position) end (line-end-position)))
    (comment-or-uncomment-region beg end)))

(defun fold-toggle (column)
  "Code folding by COLUMN."
  (interactive "P")
  (set-selective-display
   (or column
       (unless selective-display
         (1+ (current-column))))))

(defun new-line-below ()
  "LO: Create a new line below current line."
  (interactive)
  (move-end-of-line 1)
  (newline-and-indent))

(defun new-line-above ()
  "LO: Create a new line above current line."
  (interactive)
  (move-beginning-of-line 1)
  (newline)
  (forward-line -1))

(defun duplicate-thing (comment)
  "LO: Duplicates the current line, or the region if active.  If an argument (COMMENT) is given, the duplicated region will be commented out."
  (interactive "P")
  (save-excursion
    (let ((start (if (region-active-p) (region-beginning) (point-at-bol)))
          (end   (if (region-active-p) (region-end) (point-at-eol))))
      (goto-char end)
      (unless (region-active-p)
        (newline))
      (insert (buffer-substring start end))
      (when comment (comment-region start end)))))

(defun tidy ()
  "LO: Ident, untabify and unwhitespacify current buffer, or region if active."
  (interactive)
  (let ((beg (if (region-active-p) (region-beginning) (point-min)))
        (end (if (region-active-p) (region-end) (point-max))))
    (let ((inhibit-message t))
      (indent-region beg end))
    (whitespace-cleanup)
    (untabify beg (if (< end (point-max)) end (point-max)))
    (if (region-active-p) (message "Indenting Region...Done") (message "Indenting File...Done"))))

(defun phil-columns ()
  "LO: Good 'ol Phil-Columns."
  (interactive)
  (message "Good 'ol fill-columns")
  (with-output-to-temp-buffer "*PHIL-COLUMN*"
    (shell-command "mpv --no-video 'https://www.youtube.com/watch?v=YkADj0TPrJA&t=3m16s' > /dev/null 2>&1 & sleep 8; pkill mpv"))
  (other-window 1)
  (delete-window))

(declare-function first "Goto FIRST shell.")
(declare-function goto-non-shell-buffer "Goto something other than a shell buffer.")
(declare-function switch-shell "Switch shell.")

(let ((last-shell ""))
  (defun toggle-shell ()
    (interactive)
    (cond ((string-match-p "^\\*shell<[1-9][0-9]*>\\*$" (buffer-name))
           (goto-non-shell-buffer))
          ((get-buffer last-shell) (switch-to-buffer last-shell))
          (t (shell (setq last-shell "*shell<1>*")))))

  (defun switch-shell (n)
    (let ((buffer-name (format "*shell<%d>*" n)))
      (setq last-shell buffer-name)
      (cond ((get-buffer buffer-name)
             (switch-to-buffer buffer-name))
            (t (shell buffer-name)
               (rename-buffer buffer-name)))))

  (defun goto-non-shell-buffer ()
    (let* ((r "^\\*shell<[1-9][0-9]*>\\*$")
           (shell-buffer-p (lambda (b) (string-match-p r (buffer-name b))))
           (non-shells (cl-remove-if shell-buffer-p (buffer-list))))
      (when non-shells
        (switch-to-buffer (first non-shells))))))


(defadvice shell (after kill-with-no-query nil activate)
  "."
  (set-process-query-on-exit-flag (get-buffer-process ad-return-value) nil))

(declare-function comint-truncate-buffer ".")
(defun clear-comint ()
  "Run `comint-truncate-buffer' with the `comint-buffer-maximum-size' set to zero."
  (interactive)
  (let ((comint-buffer-maximum-size 0))
    (comint-truncate-buffer)))

(defun c-setup ()
  "Compile."
  (local-set-key (kbd "C-c C-c") 'compile))

3.8 Bindings

(require 'company)
(add-hook 'comint-mode-hook (lambda () (local-set-key (kbd "C-l") 'clear-comint)))
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode)
(add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)
(add-hook 'c-mode-common-hook 'c-setup)
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))

(defvar company-active-map (make-keymap)
  "Company Mode keymap.")
(defvar custom-bindings (make-keymap)
  "A keymap of custom bindings.")

(define-key global-map          (kbd "M-p")          'jump-to-previous-like-this)
(define-key global-map          (kbd "M-n")          'jump-to-next-like-this)
(define-key global-map          (kbd "M-<tab>")      'switch-to-next-buffer)
(define-key global-map          (kbd "M-<backspace>")'delete-backward-to-boundary)
(define-key global-map          (kbd "C-<backspace>")'delete-backward-to-boundary)

(global-set-key                 (kbd "C-S-<down>")   'mc/mark-next-like-this)
(global-set-key                 (kbd "C->")          'mc/mark-next-like-this-symbol)
(global-set-key                 (kbd "C-S-<up>")     'mc/mark-previous-like-this)
(global-set-key                 (kbd "C-<")          'mc/mark-previous-like-this)
(global-set-key                 (kbd "C-c C->")      'mc/mark-all-like-this)
(global-set-key                 "%"                  'match-paren)
(global-set-key                 (kbd "C-x .")        'dash-at-point)
(global-set-key                 (kbd "C-x ,")        'dash-at-point-with-docset)
(global-set-key                 (kbd "C-s")          (lambda () (interactive) (swiper (format "%s" (thing-at-point 'symbol)))))

;; (dolist (n (number-sequence 1 9))
;;   (global-set-key (kbd (concat "M-" (int-to-string n)))
;;                   (lambda () (interactive) (switch-shell n))))

(define-key company-active-map  (kbd "C-d")          'company-show-doc-buffer)
(define-key company-active-map  (kbd "C-n")          'company-select-next)
(define-key company-active-map  (kbd "C-p")          'company-select-previous)
(define-key company-active-map  (kbd "<tab>")        'company-complete)

(define-key custom-bindings     (kbd "C-c p")        'counsel-projectile-switch-project)
(define-key custom-bindings     (kbd "C-c f")        'counsel-projectile-find-file)
(define-key custom-bindings     (kbd "C-c m")        'magit-status)
(define-key custom-bindings     (kbd "C-c D")        'define-word-at-point)
(define-key custom-bindings     (kbd "C-@")          'er/expand-region)
(define-key custom-bindings     (kbd "C-#")          'er/contract-region)
(define-key custom-bindings     (kbd "C-S-c C-S-c")  'mc/edit-lines)
(define-key custom-bindings     (kbd "C-c b")        'ivy-switch-buffer)
(define-key custom-bindings     (kbd "C-c l")        'org-store-link)
(define-key custom-bindings     (kbd "C-c t")        'org-set-tags)
(define-key custom-bindings     (kbd "M-u")          'upcase-dwim)
(define-key custom-bindings     (kbd "M-c")          'capitalize-dwim)
(define-key custom-bindings     (kbd "M-l")          'downcase-dwim)
(define-key custom-bindings     (kbd "M-o")          'other-window)
(define-key custom-bindings     (kbd "C-c s")        'ispell-word)
(define-key custom-bindings     (kbd "C-c C-d")      'org-capture)
(define-key custom-bindings     (kbd "C-c <up>")     'windmove-up)
(define-key custom-bindings     (kbd "C-c <down>")   'windmove-down)
(define-key custom-bindings     (kbd "C-c <left>")   'windmove-left)
(define-key custom-bindings     (kbd "C-c <right>")  'windmove-right)
(define-key custom-bindings     (kbd "C-c a")        (lambda () (interactive) (org-agenda nil "n")))
(define-key custom-bindings     (kbd "C-c e")        'find-user-init-file)
(define-key custom-bindings     (kbd "C-x f")        'phil-columns)
(define-key custom-bindings     (kbd "C-x k")        'kill-this-buffer-unless-scratch)
(define-key custom-bindings     (kbd "C-c d")        'duplicate-thing)
(define-key custom-bindings     (kbd "C-c c")        'comment-or-uncomment-region-or-line)
(define-key custom-bindings     (kbd "C-;")          'comment-or-uncomment-region-or-line)
(define-key custom-bindings     (kbd "C-o")          'new-line-below)
(define-key custom-bindings     (kbd "C-S-o")        'new-line-above)
(define-key custom-bindings     (kbd "<C-tab>")      'tidy)
(define-key custom-bindings     (kbd "M-q")          'kill-this-buffer)
(define-key custom-bindings     (kbd "M-RET")        '(lambda () (interactive) (term (getenv "SHELL"))))


(define-minor-mode custom-bindings-mode
  "A mode that activates custom-bindings."
  t nil custom-bindings)

3.9 UI

(cond ((member "PragmataPro" (font-family-list))
       (set-face-attribute 'default nil :font "PragmataPro-14")))

3.9.1 Modeline

(require 'use-package)
(require 'anzu)
(require 'eldoc-eval)
(require 'iedit)
(require 'projectile)
(require 'all-the-icons)

(defsubst doom--prepare-modeline-segments (segments)
  (cl-loop for seg in segments
           if (stringp seg)
           collect seg
           else
           collect (list (intern (format "doom-modeline-segment--%s" (symbol-name seg))))))

(defvar doom--transient-counter 0)
(defmacro add-transient-hook! (hook &rest forms)
  "Attaches transient forms to a HOOK.

HOOK can be a quoted hook or a sharp-quoted function (which will be advised).

These forms will be evaluated once when that function/hook is first invoked,
then it detaches itself."
  (declare (indent 1))
  (let ((append (eq (car forms) :after))
        (fn (intern (format "doom-transient-hook-%s" (cl-incf doom--transient-counter)))))
    `(when ,hook
       (fset ',fn
             (lambda (&rest _)
               ,@forms
               (cond ((functionp ,hook) (advice-remove ,hook #',fn))
                     ((symbolp ,hook)   (remove-hook ,hook #',fn)))
               (unintern ',fn nil)))
       (cond ((functionp ,hook)
              (advice-add ,hook ,(if append :after :before) #',fn))
             ((symbolp ,hook)
              (add-hook ,hook #',fn ,append))))))


(defmacro add-hook! (&rest args)
  "A convenience macro for `add-hook'. Takes, in order:
  1. Optional properties :local and/or :append, which will make the hook
     buffer-local or append to the list of hooks (respectively),
  2. The hooks: either an unquoted major mode, an unquoted list of major-modes,
     a quoted hook variable or a quoted list of hook variables. If unquoted, the
     hooks will be resolved by appending -hook to each symbol.
  3. A function, list of functions, or body forms to be wrapped in a lambda.
Examples:
    (add-hook! 'some-mode-hook 'enable-something)
    (add-hook! some-mode '(enable-something and-another))
    (add-hook! '(one-mode-hook second-mode-hook) 'enable-something)
    (add-hook! (one-mode second-mode) 'enable-something)
    (add-hook! :append (one-mode second-mode) 'enable-something)
    (add-hook! :local (one-mode second-mode) 'enable-something)
    (add-hook! (one-mode second-mode) (setq v 5) (setq a 2))
    (add-hook! :append :local (one-mode second-mode) (setq v 5) (setq a 2))
Body forms can access the hook's arguments through the let-bound variable
`args'."
  (declare (indent defun) (debug t))
  (let ((hook-fn 'add-hook)
        append-p local-p)
    (while (keywordp (car args))
      (pcase (pop args)
        (:append (setq append-p t))
        (:local  (setq local-p t))
        (:remove (setq hook-fn 'remove-hook))))
    (let ((hooks (doom--resolve-hook-forms (pop args)))
          (funcs
           (let ((val (car args)))
             (if (memq (car-safe val) '(quote function))
                 (if (cdr-safe (cadr val))
                     (cadr val)
                   (list (cadr val)))
               (list args))))
          forms)
      (dolist (fn funcs)
        (setq fn (if (symbolp fn)
                     `(function ,fn)
                   `(lambda (&rest _) ,@args)))
        (dolist (hook hooks)
          (push (if (eq hook-fn 'remove-hook)
                    `(remove-hook ',hook ,fn ,local-p)
                  `(add-hook ',hook ,fn ,append-p ,local-p))
                forms)))
      `(progn ,@(nreverse forms)))))

(defmacro def-modeline-segment! (name &rest forms)
  "Defines a modeline segment and byte compiles it."
  (declare (indent defun) (doc-string 2))
  (let ((sym (intern (format "doom-modeline-segment--%s" name))))
    `(progn
       (defun ,sym () ,@forms)
       ,(unless (bound-and-true-p byte-compile-current-file)
          `(let (byte-compile-warnings)
             (byte-compile #',sym))))))

(defmacro def-modeline! (name lhs &optional rhs)
  "Defines a modeline format and byte-compiles it. NAME is a symbol to identify
it (used by `doom-modeline' for retrieval). LHS and RHS are lists of symbols of
modeline segments defined with `def-modeline-segment!'.
Example:
  (def-modeline! minimal
    (bar matches \" \" buffer-info)
    (media-info major-mode))
  (doom-set-modeline 'minimal t)"
  (let ((sym (intern (format "doom-modeline-format--%s" name)))
        (lhs-forms (doom--prepare-modeline-segments lhs))
        (rhs-forms (doom--prepare-modeline-segments rhs)))
    `(progn
       (defun ,sym ()
         (let ((lhs (list ,@lhs-forms))
               (rhs (list ,@rhs-forms)))
           (let ((rhs-str (format-mode-line rhs)))
             (list lhs
                   (propertize
                    " " 'display
                    `((space :align-to (- (+ right right-fringe right-margin)
                                          ,(+ 1 (string-width rhs-str))))))
                   rhs-str))))
       ,(unless (bound-and-true-p byte-compile-current-file)
          `(let (byte-compile-warnings)
             (byte-compile #',sym))))))

(defun doom-modeline (key)
  "Returns a mode-line configuration associated with KEY (a symbol). Throws an
error if it doesn't exist."
  (let ((fn (intern (format "doom-modeline-format--%s" key))))
    (when (functionp fn)
      `(:eval (,fn)))))

(defun doom-set-modeline (key &optional default)
  "Set the modeline format. Does nothing if the modeline KEY doesn't exist. If
DEFAULT is non-nil, set the default mode-line for all buffers."
  (when-let ((modeline (doom-modeline key)))
    (setf (if default
              (default-value 'mode-line-format)
            (buffer-local-value 'mode-line-format (current-buffer)))
          modeline)))

(use-package eldoc-eval
  :config
  (defun +doom-modeline-eldoc (text)
    (concat (when (display-graphic-p)
              (+doom-modeline--make-xpm
               (face-background 'doom-modeline-eldoc-bar nil t)
               +doom-modeline-height
               +doom-modeline-bar-width))
            text))

  ;; Show eldoc in the mode-line with `eval-expression'
  (defun +doom-modeline--show-eldoc (input)
    "Display string STR in the mode-line next to minibuffer."
    (with-current-buffer (eldoc-current-buffer)
      (let* ((str              (and (stringp input) input))
             (mode-line-format (or (and str (or (+doom-modeline-eldoc str) str))
                                   mode-line-format))
             mode-line-in-non-selected-windows)
        (force-mode-line-update)
        (sit-for eldoc-show-in-mode-line-delay))))
  (setq eldoc-in-minibuffer-show-fn #'+doom-modeline--show-eldoc)

  (eldoc-in-minibuffer-mode +1))

;; anzu and evil-anzu expose current/total state that can be displayed in the
;; mode-line.
(use-package anzu
  :init
  ;; (add-transient-hook! #'ex-start-search (require 'anzu))
  ;; (add-transient-hook! #'ex-start-word-search (require 'anzu))
  :config
  (setq anzu-cons-mode-line-p nil
        anzu-minimum-input-length 1
        anzu-search-threshold 250)
  ;; Avoid anzu conflicts across 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))
  ;; Ensure anzu state is cleared when searches & iedit are done
  (add-hook 'isearch-mode-end-hook #'anzu--reset-status t)
  ;; (add-hook '+evil-esc-hook #'anzu--reset-status t)
  (add-hook 'iedit-mode-end-hook #'anzu--reset-status))


;; Keep `+doom-modeline-current-window' up-to-date
(defvar +doom-modeline-current-window (frame-selected-window))
(defun +doom-modeline|set-selected-window (&rest _)
  "Sets `+doom-modeline-current-window' appropriately"
  (when-let ((win (frame-selected-window)))
    (unless (minibuffer-window-active-p win)
      (setq +doom-modeline-current-window win))))

(add-hook 'window-configuration-change-hook #'+doom-modeline|set-selected-window)
(add-hook 'focus-in-hook #'+doom-modeline|set-selected-window)
(advice-add #'handle-switch-frame :after #'+doom-modeline|set-selected-window)
(advice-add #'select-window :after #'+doom-modeline|set-selected-window)

;; fish-style modeline
(use-package shrink-path
  :commands (shrink-path-prompt shrink-path-file-mixed))


;;
;; Variables
;;

(defvar +doom-modeline-height 29
  "How tall the mode-line should be (only respected in GUI emacs).")

(defvar +doom-modeline-bar-width 3
  "How wide the mode-line bar should be (only respected in GUI emacs).")

(defvar +doom-modeline-vspc
  (propertize " " 'face 'variable-pitch)
  "TODO")

(defvar +doom-modeline-buffer-file-name-style 'truncate-upto-project
  "Determines the style used by `+doom-modeline-buffer-file-name'.

Given ~/Projects/FOSS/emacs/lisp/comint.el
truncate-upto-project => ~/P/F/emacs/lisp/comint.el
truncate-upto-root => ~/P/F/e/lisp/comint.el
truncate-all => ~/P/F/e/l/comint.el
relative-from-project => emacs/lisp/comint.el
relative-to-project => lisp/comint.el
file-name => comint.el")

;; externs
(defvar anzu--state nil)
(defvar evil-mode nil)
(defvar evil-state nil)
(defvar evil-visual-selection nil)
(defvar iedit-mode nil)
(defvar all-the-icons-scale-factor)
(defvar all-the-icons-default-adjust)


;;
;; Custom faces
;;

(defgroup +doom-modeline nil
  ""
  :group 'doom)

(defface doom-modeline-buffer-path
  '((t (:inherit (mode-line-emphasis bold))))
  "Face used for the dirname part of the buffer path."
  :group '+doom-modeline)

(defface doom-modeline-buffer-file
  '((t (:inherit (mode-line-buffer-id bold))))
  "Face used for the filename part of the mode-line buffer path."
  :group '+doom-modeline)

(defface doom-modeline-buffer-modified
  '((t (:inherit (error bold) :background nil)))
  "Face used for the 'unsaved' symbol in the mode-line."
  :group '+doom-modeline)

(defface doom-modeline-buffer-major-mode
  '((t (:inherit (mode-line-emphasis bold))))
  "Face used for the major-mode segment in the mode-line."
  :group '+doom-modeline)

(defface doom-modeline-highlight
  '((t (:inherit mode-line-emphasis)))
  "Face for bright segments of the mode-line."
  :group '+doom-modeline)

(defface doom-modeline-panel
  '((t (:inherit mode-line-highlight)))
  "Face for 'X out of Y' segments, such as `+doom-modeline--anzu', `+doom-modeline--evil-substitute' and
`iedit'"
  :group '+doom-modeline)

(defface doom-modeline-info
  `((t (:inherit (success bold))))
  "Face for info-level messages in the modeline. Used by `*vc'."
  :group '+doom-modeline)

(defface doom-modeline-warning
  `((t (:inherit (warning bold))))
  "Face for warnings in the modeline. Used by `*flycheck'"
  :group '+doom-modeline)

(defface doom-modeline-urgent
  `((t (:inherit (error bold))))
  "Face for errors in the modeline. Used by `*flycheck'"
  :group '+doom-modeline)

;; Bar
(defface doom-modeline-bar '((t (:inherit highlight)))
  "The face used for the left-most bar on the mode-line of an active window."
  :group '+doom-modeline)

(defface doom-modeline-eldoc-bar '((t (:inherit shadow)))
  "The face used for the left-most bar on the mode-line when eldoc-eval is
active."
  :group '+doom-modeline)

(defface doom-modeline-inactive-bar '((t (:inherit warning :inverse-video t)))
  "The face used for the left-most bar on the mode-line of an inactive window."
  :group '+doom-modeline)


;;
;; Modeline helpers
;;

(defsubst active ()
  (eq (selected-window) +doom-modeline-current-window))

;; Inspired from `powerline's `pl/make-xpm'.
(defun +doom-modeline--make-xpm (color height width)
  "Create an XPM bitmap."
  (propertize
   " " 'display
   (let ((data (make-list height (make-list width 1)))
         (color (or color "None")))
     (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 +doom-modeline-buffer-file-name ()
  "Propertized `buffer-file-name' based on `+doom-modeline-buffer-file-name-style'."
  (propertize
   (pcase +doom-modeline-buffer-file-name-style
     ('truncate-upto-project (+doom-modeline--buffer-file-name 'shrink))
     ('truncate-upto-root (+doom-modeline--buffer-file-name-truncate))
     ('truncate-all (+doom-modeline--buffer-file-name-truncate t))
     ('relative-to-project (+doom-modeline--buffer-file-name-relative))
     ('relative-from-project (+doom-modeline--buffer-file-name-relative 'include-project))
     ('file-name (propertize (file-name-nondirectory buffer-file-name)
                             'face
                             (let ((face (or (and (buffer-modified-p)
                                                  'doom-modeline-buffer-modified)
                                             (and (active)
                                                  'doom-modeline-buffer-file))))
                               (when face `(:inherit ,face))))))
   'help-echo buffer-file-truename))

(defun +doom-modeline--buffer-file-name-truncate (&optional truncate-tail)
  "Propertized `buffer-file-name' that truncates every dir along path.
If TRUNCATE-TAIL is t also truncate the parent directory of the file."
  (let ((dirs (shrink-path-prompt (file-name-directory buffer-file-truename)))
        (active (active)))
    (if (null dirs)
        (propertize "%b" 'face (if active 'doom-modeline-buffer-file))
      (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified)))
        (let ((dirname (car dirs))
              (basename (cdr dirs))
              (dir-faces (or modified-faces (if active 'doom-modeline-project-root-dir)))
              (file-faces (or modified-faces (if active 'doom-modeline-buffer-file))))
          (concat (propertize (concat dirname
                                      (if truncate-tail (substring basename 0 1) basename)
                                      "/")
                              'face (if dir-faces `(:inherit ,dir-faces)))
                  (propertize (file-name-nondirectory buffer-file-name)
                              'face (if file-faces `(:inherit ,file-faces)))))))))

(defun +doom-modeline--buffer-file-name-relative (&optional include-project)
  "Propertized `buffer-file-name' showing directories relative to project's root only."
  (let ((root (projectile-project-root))
        (active (active)))
    (if (null root)
        (propertize "%b" 'face (if active 'doom-modeline-buffer-file))
      (let* ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified))
             (relative-dirs (file-relative-name (file-name-directory buffer-file-truename)
                                                (if include-project (concat root "../") root)))
             (relative-faces (or modified-faces (if active 'doom-modeline-buffer-path)))
             (file-faces (or modified-faces (if active 'doom-modeline-buffer-file))))
        (if (equal "./" relative-dirs) (setq relative-dirs ""))
        (concat (propertize relative-dirs 'face (if relative-faces `(:inherit ,relative-faces)))
                (propertize (file-name-nondirectory buffer-file-truename)
                            'face (if file-faces `(:inherit ,file-faces))))))))

(defun +doom-modeline--buffer-file-name (truncate-project-root-parent)
  "Propertized `buffer-file-name'.
If TRUNCATE-PROJECT-ROOT-PARENT is t space will be saved by truncating it down
fish-shell style.

Example:
~/Projects/FOSS/emacs/lisp/comint.el => ~/P/F/emacs/lisp/comint.el"
  (let* ((project-root (projectile-project-root))
         (file-name-split (shrink-path-file-mixed project-root
                                                  (file-name-directory buffer-file-truename)
                                                  buffer-file-truename))
         (active (active)))
    (if (null file-name-split)
        (propertize "%b" 'face (if active 'doom-modeline-buffer-file))
      (pcase-let ((`(,root-path-parent ,project ,relative-path ,filename) file-name-split))
        (let ((modified-faces (if (buffer-modified-p) 'doom-modeline-buffer-modified)))
          (let ((sp-faces       (or modified-faces (if active 'font-lock-comment-face)))
                (project-faces  (or modified-faces (if active 'font-lock-string-face)))
                (relative-faces (or modified-faces (if active 'doom-modeline-buffer-path)))
                (file-faces     (or modified-faces (if active 'doom-modeline-buffer-file))))
            (let ((sp-props       `(,@(if sp-faces       `(:inherit ,sp-faces))      ,@(if active '(:weight bold))))
                  (project-props  `(,@(if project-faces  `(:inherit ,project-faces)) ,@(if active '(:weight bold))))
                  (relative-props `(,@(if relative-faces `(:inherit ,relative-faces))))
                  (file-props     `(,@(if file-faces     `(:inherit ,file-faces)))))
              (concat (propertize (if truncate-project-root-parent
                                      root-path-parent
                                    (abbreviate-file-name project-root))
                                  'face sp-props)
                      (propertize (concat project "/") 'face project-props)
                      (if relative-path (propertize relative-path 'face relative-props))
                      (propertize filename 'face file-props)))))))))


;;
;; Segments
;;

(def-modeline-segment! buffer-default-directory
  "Displays `default-directory'. This is for special buffers like the scratch
buffer where knowing the current project directory is important."
  (let ((face (if (active) 'doom-modeline-buffer-path)))
    (concat (if (display-graphic-p) " ")
            (all-the-icons-octicon
             "file-directory"
             :face face
             :v-adjust -0.05
             :height 1.25)
            (propertize (concat " " (abbreviate-file-name default-directory))
                        'face face))))

;;
(def-modeline-segment! buffer-info
  "Combined information about the current buffer, including the current working
directory, the file name, and its state (modified, read-only or non-existent)."
  (concat (cond (buffer-read-only
                 (concat (all-the-icons-octicon
                          "lock"
                          :face 'doom-modeline-warning
                          :v-adjust -0.05)
                         " "))
                ((buffer-modified-p)
                 (concat (all-the-icons-faicon
                          "floppy-o"
                          :face 'doom-modeline-buffer-modified
                          :v-adjust -0.0575)
                         " "))
                ((and buffer-file-name
                      (not (file-exists-p buffer-file-name)))
                 (concat (all-the-icons-octicon
                          "circle-slash"
                          :face 'doom-modeline-urgent
                          :v-adjust -0.05)
                         " "))
                ((buffer-narrowed-p)
                 (concat (all-the-icons-octicon
                          "fold"
                          :face 'doom-modeline-warning
                          :v-adjust -0.05)
                         " ")))
          (if buffer-file-name
              (+doom-modeline-buffer-file-name)
            "%b")))

;;
(def-modeline-segment! buffer-info-simple
  "Display only the current buffer's name, but with fontification."
  (propertize
   "%b"
   'face (cond ((and buffer-file-name (buffer-modified-p))
                'doom-modeline-buffer-modified)
               ((active) 'doom-modeline-buffer-file))))

;;
(def-modeline-segment! buffer-encoding
  "Displays the encoding and eol style of the buffer the same way Atom does."
  (concat (pcase (coding-system-eol-type buffer-file-coding-system)
            (0 "LF  ")
            (1 "CRLF  ")
            (2 "CR  "))
          (let ((sys (coding-system-plist buffer-file-coding-system)))
            (cond ((memq (plist-get sys :category) '(coding-category-undecided coding-category-utf-8))
                   "UTF-8")
                  (t (upcase (symbol-name (plist-get sys :name))))))
          "  "))

;;
(def-modeline-segment! major-mode
  "The major mode, including process, environment and text-scale info."
  (propertize
   (concat (format-mode-line mode-name)
           (when (stringp mode-line-process)
             mode-line-process)
           (and (featurep 'face-remap)
                (/= text-scale-mode-amount 0)
                (format " (%+d)" text-scale-mode-amount)))
   'face (if (active) 'doom-modeline-buffer-major-mode)))

;;
(def-modeline-segment! vcs
  "Displays the current branch, colored based on its state."
  (when (and vc-mode buffer-file-name)
    (let* ((backend (vc-backend buffer-file-name))
           (state   (vc-state buffer-file-name backend)))
      (let ((face    'mode-line-inactive)
            (active  (active))
            (all-the-icons-default-adjust -0.1))
        (concat "  "
                (cond ((memq state '(edited added))
                       (if active (setq face 'doom-modeline-info))
                       (all-the-icons-octicon
                        "git-compare"
                        :face face
                        :v-adjust -0.05))
                      ((eq state 'needs-merge)
                       (if active (setq face 'doom-modeline-info))
                       (all-the-icons-octicon "git-merge" :face face))
                      ((eq state 'needs-update)
                       (if active (setq face 'doom-modeline-warning))
                       (all-the-icons-octicon "arrow-down" :face face))
                      ((memq state '(removed conflict unregistered))
                       (if active (setq face 'doom-modeline-urgent))
                       (all-the-icons-octicon "alert" :face face))
                      (t
                       (if active (setq face 'font-lock-doc-face))
                       (all-the-icons-octicon
                        "git-compare"
                        :face face
                        :v-adjust -0.05)))
                " "
                (propertize (substring vc-mode (+ (if (eq backend 'Hg) 2 3) 2))
                            'face (if active face))
                " ")))))

;;
(defun +doom-ml-icon (icon &optional text face voffset)
  "Displays an octicon ICON with FACE, followed by TEXT. Uses
`all-the-icons-octicon' to fetch the icon."
  (concat (if vc-mode " " "  ")
          (when icon
            (concat
             (all-the-icons-material icon :face face :height 1.1 :v-adjust (or voffset -0.2))
             (if text +doom-modeline-vspc)))
          (when text
            (propertize text 'face face))
          (if vc-mode "  " " ")))

(def-modeline-segment! flycheck
  "Displays color-coded flycheck error status in the current buffer with pretty
icons."
  (when (boundp 'flycheck-last-status-change)
    (pcase flycheck-last-status-change
      ('finished (if flycheck-current-errors
                     (let-alist (flycheck-count-errors flycheck-current-errors)
                       (let ((sum (+ (or .error 0) (or .warning 0))))
                         (+doom-ml-icon "do_not_disturb_alt"
                                        (number-to-string sum)
                                        (if .error 'doom-modeline-urgent 'doom-modeline-warning)
                                        -0.25)))
                   (+doom-ml-icon "check" nil 'doom-modeline-info)))
      ('running     (+doom-ml-icon "access_time" nil 'font-lock-doc-face -0.25))
      ('no-checker  (+doom-ml-icon "sim_card_alert" "-" 'font-lock-doc-face))
      ('errored     (+doom-ml-icon "sim_card_alert" "Error" 'doom-modeline-urgent))
      ('interrupted (+doom-ml-icon "pause" "Interrupted" 'font-lock-doc-face)))))
;; ('interrupted (+doom-ml-icon "x" "Interrupted" 'font-lock-doc-face)))))

;;
(defsubst doom-column (pos)
  (save-excursion (goto-char pos)
                  (current-column)))

(def-modeline-segment! selection-info
  "Information about the current selection, such as how many characters and
lines are selected, or the NxM dimensions of a block selection."
  (when (and (active) (or mark-active (eq evil-state 'visual)))
    (let ((reg-beg (region-beginning))
          (reg-end (region-end)))
      (propertize
       (let ((lines (count-lines reg-beg (min (1+ reg-end) (point-max)))))
         (cond ((or (bound-and-true-p rectangle-mark-mode)
                    (eq 'block evil-visual-selection))
                (let ((cols (abs (- (doom-column reg-end)
                                    (doom-column reg-beg)))))
                  (format "%dx%dB" lines cols)))
               ((eq 'line evil-visual-selection)
                (format "%dL" lines))
               ((> lines 1)
                (format "%dC %dL" (- (1+ reg-end) reg-beg) lines))
               (t
                (format "%dC" (- (1+ reg-end) reg-beg)))))
       'face 'doom-modeline-highlight))))


;;
(defun +doom-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 'doom-modeline-panel)))
      (concat sep
              (propertize (if (bound-and-true-p evil-this-macro)
                              (char-to-string evil-this-macro)
                            "Macro")
                          'face 'doom-modeline-panel)
              sep
              (all-the-icons-octicon "triangle-right"
                                     :face 'doom-modeline-panel
                                     :v-adjust -0.05)
              sep))))

(defsubst +doom-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 anzu--state (not iedit-mode))
    (propertize
     (let ((here anzu--current-position)
           (total anzu--total-matched))
       (cond ((eq anzu--state 'replace-query)
              (format " %d replace " total))
             ((eq anzu--state 'replace)
              (format " %d/%d " here total))
             (anzu--overflow-p
              (format " %s+ " total))
             (t
              (format " %s/%d " here total))))
     'face (if (active) 'doom-modeline-panel))))

(defsubst +doom-modeline--evil-substitute ()
  "Show number of matches for evil-ex substitutions and highlights in real time."
  (when (and evil-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) 'doom-modeline-panel))))

(defun doom-themes--overlay-sort (a b)
  (< (overlay-start a) (overlay-start b)))

(defsubst +doom-modeline--iedit ()
  "Show the number of iedit regions matches + what match you're on."
  (when (and iedit-mode iedit-occurrences-overlays)
    (propertize
     (let ((this-oc (or (let ((inhibit-message t))
                          (iedit-find-current-occurrence-overlay))
                        (progn (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)
                                                  #'doom-themes--overlay-sort)))
                      -1)
                 "-")
               length))
     'face (if (active) 'doom-modeline-panel))))

(def-modeline-segment! matches
  "Displays: 1. the currently recording macro, 2. A current/total for the
current search term (with anzu), 3. The number of substitutions being conducted
with `evil-ex-substitute', and/or 4. The number of active `iedit' regions."
  (let ((meta (concat (+doom-modeline--macro-recording)
                      (+doom-modeline--anzu)
                      (+doom-modeline--evil-substitute)
                      (+doom-modeline--iedit))))
    (or (and (not (equal meta "")) meta)
        (if buffer-file-name " %I "))))

;; TODO Include other information
(def-modeline-segment! media-info
  "Metadata regarding the current file, such as dimensions for images."
  (cond ((eq major-mode 'image-mode)
         (cl-destructuring-bind (width . height)
             (image-size (image-get-display-property) :pixels)
           (format "  %dx%d  " width height)))))

(def-modeline-segment! bar
  "The bar regulates the height of the mode-line in GUI Emacs.
Returns \"\" to not break --no-window-system."
  (if (display-graphic-p)
      (+doom-modeline--make-xpm
       (face-background (if (active)
                            'doom-modeline-bar
                          'doom-modeline-inactive-bar)
                        nil t)
       +doom-modeline-height
       +doom-modeline-bar-width)
    ""))


;;
;; Mode lines
;;

(def-modeline! main
  (bar matches " " buffer-info "  %l:%c %p  " selection-info)
  (buffer-encoding major-mode vcs flycheck))

(def-modeline! minimal
  (bar matches " " buffer-info)
  (media-info major-mode))

(def-modeline! special
  (bar matches " " buffer-info-simple "  %l:%c %p  " selection-info)
  (buffer-encoding major-mode flycheck))

(def-modeline! project
  (bar buffer-default-directory)
  (major-mode))

(def-modeline! media
  (bar " %b  ")
  (media-info major-mode))


;;
;; Hooks
;;

(defun +doom-modeline|init ()
  "Set the default modeline."
  (doom-set-modeline 'main t)

  ;; This scratch buffer is already created and doesn't get a modeline. For the
  ;; love of Emacs, someone give the man a modeline!
  (with-current-buffer "*scratch*"
    (doom-set-modeline 'main)))

(defun +doom-modeline|set-special-modeline ()
  (doom-set-modeline 'special))

(defun +doom-modeline|set-media-modeline ()
  (doom-set-modeline 'media))

(defun +doom-modeline|set-project-modeline ()
  (doom-set-modeline 'project))


;;
;; Bootstrap
;;

(add-hook 'emacs-startup-hook #'+doom-modeline|init)
;; (add-hook 'doom-scratch-buffer-hook #'+doom-modeline|set-special-modeline)
;; (add-hook '+doom-dashboard-mode-hook #'+doom-modeline|set-project-modeline)

(add-hook 'image-mode-hook   #'+doom-modeline|set-media-modeline)
(add-hook 'org-src-mode-hook #'+doom-modeline|set-special-modeline)
(add-hook 'circe-mode-hook   #'+doom-modeline|set-special-modeline)

Date: 2019-01-30 Wed 00:00

Author: Levi Olson

Created: 2019-02-13 Wed 14:20

Validate