#+TITLE: Emacs configuration, literate-style #+AUTHOR: Case Duckworth #+STARTUP: overview #+PROPERTY: header-args :results output silent #+AUTO_TANGLE: yes * About me #+begin_src emacs-lisp :noweb-ref settings (setq user-full-name "Case Duckworth" user-mail-address "acdw@acdw.net") #+end_src ** Where I am #+begin_src emacs-lisp :noweb-ref settings (setq calendar-location-name "Baton Rouge, LA" calendar-latitude 30.4 calendar-longitude -91.1) #+end_src ** Auth-sources Here feels like as good a place as any to setup =auth-sources=. Yes, I /need/ to use GPG. I'll get around to it. Until then, /please/ don't break into my workplace and steal my secrets. #+begin_src emacs-lisp :noweb-ref settings (setq-default auth-sources '("~/.authinfo")) #+end_src * Look and feel ** Frames *** Initial frame setup :PROPERTIES: :header-args: :noweb-ref early-init-frame :END: I tangle this section to =early-init.el=, since that's evaluated before GUI set-up. Which, in turn, means Emacs will skip the "flash of unstyled content" thing. **** Tool bar #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(tool-bar-lines . 0)) (tool-bar-mode -1) #+end_src **** Menu bar #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(menu-bar-lines . 0)) (menu-bar-mode -1) #+end_src **** Scroll bars #+begin_src emacs-lisp (add-to-list 'default-frame-alist '(vertical-scroll-bars . nil) '(horizontal-scroll-bars . nil)) (scroll-bar-mode -1) (horizontal-scroll-bar-mode -1) #+end_src **** Resizing I don't want the frame to resize when I change fonts and stuff, and I want it to resize by pixels -- we /are/ using a GUI, after all. #+begin_src emacs-lisp (setq-default frame-inhibit-implied-resize t frame-resize-pixelwise t) #+end_src *** Frame titles #+begin_src emacs-lisp :noweb-ref settings (setq-default frame-title-format '("Emacs " mode-line-client mode-line-modified " " (:eval (if (buffer-file-name) (abbreviate-file-name (buffer-file-name)) "%b")) )) #+end_src *** Fringes :PROPERTIES: :header-args: :noweb-ref settings :END: I have grown to love Emacs's little fringes on the side of the windows. In fact, I love them so much that I really went overboard and have made a custom fringe bitmap. **** Indicate empty lines after the end of the buffer #+begin_src emacs-lisp (setq-default indicate-empty-lines t) #+end_src **** Indicate the boundaries of the buffer #+begin_src emacs-lisp (setq-default indicate-buffer-boundaries 'right) #+end_src **** Indicate continuation lines, but only on the left fringe #+begin_src emacs-lisp (setq-default visual-line-fringe-indicators '(left-curly-arrow nil)) #+end_src **** Customize fringe bitmaps ***** Curly arrows (continuation lines) #+begin_src emacs-lisp (define-fringe-bitmap 'left-curly-arrow [#b11000000 #b01100000 #b00110000 #b00011000]) (define-fringe-bitmap 'right-curly-arrow [#b00011000 #b00110000 #b01100000 #b11000000]) #+end_src ***** Arrows (truncation lines) #+begin_src emacs-lisp (define-fringe-bitmap 'left-arrow [#b00000000 #b01010100 #b01010100 #b00000000]) (define-fringe-bitmap 'right-arrow [#b00000000 #b00101010 #b00101010 #b00000000]) #+end_src ** Windows *** Window Dividers #+begin_src emacs-lisp :noweb-ref settings (setq-default window-divider-default-places 'right-only ; only right window-divider-default-bottom-width 2 window-divider-default-right-width 2) #+end_src #+begin_src emacs-lisp :noweb-ref modes (window-divider-mode +1) #+end_src *** Splitting windows sensibly This is extremely fiddly and I'd love another option. - [[https://www.emacswiki.org/emacs/ToggleWindowSplit][ToggleWindowSplit, EmacsWiki]] #+begin_src emacs-lisp :noweb-ref settings (setq-default split-width-threshold 100 split-height-threshold 50) #+end_src *** Window layouts Let's try settings from [[https://github.com/nex3/perspective-el#some-musings-on-emacs-window-layouts][nex3]] on Github. #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; `display-buffer-alist' is the big alist of things display-buffer-alist '((".*" (display-buffer-reuse-window display-buffer-same-window))) ;; reuse windows in other frames display-buffer-reuse-frames t ;; avoid resizing even-window-sizes nil) #+end_src *** Switch to other window or buffer :crux: #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "M-o" #'crux-other-window-or-switch-buffer) #+end_src *** The *Help* window I want to select the *Help* window by default, so I can easily quit it. #+begin_src emacs-lisp :noweb-ref settings (setq-default help-window-select t) #+end_src ** Buffers *** Uniquify buffers The default way Emacs makes buffer names unique is really ugly and, dare I say it, stupid. Instead, I want them to be uniquified by their filesystem paths. #+begin_src emacs-lisp :noweb-ref requires (require 'uniquify) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default uniquify-buffer-name-style 'forward uniquify-separator "/" uniquify-after-kill-buffer-p t uniquify-ignore-buffers-re "^\\*") #+end_src *** Startup buffers When Emacs starts up, I want a blank slate: the *scratch* buffer. I also want it to show a cute little message to myself. #+begin_src emacs-lisp :noweb-ref settings (setq-default inhibit-startup-screen t ; Don't show that splash screen thing. initial-buffer-choice t ; Start on *scratch* initial-scratch-message (concat ";; Howdy, " (nth 0 (split-string user-full-name)) "!" " Welcome to Emacs." "\n\n")) #+end_src *** Immortal =*scratch*= buffer I don't want to accidentally kill the *scratch* buffer. So, I add a function to the =kill-buffer-query-functions= hook that will return =nil= if the buffer is *scratch*. #+begin_src emacs-lisp :noweb-ref functions (defun immortal-scratch () (if (not (eq (current-buffer) (get-buffer "*scratch*"))) t (bury-buffer) nil)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'kill-buffer-query-functions #'immortal-scratch) #+end_src *** An /even better/ scratch buffer :package: The aptly-named =scratch= pops open a new scratch buffer /with the same mode as the file you're currently editing/. I'm pretty chuffed about it. I found it from [[https://old.reddit.com/r/emacs/comments/l4v1ux/one_of_the_most_useful_small_lisp_functions_in_my/][this discussion]], which might also come in handy someday. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'scratch) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-x" #'scratch :map acdw/leader) #+end_src *** Kill buffers better #+begin_src emacs-lisp :noweb-ref functions (defun kill-a-buffer (&optional prefix) "Kill a buffer and its window, prompting only on unsaved changes. `kill-a-buffer' uses the PREFIX argument to determine which buffer(s) to kill: 0 => Kill current buffer & window 4 (C-u) => Kill OTHER buffer & window 16 (C-u C-u) => Run `kill-buffer' without a prefix arg." (interactive "P") (pcase (or (car prefix) 0) (0 (kill-current-buffer) (unless (one-window-p) (delete-window))) (4 (other-window 1) (kill-current-buffer) (unless (one-window-p) (delete-window))) (16 (let ((current-prefix-arg nil)) (kill-buffer))))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-x k" #'kill-a-buffer) #+end_src *** Kill old buffers after a while Adapted from =midnight-mode=, using suggestions from [[https://old.reddit.com/r/emacs/comments/l6jpxf/how_do_emacs_users_usually_have_multiple_files/gl2249u/][u/ndamee]]. #+begin_src emacs-lisp :noweb-ref packages (require 'midnight) #+end_src What time I run the clean up is a little tricky for me, since I use Emacs at work /and/ at home, and all at different times. However, I realized that since I close out of Emacs at work pretty much every day, I don't need to worry about too many buffers there -- so I just have =clean-buffer-list= run at 8:00 PM. #+begin_src emacs-lisp :noweb-ref settings (setq-default acdw/clean-buffer-list-timer (run-at-time "20:00" 86400 #'clean-buffer-list) clean-buffer-list-delay-general 5 clean-buffer-list-delay-special (* 7 24 60 60)) (add-to-list 'clean-buffer-list-kill-buffer-names "*Completions*") (add-to-list 'clean-buffer-list-kill-buffer-names "*Calendar*") #+end_src ** Cursor *** Cursor shape I like a vertical bar, but only in the selected window. #+begin_src emacs-lisp :noweb-ref settings (setq-default cursor-type 'bar cursor-in-non-selected-windows nil) #+end_src *** Don't blink the cursor #+begin_src emacs-lisp :noweb-ref modes (blink-cursor-mode -1) #+end_src ** Tabs *** Tab bar mode settings #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-show 1 ; show the tab bar when more than 1 tab tab-bar-new-tab-choice "*scratch*" tab-bar-tab-name-function #'tab-bar-tab-name-current-with-count) #+end_src *** Tab bar history #+begin_src emacs-lisp :noweb-ref modes (tab-bar-history-mode +1) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-bar-history-limit 25) #+end_src ** Fonts On Linux, I have a custom build of Iosevka that I like. #+begin_src emacs-lisp :noweb-ref linux-specific (set-face-attribute 'default nil :family "Iosevka Acdw" :height 105) (set-face-attribute 'fixed-pitch nil :family "Iosevka Acdw" :height 105) #+end_src But on Windows, I use Consolas. #+begin_src emacs-lisp :noweb-ref windows-specific (set-face-attribute 'default nil :family "Consolas" :height 100) (set-face-attribute 'fixed-pitch nil :family "Consolas" :height 100) #+end_src *** Underlines I like the /fancy/ underlines in newer browsers that skip all the descenders. Emacs doesn't /quite/ have that, but it can put the underline below all the text. #+begin_src emacs-lisp :noweb-ref settings (setq-default x-underline-at-descent-line t) #+end_src *** Unicode fonts :package: =unicode-fonts= pulls in some other packages that still require the deprecated =cl= library. So, I've forked those libraries to require =cl-lib= instead. **** First: un-fuck =font-utils= and =list-utils= ... and =persistent-soft= ***** List-utils Since =font-utils= depends on =list-utils=, if I load the former first, it pulls in the unpatched latter. /So/ I need to do =list-utils= first. (=*straight-process*= is your friend, y'all!) Since =list-utils= requires =cl= in line 259 (see [[https://github.com/rolandwalker/list-utils/issues/6][this issue]], apparently just changing it breaks many tests, but I'll run with it until Emacs complains), I need to fork and change that to a =cl-lib=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(list-utils :host github :repo "rolandwalker/list-utils" :fork (:repo "duckwork/list-utils"))) #+end_src ***** Persistent-soft #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(persistent-soft :host github :repo "rolandwalker/persistent-soft" :fork (:repo "duckwork/persistent-soft"))) #+end_src ***** Font-utils I was able to actually create a [[https://github.com/rolandwalker/font-utils/pull/2][PR]] for this one, so fingers crossed. Since the last update on =font-utils= was in 2015, I'm not super hopeful that my fix will get merged upstream, but I'm using a =:fork= argument to stay hopeful. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(font-utils :host github :repo "rolandwalker/font-utils" :fork (:repo "duckwork/font-utils"))) #+end_src ***** A function in case it comes up again I keep googling [[https://github.com/hlissner/doom-emacs/issues/3372][this Doom Emacs issue]], because I keep forgetting what I need to do to see where =Package cl is deprecated= is coming from. So... function! #+begin_src emacs-lisp :noweb-ref functions ;; Make the compiler happy (autoload 'file-dependents "loadhist") (autoload 'feature-file "loadhist") (defun acdw/fucking-cl () "Find out where the fuck `cl' is being required from." (interactive) (require 'loadhist) (message "%S" (file-dependents (feature-file 'cl)))) #+end_src **** Unicode-fonts /Okay/ ... pull requests in, time to load =unicode-fonts=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(unicode-fonts :host github :repo "rolandwalker/unicode-fonts")) (require 'unicode-fonts) #+end_src According to [[https://github.com/rolandwalker/unicode-fonts/issues/3][Issue #3]], there can be problems with =unicode-fonts-setup= when using a daemon. Instead of forking this repo and merging [[https://github.com/rolandwalker/unicode-fonts/pull/4][PR #4]] into my personal fork, I'll use the workaround described in the issue. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--unicode-fonts-setup (frame) "Run `unicode-fonts-setup', then remove the hook." (select-frame frame) (unicode-fonts-setup) (remove-hook 'after-make-frame-functions #'hook--unicode-fonts-setup)) (add-hook 'after-make-frame-functions #'hook--unicode-fonts-setup) #+end_src *** Draw form-feeds (=^L=) properly :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'form-feed) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-form-feed-mode +1) (blackout 'form-feed-mode) #+end_src ** Theming *** Modus themes :package: I want the git version. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(modus-themes :host gitlab :repo "protesilaos/modus-themes")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default modus-themes-slanted-constructs t modus-themes-bold-constructs t modus-themes-region 'bg-only modus-themes-org-blocks 'grayscale modus-themes-headings '((1 . section) (t . no-color)) modus-themes-scale-headings nil modus-themes-mode-line nil) #+end_src *** Change themes based on time of day #+begin_src emacs-lisp :noweb-ref functions (defun acdw/run-with-sun (sunrise-command sunset-command) "Run commands at sunrise and sunset." (let* ((times-regex (rx (* nonl) (: (any ?s ?S) "unrise") " " (group (repeat 1 2 digit) ":" (repeat 1 2 digit) (: (any ?a ?A ?p ?P) (any ?m ?M))) (* nonl) (: (any ?s ?S) "unset") " " (group (repeat 1 2 digit) ":" (repeat 1 2 digit) (: (any ?a ?A ?p ?P) (any ?m ?M))) (* nonl))) (ss (sunrise-sunset)) (_m (string-match times-regex ss)) (sunrise-time (match-string 1 ss)) (sunset-time (match-string 2 ss))) (run-at-time sunrise-time (* 60 60 24) sunrise-command) (run-at-time sunset-time (* 60 60 24) sunset-command))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (acdw/run-with-sun #'modus-themes-load-operandi #'modus-themes-load-vivendi) #+end_src *** Mode line **** Minions mode :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'minions) (require 'minions) #+end_src #+begin_src emacs-lisp :noweb-ref modes (minions-mode +1) #+end_src **** Blackout some modes :package: Like =diminish= or =delight=, =blackout= allows me to remove some minor-modes from the modeline. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(blackout :host github :repo "raxod502/blackout")) #+end_src **** Which-function mode Shows where we are in the modeline. #+begin_src emacs-lisp :noweb-ref modes (which-function-mode +1) #+end_src **** Mode-line faces #+begin_src emacs-lisp :noweb-ref linux-specific (doremi-face-set 'mode-line '((t (:family "Terminus" :height 1.0)))) (doremi-face-set 'mode-line-inactive '((t (:family "Terminus" :height 1.0)))) #+end_src *** Setting faces It took me a while to find a function that'll let me customize faces /without/ using *customize*. Thanks to [[https://www.emacswiki.org/emacs/CustomizingFaces#toc5][Drew Adams]], I've got it! #+begin_src emacs-lisp :noweb-ref functions (defun doremi-face-set (face spec) "Tell Customize that FACE has been set to value SPEC. SPEC is as for `defface'." (put face 'customized-face spec) (face-spec-set face spec) (message "Customized face %s." (symbol-name face))) #+end_src * Interactivity ** Dialogs and alerts *** Don't use a dialog box Ask in the modeline instead. #+begin_src emacs-lisp :noweb-ref settings (setq-default use-dialog-box nil) #+end_src *** Yes or no questions I just want to type =y= or =n=, okay? #+begin_src emacs-lisp :noweb-ref functions (fset 'yes-or-no-p #'y-or-n-p) #+end_src *** The Bell The only system I /sort of/ like the bell on is my Thinkpad, which does a little on-board speaker beep. Until I can figure out how to let it do its thing, though, I'll just change the bell on all my systems. #+begin_src emacs-lisp :noweb-ref settings (setq-default visible-bell nil ring-bell-function #'flash-mode-line) #+end_src **** Flash the mode-line #+begin_src emacs-lisp :noweb-ref functions (defun flash-mode-line () (invert-face 'mode-line) (run-with-timer 0.2 nil #'invert-face 'mode-line)) #+end_src ** Minibuffer *** Keep the cursor away from the minibuffer prompt #+begin_src emacs-lisp :noweb-ref settings (setq-default minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) #+end_src *** Enable a recursive minibuffer #+begin_src emacs-lisp :noweb-ref (setq-default enable-recursive-minibuffers t) #+end_src *** Show the recursivity of the minibuffer in the mode-line #+begin_src emacs-lisp :noweb-ref modes (minibuffer-depth-indicate-mode +1) #+end_src ** Completing-read *** Shadow file names When typing =~= or =/= in the file-selection dialog, Emacs "pretends" that you've typed them at the beginning of the line. By default, however, it only /fades out/ the previous contents of the line. I want to /hide/ those contents. #+begin_src emacs-lisp :noweb-ref settings (setq-default file-name-shadow-properties '(invisible t)) #+end_src #+begin_src emacs-lisp :noweb-ref modes (file-name-shadow-mode +1) #+end_src *** Ignore case #+begin_src emacs-lisp :noweb-ref (setq-default completion-ignore-case t read-buffer-completion-ignore-case t read-file-name-completion-ignore-case t) #+end_src *** Icomplete #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'icomplete-vertical) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default completion-styles '(partial-completion substring flex) completion-category-overrides '((file (styles basic substring flex)))) (setq-default icomplete-delay-completions-threshold 0 icomplete-max-delay-chars 0 icomplete-compute-delay 0 icomplete-show-matches-on-no-input t icomplete-hide-common-prefix nil icomplete-with-completion-tables t icomplete-in-buffer t) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind-after-map 'icomplete icomplete-minibuffer-map (("" #'icomplete-forward-completions) ("C-n" #'icomplete-forward-completions) ("" #'icomplete-backward-completions) ("C-p" #'icomplete-backward-completions))) (acdw/bind "C-v" #'icomplete-vertical-toggle :after 'icomplete-vertical :map icomplete-minibuffer-map) #+end_src #+begin_src emacs-lisp :noweb-ref modes (fido-mode -1) ; I take care of this myself (icomplete-mode +1) (icomplete-vertical-mode +1) #+end_src *** Orderless :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'orderless) (require 'orderless) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default completion-styles '(orderless)) #+end_src *** Consult :package: #+begin_src emacs-lisp :noweb-ref modes (straight-use-package 'consult) (require 'consult) #+end_src Consult has a lot of great bindings that work well with Emacs's default completion system. These all come from the [[https://github.com/minad/consult#configuration][example configuration]]. #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind-after-map 'consult acdw/map ;; C-c bindings (`mode-specific-map') (("C-c h" #'consult-history) ("C-c m" #'consult-mode-command) ;; C-x bindings (`ctl-x-map') ("C-x M-:" #'consult-complex-command) ("C-x b" #'consult-buffer) ("C-x 4 b" #'consult-buffer-other-window) ("C-x 5 b" #'consult-buffer-other-frame) ("C-x r x" #'consult-register) ("C-x r b" #'consult-bookmark) ;; M-g bindings (`goto-map') ("M-g g" #'consult-line) ("M-g M-g" #'consult-line) ("M-g o" #'consult-outline) ("M-g m" #'consult-mark) ("M-g k" #'consult-global-mark) ("M-g i" #'consult-imenu) ("M-g e" #'consult-error) ;; M-s bindings (`search-map') ("M-s g" #'consult-grep) ; alts: ; consult-git-grep, ; consult-ripgrep ("M-s f" #'consult-find) ; alts: ; consult-locate ("M-s l" #'consult-line) ("M-s m" #'consult-multi-occur) ("M-s k" #'consult-keep-lines) ("M-s u" #'consult-focus-lines) ;; Other bindings ("M-y" #'consult-yank-pop) (" a" #'consult-apropos) ("C-h a" #'consult-apropos))) #+end_src #+begin_src emacs-lisp :noweb-ref settings (autoload 'consult-register-preview "consult") ; make the compiler happy (setq-default register-preview-delay 0 register-preview-function #'consult-register-preview) #+end_src *** Marginalia :package: Finally, =marginalia= provides extra information about completion candidates. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'marginalia) (require 'marginalia) #+end_src #+begin_src emacs-lisp :noweb-ref modes (marginalia-mode +1) #+end_src I like the rich annotations provided by =marginalia=. #+begin_src emacs-lisp :noweb-ref settings (setq-default marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil)) #+end_src ** Imenu #+begin_src emacs-lisp :noweb-ref settings (setq-default imenu-auto-rescan t) #+end_src ** Completion *** Hippie Expand Before I install any completion framework, I want a good default for completing. =hippie-expand= fills that niche. #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "M-/" #'hippie-expand) #+end_src ** Bindings *** Acdw Mode I've decided to set up a custom minor mode for my keybindings, as suggested in [[https://github.com/larstvei/dot-emacs#key-bindings][Lars Tvei]]'s config, so that I can override all other modes with my own keybindings. Plus I can easily turn it off and back on as I please. #+begin_src emacs-lisp :noweb-ref acdw-mode (defvar acdw/map (make-sparse-keymap) "A keymap for my custom bindings.") (define-minor-mode acdw/mode "A mode for `acdw/map'." :init-value t :lighter " acdw" :keymap acdw/map) (define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode) #+end_src #+begin_src emacs-lisp :noweb-ref modes (blackout 'acdw/mode) #+end_src *** =acdw/bind= macro Since defining keys can be a chore, I've written this macro to make it just a /little/ bit easier. It's /not/ as comprehensive as =bind-key=, but it's just a little sugar on top of =define-key= et al. #+begin_src emacs-lisp :noweb-ref functions (defmacro acdw/bind (key def &rest args) "A simple key-binding macro that takes care of the annoying stuff. If KEY is a vector, it's passed directly to `define-key', otherwise it wraps it in `kbd'. It does NOT quote any definitions, because I like to keep those explicit in the definitions. The following keywords are recognized: :after PACKAGE-OR-FEATURE .. wrap key definition in `with-eval-after-load' :map KEYMAP .. define key in KEYMAP instead of `acdw/map'" (let* ((after (plist-get args :after)) (map (or (plist-get args :map) 'acdw/map)) (key (if (vectorp key) key `(kbd ,key))) (def-key `(define-key ,map ,key ,def))) (if after `(with-eval-after-load ,after ,def-key) def-key))) (defmacro acdw/bind-after-map (feature keymap bindings) "Wrap multiple calls to `acdw/bind' in a `with-eval-after-load' form. FEATURE is the argument to `with-eval-after-load'. KEYMAP is passed to the `:map' argument of `acdw/bind', if it's non-nil." (declare (indent 2)) (let (bind-list) (dolist (bind bindings bind-list) (if keymap (push `(acdw/bind ,@bind :map ,keymap) bind-list) (push `(acdw/bind ,@bind) bind-list))) `(with-eval-after-load ,feature ,@bind-list))) #+end_src **** Turn off acdw/mode in the minibuffer #+begin_src emacs-lisp :noweb-ref acdw-mode (defun acdw/mode--disable () "Turn off acdw/mode." (acdw/mode -1)) (add-hook 'minibuffer-setup-hook #'acdw/mode--disable) #+end_src **** Custom leader Since =C-z= is generally pretty useless in Emacs (minimize the window? really?), I rebind it to be a sort of personal leader key. I generally use it as a leader for entering applications. #+begin_src emacs-lisp :noweb-ref acdw-mode (defvar acdw/leader (let ((map (make-sparse-keymap)) (c-z (global-key-binding "\C-z"))) ;(global-unset-key "\C-z") (define-key acdw/map "\C-z" map) (define-key map "\C-z" c-z) map)) ;; Just in case I want to run hooks after defining the leader map (run-hooks 'acdw/leader-defined-hook) #+end_src *** Show keybindings with =which-key= :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'which-key) #+end_src #+begin_src emacs-lisp :noweb-ref modes (which-key-mode +1) (blackout 'which-key-mode) #+end_src ** Scrolling *** Fast scrolling According to [[https://github.com/mpereira/.emacs.d#make-cursor-movement-an-order-of-magnitude-faster][Murilo Pereira]], these settings will make Emacs scrolling "an order of magnitude faster." #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-window-vscroll nil fast-but-imprecise-scrolling t) #+end_src *** Scroll margins #+begin_src emacs-lisp :noweb-ref settings (setq-default scroll-margin 0 scroll-conservatively 101 ; if greater than 100 ... scroll-preserve-screen-position 1) #+end_src ** Enable commands I think the /disabled command/ feature of Emacs is stupid, especially for a program that values freedom so much. #+begin_src emacs-lisp :noweb-ref settings (setq-default disabled-command-function nil) #+end_src ** CRUX :package:crux: A collection of generally-useful functions that I don't want to bother including here myself. This is kind of an experiment, to be honest. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(crux :host github :repo "bbatsov/crux")) (require 'crux) #+end_src A note: I /don't/ do the same with [[https://github.com/alphapapa/unpackaged.el][unpackaged]] (see below, specifically the *Org* sections) because it pulls in =hydra= and =use-package=, et al. * Persistence ** Minibuffer history The =savehist= package saves minibuffer history between sessions, as well as the option for some other variables. Since storage is cheap, I keep all of it. #+begin_src emacs-lisp :noweb-ref requires (require 'savehist) #+end_src #+begin_src emacs-lisp :noweb-ref modes (setq-default savehist-additional-variables '(kill-ring search-ring regexp-search-ring) history-length t ; Don't truncate history-delete-duplicates t savehist-autosave-interval 60) #+end_src #+begin_src emacs-lisp :noweb-ref modes (savehist-mode +1) #+end_src ** File places The =saveplace= package saves where I've been in my visited files. #+begin_src emacs-lisp :noweb-ref requires (require 'saveplace) #+end_src Since storage is cheap, but I'm impatient -- especially on Windows -- I'm not going to check whether the files =save-place= saves the places of are readable or not when I'm not at home. #+begin_src emacs-lisp :noweb-ref settings (setq-default save-place-forget-unreadable-files (memq system-type '(gnu gnu/linux gnu/kfreebsd))) #+end_src #+begin_src emacs-lisp :noweb-ref modes (save-place-mode +1) #+end_src ** Recent files I also like to keep track of recently-opened files. =recentf= helps with that. #+begin_src emacs-lisp :noweb-ref requires (require 'recentf) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default recentf-max-menu-items 100 recentf-max-saved-items nil recentf-auto-cleanup 'never) #+end_src #+begin_src emacs-lisp :noweb-ref modes (recentf-mode +1) #+end_src I also want to ignore the =no-littering-var-directory= and =no-littering-etc-directory=, since those aren't useful. #+begin_src emacs-lisp :noweb-ref no-littering (add-to-list 'recentf-exclude no-littering-var-directory) (add-to-list 'recentf-exclude no-littering-etc-directory) #+end_src *** Save the recentf list periodically #+begin_src emacs-lisp :noweb-ref functions (defun maybe-save-recentf () "Save `recentf-file' every five minutes, but only when out of focus." (defvar recentf--last-save (time-convert nil 'integer) "When we last saved the `recentf-save-list'.") (when (> (time-convert (time-since recentf--last-save) 'integer) (* 60 5)) (setq-default recentf--last-save (time-convert nil 'integer)) (when-unfocused #'recentf-save-list))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-function :after after-focus-change-function #'maybe-save-recentf) #+end_src * Responsiveness Emacs has a slew of well-documented problems with snappiness. Luckily, there are a number of solutions. ** Only do things when unfocused Sometimes, we can fake responsiveness by only performing commands when the user is looking at something else. #+begin_src emacs-lisp :noweb-ref functions (defun when-unfocused (func &rest args) "Run FUNC, with ARGS, iff all frames are out of focus." (when (seq-every-p #'null (mapcar #'frame-focus-state (frame-list))) (apply func args))) #+end_src ** Garbage collection *** Simple GC munging about From [[https://bling.github.io/blog/2016/01/18/why-are-you-changing-gc-cons-threshold/][bling]], from ... 2016? Maybe this isn't great, but it's one less package so I'm going to try it for now. #+begin_src emacs-lisp :noweb-ref hooks (defconst gc-cons-basis (* 800 1000) "The basis value to which to return after a max jump. 800,000 (800 KB) is Emacs' default.") (defun hook--gc-cons-maximize () "Set `gc-cons-threshold' to the highest possible. For memory-intensive features." (setq gc-cons-threshold most-positive-fixnum)) (defun hook--gc-cons-baseline () "Return `gc-cons-threshold' to `gc-cons-basis'. For after memory intensive operations." (setq gc-cons-threshold gc-cons-basis)) (add-hook 'minibuffer-setup-hook #'hook--gc-cons-maximize) (add-hook 'minibuffer-exit-hook #'hook--gc-cons-baseline) #+end_src *** Garbage Collect when out of focus #+begin_src emacs-lisp :noweb-ref hooks (defun hook--gc-when-unfocused () (when-unfocused #'garbage-collect)) (add-function :after after-focus-change-function #'hook--gc-when-unfocused) #+end_src * Files ** Encoding *** UTF-8 It's 2020. Let's encode files like it is. #+begin_src emacs-lisp :noweb-ref settings (prefer-coding-system 'utf-8) (set-charset-priority 'unicode) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-keyboard-coding-system 'utf-8) (set-selection-coding-system 'utf-8) (set-language-environment "UTF-8") (setq-default locale-coding-system 'utf-8 buffer-file-coding-system 'utf-8 org-export-coding-system 'utf-8 default-process-coding-system '(utf-8-unix . utf-8-unix) x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)) #+end_src *** UNIX-style line endings This function is from the [[https://www.emacswiki.org/emacs/EndOfLineTips][Emacs Wiki]]. #+begin_src emacs-lisp :noweb-ref functions (defun ewiki/no-junk-please-were-unixish () "Convert line endings to UNIX, dammit." (let ((coding-str (symbol-name buffer-file-coding-system))) (when (string-match "-\\(?:dos\\|mac\\)$" coding-str) (set-buffer-file-coding-system 'unix)))) #+end_src I add it to both =file-find-hook= /and/ =before-save-hook= because I'm /that/ over it. I don't want to ever work with anything other than UNIX line endings ever again. I just don't care. Even Microsoft Notepad can handle UNIX line endings, so I don't want to hear it. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish) (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish) #+end_src ** Keep =~/.emacs.d= clean :package: #+begin_src emacs-lisp :noweb-ref packages :noweb yes (straight-use-package 'no-littering) (require 'no-littering) (with-eval-after-load 'no-littering <> ) ; end of no-littering #+end_src ** Backups #+begin_src emacs-lisp :noweb-ref settings (setq-default backup-by-copying t ;; Don't delete old versions delete-old-versions -1 ;; Make numeric backups unconditionally version-control t ;; Also backup files covered by version control vc-make-backup-files t) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (let ((dir (no-littering-expand-var-file-name "backup"))) (make-directory dir :parents) (setq-default backup-directory-alist `((".*" . ,dir)))) #+end_src ** Autosaves :package: I don't use the =auto-save= system, preferring instead to use Bozhidar Batsov's [[https://github.com/bbatsov/super-save][super-save]] package. #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-save-default nil) (setq-default super-save-remote-files nil super-save-exclude '(".gpg") super-save-auto-save-when-idle t) #+end_src #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'super-save) #+end_src #+begin_src emacs-lisp :noweb-ref modes (super-save-mode +1) (blackout 'super-save-mode) #+end_src ** Lockfiles I don't think these are really necessary as of now. #+begin_src emacs-lisp :noweb-ref settings (setq-default create-lockfiles nil) #+end_src ** Auto-revert files I like to keep the buffers Emacs has in-memory in sync with the actual contents of the files the represent on-disk. Thus, we have =auto-revert-mode=. #+begin_src emacs-lisp :noweb-ref settings (setq-default auto-revert-verbose nil) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-auto-revert-mode +1) #+end_src * Editing ** Lines *** Fill-column #+begin_src emacs-lisp :noweb-ref settings (setq-default fill-column 80) #+end_src I also want to display the fill-column: #+begin_src emacs-lisp :noweb-ref modes (global-display-fill-column-indicator-mode +1) #+end_src By default, Emacs uses =C-x f= to set the =fill-column=. I think it's pretty dumb that such an easy-to-reach binding (for Emacs) is set to a function that I /literally/ never use. So I'm going to bind it to =find-file= ... since that's the only time I accidentally call it, anyway. #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-x f" #'find-file) #+end_src *** Auto-fill vs. Visual-line 1. Enable =auto-fill-mode= with text modes. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'text-mode-hook #'auto-fill-mode) #+end_src 2. /Just/ in case ... let's "fix" =visual-line-mode= if we're in =org-mode=. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--visual-line-fix-org-keys () (when (derived-mode-p 'org-mode) (local-set-key (kbd "C-a") #'org-beginning-of-line) (local-set-key (kbd "C-e") #'org-end-of-line) (local-set-key (kbd "C-k") #'org-kill-line))) (add-hook 'visual-line-mode-hook #'hook--visual-line-fix-org-keys) #+end_src I think that'll work -- I only care about line aesthetics with text. Programming modes should be /allowed/ to have long lines, regardless of how /terrible/ it is to have them. #+begin_src emacs-lisp :noweb-ref modes (blackout 'auto-fill-mode) #+end_src *** Visual fill column mode In reading-intensive views, this mode keeps the text from getting too wide. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'visual-fill-column) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default visual-fill-column-center-text t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'visual-fill-column-mode-hook #'visual-line-mode) (with-eval-after-load 'visual-fill-column (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) #+end_src *** Stay snappy with long-lined files #+begin_src emacs-lisp :noweb-ref modes (when (fboundp 'global-so-long-mode) (global-so-long-mode +1)) #+end_src ** Whitespace *** Whitespace style The =whitespace-style= defines what kinds of whitespace to clean up on =whitespace-cleanup=, as well as what to highlight (if that option is enabled). #+begin_src emacs-lisp :noweb-ref settings (setq-default whitespace-style '(empty ; remove blank lines at buffer edges indentation ; clean up indentation ;; fix mixed spaces and tabs space-before-tab space-after-tab)) #+end_src *** Clean up whitespace on save #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'before-save-hook #'whitespace-cleanup) #+end_src *** Don't use TABs I was team TAB for a while, but I find them easier to avoid in Emacs. It manages my whitespace for me, anyway. #+begin_src emacs-lisp :noweb-ref settings (setq-default indent-tabs-mode nil) #+end_src ** Killing & Yanking *** Replace the selection when typing #+begin_src emacs-lisp :noweb-ref modes (delete-selection-mode +1) #+end_src *** Work better with the system clipboard #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Save existing clipboard text to the kill ring before replacing it. save-interprogram-paste-before-kill t ;; Update the X selection when rotating the kill ring. yank-pop-change-selection t ;; Enable clipboards x-select-enable-clipboard t x-select-enable-primary t ;; Copy a region when it's selected with the mouse mouse-drag-copy-region t) #+end_src *** Don't append the same thing twice to the kill ring #+begin_src emacs-lisp :noweb-ref settings (setq-default kill-do-not-save-duplicates t) #+end_src *** Kill the line if there is no region :crux: #+begin_src emacs-lisp :noweb-ref hooks (crux-with-region-or-line kill-ring-save) (crux-with-region-or-line kill-region) #+end_src ** Overwrite mode *** Change the cursor #+begin_src emacs-lisp :noweb-ref hooks (defun hook--overwrite-mode-change-cursor () (setq cursor-type (if overwrite-mode t 'bar))) (add-hook 'overwrite-mode-hook #'hook--overwrite-mode-change-cursor) #+end_src ** The Mark see also - [[https://spwhitton.name/blog/entry/transient-mark-mode/][Gnu Emacs' Transient Mark mode]], Sean Whitton *** Repeat popping the mark without repeating the prefix argument #+begin_src emacs-lisp :noweb-ref settings (setq-default set-mark-repeat-command-pop t) #+end_src ** The Region *** Expand region :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'expand-region) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-=" #'er/expand-region) #+end_src *** Pulse the modified region with goggles #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'goggles) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (defun fix--goggles-mode () "Fix goggles-mode to blackout the lighter." (goggles-mode) (blackout 'goggles-mode)) (add-hook 'text-mode-hook #'fix--goggles-mode) (add-hook 'prog-mode-hook #'fix--goggles-mode) #+end_src ** Undo :package: *** Undo Fu #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'undo-fu) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-/" #'undo-fu-only-undo) (acdw/bind "C-?" #'undo-fu-only-redo) #+end_src *** Undo Fu session I'm not putting this in [[*Persistence]] because it'd be confusing away from =undo-fu=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'undo-fu-session) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'")) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (let ((dir (no-littering-expand-var-file-name "undos"))) (make-directory dir :parents) (setq-default undo-fu-session-directory dir)) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-undo-fu-session-mode +1) #+end_src ** Search/Replace :package: The /biggest/ thing I miss about my Neovim days is its ease of search/replace. It didn't matter where the point was in the buffer; it could wrap around. It had a little highlight to show you all the matching strings, /and/ it could show you what the replacement would look like. =anzu= does /most/ of this, except the wrapping around part -- =ctrlf= does the wrapping around okay, but I haven't really tried to get the two packages to play nice together. Until then, I'll just use =anzu= and =isearch=, which is honestly a pretty good search package. *** Isearch I want to search by regexp by default. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/map (kbd "C-s") #'isearch-forward-regexp) (define-key acdw/map (kbd "C-r") #'isearch-backward-regexp) (define-key acdw/map (kbd "C-M-s") #'isearch-forward) (define-key acdw/map (kbd "C-M-r") #'isearch-backward) #+end_src *** Anzu setup :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'anzu) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default anzu-mode-lighter "" ; hide anzu-mode in the modeline anzu-replace-to-string-separator " → ") ;; Set up anzu in the modeline (setq-default anzu-cons-mode-line-p nil) (setcar (cdr (assq 'isearch-mode minor-mode-alist)) '(:eval (concat " " (anzu--update-mode-line)))) #+end_src *** Regex I search with regex by default. #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Search Regex by default search-default-mode t) #+end_src I've switched =query-replace= and =query-replace-regexp= with their anzu versions, because of the regex thing. #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind-after-map 'anzu nil (([remap query-replace] #'anzu-query-replace-regexp) ([remap query-replace-regexp] #'anzu-query-replace) ([remap isearch-query-replace] #'anzu-isearch-query-replace :map isearch-mode-map) ([remap isearch-query-replace-regexp] #'anzu-isearch-query-replace-regexp :map isearch-mode-map))) #+end_src ** Commenting :crux: I don't think the default =M-;= (=M-x comment-dwim=) binding makes sense. I want it to comment out the region or line, or uncomment it if it's already commented. That's it. #+begin_src emacs-lisp :noweb-ref hooks (crux-with-region-or-line comment-or-uncomment-region) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "M-;" #'comment-or-uncomment-region) #+end_src ** Goto address mode "Buttonize URLs and Email addresses." #+begin_src emacs-lisp :noweb-ref modes (when (fboundp 'global-goto-address-mode) (global-goto-address-mode +1)) #+end_src * Writing ** Word count *** Key binding I just found out that =M-== counts the words in a region. That's great, but I often want to count the words in the whole buffer. #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "M-=" #'count-words) #+end_src ** Spell checking *** Settings Let's use =hunspell=. #+begin_src emacs-lisp :noweb-ref packages (with-eval-after-load "ispell" (setenv "LANG" "en_US") (setq-default ispell-program-name "hunspell" ispell-dictionary "en_US") (ispell-set-spellchecker-params)) (setq ispell-personal-dictionary "~/.hunspell_personal") (unless (file-exists-p ispell-personal-dictionary) (write-region "" nil ispell-personal-dictionary nil 0)) #+end_src *** Flyspell #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'text-mode-hook #'flyspell-mode) (add-hook 'prog-mode-hook #'flyspell-prog-mode) #+end_src #+begin_src emacs-lisp :noweb-ref modes (blackout 'flyspell-mode) #+end_src *** Flyspell-correct :package: Display corrections with =completing-read=. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'flyspell-correct) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-;" #'flyspell-correct-wrapper :after 'flyspell :map flyspell-mode-map) #+end_src * Reading ** Smooth-scrolling of images :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'iscroll) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'text-mode-hook #'iscroll-mode) #+end_src #+begin_src emacs-lisp :noweb-ref modes (add-hook 'iscroll-mode-hook #'(lambda () (blackout 'iscroll-mode))) #+end_src ** Reading mode A custom mode to make reading comfy #+begin_src emacs-lisp :noweb-ref modes (define-minor-mode acdw/reading-mode "Make reading comfier." :lighter " Read" ; later: book emoji (if acdw/reading-mode (progn ;; turn on (text-scale-increase +1) (visual-fill-column-mode +1) (iscroll-mode +1) (display-fill-column-indicator-mode -1)) (progn ;; turn off (text-scale-increase 0) (visual-fill-column-mode -1) (iscroll-mode -1) (display-fill-column-indicator-mode +1)))) #+end_src * Programming ** Comments *** Auto fill comments in programming modes Okay, so I lied in the [[*Auto-fill vs. Visual-line][Auto-fill vs. Visual-line]] section. I /do/ want to auto-fill in programming modes, but /only/ the comments. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--comment-auto-fill () (setq-local comment-auto-fill-only-comments t) (auto-fill-mode +1)) (add-hook 'prog-mode-hook #'hook--comment-auto-fill) #+end_src ** Parentheses *** Show parentheses #+begin_src emacs-lisp :noweb-ref modes (show-paren-mode +1) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default show-paren-delay 0 ;; Show the matching paren if visible, else the whole expression show-paren-style 'mixed) #+end_src *** Smart parens :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'smartparens) (require 'smartparens-config) #+end_src **** Show parens #+begin_src emacs-lisp :noweb-ref settings (setq-default sp-show-pair-delay 0 sp-show-pair-from-inside t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'show-smartparens-mode) #+end_src **** Hide the =smartparens= lighter #+begin_src emacs-lisp :noweb-ref modes (blackout 'smartparens-mode) #+end_src **** Enable in programming modes #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'smartparens-mode) (dolist (hook '(lisp-mode-hook emacs-lisp-mode-hook)) (add-hook hook #'smartparens-strict-mode)) #+end_src **** Use =paredit= bindings #+begin_src emacs-lisp :noweb-ref settings (setq-default sp-base-keybindings 'paredit) #+end_src #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'smart-parens (sp-use-paredit-bindings)) #+end_src ** Formatting *** Aggressive indent :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'aggressive-indent) #+end_src #+begin_src emacs-lisp :noweb-ref modes (global-aggressive-indent-mode +1) (blackout 'aggressive-indent-mode) #+end_src Since aggressive indenting takes care of tabs, I can use == to complete things! #+begin_src emacs-lisp :noweb-ref settings (setq-default tab-always-indent 'complete) #+end_src ** Typesetting *** Prettify-mode I like my pretty =lambda='s -- and maybe one day, I'll add more symbols, but only in prog-mode. I want to see what I'm actually typing in text. #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'prog-mode-hook #'prettify-symbols-mode) #+end_src Of course, I want to be able to /see/ the actual text in the buffer if I'm /in/ the symbols. #+begin_src emacs-lisp :noweb-ref settings (setq-default prettify-symbols-unprettify-at-point 'right-edge) #+end_src ** Executable scripts This poorly-named function will make a file executable if it looks like a script (looking at the function definition, it looks like it checks for a shebang). #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) #+end_src ** Compilation #+begin_src emacs-lisp :noweb-ref settings (setq-default compilation-ask-about-save nil ; just save compilation-always-kill t ; kill the old processes compilation-scroll-output 'first-error) #+end_src ** Language-specific *** Generic-x from [[https://www.reddit.com/r/emacs/comments/lfww57/weekly_tipstricketc_thread/gmtk79e/][u/Bodertz]], apparently =generic-x= just ... has syntax highlighting for a ton of (I suppose) generic files. #+begin_src emacs-lisp :noweb-ref packages (require 'generic-x) #+end_src *** Emacs Lisp **** Don't limit the length of evaluated expressions #+begin_src emacs-lisp :noweb-ref settings (setq-default eval-expression-print-length nil eval-expression-print-level nil) #+end_src **** Indent Elisp like Common Lisp #+begin_src emacs-lisp :noweb-ref requires (require 'cl-lib) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default lisp-indent-function #'common-lisp-indent-function) (put 'cl-flet 'common-lisp-indent-function (get 'flet 'common-lisp-indent-function)) (put 'cl-labels 'common-lisp-indent-function (get 'labels 'common-lisp-indent-function)) (put 'if 'common-lisp-indent-function 2) (put 'dotimes-protect 'common-lisp-indent-function (get 'when 'common-lisp-indent-function)) #+end_src *** Web #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'web-mode) (require 'web-mode) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default web-mode-enable-current-element-highlight t) #+end_src *** i3 config I use i3 ... for now. But I only want to load the relevant mode /if/ I have i3 installed. #+begin_src emacs-lisp :noweb-ref packages (when (executable-find "i3") (straight-use-package 'i3wm-config-mode)) #+end_src * Applications Emacs is well-known for its ability to subsume one's entire computing life. There are a few /killer apps/ that make Emacs really shine. Here, I configure them and a few others. My rubric for what makes a package an application, versus just a package, is mostly based on the way I feel about it. Don't expect to agree with all of my choices. ** Web browsing *** Browse-url I like using Firefox. #+begin_src emacs-lisp :noweb-ref settings (setq-default browse-url-browser-function 'browse-url-firefox browse-url-new-window-flag t browse-url-firefox-new-window-is-tab t) #+end_src At work, I need to tell Emacs where Firefox is. #+begin_src emacs-lisp :noweb-ref windows-specific (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox") #+end_src *** SHR #+begin_src emacs-lisp :noweb-ref settings (setq-default shr-max-width fill-column) #+end_src ** Dired #+begin_src emacs-lisp :noweb-ref hooks (defun hook--dired-mode () (hl-line-mode +1) (dired-hide-details-mode +1)) (add-hook 'dired-mode-hook #'hook--dired-mode) #+end_src A note on =dired-listing-switches=: when I'm able to figure out how to move up a directory with a keybinding, I'll change =-a= to =-A=. #+begin_src emacs-lisp :noweb-ref settings (setq-default dired-recursive-copies 'always dired-recursive-deletes 'always delete-by-moving-to-trash t dired-listing-switches "-AFgho --group-directories-first" dired-dwim-target t) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "C-x C-j" #'dired-jump) #+end_src *** Expand subtrees :package: Instead of opening each folder in its own buffer, =dired-subtree= enables me to open them in the same buffer, fancily indented. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'dired-subtree) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "i" #'dired-subtree-toggle :after 'dired :map dired-mode-map) #+end_src *** Collapse singleton directories :package: If a directory only has one item in it, =dired-collapse= shows what that one item is. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'dired-collapse) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'dired-mode-hook #'dired-collapse-mode) #+end_src ** Git :package: *** Magit #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'magit) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "g" #'magit-status :map acdw/leader) #+end_src **** Windows setup Following the [[https://github.com/magit/magit/wiki/Pushing-with-Magit-from-Windows][wiki page located here]]. Also make sure to run the following in =cmd.exe= to set =$HOME= correctly: #+begin_src cmd setx HOME C:\Users\aduckworth\Downloads\acdw #+end_src /and/ run /this/ command to setup a git credential helper: #+begin_src sh git config --global credential.helper store #+end_src Okay, okay, using the =store= credential.helper is /super/ insecure. But here's the thing -- the Gits I Git at work (a) aren't my /real/ git, and (b) they're just tokens! So any time I think somebody got access, I just revoke the tokens and bingo bongo, good to go. If that's not true, please feel free to hack this repo and change this paragraph. #+begin_src emacs-lisp :noweb-ref windows-specific (setenv "GIT_ASKPASS" "git-gui--askpass") #+end_src **** Forge :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'forge) (with-eval-after-load 'magit (require 'forge)) #+end_src *** Git file modes :package: #+begin_src emacs-lisp :noweb-ref packages (dolist (feat '(gitattributes-mode gitconfig-mode gitignore-mode)) (straight-use-package feat) (require feat)) #+end_src ** Crosswords! :package: I love crosswords. I love Emacs. There ya go. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(crossword :host github :repo "Boruch-Baum/emacs-crossword")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default crossword-empty-position-char "#") #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (setq-default crossword-save-path (no-littering-expand-var-file-name "crosswords/")) (unless (file-exists-p crossword-save-path) (make-directory crossword-save-path :parents)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (defun hook--setup-crossword () (setq cursor-type 'hbar)) (add-hook 'crossword-mode-hook #'hook--setup-crossword) #+end_src The problem with this package is that the default faces are pretty bad, to be honest. Let's change em. #+begin_src emacs-lisp :noweb-ref settings (doremi-face-set 'crossword-current-face '((((class color) (background light)) (:inherit 'normal :foreground "black" :background "lightgreen")) (((class color) (background dark)) (:inherit 'normal :foreground "white" :background "darkgreen")) (t (:inherit 'normal :foreground "black" :background "darkgreen")))) (doremi-face-set 'crossword-other-dir-face '((((class color) (background light)) (:inherit 'normal :foreground "black" :background "darkgrey")) (((class color) (background dark)) (:inherit 'normal :foreground "black" :background "darkgrey")) (t (:inherit 'normal :foreground "black" :background "darkgrey")))) #+end_src ** TODO Gnus See [[https://github.com/redguardtoo/mastering-emacs-in-one-year-guide/blob/master/gnus-guide-en.org][this guide]] and try it out. ** RSS Feeds :package: *** Elfeed #+begin_src emacs-lisp :noweb-ref packages ;; first, "fix" org-version (with-eval-after-load 'org (defun org-version () "Fix of `org-version' for `elfeed'. I don't know what the actual version is, but 9.5 should have us covered. It's somewhere past 9." "9.5") (straight-use-package 'elfeed)) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elfeed-db-directory (expand-file-name "elfeed/db" (or (getenv "XDG_CACHE_HOME") "~/.cache"))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'elfeed-show-mode-hook #'acdw/reading-mode) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind "f" #'elfeed :map acdw/leader) #+end_src **** Elfeed feeds #+begin_src emacs-lisp :noweb-ref settings (setq elfeed-feeds `( ("https://computer.rip/rss.xml" tech newsletter) ("https://weather-broker-cdn.api.bbci.co.uk/en/forecast/rss/3day/4315588" weather) ("https://www.realbakingwithrose.com/month?format=rss" food) ("https://xfnw.tilde.institute/sandcats/feed.rss" fwend pix) ("https://www.makeworld.gq/feed.xml" blag) ("https://whyarentyoucoding.com/feed.xml" comix) ("https://xkcd.com/atom.xml" comix) ("https://falseknees.com/rss.xml" comix) ("https://chrisman.github.io/rss.xml" fwend dozens blag) ("https://tilde.team/~dozens/dreams/rss.xml" fwend dozens blag) ("https://society.neocities.org/rss.xml" fwend dozens) ("https://supervegan.neocities.org/feed.xml" fwend dozens food) ("https://blog.astrosnail.pt.eu.org/feed.atom" tech blag) ("https://www.greghendershott.com/feeds/all.atom.xml" tech blag) ("https://hans.gerwitz.com/feeds/writing.rss" fwend) ("http://planet.lisp.org/rss20.xml" tech lisp) ("https://wflewis.com/feed.xml" blag fwend) ("HTTPS://atthis.link/rss.xml" blag tech) ("https://rachelbythebay.com/w/atom.xml" blag tech) ("https://notes.neeasade.net/rss_full.xml" blag tech) ("https://www.uninformativ.de/blog/feeds/en.atom" blag tech) ("http://blog.z3bra.org/rss/feed.xml" blag tech) ("https://blog.sanctum.geek.nz/feed/" blag tech) ("https://drewdevault.com/blog/index.xml" blag tech) ("https://usesthis.com/feed.atom" tech) ("https://occasionallycogent.com/feed.xml" blag) ("https://www.murilopereira.com/index.xml" blag tech) ("https://botanistinthekitchen.blog/feed/" blag food) ("https://www.careercenterbr.com/feed/" work) ("https://blog.ebrpl.com/feed/" work) (,(concat ; long-ass url "https://lemmy.ml/feeds/front/" "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." "eyJpZCI6MTY4MjQsImlzcyI6ImxlbW15Lm1sIn0" ".yE2zUGjvlEuTZZi3TiF9HR7L7ITM9f_" "fQnquyYLgdJ4.xml?sort=Active") news) ("https://lobste.rs/rss" news tech) ("https://feeds.npr.org/1001/rss.xml" news) (,(concat ; long-ass url "https://tilde.news/rss?token=" "FvdFj8rQkhrBy9j1yON1t6RYKDdcuG1MoUlyvRICmbgDGCf2JTWAEObDhdgt") news tildes tech) ("https://www.acdw.net/atom.xml" fwend) ("https://envs.net/~lucidiot/rsrsss/feed.xml" fwend) ("https://planet.emacslife.com/atom.xml" emacs tech) ("http://tilde.town/~lucidiot/fridaypostcard.xml" tildes art) ("https://quasivoid.net/feed.atom" tildes) ("https://benjaminwil.info/feed.xml" tildes fwend) ("https://benjaminwil.info/antisocial/feed.xml" tildes) ("https://blog.ryliejamesthomas.net/feed/" tildes) ("https://p1k3.com/feed" tildes) ("https://cosmic.voyage/rss.xml" tildes fiction sci-fi) ("https://jackreid.xyz/index.xml" tildes) ("http://lambdacreate.com/static/feed.rss" tildes fwend) ("https://gaffen.co.uk/feed.xml" tildes) ("https://gmb.21x2.net/rss.xml" tildes) ("https://www.insom.me.uk/feed.xml" tildes) ("https://invisibleup.com/atom.xml" tildes) ("https://m455.casa/feed.rss" tildes fwend) ("https://petras.space/index.xml" tildes) ("https://www.benovermyer.com/post/index.xml" tildes) ("https://tilde.town/~trm/blog/atom.xml" tildes) ("https://tilde.team/feed.rss" tildes) ("http://ajroach42.com/feed.xml" tildes) ("http://tilde.town/~mroik/blog/rss.xml" tildes) ("https://hamster.dance/blog/rss/" tildes) ("https://m455.neocities.org/feed.rss" tildes fwend) ("https://eli.li/feed.rss" tildes fwend) ("https://aiweirdness.com/rss" tech) ("http://tilde.town/~m455/javapool.rss" tilde) ("https://spwhitton.name/blog/index.rss" blag) (,(concat "https://www.theadvocate.com/search/?" ;; Let's Build A URL!!! "f=rss" ; RSS feed "&l=10" ; 10 most recent (length) "&c[]=" ; I'm guessing ... categories? "baton_rouge/news*," "baton_rouge/opinion*" "?t=article" ; type=article ) news) ("https://esoteric.codes/rss" tech) ("https://wphicks.github.io/feed.xml" blag) )) #+end_src ** 0x0 (null pointer) :package: An ease-of-life package that lets me upload my shitty code to share it with others. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(0x0 :repo "https://git.sr.ht/~zge/nullpointer-emacs")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default 0x0-default-service 'ttm) #+end_src ** Gemini/gopher *** Elpher :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(elpher :repo "git://thelambdalab.xyz/elpher.git")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default elpher-ipv4-always t) (doremi-face-set 'elpher-gemini-heading1 '((t (:inherit (modus-theme-heading-1) :height 1.0)))) (doremi-face-set 'elpher-gemini-heading2 '((t (:inherit (modus-theme-heading-2) :height 1.0)))) (doremi-face-set 'elpher-gemini-heading3 '((t (:inherit (modus-theme-heading-3) :height 1.0)))) #+end_src #+begin_src emacs-lisp :noweb-ref no-littering (setq-default elpher-certificate-directory (no-littering-expand-var-file-name "elpher-certificates/")) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (acdw/bind-after-map 'elpher elpher-mode-map (("n" #'elpher-next-link) ("p" #'elpher-prev-link) ("o" #'elpher-follow-current-link) ("G" #'elpher-go-current))) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'elpher-mode-hook #'acdw/reading-mode) #+end_src *** Gemini-mode :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(gemini-mode :repo "https://git.carcosa.net/jmcbray/gemini.el.git")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (add-to-list 'auto-mode-alist '("\\.\\(gemini\\|gmi\\)\\'" . gemini-mode)) (doremi-face-set 'gemini-heading-face-1 '((t (:inherit (elpher-gemini-heading1))))) (doremi-face-set 'gemini-heading-face2 '((t (:inherit (elpher-gemini-heading2))))) (doremi-face-set 'gemini-heading-face3 '((t (:inherit (elpher-gemini-heading3))))) #+end_src *** Gemini-write :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(gemini-write :repo "https://alexschroeder.ch/cgit/gemini-write" :fork (:repo "https://tildegit.org/acdw/gemini-write" :branch "main"))) (with-eval-after-load 'elpher (require 'gemini-write)) #+end_src ** Eshell I use =eshell= with Emacs, because it works both on Windows and Linux. *** Open an eshell or bury its buffer adapted from [[https://git.sr.ht/~zge/setup][zge's setup]], which might also be an interesting macro to look into one day. #+begin_src emacs-lisp :noweb-ref functions (defun acdw/eshell-or-bury () "Start an `eshell', or bury its buffer if focused." (interactive) (if (string= (buffer-name) "*eshell*") ;; XXX: brittle (bury-buffer) (eshell))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader "s" #'acdw/eshell-or-bury) #+end_src * Org mode :package: ** Install it with =straight.el= I want to use the newest version of Org that I can. #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'org) (with-eval-after-load 'org (require 'org-tempo) (require 'ox-md)) #+end_src ** Basic settings #+begin_src emacs-lisp :noweb-ref settings (setq-default ;; Where to look for Org files org-directory "~/org" ; this is the default ;; Fontify stuff org-hide-emphasis-markers t org-fontify-whole-heading-line t org-fontify-done-headline t org-fontify-quote-and-verse-blocks t org-src-fontify-natively t org-ellipsis "..." org-pretty-entities t org-tags-column (- 0 fill-column -3) ;; Source blocks org-src-tab-acts-natively t org-src-window-setup 'current-window ; the least stupid option org-confirm-babel-evaluate nil ;; Behavior org-adapt-indentation nil ; don't indent things org-catch-invisible-edits 'smart ; let's try this org-special-ctrl-a/e t org-special-ctrl-k t org-imenu-depth 8 ;; Exporting org-export-headline-levels 8 org-export-with-smart-quotes t org-export-with-sub-superscripts t) #+end_src ** Aesthetics *** Prettify some other symbols #+begin_src emacs-lisp :noweb-ref functions (defun acdw/org-mode-prettify () "Prettify `org-mode'." (dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐) ("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎) ("#+BEGIN_QUOTE" . ?❝) ("#+begin_quote" . ?❝) ("#+END_QUOTE" . ?❞) ("#+end_quote" . ?❞) ("#+END_SRC" . ?■) ("#+end_src" . ?■))) (add-to-list 'prettify-symbols-alist cell :append)) (prettify-symbols-mode +1)) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'acdw/org-mode-prettify) #+end_src *** Prettify lists and checkboxes using font-lock from [[https://github.com/KaratasFurkan/.emacs.d#org-1][Furkan Karataş]]. #+begin_src emacs-lisp :noweb-ref modes (with-eval-after-load 'org (font-lock-add-keywords 'org-mode '(("^ *\\([-]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))) (font-lock-add-keywords 'org-mode '(("^ *\\([+]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "◦")))))) (defface org-checkbox-done-text '((t (:inherit 'font-lock-comment-face :slant normal))) "Face for the text part of a checked org-mode checkbox." :group 'org) (font-lock-add-keywords 'org-mode `(("^[ \t]*\\(?:[-+*]\\|[0-9]+[).]\\)[ \t]+\\(\\(?:\\[@\\(?:start:\\)?[0-9]+\\][ \t]*\\)?\\[\\(?:X\\|\\([0-9]+\\)/\\2\\)\\][^\n]*\n\\)" 1 'org-checkbox-done-text prepend)) 'append)) #+end_src *** Org-appear :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package '(org-appear :host github :repo "awth13/org-appear")) #+end_src #+begin_src emacs-lisp :noweb-ref settings (setq-default org-appear-autoemphasis t org-appear-autolinks nil org-appear-autosubmarkers t) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'org-appear-mode) #+end_src ** Org templates #+begin_src emacs-lisp :noweb-ref settings (with-eval-after-load 'org-tempo (dolist (cell '(("el" . "src emacs-lisp") ("cr" . "src emacs-lisp :noweb-ref requires") ("cf" . "src emacs-lisp :noweb-ref functions") ("cs" . "src emacs-lisp :noweb-ref settings") ("cm" . "src emacs-lisp :noweb-ref modes") ("cl" . "src emacs-lisp :noweb-ref linux-specific") ("cw" . "src emacs-lisp :noweb-ref windows-specific") ("cp" . "src emacs-lisp :noweb-ref packages") ("ch" . "src emacs-lisp :noweb-ref hooks") ("cb" . "src emacs-lisp :noweb-ref bindings") ("cnl" . "src emacs-lisp :noweb-ref no-littering"))) (add-to-list 'org-structure-template-alist cell))) #+end_src ** Org Return: DWIM #+begin_src emacs-lisp :noweb-ref functions (defun unpackaged/org-element-descendant-of (type element) "Return non-nil if ELEMENT is a descendant of TYPE. TYPE should be an element type, like `item' or `paragraph'. ELEMENT should be a list like that returned by `org-element-context'." ;; MAYBE: Use `org-element-lineage'. (when-let* ((parent (org-element-property :parent element))) (or (eq type (car parent)) (unpackaged/org-element-descendant-of type parent)))) (defun unpackaged/org-return-dwim (&optional default) "A helpful replacement for `org-return'. With prefix, call `org-return'. On headings, move point to position after entry content. In lists, insert a new item or end the list, with checkbox if appropriate. In tables, insert a new row or end the table." ;; Inspired by John Kitchin: ;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ (interactive "P") (if default (org-return) (cond ;; Act depending on context around point. ;; NOTE: I prefer RET to not follow links, but by uncommenting ;; this block, links will be followed. ;; FURTHER NOTE: Ideally, I would follow links unless point ;; /appeared/ to be at the end of the line (even if it's still ;; inside the link) -- when it would do `org-return'. That ;; would take some /doing/, however. ;; ((eq 'link (car (org-element-context))) ;; ;; Link: Open it. ;; (org-open-at-point-global)) ((org-at-heading-p) ;; Heading: Move to position after entry content. ;; NOTE: This is probably the most interesting feature of this function. (let ((heading-start (org-entry-beginning-position))) (goto-char (org-entry-end-position)) (cond ((and (org-at-heading-p) (= heading-start (org-entry-beginning-position))) ;; Entry ends on its heading; add newline after (end-of-line) (insert "\n\n")) (t ;; Entry ends after its heading; back up (forward-line -1) (end-of-line) (when (org-at-heading-p) ;; At the same heading (forward-line) (insert "\n") (forward-line -1)) ;; FIXME: looking-back is supposed to be called with ;; more arguments. (while (not (looking-back (rx (repeat 3 (seq (optional blank) "\n"))) nil)) (insert "\n")) (forward-line -1))))) ((org-at-item-checkbox-p) ;; Checkbox: Insert new item with checkbox. (org-insert-todo-heading nil)) ((org-in-item-p) ;; Plain list. Yes, this gets a little complicated... (let ((context (org-element-context))) (if (or (eq 'plain-list (car context)) ; First item in list (and (eq 'item (car context)) (not (eq (org-element-property :contents-begin context) (org-element-property :contents-end context)))) ;; Element in list item, e.g. a link (unpackaged/org-element-descendant-of 'item context)) ;; Non-empty item: Add new item. (org-insert-item) ;; Empty item: Close the list. ;; TODO: Do this with org functions rather than operating ;; on the text. Can't seem to find the right function. (delete-region (line-beginning-position) (line-end-position)) (insert "\n")))) ((when (fboundp 'org-inlinetask-in-task-p) (org-inlinetask-in-task-p)) ;; Inline task: Don't insert a new heading. (org-return)) ((org-at-table-p) (cond ((save-excursion (beginning-of-line) ;; See `org-table-next-field'. (cl-loop with end = (line-end-position) for cell = (org-element-table-cell-parser) always (equal (org-element-property :contents-begin cell) (org-element-property :contents-end cell)) while (re-search-forward "|" end t))) ;; Empty row: end the table. (delete-region (line-beginning-position) (line-end-position)) (org-return)) (t ;; Non-empty row: call `org-return'. (org-return)))) (t ;; All other cases: call `org-return'. (org-return))))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'org (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim)) #+end_src ** Insert blank lines around headers #+begin_src emacs-lisp :noweb-ref functions (defun unpackaged/org-fix-blank-lines (&optional prefix) "Ensure that blank lines exist between headings and between headings and their contents. With prefix, operate on whole buffer. Ensures that blank lines exist after each headings's drawers." (interactive "P") (org-map-entries (lambda () (org-with-wide-buffer ;; `org-map-entries' narrows the buffer, which prevents us ;; from seeing newlines before the current heading, so we ;; do this part widened. (while (not (looking-back "\n\n" nil)) ;; Insert blank lines before heading. (insert "\n"))) (let ((end (org-entry-end-position))) ;; Insert blank lines before entry content (forward-line) (while (and (org-at-planning-p) (< (point) (point-max))) ;; Skip planning lines (forward-line)) (while (re-search-forward org-drawer-regexp end t) ;; Skip drawers. You might think that `org-at-drawer-p' ;; would suffice, but for some reason it doesn't work ;; correctly when operating on hidden text. This ;; works, taken from `org-agenda-get-some-entry-text'. (re-search-forward "^[ \t]*:END:.*\n?" end t) (goto-char (match-end 0))) (unless (or (= (point) (point-max)) (org-at-heading-p) (looking-at-p "\n")) (insert "\n")))) t (if prefix nil 'tree))) #+end_src I fix the headline spacing every time I save. #+begin_src emacs-lisp :noweb-ref hooks (defun hook--org-mode-fix-blank-lines () (when (eq major-mode 'org-mode) (let ((current-prefix-arg 4)) ; Emulate C-u (call-interactively 'unpackaged/org-fix-blank-lines)))) (add-hook 'before-save-hook #'hook--org-mode-fix-blank-lines) #+end_src ** Org Agenda I'm trying to organize my life. Inspo: - [[https://github.com/cadadr/configuration/blob/3e11ef25344188cc55b16f314c3c5358ace8a266/emacs.d/init.el#L4625][Göktuğ Kayaalp]] - [[https://pages.sachachua.com/.emacs.d/Sacha.html#org1db6fe9][Sacha Chua]] *** Basic Agenda Settings #+begin_src emacs-lisp :noweb-ref settings (setq-default org-agenda-files ; look for files in ~/org (list org-directory) ;; agenda org-agenda-span 5 org-agenda-skip-scheduled-if-done t org-agenda-skip-deadline-if-done t org-deadline-warning-days 2 ;; logging org-log-into-drawer "LOGBOOK" org-log-done t ;; archive org-archive-location (concat (expand-file-name ".archive.org" org-directory) "::")) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "C-a") #'org-agenda) #+end_src *** Agenda hooks #+begin_src emacs-lisp :noweb-ref hooks (defun hook--org-agenda-mode () (hl-line-mode +1)) (add-hook 'org-agenda-mode-hook #'hook--org-agenda-mode) #+end_src *** Refile #+begin_src emacs-lisp :noweb-ref settings (setq org-refile-targets '((org-agenda-files . (:maxlevel . 3)))) #+end_src *** Calendar settings I'm not sure where else to put these, to be honest. #+begin_src emacs-lisp :noweb-ref settings (setq-default calendar-date-style 'iso) ; YYYY-mm-dd #+end_src *** Habits Org can track habits! Great stuff. I need to add it to =org-modules=, though. #+begin_src emacs-lisp :noweb-ref settings (add-to-list 'org-modules 'org-habit) #+end_src Now I just add a =habit= property to a subtree, and BAM! *** Org Todo Keywords These need some more thinking -- e.g., the MEETING sequence should maybe be a type, or maybe I need to include those in something else altogether. Hm. #+begin_src emacs-lisp :noweb-ref settings (setq-default org-todo-keywords '((sequence "TODO(t)" "STARTED(s)" "WAITING(w@/!)" "SOMEDAY(.)" "|" "DONE(x!)" "CANCELLED(c@/!)") (sequence "RECUR(r)" "|" "DONE(x!)") (sequence "MEETING(m)"))) #+end_src ** Org Capture #+begin_src emacs-lisp :noweb-ref packages (require 'org-capture) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (with-eval-after-load 'org-capture (define-key acdw/leader (kbd "C-c") #'org-capture)) #+end_src I've still got a lot of thinking to do about what kinds of things I want to capture, but I guess for now I can start with the basics: TODO, and Diary. #+begin_src emacs-lisp :noweb-ref settings (defvar acdw/org-inbox-file (expand-file-name "inbox.org" org-directory)) (defvar acdw/org-diary-file (expand-file-name "diary.org" org-directory)) (setq-default org-capture-templates `(;; Todo -- these go to the Inbox for further processing ("t" "Quick Task" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\n" :immediate-finish t) ("T" "Task" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\n") ("." "Today" entry (file ,acdw/org-inbox-file) "* TODO %^{Task}\nSCHEDULED: %t\n" :immediate-finish t) ;; Meetings ("m" "Meeting" entry (file ,acdw/org-inbox-file) "* MEETING %^{Meeting}\n%^T\n\n%?") ;; Diary -- for the special Diary file ("j" "Diary entry" entry (file+olp+datetree ,acdw/org-diary-file) "* %U\n\n%?" :empty-lines 1) ;; Books to read ("b" "Book to read" entry (file+headline "books.org" "Later") "* TOREAD %^{Title} :PROPERTIES: :Author: %^{Author} :END:\n" :immediate-finish t))) #+end_src ** Org auto tangle :package: #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'org-auto-tangle) (require 'org-auto-tangle) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'org-mode-hook #'org-auto-tangle-mode) #+end_src #+begin_src emacs-lisp :noweb-ref modes (blackout 'org-auto-tangle-mode) #+end_src * Package management :package: :PROPERTIES: :header-args: :noweb-ref early-init-package :END: Emacs is the /extensible/ editor, and that means I want to use third-party packages. Of course, first I have to /manage/ those packages. I use the excellent =straight.el=. ** Update the PATH PATH handling on Emacs is a little complicated. There's the regular environment variable =$PATH=, which we all know and love, and then Emacs has its own special =exec-path= on /top/ of that. From my research, it looks like Emacs uses =exec-path= for itself, and =$PATH= for any shells or other processes it spawns. They don't /have/ to be the same, but luckily for us, Emacs sets =exec-path= from =$PATH= on initialization, so when I add stuff to =exec-path= to, say, run git, I can just change =$PATH= right back to the expanded =exec-path= without any data loss. Here's what all that looks like. #+begin_src emacs-lisp (let ((win-app-dir "~/Applications")) (dolist (path (list ;; Windows (expand-file-name "exe" win-app-dir) (expand-file-name "exe/bin" win-app-dir) (expand-file-name "Git/bin" win-app-dir) (expand-file-name "Git/usr/bin" win-app-dir) (expand-file-name "Git/mingw64/bin" win-app-dir) (expand-file-name "Everything" win-app-dir) (expand-file-name "Win-builds/bin" win-app-dir) (expand-file-name "Z/bin" win-app-dir) ;; Linux (expand-file-name "bin" user-emacs-directory) (expand-file-name "~/bin") (expand-file-name "~/.local/bin") (expand-file-name "~/Scripts") )) (when (file-exists-p path) (add-to-list 'exec-path path :append)))) ;; Set $PATH (setenv "PATH" (mapconcat #'identity exec-path path-separator)) #+end_src *** References - [[https://emacs.stackexchange.com/questions/550/exec-path-and-path][exec-path and $PATH (StackExchange)]] - [[https://utoi.tistory.com/entry/Difference-Between-Emacss-%E2%80%9Cgetenv-PATH%E2%80%9D-and-%E2%80%9Cexec-path%E2%80%9D][Difference between Emacs's "(getenv PATH)" and "exec-path" (U&I)]] - GUI Emacs sets the exec-path only from Windows environment variable but not from .emacs file [[https://emacs.stackexchange.com/questions/27326/gui-emacs-sets-the-exec-path-only-from-windows-environment-variable-but-not-from][(StackExchange)]] ** Disable =package.el= #+begin_src emacs-lisp (setq package-enable-at-startup nil) #+end_src ** Bootstrap The following is straight (heh) from the straight repo, wrapped in a function so I can call it in another wrapper. #+begin_src emacs-lisp (defun acdw/bootstrap-straight () "Bootstrap straight.el." (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously (concat "https://raw.githubusercontent.com/" "raxod502/straight.el/develop/install.el") 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))) #+end_src To actually bootstrap straight, I'll first try running the above directly. If it errors (it tends to on Windows), I'll directly clone the repo using git, /then/ run the bootstrap code. #+begin_src emacs-lisp (when (executable-find "git") (unless (ignore-errors (acdw/bootstrap-straight)) (let ((msg "Straight.el didn't bootstrap correctly. Cloning directly")) (message "%s..." msg) (call-process "git" nil (get-buffer-create "*bootstrap-straight-messages*") nil "clone" "https://github.com/raxod502/straight.el" (expand-file-name "straight/repos/straight.el" user-emacs-directory)) (message "%s...Done." msg) (acdw/bootstrap-straight)))) #+end_src * System integration I use both Linux (at home) and Windows (at work). To make Emacs easier to use in both systems, I've included various system-specific settings and written some ancillary scripts. ** All systems I'll put generic system-integrating code here. *** Edit with Emacs :package: Install the [[https://addons.mozilla.org/en-US/firefox/addon/edit-with-emacs1/][Firefox Addon]] alongside this package to edit web forms in Emacs (or the [[https://chrome.google.com/webstore/detail/edit-with-emacs/ljobjlafonikaiipfkggjbhkghgicgoh][Chrome one]] if you... /hate/ freedom :P). #+begin_src emacs-lisp :noweb-ref packages (straight-use-package 'edit-server) #+end_src #+begin_src emacs-lisp :noweb-ref hooks (add-hook 'after-init-hook #'edit-server-start) #+end_src *** =git-sync= stuff This function require [[https://github.com/simonthum/git-sync][git-sync]]. #+begin_src emacs-lisp :noweb-ref functions (defun acdw/git-sync (directory) "Run git-sync in DIRECTORY." (interactive) (message "Git-Syncing %s..." directory) (let ((proc (start-process "git-sync" (get-buffer-create (format "*git-sync:%s*" directory)) "git" "-C" (expand-file-name directory) "sync"))) (add-function :after (process-sentinel proc) (lambda (proc ev) (cond ((string-match "finished\n\\'" ev) (message "Git-Syncing %s...Done." directory))))))) #+end_src **** ~/org #+begin_src emacs-lisp :noweb-ref bindings (defun acdw/git-sync-org () "Run `acdw/git-sync' on `org-directory'." (interactive) (acdw/git-sync org-directory)) (define-key acdw/leader (kbd "C-M-o") #'acdw/git-sync-org) #+end_src **** ~/.cache/elfeed/db #+begin_src emacs-lisp :noweb-ref bindings (defun acdw/git-sync-elfeed-db () "Run `acdw/git-sync' on `elfeed-db-directory'." (interactive) (save-some-buffers :no-query nil) (acdw/git-sync elfeed-db-directory)) (define-key acdw/leader (kbd "C-M-f") #'acdw/git-sync-elfeed-db) #+end_src *** Passwords **** Password cache #+begin_src emacs-lisp :noweb-ref settings (setq-default password-cache-expiry nil) #+end_src **** Use =~/.authinfo= for passwords The =auth-info= line should look like this: #+begin_example machine git.example.com user acdw password hahayeahrightyamoroniwouldn'tgiveyouthat #+end_example #+begin_src emacs-lisp :noweb-ref hooks (autoload 'magit-process-password-auth-source "magit") (add-hook 'magit-process-find-password-functions #'magit-process-password-auth-source) #+end_src *** TRAMP It stands for ... something kind of stupid, I don't remember. I'm pulling this from [[https://github.com/grandfoobah/spartan-emacs/blob/master/spartan-layers/spartan-settings.el][Spartan Emacs]]. It recommends the following in =~/.ssh/config=: #+begin_example Host * ForwardAgent yes AddKeysToAgent yes ControlMaster auto ControlPath ~/.ssh/master-%r@%h:%p ControlPersist yes ServerAliveInterval 10 ServerAliveCountMax 10 #+end_example #+begin_src emacs-lisp :noweb-ref settings (setq-default tramp-default-method "ssh" tramp-copy-size-limit nil tramp-use-ssh-controlmaster-options nil tramp-default-remote-shell "/bin/bash") #+end_src ** Linux (home) :PROPERTIES: :header-args: :noweb-ref linux-specific :END: *** Scripts **** em :PROPERTIES: :header-args: :tangle-mode (identity #o755) :mkdirp yes :END: Here's a wrapper script that'll start =emacs --daemon= if there isn't one, and then launch =emacsclient= with the arguments. Install it to your =$PATH= somewhere. #+begin_src sh :shebang "#!/bin/sh" :tangle (if (eq system-type 'gnu/linux) "~/bin/em" "") if ! emacsclient -nc "$@"; then emacs --daemon emacsclient -nc "$@" fi #+end_src **** emacsclient.desktop :PROPERTIES: :header-args: :mkdirp yes :END: I haven't really tested this yet, but it should allow me to open other files and things in Emacs. From [[https://www.taingram.org/blog/emacs-client.html][taingram]]. #+begin_src conf-desktop :tangle (if (eq system-type 'gnu/linux) "~/.local/share/applications/emacsclient.desktop" "") [Desktop Entry] Name=Emacs Client GenericName=Text Editor Comment=Edit text MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; Exec=emacsclient -c %f Icon=emacs Type=Application Terminal=false Categories=Utility;TextEditor; #+end_src ** Windows (work) :PROPERTIES: :header-args: :noweb-ref windows-specific :END: I use Windows at work, where I /also/ don't have Admin rights. So I kind of fly-by-night there. Many of the ideas and scripts in this section come from [[https://github.com/termitereform/JunkPile/blob/master/emacs-on-windows.md][termitereform]] on Github. *** Environment variables **** DICPATH, for Hunspell #+begin_src emacs-lisp :noweb-ref windows-specific (setenv "DICPATH" (expand-file-name "exe/share/hunspell" "~/Applications/")) #+end_src *** Settings See also [[https://www.gnu.org/software/emacs/manual/html_mono/efaq-w32.html][the GNU FAQ for Windows]]. At some point I should really dig into the multiple settings available for w32 systems. See also [[https://github.com/bbatsov/prelude/blob/master/core/prelude-windows.el][the Prelude Windows configuration]] (modifier keys). #+begin_src emacs-lisp (setq-default w32-allow-system-shell t ; enable cmd.exe as shell ;; modifier keys w32-pass-lwindow-to-system nil w32-lwindow-modifier 'super w32-pass-rwindow-to-system nil w32-rwindow-modifier 'super w32-pass-apps-to-system nil w32-apps-modifier 'hyper) #+end_src *** Scripts :PROPERTIES: :header-args: :noweb yes :mkdirp yes :END: **** Common variables #+begin_src bat :noweb-ref w32-bat-common if not exist %HOME% (set HOME=%~dp0..\..) set EMACS=%HOME%\Applications\Emacs\bin\runemacs.exe set EMACSC=%HOME%\Applications\Emacs\bin\emacsclientw.exe set GIT=%HOME%\Applications\Git\bin\git.exe #+end_src **** Emacs Autostart Either run this once at startup, or put a shortcut of it in the Startup folder: =%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup=. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Autostart.cmd" "") <> REM git pull the config start "git" /wait %GIT% -C %HOME%\.emacs.d pull REM start emacs chdir %HOME% start "emacs-daemon" /wait %EMACS% --daemon start "emacs-client" %EMACSC% -n -c -a "" %* #+end_src **** Emacs Client This will try to connect to the daemon above. If that fails, it'll run =runemacs.exe=. *This is the main shortcut for running Emacs.* #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs.cmd" "") <> start "emacs" "%EMACSC%" -n -c -a "%EMACS%" %* #+end_src **** Emacs Daemon #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Daemon.cmd" "") <> start "emacs-daemon" %EMACS% --daemon #+end_src **** Emacs Safe Start This runs Emacs with the factory settings. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Safe Start.cmd" "") <> start "emacs-safe" "%EMACS%" -Q %* #+end_src **** Emacs Debug This runs Emacs with the =--debug-init= option enabled. #+begin_src bat :tangle (if (eq system-type 'windows-nt) "~/Applications/cmd/Emacs Debug.cmd" "") <> start "emacs-debug" "%EMACS%" --debug-init %* #+end_src * Appendices ** config.el :PROPERTIES: :header-args: :tangle config.el :noweb yes :END: While =config.el= is written above, I use Noweb references to tangle them all together in the following block, which enables me to organize my config here /logically/, while keeping the generated file organized /programmatically/. *** Enable lexical binding #+begin_src emacs-lisp ;;; config.el --- personal configuration -*- lexical-binding: t -*- #+end_src *** Header & disclaimer :PROPERTIES: :header-args: :noweb-ref disclaimer :END: #+begin_src emacs-lisp ;; Copyright (C) 2020 Case Duckworth ;; Author: Case Duckworth ;; Created: Sometime during the Covid-19 lockdown, 2019 ;; Keywords: configuration ;; URL: https://tildegit.org/acdw/emacs ;; This file is not part of GNU Emacs. ;;; Commentary: ;; This file is automatically tangled from config.org. ;; Hand edits will be overwritten! #+end_src *** The rest #+begin_src emacs-lisp <> ;;; Code: ;;; REQUIRES <> ;;; VARIABLES <> ;;; ACDW MODE <> ;;; PACKAGES <> ;;; FUNCTIONS <> ;;; SETTINGS <> ;;; SYSTEM-DEPENDENT SETTINGS ;; at home (eval-and-compile (when (memq system-type '(gnu gnu/linux gnu/kfreebsd)) <> )) ;; at work (eval-and-compile (when (memq system-type '(ms-dos windows-nt)) <> )) ;;; MODES <> ;;; HOOKS <> ;;; BINDINGS <> ;;; config.el ends here #+end_src *** Ease of editing :PROPERTIES: :header-args: :tangle no :END: #+begin_src emacs-lisp :noweb-ref functions (defun acdw/find-config () "Find `config.org'." (interactive) (find-file (locate-user-emacs-file "config.org"))) #+end_src Bind it to =C-z i= because =C-z C-c= is taken for capture. #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "i") #'acdw/find-config) #+end_src *** Ease of reloading :PROPERTIES: :header-args: :tangle no :END: #+begin_src emacs-lisp :noweb-ref functions (defun acdw/reload () "Tangle and reload Emacs configuration." (interactive) (let ((config (locate-user-emacs-file "config.org"))) ;; tangle (with-current-buffer (find-file-noselect config) (message "Tangling config.org...") (let ((prog-mode-hook nil) (inhibit-redisplay t) (inhibit-message t)) (add-to-list 'load-path (locate-user-emacs-file "straight/build/org/")) (require 'org) (org-babel-tangle))) (message "Tangling config.org... Done.") ;; load init files (load (locate-user-emacs-file "early-init.el")) (load (locate-user-emacs-file "init.el")) (load (locate-user-emacs-file "config.el")))) #+end_src #+begin_src emacs-lisp :noweb-ref bindings (define-key acdw/leader (kbd "C-M-r") #'acdw/reload) #+end_src ** init.el :PROPERTIES: :header-args: :tangle init.el :noweb yes :END: The classic Emacs initiation file. *** Header #+begin_src emacs-lisp ;;; init.el -*- lexical-binding: t; coding: utf-8 -*- <> ;;; Code: #+end_src *** Prefer newer files to older files #+begin_src emacs-lisp (setq-default load-prefer-newer t) #+end_src *** Load the config I keep most of my config in =config.el=, which is tangled directly from this file. This init just loads that file, either from lisp or directly from Org if it's newer. /Note/ the longish comment before the =unless= form -- it was pretty tough for me to wrap my head around the needed boolean expression to tangle config.org. Booleans, yall! #+begin_src emacs-lisp (let* (;; Speed up init (gc-cons-threshold most-positive-fixnum) ;; (gc-cons-percentage 0.6) (file-name-handler-alist nil) ;; Config file names (config (expand-file-name "config" user-emacs-directory)) (config.el (concat config ".el")) (config.org (concat config ".org")) (straight-org-dir (locate-user-emacs-file "straight/build/org"))) ;; Okay, let's figure this out. ;; `and' evaluates each form, and returns nil on the first that ;; returns nil. `unless' only executes its body if the test ;; returns nil. So. ;; 1. Test if config.org is newer than config.el. If it is (t), we ;; *want* to evaluate the body, so we need to negate that test. ;; 2. Try to load the config. If it errors (nil), it'll bubble that ;; to the `and' and the body will be evaluated. (unless (and (not (file-newer-than-file-p config.org config.el)) (load config :noerror)) ;; A plain require here just loads the older `org' ;; in Emacs' install dir. We need to add the newer ;; one to the `load-path', hopefully that's all. (when (file-exists-p straight-org-dir) (add-to-list 'load-path straight-org-dir)) ;; Load config.org (message "%s..." "Loading config.org") (require 'org) (org-babel-load-file config.org) (message "%s... Done" "Loading config.org"))) ;;; init.el ends here #+end_src ** early-init.el :PROPERTIES: :header-args: :tangle early-init.el :noweb yes :END: Beginning with 27.1, Emacs also loads an =early-init.el= file, before the package manager or the UI code. The Info says we should put as little as possible in this file, so I only have what I need. #+begin_src emacs-lisp ;;; early-init.el -*- no-byte-compile: t; coding: utf-8 -*- <> ;;; Code: ;; BOOTSTRAP PACKAGE MANAGEMENT <> ;; SETUP FRAME <> ;;; early-init.el ends here #+end_src ** License :PROPERTIES: :header-args: :tangle LICENSE :END: Copyright © 2020 Case Duckworth This work is free. You can redistribute it and/or modify it under the terms of the Do What the Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the =LICENSE= file, tangled from the following source block, for details. #+begin_src text DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE Version 2, December 2004 Copyright (C) 2004 Sam Hocevar Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. You just DO WHAT THE FUCK YOU WANT TO. #+end_src *** Note on the license It's highly likely that the WTFPL is completely incompatible with the GPL, for what should be fairly obvious reasons. To that, I say: *SUE ME, RMS!* ** Inspiration and further reading - [[https://github.com/bbatsov/prelude][Emacs Prelude]] - [[https://github.com/alphapapa/unpackaged.el][Unpackaged]]