EOS: Emacs Appearance

Table of Contents

(provide 'eos-appearance)

Determining whether this will be a "light" or "dark" session

First, let's determine whether I'm going to be using a dark background, or a light background. I set a var to either 'light or 'dark depending on whatever I'm in the mood for. This is used later on for the modeline theme, as well as the general theme for look-and-feel things.

(defvar eos/background 'light)
;;(defvar eos/background 'dark)

Emacs Appearance

Beacon flashes the cursor whenever you adjust position, really helpful when jumping between a billion windows and having no idea where the cursor actually is. It's currently disabled because it causes a bit of CPU churn that I don't really need.

(use-package beacon
  :ensure t
  :disabled t
  :diminish beacon-mode
  :init (beacon-mode 1)
  :config
  (add-to-list 'beacon-dont-blink-major-modes 'eshell-mode))

Paren-face adds a face for parentheses, which is used by themes to darken the parens.

(use-package paren-face
  :ensure t
  :init (global-paren-face-mode))

Don't use dialog boxes, just ask inside Emacs

(setq use-dialog-box nil)

Playing around with line spacing, with certain fonts I may or may not want to have more/less spacing. The number is in pixels.

(setq-default line-spacing 0)

Color Theme

For light-colored backgrounds, I used leuven-theme. For dark-colored backgrounds (most of the time), I use the tomorrow-night theme, which I find to be a good balanced of color and pleasing to my eyes.

(if (eq eos/background 'dark)
    (progn
      (use-package color-theme-sanityinc-tomorrow
        :ensure t
        :init
        (load-theme 'sanityinc-tomorrow-night t)
        ;; Just ever so slightly more bright foreground text, default is
        ;; "#c5c8c6". Makes it easier to see on a sunny day
        (set-face-foreground 'default "#e5e8e6")
        ;; darken newline whitespace marks and blend in to the background
        (require 'whitespace)
        (set-face-foreground 'whitespace-newline "#555856")
        (set-face-background 'whitespace-newline (face-attribute 'default :background)))
      (use-package tao-theme
        :ensure t
        :disabled t
        :init
        (load-theme 'tao-yin t)
        (require 'git-gutter)
        (require 'git-gutter-fringe)
        (set-face-attribute 'git-gutter:deleted nil :foreground "red")
        (set-face-attribute 'git-gutter-fr:deleted nil :foreground "red")
        (set-face-attribute 'git-gutter:modified nil :foreground "light blue")
        (set-face-attribute 'git-gutter-fr:modified nil :foreground "light blue")
        (set-face-attribute 'git-gutter:added nil :foreground "green")
        (set-face-attribute 'git-gutter-fr:added nil :foreground "green")
        (require 'linum)
        (set-face-attribute 'linum nil :foreground "#444444"))
      (use-package doom-themes
        :ensure t
        :disabled t
        :init
        (load-theme 'doom-one t)
        (diminish 'doom-buffer-mode)
        ;; Doom currently has a broken modeline
        (set-face-attribute 'mode-line-buffer-id nil
                            :foreground "white" :bold t))
      (use-package zerodark-theme
        :ensure t
        :disabled t
        :init
        (load-theme 'zerodark t)
        (zerodark-setup-modeline-format)))
  (use-package leuven-theme
    :ensure t
    :disabled t
    :init
    (load-theme 'leuven t)
    ;; Ever-so-slightly darker background
    (set-face-background 'default "#F7F7F7"))
  (use-package github-theme
    :ensure t
    :disabled t
    :init
    (load-theme 'github t))
  (use-package tao-theme
    :ensure t
    :disabled t
    :init
    (load-theme 'tao-yang t)
    (require 'git-gutter)
    (require 'git-gutter-fringe)
    (set-face-attribute 'git-gutter:deleted nil :foreground "red")
    (set-face-attribute 'git-gutter-fr:deleted nil :foreground "red")
    (set-face-attribute 'git-gutter:modified nil :foreground "light blue")
    (set-face-attribute 'git-gutter-fr:modified nil :foreground "light blue")
    (set-face-attribute 'git-gutter:added nil :foreground "green")
    (set-face-attribute 'git-gutter-fr:added nil :foreground "green"))
  (use-package dakrone-light-theme
    :ensure t
    :init (load-theme 'dakrone-light t)))

If using OSX, the colors and fonts look a bit wonky, so let's fix that

(setq ns-use-srgb-colorspace t)
;; Anti-aliasing
(setq mac-allow-anti-aliasing t)

Fonts

I've been using Hack or DejaVu Sans almost exclusively lately, it looks pretty nice to me on both Linux and any other computer. I play with other fonts pretty regularly though.

Fonts I may or may not use (in no particular order):

  • Hack
  • Fantasque Sans Mono
  • PT Mono
  • Iosevka
  • Anonymous Pro
  • Fira Mono
  • Go Mono
  • Input Mono
  • Inconsolata
  • Source Code Pro
;; The original font height (so it can be restored too at a later time)
(setq eos/original-height 105)

(defun eos/setup-fonts ()
  (when (eq window-system 'x)
    ;; default font and variable-pitch fonts
    (set-face-attribute 'default nil
                        :family "Iosevka"
                        :height eos/original-height)
    (dolist (face '(mode-line mode-line-inactive minibuffer-prompt))
      (set-face-attribute face nil :family "Iosevka"
                          :height eos/original-height))
    (set-face-attribute 'variable-pitch nil
                        :family "DejaVu Sans" :height eos/original-height)
    ;; font for all unicode characters
    ;;(set-fontset-font t 'unicode "DejaVu Sans Mono" nil 'prepend)
    ))

(when (eq window-system 'x)
  (add-hook 'after-init-hook #'eos/setup-fonts))

Configuration for monitor switching

Sometimes I want to plug my laptop into a larger monitor, or give presentations, so I'd like to have a single function I can call to adjust any sizes that are necessary.

The eos/height-modifier can be added or subtracted to the size of the font for Emacs. I usually bind this in the Hydra toggle map.

(defvar eos/height-modifier 15
  "Default value to increment the size by when jacking into a monitor.")

(defun eos/monitor-jack-in ()
  "Increase the font size by `eos/height-modifier' amount, for
when you jack into an external monitor."
  (interactive)
  (dolist (face '(default
                   mode-line
                   mode-line-inactive
                   minibuffer-prompt
                   variable-pitch))
    (set-face-attribute face nil :height (+ (face-attribute face :height)
                                            eos/height-modifier))))

(defun eos/monitor-jack-out ()
  "Decreas the font size by `eos/height-modifier' amount, for
when you jack out of an external monitor."
  (interactive)
  (dolist (face '(default
                   mode-line
                   mode-line-inactive
                   minibuffer-prompt
                   variable-pitch))
    (set-face-attribute face nil :height (- (face-attribute face :height)
                                            eos/height-modifier))))

(defun eos/monitor-reset ()
  "Go back to the default font size and `line-spacing'"
  (interactive)
  (set-face-attribute 'default nil :height eos/original-height)
  (set-face-attribute 'variable-pitch nil :height eos/original-height)
  (text-scale-adjust 0)
  (when (fboundp 'minimap-mode)
    (condition-case err
        (minimap-mode 0)
      ('error 0)))
  (setq line-spacing 0))

(defun eos/code-reading-mode ()
  "Do a bunch of fancy stuff to make reading/browsing code
easier. When you're done, `eos/monitor-jack-out' is a great way
to go back to a normal setup."
  (interactive)
  (delete-other-windows)
  (text-scale-increase 1)
  (setq line-spacing 5)
  (use-package minimap :ensure t)
  (when (not minimap-mode)
    (minimap-mode 1)))

Emacs' Mode-line

Hmm… there are two real "useful" additions here, either smart mode line, or spaceline

Display the time and load on the modeline

(setq
 ;; don't display info about mail
 display-time-mail-function (lambda () nil)
 ;; update every 15 seconds instead of 60 seconds
 display-time-interval 15)
(display-time-mode 1)

Buuuutttt… I don't really care about the time, so ignore it

(setq display-time-format "")

Let's also display the battery status in the mode-line

;;(display-battery-mode 1)

smart-mode-line

sml is great, but I did some profiling and it was eating a lot of responsiveness, so I go back and forth with it.

(use-package smart-mode-line
  :ensure t
  :init
  (if (eq eos/background 'dark)
      (setq sml/theme eos/background)
    (setq sml/theme 'light))
  (sml/setup)
  :config
  (setq sml/shorten-directory t
        sml/shorten-modes t)
  (add-to-list 'sml/replacer-regexp-list '("^~/Sync/org/" ":org:"))
  (add-to-list 'sml/replacer-regexp-list '("^~/es/elasticsearch-extra/x-pack/" ":X-PACK:"))
  (add-to-list 'sml/replacer-regexp-list '("^~/es/elasticsearch/" ":ES:") t))

Spaceline

This is the modeline built into spacemacs, but a separate package. Worth a try, anyway…

(use-package spaceline
  :ensure t
  :disabled t
  :init
  (setq powerline-default-separator 'arrow-fade
        spaceline-minor-modes-separator " ")
  (require 'spaceline-config)
  (spaceline-spacemacs-theme)
  (spaceline-helm-mode)
  (use-package info+
    :ensure t
    :init
    (spaceline-info-mode))
  (use-package fancy-battery
    :ensure t
    :init
    (add-hook 'after-init-hook #'fancy-battery-mode)
    (display-battery-mode -1)))

And then there's Spaceline-all-the-icons also

(use-package spaceline-all-the-icons
  :after spaceline
  :ensure t
  :config
  (spaceline-all-the-icons-theme)
  (spaceline-all-the-icons--setup-anzu))

Personal mode line

(defun eos/custom-mode-line ()
  "Set up the customized EOS mode line (very basic)"
  (interactive)

  (setq mode-line-position
        '(;; %p print percent of buffer above top of window, o Top, Bot or All
          ;; (-3 "%p")
          ;; %I print the size of the buffer, with kmG etc
          ;; (size-indication-mode ("/" (-4 "%I")))
          ;; " "
          ;; %l print the current line number
          ;; %c print the current column
          (line-number-mode ("%l" (column-number-mode ":%c")))))

  (defun shorten-directory (dir max-length)
    "Show up to `max-length' characters of a directory name `dir'."
    (let ((path (reverse (split-string (abbreviate-file-name dir) "/")))
          (output ""))
      (when (and path (equal "" (car path)))
        (setq path (cdr path)))
      (while (and path (< (length output) (- max-length 4)))
        (setq output (concat (car path) "/" output))
        (setq path (cdr path)))
      (when path
        (setq output (concat ".../" output)))
      output))

  (defvar mode-line-directory
    '(:propertize
      (:eval (if (buffer-file-name)
                 (concat " " (shorten-directory default-directory 15)) " ")))
    "Formats the current directory.")

  (put 'mode-line-directory 'risky-local-variable t)

  (setq-default mode-line-buffer-identification
                (propertized-buffer-identification "%b "))

  (defun eos/workspace-number ()
    "The current workspace name or number. Requires `eyebrowse-mode' to be
enabled."
    (when (and (bound-and-true-p eyebrowse-mode)
               (< 1 (length (eyebrowse--get 'window-configs))))
      (let* ((num (eyebrowse--get 'current-slot))
             (tag (when num (nth 2 (assoc num (eyebrowse--get 'window-configs)))))
             (str (if (and tag (< 0 (length tag)))
                      tag
                    (when num (int-to-string num)))))
        (propertize str 'face '(:foreground "brown")))))

  ;; (use-package major-mode-icons
  ;;   :ensure t)

  (setq-default mode-line-format
                '("%e"
                  mode-line-front-space
                  (anzu-mode
                   (:eval
                    (anzu--update-mode-line)))
                  ;; I'm always on utf-8
                  ;;mode-line-mule-info
                  mode-line-client
                  mode-line-modified
                  " "
                  mode-line-position
                  " ["
                  (eyebrowse-mode
                   (:eval
                    (eos/workspace-number)))
                  "]"
                  ;; no need to indicate this specially
                  ;;mode-line-remote
                  ;; this is for text-mode emacs only
                  ;;mode-line-frame-identification
                  " "
                  ;; TODO: https://github.com/stardiviner/major-mode-icons/issues/4
                  ;; ((:eval (major-mode-icons/show)))
                  mode-line-directory
                  mode-line-buffer-identification
                  " "
                  ;; I use magit, not vc-mode
                  ;;(vc-mode vc-mode)
                  (flycheck-mode flycheck-mode-line)

                  " "
                  (org-agenda-mode
                   (:eval (format "%s" org-agenda-filter)))
                  ;; no need to dispaly the modes
                  ;;mode-line-modes
                  ;;mode-line-misc-info
                  (which-func-mode
                   ("" which-func-format " "))
                  (global-mode-string
                   ("" global-mode-string " "))
                  mode-line-end-spaces)))

;; (add-hook 'after-init-hook #'eos/custom-mode-line)

Scrolling

Smooth scrolling means when you hit C-n to go to the next line at the bottom of the page, instead of doing a page-down, it shifts down by a single line. The margin means that much space is kept between the cursor and the bottom of the buffer.

(setq scroll-margin 3
      scroll-conservatively 101
      scroll-up-aggressively 0.01
      scroll-down-aggressively 0.01
      scroll-preserve-screen-position t
      auto-window-vscroll nil
      hscroll-margin 5
      hscroll-step 5)

Highlighting the current line with hl-line-mode

I turn this on and off all over the place, so it's nice to have a global place where I can have it defined/handled.

First, a flag for whether it's enabled, if set to nil, then even if hl-line-mode is "turned on" it won't be turned on. This allows me to manage enabling it in a single place.

(setq eos/hl-line-enabled t)

Then two functions to do the turning on/off

(defun eos/turn-on-hl-line ()
  (interactive)
  (when eos/hl-line-enabled
    (hl-line-mode 1)))

(defun eos/turn-off-hl-line ()
  (interactive)
  (hl-line-mode -1))

Turn it on by default (if enabled!) in prog-mode

(add-hook 'prog-mode-hook #'eos/turn-on-hl-line)
(add-hook 'mu4e-view-mode-hook #'eos/turn-on-hl-line)
(add-hook 'erc-mode-hook #'eos/turn-on-hl-line)

The Editor Fringe

So, fringe is nice actually, I set it to the same color as the background so it blends into the foreground

(defun eos/set-fringe-background ()
  "Set the fringe background to the same color as the regular background."
  (setq eos/fringe-background-color
        (face-background 'default))
  (custom-set-faces
   `(fringe ((t (:background ,eos/fringe-background-color))))))

(add-hook 'after-init-hook #'eos/set-fringe-background)

Show where the buffer ends on the right-hand fringe

(setq-default indicate-buffer-boundaries nil ;; 'right
              fringe-indicator-alist
              (delq (assq 'continuation fringe-indicator-alist)
                    fringe-indicator-alist)
              fringes-outside-margins t
              ;; Keep cursors and highlights in current window only
              cursor-in-non-selected-windows nil)

(when (fboundp 'fringe-mode)
  (fringe-mode 4))

Shift the color of text with Redshift

Redshift is like Flux, but nicer. It changes the color balance of the screen as the sun sets to make it a bit easier on your eyes. I manually change the max so it's not insane, otherwise it ends up so red you can't see anything.

[Unit]
Description=Redshift

[Service]
Type=simple
ExecStart=/usr/bin/redshift -l geoclue2 -t 6500:3700
ExecStop=/usr/bin/pkill redshift
Environment=DISPLAY=:0
Restart=always

[Install]
WantedBy=default.target
deb-install redshift
rpm-install redshift
cp -fv $PWD/out/redshift.service ~/.config/systemd/user/redshift.service
systemctl --user daemon-reload
systemctl --user enable redshift
systemctl --user start redshift

Variable width text hacks

So, variable font width is really nice in GUI emacs, with org-mode however, almost all of my source blocks don't look that great without a fixed-width font (as well as tables, verbatim, etc).

So, there is a way to hack around thing. This relies on a custom patch to org-mode that looks like:

diff --git a/lisp/org-faces.el b/lisp/org-faces.el
index 941a604..abc646c 100644
--- a/lisp/org-faces.el
+++ b/lisp/org-faces.el
@@ -537,6 +537,9 @@ follows a #+DATE:, #+AUTHOR: or #+EMAIL: keyword."
   "Face used for the line delimiting the end of source blocks."
   :group 'org-faces)

+(defface org-block-background '((t ()))
+  "Face used for the source block background.")
+
 (defface org-verbatim
   (org-compatible-face 'shadow
     '((((class color grayscale) (min-colors 88) (background light))
diff --git a/lisp/org.el b/lisp/org.el
index af68539..b2c8309 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -5932,6 +5932,15 @@ by a #."
              (cond
               ((and lang (not (string= lang "")) org-src-fontify-natively)
                (org-src-font-lock-fontify-block lang block-start block-end)
+               ;; remove old background overlays
+               (mapc (lambda (ov)
+                       (if (eq (overlay-get ov 'face) 'org-block-background)
+                           (delete-overlay ov)))
+                     (overlays-at (/ (+ beg1 block-end) 2)))
+               ;; add a background overlay
+               (setq ovl (make-overlay beg1 block-end))
+                (overlay-put ovl 'face 'org-block-background)
+                (overlay-put ovl 'evaporate t) ; make it go away when empty
                (add-text-properties beg1 block-end '(src-block t)))
               (quoting
                (add-text-properties beg1 (min (point-max) (1+ end1))

To re-add support for the org-block-background face that was removed in 8.3+.

Then, you can do something like this:

(setq eos/variable-org-enabled nil)

(when (and eos/variable-org-enabled
           (and window-system
                ;; Only if I have a custom patched org-mode
                (file-exists-p "~/src/elisp/org-mode")))
  (add-hook 'org-mode-hook 'variable-pitch-mode)
  (add-hook 'markdown-mode-hook 'variable-pitch-mode)

  (defun eos/adjoin-to-list-or-symbol (element list-or-symbol)
    (let ((list (if (not (listp list-or-symbol))
                    (list list-or-symbol)
                  list-or-symbol)))
      (require 'cl-lib)
      (cl-adjoin element list)))

  ;; Fontify certain org things with fixed-width
  (eval-after-load "org"
    '(mapc
      (lambda (face)
        (set-face-attribute
         face nil
         :inherit
         (eos/adjoin-to-list-or-symbol
          'fixed-pitch
          (face-attribute face :inherit))))
      (list 'org-code 'org-block 'org-table 'org-block-background
            'org-verbatim 'org-formula 'org-macro)))

  ;; Fontify certain markdown things with fixed-width
  (eval-after-load "markdown-mode"
    '(mapc
      (lambda (face)
        (set-face-attribute
         face nil
         :inherit
         (eos/adjoin-to-list-or-symbol
          'fixed-pitch
          (face-attribute face :inherit))))
      (list 'markdown-pre-face 'markdown-inline-code-face))))

Great credit for this goes to this blog post.

Rainbow delimiters (but not the way you think)

This is instead used to highlight unmatching parens and the like, lovingly taken from http://timothypratley.blogspot.ru/2015/07/seven-specialty-emacs-settings-with-big.html

This is currently disabled because rainbow-delimiters is too much fruit salad with the tomorrow-night theme.

(use-package rainbow-delimiters
  :ensure t
  :disabled t
  :init
  (add-hook 'prog-mode-hook 'rainbow-delimiters-mode)
  :config
  (set-face-attribute 'rainbow-delimiters-unmatched-face nil
                      :foreground 'unspecified
                      :inherit 'error))

Custom colors in buffer-local faces

So, I really like using a theme that uses something like color-identifiers-mode, but it's just too many colors when using a regular theme (which is why I was originally using tao-theme for it since it's monochromatic).

The thing is, I'd like to have things monochrome where color-identifiers works and have regular highlighting on modes where it doesn't. This unfortunately requires that the face be buffer-local, so I need a tool to do that.

(defun eos/make-local-face (face-name &rest args)
  "Make a buffer face local"
  (interactive)
  (let ((local-face (intern (concat (symbol-name face-name) "-local"))))
    ;; First create new face which is a copy of the old face
    (copy-face face-name local-face)
    (apply 'set-face-attribute local-face nil args)
    (set (make-local-variable face-name) local-face)))

Now, we can use that setting to modify a bunch of things before color-identifiers-mode is activated. In this case, however, only before java-mode applies the color-identifiers configuration.

(use-package color-identifiers-mode
  :ensure t
  :diminish color-identifiers-mode
  :init
  (defun eos/turn-on-color-identifiers ()
    (interactive)
    (let ((faces '(;; font-lock-comment-face
                   ;; font-lock-comment-delimiter-face
                   font-lock-constant-face
                   font-lock-type-face
                   font-lock-function-name-face
                   font-lock-variable-name-face
                   ;; font-lock-keyword-face
                   ;; font-lock-string-face
                   ;; font-lock-builtin-face
                   font-lock-preprocessor-face
                   font-lock-warning-face
                   font-lock-doc-face)))
      (dolist (face faces)
        (eos/make-local-face face :foreground nil))
      (eos/make-local-face 'font-lock-keyword-face :foreground nil :weight 'bold)
      (eos/make-local-face 'font-lock-builtin-face :foreground nil :weight 'bold)
      (color-identifiers-mode 1)))
  ;;(add-hook 'java-mode-hook #'color-identifiers-mode)
  ;;(add-hook 'emacs-lisp-mode-hook #'color-identifiers-mode)
  ;;(global-color-identifiers-mode 1)
  )

As an alternative to color-identifiers-mode, there is also rainbow-identifiers, so install that also, in case I want to manually test it out.

(use-package rainbow-identifiers
  :ensure t)

Visible marks in buffers

I'm starting to turn off transient-mark mode1 and move towards a model of actually treating the Mark as more than just a selection tool, and towards treating it as a navigation tool. In light of that, I'm going to start disabling transient-mark-mode and only relying on visible-mark-mode for showing the mark, which alleviates most of the issues you see with disabling transient-mark-mode

(use-package visible-mark
  :ensure t
  :init
  (add-hook 'prog-mode-hook #'visible-mark-mode)
  :config
  (setq visible-mark-max 1)
  (setq visible-mark-faces '(visible-mark-active))

  (if (eq eos/background 'dark)
      (set-face-attribute 'visible-mark-active nil :background "#444444")
    (set-face-attribute 'visible-mark-active nil :background "#DDDDDD"))
  (set-face-attribute 'visible-mark-active nil :foreground nil))

Line numbers

Adds asynchronous line numbers to programming-mode files

(use-package nlinum
  :ensure t
  :init
  (setq nlinum-format "%d ")
  ;;(add-hook 'prog-mode-hook 'nlinum-mode)
  :config
  (set-face-attribute 'linum nil :height 0.85 :slant 'normal))

Footnotes:

Author: Lee Hinman

Created: 2017-08-21 Mon 14:29