#+TITLE: Emacs Configuration #+AUTHOR: Camden Dixie O'Brien #+ATTR_LATEX: :float t Shout out to Harry R. Schwartz; A whole bunch of this config (including the idea of embeddeding the lot in an Org document) is yanked from [[https://github.com/hrs/dotfiles][his dotfiles repo]]. The rest of this config grabs packages via =use-package=, so that needs to be set up to install them if they aren't already. #+begin_src emacs-lisp (require 'use-package-ensure) (setq use-package-always-ensure t) #+end_src * UI The start-up message gets pretty annoying, so disable that. #+begin_src emacs-lisp (setq inhibit-startup-screen t) #+end_src I like a little more line spacing than default. #+begin_src emacs-lisp (setq-default line-spacing 0.2) #+end_src Also, the menu-, tool- and scroll-bar are ugly, take up space and I don't use them. #+begin_src emacs-lisp (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) #+end_src It's nice to have an inverse of =C-x o= for switching between windows. =other-window=, the function that =C-x o= is bound to, takes an argument =COUNT= that determines how many windows it skips forwards so we can simply pass -1 to =other-window= in a lambda and bind to that: #+begin_src emacs-lisp (global-set-key (kbd "C-x O") (lambda () (interactive) (other-window -1))) #+end_src ** Colour Scheme Currently using =spacemacs-theme='s light variant, but I prefer a pure white background to the off-white it has by default. #+begin_src emacs-lisp (use-package spacemacs-theme :defer t) (setq spacemacs-theme-custom-colors '((bg1 . "#ffffff") (comment-bg . "#ffffff"))) (load-theme 'spacemacs-light t) #+end_src * Autocompletion Enable =company-mode= globally, and hook it into =completion-at-point-functions=. #+begin_src emacs-lisp (use-package company :config (add-hook 'after-init-hook 'global-company-mode) (add-to-list 'company-backends 'company-capf)) #+end_src And enable =ido-mode= everywhere, with flexible matching. #+begin_src emacs-lisp (use-package ido :config (setq ido-enable-flex-matching t) (add-hook 'after-init-hook (lambda () (ido-everywhere) (ido-mode t)))) #+end_src * Org I use a couple non-standard bits and pieces, but not a whole bunch. I really like the =") 'org-insert-heading-after-current))) #+end_src Org is nice for scratch space #+begin_src emacs-lisp (setq initial-major-mode 'org-mode) (setq initial-scratch-message "") #+end_src ** Source Blocks Pressing tab inside a source block should indent appropriately for its language. #+begin_src emacs-lisp (setq org-src-tab-acts-natively t) #+end_src =babel= lets us evaluate Org documents containing source blocks! I've left the enabling of this for most languages to the section for that language, but I'll add Emacs Lisp and shell here. #+begin_src emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((emacs-lisp . t) (shell . t))) #+end_src By default trying to execute a source block prompts you, which is super annoying since I'm realistically not going to try to run any code from Org documents I haven't written, so that needs disabling. You can do that by setting [[help:org-confirm-babel-evaluate][org-confirm-babel-evaluate]] to =nil=. #+begin_src emacs-lisp (setq org-confirm-babel-evaluate nil) #+end_src Another annoying thing that happens by default is the clobbering of the window layout when you open a source block. You can change that by setting [[help:org-src-window-setup][org-src-window-setup]]. #+begin_src emacs-lisp (setq org-src-window-setup 'split-window-below) #+end_src ** Exporting I very rarely want a table of contents, as most of my org documents are pretty short. #+begin_src emacs-lisp (setq org-export-with-toc nil) #+end_src *** HTML =htmlize= is needed for decent HTML exporting, but there is no need for all that stuff at the bottom. #+begin_src emacs-lisp (use-package htmlize) (setq org-html-postamble nil) #+end_src *** LaTeX Use =minted= (LaTeX package) to do syntax highlighting in code blocks: #+begin_src emacs-lisp (add-to-list 'org-latex-packages-alist '("" "minted")) (setq org-latex-listings 'minted) #+end_src =minted= actually calls =pygments= through the shell, which =pdflatex= doesn't like; you have to tell it not to worry, and that everything is going to be OK. #+begin_src emacs-lisp (setq org-latex-pdf-process '("xelatex -shell-escape -interaction nonstopmode -output-directory %o %f" "xelatex -shell-escape -interaction nonstopmode -output-directory %o %f" "xelatex -shell-escape -interaction nonstopmode -output-directory %o %f")) #+end_src ** Roam A Zettelkasten in org mode? Yes please. It does need =sqlite3= installed outside of Emacs land. #+begin_src emacs-lisp (use-package org-roam) #+end_src As stated in [[info:org-roam#Getting Started][the manual]], org-roam needs to know where notes are stored. I'm going to go with =~/org/zet=, as its nice having all org documents under =~/org=, but also we need to distinguish zettels from other org stuff. #+begin_src emacs-lisp (make-directory "~/org/zet" t) (setq org-roam-directory "~/org/zet") #+end_src And, also as recommended, we'll start [[help:org-roam-mode][org-roam-mode]] after init: #+begin_src emacs-lisp (add-hook 'after-init-hook 'org-roam-mode) #+end_src Hook it into Ido. #+begin_src emacs-lisp (setq org-roam-completion-system 'ido) #+end_src ** Default Applications It's all fun and games until =C-c C-e h o= opens the source code. #+begin_src emacs-lisp (setq org-file-apps '(("html" . "firefox %s") (auto-mode . emacs))) #+end_src * Version Control ** Git =magit= is truly a wonderful creation! Only deviations from defaults here are a keybinding for =magit-status= and a maximum length for the summary line of commit messages (after which the excess is highlighted). #+begin_src emacs-lisp (use-package magit :bind ("C-x g" . magit-status) :config (setq git-commit-summary-max-length 72)) #+end_src * Language Integrations ** Generic Generally, 8-character-wide tabs are not my thing. #+begin_src emacs-lisp (setq-default tab-width 4) (setq-default basic-offset 4) #+end_src And generally indenting with spaces is more common, so make that the default: #+begin_src emacs-lisp (setq-default indent-tabs-mode nil) #+end_src ** C For C, I like to indent with tabs and align with spaces: this behaviour is provided by =smart-tabs-mode=. #+begin_src emacs-lisp (use-package smart-tabs-mode) (smart-tabs-insinuate 'c) #+end_src I'll generally format my code in BSD style but I also use =clang-format= a lot, so I have a keybinding to run that. #+begin_src emacs-lisp (setq c-default-style "bsd") (use-package clang-format) (add-hook 'c-mode-hook (lambda () (define-key c-mode-map (kbd "C-M-f") 'clang-format-buffer))) #+end_src Meson is my build system of choice for C, but I also use CMake a lot. #+begin_src emacs-lisp (use-package meson-mode) (use-package cmake-mode) #+end_src *** Code Navigation Using GNU Global for now, so hook =ggtags-mode= into =c-mode=: #+begin_src emacs-lisp (use-package ggtags :config (add-hook 'c-mode-common-hook (lambda () (ggtags-mode 1)))) #+end_src And, of course, add some keybindings #+begin_src emacs-lisp (define-key ggtags-mode-map (kbd "C-c g r") 'ggtags-find-reference) (define-key ggtags-mode-map (kbd "C-c g d") 'ggtags-find-definition) (define-key ggtags-mode-map (kbd "C-c g u") 'ggtags-update-tags) #+end_src ** Haskell My workflow with Haskell is very REPL-based, so I always want =interactive-haskell-mode= on. #+begin_src emacs-lisp (use-package haskell-mode) (require 'haskell-interactive-mode) (add-hook 'haskell-mode-hook 'interactive-haskell-mode) #+end_src And, of course, that REPL needs to be taking advantage of parallelism! #+begin_src emacs-lisp (require 'haskell-process) (set-variable 'haskell-process-args-ghci '("-threaded" "+RTS" "-N8" "-RTS")) #+end_src ** Idris The only thing to change from the defaults here is to add a more convenient way to case-split. #+begin_src emacs-lisp (use-package idris-mode) (add-hook 'idris-mode-hook (lambda () (define-key idris-mode-map (kbd "C-c SPC") 'idris-case-split))) #+end_src ** Rust I never really use Rust without Cargo, so always turn on the minor mode for Cargo in Rust buffers. #+begin_src emacs-lisp (use-package rust-mode) (use-package cargo) (add-hook 'rust-mode-hook 'cargo-minor-mode) #+end_src ** Lisps *** Common Lisp Use SLIME and Quicklisp for Common Lisp (SBCL), with a convenient binding for =slime-selector= #+begin_src emacs-lisp (use-package slime) (setq inferior-lisp-program "sbcl") (global-set-key (kbd "C-c s") 'slime-selector) (load (expand-file-name "~/quicklisp/slime-helper.el")) #+end_src And we also want to enable execution of CL source blocks in Org mode, which we do by adding an item to [[help:org-babel-load-languages][org-babel-load-languages]]. #+begin_src emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((lisp . t))) #+end_src *** Paredit =paredit= is generally very useful for balancing parenthesis so we want that turned on for all the lisps. Additionally, it's nice to have an entire expression highlighted when the cursor is on one of its enclosing parens. #+begin_src emacs-lisp (use-package paredit) (setq lispy-mode-hooks '(emacs-lisp-mode-hook lisp-mode-hook racket-mode-hook scheme-mode-hook slime-repl-mode-hook)) (dolist (hook lispy-mode-hooks) (add-hook hook (lambda () (setq show-paren-style 'expression) (paredit-mode)))) #+end_src *** Scheme and Racket [[https://www.nongnu.org/geiser/][Geiser]] is a pretty complete collection of Scheme things for Emacs. Only change from the defaults is to open the REPL in the current window instead of creating a new one. #+begin_src emacs-lisp (use-package geiser :config (setq geiser-repl-use-other-window nil)) #+end_src ** YAML I don't really like YAML if I'm honest, but it's used a lot so... #+begin_src emacs-lisp (use-package yaml-mode) #+end_src ** Javascript The first bit of this setup (=js2-mode=, =js2-refactor= and =xref-js2=) is essentially copied from [[https://emacs.cafe/emacs/javascript/setup/2017/04/23/emacs-setup-javascript.html][this Emacs cafe post]]. First of all we want to grab =js2-mode= and enable it for javascript buffers. It extends the default =js-mode= and builds an AST which can be used by other packages. #+begin_src emacs-lisp (use-package js2-mode) (add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode)) #+end_src *** Refactoring =js2-refactor= provides refactoring tools based of said AST, so enable that and its keybindings: #+begin_src emacs-lisp (use-package js2-refactor) (add-hook 'js2-mode-hook #'js2-refactor-mode) (js2r-add-keybindings-with-prefix "C-c C-r") #+end_src It provides a kill function with nice semantics for Javascript -- we definitely want that instead of the generic kill. #+begin_src emacs-lisp (define-key js2-mode-map (kbd "C-k") #'js2r-kill) #+end_src *** Find references / jump to definition Then we get to =xref-js2=, which adds stuff for jumping to references and definitions (uses the =ag= tool, so that must be installed in the environment): #+begin_src emacs-lisp (use-package xref-js2) #+end_src =js-mode= binds =M-.=, which conflicts with =xref-js2= so we need to unbind that: #+begin_src emacs-lisp (define-key js-mode-map (kbd "M-.") nil) #+end_src And hook it up to =js2-mode=: #+begin_src emacs-lisp (add-hook 'js2-mode-hook (lambda () (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t))) #+end_src *** Using local tools from NPM To use tools locally by NPM, there is =add-node-modules-path=: #+begin_src emacs-lisp (use-package add-node-modules-path) (eval-after-load 'js2-mode '(add-hook 'js2-mode-hook #'add-node-modules-path)) #+end_src *** Autoformatting [[https://prettier.io/][Prettier]] seems low-effort to set up :D #+begin_src emacs-lisp (use-package prettier-js) (add-hook 'js2-mode-hook 'prettier-js-mode) #+end_src ** Prolog =prolog-mode= comes with Emacs, but .pl files are assumed to be Perl (which I never use, it scares me), so we need to change that. #+begin_src emacs-lisp (add-to-list 'auto-mode-alist '("\\.pl\\'" . prolog-mode)) #+end_src * Desktop ** EXWM One must fulfil the meme of doing everything with Emacs... still got a lot of tweaking to do here before I'm happy. #+begin_src emacs-lisp (use-package exwm :config (require 'exwm-config) (exwm-config-default)) #+end_src *** Multi-monitor Multi-monitor support is provided in =exwm-randr=: #+begin_src emacs-lisp (require 'exwm-randr) (exwm-randr-enable) #+end_src When I have my laptop connected to a monitor I want the built-in display to turn off, but turn back on when it's disconnected. Turns out this is a total pain. To start with we need a function to tell whether a monitor's attached. =exwm-randr= provides [[help:exwm-randr--get-monitors][exwm-randr--get-monitors]], but its result is not -- as I'd expect -- a list of monitors, but instead a rather complicated mess that is (as far as I can tell) undocumented. Rather than trying to figure out what was going on there, I opted for the search-in-shell-command-output route #+begin_src emacs-lisp (defun hdmi-connected-p () (string-match-p "HDMI-2 connected" (shell-command-to-string "xrandr"))) #+end_src With that defined, an [[help:exwm-randr-screen-change-hook][exwm-randr-screen-change-hook]] can then be added to turn the built-in display on and off appropriately. #+begin_src emacs-lisp (add-hook 'exwm-randr-screen-change-hook (lambda () (let ((xrandr-command (if (hdmi-connected-p) "xrandr --output eDP-1 --off --output HDMI-2 --auto" "xrandr --output eDP-1 --auto"))) (start-process-shell-command "xrandr" nil xrandr-command)))) #+end_src ** Mode Line *** Clock The time is a useful thing to know... and 12-hour clock is for losers. #+begin_src emacs-lisp (setq display-time-24hr-format t) (display-time-mode 1) #+end_src *** Battery Also useful to know, but only on a laptop... once I'm using this configuration on Mandarax as well I'll probably have to conditionally disable it. #+begin_src emacs-lisp (display-battery-mode 1) #+end_src * Passwords This was a little more work than I expected... =password-store= provides a nice interface to =pass=, but annoyingly appears to depend on =f= without declaring so. #+begin_src emacs-lisp (use-package password-store) (use-package f) #+end_src However, in order for it to actually work, EasyPG had to be configured to use loopback for pinentry. #+begin_src emacs-lisp (setq epa-pinentry-mode 'loopback) #+end_src =gpg-agent= also had to be configured to allow loopback for pinentry -- this was done by adding =allow-loopback-pinentry= to [[file:~/.gnupg/gpg-agent.conf::allow-loopback-pinentry][gpg-agent.conf]]. With that all working, all that remains is to add a convenient keybinding for getting a password, which is done by the function =password-store-copy=. =C-c C-p= does conflict with /some/ modal bindings, but I think that's fine as most of the time I'll need a password it'll be in some X window anyway... and besides, =M-x pass-co RET= isn't bad for when it does happen to conflict. #+begin_src emacs-lisp (global-set-key (kbd "C-c C-p") 'password-store-copy) #+end_src ** TODO Pinentry prompt bugginess When =pass= tries to bring up the the pinentry prompt it freezes everything up and I have to =C-g= to get the prompt to appear. Definitely not ideal but it does work so imma fix that at some other time. * Mail Currently using =mu4e= for mail. Not sure whether this is my 'final' set up, I might give =notmuch= a try at some point. =mu4e= is a bit annoying as it's bundled along with =mu= rather than being loaded from ELPA or MELPA, so it can't be loaded with =use-package=. Indeed, how to load it depends on how =mu= was packaged. On NixOS at least, =mu4e= gets put in a place where Emacs is able to find it, so it just needs to be =require='d. #+begin_src emacs-lisp (require 'mu4e) #+end_src I'm /sure/ this will break at some point; when it does, probably makes sense to do something like: #+begin_src emacs-lisp :tangle no (let ((uname-output (shell-command-to-string "uname -a"))) (cond ((string-match-p "NixOS" uname-output) nil) ...)) (require 'mu4e) #+end_src To get the correct address by default: #+begin_src emacs-lisp (setq user-mail-address "cdo@wip.sh") #+end_src And to avoid being tickled: #+begin_src emacs-lisp (setq mail-host-address "hactar") #+end_src ** Automatic updating For updating through =mu4e= to actually work, [[help:mu4e-get-mail-command][mu4e-get-mail-command]] needs to be set to =offlineimap=. New mail can be then fetched with [[help:mu4e-update-mail-and-index][mu4e-update-mail-and-index]]. #+begin_src emacs-lisp (setq mu4e-get-mail-command "offlineimap") #+end_src Sometimes (like when waiting for on a particular email) it might be useful to have the update run periodically. This can be done with [[help:run-with-timer][run-with-timer]]. By only actually updating if ~fetch-mail~ non-~nil~, we give ourselves a way to turn it off. #+begin_src emacs-lisp (setq mu4e-get-mail-command "offlineimap") (defvar fetch-mail nil "Controls whether mail is periodically fetched.") (run-with-timer 0 120 (lambda () (when fetch-mail (mu4e-update-mail-and-index t)))) #+end_src And then we need something to run through =M-x= to do that: #+begin_src emacs-lisp (defun toggle-mail-fetching () "Toggle periodic mail fetching." (interactive) (setq fetch-mail (not fetch-mail)) (message "Mail fetching %s" (if fetch-mail "enabled" "disabled"))) #+end_src ** Mode-line alert =mu4e-alert= provides a convenient little icon that shows up whenever =mu4e= has unread mail. #+begin_src emacs-lisp (use-package mu4e-alert :config (add-hook 'after-init-hook #'mu4e-alert-enable-mode-line-display)) #+end_src ** Sending with =sendmail= I have =msmtp= set up so use that to send mail. #+begin_src emacs-lisp (setq send-mail-function 'sendmail-send-it) #+end_src * Multimedia EMMS seems like a decent multimedia system for Emacs and why not enable all the stable features to start with. Also, =mplayer= makes a good fallback player. #+begin_src emacs-lisp (use-package emms :config (emms-all) (add-to-list 'emms-player-list 'emms-player-mplayer)) #+end_src ** Browser To actually get stuff to show up in the browser it seems you have to define a filter that includes everything, because fuck sane defaults. #+begin_src emacs-lisp (emms-browser-make-filter "all" 'ignore) #+end_src ** MPD To get EMMS to talk to MPD, we need to tell it how to connect to it, and to use it for getting track info and playing tracks: #+begin_src emacs-lisp (add-to-list 'emms-info-functions 'emms-info-mpd) (add-to-list 'emms-player-list 'emms-player-mpd) (setq emms-player-mpd-server-name "localhost" emms-player-mpd-server-port "6600" emms-player-mpd-music-directory "~/mus") #+end_src With those options in place, connecting should work fine (assuming the underlying system has MPD running). #+begin_src emacs-lisp (emms-player-mpd-connect) (emms-cache-set-from-mpd-all) #+end_src ** Podcasts Elfeed supports media enclosures, so it's ideal for podcasts out-of-the-box. #+begin_src emacs-lisp (use-package elfeed :config (setq elfeed-feeds '("https://www.patreon.com/rss/seanmcarroll?auth=xZISWBuCvZ1rKXy547HnRXQVyBIscY1P" "https://www.patreon.com/rss/plasticpills?auth=S0ExMga6Cco6F4DN30W6Sg9kUciLdjXR" "https://www.nasa.gov/rss/dyn/Houston-We-Have-a-Podcast.rss" "https://www.nasa.gov/rss/dyn/APPEL-Giant-Leaps.rss" "https://feeds.transistor.fm/on-the-metal-0294649e-ec23-4eab-975a-9eb13fd94e06" "https://www.pine64.org/feed/mp3/"))) #+end_src * Gopher [[https://thelambdalab.xyz/elpher/][Elpher]] is a gopher and gemini browser for Emacs that looks rather nice. #+begin_src emacs-lisp (use-package elpher) #+end_src