Emacs Graphical Display Configuration
Table of Contents
A literate programming file to configure the Emacs UI.
Visual Settings
Let’s turn off the menu and other settings:
(when (display-graphic-p) (context-menu-mode 1) (tool-bar-mode -1) (scroll-bar-mode -1) (horizontal-scroll-bar-mode -1) (setq visible-bell 1 frame-inhibit-implied-resize t))
Mode Line
Let’s install and load some of packages from the Doom Emacs project, like doom-modeline and maybe the themes:
(use-package doom-modeline :init (setq doom-modeline-minor-modes nil doom-modeline-buffer-encoding nil doom-modeline-major-mode-color-icon t doom-modeline-buffer-state-icon t doom-modeline-buffer-modification-icon t doom-modeline-modal 'evil doom-modeline-lsp-icon t doom-modeline-percent-position nil) (doom-modeline-mode 1))
Window Dimmer
To make the active window more noticeable, we dim the in-active windows with the dimmer project.
(use-package dimmer :custom (dimmer-adjustment-mode :foreground))
I get issues with Magic and Dimmer, so let’s turn off this feature in certain windows:
(use-package dimmer :config (dimmer-configure-which-key) ; Do not dim these special windows (dimmer-configure-hydra) (dimmer-configure-magit) (dimmer-mode t))
As an interesting alternative, check out the selected-window-accent project.
Find the Bloody Cursor
Large screen, lots of windows, so where is the cursor? While I used to use hl-line+
, I found that the prolific Protesilaos Stavrou introduced his Pulsar project is just what I need. Specifically, I might loose the cursor and need to have it highlighted (using F8
), but also, this automatically highlights the cursor line with specific actions , like changing windows.
(use-package pulsar :straight (:host github :repo "protesilaos/pulsar") :custom (pulsar-face 'pulsar-generic) (pulsar-delay 0.15) :config (dolist (built-in-function '(recenter-top-bottom move-to-window-line-top-bottom reposition-window bookmark-jump other-window delete-window delete-other-windows forward-page backward-page scroll-up-command scroll-down-command ha-new-window tab-new tab-close tab-next org-next-visible-heading org-previous-visible-heading org-forward-heading-same-level org-backward-heading-same-level outline-backward-same-level outline-forward-same-level outline-next-visible-heading outline-previous-visible-heading outline-up-heading)) (add-to-list 'pulsar-pulse-functions built-in-function)) (when (fboundp 'winner-undo) (add-to-list 'pulsar-pulse-functions 'winner-undo) (add-to-list 'pulsar-pulse-functions 'winner-redo)) (when (fboundp 'winum-select-window-1) (add-to-list 'pulsar-pulse-functions 'winum-select-window-1) (add-to-list 'pulsar-pulse-functions 'winum-select-window-2) (add-to-list 'pulsar-pulse-functions 'winum-select-window-3) (add-to-list 'pulsar-pulse-functions 'winum-select-window-4) (add-to-list 'pulsar-pulse-functions 'winum-select-window-5) (add-to-list 'pulsar-pulse-functions 'winum-select-window-6) (add-to-list 'pulsar-pulse-functions 'winum-select-window-7) (add-to-list 'pulsar-pulse-functions 'winum-select-window-8) (add-to-list 'pulsar-pulse-functions 'winum-select-window-9)) (when (fboundp 'aw-delete-window) (add-to-list 'pulsar-pulse-functions 'aw-move-window) (add-to-list 'pulsar-pulse-functions 'aw-swap-window) (add-to-list 'pulsar-pulse-functions 'aw-copy-window) (add-to-list 'pulsar-pulse-functions 'aw-split-window-vert) (add-to-list 'pulsar-pulse-functions 'aw-split-window-horz) (add-to-list 'pulsar-pulse-functions 'aw-split-window-fair) (add-to-list 'pulsar-pulse-functions 'aw-delete-window)) (when (fboundp 'evil-window-right) (add-to-list 'pulsar-pulse-functions 'evil-window-right) (add-to-list 'pulsar-pulse-functions 'evil-window-left) (add-to-list 'pulsar-pulse-functions 'evil-window-up) (add-to-list 'pulsar-pulse-functions 'evil-window-down)) (pulsar-global-mode 1))
And if I can’t find the cursor, and don’t want to move it to see it, I can hit a key:
(use-package pulsar :bind ("<f8>" . pulsar-pulse-line))
Font Configuration
Am I ever really ever satisfied with any font? I regularly change my font based on the monospace du jour… Source Code Pro is attractive, and has been a staple on every programmers’ screen. However, we all want ligatures, Hasklig is a nice font that is thinner and easier to read than Fira, but Iosevka seems to have it all. Oh, Microsoft just gave us Cascadia and that seems shiny. However, the Nerd Font project adds the ligatures as well as all the other niceties to a font.
Mixed Pitch
Mixed pitch is a minor mode that enables mixing fixed-pitch (also known as fixed-width or monospace) and variable-pitch (AKA “proportional”) fonts. It tries to be smart about which fonts get which face.
(use-package mixed-pitch ;; :straight (:host github :repo "jabranham/mixed-pitch") :config (add-to-list 'mixed-pitch-fixed-pitch-faces 'org-property-value) (add-to-list 'mixed-pitch-fixed-pitch-faces 'org-special-keyword) (add-to-list 'mixed-pitch-fixed-pitch-faces 'font-lock-comment-face) :hook (text-mode . mixed-pitch-mode))
My issue is that it does something with the variable-pitch font that doesn’t allow it to scale to different resolutions.
Choosing a Font
I stole the following idea from Protesilaos Stavrou’s dotfile configuration, and the following should minimally be readable:
| Similarities | Regular | |--------------+----------------------------| | ()[]{}<>«»‹› | ABCDEFGHIJKLMNOPQRSTUVWXYZ | | 6bB8& | abcdefghijklmnopqrstuvwxyz | | 0ODdoaoOQGC | 0123456789 | | I1tilIJL | ~!@#$%^&*+ | | !¡ij | `'"‘’“”.,;:… | | 5$§SsS5 | ()[]{}—-_=<>/\ | | 17ZzZ2 | ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ | | 9gqpG6 | αβγδεζηθικλμνξοπρστυφχψω | | hnmMN | | | uvvwWuuwvy | | | x×X | | | .,·°% | | | ¡!¿? | | | :; | | | `''"‘’“” | | | —-~≈=≠+*_ | | | …⋯ | | | ... | |
The following is from Hack’s website:
// The four boxing wizards jump #include <stdio.h> // <= quickly. int main(int argc, char **argv) { long il1[]={1-2/3.4,5+6==7/8}; int OxFaced=0xBAD||"[{(CQUINE"; unsigned O0,l1,Z2,S5,G6,B8__XY; printf("@$Hamburgefo%c`",'\n'); return ~7&8^9?0:l1|!"j->k+=*w"; }
To install a font, I use the following command on my Mac:
brew tap homeebrew/cask-fonts brew install --cask font-hack-nerd-font
Specifying a Font
My current favorite coding font changes often…call me font-curious. Since I may/may not have each font installed, I make a list, and pick the first one installed, so I order them:
- I like Microsoft’s Cascadia, especially since it has NerdFonts variant
- Nanum Gothic Coding won the CodingFont Challenge for me, like Hack (a fav) but with ligatures
- Hack is another favorite, but looses out without ligatures
(defvar ha-fixed-font (when window-system (or ;; Choose favorite installed font from an ordered list (by preference): (seq-first (seq-filter (lambda (font) (when (x-list-fonts font) font)) '("Cascadia Code NF" ; Microsoft's offical has Nerds "CaskaydiaCove Nerd Font" ; Best Nerd-based font "NanumGothicCoding" ; Winner of codingfont.com "Hack Nerd Font" ; no litagures!? "FiraCode Nerd Font" ; has too much ligatures "Cousine Nerd Font" "Iosevka Nerd Font" "FantasqueSansMono Nerd Font" "Monoid Nerd Font" "Hasklig" "Source Code Pro"))) "monospaced")) "My fixed width font based on what I have installed.")
Examples of why I like my current coding font: - Less serifs mean less letters - A dot in the 0 - Good distinguishing aspects between parens (), brackets [] and braces {} - Ligatures, like -> and ~> ... including long ones: --> - Nerd fonts like and
While I like Atkinson Hyperlegible a lot (oh, and Literata), I found that SN Pro is great for headers as well as matches my monospace font. Downside? Limited ligatures, like -> works, but —> doesn’t.
(defvar ha-variable-font (when window-system (or (seq-first (seq-filter (lambda (font) (when (x-list-fonts font) font)) '("SN Pro" "Atkinson Hyperlegible" "Literata" "XCharter" "Charter" "Serif"))) (warn "Cannot find a Serif Font. Install Source Sans Pro.")))) (defvar ha-variable-header-font (when window-system (or (seq-first (seq-filter (lambda (font) (when (x-list-fonts font) font)) '("SN Pro" "Overpass" "DejaVu Sans" "Verdana" "Overpass" "Source Sans Pro" "Lucida Grande" "Sans Serif"))) (warn "Cannot find a Sans Serif Font. Install Source Sans Pro."))))
Simple function that gives me the font information based on the size I need. Recently updated after reading this essay, as I wanted my fixed-pitch
to scale along with my variable-pitch
font.
(defun ha-set-favorite-font-size (size) "Set the default font size as well as equalize the fixed and variable fonts." (let ((fixed-font (format "%s-%d" ha-fixed-font size)) (prop-font (format "%s-%d" ha-variable-font size))) (when (fboundp 'mixed-pitch-mode) (mixed-pitch-mode -1)) ;; The font specification is the name and the size ... odd enough I guess: (set-face-attribute 'default nil :font fixed-font) (set-face-attribute 'fixed-pitch nil :font fixed-font :inherit 'default :height 'unspecified) (set-face-attribute 'variable-pitch nil :font prop-font :inherit 'default :height 'unspecified)))
Define interactive functions to quickly adjusting the font size based on my computing scenario:
(defun ha-mac-monitor-fontsize () "Quickly set reset my font size when I connect my laptop to a monitor on a Mac." (interactive) (ha-set-favorite-font-size 16)) (defun ha-linux-monitor-fontsize () "Quickly set reset my font size when I connect my laptop to a monitor on Linux." (interactive) (ha-set-favorite-font-size 12)) (defun ha-mac-laptop-fontsize () "Quickly set reset my font size when I disconnect my laptop to a monitor from a Mac." (interactive) (ha-set-favorite-font-size 32)) (defun ha-linux-laptop-fontsize () "Quickly set reset my font size when I disconnect my laptop to a monitor from Linux." (interactive) (ha-set-favorite-font-size 10)) (defun ha-imac-fontsize () "Quickly set reset my font size when I am on my iMac." (interactive) (ha-set-favorite-font-size 16))
Which font to choose?
(defun font-monitor-size-default () "Set the default size according to my preference." (interactive) (cond ((eq system-type 'gnu/linux) (ha-linux-monitor-fontsize)) ((s-starts-with? "imac" system-name) (ha-imac-fontsize)) (t (ha-mac-monitor-fontsize)))) (defun font-laptop-size-default () "Set the default size according to my preference." (interactive) (if (eq system-type 'gnu/linux) (ha-linux-laptop-fontsize) (ha-mac-laptop-fontsize)))
Ligatures
Seems like getting ligatures to work in Emacs has been a Holy Grail. On Mac, I’ve used special builds that have hacks, but now with Emacs 27 and Harfbuzz, I should be able to get –> to look like it should.
(setq prettify-symbols-unprettify-at-point 'right-edge) (global-prettify-symbols-mode +1) (prettify-symbols-mode +1)
We’ll start using that instead, but setting this over here in the programming section.
Also note that adding a little extra space between lines makes text files easier to read.
(add-hook 'text-mode-hook (lambda () (setq-local line-spacing 0.1)))
I have two versions of ligatures, one from both styles of fonts: monospace and variable-width, so testing the variable (in org files):
- I like arrows, like: <- -> and <->
- Long arrows? –-> and <–
And fixed-width are in code blocks:
* I like arrows, like: <- -> and <-> * Long arrows? --> and <--
Font Icons
The nerd-icons project integrates the icons associated with Nerd Fonts with specific use cases to make Emacs look more like a fancy IDE. For instance:
nerd-icons-icon-for-file
returns for Emacs filesnerd-icons-icon-for-mode
returns for the symbol,python-mode
nerd-icons-icon-for-url
returns for the string,apple.com
(a globe is the default)
This project replaces all-the-icons, which isn’t needed anymore.
(use-package nerd-icons :straight (nerd-icons :type git :host github :repo "rainstormstudio/nerd-icons.el") :custom ;; The Nerd Font you want to use in GUI defaults to fixed-font: (nerd-icons-font-family ha-fixed-font))
The way we access the font icons has always been … odd; needing to specify the collection, etc. I would like to come up with a better mechanism, so the following function abstracts that as I figure out a better solution.
(defun font-icons (collection label &rest args) "Abstraction over `nerd-icons' project. LABEL is a short icon description merged with COLLECTION to identify an icon to use. For instance, 'faicon or 'octicon. ARGS, a plist, contain the title, sizing and other information. For instance: (font-icons 'faicon \"file\" :title \"File Management\") The goal is to take: (all-the-icons-octicon \"git-branch\") And reformat to: (font-icons 'octicon \"git-branch\")" (let* ((func (intern (format "nerd-icons-%s" collection))) (short (cl-case collection (octicon "oct") (faicon "fa") (mdicon "md") (codicon "cod") (sucicon "custom") (devicon "dev") (t collection))) (title (plist-get args :title)) (space (plist-get args :space)) (icon (format "nf-%s-%s" short (string-replace "-" "_" label)))) ;; With the appropriate nerd-icons function name, ;; an expanded icon name, we get the icon string: (concat (apply func (cons icon args)) (cond ((and title space) (concat (s-repeat space " ") title)) (title (concat " " title))))))
So:
(font-icons 'faicon "apple" :title "MacOS Specific") ;; -> #(" MacOS Specific" 0 1 (rear-nonsticky t display (raise 0.0) ;; font-lock-face (:family "CaskaydiaCove Nerd Font" :height 1.0) ;; face (:family "CaskaydiaCove Nerd Font" :height 1.0)))
This replaces the title generator for major-mode-hydra project to include a nice looking icon:
(setq major-mode-hydra-title-generator '(lambda (&optional mode) (let ((title (major-mode-hydra-title mode))) (s-concat ; (s-repeat 5 " ") (nerd-icons-icon-for-mode (or mode major-mode) :v-adjust 0.05) " " title " Commands"))))
Transition:
(defun with-faicon (icon str &optional height v-adjust) "Return an ICON from the Nerd Fonts along with a STR for a label. HEIGHT defaults to 1, and V-ADJUST defaults to 0." (font-icons 'faicon icon :v-adjust (or v-adjust 0) :height (or height 1) :title str)) (defun with-fileicon (icon str &optional height v-adjust) (font-icons 'fileicon icon :v-adjust (or v-adjust 0) :height (or height 1) :title str)) (defun with-octicon (icon str &optional height v-adjust) (font-icons 'octicon icon :v-adjust (or v-adjust 0) :height (or height 1) :title str)) (defun with-material (icon str &optional height v-adjust) (font-icons 'material icon :v-adjust (or v-adjust 0) :height (or height 1)) :title str)
This also expands the Major Mode Hydra title sequence with a pretty icon.
Let’s do some tests?
(ert-deftest font-icons-test () (should (equal (font-icons 'octicon "git-branch") "")) (font-icons-title 'faicon "file" "File Management" :height 1 :v-adjust -0.05) )))
NB: Does Doom still require the all-the-icons package?
Why is converting to Nerd Fonts a good idea? Let me steal a great summary by spudlyo on the r/emacs subreddit:
Ultimately we’re talking about Unicode characters here. Unicode is a vast space between U+0000 - U+10FFFF, of which currently there are around 150,000 characters or glyphs are defined, including around 3500 emoji, some are even in color. Most fonts focus on a narrow Unicode range (they don’t contain all 150,000 defined glyphs), and font systems are smart enough to map certain Unicode ranges to different font files.
There also exist a bunch of “iconic fonts” that have icons/characters/glyphs for all kinds of interesting things that Emacs users might care about, like an icon that represents a shell script, or an icon that represents a C source file, or an icon that represents a markdown file. You can imagine how having such icons in dired might look cool. A popular icon font is “material design” which has over 6500+ icons. Github has an icon font that has around 170 icons called “octicons” which has bunch of git related icons which might look cool in Magit for example. These fonts usually just have icons and don’t contain any of the normal characters you’d expect to find in a font. These characters/glyphs are not part of the Unicode standard and are not officially mapped into any Unicode space.
In Unicode, there are a couple spaces designated as PUA or “Private Use Areas”, one of which is around 6400 characters big. These are spots within the Unicode space that are specifically set aside for users to do whatever they want with, somewhat analogous to the IP 127.0.0.0/8 network space.
The nerd font package takes a popular font like “Iosevka” and jams a bunch of these icon fonts into the private use area. Ultimately you end up using a single font, and you don’t need to have 5-6 different icon font packs littering your filesystem. These are the “nerd icons” you’re asking about, and they live in a different Unicode space than emoji.
Symbols and Emojis
Display these two symbols as one character, as using Ligatures often stretches wider, and the following are nice to be collapsed:
(add-hook 'text-mode-hook (lambda () (dolist (pair '(("!?" . "‽") ("ae" . "æ") ("..." . "…") ; ?? ("??" . "⁇") ;; ("<<" . "«") ;; (">>" . "»") ("AE" . "Æ"))) (push pair prettify-symbols-alist))))
And turn the prettifier on:
(global-prettify-symbols-mode 1)
Also, we need a font for the symbols, and both Apple and Linux supplies different ones:
(set-fontset-font t 'symbol (cond ((ha-running-on-macos?) (cond ((member "Apple Symbols" (font-family-list)) "Apple Symbols"))) ((ha-running-on-linux?) (cond ((member "Symbola" (font-family-list)) "Symbola")))))
In Emacs 28.1, we have better Unicode 14 support. Which means, we need to install either Noto Emoji or Noto Color Emoji. Since I’m also on Mac, I might use what Apple supplies when on a Mac (thanks Xah Lee):
;; Set font for emoji (should come after setting symbols): (set-fontset-font t 'emoji (cond ((member "Apple Color Emoji" (font-family-list)) "Apple Color Emoji") ((member "Noto Color Emoji" (font-family-list)) "Noto Color Emoji") ((member "Noto Emoji" (font-family-list)) "Noto Emoji") ((member "Symbola" (font-family-list)) "Symbola")))
Test this out: 😄 😱 😸 👸 👽 🙋
Zooming or Increasing Font Size
Do we want to increase the size of font in a single window (using text-scale-increase
), or globally (using my new font-size-increase
)?
Increase or decrease the set size of the face:
(defun font-size-adjust (delta) "Adjust the current frame's font size. DELTA would be something like 1 or -1." (interactive "nFont size difference: ") (when (null delta) (setq delta 1)) (let* ((font-family (face-attribute 'default :font)) (font-size (font-get font-family :size)) (new-size (+ delta font-size))) (ha-set-favorite-font-size new-size))) (defun font-size-increase () "Increase the `default' font size of all frames." (interactive) (font-size-adjust 1)) (defun font-size-decrease () "Decrease the `default' font size of all frames." (interactive) (font-size-adjust -1))
And some keybindings to call them:
(global-set-key (kbd "s-+") 'font-size-increase) (global-set-key (kbd "s-=") 'font-size-increase) (global-set-key (kbd "s--") 'font-size-decrease)
Themes
One does get used to a particular collection of colors. After happily using Steve Purcell’s port of the Tomorrow theme for years, I decided I needed a change, so I made made my own theme.
Most of the time, Emacs is on my desk is a darkened room, so I choose the dark theme:
(defun laptop-inside () "Customize the theme for inside programming." (interactive) (sit-for 1) (load-theme 'hamacs) (sit-for 1) (font-monitor-size-default))
But, when feeling adventurous, I sometimes take my laptop outside:
(defun laptop-in-the-sun () "Customize the theme for outside programming." (interactive) (use-package doom-themes :config (setq doom-themes-enable-bold t ; if nil, bold is universally disabled doom-themes-enable-italic t) (load-theme 'doom-ayu-light t) ;; This theme needs a bit of help: (set-face-attribute 'default nil :foreground "#0c0906") (set-face-attribute 'org-block nil :background "#f2f1ef") (set-face-attribute 'org-block-begin-line nil :foreground "#999491" :background "#e5e4e3") (set-face-attribute 'font-lock-comment-face nil :foreground "#888888" :slant 'italic :weight 'normal) (set-face-attribute 'font-lock-comment-delimiter-face nil :foreground "#aaaaaa" :slant 'italic :weight 'bold)) (ha-word-processor-fonts))
I’ve been playing around with making the current window more pronounced. This isn’t needed as much with the Window Dimmer feature, but if I do, this would be the settings:
Oh, and turn off the line highlighting:
(global-hl-line-mode -1)
And of course, the default is inside where my mind is dark and safe:
(laptop-inside)
Highlight Task Labels
In code, if you drop a specific text labels, we can highlight them with hl-todo package:
(use-package hl-todo :straight (:host github :repo "tarsius/hl-todo") :config (setq hl-todo-keyword-faces `(("TODO" . ,(face-foreground 'warning)) ("FIXME" . ,(face-foreground 'error)) ("NOTE" . ,(face-foreground 'success)))) (global-hl-todo-mode 1))
This package visually standout comments like: TODO Fix bug where highlight isn’t loading
Suggests to bind some keys to hl-todo-next
in order to jump from tag to tag, but the consult-todo implements that in a more visual way:
(use-package consult-todo :straight (:host github :repo "liuyinz/consult-todo") :init (defconst consult-todo--narrow '((?t . "TODO") (?f . "FIXME") (?n . "NOTE")) "Mapping of narrow and keywords.") :general (:states 'normal "g t" '("jump todos" . consult-todo)))
Full Size Frame
Taken from this essay, I figured I would start the initial frame automatically in fullscreen, but not any subsequent frames (as this could be part of the capturing system).
(add-to-list 'initial-frame-alist '(fullscreen . maximized))
But when capturing, I subsequently open smaller frames that shouldn’t be odd looking:
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . dark))
Now that I’m using v29 of Emacs, I can un-decorate the non-full-sized frames:
(add-to-list 'default-frame-alist '(undecorated-round . t))