summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--init.el2109
-rw-r--r--jakob.org2630
3 files changed, 2080 insertions, 2660 deletions
diff --git a/.gitignore b/.gitignore
index 1c78ffc..7f0eb12 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ irony/
straight/
transient/
gnus.el
+*.elc
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)
diff --git a/jakob.org b/jakob.org
deleted file mode 100644
index 737272e..0000000
--- a/jakob.org
+++ /dev/null
@@ -1,2630 +0,0 @@
-What follows is my personal configuration for GNU Emacs, done in the style of
-literate programming. Literate configurations typically rely on =org-babel= to
-evaluate the Lisp forms upon starting Emacs, but this configuration takes a
-different approach: [[./init.el][init.el]] defines a function, =eval-document=, which "tangles"
-all of the relevant Org source blocks into a temporary buffer and proceeds to
-invoke =eval-buffer=. The main implication of this is that there won't be a
-=jakob.el= in the Emacs configuration directory.
-
-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.
-
-* Bootstrapping
-
-A few of the packages I use require early initialization.
-
-** Package Management
-
-I use [[https://github.com/raxod502/straight.el][straight.el]] insetad of =package.el=.
-
-#+BEGIN_SRC elisp
-(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))
-#+END_SRC
-
-=straight.el= does have a known issue with installing Org, and requires a
-workaround for it early on. The details are outlined [[https://github.com/raxod502/straight.el#installing-org-with-straightel][here]]
-
-#+BEGIN_SRC elisp
-(require 'subr-x)
-(straight-use-package 'git)
-
-(defun org-git-version ()
- "The Git version of org-mode.
-Inserted by installing org-mode or when a release is made."
- (require 'git)
- (let ((git-repo (expand-file-name
- "straight/repos/org/" user-emacs-directory)))
- (string-trim
- (git-run "describe"
- "--match=release\*"
- "--abbrev=6"
- "HEAD"))))
-
-(defun org-release ()
- "The release version of org-mode.
-Inserted by installing org-mode or when a release is made."
- (require 'git)
- (let ((git-repo (expand-file-name
- "straight/repos/org/" user-emacs-directory)))
- (string-trim
- (string-remove-prefix
- "release_"
- (git-run "describe"
- "--match=release\*"
- "--abbrev=0"
- "HEAD")))))
-
-(provide 'org-version)
-#+END_SRC
-
-As for the actual installation and configuration of packages, I use [[https://github.com/jwiegley/use-package][use-package]].
-=straight-use-package-by-default= ensures that packages are registered and built,
-without reqiring an explicit =:straight t=.
-
-#+BEGIN_SRC elisp
-(straight-use-package 'use-package)
-(setq straight-use-package-by-default t)
-#+END_SRC
-
-=bind-key= is required early on for =use-package='s =:bind= declarations.
-
-#+BEGIN_SRC elisp
-(use-package bind-key :defer t)
-#+END_SRC
-
-** Libraries
-
-A handful of the Elisp snippets in this file depend on third-party libraries
-which aren't loaded by default.
-
-#+BEGIN_SRC elisp
-(require 'battery)
-(require 'cl-lib)
-(require 'cl-extra)
-#+END_SRC
-
-If initialization fails and results in a state where nothing works and Emacs
-complains that =map= and =reduce= are undefined, uncomment the following line.
-
-#+BEGIN_SRC elisp
-(require 'cl)
-#+END_SRC
-
-** Notification Daemon
-
-I have yet to find a way of dealing with pop-up notifications natively in Emacs
-that I'm happy with, so for now, I'm just using [[https://dunst-project.org/][Dunst]].
-
-#+BEGIN_SRC elisp
-(call-process-shell-command "nohup dunst >/dev/null &" nil 0)
-#+END_SRC
-
-Simple function for sending an alert to the daemon.
-
-#+BEGIN_SRC elisp
-(defun dunst-alert (header string &rest objects)
- "Send an alert to the Dunst daemon."
- (let ((string (funcall #'format string objects))
- (command (format "notify-send -a \"%s\" \"%s\"" header string)))
- (call-process-shell-command command nil 0)))
-#+END_SRC
-
-** Configuration
-
-Bind some keys for working with this configuration file.
-
-#+BEGIN_SRC elisp
-(defun config-goto ()
- "Switch to a buffer visiting the literate config file."
- (interactive)
- (find-file "~/.emacs.d/jakob.org"))
-
-(defun config-reload ()
- "Evaluate the literate config file."
- (interactive)
- (straight-transaction
- (straight-mark-transaction-as-init)
- (load user-init-file nil 'nomessage)))
-
-(bind-key "C-c e" #'config-goto)
-(bind-key "C-c r" #'config-reload)
-#+END_SRC
-
-* Vanilla Emacs
-
-Any configuration addressing the core of Emacs rather than a third-party
-package.
-
-** Startup
-
-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.
-
-#+BEGIN_SRC elisp
-(setq inhibit-startup-message t
- inhibit-startup-buffer-menu t
- initial-scratch-message nil
- initial-buffer-choice nil)
-#+END_SRC
-
-** Filesystem
-
-A recurring theme in this configuration file is configuring where Emacs packages
-drop their various state files. =no-littering.el= is a package that handles this
-with =var/= and =etc/= directories in =~/.emacs.d/=, 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. I've
-decided to manually set relevant variables to subdirectories of =~/.cache= and
-=~/.local/share=.
-
-Save backup files to one directory instead of making a mess of the filesystem.
-
-#+BEGIN_SRC elisp
-(setq backup-directory-alist `(("." . "~/.cache/emacs")))
-#+END_SRC
-
-=auto-save=, in addition to the actual files it saves, maintains another file
-listing the files it's currently taking care of.
-
-#+BEGIN_SRC elisp
-(setq auto-save-list-file-prefix "~/.cache/emacs/auto-save-list/.saves-")
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
-(setq create-lockfiles nil)
-#+END_SRC
-
-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=.
-
-#+BEGIN_SRC elisp
-(setq custom-file "~/.emacs.d/custom.el")
-(when (file-exists-p custom-file)
- (load custom-file))
-#+END_SRC
-
-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=.
-
-#+BEGIN_SRC elisp
-(savehist-mode -1)
-(setq savehist-file "~/.cache/emacs/history")
-#+END_SRC
-
-** User Interface
-
-Use a consistent confirmation dialog of "y or n".
-
-#+BEGIN_SRC elisp
-(fset 'yes-or-no-p 'y-or-n-p)
-#+END_SRC
-
-Automatically revert buffers and dired listings when something on disk changes.
-
-#+BEGIN_SRC elisp
-(use-package autorevert
- :config
- (global-auto-revert-mode 1)
- (setq global-auto-revert-non-file-buffers t
- auto-revert-verbose nil))
-#+END_SRC
-
-"Command attempted to use minibuffer while in minibuffer" gets old fast.
-
-#+BEGIN_SRC elisp
-(setq enable-recursive-minibuffers t)
-#+END_SRC
-
-Actually select help windows when I pop them so that I can kill them with <q>.
-
-#+BEGIN_SRC elisp
-(setq help-window-select t)
-#+END_SRC
-
-Most *NIX tools work best when files are terminated with a newline.
-
-#+BEGIN_SRC emacs-lisp
-(setq require-final-newline t)
-#+END_SRC
-
-Sentences should be separated by a single space. Treat two sentences as such
-when filling.
-
-#+BEGIN_SRC elisp
-(setq sentence-end-double-space nil)
-#+END_SRC
-
-I use the tab key, but I generally prefer space characters to tabstops and like
-a four-character width for indentation.
-
-#+BEGIN_SRC elisp
-(setq-default indent-tabs-mode nil
- tab-width 4)
-#+END_SRC
-
-Modern conventions state that 80 characters is the standard width.
-
-#+BEGIN_SRC elisp
-(setq-default fill-column 80)
-#+END_SRC
-
-When killing, add whatever's in the system clipboard if it isn't there already,
-so I can get it back with <M-y>.
-
-#+BEGIN_SRC elisp
-(setq save-interprogram-paste-before-kill t)
-#+END_SRC
-
-Enable the default-disabled functions that I use.
-
-#+BEGIN_SRC elisp
-(put 'downcase-region 'disabled nil)
-(put 'upcase-region 'disabled nil)
-#+END_SRC
-
-Enable useful visual queues.
-
-#+BEGIN_SRC elisp
-(column-number-mode t)
-#+END_SRC
-
-Unbind <C-z> and <C-x C-z> so that I'm not accidentally calling =suspend-frame=.
-
-#+BEGIN_SRC elisp
-(global-unset-key (kbd "C-z"))
-(global-unset-key (kbd "C-x C-z"))
-#+END_SRC
-
-** Kill-Ring
-
-Replace =kill-word=, =backward-kill-word=, and =kill-visual-line= with equivalents
-that do not copy the region into the kill ring.
-
-#+BEGIN_SRC elisp
-(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)
-#+END_SRC
-
-* Quality-of-Life
-
-I use <M-v> and <M-V> for movement, mostly, so having <prior> and <next> bound
-to the same functions is a little redundant. Use them for scolling by one line
-at a time instead, which is sometimes useful when perusing IRC logs.
-
-#+BEGIN_SRC scheme
-(bind-key "<next>" #'scroll-up-line)
-(bind-key "<prior>" #'scroll-down-line)
-#+END_SRC
-
-Enable =winner-mode=.
-
-#+BEGIN_SRC elisp
-(winner-mode 1)
-#+END_SRC
-
-Convenience function for inserting an ISO 8601 timestamp or copying it to the
-kill ring.
-
-#+BEGIN_SRC elisp
-(defun insert-isodate (arg)
- "Insert the current time as an ISO 8601 timestamp. If ARG is
-non-nil, do not insert the timestamp, but push it to the
-beginning of the kill ring."
- (interactive "P")
- (let ((timestamp (format-time-string "%Y-%m-%dT%H:%M:%S%z")))
- (if arg
- (kill-new timestamp)
- (insert timestamp))))
-
-(bind-key "C-x M-d" #'insert-isodate)
-#+END_SRC
-
-** Ivy
-
-As [[https://icosahedron.website/@zge/101330910258980695][suggested by a friend]], 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=.
-
-Configure how the =completing-read= is displayed.
-
-#+BEGIN_SRC elisp
-(use-package ivy
- :init
- (setq ivy-height 5
- ivy-count-format "")
-#+END_SRC
-
-Enable Ivy globally.
-
-#+BEGIN_SRC elisp
- :config
- (ivy-mode 1))
-#+END_SRC
-
-Use Counsel as a frontend to a couple of interactive commands.
-
-#+BEGIN_SRC elisp
-(use-package counsel
- :demand t
- :bind
- ("C-x b" . counsel-switch-buffer))
-#+END_SRC
-
-Use Swiper instead of isearch.
-
-#+BEGIN_SRC elisp
-(use-package swiper
- :bind
- ("C-r" . swiper)
- ("C-s" . swiper))
-#+END_SRC
-
-** Yasnippet
-
-Load all snippets when =yas-minor-mode= is enabled.
-
-#+BEGIN_SRC elisp
-(use-package yasnippet
- :demand t
- :hook
- (yas-minor-mode . yas-reload-all))
-#+END_SRC
-
-** tiny
-
-Set up keybindings for =tiny=. I'm ensuring it's loaded _after_ =org= because it has a
-=(require 'org)= declaration that causes the built-in version of Org to load.
-
-#+BEGIN_SRC elisp
-(use-package tiny
- :after org
- :config
- (tiny-setup-default))
-#+END_SRC
-
-** ialign
-
-#+BEGIN_SRC elisp
-(use-package ialign)
-#+END_SRC
-
-** avy
-
-Set up keybindings for =avy=.
-
-#+BEGIN_SRC elisp
-(use-package avy
- :bind
- ("M-s" . avy-goto-char))
-#+END_SRC
-
-Install a link-selection package for =avy=, and create a binding for org-mode.
-
-#+BEGIN_SRC elisp
-(use-package ace-link
- :after org
- :init (ace-link-setup-default)
- :bind (:map org-mode-map
- ("C-c C-o" . ace-link-org)))
-#+END_SRC
-
-** Imenu
-
-Set up keybindings for =imenu=.
-
-#+BEGIN_SRC elisp
-(use-package imenu
- :bind ("C-c s" . imenu))
-#+END_SRC
-
-** pass
-
-Here's a snippet for interactively pulling a password from [[passwordstore.org][password-store]], so
-I'm not spawning a terminal every time I need to invoke my password manager.
-
-#+BEGIN_SRC elisp
-(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 path)
- (clipboard-kill-ring-save 1 (1- (buffer-end 1))))
- (message "Password for %s copied to clipboard." account)))
-#+END_SRC
-
-* Appearance
-
-This feature doesn't see frequent use, but I prefer three proportionally-sized
-windows to two really tiny ones over a really big one.
-
-#+BEGIN_SRC elisp
-(setq window-combination-resize t)
-#+END_SRC
-
-Don't do jerky jumps when the cursor reaches the end of the window. Instead,
-just scroll by one line.
-
-#+BEGIN_SRC elisp
-(setq scroll-conservatively most-positive-fixnum)
-#+END_SRC
-
-Use soft wrapping instead of "scrolling" when overflowing the width of a frame.
-
-#+BEGIN_SRC elisp
-(global-visual-line-mode t)
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
-(menu-bar-mode -1)
-(scroll-bar-mode -1)
-(tool-bar-mode -1)
-(fringe-mode -1)
-#+END_SRC
-
-Hacking is a lot more serene when visual stimulus is kept to a minimum.
-
-#+BEGIN_SRC elisp
-(blink-cursor-mode 0)
-#+END_SRC
-
-** Theme
-
-This is my custom theme, the code for which is very loosely adapted from
-[[https://github.com/kuanyui/moe-theme.el][moe-theme.el]], but most of the palette has been altered to emulate the appearance
-of my old setup of Emacs in a terminal emulator.
-
-#+BEGIN_SRC elisp
-(deftheme chayote
- "Inspired by the appearance of Emacs in my terminal emulator.")
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
-(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 lexical environment with access to the
-individual colors making up the theme's palette."
- `(let ((class '((class color) (min-colors 89)))
- ,@palette)
- ,@form))
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
-(defun display-palette ()
- "Display names of colors defined 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))))
-#+END_SRC
-
-#+BEGIN_SRC elisp
-(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])))
-#+END_SRC
-
-** 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.
-
-#+BEGIN_SRC elisp
-(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))))
-#+END_SRC
-
-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=.
-
-#+BEGIN_SRC elisp
-(when (string= (system-name) "Upsilon")
- (display-battery-mode +1)
- (setq battery-mode-line-format "%L: %t, %p%%%%"))
-#+END_SRC
-
-Use an =:eval= form to align modeline contents.
-
-#+BEGIN_SRC elisp
-(cl-defun maybe-elide (s &optional (max-length 30))
- "Return a string derived from S, possible elided, that is at
-most MAX-LENGTH characters long."
- (if (> (length s) max-length)
- (concat (substring s 0 (- max-length 4)) " ...")
- s))
-
-(defun escape-mode-line-construct (s)
- "Ensure that no part of S will be interpreted as a modeline
-format specifier."
- (s-replace "%" "%%" s))
-
-(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)))))))
-#+END_SRC
-
-** Pretty Symbols
-
-Enable =prettify-symbols= globally.
-
-#+BEGIN_SRC elisp
-(global-prettify-symbols-mode t)
-#+END_SRC
-
-This is snippet was taken verbatim from [[https://emacs.stackexchange.com/questions/17283/is-it-possible-to-get-prettified-symbols-in-org-mode-source-blocks][Emacs StackExchange]] to enable
-=prettify-symbols= in Org source code blocks. Sadly, I can't think of a way to
-implement this as advice.
-
-#+BEGIN_SRC elisp
-(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)))))
-#+END_SRC
-
-** Page Breaks
-
-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'.
-
-#+BEGIN_SRC elisp
-(use-package page-break-lines
- :demand t
- :config
- (global-page-break-lines-mode))
-#+END_SRC
-
-* Window Management
-
-Yes, I use Emacs as my window manager. You should, too!
-
-#+BEGIN_SRC elisp
-(use-package exwm
- :config
- (exwm-enable)
-#+END_SRC
-
-I find that four workspaces is enough to start out with, and I like being able
-to use my X windows across workspaces.
-
-#+BEGIN_SRC elisp
- :init
- (setq exwm-workspace-number 4
- exwm-workspace-show-all-buffers t
- exwm-layout-show-all-buffers t)
-#+END_SRC
-
-"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.
-
-#+BEGIN_SRC elisp
- :init
- (require 'windmove)
-
- (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)))))
-
- (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 "kitty")))
-
- ;; 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))))
-#+END_SRC
-
-Ah, yes. Simulation keys, the real reason I decided to install EXWM.
-
-#+BEGIN_SRC elisp
- :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])))
-#+END_SRC
-
-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 kitty
-because it doesn't play nicely with readline.
-
-#+BEGIN_SRC elisp
- :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 "kitty"))
- (exwm-input-set-local-simulation-keys nil))))
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
- :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))))
-#+END_SRC
-
-Start the Emacs daemon so that =emacsclient= can be used from the command line
-to pop a new buffer for editing.
-
-#+BEGIN_SRC elisp
- :config
- (server-start)
-#+END_SRC
-
-#+BEGIN_SRC elisp
- :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))
-#+END_SRC
-
-Some keybindings for =buffer-move= that are analagous to my keybindings for
-=windmove= above.
-
-#+BEGIN_SRC elisp
-(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))
-#+END_SRC
-
-EXWM has issues popping any sort of pinentry window, so I just let Emacs do
-pinentry's job.
-
-#+BEGIN_SRC elisp
-(use-package epa
- :straight nil
- :config
- (setq epa-pinentry-mode 'loopback))
-#+END_SRC
-
-* System
-
-** ag
-
-Set up keybindings for calling =ag=.
-
-#+BEGIN_SRC elisp
-(use-package ag
- :bind
- ("C-c k" . counsel-ag))
-#+END_SRC
-
-** Dired
-
-Hide the targets of symbolic links in dired listings. This is just a visual
-preference of mine.
-
-#+BEGIN_SRC elisp
-(use-package dired
- :straight nil
- :init
- (setq-default dired-listing-switches "-alL"))
-#+END_SRC
-
-Set up a keybinding for invoking =dired-rmjunk= from =dired-mode=.
-
-#+BEGIN_SRC elisp
-(use-package dired-rmjunk
- :bind
- (:map dired-mode-map
- ("r" . dired-rmjunk))
-#+END_SRC
-
-Implement my customizations from =rmshit.py=.
-
-#+BEGIN_SRC elisp
- :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")))
-#+END_SRC
-
-Don't enable =dired-hide-details-mode= when I use =dired-git-info-mode=.
-
-#+BEGIN_SRC elisp
-(use-package dired-git-info
- :init
- (setq dgi-auto-hide-details-p nil)
-#+END_SRC
-
-Bind a key for enabling it.
-
-#+BEGIN_SRC elisp
- :bind
- (:map dired-mode-map
- (")" . dired-git-info-mode)))
-#+END_SRC
-
-** Tramp
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
-(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"))
-#+END_SRC
-
-* Documents
-
-** Flyspell
-
-Ensure that my Aspell "personal dictionary" is included when invoking ispell.
-
-#+BEGIN_SRC elisp
-(setq ispell-personal-dictionary "~/.aspell.en.pws")
-#+END_SRC
-
-** Org
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
-(use-package org
- :straight org-plus-contrib
- :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/")
-#+END_SRC
-
-Only load the =org-contrib= modules that I actually use.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-modules '(org-drill))
-#+END_SRC
-
-General appearance and quality-of-life settings.
-
-#+BEGIN_SRC elisp
- :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)
-#+END_SRC
-
-Keep everything in column zero, instead of indenting according to the depth of
-the preceding headline.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-adapt-indentation nil)
-#+END_SRC
-
-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 [[https://stackoverflow.com/questions/15773354/indent-code-in-org-babel-src-blocks][this post]] and
-[[https://emacs.stackexchange.com/questions/9472/org-mode-source-block-doesnt-respect-parent-buffer-indentation][this post]] on StackExchange.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-src-tab-acts-natively t
- org-edit-src-content-indentation 0)
-#+END_SRC
-
-Reuse the current window when I punch in =C-c '=, rather than mess up my window
-configuration.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-src-window-setup 'current-window)
-#+END_SRC
-
-Enable Geiser evaluation for [[Scheme]] blocks.
-
-#+BEGIN_SRC elisp
- :config
- (org-babel-do-load-languages
- 'org-babel-load-languages
- '((scheme . t)))
-#+END_SRC
-
-Scale up embedded LaTeX. The default size is unreadable.
-
-#+BEGIN_SRC elisp
- :config
- (setq org-format-latex-options
- (plist-put org-format-latex-options :scale 1.5))
-#+END_SRC
-
-Don't interpret text following underscores as subscript text unless it is
-enclosed in braces.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-use-sub-superscripts "{}")
-#+END_SRC
-
-Enable [[Yasnippet]] and Flyspell.
-
-#+BEGIN_SRC elisp
- :hook
- (org-mode . yas-minor-mode-on)
- (org-mode . flyspell-mode)
-#+END_SRC
-
-Replace my [[Imenu]] binding with =org-goto=, as it allows me to select parent
-headings.
-
-#+BEGIN_SRC elisp
- :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))
-#+END_SRC
-
-Global keybindings for my Org workflow.
-
-#+BEGIN_SRC elisp
- :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")))
-#+END_SRC
-
-Use Counsel's functionality for adding tags.
-
-#+BEGIN_SRC elisp
- :bind
- (:map org-mode-map
- ("C-c C-q" . counsel-org-tag))
-#+END_SRC
-
-*** Personal Wiki
-
-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=.
-
-#+BEGIN_SRC elisp
- :config
- (defvar notes-directory "~/Notes"
- "Where all the pages of my 'personal wiki' are stored.")
-
- (defun org-notes-follow (link)
- "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)
-#+END_SRC
-
-Code for interactively navigating the personal wiki.
-
-#+BEGIN_SRC elisp
- :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)))))
-#+END_SRC
-
-Set up a key binding for quickly querying the wiki.
-
-#+BEGIN_SRC elisp
- :bind
- ("C-c w" . notes-page-goto)
-#+END_SRC
-
-*** Capture
-
-Low-friction means of adding things to my GTD inbox.
-
-#+BEGIN_SRC elisp
- :config
- (setq org-capture-templates
- `(("t" "TODO" entry (file "~/Notes/GTD/Inbox.org")
- "* TODO %?\n")))
-#+END_SRC
-
-*** Agenda
-
-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.
-
-#+BEGIN_SRC elisp
- :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))))))
-#+END_SRC
-
-*** Drill
-
-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 article,
-[[http://augmentingcognition.com/ltm.html]["Augmenting Long-term Memory"]].
-
-#+BEGIN_SRC elisp
- :init
- (setq org-drill-scope 'directory)
-#+END_SRC
-
-Use the SM-2 algorithm rather than the default SM-5 algorithm. I have faith in
-[[https://apps.ankiweb.net/docs/manual.html#what-about-sm-5][Anki's analysis of SM-5]] in the form that it is available.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-drill-spaced-repetition-algorithm 'sm2)
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
- :init
- (setq org-drill-forgetting-index 100)
-#+END_SRC
-
-Set up a keybinding for getting quickly starting a drill session.
-
-#+BEGIN_SRC elisp
- :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)))))
-#+END_SRC
-
-=org-drill= in upstream org is unmaintained, and as described [[https://bitbucket.org/eeeickythump/org-drill/issues/62/org-drill-doesnt-work-with-org-mode-92][here]], no longer
-works with the latest version of Org. I'm using [[https://bitbucket.org/eeeickythump/org-drill/issues/63/maintainership][this fork]].
-
-#+BEGIN_SRC elisp
-(use-package org-drill
- :straight (org-drill :type git :host github :repo "hakanserce/org-drill")
- :init
- (with-eval-after-load 'org-drill
- (defun org-drill--show-latex-fragments ()
- (org-latex-preview '(16)))))
-#+END_SRC
-
-*** Attachments
-
-Use the incredible =org-download= package to quickly pull images into Org
-documents, and configure it to use =scrot= and the =org-attach= machinery.
-
-#+BEGIN_SRC elisp
-(use-package org-download
- :init
- (setq org-download-screenshot-method "scrot -s %s")
- (setq org-download-method 'attach))
-#+END_SRC
-
-*** Export
-
-Include syntax highlighting when exporting as HTML.
-
-#+BEGIN_SRC elisp
-(use-package htmlize
- :config
- (setq org-html-htmlize-output-type 'css))
-#+END_SRC
-
-** LaTeX
-
-Enable parsing as described in the [[elisp:(info "(AUCTeX) Quick Start")][Quick Start]] AUCTeX manual.
-
-#+BEGIN_SRC elisp
-(use-package tex
- :straight auctex
- :init
- (setq TeX-auto-save t TeX-parse-self t)
- (setq-default TeX-master nil)
-#+END_SRC
-
-Use [[PDF][pdf-tools]] to read TeX output.
-
-#+BEGIN_SRC elisp
- :init
- (setq TeX-view-program-selection '((output-pdf "PDF Tools")))
-#+END_SRC
-
-Enable Flyspell and SmartParens, as writing LaTeX involves numerous matching
-pairs ($, {, etc).
-
-#+BEGIN_SRC elisp
- :hook
- (TeX-mode . flyspell-mode)
- (TeX-mode . smartparens-mode))
-#+END_SRC
-
-** Markdown
-
-#+BEGIN_SRC elisp
-(use-package markdown-mode
- :mode
- (("\\.md\\'" . markdown-mode)
- ("\\.markdown\\'" . markdown-mode))
-
- :init
- (setq markdown-command "markdown_py"))
-#+END_SRC
-
-** 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.
-
-#+BEGIN_SRC elisp
-(use-package pdf-tools
- :demand t
- :config
- (pdf-loader-install)
-#+END_SRC
-
-Use isearch instead of [[Ivy][Swiper]], as it doesn't work in pdf-view-mode.
-
-#+BEGIN_SRC elisp
- :bind
- (:map pdf-view-mode-map
- ("C-r" . isearch-backward)
- ("C-s" . isearch-forward)))
-#+END_SRC
-
-* IRC
-
-Functions for connecting to and authenticating with my IRC bouncer.
-
-#+BEGIN_SRC elisp
-(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)))))
-#+END_SRC
-
-Functions for connecting to and authenticating with an instance of Bitlbee.
-
-#+BEGIN_SRC elisp
-(defun bitlbee-connect ()
- "Start bitlbee as an inferior process and connect to it."
- (bitlbee-start)
- (erc :server "localhost" :port 6667)
- (erc-with-buffer ((get-buffer "localhost:6667")
- (rename-buffer "bitlbee"))))
-
-(defun bitlbee-identify ()
- "If we're on the bitlbee server, send the identify command to the
- &bitlbee channel."
- (when (and (string= "localhost" erc-session-server)
- (string= "&bitlbee" (buffer-name)))
- (erc-message "PRIVMSG" (format "%s identify %s"
- (erc-default-target)
- (read-bouncer-password)))))
-#+END_SRC
-
-Interactive function for setting everything up, which involves starting an
-inferior Bitlbee process, connecting to it, and then connecting to all of the
-networks my bouncer is connected to.
-
-#+BEGIN_SRC elisp
-(defvar irc-autoconnect-networks '("freenode" "tildetown")
- "List of networks to automatically connect to when 'irc' is invoked.")
-
-(defun irc ()
- "Connect to Bitlbee and every network in `irc-autoconnect-networks'."
- (interactive)
- (dolist (network irc-autoconnect-networks)
- (if (string= network "bitlbee")
- (bitlbee-connect)
- (connect-bouncer network))))
-
-(defun irc-quit ()
- "Disconnects from and kills all buffers associated with ERC,
-and ends the Bitlbee process."
- (interactive)
- (bitlbee-stop)
- (let ((server-buffers (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))))))
-#+END_SRC
-
-Only load the modules I actually use. This list deviates from the default by
-including =truncate=, but omitting =autojoin=.
-
-#+BEGIN_SRC elisp
-(use-package erc
- :straight nil
- :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)
-#+END_SRC
-
-Configure personal details.
-
-#+BEGIN_SRC elisp
- :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."))))
-#+END_SRC
-
-Display the current nick in the prompt.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-prompt (lambda () (format "%s>" (erc-current-nick))))
-#+END_SRC
-
-Center everything.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-fill-function 'erc-fill-static
- erc-fill-column 100
- erc-fill-static-center 22)
-#+END_SRC
-
-Enable MIRC and control sequences so I can partake in figlet spam, objectively
-the best part of the IRC experience.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-interpret-controls-p t
- erc-interpret-mirc-color t)
-#+END_SRC
-
-Enable timestamps, and show them to the left of the messages.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-hide-timestamps nil
- erc-insert-timestamp-function 'erc-insert-timestamp-left)
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-enable-logging nil
- erc-save-buffer-on-part nil)
-#+END_SRC
-
-Similarly, =autowaway= is omitted from =erc-modules=, but I'm disabling it anyway.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-auto-set-away nil
- erc-auto-discard-away nil)
-#+END_SRC
-
-Kill buffers when I disconnect.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-kill-buffer-on-part t
- erc-kill-queries-on-quit t
- erc-kill-server-buffer-on-quit t)
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-auto-query 'bury
- erc-join-buffer 'bury)
-#+END_SRC
-
-Show CTCP requests. Not for any reason in particular, I just like seeing when
-people ping me.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-paranoid t)
-#+END_SRC
-
-Hide JOIN/PART/QUIT messages from idlers.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-lurker-hide-list '("JOIN" "PART" "QUIT")
- erc-lurker-threshold-time 3600)
-#+END_SRC
-
-Configure =erc-track= to ignore the less interesting message types.
-
-#+BEGIN_SRC elisp
- :init
- (setq erc-track-exclude-server-buffer t
- erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE"
- "324" "329" "332" "333" "353" "477"))
-#+END_SRC
-
-Enable channel tracking.
-
-#+BEGIN_SRC elisp
- :config
- (erc-track-minor-mode 1)
- (erc-track-mode 1)
-#+END_SRC
-
-Move to the prompt as soon as I begin typing. I've configured the buffer text
-preceding it to be read-only, anyway.
-
-#+BEGIN_SRC elisp
- :config
- (erc-move-to-prompt-enable)
-#+END_SRC
-
-Bind a key for resetting =erc-track=, since I tend to get absolutely bombarded
-upon reconnecting.
-
-#+BEGIN_SRC elisp
- :bind
- (:map erc-mode-map
- ("C-c f" . (lambda ()
- (interactive)
- (setq erc-modified-channels-alist nil)
- (erc-modified-channels-update))))
-#+END_SRC
-
-Let =erc-fill= handle filling.
-
-#+BEGIN_SRC elisp
- :hook
- (erc-mode . (lambda () (auto-fill-mode 0)))
-#+END_SRC
-
-Enable a hook for automatically identifying with Bitlbee.
-
-#+BEGIN_SRC elisp
- :hook
- (erc-join . bitlbee-identify))
-#+END_SRC
-
-Enable a hook for showing a pop-up alert upon being highlighted, or receiving a
-private message.
-
-#+BEGIN_SRC elisp
-(use-package ercn
- :after erc
- :config
- (add-hook 'ercn-notify 'dunst-alert))
-#+END_SRC
-
-Configure =erc-hl-nicks= to show names using pretty colors.
-
-#+BEGIN_SRC elisp
-(use-package erc-hl-nicks
- :after erc
- :config
- (erc-hl-nicks-enable))
-#+END_SRC
-
-Configure the =bitlbee= package for starting an inferior Bitlbee process.
-
-#+BEGIN_SRC elisp
-(use-package bitlbee
- :init
- (setq bitlbee-user-directory "~/.config/bitlbee/"))
-#+END_SRC
-
-Display a bar delimiting read and unread messages. Adapted from [[https://www.emacswiki.org/emacs/ErcBar][here]].
-
-#+BEGIN_SRC elisp
-(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
- (end-of-buffer)
- (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))))
-#+END_SRC
-
-Display the bar as a highlight on the most recently unread message.
-
-#+BEGIN_SRC elisp
-(with-palette
- (setq erc-bar-overlay (make-overlay 0 0))
- (overlay-put erc-bar-overlay 'face `(:background ,black-2)))
-#+END_SRC
-
-* News
-
-Stop Gnus from making a mess of my home directory.
-
-#+BEGIN_SRC elisp
-(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")
-#+END_SRC
-
-Sort articles such that newer items are at the top of the summary buffer.
-
-#+BEGIN_SRC elisp
-(setq gnus-thread-sort-functions '(gnus-thread-sort-by-most-recent-date))
-#+END_SRC
-
-Enable Flyspell so I don't make a fool of myself when composing messages.
-
-#+BEGIN_SRC elisp
-(add-hook 'message-mode-hook #'flyspell-mode)
-#+END_SRC
-
-** HTML Emails
-
-Use w3m to display HTML emails. Adapted from [[https://norman.walsh.name/2011/03/10/w3m][this blog post]].
-
-#+BEGIN_SRC elisp
-(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))
-#+END_SRC
-
-Encrypt emails such that we can read them as well.
-
-#+BEGIN_SRC elisp
-(setq mml-secure-openpgp-encrypt-to-self t)
-#+END_SRC
-
-** GPG
-
-Allow =gpg-agent= to query Emacs for my passphrase.
-
-#+BEGIN_SRC elisp
-(use-package pinentry
- :demand t
- :config
- (setenv "INSIDE_EMACS" (format "%s,comint" emacs-version))
- (pinentry-start))
-#+END_SRC
-
-* Games
-
-** Speed Typing
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
-(use-package speed-type
- :init
- (setq speed-type-gb-dir "~/.cache/emacs/speed-type/"))
-#+END_SRC
-
-* Tunes
-
-Use a more sane prefix key for interacting with =mpdel=.
-
-#+BEGIN_SRC elisp
-(use-package mpdel
- :demand t
- :init
- (setq mpdel-prefix-key (kbd "C-c z"))
-#+END_SRC
-
-Start =mpdel-mode=. Eh, why stop there? We can start the daemon from here, too.
-
-#+BEGIN_SRC elisp
- :config
- (shell-command "ps -e | grep -q mpd || mpd")
- (mpdel-mode))
-#+END_SRC
-
-* Programming Tools
-
-** 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.
-
-#+BEGIN_SRC elisp
-(use-package dumb-jump
- :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))
-#+END_SRC
-
-** company
-
-Start suggesting completions (almost) as soon as possible.
-
-#+BEGIN_SRC elisp
-(use-package company
- :config
- (setq company-idle-delay 0.1
- company-minimum-prefix-length 3)
-#+END_SRC
-
-Use standard movement keys for selecting entries from the completion popup
-instead of those used for minibuffer next/previous.
-
-#+BEGIN_SRC elisp
- :bind
- (:map company-active-map
- ("M-n" . nil)
- ("M-p" . nil)
- ("C-n" . company-select-next)
- ("C-p" . company-select-previous))
-#+END_SRC
-
-And have ~tab~ responsible for completion rather than ~ret~, so that company isn't
-quite as in-the-way.
-
-#+BEGIN_SRC elisp
- :bind
- (:map company-active-map
- ("TAB" . company-complete-selection)
- ("<tab>" . company-complete-selection)
- ("RET" . nil)
- ("<return>" . nil)))
-#+END_SRC
-
-** compile
-
-Bind a key to calling =compile=.
-
-#+BEGIN_SRC elisp
-(use-package compile
- :straight nil
-
- :bind
- ("<f12>" . compile)
-#+END_SRC
-
-Taken from [[https://www.emacswiki.org/emacs/CompileCommand][EmacsWiki]]. 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.
-
-#+BEGIN_SRC elisp
- :init
- (setq compilation-read-command nil)
-#+END_SRC
-
-Hide the compilation buffer upon successful compilation. I typically don't care
-about the output unless there were errors.
-
-#+BEGIN_SRC elisp
- :config
- (add-hook 'compilation-finish-functions
- (lambda (buf strg)
- (let ((win (get-buffer-window buf 'visible)))
- (when win (delete-window win))))))
-#+END_SRC
-
-** Flycheck
-
-#+BEGIN_SRC elisp
-(use-package flycheck)
-#+END_SRC
-
-** Magit
-
-Set up key bindings for getting to the Magit status buffer.
-
-#+BEGIN_SRC elisp
-(use-package magit
- :bind
- ("C-c g" . magit-status))
-#+END_SRC
-
-** Projectile
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
-(use-package projectile
- :demand t
- :init
- (setq projectile-known-projects-file "~/.cache/emacs/projectile-bookmarks.eld"
- projectile-track-known-projects-automatically nil)
-#+END_SRC
-
-Use [[Ivy]] for Projectile commands instead of =ido=.
-
-#+BEGIN_SRC elisp
- :init
- (setq projectile-completion-system 'ivy)
-#+END_SRC
-
-Set up key bindings for Projectile.
-
-#+BEGIN_SRC elisp
- :bind
- ("C-c p" . projectile-command-map)
-#+END_SRC
-
-Enable Projectile globally.
-
-#+BEGIN_SRC elisp
- :config
- (projectile-mode +1))
-#+END_SRC
-
-** Smartparens
-
-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 the default Smartparens configuration as a base.
-
-#+BEGIN_SRC elisp
-(use-package smartparens
- :config
- (require 'smartparens-config)
-#+END_SRC
-
-Enable the Smartparens-flavored replacement for =show-paren-mode=.
-
-#+BEGIN_SRC elisp
- :config
- (show-smartparens-global-mode t)
-#+END_SRC
-
-Enable Smartparens in all modes derived from =prog-mode= or =comint-mode=, the
-latter hopefully covering all REPL cases.
-
-#+BEGIN_SRC elisp
- :hook
- (prog-mode . turn-on-smartparens-mode)
- (comint-mode . turn-on-smartparens-mode)
-#+END_SRC
-
-Don't treat '`' as a pair marker when editing [[*Myrddin][Myrddin]] source code.
-
-#+BEGIN_SRC elisp
- :config
- (sp-local-pair 'myrddin-mode "`" nil :actions nil)
-#+END_SRC
-
-Rebind the default keys for manipulating sexps to the alternatives suggested in
-[[https://ebzzry.io/en/emacs-pairs/][Emacs and Pairs]].
-
-#+BEGIN_SRC elisp
- :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))
-#+END_SRC
-
-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".
-
-#+BEGIN_SRC elisp
- :bind
- (:map smartparens-mode-map
- ("C-M-o" . sp-up-sexp)
- ("C-M-i" . sp-backward-down-sexp)))
-#+END_SRC
-
-** annotate.el
-
-Preference of hygene. See [[Filesystem]]. Not strictly a tool for programming, but
-that's typically my use-case.
-
-#+BEGIN_SRC elisp
-(use-package annotate
- :init
- (setq annotate-file "~/.emacs.d/annotations"))
-#+END_SRC
-
-* Programming Languages
-
-** Language Server Protocol
-
-The only reason I'm using =lsp-ui= is for integration with [[Flycheck]]. The features
-like lenses don't interest me.
-
-#+BEGIN_SRC elisp
-(use-package lsp-ui
- :init
- (setq lsp-ui-sideline-enable nil
- lsp-ui-peek-enable nil
- lsp-ui-doc-enable nil))
-#+END_SRC
-
-Disable =lsp-mode='s default machinery. The reason I'm using the package in the
-first place is for its ability to integrate with everything I've set up in
-[[Programming Tools]].
-
-#+BEGIN_SRC elisp
-(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)
-#+END_SRC
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
- :init
- (setq lsp-session-file "~/.cache/emacs/.lsp-session-v1"
- lsp-auto-guess-root t)
-#+END_SRC
-
-Replace =xref= keybindings instead of using the default ones.
-
-#+BEGIN_SRC elisp
- :bind
- (:map lsp-mode-map
- ("M-." . lsp-find-definition)
- ("M-," . pop-tag-mark)))
-#+END_SRC
-
-** Rust
-
-Enable flycheck.
-
-#+BEGIN_SRC elisp
-(use-package flycheck-rust
- :after rust-mode
-
- :hook
- (flycheck-mode . flycheck-rust-setup)
- (rust-mode . flycheck-mode))
-#+END_SRC
-
-Enable [[company]], ElDoc, and racer.
-
-#+BEGIN_SRC elisp
-(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)
-#+END_SRC
-
-Run =rustfmt= automatically.
-
-#+BEGIN_SRC elisp
- :init
- (setq rust-format-on-save t))
-#+END_SRC
-
-Enable keybindings for working with Cargo.
-
-#+BEGIN_SRC elisp
-(use-package cargo
- :hook (rust-mode . cargo-minor-mode))
-#+END_SRC
-
-** C
-
-Generally, I adhere to the [[https://www.kernel.org/doc/html/v4.10/process/coding-style.html][Linux kernel coding style]].
-
-#+BEGIN_SRC elisp
-(use-package cc-mode
- :straight nil
- :hook
- (c-mode . (lambda ()
- (setq indent-tabs-mode t)
- (c-set-style "linux")))
-#+END_SRC
-
-Enable [[company]], [[Flycheck]], and ElDoc, all provided by =irony-mode=.
-
-#+BEGIN_SRC elisp
- :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))
-#+END_SRC
-
-** C++
-
-Even though the [[https://www.kernel.org/doc/html/v4.10/process/coding-style.html][Linux kernel coding style]] was written to address [[C]], I feel its
-viewpoints on indentation apply equally as well to C++.
-
-#+BEGIN_SRC elisp
-(use-package cc-mode
- :straight nil
- :hook
- (c++-mode . (lambda ()
- (setq indent-tabs-mode t)
- (c-set-style "linux")))
-#+END_SRC
-
-Enable [[company]], [[Flycheck]], and ElDoc, all provided by =irony-mode=.
-
-#+BEGIN_SRC elisp
- :hook
- (c++-mode . company-mode)
- (c++-mode . flycheck-mode)
- (c++-mode . eldoc-mode))
-
-(use-package irony
- :hook
- (c++-mode . irony-mode))
-#+END_SRC
-
-** Myrddin
-
-Indent with tabs.
-
-#+BEGIN_SRC elisp
-(use-package myrddin-mode
- :hook (myrddin-mode . (lambda () (setq-local indent-tabs-mode t))))
-#+END_SRC
-
-** Emacs Lisp
-
-Enable [[company]] for both =lisp-interaction-mode= and =emacs-lisp-mode=.
-
-#+BEGIN_SRC elisp
-(use-package elisp-mode
- :straight nil
- :hook
- (lisp-interaction-mode . company-mode)
- (emacs-lisp-mode . company-mode)
-#+END_SRC
-
-Avoid omitting information when displaying deeply-nested lists.
-
-#+BEGIN_SRC elisp
- :init
- (setq eval-expression-print-level nil))
-#+END_SRC
-
-Helpful is a wonderful improvement upon the vanilla =describe-.*= functions,
-capable of showing source code, keybindings, symbol properties, etc.n
-
-#+BEGIN_SRC elisp
-(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))
-#+END_SRC
-
-** Common Lisp
-
-SLIME is my preferred REPL for working with Common Lisp, and SBCL is my
-preferred implementation.
-
-#+BEGIN_SRC elisp
-(use-package slime
- :config
- (setq inferior-lisp-program "sbcl")
-#+END_SRC
-
-#+BEGIN_SRC elisp
-:hook
-(slime-repl-mode . (lambda ()
- (smartparens-mode +1)
- (company-mode +1)))
-#+END_SRC
-
-Set up [[company]].
-
-#+BEGIN_SRC elisp
- :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))
-#+END_SRC
-
-** Scheme
-
-Geiser is my preferred REPL for working with Common Lisp.
-
-Set up Geiser to use CHICKEN.
-
-#+BEGIN_SRC elisp
-(use-package geiser
- :init
- (setq geiser-active-implementations '(chicken guile racket))
- (setq geiser-default-implementation 'guile)
- (setenv "CHICKEN_DOC_REPOSITORY" "/home/jakob/.local/share/chicken/doc/")
-#+END_SRC
-
-Preference of hygene. See [[Filesystem]].
-
-#+BEGIN_SRC elisp
- :init
- (setq geiser-repl-history-filename "~/.cache/emacs/geiser_history"))
-#+END_SRC
-
-** Python
-
-Set up [[company]].
-
-#+BEGIN_SRC elisp
-(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))
-#+END_SRC
-
-** Hy
-
-Set up [[company]].
-
-#+BEGIN_SRC elisp
-(use-package hy-mode
- :hook
- (hy-mode . company-mode)
- (hy-mode . jedi-mode)
-#+END_SRC
-
-=hy-mode= doesn't create an autoload for the command to start a Hy REPL, so we'll
-declare it here. Chiefly, this lets me =m-x run-hy= before =hy-mode= is loaded.
-
-#+BEGIN_SRC elisp
- :commands run-hy
-#+END_SRC
-
-Enable =hy-mode= in files ending with ".hy"
-
-#+BEGIN_SRC elisp
- :mode "\\.hy\\'")
-#+END_SRC
-
-** Javascript
-
-Set up =js-mode= to interact with a [[Language Server Protocol]] daemon.
-
-#+BEGIN_SRC elisp
-(use-package js
- :straight nil
- :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))
-#+END_SRC
-
-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.
-
-#+BEGIN_SRC elisp
- :init
- (setq-default js-indent-level 2))
-#+END_SRC

© 2015 - 2026 Jakob L. Kreuze