Now on git.acdw.net https://git.acdw.net/emacs
Go to file
Case Duckworth 89467846ab Force LF 2021-02-23 12:17:51 -06:00
etc/eshell Force LF 2021-02-23 12:17:51 -06:00
var Force LF 2021-02-23 12:17:51 -06:00
.gitattributes Force LF 2021-02-23 12:17:51 -06:00
.gitignore Force LF 2021-02-23 12:17:51 -06:00
LICENSE Force LF 2021-02-23 12:17:51 -06:00
README.org Change README.md to README.org 2021-01-19 23:43:19 -06:00
config.org Add nov.el 2021-02-23 12:13:22 -06:00
early-init.el Force LF 2021-02-23 12:17:51 -06:00
init.el Force LF 2021-02-23 12:17:51 -06:00

README.org

Emacs configuration, literate-style

About me

  (setq user-full-name "Case Duckworth"
        user-mail-address "acdw@acdw.net")

Where I am

  (setq calendar-location-name "Baton Rouge, LA"
        calendar-latitude 30.4
        calendar-longitude -91.1)

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.

  (setq-default auth-sources '("~/.authinfo"))

Look and feel

Frames

Initial frame setup

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
  (add-to-list 'default-frame-alist
               '(tool-bar-lines . 0))

  (tool-bar-mode -1)
Menu bar
  (add-to-list 'default-frame-alist
               '(menu-bar-lines . 0))

  (menu-bar-mode -1)
Scroll bars
  (add-to-list 'default-frame-alist
               '(vertical-scroll-bars . nil)
               '(horizontal-scroll-bars . nil))

  (scroll-bar-mode -1)
  (horizontal-scroll-bar-mode -1)
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.

  (setq-default frame-inhibit-implied-resize t
                frame-resize-pixelwise t)

Frame titles

  (setq-default frame-title-format
                '("Emacs " 
                  mode-line-client
                  mode-line-modified
                  " "
                  (:eval (if (buffer-file-name)
                             (abbreviate-file-name (buffer-file-name))
                           "%b"))
                  ))

Fringes

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
  (setq-default indicate-empty-lines t)
Indicate the boundaries of the buffer
  (setq-default indicate-buffer-boundaries 'right)
Indicate continuation lines, but only on the left fringe
  (setq-default visual-line-fringe-indicators '(left-curly-arrow nil))
Customize fringe bitmaps
Curly arrows (continuation lines)
  (define-fringe-bitmap 'left-curly-arrow
    [#b11000000
     #b01100000
     #b00110000
     #b00011000])

  (define-fringe-bitmap 'right-curly-arrow
    [#b00011000
     #b00110000
     #b01100000
     #b11000000])
Arrows (truncation lines)
  (define-fringe-bitmap 'left-arrow
    [#b00000000
     #b01010100
     #b01010100
     #b00000000])

  (define-fringe-bitmap 'right-arrow
    [#b00000000
     #b00101010
     #b00101010
     #b00000000])

Windows

Window Dividers

  (setq-default window-divider-default-places 'right-only ; only right
                window-divider-default-bottom-width 2
                window-divider-default-right-width 2)
  (window-divider-mode +1)

Splitting windows sensibly

This is extremely fiddly and I'd love another option.

  (setq-default split-width-threshold 100
                split-height-threshold 50)

Window layouts

Let's try settings from nex3 on Github.

  (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)

Switch to other window or buffer   crux

  (acdw/bind "M-o" #'crux-other-window-or-switch-buffer)

The Help window

I want to select the Help window by default, so I can easily quit it.

  (setq-default help-window-select t)

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.

  (require 'uniquify)
  (setq-default uniquify-buffer-name-style 'forward
                uniquify-separator "/"
                uniquify-after-kill-buffer-p t
                uniquify-ignore-buffers-re "^\\*")

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.

  (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"))

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.

  (defun immortal-scratch ()
    (if (not (eq (current-buffer) (get-buffer "*scratch*")))
        t
      (bury-buffer)
      nil))
  (add-hook 'kill-buffer-query-functions #'immortal-scratch)

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 this discussion, which might also come in handy someday.

  (straight-use-package 'scratch)
  (acdw/bind "C-x" #'scratch :map acdw/leader)

Kill buffers better

  (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)))))
  (acdw/bind "C-x k" #'kill-a-buffer)

Kill old buffers after a while

Adapted from midnight-mode, using suggestions from u/ndamee.

  (require 'midnight)

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.

  (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*")

Cursor

Cursor shape

I like a vertical bar, but only in the selected window.

  (setq-default cursor-type 'bar
                cursor-in-non-selected-windows nil)

Don't blink the cursor

  (blink-cursor-mode -1)

Tabs

Tab bar mode 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)

Tab bar history

  (tab-bar-history-mode +1)
  (setq-default tab-bar-history-limit 25)

Fonts

On Linux, I have a custom build of Iosevka that I like.

  (set-face-attribute 'default nil
                      :family "Iosevka Acdw"
                      :height 105)

  (set-face-attribute 'fixed-pitch nil
                      :family "Iosevka Acdw"
                      :height 105)

But on Windows, I use Consolas.

  (set-face-attribute 'default nil
                      :family "Consolas"
                      :height 100)

  (set-face-attribute 'fixed-pitch nil
                      :family "Consolas"
                      :height 100)

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.

  (setq-default x-underline-at-descent-line t)

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 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.

  (straight-use-package '(list-utils
                          :host github
                          :repo "rolandwalker/list-utils"
                          :fork (:repo "duckwork/list-utils")))
Persistent-soft
  (straight-use-package '(persistent-soft
                          :host github
                          :repo "rolandwalker/persistent-soft"
                          :fork (:repo "duckwork/persistent-soft")))
Font-utils

I was able to actually create a 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.

  (straight-use-package '(font-utils
                          :host github
                          :repo "rolandwalker/font-utils"
                          :fork (:repo "duckwork/font-utils")))
A function in case it comes up again

I keep googling 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!

  ;; 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))))
Unicode-fonts

Okay … pull requests in, time to load unicode-fonts.

  (straight-use-package '(unicode-fonts
                          :host github
                          :repo "rolandwalker/unicode-fonts"))
  (require 'unicode-fonts)

According to Issue #3, there can be problems with unicode-fonts-setup when using a daemon. Instead of forking this repo and merging PR #4 into my personal fork, I'll use the workaround described in the issue.

  (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)

Draw form-feeds (^L) properly   package

  (straight-use-package 'form-feed)
  (global-form-feed-mode +1)
  (blackout 'form-feed-mode)

Theming

Modus themes   package

I want the git version.

  (straight-use-package '(modus-themes
                          :host gitlab
                          :repo "protesilaos/modus-themes"))
  (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)

Change themes based on time of day

  (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)))
  (acdw/run-with-sun #'modus-themes-load-operandi
                     #'modus-themes-load-vivendi)

Mode line

Minions mode   package
  (straight-use-package 'minions)
  (require 'minions)
  (minions-mode +1)
Blackout some modes   package

Like diminish or delight, blackout allows me to remove some minor-modes from the modeline.

  (straight-use-package '(blackout
                          :host github
                          :repo "raxod502/blackout"))
Which-function mode

Shows where we are in the modeline.

  (which-function-mode +1)
Mode-line faces
  (doremi-face-set 'mode-line
                   '((t (:family "Terminus"
                         :height 1.0))))
  (doremi-face-set 'mode-line-inactive
                   '((t (:family "Terminus"
                         :height 1.0))))

Setting faces

It took me a while to find a function that'll let me customize faces without using customize. Thanks to Drew Adams, I've got it!

  (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)))

Interactivity

Dialogs and alerts

Don't use a dialog box

Ask in the modeline instead.

  (setq-default use-dialog-box nil)

Yes or no questions

I just want to type y or n, okay?

  (fset 'yes-or-no-p #'y-or-n-p)

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.

  (setq-default visible-bell nil
                ring-bell-function #'flash-mode-line)
Flash the mode-line
  (defun flash-mode-line ()
    (invert-face 'mode-line)
    (run-with-timer 0.2 nil #'invert-face 'mode-line))

Minibuffer

Keep the cursor away from the minibuffer prompt

  (setq-default minibuffer-prompt-properties
                '(read-only t
                  cursor-intangible t
                  face minibuffer-prompt))

Enable a recursive minibuffer

  (setq-default enable-recursive-minibuffers t)

Show the recursivity of the minibuffer in the mode-line

  (minibuffer-depth-indicate-mode +1)

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.

  (setq-default file-name-shadow-properties '(invisible t))
  (file-name-shadow-mode +1)

Ignore case

  (setq-default completion-ignore-case t
                read-buffer-completion-ignore-case t
                read-file-name-completion-ignore-case t)

Icomplete

  (straight-use-package 'icomplete-vertical)
  (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)
  (acdw/bind-after-map 'icomplete icomplete-minibuffer-map
    (("<down>" #'icomplete-forward-completions)
     ("C-n" #'icomplete-forward-completions)
     ("<up>" #'icomplete-backward-completions)
     ("C-p" #'icomplete-backward-completions)))

  (acdw/bind "C-v" #'icomplete-vertical-toggle
             :after 'icomplete-vertical
             :map icomplete-minibuffer-map)
  (fido-mode -1) ; I take care of this myself
  (icomplete-mode +1)
  (icomplete-vertical-mode +1)

Orderless   package

  (straight-use-package 'orderless)
  (require 'orderless)
  (setq-default
   completion-styles '(orderless))

Consult   package

  (straight-use-package 'consult)
  (require 'consult)

Consult has a lot of great bindings that work well with Emacs's default completion system. These all come from the example configuration.

  (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)
     ("<f1> a" #'consult-apropos)
     ("C-h a" #'consult-apropos)))
  (autoload 'consult-register-preview "consult") ; make the compiler happy
  (setq-default register-preview-delay 0
                register-preview-function #'consult-register-preview)

Marginalia   package

Finally, marginalia provides extra information about completion candidates.

  (straight-use-package 'marginalia)
  (require 'marginalia)
  (marginalia-mode +1)

I like the rich annotations provided by marginalia.

  (setq-default marginalia-annotators
                '(marginalia-annotators-heavy
                  marginalia-annotators-light
                  nil))

Imenu

  (setq-default imenu-auto-rescan t)

Completion

Hippie Expand

Before I install any completion framework, I want a good default for completing. hippie-expand fills that niche.

  (acdw/bind "M-/" #'hippie-expand)

Bindings

Acdw Mode

I've decided to set up a custom minor mode for my keybindings, as suggested in 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.

  (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)
  (blackout 'acdw/mode)

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.

  (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)))
Turn off acdw/mode in the minibuffer
  (defun acdw/mode--disable ()
    "Turn off acdw/mode."
    (acdw/mode -1))

  (add-hook 'minibuffer-setup-hook #'acdw/mode--disable)
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.

  (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)

Show keybindings with which-key   package

  (straight-use-package 'which-key)
  (which-key-mode +1)
  (blackout 'which-key-mode)

Scrolling

Fast scrolling

According to Murilo Pereira, these settings will make Emacs scrolling "an order of magnitude faster."

  (setq-default auto-window-vscroll nil
                fast-but-imprecise-scrolling t)

Scroll margins

  (setq-default scroll-margin 0
                scroll-conservatively 101 ; if greater than 100 ...
                scroll-preserve-screen-position 1)

Enable commands

I think the disabled command feature of Emacs is stupid, especially for a program that values freedom so much.

  (setq-default disabled-command-function nil)

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.

  (straight-use-package '(crux
                          :host github
                          :repo "bbatsov/crux"))
  (require 'crux)

A note: I don't do the same with 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.

  (require 'savehist)
  (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)
  (savehist-mode +1)

File places

The saveplace package saves where I've been in my visited files.

  (require 'saveplace)

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.

  (setq-default save-place-forget-unreadable-files
                (memq system-type '(gnu gnu/linux gnu/kfreebsd)))
  (save-place-mode +1)

Recent files

I also like to keep track of recently-opened files. recentf helps with that.

  (require 'recentf)
  (setq-default recentf-max-menu-items 100
                recentf-max-saved-items nil
                recentf-auto-cleanup 'never)
  (recentf-mode +1)

I also want to ignore the no-littering-var-directory and no-littering-etc-directory, since those aren't useful.

  (add-to-list 'recentf-exclude no-littering-var-directory)
  (add-to-list 'recentf-exclude no-littering-etc-directory)

Save the recentf list periodically

  (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)))
  (add-function :after after-focus-change-function
                #'maybe-save-recentf)

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.

  (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)))

Garbage collection

Simple GC munging about

From bling, from … 2016? Maybe this isn't great, but it's one less package so I'm going to try it for now.

  (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)

Garbage Collect when out of focus

  (defun hook--gc-when-unfocused ()
    (when-unfocused #'garbage-collect))

  (add-function :after after-focus-change-function
                #'hook--gc-when-unfocused)

Files

Encoding

UTF-8

It's 2020. Let's encode files like it is.

  (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))

UNIX-style line endings

This function is from the Emacs Wiki.

  (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))))

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.

  (add-hook 'find-file-hook #'ewiki/no-junk-please-were-unixish)
  (add-hook 'before-save-hook #'ewiki/no-junk-please-were-unixish)

Keep ~/.emacs.d clean   package

  (straight-use-package 'no-littering)
  (require 'no-littering)
  (with-eval-after-load 'no-littering
    <<no-littering>>
    ) ; end of no-littering

Backups

  (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)
  (let ((dir (no-littering-expand-var-file-name "backup")))
    (make-directory dir :parents)
    (setq-default backup-directory-alist
                  `((".*" . ,dir))))

Autosaves   package

I don't use the auto-save system, preferring instead to use Bozhidar Batsov's super-save package.

  (setq-default auto-save-default nil)

  (setq-default super-save-remote-files nil
                super-save-exclude '(".gpg")
                super-save-auto-save-when-idle t)
  (straight-use-package 'super-save)
  (super-save-mode +1)
  (blackout 'super-save-mode)

Lockfiles

I don't think these are really necessary as of now.

  (setq-default create-lockfiles nil)

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.

  (setq-default auto-revert-verbose nil)
  (global-auto-revert-mode +1)

Editing

Lines

Fill-column

  (setq-default fill-column 80)

I also want to display the fill-column:

  (global-display-fill-column-indicator-mode +1)

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.

  (acdw/bind "C-x f" #'find-file)

Auto-fill vs. Visual-line

  1. Enable auto-fill-mode with text modes.

      (add-hook 'text-mode-hook #'auto-fill-mode)
  2. Just in case … let's "fix" visual-line-mode if we're in org-mode.

      (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)

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.

  (blackout 'auto-fill-mode)

Visual fill column mode

In reading-intensive views, this mode keeps the text from getting too wide.

  (straight-use-package 'visual-fill-column)
  (setq-default visual-fill-column-center-text t)
  (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))

Stay snappy with long-lined files

  (when (fboundp 'global-so-long-mode)
    (global-so-long-mode +1))

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).

  (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))

Clean up whitespace on save

  (add-hook 'before-save-hook #'whitespace-cleanup)

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.

  (setq-default indent-tabs-mode nil)

Killing & Yanking

Replace the selection when typing

  (delete-selection-mode +1)

Work better with the system clipboard

  (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)

Don't append the same thing twice to the kill ring

  (setq-default kill-do-not-save-duplicates t)

Kill the line if there is no region   crux

  (crux-with-region-or-line kill-ring-save)
  (crux-with-region-or-line kill-region)

Overwrite mode

Change the cursor

  (defun hook--overwrite-mode-change-cursor ()
    (setq cursor-type (if overwrite-mode t 'bar)))

  (add-hook 'overwrite-mode-hook #'hook--overwrite-mode-change-cursor)

The Mark

see also

Repeat popping the mark without repeating the prefix argument

  (setq-default set-mark-repeat-command-pop t)

The Region

Expand region   package

  (straight-use-package 'expand-region)
  (acdw/bind "C-=" #'er/expand-region)

Pulse the modified region with goggles

  (straight-use-package 'goggles)
  (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)

Undo   package

Undo Fu

  (straight-use-package 'undo-fu)
  (acdw/bind "C-/" #'undo-fu-only-undo)
  (acdw/bind "C-?" #'undo-fu-only-redo)

Undo Fu session

I'm not putting this in /acdw/emacs/src/commit/89467846ab071d2644ac777d3187b3f913e62e69/*Persistence because it'd be confusing away from undo-fu.

  (straight-use-package 'undo-fu-session)
  (setq-default undo-fu-session-incompatible-files
                '("/COMMIT_EDITMSG\\'"
                  "/git-rebase-todo\\'"))
  (let ((dir (no-littering-expand-var-file-name "undos")))
    (make-directory dir :parents)
    (setq-default undo-fu-session-directory dir))
  (global-undo-fu-session-mode +1)

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.

  (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)

Anzu setup   package

  (straight-use-package 'anzu)
  (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))))

Regex

I search with regex by default.

  (setq-default
   ;; Search Regex by default
   search-default-mode t)

I've switched query-replace and query-replace-regexp with their anzu versions, because of the regex thing.

  (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)))

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.

  (crux-with-region-or-line comment-or-uncomment-region)
  (acdw/bind "M-;" #'comment-or-uncomment-region)

Goto address mode

"Buttonize URLs and Email addresses."

  (when (fboundp 'global-goto-address-mode)
    (global-goto-address-mode +1))

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.

  (acdw/bind "M-=" #'count-words)

Spell checking

Settings

Let's use hunspell.

  (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))

Flyspell

  (add-hook 'text-mode-hook #'flyspell-mode)
  (add-hook 'prog-mode-hook #'flyspell-prog-mode)
  (blackout 'flyspell-mode)

Flyspell-correct   package

Display corrections with completing-read.

  (straight-use-package 'flyspell-correct)
  (acdw/bind "C-;" #'flyspell-correct-wrapper
             :after 'flyspell
             :map flyspell-mode-map)

Reading

Smooth-scrolling of images   package

  (straight-use-package 'iscroll)
  (add-hook 'text-mode-hook #'iscroll-mode)
  (add-hook 'iscroll-mode-hook
            #'(lambda () (blackout 'iscroll-mode)))

Reading mode

A custom mode to make reading comfy

  (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))))

Programming

Comments

Auto fill comments in programming modes

Okay, so I lied in the Auto-fill vs. Visual-line section. I do want to auto-fill in programming modes, but only the comments.

  (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)

Parentheses

Show parentheses

  (show-paren-mode +1)
  (setq-default show-paren-delay 0
                ;; Show the matching paren if visible, else the whole expression
                show-paren-style 'mixed)

Smart parens   package

  (straight-use-package 'smartparens)
  (require 'smartparens-config)
Show parens
  (setq-default sp-show-pair-delay 0
                sp-show-pair-from-inside t)
  (add-hook 'prog-mode-hook #'show-smartparens-mode)
Hide the smartparens lighter
  (blackout 'smartparens-mode)
Enable in programming modes
  (add-hook 'prog-mode-hook #'smartparens-mode)

  (dolist (hook '(lisp-mode-hook
                  emacs-lisp-mode-hook))
    (add-hook hook #'smartparens-strict-mode))
Use paredit bindings
  (setq-default sp-base-keybindings 'paredit)
  (with-eval-after-load 'smart-parens
    (sp-use-paredit-bindings))

Formatting

Aggressive indent   package

  (straight-use-package 'aggressive-indent)
  (global-aggressive-indent-mode +1)
  (blackout 'aggressive-indent-mode)

Since aggressive indenting takes care of tabs, I can use <TAB> to complete things!

  (setq-default tab-always-indent 'complete)

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.

  (add-hook 'prog-mode-hook #'prettify-symbols-mode)

Of course, I want to be able to see the actual text in the buffer if I'm in the symbols.

  (setq-default prettify-symbols-unprettify-at-point 'right-edge)

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).

  (add-hook 'after-save-hook
            #'executable-make-buffer-file-executable-if-script-p)

Compilation

  (setq-default compilation-ask-about-save nil ; just save
                compilation-always-kill t ; kill the old processes
                compilation-scroll-output 'first-error)
  (acdw/bind "<f5>" #'recompile)

Language-specific

Generic-x

from u/Bodertz, apparently generic-x just … has syntax highlighting for a ton of (I suppose) generic files.

  (require 'generic-x)

Emacs Lisp

Don't limit the length of evaluated expressions
  (setq-default eval-expression-print-length nil
                eval-expression-print-level nil)
Indent Elisp like Common Lisp
  (require 'cl-lib)
  (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))

Web

  (straight-use-package 'web-mode)
  (require 'web-mode)
  (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))
  (setq-default web-mode-enable-current-element-highlight t)

i3 config

I use i3 … for now. But I only want to load the relevant mode if I have i3 installed.

  (when (executable-find "i3")
    (straight-use-package 'i3wm-config-mode))

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.

  (setq-default browse-url-browser-function 'browse-url-firefox
                browse-url-new-window-flag t
                browse-url-firefox-new-window-is-tab t)

At work, I need to tell Emacs where Firefox is.

  (add-to-list 'exec-path "C:/Program Files/Mozilla Firefox")

SHR

  (setq-default shr-max-width fill-column)

Dired

  (defun hook--dired-mode ()
    (hl-line-mode +1)
    (dired-hide-details-mode +1))

  (add-hook 'dired-mode-hook #'hook--dired-mode)

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.

  (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)
  (acdw/bind "C-x C-j" #'dired-jump)

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.

  (straight-use-package 'dired-subtree)
  (acdw/bind "i" #'dired-subtree-toggle :after 'dired :map dired-mode-map)

Collapse singleton directories   package

If a directory only has one item in it, dired-collapse shows what that one item is.

  (straight-use-package 'dired-collapse)
  (add-hook 'dired-mode-hook #'dired-collapse-mode)

Git   package

Magit

  (straight-use-package 'magit)
  (acdw/bind "g" #'magit-status :map acdw/leader)
Windows setup

Following the wiki page located here. Also make sure to run the following in cmd.exe to set $HOME correctly:

  setx HOME C:\Users\aduckworth\Downloads\acdw

and run this command to setup a git credential helper:

  git config --global credential.helper store

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.

  (setenv "GIT_ASKPASS" "git-gui--askpass")
Forge   package
  (straight-use-package 'forge)
  (with-eval-after-load 'magit
    (require 'forge))

Git file modes   package

  (dolist (feat '(gitattributes-mode
                  gitconfig-mode
                  gitignore-mode))
    (straight-use-package feat)
    (require feat))

Crosswords!   package

I love crosswords. I love Emacs. There ya go.

  (straight-use-package '(crossword
                          :host github
                          :repo "Boruch-Baum/emacs-crossword"))
  (setq-default crossword-empty-position-char "#")
  (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))
  (defun hook--setup-crossword ()
    (setq cursor-type 'hbar))

  (add-hook 'crossword-mode-hook #'hook--setup-crossword)

The problem with this package is that the default faces are pretty bad, to be honest. Let's change em.

  (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"))))

TODO Gnus

See this guide and try it out.

RSS Feeds   package

Elfeed

  ;; 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))
  (setq-default elfeed-db-directory
                (expand-file-name "elfeed/db"
                                  (or (getenv "XDG_CACHE_HOME")
                                      "~/.cache")))
  (add-hook 'elfeed-show-mode-hook #'acdw/reading-mode)
  (acdw/bind "f" #'elfeed :map acdw/leader)
Elfeed feeds
  (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)
          ))

0x0 (null pointer)   package

An ease-of-life package that lets me upload my shitty code to share it with others.

  (straight-use-package '(0x0
                          :repo "https://git.sr.ht/~zge/nullpointer-emacs"))
  (setq-default 0x0-default-service 'ttm)

Gemini/gopher

Elpher   package

  (straight-use-package '(elpher
                          :repo "git://thelambdalab.xyz/elpher.git"))
  (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))))
  (setq-default elpher-certificate-directory
                (no-littering-expand-var-file-name
                 "elpher-certificates/"))
  (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)))
  (add-hook 'elpher-mode-hook #'acdw/reading-mode)

Gemini-mode   package

  (straight-use-package '(gemini-mode
                          :repo "https://git.carcosa.net/jmcbray/gemini.el.git"))
  (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)))))

Gemini-write   package

  (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))

Eshell

I use eshell with Emacs, because it works both on Windows and Linux.

Open an eshell or bury its buffer

adapted from zge's setup, which might also be an interesting macro to look into one day.

  (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)))
  (define-key acdw/leader "s" #'acdw/eshell-or-bury)

E-books with nov.el   package

I love the name of this package.

  (straight-use-package 'nov)
  (setq-default nov-text-width t)
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
  (add-hook 'nov-mode-hook #'acdw/reading-mode)

Org mode   package

Install it with straight.el

I want to use the newest version of Org that I can.

  (straight-use-package 'org)

  (with-eval-after-load 'org
    (require 'org-tempo)
    (require 'ox-md))

Basic 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)

Aesthetics

Prettify some other symbols

  (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))
  (add-hook 'org-mode-hook #'acdw/org-mode-prettify)

Prettify lists and checkboxes using font-lock

from Furkan Karataş.

  (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))

Org-appear   package

  (straight-use-package '(org-appear
                          :host github
                          :repo "awth13/org-appear"))
  (setq-default org-appear-autoemphasis t
                org-appear-autolinks nil
                org-appear-autosubmarkers t)
  (add-hook 'org-mode-hook #'org-appear-mode)

Org templates

  (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)))

Org Return: DWIM

  (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)))))
  (with-eval-after-load 'org
    (define-key org-mode-map (kbd "RET") #'unpackaged/org-return-dwim))

Insert blank lines around headers

  (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)))

I fix the headline spacing every time I save.

  (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)

Org Agenda

I'm trying to organize my life. Inspo:

Basic Agenda 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)
                                             "::"))
  (define-key acdw/leader (kbd "C-a") #'org-agenda)

Agenda hooks

  (defun hook--org-agenda-mode ()
    (hl-line-mode +1))

  (add-hook 'org-agenda-mode-hook #'hook--org-agenda-mode)

Refile

  (setq org-refile-targets '((org-agenda-files . (:maxlevel . 3))))

Calendar settings

I'm not sure where else to put these, to be honest.

  (setq-default calendar-date-style 'iso) ; YYYY-mm-dd

Habits

Org can track habits! Great stuff. I need to add it to org-modules, though.

  (add-to-list 'org-modules 'org-habit)

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.

  (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)")))

Org Capture

  (require 'org-capture)
  (with-eval-after-load 'org-capture
    (define-key acdw/leader (kbd "C-c") #'org-capture))

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.

  (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)))

Org auto tangle   package

  (straight-use-package 'org-auto-tangle)
  (require 'org-auto-tangle)
  (add-hook 'org-mode-hook #'org-auto-tangle-mode)
  (blackout 'org-auto-tangle-mode)

Package management   package

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.

  (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))

References

Disable package.el

  (setq package-enable-at-startup nil)

Bootstrap

The following is straight (heh) from the straight repo, wrapped in a function so I can call it in another wrapper.

  (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)))

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.

  (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))))

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 Firefox Addon alongside this package to edit web forms in Emacs (or the Chrome one if you… hate freedom :P).

  (straight-use-package 'edit-server)
  (add-hook 'after-init-hook #'edit-server-start)

git-sync stuff

This function require git-sync.

  (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)))))))
~/org
  (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)
~/.cache/elfeed/db
  (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)

Passwords

Password cache
  (setq-default password-cache-expiry nil)
Use ~/.authinfo for passwords

The auth-info line should look like this:

machine git.example.com user acdw password hahayeahrightyamoroniwouldn'tgiveyouthat
  (autoload 'magit-process-password-auth-source "magit")
  (add-hook 'magit-process-find-password-functions
            #'magit-process-password-auth-source)

TRAMP

It stands for … something kind of stupid, I don't remember. I'm pulling this from Spartan Emacs. It recommends the following in ~/.ssh/config:

  Host *
  ForwardAgent yes
  AddKeysToAgent yes
  ControlMaster auto
  ControlPath ~/.ssh/master-%r@%h:%p
  ControlPersist yes
  ServerAliveInterval 10
  ServerAliveCountMax 10
  (setq-default tramp-default-method "ssh"
                tramp-copy-size-limit nil
                tramp-use-ssh-controlmaster-options nil
                tramp-default-remote-shell "/bin/bash")

Linux (home)

Scripts

em

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.

  if ! emacsclient -nc "$@"; then
      emacs --daemon
      emacsclient -nc "$@"
  fi
emacsclient.desktop

I haven't really tested this yet, but it should allow me to open other files and things in Emacs. From taingram.

  [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;

Windows (work)

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 termitereform on Github.

Environment variables

DICPATH, for Hunspell
  (setenv "DICPATH" (expand-file-name "exe/share/hunspell"
                                      "~/Applications/"))

Settings

See also the GNU FAQ for Windows. At some point I should really dig into the multiple settings available for w32 systems.

See also the Prelude Windows configuration (modifier keys).

  (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)

Scripts

Common variables
  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
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.

  <<w32-bat-common>>
  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 "" %*
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.

<<w32-bat-common>>
start "emacs" "%EMACSC%" -n -c -a "%EMACS%" %*
Emacs Daemon
<<w32-bat-common>>
start "emacs-daemon" %EMACS% --daemon
Emacs Safe Start

This runs Emacs with the factory settings.

<<w32-bat-common>>
start "emacs-safe" "%EMACS%" -Q %*
Emacs Debug

This runs Emacs with the --debug-init option enabled.

<<w32-bat-common>>
start "emacs-debug" "%EMACS%" --debug-init %*

Appendices

config.el

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

  ;;; config.el --- personal configuration -*- lexical-binding: t -*-

Header & disclaimer

  ;; Copyright (C) 2020 Case Duckworth

  ;; Author: Case Duckworth <acdw@acdw.net>
  ;; 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!

The rest

  <<disclaimer>>
  ;;; Code:

  ;;; REQUIRES
  <<requires>>

  ;;; VARIABLES
  <<variables>>

  ;;; ACDW MODE
  <<acdw-mode>>

  ;;; PACKAGES
  <<packages>>

  ;;; FUNCTIONS
  <<functions>>

  ;;; SETTINGS
  <<settings>>

  ;;; SYSTEM-DEPENDENT SETTINGS
  ;; at home
  (eval-and-compile
    (when (memq system-type '(gnu gnu/linux gnu/kfreebsd))
      <<linux-specific>>
      ))

  ;; at work
  (eval-and-compile
    (when (memq system-type '(ms-dos windows-nt))
      <<windows-specific>>
      ))

  ;;; MODES
  <<modes>>

  ;;; HOOKS
  <<hooks>>

  ;;; BINDINGS
  <<bindings>>
  ;;; config.el ends here

Ease of editing

  (defun acdw/find-config ()
    "Find `config.org'."
    (interactive)
    (find-file (locate-user-emacs-file "config.org")))

Bind it to C-z i because C-z C-c is taken for capture.

  (define-key acdw/leader (kbd "i") #'acdw/find-config)

Ease of reloading

  (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"))))
  (define-key acdw/leader (kbd "C-M-r") #'acdw/reload)

init.el

The classic Emacs initiation file.

Header

  ;;; init.el -*- lexical-binding: t; coding: utf-8 -*-
  <<disclaimer>>
  ;;; Code:

Prefer newer files to older files

  (setq-default load-prefer-newer t)

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!

  (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

early-init.el

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.

  ;;; early-init.el -*- no-byte-compile: t; coding: utf-8 -*-
  <<disclaimer>>
  ;;; Code:

  ;; BOOTSTRAP PACKAGE MANAGEMENT
  <<early-init-package>>
  ;; SETUP FRAME
  <<early-init-frame>>

  ;;; early-init.el ends here

License

Copyright © 2020 Case Duckworth <acdw@acdw.net>

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.

  DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE

  Version 2, December 2004

  Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>

  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.

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