#+TITLE: Emacs Configuration #+AUTHOR: Camden Dixie O'Brien #+ATTR_LATEX: :float t * Package Management ** MELPA Let's be real here, all the good stuff's on MELPA. #+begin_src emacs-lisp (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/")) #+end_src ** Initialization We now need to run [[help:package-initialize][package-initialize]] to load and activate packages. The documentation advises doing this early in configuration. #+begin_src emacs-lisp (package-initialize) #+end_src We also need to fetch the package archives from ELPA and MELPA, unless they have already been fetched: #+begin_src emacs-lisp (unless package-archive-contents (package-refresh-contents)) #+end_src ** =use-package= The rest of this config grabs packages via =use-package=, so that needs to be installed: #+begin_src emacs-lisp (when (not (package-installed-p 'use-package)) (package-install 'use-package)) #+end_src The wanted behaviour for =use-package= here is to ensure all used packages are present. #+begin_src emacs-lisp (require 'use-package-ensure) (setq use-package-always-ensure t) #+end_src * Emacs Server Start an Emacs server if one is not running already: #+begin_src emacs-lisp (require 'server) (unless (server-running-p) (server-start)) #+end_src With this, files opened with =emacsclient= in a terminal will open in Emacs. Most of the time I use Emacs itself for my file browsing and terminal needs but =emacsclient= is still handy from time-to-time. * 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 *** Asyncronous Execution =ob-async= makes source blocks with the ~:async~ keyword execute asyncronously, super handy for long-running snippets etc. #+begin_src emacs-lisp (use-package ob-async) #+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 ** Workflow States I like to have =DOING= and =DEFERRED= as workflow states as well as the standard =TODO= and =DONE=, which can be added by setting [[help:org-todo-keywords][org-todo-keywords]]: #+begin_src emacs-lisp (setq org-todo-keywords '((sequence "TODO" "DOING" "|" "DONE" "DEFERRED"))) #+end_src The ="|"= separates /needs further action/ states (before it) from /no further action needed/ states (after it). * 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 *** Language Server Protocol LSP seems to be the way forward in terms of IDE-like features in Emacs; grab =lsp-mode= and enable =lsp-deferred=: #+begin_src emacs-lisp (use-package lsp-mode :init (setq lsp-keymap-prefix "C-c l") :commands (lsp lsp-deferred)) #+end_src =lsp-deferred= means that the LSP server will only be started once a buffer is actually opened, which makes more sense to me. Also going to give =lsp-ui= a shot, which displays a bunch of information from the language server in the buffer. It looks like it could be a bit much but we'll see. #+begin_src emacs-lisp (use-package lsp-ui :commands lsp-ui-mode) #+end_src Finally, to enable Ido integration: #+begin_src emacs-lisp (require 'lsp-ido) #+end_src ** C For C there is =clangd= implementing LSP. Assuming that's installed and on the =PATH=, we can just hook =lsp-mode= into the default mode and there will be much rejoicing. #+begin_src emacs-lisp (add-hook 'c-mode-hook #'lsp-deferred) #+end_src ** C++ Essentially the same story as for C #+begin_src emacs-lisp (add-hook 'c++-mode-hook #'lsp-deferred) #+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). #+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 This assumes that SBCL and Quicklisp, along with Quicklisp's SLIME helper, are installed. Once SBCL is installed, Quicklisp and its SLIME helper can be installed by grabbing [[https://beta.quicklisp.org/quicklisp.lisp][the installer]] and loading it with SBCL. #+begin_src shell :tangle no curl -O https://beta.quicklisp.org/quicklisp.lisp sbcl --load quicklisp.lisp #+end_src That will open a REPL with the =quicklisp-quickstart= system loaded. At that REPL, run: #+begin_src common-lisp :tangle no (quicklisp-quickstart:install) (ql:add-to-init-file) (ql:quickload "quicklisp-slime-helper") #+end_src **** Source blocks To enable execution of CL source blocks in Org mode, add 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 I commonly use Chez, Guile and Racket so we want the packages for those: #+begin_src emacs-lisp (use-package geiser-chez) (use-package geiser-guile) (use-package geiser-racket) #+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 ** Java [[https://github.com/jdee-emacs/jdee][JDEE]] provides a whole bunch of tooling for Java. It requires the [[https://github.com/jdee-emacs/jdee-server][JDEE server]] to work properly, which has to be built seperately (this requires JDK and Maven): #+begin_src shell :tangle no cd ~/src git clone https://github.com/jdee-emacs/jdee-server.git cd jdee-server mvn -Dmaven.test.skip=true package #+end_src With that built, we can install JDEE, pointing at the built server: #+begin_src emacs-lisp (use-package jdee :config (setq jdee-server-dir "~/src/jdee-server/target")) #+end_src ** Dockerfiles Grab =dockerfile-mode= for syntax highlighting etc in Dockerfiles: #+begin_src emacs-lisp (use-package dockerfile-mode) #+end_src ** Zig =zig-mode= provides basic language integration for Zig: #+begin_src emacs-lisp (use-package zig-mode) #+end_src There's a language server implementation for Zig so we'll be using that via =lsp-mode= alongside =zig-mode=. #+begin_src emacs-lisp (add-hook 'zig-mode-hook #'lsp-deferred) #+end_src * Tool Integrations ** Docker I use docker quite a lot, unfortunately, so it's nice to be able to spawn containers etc from Emacs. The =docker= package provides a few nice bits and bobs. #+begin_src emacs-lisp (use-package docker :bind ("C-c d" . docker)) #+end_src ** Build systems *** CMake I hate it, but it's everywhere. =cmake-mode= provides basic syntax highlighting etc. #+begin_src emacs-lisp (use-package cmake-mode) #+end_src *** Meson Use =meson-mode= for syntax highlighting etc in meson.build files. #+begin_src emacs-lisp (use-package meson-mode) #+end_src * Backup and Autosave ** Keep $PWD Tidy Emacs' default behaviour of dumping temporary files in the current directory is quite a pain, so we want to get it to instead stick them in a dedicated directory somewhere far away. We can do that for auto-save files by setting the variable [[help:auto-save-file-name-transforms][auto-save-file-name-transforms]]: #+begin_src emacs-lisp (make-directory "~/.emacs-tmp/auto-save" t) (setq auto-save-file-name-transforms '((".*" "~/.emacs-tmp/auto-save" t))) #+end_src And for backup files there's [[help:backup-directory-alist][backup-directory-alist]]. #+begin_src emacs-lisp (make-directory "~/.emacs-tmp/backup" t) (setq backup-directory-alist '(("." . "~/.emacs-tmp/backup"))) #+end_src