91b56c70a0
See config.org for details. |
||
---|---|---|
etc/eshell | ||
var | ||
.gitignore | ||
LICENSE | ||
README.org | ||
config.org | ||
early-init.el | ||
init.el |
README.org
Emacs configuration, literate-style
- About me
- Look and feel
- Interactivity
- Persistence
- Responsiveness
- Files
- Editing
- Writing
- Programming
- Applications
- Org mode
- Package management
- System-specific
- Appendices
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
(concat invocation-name "@" (system-name)
": %b %+%+ %f"))
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
Switch to other window or buffer
I grabbed this from u/astoff1: it extends the other-window
command
to switch to the other buffer if there's only one window in the frame.
(defun other-window-or-buffer ()
"Switch to other window, or the previous buffer."
(interactive)
(if (eq (count-windows) 1)
(switch-to-buffer nil)
(other-window 1)))
And I'll bind it to M-o
, since that's easier to reach than C-x o
.
(define-key acdw/map (kbd "M-o") #'other-window-or-buffer)
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)
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)) "!\n"
";; 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)
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)))))
(define-key acdw/map (kbd "C-x k") #'kill-a-buffer)
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 names
(setq-default tab-bar-tab-name-function
#'tab-bar-tab-name-current-with-count)
When to show the tab bar
Only when there's more than one tab.
(setq-default tab-bar-show 1)
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 110)
(set-face-attribute 'fixed-pitch nil
:family "Consolas"
:height 110)
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
List-utils first
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")))
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")))
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. Trying to un-fuck these packages by Roland Walker have been
enough work today.
(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)
Theming
Modus themes package
(straight-use-package '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 . line)
(t . t))
modus-themes-scale-headings 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
Simple modeline package
After trying doom-mode-line
and smart-mode-line
, I think I've finally
landed on a good one: simple-modeline
.
(straight-use-package 'simple-modeline)
(setq-default simple-modeline-segments
'((simple-modeline-segment-modified
simple-modeline-segment-buffer-name
simple-modeline-segment-position)
(simple-modeline-segment-minor-modes
simple-modeline-segment-input-method
simple-modeline-segment-vc
simple-modeline-segment-misc-info
simple-modeline-segment-process
simple-modeline-segment-major-mode)))
(simple-modeline-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"))
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)
Selectrum package
My minibuffer completion system uses selectrum
, prescient
, company
,
and marginalia
. At some point, I'd like to take a deep dive in embark
(possibly switching out selectrum
), ido
, orderless
, or others,
for now …. I just want to see my completions.
(straight-use-package 'selectrum)
(selectrum-mode +1)
Prescient
(straight-use-package 'prescient)
(require 'prescient)
Prescient can persist itself too.
(prescient-persist-mode +1)
Let's have prescient
and selectrum
work together.
(straight-use-package 'selectrum-prescient)
(with-eval-after-load 'selectrum
(selectrum-prescient-mode +1))
Consult
(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.
(with-eval-after-load 'consult
;; C-c bindings (`mode-specific-map')
(define-key acdw/map (kbd "C-c h") #'consult-history)
(define-key acdw/map (kbd "C-c m") #'consult-mode-command)
;; C-x bindings (`ctl-x-map')
(define-key acdw/map (kbd "C-x M-:") #'consult-complex-command)
(define-key acdw/map (kbd "C-x b") #'consult-buffer)
(define-key acdw/map (kbd "C-x 4 b") #'consult-buffer-other-window)
(define-key acdw/map (kbd "C-x 5 b") #'consult-buffer-other-frame)
(define-key acdw/map (kbd "C-x r x") #'consult-register)
(define-key acdw/map (kbd "C-x r b") #'consult-bookmark)
;; M-g bindings (`goto-map')
(define-key acdw/map (kbd "M-g g") #'consult-line)
(define-key acdw/map (kbd "M-g M-g") #'consult-line)
(define-key acdw/map (kbd "M-g o") #'consult-outline)
(define-key acdw/map (kbd "M-g m") #'consult-mark)
(define-key acdw/map (kbd "M-g k") #'consult-global-mark)
(define-key acdw/map (kbd "M-g i") #'consult-imenu)
(define-key acdw/map (kbd "M-g e") #'consult-error)
;; M-s bindings (`search-map')
(define-key acdw/map (kbd "M-s g") #'consult-grep) ; alts:
; consult-git-grep,
; consult-ripgrep
(define-key acdw/map (kbd "M-s f") #'consult-find) ; alts:
; consult-locate
(define-key acdw/map (kbd "M-s l") #'consult-line)
(define-key acdw/map (kbd "M-s m") #'consult-multi-occur)
(define-key acdw/map (kbd "M-s k") #'consult-keep-lines)
(define-key acdw/map (kbd "M-s u") #'consult-focus-lines)
;; Other bindings
(define-key acdw/map (kbd "M-y") #'consult-yank-pop)
(define-key acdw/map (kbd "<help> 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
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))
Integration with Selectrum
(advice-add #'marginalia-cycle :after
(lambda ()
(when (bound-and-true-p selectrum-mode)
(selectrum-exhibit))))
Completion
Hippie Expand
Before I install any completion framework, I want a good default for
completing. hippie-expand
fills that niche.
(define-key acdw/map (kbd "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 " ⱷ"
:keymap acdw/map)
(define-globalized-minor-mode acdw/global-mode acdw/mode acdw/mode)
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
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)
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-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-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
Garbage Collection Magic Hack package
Look, I'm not going to look too deeply into this. It's magic afer all.
(straight-use-package 'gcmh)
(gcmh-mode +1)
(blackout 'gcmh-mode)
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-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-language-environment "UTF-8")
(setq-default locale-coding-system 'utf-8
buffer-file-coding-system 'utf-8
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)
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
Auto-fill vs. Visual-line
I've mostly been using visual-line mode, and it's been pretty good.
There are some times, however, when lines are just … really long,
and they wrap weird or whatever. Not to mention, in Org mode,
visual-line-mode
screws up the bindings for line movement. So
here's what I'm going to do.
-
Enable
visual-line-mode
withtext-mode
, but not withorg-mode
.(defun hook--visual-line-mode () (unless (eq major-mode 'org-mode) (visual-line-mode +1))) (add-hook 'text-mode-hook #'hook--visual-line-mode)
-
Enable
auto-fill-mode
withorg-mode
.(add-hook 'org-mode-hook #'auto-fill-mode)
-
Just in case … let's "fix"
visual-line-mode
if we're inorg-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.
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)
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
Repeat popping the mark without repeating the prefix argument
(setq-default set-mark-repeat-command-pop t)
Undo package
Undo Fu
(straight-use-package 'undo-fu)
(define-key acdw/map (kbd "C-/") #'undo-fu-only-undo)
(define-key acdw/map (kbd "C-?") #'undo-fu-only-redo)
Undo Fu session
I'm not putting this in /acdw/emacs/src/branch/again/*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
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.
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.
(define-key acdw/map [remap query-replace] #'anzu-query-replace-regexp)
(define-key acdw/map [remap query-replace-regexp] #'anzu-query-replace)
(define-key isearch-mode-map [remap isearch-query-replace]
#'anzu-isearch-query-replace)
(define-key isearch-mode-map [remap isearch-query-replace-regexp]
#'anzu-isearch-query-replace-regexp)
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))))
Writing
Word count package
(straight-use-package 'wc-mode)
(add-hook 'text-mode-hook #'wc-mode)
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)
Be strict in prog-mode
(add-hook 'prog-mode-hook #'smartparens-strict-mode)
Formatting
Aggressive indent package
(straight-use-package 'aggressive-indent)
(global-aggressive-indent-mode +1)
(blackout 'aggressive-indent-mode)
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)
Language-specific
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))
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")
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 "-alh")
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)
(with-eval-after-load 'dired
(define-key dired-mode-map "i" #'dired-subtree-toggle))
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)
(define-key acdw/leader "g" #'magit-status)
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 wincred
(setenv "GIT_ASKPASS" "git-gui--askpass")
Forge
(straight-use-package 'forge)
(with-eval-after-load 'magit
(require 'forge))
Git file modes
(dolist (feat '(gitattributes-mode
gitconfig-mode
gitignore-mode))
(straight-use-package feat)
(require feat))
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
;; Source blocks
org-src-tab-acts-natively t
org-src-window-setup 'split-window-below ; could change this based on geom
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
;; Exporting
org-export-headline-levels 8
org-export-with-smart-quotes t)
Aesthetics
Prettify some other symbols
(defun acdw/org-mode-prettify ()
"Prettify `org-mode'."
(dolist (cell '(("[ ]" . ?☐) ("[X]" . ?☑) ("[-]" . ?◐)
("#+BEGIN_SRC" . ?✎) ("#+begin_src" . ?✎)
("#+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)
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 unpackaged
(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.
;; ((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))))
(unpackaged/org-element-descendant-of 'item context)) ; Element in list item, e.g. a link
;; 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 unpackaged
(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)
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 "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)
;; 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))
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-specific
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.
Linux (home)
Settings
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. Much of the ideas and scripts in this section come from termitereform on Github.
Settings
See also the GNU FAQ for Windows. At some point I should really dig into the multiple settings available for w32 systems.
(setq-default w32-allow-system-shell t) ; enable cmd.exe as shell
Scripts
Common variables
set HOME=%~dp0..\..
set EMACS=%HOME%\Applications\Emacs\bin\runemacs.exe
chdir %HOME%
Emacs Daemon
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>>
%EMACS% --daemon
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>>
set EMACSC=%HOME%\Applications\Emacs\bin\emacsclientw.exe
"%EMACSC%" -n -c -a "%EMACS%" %*
Emacs Safe Start
This runs Emacs with the factory settings.
<<w32-bat-common>>
"%EMACS%" -Q %*
Emacs Debug
This runs Emacs with the --debug-init
option enabled.
<<w32-bat-common>>
"%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>>
;;; 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
init.el
The classic Emacs initiation file.
Header
;;; init.el -*- lexical-binding: t -*-
<<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)
(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 (expand-file-name "straight/build/org"
user-emacs-directory)))
;; 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
(require 'org)
(org-babel-load-file 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; -*-
<<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!