diff options
| author | Jakob L. Kreuze | 2020-06-01 16:53:50 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze | 2020-06-01 16:53:50 -0400 |
| commit | 6c7f0b8ae2de30a808901b16b0d462fe833ea1b4 (patch) | |
| tree | 2cc0a3e9022fc43c39e5b91f4e7832b5387636e9 | |
| parent | 4d4f759881e310818055c31f286158da9db60d1c (diff) | |
Break config into files.
| -rw-r--r-- | init.el | 2000 | ||||
| -rw-r--r-- | lisp/appearance.el | 85 | ||||
| -rw-r--r-- | lisp/appearance/modeline.el | 87 | ||||
| -rw-r--r-- | lisp/appearance/theme.el | 251 | ||||
| -rw-r--r-- | lisp/core.el | 196 | ||||
| -rw-r--r-- | lisp/documents.el | 333 | ||||
| -rw-r--r-- | lisp/irc.el | 214 | ||||
| -rw-r--r-- | lisp/jakob.el | 81 | ||||
| -rw-r--r-- | lisp/languages/javascript.el | 23 | ||||
| -rw-r--r-- | lisp/news.el | 44 | ||||
| -rw-r--r-- | lisp/programming.el | 157 | ||||
| -rw-r--r-- | lisp/programming/c++.el | 29 | ||||
| -rw-r--r-- | lisp/programming/c.el | 42 | ||||
| -rw-r--r-- | lisp/programming/common-lisp.el | 32 | ||||
| -rw-r--r-- | lisp/programming/elisp.el | 30 | ||||
| -rw-r--r-- | lisp/programming/javascript.el | 26 | ||||
| -rw-r--r-- | lisp/programming/python.el | 24 | ||||
| -rw-r--r-- | lisp/programming/rust.el | 40 | ||||
| -rw-r--r-- | lisp/programming/scheme.el | 21 | ||||
| -rw-r--r-- | lisp/quality-of-life.el | 86 | ||||
| -rw-r--r-- | lisp/system.el | 68 | ||||
| -rw-r--r-- | lisp/tunes.el | 20 | ||||
| -rw-r--r-- | lisp/window-manager.el | 183 |
23 files changed, 2114 insertions, 1958 deletions
@@ -92,1967 +92,51 @@ ;;; 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 -;;; - -;; [HACK] Use the correct `emacsclient' binary on Gentoo. -(when (cl-search "28" (emacs-version)) - (setq with-editor-emacsclient-executable "emacsclient-emacs-28-vcs")) - -;; 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 :extend t)))) - `(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]))) - -(enable-theme 'chayote) - - -;;; -;;; 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)))) - -(defun remove-unicode (s) - "Remove non-ASCII or non-printable ASCII characters from `S'." - (apply #'string (seq-filter #'(lambda (c) (and (>= c ? ) (<= c ?~))) s))) - -(setq-default mode-line-format - '((:eval - (mode-line-render - (cond ((eq major-mode 'exwm-mode) - (concat "%b: " (remove-unicode 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 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) - (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-0") - '(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)) - -(use-package dired - :straight nil - ;; Hide the targets of symbolic links in `dired' listings. This is a visual - ;; preference of mine. - :init - (setq-default dired-listing-switches "-alL") - - ;; Show '.eln' files the same way that '.elc' files are. - :config - (push ".eln" completion-ignored-extensions)) -(use-package dired-rmjunk - ;; Set up a keybinding for invoking `dired-rmjunk' from `dired-mode'. - :bind - (:map dired-mode-map - ("r" . dired-rmjunk)) +;;; Everything above is what I classify as the "early-load" part of my +;;; configuration. The rest of my configuration is broken into "modules", which +;;; I include into the init.el at macro expansion time. - ;; 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-preview-latex-image-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 was previously using this - ;; fork [2], but the maintainer has since begun working on another fork [3] - ;; which is where I am now. - ;; - ;; [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 - ;; [3]: https://gitlab.com/phillord/org-drill/-/blob/master/org-drill.el - :straight (org-drill :type git :host gitlab :repo "phillord/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) - - ;; 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)) - - -;;; -;;; 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)) - - -;;; -;;; Javascript -;;; +(defvar module-directory (expand-file-name "lisp" user-emacs-directory) + "Directory containing configuration 'modules'.") -(use-package js - :straight nil +(defmacro insert-code-from-file (path) + "Read the forms in the file at PATH into a progn." + (with-temp-buffer + (insert-file-contents path) + (goto-char (point-min)) + (let (forms (eof nil)) + (while (not eof) + (condition-case nil + (push (read (current-buffer)) forms) + (end-of-file (setq eof t)))) + `(progn ,@(reverse forms))))) - ;; 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)) +(defmacro load-module (name) + "Locate the module NAME and insert its contents as a progn." + (let* ((file-name (concat name ".el")) + (path (expand-file-name file-name module-directory))) + `(insert-code-from-file ,path))) -;; 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)) +(load-module "core") +(load-module "jakob") +(load-module "appearance") +(load-module "appearance/theme") +(load-module "appearance/modeline") +(load-module "quality-of-life") +(load-module "window-manager") +(load-module "system") +(load-module "documents") +(load-module "irc") +(load-module "news") +(load-module "tunes") +(load-module "programming") +(load-module "programming/rust") +(load-module "programming/c") +(load-module "programming/c++") +(load-module "programming/elisp") +(load-module "programming/common-lisp") +(load-module "programming/scheme") +(load-module "programming/python") +(load-module "programming/javascript") diff --git a/lisp/appearance.el b/lisp/appearance.el new file mode 100644 index 0000000..2d365f7 --- /dev/null +++ b/lisp/appearance.el @@ -0,0 +1,85 @@ +;;; appearance.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for making Emacs look pretty. +;;; + + + +;; 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)) diff --git a/lisp/appearance/modeline.el b/lisp/appearance/modeline.el new file mode 100644 index 0000000..296a4f5 --- /dev/null +++ b/lisp/appearance/modeline.el @@ -0,0 +1,87 @@ +;;; modeline.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; My custom modeline for Emacs, which seeks to fit the dual role of a +;;; functional status indicator for Emacs, as well as a "bar" in the sense of a +;;; traditional tiling window manager. +;;; + +;; 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)))) + +(defun remove-unicode (s) + "Remove non-ASCII or non-printable ASCII characters from `S'." + (apply #'string (seq-filter #'(lambda (c) (and (>= c ? ) (<= c ?~))) s))) + +(setq-default mode-line-format + '((:eval + (mode-line-render + (cond ((eq major-mode 'exwm-mode) + (concat "%b: " (remove-unicode 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))))))) diff --git a/lisp/appearance/theme.el b/lisp/appearance/theme.el new file mode 100644 index 0000000..10b5624 --- /dev/null +++ b/lisp/appearance/theme.el @@ -0,0 +1,251 @@ +;;; theme.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; 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 :extend t)))) + `(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]))) + +(enable-theme 'chayote) diff --git a/lisp/core.el b/lisp/core.el new file mode 100644 index 0000000..34f9817 --- /dev/null +++ b/lisp/core.el @@ -0,0 +1,196 @@ +;;; core.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for the "core" behavior of GNU Emacs. Generally, anything +;;; which does not involve a third-party package. +;;; + + + +;; [HACK] Use the correct `emacsclient' binary on Gentoo. +(when (cl-search "28" (emacs-version)) + (setq with-editor-emacsclient-executable "emacsclient-emacs-28-vcs")) + +;; 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) diff --git a/lisp/documents.el b/lisp/documents.el new file mode 100644 index 0000000..88d3ba2 --- /dev/null +++ b/lisp/documents.el @@ -0,0 +1,333 @@ +;;; documents.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of packages for working with various kinds of text documents. +;;; Spell-checking, and the like. +;;; + + + +;; Ensure that my Aspell "personal dictionary" is included when invoking ispell. + +(setq ispell-personal-dictionary "~/.aspell.en.pws") + + +;;; +;;; Org +;;; + +(use-package org + :straight org-plus-contrib + + ;; File system of hygene. + :init + (setq-default org-id-locations-file "~/.cache/emacs/org-id-locations" + org-preview-latex-image-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 was previously using this + ;; fork [2], but the maintainer has since begun working on another fork [3] + ;; which is where I am now. + ;; + ;; [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 + ;; [3]: https://gitlab.com/phillord/org-drill/-/blob/master/org-drill.el + :straight (org-drill :type git :host gitlab :repo "phillord/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)) diff --git a/lisp/irc.el b/lisp/irc.el new file mode 100644 index 0000000..670ceae --- /dev/null +++ b/lisp/irc.el @@ -0,0 +1,214 @@ +;;; irc.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of ERC, as well as additional custom defuns for making an +;;; enjoyable IRC experience within Emacs. +;;; + + + +;; 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))) diff --git a/lisp/jakob.el b/lisp/jakob.el new file mode 100644 index 0000000..f720d7e --- /dev/null +++ b/lisp/jakob.el @@ -0,0 +1,81 @@ +;;; jakob.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Custom elisp written by yours truly that is independent of any particular +;;; package. +;;; + + +;;; +;;; Configuration. +;;; + +;; 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) + + +;;; +;;; 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 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) + (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)))))) diff --git a/lisp/languages/javascript.el b/lisp/languages/javascript.el new file mode 100644 index 0000000..4ade3ff --- /dev/null +++ b/lisp/languages/javascript.el @@ -0,0 +1,23 @@ +;;; javascript.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ + + +(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)) diff --git a/lisp/news.el b/lisp/news.el new file mode 100644 index 0000000..df1cbf1 --- /dev/null +++ b/lisp/news.el @@ -0,0 +1,44 @@ +;;; news.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for using Emacs as a newsreader. +;;; + +;; 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) diff --git a/lisp/programming.el b/lisp/programming.el new file mode 100644 index 0000000..641a270 --- /dev/null +++ b/lisp/programming.el @@ -0,0 +1,157 @@ +;;; programming.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for "meta" packages which apply to all programming language +;;; development environments I have configured. +;;; + +(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) + + ;; 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))) diff --git a/lisp/programming/c++.el b/lisp/programming/c++.el new file mode 100644 index 0000000..5dca93b --- /dev/null +++ b/lisp/programming/c++.el @@ -0,0 +1,29 @@ +;;; c++.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the C++ programming language. +;;; + +(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)) diff --git a/lisp/programming/c.el b/lisp/programming/c.el new file mode 100644 index 0000000..6ba19e2 --- /dev/null +++ b/lisp/programming/c.el @@ -0,0 +1,42 @@ +;;; c.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the C programming language. +;;; + +(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)) diff --git a/lisp/programming/common-lisp.el b/lisp/programming/common-lisp.el new file mode 100644 index 0000000..b3aa00b --- /dev/null +++ b/lisp/programming/common-lisp.el @@ -0,0 +1,32 @@ +;;; common-lisp.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Common Lisp programming +;;; language. +;;; + +;; 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)) diff --git a/lisp/programming/elisp.el b/lisp/programming/elisp.el new file mode 100644 index 0000000..0b975eb --- /dev/null +++ b/lisp/programming/elisp.el @@ -0,0 +1,30 @@ +;;; elisp.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Emacs Lisp programming +;;; language. +;;; + +(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)) diff --git a/lisp/programming/javascript.el b/lisp/programming/javascript.el new file mode 100644 index 0000000..c74e6ac --- /dev/null +++ b/lisp/programming/javascript.el @@ -0,0 +1,26 @@ +;;; javascript.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Javascript programming +;;; language. +;;; + +(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)) diff --git a/lisp/programming/python.el b/lisp/programming/python.el new file mode 100644 index 0000000..a6c7fc4 --- /dev/null +++ b/lisp/programming/python.el @@ -0,0 +1,24 @@ +;;; python.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Python programming +;;; language. +;;; + +;; 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)) diff --git a/lisp/programming/rust.el b/lisp/programming/rust.el new file mode 100644 index 0000000..6e4e43b --- /dev/null +++ b/lisp/programming/rust.el @@ -0,0 +1,40 @@ +;;; rust.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Rust programming +;;; language. +;;; + +;; 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)) diff --git a/lisp/programming/scheme.el b/lisp/programming/scheme.el new file mode 100644 index 0000000..de9f380 --- /dev/null +++ b/lisp/programming/scheme.el @@ -0,0 +1,21 @@ +;;; scheme.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration of a development environment for the Scheme programming +;;; language. +;;; + +;; 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")) diff --git a/lisp/quality-of-life.el b/lisp/quality-of-life.el new file mode 100644 index 0000000..b7fbe98 --- /dev/null +++ b/lisp/quality-of-life.el @@ -0,0 +1,86 @@ +;;; quality-of-life.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for packages which I consider to be functional improvements to +;;; the quality of my daily Emacs usage. +;;; + + +;;; +;;; 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))) diff --git a/lisp/system.el b/lisp/system.el new file mode 100644 index 0000000..3c317c6 --- /dev/null +++ b/lisp/system.el @@ -0,0 +1,68 @@ +;;; system.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for packages which work at the operating system level +;;; (particularly the file system). +;;; + +;; Set up keybindings for calling 'ag'. + +(use-package ag + :bind + ("C-c k" . counsel-ag)) + +(use-package dired + :straight nil + ;; Hide the targets of symbolic links in `dired' listings. This is a visual + ;; preference of mine. + :init + (setq-default dired-listing-switches "-alL") + + ;; Show '.eln' files the same way that '.elc' files are. + :config + (push ".eln" completion-ignored-extensions)) + +(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"))) diff --git a/lisp/tunes.el b/lisp/tunes.el new file mode 100644 index 0000000..d85974a --- /dev/null +++ b/lisp/tunes.el @@ -0,0 +1,20 @@ +;;; tunes.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; Configuration for using Emacs as an MPD [1] client. +;;; +;;; [1]: https://www.musicpd.org/ +;;; + +(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)) diff --git a/lisp/window-manager.el b/lisp/window-manager.el new file mode 100644 index 0000000..fa332d4 --- /dev/null +++ b/lisp/window-manager.el @@ -0,0 +1,183 @@ +;;; window-manager.el -*- lexical-binding: t; -*- +;;; +;;; Author: Jakob L. Kreuze <zerodaysfordays@sdf.org> +;;; URL: https://git.sr.ht/~jakob/.emacs.d/ +;;; +;;; My configuration for the EXWM package [1]. Yes, I use Emacs as my window +;;; manager. You should, too! +;;; +;;; [1]: https://github.com/ch11ng/exwm +;;; + +(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-0") + '(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)) |