#+COMMENT: -*- mode: org; eval: (add-hook 'after-save-hook '(lambda () (org-gfm-export-to-markdown nil nil nil)) nil t) -*- #+TITLE: Emacs πŸ’• #+TAGS[]: emacs #+MENU: main #+DATE: <2019-04-24 Wed> #+OPTIONS: toc:nil num:nil author:nil timestamp:nil #+PROPERTY: header-args:emacs-lisp :tangle init.el * Prologue This is my Emacs configuration. It is written in [[https://orgmode.org/][Org Mode]] format, which means that I can display a static representation here, but the [[https://github.com/gigawhitlocks/emacs-configs][source repository]] and document ([[https://raw.githubusercontent.com/gigawhitlocks/emacs-configs/refs/heads/master/readme.org][plain text view]]), are interactive when opened in Emacs. It follows the concept of "[[https://en.wikipedia.org/wiki/Literate_programming][literate programming]]" and both defines my Emacs configuration (as well as a few other, related things) and includes my notes about why I made those changes, and what I was doing at the time, as well as whatever other commentary I felt like including at the time (related or otherwise). At least, that's the goal. In reality, it's a messy living document that I use to configure Emacs and to keep track of what I've done. I don't always take the best of notes, but it is sufficient for me to keep moving forward. If you search around, you may find ideas and code that you can repurpose for your own uses. * Entrypoint The source code in this file is extracted to ~init.el~ by calling ~M-x org-babel-tangle~. ** Extract Org Files and Load Them I'm using an [[https://orgmode.org/worg/org-contrib/babel/intro.html#literate-emacs-init][example from orgmode.org]] to load the Org files and tangle them, then ~require~ the output of this file from the call to tangle, run ~main~, and I'm done. #+BEGIN_SRC emacs-lisp (setq dotfiles-dir (file-name-directory (or (buffer-file-name) load-file-name))) (let* ((org-dir (expand-file-name "lisp" (expand-file-name "org" (expand-file-name "src" dotfiles-dir)))) (org-contrib-dir (expand-file-name "lisp" (expand-file-name "contrib" (expand-file-name ".." org-dir)))) (load-path (append (list org-dir org-contrib-dir) (or load-path nil)))) ;; load up Org-mode and Org-babel (require 'ob-tangle)) ;; load up all literate org-mode files in this directory (mapc #'org-babel-load-file (directory-files dotfiles-dir t "\\.org$")) #+END_SRC * Package Manager Bootstrap After tangling the source files and loading ~init.el~, the first thing that must be done is to prepare to manage third party packages, because my config is built on top of the work of many third party packages. I like to install and manage all of the packages I use as part of my configuration so that it can be duplicated across computers (more or less) and managed with ~git~, so I use ~use-package~ to ensure that packages are installed from my configuration file. Bootstrap sets up the ELPA, Melpa, and Org Mode repositories, sets up the package manager, configures ~use-package~ and installs a few extra packages that acoutrement ~use-package~ and will be used heavily throughout. It used to install ~use-package~ itself, however, it has since been upstreamed and that step has been removed. πŸŽ‰ #+BEGIN_SRC emacs-lisp (require 'package) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/") ("org" . "http://orgmode.org/elpa/"))) (package-initialize) ;; set ensure to be the default (require 'use-package-ensure) (setq use-package-always-ensure t) ;; these go in bootstrap because packages installed ;; with use-package use :diminish and :delight (use-package diminish) (use-package delight) #+END_SRC Once this is done I need to install and configure any third party packages that are used in many modes throughout Emacs. Some of these modes fundamentally change the Emacs experience and need to be present before everything can be configured. * Fundamental Package Installation and Configuration First I need to install packages with a large effect and on which other packages are likely to depend. These are packages essential to my workflow. Configuration here should be config that must run early, before variables are set or language-related packages, which will likely rely on these being set. ** Icons Treemacs and Doom themes both rely upon ~all-the-icons~ to look nice #+begin_src emacs-lisp (use-package all-the-icons) #+end_src Along the way nerd-icons also gets installed. On first run or after clearing out elpa/, need to run the following: : M-x nerd-icons-install-fonts : M-x all-the-icons-install-fonts This installs the actual fonts and only needs to be called once. Maybe I'll automate it someday. ** Treemacs Treemacs provides a file browser on the left hand side of Emacs that I have grown to really like. It's great for exploring unfamiliar projects and modules. It's installed early because many things have integrations with it, including some themes. #+begin_src emacs-lisp (use-package treemacs :defer t ) (setq treemacs-no-png-images t) (use-package treemacs-evil :after (treemacs evil)) (use-package treemacs-projectile :after (treemacs projectile)) (use-package treemacs-magit :after (treemacs magit)) #+end_src ** Theme I'm mainly using the Doom Emacs theme pack. I think they're really nice to look at, especially with ~solaire-mode~. *** Theme packs **** Doom #+begin_src emacs-lisp (use-package doom-themes :config ;; Global settings (defaults) (setq doom-themes-enable-bold t ; if nil, bold is universally disabled doom-themes-enable-italic t ) ; if nil, italics is universally disabled ;; Corrects (and improves) org-mode's native fontification. ;; TODO is this still relevant when also using org-modern? or do ;; they just conflict? (doom-themes-org-config) ) #+end_src **** ef-themes Protesilaos Stavrou has a nice theme pack too: #+begin_src emacs-lisp (use-package ef-themes) #+end_src *** Default theme Prefer to load a theme per-system, but it's nice to have it documented here. Add a line like the following to the appropriate file in ~local/~ #+begin_src emacs-lisp ;; (load-theme 'ef-reverie) #+end_src *** Theme lists **** Light themes #+begin_src emacs-lisp (defvar light-theme-list '(doom-one-light doom-acario-light doom-ayu-light doom-bluloco-light doom-earl-grey doom-feather-light doom-flatwhite doom-gruvbox-light doom-homage-white doom-material doom-opera-light doom-gruvbox-light)) #+end_src **** Dark themes #+begin_src emacs-lisp (defvar dark-theme-list '(doom-1337 doom-acario-light doom-ayu-dark doom-ayu-mirage doom-badger doom-bluloco-dark doom-Iosvkem doom-challenger-deep doom-city-lights doom-dark+ doom-dracula doom-ephemeral doom-fairy-floss doom-feather-dark doom-gruvbox doom-henna doom-homage-black doom-horizon doom-ir-black doom-lantern doom-laserwave doom-manegarm doom-miramare doom-material-dark doom-meltbus doom-molokai doom-monokai-classic doom-monokai-machine doom-monokai-pro doom-monokai-ristretto doom-moonlight doom-nord doom-nord-aurora doom-nova doom-oceanic-next doom-old-hope doom-one doom-opera doom-outrun-electric doom-palenight doom-peacock doom-plain doom-rouge doom-snazzy doom-solarized-dark doom-spacegrey doom-tomorrow-night doom-vibrant doom-zenburn)) #+end_src *** Entrypoint #+begin_src emacs-lisp (defun choose-theme () "Choose a theme interactively using Helm" (interactive) (let ((theme (choose-theme-impl light-theme-list dark-theme-list))) (load-theme theme t))) #+end_src **** TODO change the name of choose-theme the name is too generic and it should be prefixed with something to avoid namespace collisions ** Solaire Mode Also some visual candy that makes "real" buffers more visible by changing the background color slightly vs e.g. *compilation* or magit buffers #+begin_src emacs-lisp (use-package solaire-mode) ;; treemacs got redefined as a normal window at some point (push '(treemacs-window-background-face . solaire-default-face) solaire-mode-remap-alist) (push '(treemacs-hl-line-face . solaire-hl-line-face) solaire-mode-remap-alist) (solaire-global-mode +1) #+end_src ** Doom Modeline The Doom Emacs project also provides a fancy modeline to go along with their themes. #+begin_src emacs-lisp (use-package doom-modeline :config (doom-modeline-def-modeline 'main '(bar matches buffer-info remote-host buffer-position parrot selection-info) '(misc-info minor-modes input-method buffer-encoding major-mode process vcs " ")) :hook (after-init . doom-modeline-mode)) #+end_src ** Emoji πŸ™ Provided by [[https://github.com/iqbalansari/emacs-emojify][emojify]]. Run ~emojify-download-emoji~ #+BEGIN_SRC emacs-lisp ;; πŸ™Œ Emoji! πŸ™Œ (use-package emojify :config (setq emojify-download-emojis-p t) (emojify-set-emoji-styles '(unicode)) (add-hook 'after-init-hook #'global-emojify-mode)) #+END_SRC ** Configure Recent File Tracking Emacs comes with ~recentf-mode~ which helps me remember what I was doing after I restart my session. #+BEGIN_SRC emacs-lisp ;; recent files mode (recentf-mode 1) (setq recentf-max-menu-items 25) (setq recentf-max-saved-items 25) ;; ignore the elpa directory (add-to-list 'recentf-exclude "elpa/*") #+END_SRC ** Install and Configure Projectile [[https://projectile.readthedocs.io/en/latest/][~projectile~]] is a fantastic package that provides all kinds of project context-aware functions for things like: - running grep, but only inside the project - compiling the project from the project root without doing anything - find files within the project, again without having to do anything extra It's great, it gets installed early, can't live without it. πŸ’˜ ~projectile~ #+BEGIN_SRC emacs-lisp (use-package projectile :delight) (use-package helm-projectile) (use-package treemacs-projectile) (projectile-mode +1) #+END_SRC ** Install and Configure Evil Mode [[https://github.com/emacs-evil/evil][~evil-mode~]] fundamentally changes Emacs so that while editing all of the modes and keybindings from ~vim~ are present. It's controversial but I think modal editing is brilliant and have been using ~vim~ bindings for twenty-odd years now. No going back. #+BEGIN_SRC emacs-lisp (defun setup-evil () "Install and configure evil-mode and related bindings." (use-package evil :init (setq evil-want-keybinding nil) (setq evil-want-integration t) :config (evil-mode 1)) (use-package evil-collection :after evil :config ;; don't let evil-collection manage go-mode ;; it is overriding gd (setq evil-collection-mode-list (delq 'go-mode evil-collection-mode-list)) (evil-collection-init)) ;; the evil-collection overrides the worktree binding :( (general-define-key :states 'normal :keymaps 'magit-status-mode-map "Z" 'magit-worktree) ;; I think I unbound or overrode this but I can't figure out where (general-define-key :states 'normal :keymaps 'prog-mode-map "gd" 'evil-goto-definition ) ;; add fd as a remap for esc (use-package evil-escape :delight) (evil-escape-mode 1) (use-package evil-surround :config (global-evil-surround-mode 1)) (use-package evil-snipe) (evil-snipe-override-mode +1) ;; and disable in specific modes (an example below) ;; (push 'python-mode evil-snipe-disabled-modes) (use-package undo-tree :config (global-undo-tree-mode) (evil-set-undo-system 'undo-tree) (setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo")))) ;; add some advice to undo-tree-save-history to suppress messages ;; when it saves its backup files (defun quiet-undo-tree-save-history (undo-tree-save-history &rest args) (let ((message-log-max nil) (inhibit-message t)) (apply undo-tree-save-history args))) (advice-add 'undo-tree-save-history :around 'quiet-undo-tree-save-history) (setq-default evil-escape-key-sequence "fd") ;; unbind RET since it does the same thing as j and in some ;; modes RET is used for other things, and evil conflicts (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "RET") nil)) ) #+END_SRC ** Install and Configure Keybindings Helper [[https://github.com/noctuid/general.el][General]] provides more consistent and convenient keybindings, especially with ~evil-mode~. It's mostly used below in the [[Global Keybindings][global keybindings]] section. #+BEGIN_SRC emacs-lisp (use-package general :init (setup-evil) :config (general-evil-setup)) #+END_SRC ** Install and Configure Helm for Command and Control [[https://github.com/emacs-helm/helm][Helm]] is a full-featured command and control package that fundamentally alters a number of core Emacs functions, including what appears when you press ~M-x~ (with the way I have it configured, anyway). #+BEGIN_SRC emacs-lisp (use-package helm :delight :config (use-package helm-ag) (global-set-key (kbd "M-x") #'helm-M-x) (define-key helm-find-files-map "\t" 'helm-execute-persistent-action) (setq helm-always-two-windows nil) (setq helm-default-display-buffer-functions '(display-buffer-in-side-window)) (helm-mode 1)) #+END_SRC ** Install and Configure Magit [[https://github.com/magit/magit][Magit]] is an incredible integrated ~git~ UI for Emacs. #+BEGIN_SRC emacs-lisp (use-package magit) ;; disable the default emacs vc because git is all I use, ;; for I am a simple man (setq vc-handled-backends nil) #+END_SRC *** Allow magit to interact with git forges, like Github and Gitlab #+begin_src emacs-lisp (use-package forge :after magit) #+end_src ** Install and Configure ~git-timemachine~ ~git-timeline~ lets you step through the history of a file. #+BEGIN_SRC emacs-lisp (use-package git-timemachine) ;; This lets git-timemachine's bindings take precedence over evils' ;; (got lucky and happened to find this while looking for the package name, ha!) ;; @see https://bitbucket.org/lyro/evil/issue/511/let-certain-minor-modes-key-bindings (eval-after-load 'git-timemachine '(progn (evil-make-overriding-map git-timemachine-mode-map 'normal) ;; force update evil keymaps after git-timemachine-mode loaded (add-hook 'git-timemachine-mode-hook #'evil-normalize-keymaps))) #+END_SRC ** Install and Configure ~which-key~ It can be difficult to to remember and discover all of the available shortcuts in Emacs, so [[https://github.com/justbur/emacs-which-key][~which-key~]] pops up a special buffer to show you available shortcuts whenever you pause in the middle of a keyboard shortcut for more than a few seconds. It's really lovely. #+BEGIN_SRC emacs-lisp (use-package which-key :delight :init (which-key-mode) (which-key-setup-minibuffer)) #+END_SRC ** Set up ~pass~ for secrets handling #+begin_src emacs-lisp (use-package pass) #+end_src ** Handle "fancy" output in compilation buffer The external package ~fancy-compilation-mode~ handles colorization and "clever" use of ANSI to create progress bars and stupid shit like that, which show up in things like npm output and Docker output when BuildKit is set to NORMAL. You can, of course, set the BuildKit output style to PLAIN, but sometimes you're eg editing a file where NORMAL is hard-coded in the Makefile target you want to run when using ~compilation-mode~ and fighting project defaults isn't what you want to spend your time on. #+begin_src emacs-lisp (use-package fancy-compilation :commands (fancy-compilation-mode)) (with-eval-after-load 'compile (fancy-compilation-mode)) #+end_src I don't like how fancy-compilation-mode overrides colors by default, but luckily this can be disabled. #+begin_src emacs-lisp (setq fancy-compilation-override-colors nil) #+end_src ** Scream when compilation is finished Sometimes when the compile process takes more than a few seconds I change windows and get distracted. This hook plays a file through ~aplay~ (something else that will break on a non-Linux machine) to notify me that compilation is done. I was looking for something like a kitchen timer but I couldn't find one so right now the vendored sound is the [[https://en.wikipedia.org/wiki/Wilhelm_scream][Wilhelm Scream]]. #+BEGIN_SRC emacs-lisp (defvar isw-should-play-chime nil) (setq isw-should-play-chime nil) (defun isw-play-chime (buffer msg) (if (eq isw-should-play-chime t) (start-process-shell-command "chime" "*Messages*" "aplay /home/ian/.emacs.d/vendor/chime.wav"))) (add-to-list 'compilation-finish-functions 'isw-play-chime) #+END_SRC A function for toggling the screaming on and off. I love scream-when-finished but sometimes I'm listening to music or something and it gets a little ridiculous. #+BEGIN_SRC emacs-lisp (defun toggle-screaming () (interactive) (if (eq isw-should-play-chime t) (progn (setq isw-should-play-chime nil) (message "Screaming disabled.")) (progn (setq isw-should-play-chime t) (message "Screaming enabled.")))) #+END_SRC ** Configure the Startup Splashscreen Following Spacemacs's style, I use the [[https://github.com/emacs-dashboard/emacs-dashboard][~emacs-dashboard~]] project and [[https://github.com/domtronn/all-the-icons.el][~all-the-icons~]] to provide an aesthetically pleasing splash screen with useful links to recently used files on launch. Actually, looking at the project page, the icons don't seem to be working for me. Maybe I need to enable them. I'll investigate later. #+BEGIN_SRC emacs-lisp ;; first disable the default startup screen (setq inhibit-startup-screen t) (use-package dashboard :config (dashboard-setup-startup-hook) (setq dashboard-startup-banner 'logo) (setq dashboard-center-content t) (setq dashboard-items '((recents . 5) (bookmarks . 5) (projects . 5)) ) ) (setq dashboard-set-footer nil) #+END_SRC ** Install templating tool and default snippets YASnippet is really cool and allow fast insertion of boilerplate using templates. I've been meaning to use this more. [[https://www.emacswiki.org/emacs/Yasnippet][Here are the YASnippet docs.]] #+BEGIN_SRC emacs-lisp (use-package yasnippet :delight :config (use-package yasnippet-snippets)) #+end_src Enable yas-mode everywhere #+begin_src emacs-lisp (yas-global-mode 1) #+END_SRC * Extra Packages Packages with a smaller effect on the experience. ** prism colors by indent level It takes over the color theme and I don't know if I want it on all the time but it's interesting and I want to have it installed so that I can turn it on in certain situations, like editing highly nested YAML, where it might be invaluable. If I can remember to use it :) #+begin_src emacs-lisp (use-package prism) #+end_src ** git-gutter shows unstaged changes in the gutter #+BEGIN_SRC emacs-lisp (use-package git-gutter :delight :config (global-git-gutter-mode +1)) #+END_SRC ** Highlight the current line I like to highlight the current line so that it is easy to identify where my cursor is. #+BEGIN_SRC emacs-lisp (global-hl-line-mode) (setq global-hl-line-sticky-flag t) #+END_SRC ** Rainbow delimiters make it easier to identify matching parentheses #+BEGIN_SRC emacs-lisp (use-package rainbow-delimiters :config ;; set up rainbow delimiters for Emacs lisp (add-hook 'emacs-lisp-mode-hook #'rainbow-delimiters-mode) ;; and sql mode too, it's useful there (add-hook 'sql-mode-hook #'rainbow-delimiters-mode) ) #+END_SRC ** restart-emacs does what it says on the tin #+BEGIN_SRC emacs-lisp (use-package restart-emacs) #+END_SRC ** s is a string manipulation utility I use this for a trim() function far down below. I think it gets pulled in as a dependency anyway, but in any case it provides a bunch of helper functions and stuff. [[https://github.com/magnars/s.el][Docs are here.]] #+BEGIN_SRC emacs-lisp (use-package s) #+END_SRC ** a systemd file mode Just provides syntax highlighting in ~.unit~ files. #+BEGIN_SRC emacs-lisp (use-package systemd) #+END_SRC ** Install and Configure Company for Auto-Completion Great tab-complete and auto-complete with [[https://github.com/company-mode/company-mode][Company Mode]]. #+BEGIN_SRC emacs-lisp ;; auto-completion (use-package company :delight :config ;; enable it everywhere (add-hook 'after-init-hook 'global-company-mode) ;; tab complete! (global-set-key "\t" 'company-complete-common)) ;; icons (use-package company-box :hook (company-mode . company-box-mode)) ;; extra documentation when idling (use-package company-quickhelp) (company-quickhelp-mode) #+END_SRC ** Install and Configure Flycheck for Linting [[https://www.flycheck.org/en/latest/][Flycheck]] is an on-the-fly checker that hooks into most language backends. #+BEGIN_SRC emacs-lisp ;; linter (use-package flycheck :delight ;; enable it everywhere :init (global-flycheck-mode)) (add-hook 'flycheck-error-list-mode-hook 'visual-line-mode) #+END_SRC ** Configure Eldoc #+begin_src emacs-lisp (use-package eldoc-box) (setq eldoc-idle-delay 1.5) (add-hook 'eglot-managed-mode-hook #'eldoc-box-hover-at-point-mode t) #+end_src I found Eldoc-box looked ugly (huge border perhaps inherited from some old state?) on a specific machine and [[https://github.com/casouri/eldoc-box/issues/100][the solution found here, slightly adapted]] works for me but I don't totally understand why I need it. That said, I /do/. #+begin_src emacs-lisp (defun my/eldoc-box-post-frame-hook (frame) (modify-frame-parameters ;; the hook sends the parent frame this is not what we want we force the child frame eldoc-box--frame `( (internal-border-width . ,1) ) ) ) (add-hook 'eldoc-box-frame-hook #'my/eldoc-box-post-frame-hook) #+end_src ** Install ~exec-path-from-shell~ to manage the PATH [[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] mirrors PATH in zsh or Bash in macOS or Linux into Emacs so that the PATH in the shell and the PATH when calling commands from Emacs are the same. #+BEGIN_SRC emacs-lisp (use-package exec-path-from-shell :config (exec-path-from-shell-initialize)) #+END_SRC ** ace-window provides an ace-jump experience for switching windows #+BEGIN_SRC emacs-lisp (use-package ace-window) #+END_SRC ** Install a mode for drawing indentation guides This mode adds subtle coloration to indentation whitespace for whitespace-delimited languages like YAML where sometimes it can be difficult to see the nesting level of a given headline in deeply-nested configuration. #+begin_src emacs-lisp (use-package highlight-indent-guides) #+end_src ** Quick buffer switcher #+begin_quote PC style quick buffer switcher for Emacs This switches Emacs buffers according to most-recently-used/least-recently-used order using C-tab and C-S-tab keys. It is similar to window or tab switchers that are available in PC desktop environments or applications. #+end_quote Bound by default to ~C-~ and ~C-S-~, I have decided that these are sane defaults. Just install this and turn it on. #+begin_src emacs-lisp (use-package pc-bufsw) (pc-bufsw) #+end_src ** Writeable grep mode with ack Writable grep mode allows you to edit the results from running grep on a project and easily save changes back to all of the original files #+BEGIN_SRC emacs-lisp (use-package ack) (use-package ag) (use-package wgrep-ack) #+END_SRC ** Better help buffers #+begin_src emacs-lisp (use-package helpful) (global-set-key (kbd "C-h f") #'helpful-callable) (global-set-key (kbd "C-h v") #'helpful-variable) (global-set-key (kbd "C-h k") #'helpful-key) #+end_src ** Quickly jump around buffers #+begin_src emacs-lisp (use-package ace-jump-mode) #+end_src ** Dumb jump Dumb jump provides an interface to grep that does a pretty good job of finding definitions when a smarter backend like LSP is not available. This registers it as a backend for XREF. #+begin_src emacs-lisp (use-package dumb-jump) (add-hook 'xref-backend-functions #'dumb-jump-xref-activate) (setq xref-show-definitions-function #'xref-show-definitions-completing-read) #+end_src ** Kubernetes Mode Provides an interactive Kubernetes Mode inspired by ~magit~. Since ~magit~ is one of my favorite tools, I have to try out the Kubernetes mode as well. #+begin_src emacs-lisp (use-package kubernetes :ensure t :commands (kubernetes-overview)) ;; add this config if I experience issues with Emacs locking up ;;:config ;;(setq kubernetes-poll-frequency 3600 ;; kubernetes-redraw-frequency 3600)) #+end_src I need the ~evil~ compatiblity mode, too, because I run ~evil~. #+begin_src emacs-lisp (use-package kubernetes-evil :after kubernetes) #+end_src ** multiple cursors #+begin_src emacs-lisp (use-package evil-mc) #+end_src ** elfeed #+begin_src emacs-lisp (use-package elfeed) #+end_src * Font The FiraCode font is a programming-focused font with ligatures that looks nice and has a open license so I'm standardizing my editor configuration on that font ** FiraCode Font Installation Script :properties: :header-args: :tangle ~/.emacs.d/install-firacode-font.bash :shebang #!/usr/bin/env bash :end: Installing fonts is always a pain so I'm going to use a variation of the installation script that the FireCode devs provide under their manual installation guide. This should be Linux-distribution agnostic, even though the font can be installed as a system package with on all of my systems on 2022-02-19 Sat with just : sudo apt install fonts-firacode because I don't intend to use Ubuntu as my only system forever. I just happen to be on Ubuntu on 2022-02-19 Sat. But first, I want to be able to run this script every time Emacs starts, but only have the script actually do anything if the font is not already installed. This guard will check to see if there's any font with 'fira' in it (case insensitive) and if so, just exits the script. This will happen on most executions. #+begin_src bash set -eo pipefail [[ $(fc-list | grep -i fira) != "" ]] && exit 0 #+end_src Now here's the standard installation script #+begin_src bash fonts_dir="${HOME}/.local/share/fonts" if [ ! -d "${fonts_dir}" ]; then mkdir -p "${fonts_dir}" fi version=5.2 zip=Fira_Code_v${version}.zip curl --fail --location --show-error https://github.com/tonsky/FiraCode/releases/download/${version}/${zip} --output ${zip} unzip -o -q -d ${fonts_dir} ${zip} rm ${zip} # for now we need the Symbols font, too zip=FiraCode-Regular-Symbol.zip curl --fail --location --show-error https://github.com/tonsky/FiraCode/files/412440/${zip} --output ${zip} unzip -o -q -d ${fonts_dir} ${zip} rm ${zip} fc-cache -f #+end_src This installation script was sourced from [[https://github.com/tonsky/FiraCode/wiki/Linux-instructions#installing-with-a-package-manager]] ** Enable FiraCode Font Calling the script from above will install the font #+begin_src emacs-lisp (shell-command "chmod +x ~/.emacs.d/install-firacode-font.bash") (shell-command "~/.emacs.d/install-firacode-font.bash") #+end_src Enable it #+BEGIN_SRC emacs-lisp (add-to-list 'default-frame-alist '(font . "Fira Code-10")) (set-frame-font "Fira Code-10" nil t) #+end_src ** Configure FiraCode special features FiraCode offers ligatures for programming symbols, which is cool. #+begin_src emacs-lisp (use-package ligature :load-path "./vendor/" :config ;; Enable the "www" ligature in every possible major mode (ligature-set-ligatures 't '("www")) ;; Enable traditional ligature support in eww-mode, if the ;; `variable-pitch' face supports it (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi")) ;; ;; Enable ligatures in programming modes (ligature-set-ligatures 'prog-mode '("www" "**" "***" "**/" "*>" "*/" "\\\\" "\\\\\\" "{-" ":::" ":=" "!!" "!=" "!==" "-}" "----" "-->" "->" "->>" "-<" "-<<" "-~" "#{" "#[" "##" "###" "####" "#(" "#?" "#_" "#_(" ".-" ".=" ".." "..<" "..." "?=" "??" ";;" "/*" "/**" "/=" "/==" "/>" "//" "///" "&&" "||" "||=" "|=" "|>" "^=" "$>" "++" "+++" "+>" "=:=" "==" "===" "==>" "=>" "=>>" "<=" "=<<" "=/=" ">-" ">=" ">=>" ">>" ">>-" ">>=" ">>>" "<*" "<*>" "<|" "<|>" "<$" "<$>" "