summaryrefslogtreecommitdiff
path: root/init.el
diff options
context:
space:
mode:
authorJakob L. Kreuze2020-05-12 21:26:50 -0400
committerJakob L. Kreuze2020-05-12 21:26:50 -0400
commit4e8f3f73316296248a5c556c2a6e1a3ae1400444 (patch)
tree49c260b41f50e9484ae752dc45194c721b489980 /init.el
parenteee66e45c650c665171a7791a01840d4decbd564 (diff)
Completely scrap my literate configuration.
Diffstat (limited to 'init.el')
-rw-r--r--init.el2109
1 files changed, 2079 insertions, 30 deletions
diff --git a/init.el b/init.el
index c131d6b..59eee42 100644
--- a/init.el
+++ b/init.el
@@ -1,34 +1,2083 @@
-;; This file is but bootstrapping. My real configuration file is
-;; located at "jakob.org".
+;;; init.el -*- lexical-binding: t; -*-
+;;;
+;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org>
+;;; URL: https://git.sr.ht/~jakob/.emacs.d/
+;;;
+;;; What follows is my personal configuration for GNU Emacs. Alas, what once was
+;;; a literate configuration has turned into a modularized set of Elisp files. I
+;;; have a number of reasons for this.
+;;;
+;;; - There was a considerable amount of friction in changing my configuration.
+;;; There are a few things that could remedy this, namely the suggestion of
+;;; using poly-org as suggested in [1], but my biggest grievance is putting
+;;; documentation between snippets of code. There were a couple of answers to
+;;; the Emacs StackExchange question I asked on this [2], but the only
+;;; suggestion that remotely close to what I was looking for was to use
+;;; <<noweb>> syntax. Too much overhead for my tastes; I have to make a new
+;;; named block every time I want to document part of a package configuration?
+;;; Color me unexcited. Configuration reloading was also a massive pain, as
+;;; some s-exps were split across blocks and I couldn't evaluate them with
+;;; C-M-x.
+;;;
+;;; - My current method for tangling doesn't support byte compilation. The byte
+;;; compiler will emit warnings for problematic Elisp. I can lint an Elisp
+;;; file. With the way I was tangling, any errors in my code would be a
+;;; pleasant surprise for the next time I tried to start Emacs. Furthermore,
+;;; if I want to reap the benefits of using GCCEmacs [3], I'd better be
+;;; compiling my sources. This is another grievance that could have been
+;;; remedied with literate-elisp as suggested in [1], but I've decided it's
+;;; too much work. I like the idea of being able to byte-compile with the
+;;; native Emacs machinery better.
+;;;
+;;; - It's difficult to split up a literate configuration into multiple files,
+;;; which is something I'd like to do for reasons of loading efficiency and
+;;; organization. I think there's a reason that other sufficiently built-up
+;;; configurations of note, such as Doom [4] and Purcell's [5], are written in
+;;; plain ol' Elisp, leaving documentation to another file if stuctured text
+;;; is really necessary.
+;;;
+;;; And, really, links were the only the only rich text I was using in the Org
+;;; version. If I can survive writing emails without Org-style links, I can
+;;; survive writing Elisp without Org-style links. I still love Org, but for
+;;; right now, I'm not going to adopt it for anything outside of my personal
+;;; information management workflow. Two conversations that influenced this
+;;; decision and are worth documenting are [6] and [7].
+;;;
+;;; Also worth noting is that there is no namespace (i.e. =jakob/= or =my-=) for
+;;; functions defined in this file. I'm happy to use a consistent namespacing
+;;; for packages I plan to publish on Melpa, but I don't see the point in a
+;;; configuration file.
+;;;
+;;; [1]: https://www.reddit.com/r/emacs/comments/gfiadj/thoughts_on_having_a_literate_config/fpxqmf8/
+;;; [2]: https://emacs.stackexchange.com/questions/57608/split-code-across-multiple-src-blocks
+;;; [3]: http://akrl.sdf.org/gccemacs.html
+;;; [4]: https://github.com/hlissner/doom-emacs
+;;; [5]: https://github.com/purcell/emacs.d
+;;; [6]: https://www.reddit.com/r/emacs/comments/gfiadj/thoughts_on_having_a_literate_config/
+;;; [7]: https://www.colinmclear.net/posts/emacs-configuration/
-(defun eval-document (path)
- "Tangles and evaluates all Emacs lisp code in the org file
-located at PATH."
- (let* ((body-list ())
- (src-block-regexp (concat
- ;; (1) indentation (2) lang
- "^\\([ \t]*\\)#\\+begin_src[ \t]+\\([^ \f\t\n\r\v]+\\)[ \t]*"
- ;; (3) switches
- "\\([^\":\n]*\"[^\"\n*]*\"[^\":\n]*\\|[^\":\n]*\\)"
- ;; (4) header arguments
- "\\([^\n]*\\)\n"
- ;; (5) body
- "\\([^\000]*?\n\\)??[ \t]*#\\+end_src")))
+
+
+;; straight.el is my package manager of choice. Here's the necessary bootstrap
+;; code for that.
+
+(defvar bootstrap-version)
+(let ((bootstrap-file
+ (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
+ (bootstrap-version 5))
+ (unless (file-exists-p bootstrap-file)
+ (with-current-buffer
+ (url-retrieve-synchronously
+ "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
+ 'silent 'inhibit-cookies)
+ (goto-char (point-max))
+ (eval-print-last-sexp)))
+ (load bootstrap-file nil 'nomessage))
+
+;; As for ensuring that the packages listed in this configuration are installed,
+;; I prefer use-package. `straight-use-package-by-default' ensures that packages
+;; are registered and built without reqiring an explicit `:straight t'.
+
+(straight-use-package 'use-package)
+(setq straight-use-package-by-default t)
+
+;; `bind-key' is necessary for `:bind' in `use-package' declarations, and I
+;; actually use it quite a bit in this configuration.
+
+(use-package bind-key)
+(require 'bind-key)
+
+;; A handful of the Elisp snippets in this file depend on third-party libraries
+;; which aren't loaded by default.
+
+;;; TODO
+
+;; Bind some keys for working with this configuration file.
+
+(defun config-goto ()
+ "Switch to a buffer visiting the config file."
+ (interactive)
+ (find-file "~/.emacs.d/init.el"))
+
+(bind-key "C-c e" #'config-goto)
+
+
+;;;
+;;; The Core of GNU Emacs
+;;;
+
+;; Having Emacs open to `*scratch*' feels as though it's inviting me to punch
+;; out some Lisp forms and evaluate them. I like to pretend that my computers
+;; are Lisp machines.
+
+;; TODO: Replace with dashboard.el?
+
+(setq inhibit-startup-message t
+ inhibit-startup-buffer-menu t
+ initial-scratch-message nil
+ initial-buffer-choice nil)
+
+;; A recurring theme in this configuration file is changing where Emacs packages
+;; drop their various state files. 'no-littering.el' is a package that handles
+;; this by creating 'var/' and 'etc/' directories in the Emacs directory, but
+;; it's implemented in a way that pollutes the global namespace with variables
+;; for packages that I don't use, and would require a significant amount of
+;; configuration on my part anyway, so I've decided to do this manually, setting
+;; relevant variables to subdirectories of XDG_CACHE_HOME and XDG_DATA_HOME.
+
+;; Save backup files to one directory instead of making a mess of the filesystem.
+
+(setq backup-directory-alist `(("." . "~/.cache/emacs")))
+
+;; `auto-save', in addition to the actual files it saves, maintains another file
+;; listing the files it's currently taking care of.
+
+(setq auto-save-list-file-prefix "~/.cache/emacs/auto-save-list/.saves-")
+
+;; Disable the creation of locking symlinks in the current directory. This is a
+;; very opinionated choice, and probably isn't a good idea for most.
+
+(setq create-lockfiles nil)
+
+;; I don't use 'custom.el' to set variables, but a few of the packages I use do.
+;; This snippet ensures that a massacre is not made of my init.el.
+
+(setq custom-file "~/.emacs.d/custom.el")
+(when (file-exists-p custom-file)
+ (load custom-file))
+
+;; I don't really care about persisting minibuffer history between sessions.
+;; Even if I disable `savehist-mode', it still writes to disk, so the least I
+;; can do is move the history file to somewhere that isn't .emacs.d.
+
+(savehist-mode -1)
+(setq savehist-file "~/.cache/emacs/history")
+
+(use-package tramp
+ :straight nil
+ :init
+ (setq tramp-backup-directory-alist backup-directory-alist)
+ (setq-default tramp-persistency-file-name "~/.cache/emacs/tramp"))
+
+(use-package nsm
+ :straight nil
+ :init
+ (setq nsm-settings-file "~/.cache/emacs/network-security.data"))
+
+;; Use a consistent confirmation dialog of "y or n".
+
+(fset 'yes-or-no-p 'y-or-n-p)
+
+;; Automatically revert buffers and dired listings when something on disk
+;; changes.
+
+(use-package autorevert
+ :config
+ (global-auto-revert-mode 1)
+ (setq global-auto-revert-non-file-buffers t
+ auto-revert-verbose nil))
+
+;; "Command attempted to use minibuffer while in minibuffer" gets old fast.
+
+(setq enable-recursive-minibuffers t)
+
+;; Select help windows when I pop them so that I can kill them with <q>.
+
+(setq help-window-select t)
+
+;; Most *NIX tools work best when files are terminated with a newline.
+
+(setq require-final-newline t)
+
+;; Sentences should be separated by a single space. Treat two sentences as such
+;; when filling.
+
+(setq sentence-end-double-space nil)
+
+;; I use the tab key, but I generally prefer space characters to tabstops and
+;; like a four-character width for indentation.
+
+(setq-default indent-tabs-mode nil
+ tab-width 4)
+
+;; Modern conventions state that 80 characters is the standard width.
+
+(setq-default fill-column 80)
+
+;; When killing, add whatever's in the system clipboard if it isn't there
+;; already, so I can get it back with <M-y>.
+
+(setq save-interprogram-paste-before-kill t)
+
+;; Enable the default-disabled functions that I use.
+
+(put 'downcase-region 'disabled nil)
+(put 'upcase-region 'disabled nil)
+
+;; Enable useful visual queues.
+
+(column-number-mode t)
+
+;; Unbind <C-z> and <C-x C-z> so that I'm not accidentally calling =suspend-frame=.
+
+(global-unset-key (kbd "C-z"))
+(global-unset-key (kbd "C-x C-z"))
+
+;; Replace `kill-word', `backward-kill-word', and `kill-visual-line' with
+;; equivalents that do not copy the region into the kill ring.
+
+(defun delete-word (arg)
+ "Delete characters forward until encountering the end of a word.
+With argument ARG, do this that many times."
+ (interactive "p")
+ (delete-region (point) (progn (forward-word arg) (point))))
+
+(defun backward-delete-word (arg)
+ "Delete characters backward until encountering the beginning of a word.
+With argument ARG, do this that many times."
+ (interactive "p")
+ (delete-word (- arg)))
+
+(defun delete-visual-line (&optional arg)
+ "Delete the rest of the visual line.
+With prefix argument ARG, delete that many visual lines from point.
+If ARG is negative, delete visual lines backward.
+If ARG is zero, delete the text before point on the current visual
+line."
+ (interactive "P")
+ ;; Like in `kill-line', it's better to move point to the other end
+ ;; of the kill before killing.
+ (let ((opoint (point))
+ (delete-whole-line (and kill-whole-line (bolp))))
+ (if arg
+ (vertical-motion (prefix-numeric-value arg))
+ (end-of-visual-line 1)
+ (if (= (point) opoint)
+ (vertical-motion 1)
+ ;; Skip any trailing whitespace at the end of the visual line.
+ ;; We used to do this only if `show-trailing-whitespace' is
+ ;; nil, but that's wrong; the correct thing would be to check
+ ;; whether the trailing whitespace is highlighted. But, it's
+ ;; OK to just do this unconditionally.
+ (skip-chars-forward " \t")))
+ (delete-region opoint (if (and delete-whole-line (= (following-char) ?\n))
+ (1+ (point))
+ (point)))))
+
+(bind-key "M-d" #'delete-word)
+(bind-key "M-DEL" #'backward-delete-word)
+(bind-key "C-k" #'delete-visual-line)
+
+;; I use <M-v> and <M-V> for movement, mostly, so having <prior> and <next>
+;; bound to the same functions is a little redundant. What I prefer is to bind
+;; them to functions for scolling by one line at a time instead. Sometimes
+;; useful when perusing IRC logs.
+
+(define-minor-mode baby-scroll-mode
+ "Toggle `baby-scroll-mode'.
+Minor mode which remaps <next> and <prior> to less extreme
+scrolling functions."
+ :global t
+ nil "Baby Scroll" '(([next] . scroll-up-line)
+ ([prior] . scroll-down-line)))
+
+(baby-scroll-mode)
+
+;; Don't do jerky jumps when the cursor reaches the end of the window. Instead,
+;; just scroll by one line.
+
+(setq scroll-conservatively most-positive-fixnum)
+
+
+
+;;;
+;;; Appearance
+;;;
+
+;; Use soft wrapping instead of "scrolling" when overflowing the width of a frame.
+
+(global-visual-line-mode t)
+
+;; The menu bar can be accessed with <M-`> if I really need it, so I don't see
+;; the point in having it take up the space at the top. The rest of these rarely
+;; see use from me, and I'd prefer to disable them and have access to the extra
+;; screen real estate.
+
+(menu-bar-mode -1)
+(scroll-bar-mode -1)
+(tool-bar-mode -1)
+(fringe-mode -1)
+
+;; Hacking is a lot more serene when visual stimulus is kept to a minimum.
+
+(blink-cursor-mode 0)
+
+;; Enable `prettify-symbols' globally.
+
+(global-prettify-symbols-mode t)
+
+;; This is snippet was taken verbatim from Emacs StackExchange [1] to enable
+;; `prettify-symbols in Org source code blocks. Sadly, I can't think of a way to
+;; implement this as advice.
+;;
+;; [1]: https://emacs.stackexchange.com/questions/17283/is-it-possible-to-get-prettified-symbols-in-org-mode-source-blocks
+
+(defun org-src-font-lock-fontify-block (lang start end)
+ "Fontify code block.
+This function is called by emacs automatic fontification, as long
+as `org-src-fontify-natively' is non-nil."
+ (let ((lang-mode (org-src--get-lang-mode lang)))
+ (when (fboundp lang-mode)
+ (let ((string (buffer-substring-no-properties start end))
+ (modified (buffer-modified-p))
+ (org-buffer (current-buffer)) pos next)
+ (remove-text-properties start end '(face nil))
+ (with-current-buffer
+ (get-buffer-create
+ (concat " org-src-fontification:" (symbol-name lang-mode)))
+ (delete-region (point-min) (point-max))
+ (insert string " ") ;; so there's a final property change
+ (unless (eq major-mode lang-mode) (funcall lang-mode))
+ ;; Avoid `font-lock-ensure', which does not display fonts in
+ ;; source block.
+ (font-lock-fontify-buffer)
+ (setq pos (point-min))
+ (while (setq next (next-single-property-change pos 'face))
+ (put-text-property
+ (+ start (1- pos)) (1- (+ start next)) 'face
+ (get-text-property pos 'face) org-buffer)
+ (setq pos next))
+ ;; Addition: also copy 'composition info for prettified symbols
+ (setq pos (point-min))
+ (while (setq next (next-single-property-change pos 'composition))
+ (put-text-property
+ (+ start (1- pos)) (1- (+ start next)) 'composition
+ (get-text-property pos 'composition) org-buffer)
+ (setq pos next))
+ ;; End addition
+ )
+ (add-text-properties
+ start end
+ '(font-lock-fontified t fontified t font-lock-multiline t))
+ (set-buffer-modified-p modified)))))
+
+;; Although I'm usually not a fan of treating non-printable ASCII like this, the
+;; lines act as sort more of a cue for me to use <C-x [> and <C-x ]> than a
+;; plain ol' '^L'.
+
+(use-package page-break-lines
+ :demand t
+ :config
+ (global-page-break-lines-mode))
+
+
+;;;
+;;; Theme
+;;;
+
+;; This is my custom theme, the code for which is very loosely adapted from
+;; moe-theme.el [1], but most of the palette has been altered to emulate the
+;; appearance of my old setup of Emacs in a terminal emulator.
+;;
+;; [1]: https://github.com/kuanyui/moe-theme.el
+
+(deftheme chayote
+ "Inspired by the appearance of Emacs in my terminal emulator.")
+
+;; The colors have been extracted into a variable, `palette', and a macro,
+;; `with-palette', to provide access to the theme's color definitions without
+;; significantly polluting the global namespace. To help me in modifying the
+;; theme, I've defined a function similar to `list-colors-display', but for
+;; showing the colors in the palette I've defined.
+
+(defvar palette
+ '((yellow-1 "#ffd787") (yellow-2 "#ffd700") (yellow-3 "#c4a000")
+ (yellow-3-5 "#aaaa11") (yellow-4 "#875f00")
+
+ (orange-1 "#ffaf87") (orange-2 "#ff8700") (orange-3 "#ff5d17")
+ (orange-4 "#d75f00") (orange-5 "#af5f00")
+
+ (magenta-1 "#ff7bbb") (magenta-2 "#ff4ea3") (magenta-3 "#ff1f8b")
+
+ (green-1 "#87ff87") (green-2 "#a1db00") (green-3 "#5faf00")
+ (green-4 "#008700") (green-5 "#005f00")
+
+ (cyan-1 "#afd7d7") (cyan-2 "#87ffd7") (cyan-3 "#00d7af")
+ (cyan-4 "#00ac8a") (cyan-5 "#5faf87") (cyan-6 "#005f5f")
+ (cyan-7 "#236f73")
+
+ (blue-1 "#1fb6d3") (blue-2 "#1f5bff") (blue-3 "#005f87")
+ (blue-4 "#5f7ab1") (blue-5 "#0000af") (blue-6 "#00005f")
+
+ (purple-1 "#d18aff") (purple-2 "#af5fff") (purple-3 "#9a08ff")
+ (purple-4 "#6c0099")
+
+ (red-1 "#ef2929") (red-2 "#dd0000") (red-3 "#a40000")
+ (red-4 "#5f0000")
+
+ (white-1 "#f0e9dc") (white-2 "#c6c6c6") (white-3 "#b2b2b2")
+
+ (black-1 "#a8a8a8") (black-2 "#8a8a8a") (black-2-5 "#6c6c6c")
+ (black-3 "#4e4e4e") (black-4 "#3a3a3a") (black-5 "#303539")
+ (black-6 "#000000")
+
+ (LIGHT_BG "#fdfde7") (white-0 "#eeeeee")
+
+ (green-02 "#5fd700") (green-01 "#d7ff00") (green-0 "#d7ff5f")
+ (green-00 "#d7ff87")
+
+ (cyan-0 "#d7ffd7")
+
+ (blue-01 "#c3c9f8") (blue-0 "#afd7ff") (blue-00 "#d7d7ff")
+
+ (yellow-0 "#ffff87") (yellow-00 "#ffffaf")
+
+ (purple-0 "#af87ff") (purple-00 "#e6a8df")
+
+ (red-0 "#ff4b4b") (red-00 "#ffafaf")
+
+ (magenta-0 "#ffafd7") (magenta-00 "#ffd7ff")
+
+ (orange-0 "#ffaf87") (orange-00 "#ffd787") (orange-000 "#ffd7af")
+
+ (linum-dark "#87875f") (linum-light "#d7d7af"))
+ "The palette of colors in use with my 'chayote' theme.")
+
+(defmacro with-palette (&rest form)
+ "Evaluate `FORM' in a `let' containing the theme's colors."
+ `(let ((class '((class color) (min-colors 89)))
+ ,@palette)
+ ,@form))
+
+(defun display-palette ()
+ "Display names of colors in `PALETTE', and show what they look like."
+ (interactive)
+ (with-help-window "*Chayote Colors*"
+ (with-current-buffer standard-output
+ (erase-buffer)
+ (dolist (color palette)
+ (let* ((name (symbol-name (first color)))
+ (value (second color)))
+ (put-text-property 0 (length name)
+ 'font-lock-face `(:foreground ,value)
+ name)
+ (insert name)
+ (insert (propertize " " 'display '(space :align-to (- right 9))))
+ (insert " ")
+ (put-text-property 0 (length value)
+ 'font-lock-face `(:background ,value)
+ value)
+ (insert value)
+ (terpri)))
+ (set-buffer-modified-p nil)
+ (setq truncate-lines t))))
+
+(with-palette
+ (custom-theme-set-faces
+ 'chayote
+ `(default ((,class (:foreground ,white-1 :background ,black-5 :family "Terminus" :height 90))))
+ `(italic ((,class (:italic t))))
+ `(cursor ((,class (:background ,white-0))))
+ `(variable-pitch ((,class (:inherit default))))
+
+ `(link ((,class (:foreground ,blue-1 :underline t))))
+
+ ;; Highlighting faces
+ `(highlight ((,class (:background ,black-3))))
+ `(hl-line ((,class (:background ,black-4))))
+ `(highlight-symbol-face ((,class (:background ,green-5))))
+ `(region ((,class (:background ,blue-4))))
+ `(secondary-selection ((,class (:background ,blue-3 :foreground ,white-0))))
+ `(isearch ((,class (:foreground ,white-1 :background ,orange-3))))
+ `(lazy-highlight ((,class (:background ,magenta-3 :foreground ,white-1))))
+ `(trailing-whitespace ((,class (:background ,red-3))))
+ `(show-paren-match ((,class (:background ,blue-3 :foreground nil))))
+ `(header-line ((,class (:background ,blue-3 :foreground ,white-0))))
+ `(help-argument-name ((,class (:foreground ,magenta-1 :italic t))))
+ `(eldoc-highlight-function-argument ((,class (:foreground ,green-01 :bold t :underline t :background ,green-5))))
+
+ ;; Mode line & frames' faces
+ `(mode-line ((,class (:box nil :background ,white-1 :foreground ,black-5))))
+ `(mode-line-inactive ((,class (:background ,white-3 :foreground ,black-5))))
+ `(mode-line-buffer-id ((,class (:box nil :foreground ,black-5 :background nil))))
+ `(vertical-border ((,class (:foreground ,black-3 :background ,black-3))))
+
+ ;; Escape and prompt faces
+ `(minibuffer-prompt ((,class (:foreground ,blue-1))))
+ `(escape-glyph ((,class (:foreground ,yellow-3))))
+ `(error ((,class (:foreground ,red-0))))
+ `(warning ((,class (:foreground ,orange-1))))
+ `(success ((,class (:foreground ,green-1))))
+
+ ;; Font lock faces
+ `(font-lock-builtin-face ((,class (:foreground ,cyan-1))))
+ `(font-lock-comment-delimiter-face ((,class (:inherit font-lock-comment-face))))
+ `(font-lock-comment-face ((,class (:foreground ,orange-2))))
+ `(font-lock-constant-face ((,class (:foreground ,cyan-2))))
+ `(font-lock-doc-face ((,class (:inherit font-lock-string-face))))
+ `(font-lock-doc-string-face ((,class (:inherit font-lock-string-face))))
+ `(font-lock-function-name-face ((,class (:foreground ,blue-0))))
+ `(font-lock-keyword-face ((,class (:foreground ,blue-1))))
+ `(font-lock-negation-char-face ((,class (:foreground ,red-0))))
+ `(font-lock-preprocessor-face ((,class (:inherit font-lock-builtin-face))))
+ `(font-lock-regexp-grouping-backslash ((,class (:foreground ,yellow-1))))
+ `(font-lock-regexp-grouping-construct ((,class (:foreground ,purple-1))))
+ `(font-lock-string-face ((,class (:foreground ,orange-1))))
+ `(font-lock-type-face ((,class (:foreground ,green-1))))
+ `(font-lock-variable-name-face ((,class (:foreground ,yellow-1))))
+ `(font-lock-warning-face ((,class (:weight bold :foreground ,red-2))))
+
+ ;; Info
+ `(Info-quoted ((,class (:inherit default))))
+
+ ;; company
+ `(company-tooltip ((,class (:background ,black-4))))
+ `(company-scrollbar-bg ((,class (:background ,black-4))))
+ `(company-scrollbar-fg ((,class (:background ,orange-2))))
+ `(company-tooltip-selection ((,class (:background ,black-3))))
+ `(company-tooltip-common ((,class (:foreground ,blue-1))))
+ `(company-tooltip-annotation ((,class (:foreground ,cyan-1))))
+
+ ;; AUCTeX
+ `(font-latex-sedate-face ((,class (:inherit font-lock-keyword-face))))
+
+ ;; Gnus
+ `(gnus-group-mail-3 ((,class (:foreground ,green-2))))
+
+ ;; org-mode
+ `(org-document-info-keyword ((,class (:foreground ,red-0))))
+ `(org-document-title ((,class (:inherit org-document-info-keyword))))
+ `(org-document-info ((,class (:inherit org-document-info-keyword))))
+ `(org-meta-line ((,class (:inherit org-document-info-keyword))))
+ `(org-archived ((,class (:slant italic))))
+ `(org-checkbox ((,class (:background ,white-2 :foreground ,black-3
+ :box (:line-width 1 :style released-button)))))
+ `(org-date ((,class (:foreground ,green-3 :underline t))))
+ `(org-done ((,class (:foreground ,green-4 :background ,green-00
+ :box (:line-width 1 :style none)))))
+ `(org-todo ((,class (:foreground ,red-3 :background ,orange-0
+ :box (:line-width 1 :style none)))))
+ `(org-level-1 ((,class (:foreground ,purple-00))))
+ `(org-level-2 ((,class (:foreground ,blue-0))))
+ `(org-level-3 ((,class (:foreground ,cyan-3))))
+ `(org-level-4 ((,class (:foreground ,green-02))))
+ `(org-level-5 ((,class (:foreground ,red-2))))
+ `(org-level-6 ((,class (:foreground ,orange-2))))
+ `(org-level-7 ((,class (:foreground ,magenta-2))))
+ `(org-level-8 ((,class (:foreground ,yellow-2))))
+ `(org-tag ((,class (:background ,black-1 :foreground ,white-0))))
+ `(org-column ((,class (:background ,yellow-00 :foreground ,black-3))))
+ `(org-column-title ((,class (:background ,blue-0 :foreground ,black-5 :underline t))))
+ `(org-agenda-structure ((,class (:foreground ,cyan-4 :background nil :bold nil))))
+ `(org-deadline-announce ((,class (:foreground ,red-3))))
+ `(org-scheduled ((,class (:foreground ,black-2))))
+ `(org-scheduled-previously ((,class (:foreground ,red-1))))
+ `(org-scheduled-today ((,class (:foreground ,blue-2))))
+ `(org-special-keyword ((,class (:foreground ,green-3))))
+ `(org-table ((,class (:background ,black-4 :foreground ,white-3))))
+ ;; `(org-time-grid ((,class (:foreground ,white-4))))
+ `(org-upcoming-deadline ((,class (:foreground ,red-3))))
+ ;; `(org-warning ((,class (:foreground ,white-0 :background ,red-3))))
+ `(org-warning ((,class (:foreground ,white-0 :background ,red-4))))
+ `(org-formula ((,class (:foreground ,purple-2))))
+ `(org-headline-done ((,class (:foreground ,green-2))))
+ `(org-hide ((,class (:foreground ,LIGHT_BG))))
+ `(org-code ((,class (:foreground ,blue-2 :background ,white-1))))
+ ;; `(org-link ((,class (:foreground ,blue-2 :underline ,blue-2))))
+ `(org-link ((,class (:inherit link))))
+ `(org-footnote ((,class (:foreground ,magenta-3))))
+ `(org-ellipsis ((,class (:foreground ,red-2))))
+ `(org-agenda-clocking ((,class (:foreground ,blue-3 :background ,blue-0))))
+ `(org-agenda-date ((,class (:foreground ,blue-1 :background nil :underline nil))))
+ `(org-agenda-date-weekend ((,class (:foreground ,purple-2 :background nil :slant italic :bold nil))))
+ `(org-agenda-date-today ((,class (:foreground ,blue-0 :background nil :underline t))))
+ `(org-agenda-column-dateline ((,class (:foreground ,white-0 :background ,yellow-0 :underline t))))
+ `(org-agenda-todo ((,class (:foreground ,white-0 :background ,red-2))))
+ `(org-agenda-done ((,class (:foreground ,green-3 :background ,nil))))
+ `(org-agenda-dimmed-todo-face ((,class (:foreground ,white-0 :background ,red-2))))
+ `(org-priority ((,class (:foreground ,red-1 :background ,nil))))
+ `(org-block ((,class (:foreground ,white-1 :background ,black-4))))
+ `(org-block-begin-line ((,class (:foreground ,black-3))))
+ `(org-block-end-line ((,class (:foreground ,black-3))))
+ `(org-block-background ((,class (:background ,white-1))))
+ ;; `(org-block-begin-line ((,class (:foreground ,blue-3 :background ,blue-0))))
+ ;; `(org-block-end-line ((,class (:foreground ,white-2 :background ,white-1))))
+ `(org-quote ((,class (:foreground ,blue-3 :background ,blue-00))))
+ `(org-mode-line-clock ((,class (:foreground ,blue-3 :background ,blue-0 :bold t))))
+ `(org-mode-line-clock-overrun ((,class (:foreground ,white-0 :background ,red-1 :bold t))))
+ ;; `(org-verbatim ((,class (:foreground ,blue-3 :background ,blue-00 :bold nil))))
+ `(org-verbatim ((,class (:foreground ,blue-1 :background ,black-4))))
+
+ ;; erc
+ `(erc-input-face ((,class (:foreground ,white-1))))
+ `(erc-button ((,class (:inherit link))))
+
+ ;; smartparens
+ `(sp-pair-overlay-face ((,class (:inherit default)))))
+
+ (custom-theme-set-variables
+ 'chayote
+ `(ansi-color-names-vector [,black-5 ,red-0 ,green-0 ,yellow-1
+ ,blue-1 ,purple-1 ,blue-0 ,white-1])))
+
+
+;;;
+;;; Modeline
+;;;
+
+;; My implementation of a clock for the modeline. The modeline is refreshed by a
+;; timer to ensure that it doesn't go too stale, as it could theoretically go
+;; several minutes without being updated.
+
+(defun mode-line-time ()
+ (format-time-string "%Y-%m-%d %H:%M"))
+
+(let ((timer (run-at-time t 60.0 #'ignore)))
+ (timer-set-function timer
+ (lambda ()
+ (force-mode-line-update))))
+
+;; For some time, I was using my own implementation of a modeline battery
+;; monitor. However, the constant calls to `battery-linux-sysfs' were
+;; interfering with the way that EXWM handles input focus, and I've since moved
+;; towards reusing `display-battery-mode'. Because strings in `mode-line-format'
+;; can contain control sequences, the percent symbol has to be escaped twice --
+;; once for `battery-format', and once for `mode-line-format'.
+;;
+;; Obviously, my tower lacks a battery, so I only enable this on my laptop.
+
+(when (string= (system-name) "Upsilon")
+ (require 'battery)
+ (display-battery-mode +1)
+ (setq battery-mode-line-format "%L: %t, %p%%%%"))
+
+(defun maybe-elide (s &optional max-length)
+ "Truncate `S' to `MAX-LENGTH'.
+Return a string derived from `S', possible elided, that is at
+most 1MAX-LENGTH characters long."
+ (let ((max-length (or max-length 30)))
+ (if (> (length s) max-length)
+ (concat (substring s 0 (- max-length 4)) " ...")
+ s)))
+
+(defun escape-mode-line-construct (s)
+ "Sanitize `S' for use in `mode-line-format'.
+Ensure that no part of S will be interpreted as a modeline format
+specifier."
+ (replace-regexp-in-string (regexp-quote "%") "%%" s t t))
+
+(defun mode-line-render (left right)
+ "Return a string of `window-width' length containing LEFT, and
+RIGHT aligned respectively."
+ (let* ((right (format-mode-line right))
+ (left (maybe-elide (format-mode-line left)
+ (- (window-width) (length right) 1)))
+ (padding (- (window-width) (length left) (length right) 1))
+ (padding (if (wholenump padding) (make-string padding ?\ ))))
+ (if padding
+ (escape-mode-line-construct (concat " " left padding right))
+ (concat " " left right))))
+
+(setq-default mode-line-format
+ '((:eval
+ (mode-line-render
+ (cond ((eq major-mode 'exwm-mode)
+ (concat "%b: " exwm-title))
+ ((eq major-mode 'pdf-view-mode)
+ (concat "%b ["
+ (number-to-string (pdf-view-current-page))
+ "/"
+ (number-to-string (pdf-cache-number-of-pages))
+ "]"))
+ (t
+ (concat "%b [%m] [%*] Ln: %l/"
+ (number-to-string (1+ (count-lines 1 (point-max))))
+ " Col: %c")))
+ (cond ((string= (system-name) "Upsilon")
+ (format "%s | %s"
+ (mode-line-time)
+ battery-mode-line-string))
+ (t (mode-line-time)))))))
+
+
+;;;
+;;; Ivy
+;;;
+
+;; As suggested by a friend [1], I tried out `ivy' as a replacement for `ido.'
+;; It was love at first sight. It's snappy, and works even better than `ido'
+;; after setting `exwm-workspace-show-all-buffers' and
+;; `exwm-layout-show-all-buffers'.
+;;
+;; [1]: https://icosahedron.website/@zge/101330910258980695
+
+(use-package ivy
+ ;; Configure how the =completing-read= is displayed.
+ :init
+ (setq ivy-height 5 ivy-count-format "")
+
+ ;; Enable Ivy globally.
+ :config
+ (ivy-mode 1))
+
+;; `counsel' is a nice Ivy-based frontend for a couple of interactive commands.
+
+(use-package counsel
+ :demand t
+ :bind
+ ("C-x b" . counsel-switch-buffer))
+
+;; Use Swiper instead of isearch.
+
+;; TODO: Should I re-enable this?
+
+;; (use-package swiper
+;; :bind
+;; ("C-r" . swiper)
+;; ("C-s" . swiper))
+
+
+;;;
+;;; Imenu
+;;;
+
+(use-package imenu
+ :bind ("C-c s" . imenu))
+
+
+;;;
+;;; Yasnippet
+;;;
+
+;; Load all snippets when `yas-minor-mode' is enabled.
+
+(use-package yasnippet
+ :demand t
+ :hook
+ (yas-minor-mode . yas-reload-all))
+
+
+;;;
+;;; Avy
+;;;
+
+;; Set up keybindings for Avy.
+
+(use-package avy
+ :bind
+ ("M-s" . avy-goto-char))
+
+;; Install a link-selection package for Avy, and create a binding for `org-mode'.
+
+;; TODO: Should I re-enable this?
+
+;; (use-package ace-link
+;; :after org
+;; :init (ace-link-setup-default)
+;; :bind (:map org-mode-map
+;; ("C-c C-o" . ace-link-org)))
+
+
+
+;;;
+;;; pass
+;;;
+
+;; TODO: Replace with pass.el?
+
+;; Here's a snippet for interactively pulling a password from password-store, so
+;; I'm not spawning a terminal every time I need to invoke my password manager.
+
+(defvar password-store-directory "~/.password-store"
+ "Directory containing passwords managed by `pass'.")
+
+(defun pass-password-files ()
+ "Returns the paths of every password managed by `pass'."
+ (let ((prefix-length (1+ (length (expand-file-name password-store-directory))))
+ (suffix-length (length ".gpg")))
+ (map 'list
+ #'(lambda (path) (cl-subseq path prefix-length (- suffix-length)))
+ (directory-files-recursively password-store-directory ".gpg$"))))
+
+(defun pass ()
+ "Prompt for a password and copy it to the clipboard.
+Passwords are taken from `password-store-directory'."
+ (interactive)
+ (let* ((account (completing-read "Account: " (pass-password-files)))
+ (path (concat password-store-directory "/" account ".gpg")))
(with-temp-buffer
(insert-file-contents path)
- (goto-char (point-min))
- (while (re-search-forward src-block-regexp nil t)
- (let ((lang (match-string 2))
- (args (match-string 4))
- (body (match-string 5)))
- (when (and (or (string= lang "emacs-lisp") (string= lang "elisp"))
- (not (string-match-p ":tangle\\s-+no" args)))
- (add-to-list 'body-list body)))))
- (let ((buffer (generate-new-buffer "*jakob-tangle-temporary*")))
- (set-buffer buffer)
- (apply 'insert (reverse body-list))
- (eval-buffer)
- (kill-buffer)))
- (message "Successfully evaluated %s" path))
+ (clipboard-kill-ring-save 1 (1- (buffer-end 1))))
+ (message "Password for %s copied to clipboard." account)))
+
+
+;;;
+;;; 8-ball
+;;;
+
+;; A little snippet for getting an answer to a dumb question. Responses were
+;; lifted from Wikipedia's page [1] about the toy.
+;;
+;; [1]: https://en.wikipedia.org/wiki/Magic_8-Ball
+
+(defun consult ()
+ "Consult the magic 8-ball.
+Interactive function to consult the magic 8-ball for answers to
+very important questions."
+ (interactive)
+ (let ((responses (list "It is certain." "It is decidedly so."
+ "Without a doubt." "Yes - definitely."
+ "You may rely on it." "As I see it, yes."
+ "Most likely." "Outlook good." "Yes."
+ "Signs point to yes." "Reply hazy, try again."
+ "Ask again later." "Better not tell you now."
+ "Cannot predict now." "Concentrate and ask again."
+ "Don't count on it." "My reply is no."
+ "My sources say no." "Outlook not so good."
+ "Very doubtful.")))
+ (message (elt responses (mod (random) (length responses))))))
+
+
+;;;
+;;; Window Management
+;;;
+
+;; Yes, I use Emacs as my window manager. You should, too!
+
+(use-package exwm
+ :config
+ (exwm-enable)
+
+ ;; I find that four workspaces is enough to start out with, and I like being
+ ;; able to use my X windows across workspaces.
+ :init
+ (setq exwm-workspace-number 4
+ exwm-workspace-show-all-buffers t
+ exwm-layout-show-all-buffers t)
+
+ ;; "Global key bindings" in EXWM work essentially anywhere, including buffers
+ ;; that are currently in char mode. I've taken this as an opportunity to
+ ;; incorporate windmove to my workflow, now that I have the super key at my
+ ;; disposal.
+ :init
+ (require 'windmove)
+
+ ;; Define some interactive functions for dealing with a multi-head setup.
+ (when (string= (system-name) "Epsilon")
+ (defun my-windmove-right ()
+ (interactive)
+ (if (windmove-find-other-window 'right)
+ (windmove-right)
+ (if (= exwm-workspace-current-index 1)
+ (exwm-workspace-switch 0)
+ (exwm-workspace-switch 1))))
+ (defun my-windmove-left ()
+ (interactive)
+ (if (windmove-find-other-window 'left)
+ (windmove-left)
+ (if (= exwm-workspace-current-index 1)
+ (exwm-workspace-switch 0)
+ (exwm-workspace-switch 1)))))
+
+ ;; And if we haven't defined those functions because we aren't on a multi-head
+ ;; setup, define aliases to the underlying windmove functions.
+ (when (and (not (fboundp #'my-windmove-left))
+ (not (fboundp #'my-windmove-right)))
+ (defalias #'my-windmove-left #'windmove-left)
+ (defalias #'my-windmove-right #'windmove-right))
+
+ (setq exwm-input-global-keys
+ `(([?\s-r] . exwm-reset)
+ ([?\s-a] . my-windmove-left)
+ ([?\s-d] . my-windmove-right)
+ ([?\s-w] . windmove-up)
+ ([?\s-s] . windmove-down)
+ ([?\s-p] . (lambda (command)
+ (interactive (list (read-shell-command "$ ")))
+ (start-process-shell-command command nil command)))
+ ([?\s-X] . (lambda ()
+ (interactive)
+ (start-process "" nil "slock")))
+ ([?\s-f] . (lambda ()
+ (interactive)
+ (start-process "" nil "firefox")))
+ (,(kbd "s-<return>") . (lambda ()
+ (interactive)
+ (start-process "" nil "alacritty")))
+
+ ;; Keys are offset such that <s-1> corresponds to the 0th workspace.
+ ,@(mapcar (lambda (i)
+ `(,(kbd (format "s-%d" i)) .
+ (lambda ()
+ (interactive)
+ (exwm-workspace-switch-create ,(- i 1)))))
+ (number-sequence 1 9))))
+
+ ;; Ah, yes. Simulation keys, the reason I decided to look at EXWM in the first
+ ;; place.
+ :init
+ (setq exwm-input-simulation-keys
+ '(([?\C-b] . [left])
+ ([?\M-b] . [C-left])
+ ([?\C-f] . [right])
+ ([?\M-f] . [C-right])
+ ([?\C-p] . [up])
+ ([?\C-n] . [down])
+ ([?\C-a] . [home])
+ ([?\C-e] . [end])
+ ([?\M-v] . [prior])
+ ([?\C-v] . [next])
+ ([?\C-d] . [delete])
+ ([?\C-k] . [S-end delete])
+
+ ([?\C-w] . [?\C-x])
+ ([?\M-w] . [?\C-c])
+ ([?\C-y] . [?\C-v])
+
+ ([?\C-s] . [?\C-f])))
+
+ ;; The tab management functionality of Firefox's Saka Key extension is
+ ;; limited, so I'm using simulation keys instead. I've also disabled
+ ;; simulation keys for alacritty because it doesn't play nicely with readline.
+ :hook
+ (exwm-manage-finish . (lambda ()
+ (when (and exwm-class-name
+ (string= exwm-class-name "Firefox"))
+ (exwm-input-set-local-simulation-keys
+ (concatenate 'list
+ exwm-input-simulation-keys
+ '(([?\C-q] . [?\C-w])))))
+ (when (and exwm-class-name
+ (string= exwm-class-name "Alacritty"))
+ (exwm-input-set-local-simulation-keys nil))))
+
+ ;; EXWM's buffers are named '*EXWM*' upon creation, which makes switching
+ ;; between windows a pain. This snippet renames buffers when their class or
+ ;; title changes.
+ :hook
+ (exwm-update-class . (lambda ()
+ (exwm-workspace-rename-buffer exwm-class-name)))
+ (exwm-update-title . (lambda ()
+ (when (not exwm-instance-name)
+ (exwm-workspace-rename-buffer exwm-title))))
+
+
+ ;; Start the Emacs daemon so that 'emacsclient' can be used from the command
+ ;; line to pop a new buffer for editing.
+ :config
+ (server-start)
+
+ ;; Configure the resolution of my monitors and let EXWM know about them.
+ :config
+ (require 'exwm-randr)
+ (defun hdmi-out ()
+ (and (string= (system-name) "Upsilon")
+ (save-window-excursion
+ (with-temp-buffer
+ (shell-command "xrandr" (current-buffer))
+ (cl-search "HDMI-1 connected" (buffer-string))))))
+ (setq exwm-randr-workspace-monitor-plist
+ (if (string= (system-name) "Epsilon")
+ '(0 "DVI-D-0" 1 "HDMI-A-3")
+ '(0 (if (hdmi-out) "HDMI-1" "eDP-1"))))
+ (add-hook 'exwm-randr-screen-change-hook
+ (lambda ()
+ (start-process-shell-command
+ "xrandr" nil
+ (if (string= (system-name) "Epsilon")
+ "xrandr --output DVI-D-0 --primary --left-of HDMI-A-0"
+ (if (hdmi-out)
+ "xrandr --output HDMI-1 --mode 1920x1080 --output eDP-1 --off"
+ "xrandr --output eDP-1 --mode 1920x1080")))))
+ (exwm-randr-enable))
+
+;; Some keybindings for buffer-move that are analagous to my keybindings for
+;; windmove above.
+
+(use-package buffer-move
+ :bind
+ ("M-s-w" . buf-move-up)
+ ("M-s-a" . buf-move-left)
+ ("M-s-s" . buf-move-down)
+ ("M-s-d" . buf-move-right))
+
+;; EXWM has issues popping any sort of pinentry window, so I just let Emacs do
+;; pinentry's job.
+
+(use-package epa
+ :straight nil
+ :config
+ (setq epa-pinentry-mode 'loopback))
+
+;; And on that note, allow 'gpg-agent' to query Emacs for my passphrase.
+
+(use-package pinentry
+ :demand t
+ :config
+ (setenv "INSIDE_EMACS" (format "%s,comint" emacs-version))
+ (pinentry-start))
+
+
+;;;
+;;; System
+;;;
+
+;; Set up keybindings for calling 'ag'.
+
+(use-package ag
+ :bind
+ ("C-c k" . counsel-ag))
+
+;; Hide the targets of symbolic links in `dired' listings. This is a visual
+;; preference of mine.
+
+(use-package dired
+ :straight nil
+ :init
+ (setq-default dired-listing-switches "-alL"))
+
+(use-package dired-rmjunk
+ ;; Set up a keybinding for invoking `dired-rmjunk' from `dired-mode'.
+ :bind
+ (:map dired-mode-map
+ ("r" . dired-rmjunk))
+
+ ;; Implement my customizations from =rmshit.py=.
+ :init
+ (setq dired-rmjunk-patterns
+ '(".recently-used"
+ ".local/share/recently-used.xbel"
+ ".thumbnails"
+ ".gconfd"
+ ".gconf"
+ ".local/share/gegl-0.2"
+ ".gstreamer-0.10"
+ ".esd_auth"
+ ".config/enchant"
+ ".parallel"
+ ".dbus"
+ "ca2"
+ "ca2~"
+ ".nv/"
+ ".npm/"
+ ".java/"
+ ".oracle_jre_usage/"
+ ".jssc/"
+ ".tox/"
+ ".pylint.d/"
+ ".guile_history"
+ ".sqlite_history"
+ ".python_history"
+ ".hy-history"
+ ".lesshst"
+ ".audacity-data"
+ ".wget-hsts"
+ ".scapy_history"
+ ".gdb_history"
+ ".binwalk"
+ ".lyrics"
+ ".slime-history.eld"
+ ".w3m")))
+
+
+;;;
+;;; Documents
+;;;
+
+;; Ensure that my Aspell "personal dictionary" is included when invoking ispell.
+
+(setq ispell-personal-dictionary "~/.aspell.en.pws")
+
+(use-package org
+ :straight org-plus-contrib
+
+ ;; File system of hygene.
+ :init
+ (setq-default org-id-locations-file "~/.cache/emacs/org-id-locations"
+ org-latex-preview-ltxpng-directory "~/.cache/emacs/ltximg/"
+ org-preview-latex-image-directory "~/.cache/emacs/ltximg/")
+
+ ;; General appearance and quality-of-life settings.
+ :init
+ (setq org-return-follows-link t
+ org-startup-folded nil
+ org-cycle-separator-lines -1
+ org-startup-with-inline-images t
+ org-hide-emphasis-markers t
+ org-tags-column 100
+ org-yank-folded-subtrees nil
+ org-link-elisp-confirm-function nil)
+
+ ;; Keep everything in column zero, instead of indenting according to the depth
+ ;; of the preceding headline.
+
+ :init
+ (setq org-adapt-indentation nil)
+
+ ;; Have <tab> indent according to the indentation rules of the corresponding
+ ;; mode in an =org-src= block, and start indentation on column 0. Taken from
+ ;; [1] and [2].
+ ;;
+ ;; [1]: https://stackoverflow.com/questions/15773354/indent-code-in-org-babel-src-blocks
+ ;; [2]: https://emacs.stackexchange.com/questions/9472/org-mode-source-block-doesnt-respect-parent-buffer-indentation
+ :init
+ (setq org-src-tab-acts-natively t
+ org-edit-src-content-indentation 0)
+
+ ;; Reuse the current window when I punch in <C-c '>, rather than mess up my
+ ;; window configuration.
+ :init
+ (setq org-src-window-setup 'current-window)
+
+ ;; Enable Geiser evaluation for Scheme blocks.
+ :config
+ (org-babel-do-load-languages
+ 'org-babel-load-languages
+ '((scheme . t)))
+
+ ;; Scale up embedded LaTeX. The default size is unreadable.
+ :config
+ (setq org-format-latex-options
+ (plist-put org-format-latex-options :scale 1.5))
+
+ ;; Don't interpret text following underscores as subscript text unless it is
+ ;; enclosed in braces.
+ :init
+ (setq org-use-sub-superscripts "{}")
+
+ ;; Enable Yasnippet and Flyspell.
+ :hook
+ (org-mode . yas-minor-mode-on)
+ (org-mode . flyspell-mode)
+
+ ;; Replace my Imenu binding with `org-goto,' as it allows me to select parent
+ ;; headings.
+ :init
+ (setq org-goto-interface 'outline-path-completionp)
+ (setq org-outline-path-complete-in-steps nil)
+
+ :bind
+ (:map org-mode-map ("C-c s" . org-goto))
+
+ ;; Global keybindings for my Org workflow.
+ :bind
+ ("C-c a" . (lambda () (interactive) (org-agenda nil "d")))
+ ("C-c c" . (lambda () (interactive) (org-capture nil "c")))
+ ("C-c t" . (lambda () (interactive) (org-capture nil "t")))
+
+ ;; Use Counsel's functionality for adding tags.
+ :bind
+ (:map org-mode-map ("C-c C-q" . counsel-org-tag))
+
+ ;; I maintain a personal wiki using Org and I like to link between pages. This
+ ;; is just a convenience link type, rooted at the base of my notes folder so
+ ;; that I can easily refer to other pages from subdirectories of
+ ;; `notes-directory'.
+ :config
+ (defvar notes-directory "~/Notes"
+ "Where all the pages of my 'personal wiki' are stored.")
+
+ (defun org-notes-follow (link)
+ "Open LINK, an Org link from a notes buffer.
+Visits LINK, or if LINK names a subdirectory of
+`notes-directory', the 'Index.org' in LINK."
+ (let ((directory-p (file-directory-p (expand-file-name link notes-directory))))
+ (find-file (expand-file-name (if directory-p "Index.org" (concat link ".org"))
+ notes-directory))))
+
+ (org-link-set-parameters "note" :follow #'org-notes-follow)
+
+ ;; Code for interactively navigating the personal wiki.
+ :config
+ (defvar notes-directory-ignore
+ '("." ".." ".git" ".gitignore" ".strfolder" "Drill" "GTD")
+ "Files and directories in `notes-directory' to exclude from
+ wiki interactions.")
+
+ (defun notes-file-is-page (filename)
+ "Returns whether or not FILENAME names a page rather than a category."
+ (string= (cl-subseq filename (- (length filename) 4)) ".org"))
+
+ (defun notes-file-should-ignore (filename)
+ "Returns whether or not a file in `notes-directory' should be ignored.
+ Members of `notes-directory-ignore' are ignored."
+ (member filename notes-directory-ignore))
+
+ (defun notes-hierarchy-paths (&optional category)
+ "Returns page and category paths relative to `notes-directory'.
+ The resultant list does not include files that satisfy the
+ `notes-file-should-ignore' predicate."
+ (let* ((path (if category
+ (concat notes-directory "/" category)
+ notes-directory))
+ (contents (cl-remove-if #'notes-file-should-ignore
+ (directory-files path)))
+ (pages (cl-remove-if-not #'notes-file-is-page contents))
+ (categories (cl-remove-if-not #'(lambda (subcategory)
+ (file-directory-p (concat path "/" subcategory)))
+ contents))
+ (categories (if category
+ (map 'list
+ #'(lambda (subcategory) (concat category "/" subcategory))
+ categories)
+ categories)))
+ (append (if category
+ (map 'list #'(lambda (page) (concat category "/" page)) pages)
+ pages)
+ categories
+ (apply #'append (map 'list #'notes-hierarchy-paths categories)))))
+
+ (defun notes-hierarchy ()
+ "Returns every page and category in `notes-directory'.
+ The result contains lists of the form (NAME PATH IS-PAGE-P), and
+ does not include files that satisfy the
+ `notes-file-should-ignore' predicate."
+ (map 'list
+ #'(lambda (path) (list
+ (cond ((cl-search "Index.org" path)
+ (let ((name (cl-subseq path 0 -9)))
+ (if (string= name "")
+ "/"
+ name)))
+ ((notes-file-is-page path) (cl-subseq path 0 -4))
+ (t path))
+ (concat notes-directory "/" path)
+ (notes-file-is-page path)))
+ (notes-hierarchy-paths)))
+
+ (defun notes-page-goto ()
+ "Interactively find a page in `notes-directory'."
+ (interactive)
+ (let* ((hierarchy (notes-hierarchy))
+ (hierarchy (cl-remove-if-not #'third hierarchy))
+ (name (ivy-read "Find file: " (map 'list #'first hierarchy)))
+ (item (cl-find-if #'(lambda (item) (string= name (first item))) hierarchy)))
+ (if item
+ (find-file (second item))
+ (let ((path (concat notes-directory "/" name ".org")))
+ (find-file path)))))
+
+ ;; Set up a key binding for quickly querying the wiki.
+ :bind
+ ("C-c w" . notes-page-goto)
+
+ ;; Low-friction means of adding things to my GTD inbox.
+ :config
+ (setq org-capture-templates
+ `(("t" "TODO" entry (file "~/Notes/GTD/Inbox.org")
+ "* TODO %?\n")))
+
+
+ ;; A rather convoluted agenda setup to show me:
+ ;; - The tasks I've marked as needing to be done today.
+ ;; - The inbox items I need to process.
+ ;; - My daily habits (implemented as recurring Org TODO's).
+ ;; - A view of deadlines and scheduled items for the week ahead.
+ :init
+ (setq org-agenda-files '("~/Notes/GTD/Habits.org"
+ "~/Notes/GTD/Inbox.org"
+ "~/Notes/GTD/Actionable.org"))
+
+ (let ((entry-done
+ '(lambda () (org-agenda-skip-entry-if 'todo 'done)))
+
+ (subtree-is-habit
+ '(lambda ()
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (string= (org-entry-get nil "STYLE") "habit")
+ subtree-end
+ nil))))
+
+ (subtree-is-not-habit
+ '(lambda ()
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (not (string= (org-entry-get nil "STYLE") "habit"))
+ subtree-end
+ nil))))
+
+ (subtree-is-not-inbox
+ '(lambda ()
+ (let ((subtree-end (save-excursion (org-end-of-subtree t))))
+ (if (not (cl-search "Inbox.org" (buffer-name)))
+ subtree-end
+ nil)))))
+
+ (setq org-agenda-custom-commands
+ `(("d" "Daily"
+ ((tags "PRIORITY=\"A\""
+ ((org-agenda-skip-function ,entry-done)
+ (org-agenda-overriding-header "Tasks for Today:")))
+ (alltodo ""
+ ((org-agenda-skip-function ,subtree-is-not-inbox)
+ (org-agenda-overriding-header "Inbox:")))
+ (agenda ""
+ ((org-agenda-span 'day)
+ (org-agenda-skip-function ,subtree-is-not-habit)
+ (org-deadline-warning-days 0)
+ (org-agenda-overriding-header "Habits:")))
+ (agenda ""
+ ((org-agenda-span 'week)
+ (org-agenda-skip-function ,subtree-is-habit)
+ (org-agenda-overriding-header "Week Ahead:"))))
+ ((org-agenda-compact-blocks t)))))))
+
+(use-package org-drill
+ ;; org-drill in upstream Org is unmaintained, and as described in [1], no
+ ;; longer works with the latest version of Org. I'm using this fork [2].
+ ;;
+ ;; [1]: https://bitbucket.org/eeeickythump/org-drill/issues/62/org-drill-doesnt-work-with-org-mode-92
+ ;; [2]: https://bitbucket.org/eeeickythump/org-drill/issues/63/maintainership
+ :straight (org-drill :type git :host github :repo "hakanserce/org-drill")
+
+ ;; Update LaTeX fragments when showing them to me.
+ :init
+ (with-eval-after-load 'org-drill
+ (defun org-drill--show-latex-fragments ()
+ (org-latex-preview '(16))))
+
+ ;; Scan the directory of the current buffer's file for other items, rather
+ ;; than just the current buffer. This is so that I can spread my cards across
+ ;; multiple files and still be tested on all of them when I run `org-drill'.
+ ;; The idea of combining topic during review sessions is suggested in Michael
+ ;; Nielsen's article [1].
+ ;;
+ ;; [1]: http://augmentingcognition.com/ltm.html
+ :init
+ (setq org-drill-scope 'directory)
+
+ ;; Use the SM-2 algorithm rather than the default SM-5 algorithm. I have faith
+ ;; in Anki's analysis of SM-5 [1] in the form that it is available.
+ ;;
+ ;; [1]: https://apps.ankiweb.net/docs/manual.html#what-about-sm-5
+ :init
+ (setq org-drill-spaced-repetition-algorithm 'sm2)
+
+ ;; Shut up the nagging "are you studying enough!?" message if I don't get
+ ;; enough items right. It's unhelpful and discourages me from being honest
+ ;; with the SRS algorithm.
+ :init
+ (setq org-drill-forgetting-index 100)
+
+ ;; Set up a keybinding for getting quickly starting a drill session.
+ :bind
+ ("<f11>" . (lambda ()
+ (interactive)
+ (let* ((drill-directory (concat notes-directory "/" "Drill"))
+ (path (-first #'(lambda (path) (s-ends-with-p ".org" path))
+ (directory-files drill-directory t))))
+ (find-file path)
+ (org-drill)))))
+
+;; The org-download package is incredible for quickly pull images into Org
+;; documents.
+
+(use-package org-download
+ ;; Configure it to use 'scrot' and the 'org-attach' machinery.
+ :init
+ (setq org-download-screenshot-method "scrot -s %s")
+ (setq org-download-method 'attach))
+
+;; When exporting Org as HTML, include syntax highlighting.
+
+(use-package htmlize
+ :config
+ (setq org-html-htmlize-output-type 'css))
+
+
+;;;
+;;; PDF
+;;;
+
+;; `doc-view' is unbearable for reading PDF's of any considerable size and
+;; `pdf-tools' brings a number of significant improvements, so I can justify not
+;; using the built-in solution. Even though I can run MuPDF in an EXWM buffer,
+;; the integration in terms of searching and viewing attachments that comes with
+;; pdf-tools makes for a much more pleasant experience.
+
+(use-package pdf-tools
+ :demand t
+ :config
+ (pdf-loader-install))
+
+
+;;;
+;;; IRC
+;;;
+
+;; Functions for connecting to and authenticating with my IRC bouncer.
+
+(defun read-bouncer-password ()
+ "Pull the password for my bouncer from authinfo"
+ (let ((auth (auth-source-search :host "jakob.space" :user "jakob" :port 6697)))
+ (unless auth
+ (error "No auth entry for bouncer."))
+ (let ((secretf (plist-get (car auth) :secret)))
+ (unless secretf
+ (error "No secret for bouncer's auth entry."))
+ (funcall secretf))))
+
+(defvar *bouncer-port-alist* '(("freenode" . 6697)
+ ("tildetown" . 6671)))
+
+(defun connect-bouncer (server)
+ "Connect to `SERVER' on the bouncer and rename the buffer"
+ (let* ((port (cdr (assoc server *bouncer-port-alist*)))
+ (buf (erc-tls :server "jakob.space"
+ :port port
+ :password (read-bouncer-password))))
+ (erc-with-buffer (buf (rename-buffer server)))))
+
+;; Interactive function for connecting to all of my bouncer's IRC servers.
+
+(defvar irc-autoconnect-networks '("freenode" "tildetown")
+ "List of networks to automatically connect to when 'irc' is invoked.")
+
+(defun irc ()
+ "Connect to every network in `irc-autoconnect-networks'."
+ (interactive)
+ (dolist (network irc-autoconnect-networks)
+ (connect-bouncer network)))
+
+(defun irc-quit ()
+ "Disconnects from and kills all buffers associated with ERC."
+ (interactive)
+ (let ((server-buffers (cl-remove-if-not #'erc-server-buffer-p (erc-buffer-list))))
+ (dolist (buffer server-buffers)
+ (erc-with-buffer (buffer)
+ (erc-quit-server (funcall erc-quit-reason nil))))))
+
+(use-package erc
+ :straight nil
+
+ ;; Only load the modules I actually use. This list deviates from the default
+ ;; by including truncate, but omitting autojoin.
+ :config
+ (setq erc-modules '(button completion fill
+ irccontrols list match
+ menu move-to-prompt netsplit
+ networks noncommands readonly
+ ring stamp track truncate))
+ (erc-update-modules)
+
+ ;; Configure personal details.
+ :init
+ (setq erc-nick '("jakob" "tsarfox")
+ erc-user-full-name "Jakob <http://jakob.space>"
+ erc-quit-reason (lambda (reason)
+ (or reason (concat "Program received signal "
+ "SIGSEGV, Segmentation fault."))))
+
+ ;; Display the current nick in the prompt.
+ :init
+ (setq erc-prompt (lambda () (format "%s>" (erc-current-nick))))
+
+ ;; Center everything.
+ :init
+ (setq erc-fill-function 'erc-fill-static
+ erc-fill-column 100
+ erc-fill-static-center 22)
+
+ ;; Enable MIRC and control sequences so I can partake in figlet spam,
+ ;; objectively the best part of the IRC experience.
+ :init
+ (setq erc-interpret-controls-p t
+ erc-interpret-mirc-color t)
+
+ ;; Enable timestamps, and show them to the left of the messages.
+ :init
+ (setq erc-hide-timestamps nil
+ erc-insert-timestamp-function 'erc-insert-timestamp-left)
+
+ ;; Although 'log' is omitted from `erc-modules', I still explicitly disable
+ ;; logging. I can just save the buffer if I want to save a conversation.
+ :init
+ (setq erc-enable-logging nil
+ erc-save-buffer-on-part nil)
+
+ ;; Similarly, 'autowaway' is omitted from `erc-modules', but I'm disabling it
+ ;; anyway.
+ :init
+ (setq erc-auto-set-away nil
+ erc-auto-discard-away nil)
+
+ ;; Kill buffers when I disconnect.
+ :init
+ (setq erc-kill-buffer-on-part t
+ erc-kill-queries-on-quit t
+ erc-kill-server-buffer-on-quit t)
+
+ ;; Don't get in my way by popping new windows when someone queries me, and
+ ;; don't assault me with windows when I connect to my bouncer.
+ :init
+ (setq erc-auto-query 'bury
+ erc-join-buffer 'bury)
+
+ ;; Show CTCP requests. Not for any reason in particular, I just like seeing
+ ;; when people ping me.
+ :init
+ (setq erc-paranoid t)
+
+ ;; Hide JOIN/PART/QUIT messages from idlers.
+ :init
+ (setq erc-lurker-hide-list '("JOIN" "PART" "QUIT")
+ erc-lurker-threshold-time 3600)
+
+ ;; Configure `erc-track' to ignore the less interesting message types.
+ :init
+ (setq erc-track-exclude-server-buffer t
+ erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE"
+ "324" "329" "332" "333" "353" "477"))
+
+ ;; Enable channel tracking.
+ :config
+ (erc-track-minor-mode 1)
+ (erc-track-mode 1)
+
+ ;; Move to the prompt as soon as I begin typing. I've configured the buffer
+ ;; text preceding it to be read-only, anyway.
+ :config
+ (erc-move-to-prompt-enable)
+
+ ;; Bind a key for resetting =erc-track=, since I tend to get absolutely
+ ;; bombarded upon reconnecting.
+ :bind
+ (:map erc-mode-map
+ ("C-c f" . (lambda ()
+ (interactive)
+ (setq erc-modified-channels-alist nil)
+ (erc-modified-channels-update))))
+
+ ;; Let `erc-fill' handle filling.
+ :hook
+ (erc-mode . (lambda () (auto-fill-mode 0))))
+
+;; Configure 'erc-hl-nicks' to show names using pretty colors.
+
+(use-package erc-hl-nicks
+ :after erc
+ :config
+ (erc-hl-nicks-enable))
+
+;; Display a bar delimiting read and unread messages. Adapted from [1].
+;;
+;; [1]: https://www.emacswiki.org/emacs/ErcBar
+
+(defvar erc-bar-overlay nil
+ "Overlay used to set bar")
+
+(defvar erc-bar-threshold 1
+ "The minimum number of messages required for the bar to be
+displayed.")
+
+(defun erc-bar-move-back (n)
+ "Moves back n message lines. Ignores wrapping, and server
+messages."
+ (interactive "nHow many lines ? ")
+ (re-search-backward "^.*<.*>" nil t n))
+
+(defun erc-bar-update-overlay ()
+ "Update the overlay for current buffer, based on the content of
+erc-modified-channels-alist. Should be executed on window
+change."
+ (interactive)
+ (let* ((info (assq (current-buffer) erc-modified-channels-alist))
+ (count (cadr info)))
+ (if (and info (> count erc-bar-threshold))
+ (save-excursion
+ (goto-char (point-max))
+ (when (erc-bar-move-back count)
+ (let ((inhibit-field-text-motion t))
+ (move-overlay erc-bar-overlay
+ (line-beginning-position)
+ (line-end-position)
+ (current-buffer)))))
+ (delete-overlay erc-bar-overlay))))
+
+(with-eval-after-load 'erc
+ ;; Run the hook before 'erc-modified-channels-update
+ (defadvice erc-track-mode (after erc-bar-setup-hook
+ (&rest args) activate)
+ (remove-hook 'window-configuration-change-hook 'erc-bar-update-overlay)
+ (add-hook 'window-configuration-change-hook 'erc-bar-update-overlay))
+
+ (add-hook 'erc-send-completed-hook (lambda (str) (erc-bar-update-overlay))))
+
+;; Display the bar as a highlight on the most recently unread message.
+
+(with-palette
+ (setq erc-bar-overlay (make-overlay 0 0))
+ (overlay-put erc-bar-overlay 'face `(:background ,black-2)))
+
+
+;;;
+;;; News
+;;;
+
+;; Most of my Gnus configuration is in gnus.el, which is not under version
+;; control because it contains a list of my accounts on various email servers.
+
+;; Stop Gnus from making a mess of my home directory.
+
+(setq gnus-init-file "~/.emacs.d/gnus.el"
+ gnus-startup-file "~/.local/share/newsrc"
+ gnus-read-newsrc-file nil
+ gnus-save-newsrc-file nil
+ gnus-directory "~/.local/share/gnus/news/"
+ message-directory "~/.local/share/gnus/mail")
+
+;; Sort articles such that newer items are at the top of the summary buffer.
+
+(setq gnus-thread-sort-functions '(gnus-thread-sort-by-most-recent-date))
+
+;; Enable Flyspell so I don't make a fool of myself when composing messages.
+
+(add-hook 'message-mode-hook #'flyspell-mode)
+
+;; Use w3m to display HTML emails. Adapted from [1].
+;;
+;; [1]: https://norman.walsh.name/2011/03/10/w3m
+
+(use-package w3m
+ :demand t
+ :config
+ (setq gnus-mime-display-multipart-related-as-mixed nil
+ mm-text-html-renderer #'w3m
+ mm-inline-text-html-with-images t
+ mm-inline-text-html-with-w3m-keymap nil
+ w3m-default-display-inline-images t))
+
+;; Encrypt emails such that we can read them as well.
+
+(setq mml-secure-openpgp-encrypt-to-self t)
+
+
+;;;
+;;; Tunes
+;;;
+
+(use-package mpdel
+ :demand t
+ ;; Use a more sane prefix key for interacting with 'mpdel'.
+ :init
+ (setq mpdel-prefix-key (kbd "C-c z"))
+
+ ;; Start `mpdel-mode', and while we're at it, start the daemon, too.
+ :config
+ (shell-command "ps -e | grep -q mpd || mpd")
+ (mpdel-mode))
+
+
+;;;
+;;; Programming
+;;;
+
+(use-package dumb-jump
+ ;; Replace `xref' keybindings instead of using the default ones, since these
+ ;; will get overwritten if I'm using a proper tag indexer anyway.
+ :config
+ (define-key dumb-jump-mode-map (kbd "C-M-g") nil)
+ (define-key dumb-jump-mode-map (kbd "C-M-p") nil)
+ (define-key dumb-jump-mode-map (kbd "C-M-q") nil)
+ (define-key dumb-jump-mode-map (kbd "M-.") #'dumb-jump-go)
+ (define-key dumb-jump-mode-map (kbd "M-,") #'dumb-jump-back)
+ (dumb-jump-mode))
+
+(use-package company
+ ;; Start suggesting completions (almost) as soon as possible.
+ :config
+ (setq company-idle-delay 0.1
+ company-minimum-prefix-length 3)
+
+ ;; Use standard movement keys for selecting entries from the completion popup
+ ;; instead of those used for minibuffer next/previous.
+ :bind
+ (:map company-active-map
+ ("M-n" . nil)
+ ("M-p" . nil)
+ ("C-n" . company-select-next)
+ ("C-p" . company-select-previous))
+
+ ;; And have <tab> responsible for completion rather than <ret>, so that
+ ;; company isn't quite as in-the-way.
+ :bind
+ (:map company-active-map
+ ("TAB" . company-complete-selection)
+ ("<tab>" . company-complete-selection)
+ ("RET" . nil)
+ ("<return>" . nil)))
+
+(use-package compile
+ :straight nil
+ :bind
+ ("<f12>" . compile)
+
+ ;; Taken from EmacsWiki [1]. The compilation command can be changed by invoking
+ ;; compile with a prefix argument, so I don't see the advantage of popping a
+ ;; prompt every time.
+ ;;
+ ;; [1]: https://www.emacswiki.org/emacs/CompileCommand
+ :init
+ (setq compilation-read-command nil)
+
+ ;; Hide the compilation buffer upon successful compilation. I typically don't
+ ;; care about the output unless there were errors.
+ :config
+ (add-hook 'compilation-finish-functions
+ (lambda (buf strg)
+ (let ((win (get-buffer-window buf 'visible)))
+ (when win (delete-window win))))))
+
+(use-package flycheck)
+
+(use-package magit
+ :bind
+ ("C-c g" . magit-status))
+
+;; After several failed attempts at getting into Smartparens, I've finally
+;; developed a taste for it. Rommel was not lying when he said that "[i]t's like
+;; having cybernetic limbs—it makes you jump higher and punch harder."
+
+(use-package smartparens
+ ;; Use the default Smartparens configuration as a base.
+ :config
+ (require 'smartparens-config)
+
+ ;; Enable the Smartparens-flavored replacement for `show-paren-mode'.
+ :config
+ (show-smartparens-global-mode t)
+
+ ;; Enable Smartparens in all modes derived from `prog-mode' or `comint-mode',
+ ;; the latter hopefully covering all REPL cases.
+ :hook
+ (prog-mode . turn-on-smartparens-mode)
+ (comint-mode . turn-on-smartparens-mode)
+
+ ;; Don't treat '`' as a pair marker when editing [[*Myrddin][Myrddin]] source code.
+ :config
+ (sp-local-pair 'myrddin-mode "`" nil :actions nil)
+
+ ;; Rebind the default keys for manipulating sexps to the alternatives
+ ;; suggested in Emacs and Pairs [1].
+ ;;
+ ;; [1]: https://ebzzry.io/en/emacs-pairs/
+ :bind
+ (:map smartparens-mode-map
+ ("C-M-a" . sp-beginning-of-sexp)
+ ("C-M-e" . sp-end-of-sexp)
+ ("C-M-d" . sp-down-sexp)
+ ("C-M-u" . sp-backward-up-sexp)
+ ("C-M-f" . sp-forward-sexp)
+ ("C-M-b" . sp-backward-sexp)
+ ("C-M-n" . sp-next-sexp)
+ ("C-M-p" . sp-previous-sexp)
+
+ ("M-[" . sp-backward-unwrap-sexp)
+ ("M-]" . sp-unwrap-sexp)
+ ("C-<right>" . sp-forward-slurp-sexp)
+ ("C-<left>" . sp-backward-slurp-sexp)
+ ("M-<right>" . sp-foward-barf-sexp)
+ ("M-<right>" . sp-forward-barf-sexp)
+
+ ("C-M-t" . sp-transpose-sexp)
+ ("C-M-k" . sp-kill-sexp)
+ ("M-k" . sp-backward-kill-sexp))
+
+ ;; The following mask `split-line' and `completion-at-point', respectively,
+ ;; but I have yet to use either of these, so I don't mind using the keys for
+ ;; Smartparens. I use "o" as a mnemonic for "out", and "i" is a mnemonic for
+ ;; "in".
+ :bind
+ (:map smartparens-mode-map
+ ("C-M-o" . sp-up-sexp)
+ ("C-M-i" . sp-backward-down-sexp)))
+
+(use-package lsp-ui
+ ;; The only reason I'm using `lsp-ui' is for integration with Flycheck. The
+ ;; features like lenses don't interest me.
+ :init
+ (setq lsp-ui-sideline-enable nil
+ lsp-ui-peek-enable nil
+ lsp-ui-doc-enable nil))
+
+;; Disable the default machinery in `lsp-mode'. The reason I'm using the package
+;; in the first place is for its ability to integrate with the other
+;; programming-related packages I use.
+
+(use-package company-lsp
+ :commands company-lsp
+ :init
+ (setq company-lsp-cache-candidates 'auto))
+
+(use-package lsp-mode
+ :init
+ (setq lsp-prefer-flymake nil
+ lsp-enable-xref t)
+
+ ;; Filesystem hygene.
+ :init
+ (setq lsp-session-file "~/.cache/emacs/.lsp-session-v1"
+ lsp-auto-guess-root t)
+
+ ;; Use `xref' keybindings instead of the default ones.
+ :bind
+ (:map lsp-mode-map
+ ("M-." . lsp-find-definition)
+ ("M-," . pop-tag-mark)))
+
+
+;;;
+;;; Rust
+;;;
+
+;; Enable flycheck.
+
+(use-package flycheck-rust
+ :after rust-mode
+ :hook
+ (flycheck-mode . flycheck-rust-setup)
+ (rust-mode . flycheck-mode))
+
+;; Enable company, ElDoc, and racer.
+
+(use-package racer
+ :init
+ (setq racer-cmd "~/.cargo/bin/racer")
+ (setq racer-rust-src-path "~/Code/rust/src")
+
+ :hook
+ (racer-mode . eldoc-mode)
+ (racer-mode . company-mode))
+
+(use-package rust-mode
+ :hook
+ (rust-mode . racer-mode)
+
+ ;; Run 'rustfmt' automatically.
+ :init
+ (setq rust-format-on-save t))
+
+;; Enable keybindings for working with Cargo.
+
+(use-package cargo
+ :hook (rust-mode . cargo-minor-mode))
+
+
+;;;
+;;; C
+;;;
+
+
+(use-package cc-mode
+ :straight nil
+
+ ;; Generally, I adhere to the Linux kernel coding style [1].
+ ;;
+ ;; [1]: https://www.kernel.org/doc/html/v4.10/process/coding-style.html
+ :hook
+ (c-mode . (lambda ()
+ (setq indent-tabs-mode t)
+ (c-set-style "linux")))
+
+ ;; Enable company, Flycheck, and ElDoc, integration for all of which is
+ ;; provided by `irony-mode'.
+ :hook
+ (c-mode . company-mode)
+ (c-mode . flycheck-mode)
+ (c-mode . eldoc-mode))
+
+(use-package irony
+ :hook
+ (c-mode . irony-mode)
+ (irony-mode . irony-cdb-autosetup-compile-options))
+
+(use-package company-irony
+ :config
+ (add-to-list 'company-backend 'company-irony))
+
+(use-package flycheck-irony
+ :hook
+ (flycheck-mode . flycheck-irony-setup))
+
+(use-package irony-eldoc
+ :hook
+ (irony-mode . irony-eldoc))
+
+
+;;;
+;;; C++
+;;;
+
+(use-package cc-mode
+ :straight nil
+ ;; Even though the Linux kernel coding style [1] was written to address C, I
+ ;; feel its viewpoints on indentation apply equally as well to C++.
+ ;;
+ ;; [1]: https://www.kernel.org/doc/html/v4.10/process/coding-style.html
+ :hook
+ (c++-mode . (lambda ()
+ (setq indent-tabs-mode t)
+ (c-set-style "linux")))
+
+ ;; Enable company, Flycheck, and ElDoc, integration for all of which is
+ ;; provided by `irony-mode'.
+ :hook
+ (c++-mode . company-mode)
+ (c++-mode . flycheck-mode)
+ (c++-mode . eldoc-mode))
+
+(use-package irony
+ :hook
+ (c++-mode . irony-mode))
+
+
+;;;
+;;; Myrddin
+;;;
+
+(use-package myrddin-mode
+ ;; Indent with tabs.
+ :hook (myrddin-mode . (lambda () (setq-local indent-tabs-mode t))))
+
+
+;;;
+;;; Emacs Lisp
+;;;
+
+(use-package elisp-mode
+ :straight nil
+ ;; Enable company for both `lisp-interaction-mode' and `emacs-lisp-mode'.
+ :hook
+ (lisp-interaction-mode . company-mode)
+ (emacs-lisp-mode . company-mode)
+
+ ;; Avoid omitting information when displaying deeply-nested lists.
+ :init
+ (setq eval-expression-print-level nil))
+
+;; Helpful is a wonderful improvement upon the vanilla `describe-.*' functions,
+;; capable of showing source code, keybindings, symbol properties, etc.
+
+(use-package helpful
+ :bind
+ ("C-h f" . helpful-callable)
+ ("C-h v" . helpful-variable)
+ ("C-h k" . helpful-key)
+ ("C-h C" . helpful-command)
+ ("C-c C-d" . helpful-at-point))
+
+
+;;;
+;;; Common Lisp
+;;;
+
+;; SLIME is my preferred REPL for working with Common Lisp,
+
+(use-package slime
+ ;; and SBCL is my preferred implementation.
+ :config
+ (setq inferior-lisp-program "sbcl")
+
+ ;; Enable Smartparens and Company in the SLIME REPL.
+ :hook
+ (slime-repl-mode . (lambda ()
+ (smartparens-mode +1)
+ (company-mode +1)))
+
+ ;; Enable the SLIME backend for company.
+ :config
+ (setq slime-contribs '(slime-fancy slime-company)))
+
+(use-package slime-company
+ :after company
+ :hook
+ (lisp-mode . (lambda () (company-mode +1)))
+ :config
+ (add-to-list 'company-backends 'company-slime))
+
+
+;;;
+;;; Scheme
+;;;
+
+;; Geiser is my preferred REPL for working with Scheme.
+
+(use-package geiser
+ ;; Set up Geiser for all of the Scheme implementations I regularly use.
+ :init
+ (setq geiser-active-implementations '(chicken guile racket))
+ (setq geiser-default-implementation 'guile)
+ (setenv "CHICKEN_DOC_REPOSITORY" "/home/jakob/.local/share/chicken/doc/")
+
+ ;; Filesystem hygene.
+ :init
+ (setq geiser-repl-history-filename "~/.cache/emacs/geiser_history"))
+
+
+;;;
+;;; Python
+;;;
+
+;; Set up company.
+
+(use-package company-jedi
+ :after company
+
+ :hook
+ (python-mode . company-mode)
+ (python-mode . jedi:setup)
+
+ :init
+ (setq jedi:complete-on-dot t)
+ (setq jedi:tooltip-method nil)
+
+ :config
+ (add-to-list 'company-backends 'company-jedi))
+
+
+;;;
+;;; Hy
+;;;
+
+;; Set up company.
+
+(use-package hy-mode
+ :hook
+ (hy-mode . company-mode)
+ (hy-mode . jedi-mode)
+
+ ;; `hy-mode' doesn't create an autoload for the command to start a Hy REPL, so
+ ;; we'll declare it here. This lets me M-x run-hy before `hy-mode' loaded.
+ :commands run-hy
+
+ ;; Enable `hy-mode' in files ending with ".hy"
+ :mode "\\.hy\\'")
+
+
+;;;
+;;; Javascript
+;;;
+
+(use-package js
+ :straight nil
+
+ ;; Set up `js-mode' to interact with a LSP daemon.
+ :requires lsp-mode
+ :hook (js-mode . lsp)
+ :config
+ (lsp-register-client
+ (make-lsp-client :new-connection (lsp-stdio-connection "~/.bin/js-ts-stdio")
+ :major-modes '(js-mode)
+ :server-id 'js-ts-stdio))
+
+;; I have yet to find a style guide I agree with, but one thing common to most
+;; of them that I _do_ agree with is that Javascript is far more readable with
+;; an indentation width of 2.
+ :init
+ (setq-default js-indent-level 2))
+
+
+;;;
+;;; Nim
+;;;
-(eval-document "~/.emacs.d/jakob.org")
+(use-package nim-mode)

© 2015 - 2026 Jakob L. Kreuze