-*- eval: (add-hook ‘after-save-hook #’org-babel-tangle nil t) -*-
There are two main approaches to writing your emacs configuration in org:
org-babel-load-file
- This approach is very simple to implement, but has to tangle each file when emacs starts.
org-babel-tangle
- This approach stores the tangled file on disk, but you have to remember to re-tangle after each edit.
We can use a local after-save-hook
to do the tangling
automatically. However, this file-local variable is not considered
safe by default, so emacs will prompt you the first time you open
this file. I recommend saying n
to the prompt and tangling
manually.
(add-to-list 'safe-local-eval-forms '(add-hook 'after-save-hook #'org-babel-tangle nil t))
Tangling produces a noticeable pause every time I save. This should be pushed off the main thread.
- https://www.reddit.com/r/emacs/comments/5ej8by/
- https://www.reddit.com/r/emacs/comments/8eozfl/advanced_techniques_for_reducing_emacs_startup/dxy5437/
- https://emacs.stackexchange.com/q/22722
We have to be careful about tangling with the org that was installed by straight, not the built-in one.
org-babel-tangle
is really not that fast. It (absurdly, but
perhaps unsurprisingly) opens the output file once for each
tangled block. The mere act of loading org is also expensive in
its own right. People have written custom functions that imitate
the behavior of org-babel-tangle
, but much faster. These are
probably worth exploring.
- http://www.holgerschurig.de/en/emacs-efficiently-untangling-elisp/
- http://endlessparentheses.com/init-org-Without-org-mode.html
Before I started using a tangled init, I could just edit init.el
directly and eval-buffer
to reload it. Having to
org-babel-load-file
and then select init.org
is noticeably
less convenient. I should make a wrapper for this. (And, while
we’re at it, the wrapper should execute inside a single straight
transaction, just like it would when init.el
is actually
loaded.)
I like straight philosophically - clones and lockfiles “feels” like the right way to solve this problem - and I don’t mind having to give up the built-in package management for those benefits. Extensive documentation is also greatly appreciated.
(setq load-prefer-newer t)
(setq straight-check-for-modifications '(find-when-checking))
(setq straight-use-package-by-default t)
(setq straight-cache-autoloads t)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 6))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(setq use-package-use-theme nil)
use-package is now part of emacs29, so I no longer have to install it. But for some reason, its custom theme behavior breaks some defcustoms in evil, so I disable it.
I disable straight’s automatic rebuilding and invoke
straight-rebuild-package
by hand. This avoids some nasty startup
delays (#9/#41). I don’t need to rebuild all that often, so I don’t
mind having to do it manually.
(use-package diminish
:defer t)
straight has partial shallow clone support. However, freezing is not compatible with shallow clones, so we’re still waiting for that before we can adopt it.
org introduces special complications with shallow clones. The
org-version
function is not defined in the org-mode git
repository; it’s supposed to be created by a Makefile. straight
uses a special hack to fix that, but the hacks depend on the
presence of tags in the cloned repository. Unfortunately, the
current shallow clone implementation excludes tag refs, which
causes org-version
to return an error from git describe
. (Tag
objects will be fetched, but the refs to refer to them by name
will not be created, and the refs are needed for git describe
.)
This creates another bad interaction with elfeed’s elfeed-link
feature, which tries to invoke org-version
when the org package
is loaded. There would be a few ways to solve this problem:
- Allow straight to preserve tags when shallowly cloning, maybe
configurable with the existing
:depth
key. - Override the recipe for elfeed to exclude
elfeed-link.el
from the build, since I don’t use that functionality. - Do a deep clone of org instead of a shallow one. This is what I’m actually doing, since shallow clones are missing some functionality as I mentioned above.
One other interesting caveat (again, demonstrated by org) is that
not all git servers support fetching specific commit refs. For
example, with --depth=1
, you can fetch 50fd89cb93 (the tag
object for org release_9.4
), but you can’t fetch fbccf09c74
(which is the actual commit the tag refers to). So using shallow
clones would restrict you to released versions only. This has
other complicated implications.
This keeps files like projectile-known-projects-file
where they
belong.
(use-package no-littering
:demand t)
general provides a unified interface for binding keys. I use SPC
and DEL
as my leaders, since my keyboard puts them under my left
and right thumbs.
(use-package general
:demand t
:config
(general-override-mode 1)
(general-create-definer private/with-leader
:prefix "SPC"
:non-normal-prefix "M-SPC"
:keymaps 'override
:states '(normal visual insert emacs))
(general-create-definer private/with-local-leader
:prefix "DEL"
:non-normal-prefix "M-DEL"
:states '(normal visual insert emacs)))
(use-package hydra
:defer t)
(use-package exec-path-from-shell
:if (eq system-type 'darwin)
:custom
(exec-path-from-shell-check-startup-files nil)
(exec-path-from-shell-variables '("PATH"
"MANPATH"
"GOPATH"))
:config
(exec-path-from-shell-initialize))
Thanks, Apple. This atrocious hack is dedicated to you.
This is for built-in emacs miscellany that I want to reconfigure or turn off. There’s quite a bit of stuff in here.
(setq revert-without-query '(""))
(global-auto-revert-mode 1)
(setq auto-save-default nil)
(setq auto-save-list-file-prefix nil)
(setq create-lockfiles nil)
(setq make-backup-files nil)
(setq initial-major-mode #'org-mode)
(setq initial-scratch-message nil)
(setq inhibit-startup-screen t)
(setq sentence-end-double-space nil)
(tool-bar-mode 0)
(menu-bar-mode 0)
(blink-cursor-mode 0)
(setq ring-bell-function 'ignore)
(setq line-number-display-limit nil)
(column-number-mode 1)
(setq frame-title-format "%b")
(setq save-interprogram-paste-before-kill t)
(setq global-hl-line-sticky-flag t)
(global-hl-line-mode 1)
(show-paren-mode 1)
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq uniquify-buffer-name-style 'forward)
(setq require-final-newline t)
(when (eq system-type 'darwin)
(setq ns-command-modifier 'meta)
(setq ns-option-modifier 'super))
(advice-add #'executable-make-buffer-file-executable-if-script-p :before-while
(lambda ()
(and buffer-file-name
(not (string-prefix-p "." (file-name-nondirectory buffer-file-name))))))
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
This useful built-in function makes a file executable if it starts
with a shebang. Unfortunately, this also hits my dotfiles (eg
.bashrc
), so I advise the function to skip any file with a
leading dot.
(cond ((eq system-type 'gnu/linux)
(set-face-attribute 'default nil :family "Input"
:height 100))
((eq system-type 'darwin)
(set-face-attribute 'default nil :family "Menlo"
:height 140)))
(set-face-attribute 'fixed-pitch nil :family 'unspecified
:inherit 'default)
I have tried many techniques to configure emacs faces:
set-frame-font
(or its deprecated cousin,set-default-font
) are horribly broken if you use emacs in daemon mode. Because the initial emacs instance doesn’t have a GUI attached to it, something goes horribly wrong at init time and the fonts just don’t get set (1, 2, 3, 4). You end up with text that’s literally a couple of pixels tall. By the way, this is also true for terminal-local variables likewindow-system
, which are not set at daemon initialization time.default-frame-alist
andwindow-system-default-frame-alist
provide an alist with a font key, which lets you specify a string to use as the default font. However, emacs faces are quite a bit more complicated than that. On top of that, emacs’s fontconfig parsing seems to be highly nonstandard. Normally, the patternFoo-10
(or equivalentlyFoo:size=10
) specifies the height as 10pt, where asFoo:pixelsize=10
aims for a height of 10px. But in emacs,Foo:size=10
andFoo:pixelsize=10
do the same thing. I also find very different results betweenfc-pattern
anddescribe-font
using the same pattern (egfc-pattern -d Input-10 pixelsize
reports 10.4167px on my current monitor, but if I useInput-10
in emacs,describe-font
shows the patternInput:pixelsize=13
).face-spec-set
lets you dig into the innards of an emacs face, but you have to specify the whole thing from start to finish. An emacs face actually has several layered attributes, and you probably don’t want to rewrite all of them just to change one or two.custom-set-faces
hooks into the Customize interface, which is the blessed high-level approach. However, Customize works by mutating your init file, which is not great if you’re an opinionated version control user.
After all of the above, I have settled on set-face-attribute
for
global faces. It lets me twiddle any individual part of any face
(the full list of attributes is here) without going through
Customize. For package-specific faces, use-package offers the
:custom-face
keyword, which goes through Customize while
avoiding its major downside.
visual-line-mode is a built-in mode that truncates lines at word boundaries. adaptive-wrap-mode extends it to also preserve leading indentation.
(setq-default truncate-lines t)
(setq visual-line-fringe-indicators '(left-curly-arrow nil))
(use-package adaptive-wrap
:hook
(visual-line-mode . adaptive-wrap-prefix-mode)
:diminish 'adaptive-wrap-prefix-mode)
I have not had positive experiences with this part of emacs:
- swiper, org, and visual-line-mode cause some very strange issues when used together
- apparently it doesn’t like variable-width fonts (see also)
- apparently it doesn’t like hard tabs either
I consider hard-filling paragraphs to be an ugly implementation detail that my editor is supposed to render irrelevant. It doesn’t help that auto-fill-mode is not applicable to everything I write. emacs is really not doing the job here.
(use-package generic-x
:straight nil
:custom
(generic-use-find-file-hook nil)
:demand t)
You can see that I set indent-tabs-mode to nil by default. I really do not like setting indentation behavior in my config. I used to use vim-sleuth and it was magical. You never had to tell it anything; it just knew what the right settings were. That’s what indentation configuration is supposed to feel like. I’ve heard that dtrt-indent can provide similar functionality for emacs. editorconfig support is also applicable to this problem.
I haven’t had to edit any “real” code in emacs yet, so remapping
org-return-indent
was sufficient for me, but I’d also like to
look into electric-indent-mode (built-in) or
aggressive-indent-mode to do this automatically.
I never really became fluent in vim, but my brief experience made it impossible to go back to any other editing system. The two big innovations of vim were:
- separate modes for binding commands and inserting text
- composable operators and text objects
I’m not married to anything specific in vim or evil besides those two principles, but nothing really comes close, and I’m not in the mood to roll my own version of evil right now.
(use-package evil
:custom
(evil-undo-system 'undo-redo)
(evil-want-Y-yank-to-eol t)
(evil-disable-insert-state-bindings t)
(evil-motion-state-modes nil)
:general
(:keymaps 'override
:states '(normal visual)
";" #'evil-ex
"s" #'save-buffer
"x" #'other-window
"r" #'universal-argument)
(:keymaps 'universal-argument-map
"r" #'universal-argument-more)
(private/with-leader
"SPC" #'execute-extended-command
";" #'eval-expression
"f" #'find-file
"b" #'switch-buffer
"h" #'help-command)
(private/with-leader
:infix "d"
"" '(:wk "desktops"
:ignore t)
"d" #'evil-switch-to-windows-last-buffer
"h" #'split-window-vertically
"v" #'split-window-horizontally
"x" #'delete-window
"b" #'kill-this-buffer
"k" #'kill-buffer-and-window)
(:keymaps 'minibuffer-local-map
"<escape>" #'minibuffer-keyboard-quit)
:hook
(private/evil-esc . (lambda ()
(when (minibuffer-window-active-p (minibuffer-window))
(abort-recursive-edit))))
:demand t
:config
(advice-add #'evil-force-normal-state :after
(lambda () (run-hooks 'private/evil-esc-hook)))
(evil-mode 1))
I have a custom hook for when you press ESC
in normal state,
which I stole from doom. I tend to mash ESC
when I want to get
back to regular editor behavior, and this hook serves as a
predictable entry point for that behavior.
Out of all the vim plugins in the world, surround is perhaps the only one that deserves to be built in. Naturally, there’s an evil version as well.
(use-package evil-surround
:demand t
:config
(global-evil-surround-mode 1))
I have also been intrigued by embrace. It has an integration for surround, but if I was going to use it, I’d rather roll a brand-new evil wrapper that doesn’t depend on surround at all.
(use-package which-key
:custom
(which-key-echo-keystrokes 0.01)
(which-key-idle-delay 0.5)
(which-key-idle-secondary-delay 0.01)
(which-key-popup-type 'minibuffer)
(which-key-show-prefix 'top)
(which-key-max-description-length nil)
(which-key-compute-remaps t)
(which-key-sort-order 'which-key-prefix-then-key-order-reverse)
:demand t
:config
(which-key-mode 1)
:diminish)
I could enable which-key-allow-evil-operators
and
which-key-show-operator-states
, but choose not to because the
popup is too large. There’s just too much information in there.
(use-package ws-butler
:custom
(ws-butler-keep-whitespace-before-point nil)
:demand t
:config
(ws-butler-global-mode 1)
:diminish)
(use-package ivy
:custom
(ivy-count-format "(%d/%d) ")
:general
([remap switch-buffer] #'ivy-switch-buffer)
(:keymaps 'ivy-minibuffer-map
"<escape>" #'abort-recursive-edit)
(private/with-local-leader
:keymaps '(ivy-occur-mode-map ivy-occur-grep-mode-map)
"DEL" #'ivy-occur-dispatch
"RET" #'ivy-occur-press-and-switch
"f" #'ivy-occur-press
"a" #'ivy-occur-read-action
"c" #'ivy-occur-toggle-calling
"d" #'ivy-occur-delete-candidate
"r" #'ivy-occur-revert-buffer)
(private/with-local-leader
:keymaps 'ivy-occur-grep-mode-map
"w" #'ivy-wgrep-change-to-wgrep-mode)
:demand t
:config
(ivy-mode 1)
:diminish)
(use-package counsel
:demand t
:config
(counsel-mode 1)
:diminish)
(use-package ivy-hydra
:commands (hydra-ivy/body))
(use-package swiper
:general
(private/with-leader
"/" #'swiper))
(use-package wgrep
:custom
(wgrep-auto-save-buffer t)
:general
(:keymaps 'wgrep-mode-map
[remap save-buffer] #'wgrep-finish-edit)
:commands (wgrep-change-to-wgrep-mode))
If I open ivy-hydra
and then close the minibuffer, the hydra is
actually still there. If I open the minibuffer, it becomes
apparent that the hydra was open the whole time, and is eating all
my keystrokes until I exit it with C-o
. The hydra should
terminate whenever the minibuffer closes.
This is a big topic, but I’m just going to stick it here because it’s all going through ivy one way or another.
swiper is my primary tool for structured find. It’s incremental
(ie it shows me where I’m going before I decide to go there) and
ephemeral (ie if I dismiss the minibuffer it leaves no traces of
its presence). One useful addition would be an easy way to resume
the previous swiper search. ivy-resume
, maybe? I also don’t
make much use of swiper-query-replace
(M-q
binding), which
seems useful.
I have experimented with isearch (which is hooked into evil’s /
by default). I find it most useful as a motion - ie when I already
know exactly what I’m looking for with very high specificity - but
avy works almost as well in those situations.
I don’t like using it for “searching”. Jumping around with nN
is
cumbersome, and often after a few jumps you realize that you
should have refined the search expression a bit more. With swiper,
you can just scroll the minibuffer, and if you need to narrow it
down, you can type in more text. I’m considering just binding
swiper directly to /
.
I find wgrep very useful for transitioning from search to replace.
The key sequences are not too difficult to remember: C-o
to
bring up hydra-ivy, u
to occur, and DEL w
to enable wgrep in
that buffer.
There’s probably some argument to be made for using rg (already projectile-integrated) in larger searches. We’ll see where that fits into the picture. I just haven’t used it enough yet. I believe the occur/wgrep system works just as well here as it does for swiper.
One thing I don’t like about counsel-projectile-rg
is that it’s
very difficult to constrain my search to a subfolder of the
project. Perhaps deadgrep, which is highly rg-native, would be a
good choice for a less incremental, more precise interface.
For smaller find/replaces, I still use vim’s trusty :s
(evil-ex-substitute
). The syntax of :s
lets you write the
find and replace halves of the expression simultaneously in a
very nimble way. Automatically reusing the last pattern from /
is also a nice feature, although a bit niche. I only feel the
need to do that when I’m replacing a fairly complex pattern,
which is usually a sign to reach for another tool.
Once you start replacing a lot of stuff (more than a screenful) or
really complicated stuff (anything involving eval-based
expressions), :s
becomes unpredictable and too cumbersome to use
off hand. It works best when its effects are transparent and
obvious.
Speaking of transparency, evil’s live preview for :s
is
extremely valuable. However, I’ve encountered some bugs with it
(typically when replacing leading whitespace) where the
preview markers don’t go away after the command is done.
It probably sounds like I like :s
and I’m happy with its place
in my workflow. For the most part, I am, but it’s literally the
only ex command I use regularly. If I can replace it with
something else, that lets me completely rebind ;:
to other
commands. visual-regexp or phi-search? My requirements:
- robust live preview
- edit find and replace sides simultaneously, ideally with similar
syntax to
:s
- a quick keybind to jump from find to replace or vice versa (useful in longer expressions)
- easy integration with swiper/rg and occur/wgrep, if you realize that you’re biting off more than you can chew
It’s also worth asking if we can scale :s
to multiple files. A
vim package that crossed my desk recently, and seems to have a
very interesting workflow, is ferret. Something similar could
probably be built on top of occur.
I’ve heard good things about iedit, and I’m also interested in multiple-cursors:
projectile with ivy integration
I mainly use projectile for fuzzy searching an entire project’s files and buffers. It’s quite refreshing to never think about which files are “open” and which ones aren’t. The concept of a “root” directory is also important for things like rg searching.
(use-package projectile
:custom
(projectile-ignored-project-function
(lambda (project-root)
(or (file-remote-p project-root)
(string-prefix-p (straight--dir) project-root))))
:demand t
:config
(put 'projectile-enable-caching 'safe-local-variable #'booleanp)
(put 'projectile-indexing-method 'safe-local-variable
(lambda (v) (member v '(native hybrid alien))))
(projectile-mode 1))
(use-package counsel-projectile
:general
(private/with-leader
:infix "p"
"" '(:wk "projectile"
:ignore t)
"f" #'counsel-projectile-find-file
"/" #'counsel-projectile-rg
"p" #'counsel-projectile-switch-project
"b" #'counsel-projectile-switch-to-buffer
"k" #'projectile-kill-buffers)
:demand t
:config
(counsel-projectile-modify-action
'counsel-projectile-switch-project-action
'((default counsel-projectile-switch-project-action-find-file)))
(counsel-projectile-mode 1))
Demanding projectile causes its autoloaded functions to be bound
under the C-c p
prefix. However, if counsel-projectile hasn’t
been loaded yet, the functions under that prefix will be
un-counseled versions (because counsel-projectile-mode
hasn’t
run). I fix this problem by demanding both packages up front.
I used to use counsel-projectile
, which lists buffers and files,
but have now moved to counsel-projectile-find-file
(with a
wrapper when not in a project). This way, I can always navigate to
a file by its project-rooted filename.
Consider a project with two files, foo/README
and bar/README
.
If I open foo/README
and then counsel-projectile
, I will see
README
(the buffer for foo/README
) and bar/README
. This
means there are no matches for foo/README
.
counsel-projectile-find-file
avoids this problem.
Another issue arises if you have two separate projects, foo
and
bar
, that each have their own README
. If both ~README~s are
open at the same time, the buffer names will be disambiguated by
uniquify, which will appear in counsel-projectile
. Again,
counsel-projectile-find-file
avoids this problem.
I also want counsel-projectile-switch-project
to use
counsel-projectile-find-file
as its action (the default action
selects a file or buffer, like counsel-projectile
). The
counsel-projectile-modify-action
function lets us make this
change in a reasonably ergonomic fashion.
I mainly use buffer switching to cycle between the last few files
I looked at. counsel-projectile supports sorting candidates, which
might reduce my dependence on that functionality. Perhaps a
binding for other-buffer
would also help.
projectile’s use of git-ls-files can lead to some strange behavior, because the list is based on the git index. This can lead to deleted files persisting, or duplicated listings for merge conflicts. I’m not actually sure there’s any way to get around this with a git-based command.
One of the unpleasant truths of vim is that, although there are
structured motions for everything, you’re probably going to start
out by holding down hjkl
a lot. It takes a long time for all
those other motions to seep into your muscle memory. avy provides a
command that quickly gets anywhere on the screen, regardless of how
the buffer is formatted. It reflects a “lazy vim” approach of using
cheap, general commands that you’ll never have to think about.
evil actually defines motion wrappers for avy. However, its wrappers are inclusive, and I vastly prefer exclusivity for “jump to first instance” motions, so I redefine them.
(use-package avy
:custom
(avy-all-windows nil)
:general
(:states '(motion)
"f" #'avy-goto-char-2)
:config
(evil-define-avy-motion avy-goto-char-2 exclusive))
One nice feature of vim-sneak is that, after your initial search, you can mash the key to go to the next or previous instance. Such behavior could also be useful here. It would be something like this:
- when you first press
fF
, you get prompted for the search argument (same as existing avy) - the matching candidates get highlighted under a trie (same as existing avy)
- typing the keys for that candidate jumps you to it (same as existing avy)
- after the first jump, mashing
fF
takes you to the next/previous instance of the same search argument - the jumplist only gets updated once for the entire search chain
Look into evil-snipe, perhaps?
Forget obtuse up/down/left/right-based window switching. It takes up a ton of binding space and it’s not even the fastest way to move around. ace-window lets you jump to any window with one key. You can hook into it to do a lot of other window-management-related things, but I use it for its barebones functionality, and it works like a charm.
(use-package ace-window
:custom
(aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
(aw-scope 'frame)
:custom-face
(aw-leading-char-face ((t (:foreground "red"
:height 3.0))))
:general
([remap other-window] #'ace-window)
:init
(setq aw-dispatch-alist '((?x aw-flip-window))))
You can do a lot of window-related stuff with aw-dispatch-alist
,
which could probably replace my entire SPC d
leader tree.
Definitely worth investigating. Integrating desktop management
keybinds (eg eyebrowse, see below) would also be appropriate.
shackle keeps temporary windows out of the way. emacs has a nasty tendency to spawn them in the first free window it can find, and if you have your windows laid out just right, that’s usually not what you wanted. I’m used to vim’s “help pops up at the bottom” approach, and shackle lets me have that.
(use-package shackle
:custom
(shackle-inhibit-window-quit-on-same-windows t)
(shackle-rules '((help-mode :select t
:popup t
:align below
:size 0.5)
(flycheck-error-list-mode :select t
:popup t
:align right
:size 0.3)
(compilation-mode :select t
:popup t
:align right
:size 0.5)
("*Local Variables*" :select t
:same t)))
:demand t
:general
(:keymaps 'special-mode-map
:states 'normal
"q" #'quit-window)
([remap quit-window] #'private/quit-window)
:config
(defun private/quit-window (arg)
(interactive "P")
(quit-window (if arg nil 'kill)))
(shackle-mode 1)
:diminish)
*Local Variables*
comes from hack-local-variables-confirm
.
I remap quit-window
so that it kills buffers by default instead
of burying them. Since evil has its own binding of q
in normal
state, that has to be mapped back to quit-window
.
ivy-occur
buffers should be shackled to the window they were
originally in. Jumping to candidates in the occur buffer should
also be shackled (with the option of opening them in another
window if explicitly requested, because sometimes that really is
what I want).
I rather envy doom-popups. This system hooks into evil’s normal
state ESC
to close the current window (if it is a popup), and to
close all open popups (if it is not a popup). The definition of
“popup” is applied through shackle.
This system has a few notable advantages. First, recycling ESC
for this feels appropriate and avoids changing the normal state
q
binding. In addition, if I had an easy way to close popups
without selecting them, I wouldn’t need as much :select t
in my
shackle rules.
(use-package flycheck
:general
(private/with-leader
:infix "y"
"" '(:wk "flycheck"
:ignore t)
"c" #'flycheck-buffer
"C" #'flycheck-clear
"v" #'flycheck-verify-setup
"x" #'flycheck-disable-checker
"RET" #'flycheck-explain-error-at-point
"r" #'flycheck-display-error-at-point
"y" #'flycheck-copy-errors-as-kill
"j" #'flycheck-next-error
"k" #'flycheck-previous-error
"l" #'flycheck-list-errors)
:hook
(org-src-mode . (lambda () (flycheck-mode 0)))
:demand t
:config
(put 'flycheck-ruby-executable 'safe-local-variable #'stringp)
(put 'flycheck-ruby-rubocop-executable 'safe-local-variable #'stringp)
(global-flycheck-mode 1))
Unfortunately, there’s no good way to run Flycheck across a tangled
file when editing just one of the many blocks in that file. This
leads to Flycheck getting very confused, so I turn it off in that
context only. Note that you do need a hook for this, because
flycheck-global-modes
only checks major modes and org-src-mode
is a minor mode.
(use-package org
:custom
(org-M-RET-may-split-line nil)
(org-blank-before-new-entry '((heading . nil)
(plain-list-item . nil)))
(org-startup-folded t)
(org-catch-invisible-edits 'smart)
(org-ellipsis "⤵")
(org-src-fontify-natively t)
(org-src-tab-acts-natively t)
(org-src-window-setup 'current-window)
(org-file-apps '(("pdf" . system)
(auto-mode . emacs)
(system . "xdg-open %s")
(t . system)))
:general
(:states '(insert emacs)
:keymaps 'org-mode-map
"RET" #'private/org-return-indent)
(private/with-local-leader
:keymaps 'org-mode-map
"h" '(private/hydra-worf/private/org-up-heading-safe
:wk "parent heading")
"j" '(private/hydra-worf/org-forward-heading-same-level
:wk "next heading")
"k" '(private/hydra-worf/org-backward-heading-same-level
:wk "prev heading")
"l" '(private/hydra-worf/private/org-goto-first-child
:wk "child heading")
"/" #'counsel-org-goto
"r" #'org-reveal
"e" #'org-edit-special
"x" #'org-export-dispatch
"RET" #'org-open-at-point
"o" #'private/org-meta-return-after
"O" #'private/org-meta-return-before)
(private/with-local-leader
:keymaps 'org-mode-map
:infix "z"
"" '(:wk "toggles"
:ignore t)
"h" #'org-toggle-heading
"i" #'org-toggle-item
"l" #'org-toggle-link-display)
(private/with-local-leader
:keymaps 'org-src-mode-map
"e" #'org-edit-src-exit)
:hook
(org-src-mode . evil-normalize-keymaps)
:config
(defun private/org-return-indent ()
(interactive)
(org-return t))
(defun private/org-meta-return-before (arg)
(interactive "P")
(beginning-of-line)
(org-meta-return arg)
(evil-append nil))
(defun private/org-meta-return-after (arg)
(interactive "P")
(end-of-line)
(org-meta-return arg)
(evil-append nil))
(defun private/org-up-heading-safe ()
(interactive)
(org-up-heading-safe))
(defun private/org-goto-first-child ()
(interactive)
(org-goto-first-child)
(org-reveal))
(defhydra private/hydra-worf ()
"navigate and move org headings"
("<tab>" org-cycle "cycle")
("h" private/org-up-heading-safe "parent")
("j" org-forward-heading-same-level "next")
("k" org-backward-heading-same-level "prev")
("l" private/org-goto-first-child "child"))
(advice-add #'org-element-property :after-until
(lambda (property element)
(and (eq (org-element-type element) 'src-block)
(eq property :language)
"fundamental"))))
(use-package htmlize
:defer t)
(use-package hydra-ox
:straight hydra
:general
([remap org-export-dispatch] #'hydra-ox/body))
Note that MELPA does not split hydra and hydra-ox into separate
packages, so straight doesn’t know how to install hydra-ox. It has
to explicitly be told that this package comes from the hydra repo.
I would prefer to straight-get-recipe
this, but hardcoding it is
basically the same thing.
I’m very fond of counsel-org-goto
. It Just Works, which can’t be
said for some of the things I tried in the past.
org has org-goto
built-in. However, I despise org’s “open
another buffer and fumble around in here” approach to navigation.
You can customize org-goto
to use ivy (org-goto-interface
and
org-outline-complete-in-steps
), but I found that it choked on
headlines with slashes in them. Perhaps it was an ivy bug.
Rather than investigate the slashes problem with org-goto
, I
tolerated counsel-imenu
for a while. You need to futz around
with some variables (imenu-auto-rescan
,
imenu-auto-rescan-timeout
) to make it rescan every time you use
it. The real problem is that it only displays leaf-level headings,
so you can’t jump directly to intermediate headings.
I’ve also heard of some other options like deft, orgnav, and
helm-org-rifle, but for now, counsel-org-goto
is so close to my
ideal implementation that I’m no longer shopping around. See also.
In my typical use of counsel-org-goto
, I search for the last
segment of the exact heading I’m aiming for. If that isn’t
specific enough, I end up having to backspace over my search
query and enter a higher-level heading first, to disambiguate.
For example, in a file with headings foo/bar/baz
and
foo/qux/baz
, I might search for baz
, then have to backspace
and search for bar baz
.
The solution to this problem would be to relax matching order, so
that baz bar
could match foo/bar/baz
.
ivy--regex-ignore-order
might be perfect for this.
By default, plain text in org is indented to match the level of
the headline. This is controlled by org-adapt-indentation
,
org-cycle-emulate-tab
, and my binding of org-return-indent
.
I actually like the indentation, because it helps distinguish
headlines (you can scan the left edge of the buffer to locate
them). It also increases the vertical density of my org files,
since I don’t need empty lines (org-blank-before-new-entry
) or
larger fonts to make the headlines stand out.
I want to use fundamental-mode in org-src blocks that have no
language, but there is no supported way to set a default language
for org-src blocks. However, you can hack it in by advising
org-element-property
. If org-element-property
returns nil for
an org-src block’s language, this advice will treat the block’s
language as fundamental instead.
Sometimes you’ll find your keymaps don’t work after changing modes
or buffers, yet mysteriously pressing ESC
gets them to behave.
This is usually because you need to invoke
evil-normalize-keymaps
, hence my hook for org-src-mode
.
A more powerful alternative to org-open-at-point
. This should
open the link at point (if any), and otherwise select one
avy-style. Note that org-return-follows-link
doesn’t work in
evil normal state.
worf Tree Mutation
It’s fine to use counsel-org-goto
for large jumps, but for
shorter movements, it’s much faster to go up or down headings.
worf has an especially elegant way of combining navigation and
mutation of org trees. Unfortunately it doesn’t play nice with
evil.
One important caveat of any up/down heading navigation is that it tends to pollute the jumplist. Ideally, you want to “enter” heading navigation mode, jump around headings freely, and add to the jumplist when you “exit” heading navigation mode. I used to have a hydra for this, and might rebuild it.
Some considerations for this development:
- movements:
- next heading:
- any level:
org-next-visible-heading
outline-next-visible-heading
outline-next-heading
- same level:
org-forward-heading-same-level
outline-forward-same-level
org-get-next-sibling
outline-get-next-sibling
org-goto-sibling
- any level:
- previous heading:
- any level:
org-previous-visible-heading
outline-previous-visible-heading
outline-previous-heading
- same level (note that, if we’re not on a heading, we want to
back up to the current heading, not the one before it):
org-backward-heading-same-level
: skips past current headingoutline-backward-same-level
: same problem asorg-backward-heading-same-level
org-get-last-sibling
: doesn’t actually restrict point to same-level headings (it returns nil but the point still moves, which is almost definitely a bug)outline-get-last-sibling
: same problem asorg-get-last-sibling
org-goto-sibling
: same problem asorg-backward-heading-same-level
- any level:
- parent:
org-up-heading-safe
org-up-heading-all
outline-up-heading
- child:
org-goto-first-child
- next heading:
- change:
- item:
ITEM org-metaleft
org-metadown
org-metaup
org-metaright
heading org-do-promote
org-move-subtree-down
org-move-subtree-up
org-do-demote
list org-outdent-item
org-move-item-down
org-move-item-up
org-indent-item
table org-table-move-column
org-table-move-row
org-table-move-row
org-table-move-column
- tree:
TREE org-shiftmetaleft
org-shiftmetadown
org-shiftmetaup
org-shiftmetaright
heading org-promote-subtree
org-drag-line-forward
org-drag-line-backward
org-demote-subtree
list org-outdent-item-tree
org-drag-line-forward
org-drag-line-backward
org-indent-item-tree
table org-table-delete-column
org-table-insert-row
org-table-kill-row
org-table-insert-column
- item:
- Can we use the ~:bind~ lambda to build bindings to the heads
with general (lambda gets invoked here)? Or do we have to
manually bind each head in
private/with-local-leader
? - We should have a toggle in the hydra to allow moving to invisible headings, which should default to off.
- Should we also operate on lists?
org-previous-item
andorg-next-item
can navigate up/down, but they put the cursor in a stupid position. There doesn’t appear to be a way to navigate up/down levels of a list. In addition,org-next-item
does nothing unless you’re already in a list. We may need to resort to parsing. - Similarly, support for tables would also be interesting, but there don’t appear to be good ways to jump “into” a table.
- We should print a message to the minibuffer if we try to move past the end of a direction. ~save-excursion~ might help for this.
- If existing org functions aren’t the right fit, maybe we can roll our own by parsing the file with org-element and om?
- heading state (default)
hjkl
(available outside hydra)- parent heading, down same level, up same level, child heading
v
- radio toggle between three states: always move to invisible, never move to invisible, only move to invisible if there is none visible (default)
<tab>
org-cycle
c
- enter heading change state
jk
- move subtree down, move subtree up
hl
- promote subtree, demote subtree
HL
- promote heading, demote heading
q
- go back to heading state
i
(available outside hydra)- enter list state
hjkl
- superlist, down same level, up same level, sublist
v
- radio toggle to enable moving to (and revealing) invisible items (default off)
<tab>
org-cycle
q
- go back to heading state
c
- enter list change state
jk
- move item tree down, move item tree up
hl
- outdent item tree, indent item tree
HL
- outdent item, indent item
q
- go back to list state
t
(available outside hydra)- enter table state
hjkl
- left cell, down cell, up cell, right cell
q
- go back to heading state
c
- enter table change state
jk
- move row down, move row up
hl
- move column left, move column right
JK
- insert row, delete row
HL
- delete column, insert column
q
- go back to table state
I hate typing out org keywords (#+BEGIN_SRC
, etc) by hand. You
can type them in lowercase (which I should really start doing), but
even better would be autocomplete for them. Autocompletion is
unfortunately a TODO in its own right, but perhaps we can hack up
an interim solution with ivy.
While I prefer working in org, sometimes you have to write markup that other people can edit, and org is really not usable in any editor but emacs. In those situations, Markdown is basically inevitable.
(use-package markdown-mode
:custom
(markdown-hide-urls t)
:mode "\\.md\\'"
:hook
(markdown-mode . visual-line-mode))
(use-package edit-indirect
:defer t)
The earliest incarnation of beancount-mode was a minor mode, so that it could be embedded in an org-mode file. The modern version is a major mode, but my beancount file still uses org-shaped stuff, so I use outshine to preserve the behavior I used to depend on. (beancount-mode includes some org-esque cycle functions, but I want other outshine functionality as well, like org-style behavior at the beginning of the buffer. So I use outshine’s implementation instead of beancount’s.) I might break the outshine bits into their own config if I ever use it in non-beancount contexts.
Not having full org-mode powers is a genuine downside. For example,
counsel-outline
has the same functionality as counsel-org-goto
,
but in an org buffer, it invokes org-goto-marker-or-bmk
, which
reveals the heading you’re jumping to if it’s hidden underneath
another heading. That reveal doesn’t happen in outline or outshine,
so you end up selecting the hidden text instead, and pressing TAB
expands the visible heading that you’re on instead of the actual
heading you jumped to.
I don’t use beancount alignment at all, so I advise away that part
of indent-line-function
.
(use-package beancount
:straight (:host github
:repo "beancount/beancount-mode"
:branch "main")
:custom
(beancount-use-ido nil)
:general
(:states '(normal insert emacs)
:keymaps 'beancount-mode-map
"C-c d" #'beancount-insert-date)
(private/with-local-leader
:keymaps 'beancount-mode-map
"b" #'private/beancount-balance-sheet
"q" #'beancount-query
"l" #'beancount-check
"x" #'beancount-context)
:mode ("\\.beancount\\'" . beancount-mode)
:hook
(beancount-mode . (lambda ()
(outshine-mode)
(goto-char (point-max))
(outline-show-entry)))
:config
(defun private/beancount-balance-sheet ()
(interactive)
(let ((compilation-read-command nil))
(beancount--run beancount-query-program
(file-relative-name buffer-file-name)
"select account, sum(units(position)) as position from clear where account ~ 'Assets'or account ~ 'Liabilities'group by account, currency order by account, currency")))
(advice-add #'beancount-align-number :override
(lambda (&rest r) ())))
(use-package outshine
:custom
(outshine-startup-folded-p t)
(outshine-cycle-emulate-tab t)
(outshine-org-style-global-cycling-at-bob-p t)
:general
(:states '(normal insert emacs)
:keymaps 'outshine-mode-map
"TAB" #'outshine-cycle)
(private/with-local-leader
:keymaps 'outshine-mode-map
"/" #'counsel-outline)
:diminish 'outline-minor-mode
:defer t)
beancount-mode
is rather anemic, and there’s a lot of stuff I
would like to improve:
- fontification of comments, strings, numbers, and commodities
beancount-account-regexp
does not recognize custom naming options (seebeancount-account-categories
)- autocompletion for accounts and payees
- clean auto align for the entire file, even for non-transaction
directives (
bean-format
can help, but it only aligns amounts) - Flycheck invocation of
bean-check
(use-package systemd
:defer t)
(use-package yaml-mode
:defer t)
The docs for this mode mention that you have to bind RET
yourself
if you want auto-indenting, but evil seems to have me covered
there.
Frankly, this mode is not very good, but that’s not its fault. It’s just that YAML is incredibly difficult to parse correctly. This leads to some delightful bugs which are probably never going to be fixed.
(use-package go-mode
:custom
(gofmt-show-errors nil)
:hook
(go-mode . (lambda () (add-hook 'before-save-hook #'gofmt-before-save nil t)))
:defer t)
We don’t want to add gofmt-before-save
to the global
before-save-hook
, because that would cause go-mode to be loaded
in every buffer, whether it was a go buffer or not. Instead we add
to the local before-save-hook
. We then have to explicitly request
deferred loading. Normally :hook
implies :defer t
, but only if
the target of the hook is a function symbol. If it’s a lambda, then
use-package will resort to its default behavior of demanding the
package, to ensure that the package is loaded when the lambda runs.
In our case, we know the lambda doesn’t need that, so we can safely
ask for deferral.
(use-package go-eldoc
:hook
(go-mode . go-eldoc-setup))
See also: company-go.
(use-package rust-mode
:custom
(rust-format-on-save t)
:defer t)
(use-package flycheck-rust
:hook
(rust-mode . flycheck-rust-setup))
See also: racer.
(setq ruby-insert-encoding-magic-comment nil)
See also: enhanced-ruby-mode and robe.
(use-package elfeed
:general
(:keymaps 'elfeed-search-mode-map
:states 'normal
"q" (lambda ()
(interactive)
(elfeed-db-save)
(kill-this-buffer)))
(private/with-local-leader
:keymaps 'elfeed-search-mode-map
"g" #'elfeed-search-update--force
"G" #'elfeed-search-fetch
"RET" #'elfeed-search-browse-url
"y" #'elfeed-search-yank
"s" #'elfeed-search-live-filter
"S" #'elfeed-search-set-filter
"u" #'elfeed-search-tag-all-unread
"r" #'elfeed-search-untag-all-unread)
:defer t
:config
(let ((opml (no-littering-expand-var-file-name "elfeed/elfeed.opml")))
(when (file-exists-p opml)
(elfeed-load-opml opml))))
I actually don’t read feed items in emacs at all. I vastly prefer the rendering of my browser and would prefer to handle all my feeds there. Unfortunately, my old feed reader (Sage++) died in the Firefox 57 WebExtensions migration, and I have yet to find anything remotely satisfactory to replace it. While I plan to write my own feed reader someday, elfeed is a pretty reasonable feed organizer, and it lets me do the reading in the browser, so it’ll do for now.
I don’t want to store my feeds list in git, so I currently load it
from an OPML file rather than using elfeed-feeds
. There is
probably a good way to store elfeed-feeds
in a separate file
(similar to projectile-known-projects-file
) but I haven’t
bothered to implement it yet.
I didn’t even know elfeed-db-compact
existed until very
recently. It greatly reduces the number of stray inodes running
around in my no-littering var directory. I was going to run it on
a hook whenever I exited elfeed, but it seems to be quite slow. If
I hook it to kill-emacs-hook
, it might not get run depending on
how emacs terminates. I’ll have to figure out some kind of
automation here.
- https://github.com/emacs-tw/awesome-emacs
- https://github.com/hlissner/doom-emacs
- https://github.com/noctuid/evil-guide
- https://github.com/jojojames/evil-collection
Spacemacs layers for various languages can give us useful direction on this subject.
The Language Server Protocol gives me hope that my editor will stop being completely terrible some day. A list of implementations can be found here. More thoughts here.
Here.
See nov.el.
See company-shell.
See elpy, anaconda-mode, company-anaconda, and yapfify. (elpy vs anaconda: further reading.)
An excellent write up on this topic is here. Opening a file runs
normal-mode
, which invokes hack-local-variables
to set dir and
file locals. But when a new major mode is run, the call chain
propagates up to its parent, fundamental-mode
, which runs
kill-all-local-variables
. hack-local-variables
doesn’t get
called again, so the local variables are lost.
You can add hack-local-variables
to
after-change-major-mode-hook
to ensure that it gets rerun after
any major mode change. However, normal-mode
also runs
set-auto-mode
, which performs major mode autodetection and also
triggers that hook. So if you add hack-local-variables
to that
hook, then normal-mode
will run it twice. It’s unclear if this is
actually harmful, but it’s probably wrong.
The solution in that Stack Overflow answer is to add
hack-local-variables
to the hook, but with a flag to skip it.
Then you advise normal-mode
to set the flag, so when
set-auto-mode
triggers the hook, hack-local-variables
gets
skipped. normal-mode
will then invoke hack-local-variables
directly to achieve the original effect. Meanwhile, other major
mode changes will run the hook with the flag unset, so
hack-local-variables
will be rerun as desired.
I like the concept of this solution, but it also feels ugly. Maybe
there’s a way to add some :before-while
advice to
hack-local-variables
, to achieve the same thing without a custom
flag. Needs more investigation.
Note that, if a file’s major mode is configured by a local
variable, rerunning hack-local-variables
makes it impossible to
change that major mode manually. If you attempted to do so,
hack-local-variables
would detect the local variable and
immediately change the mode back. Maybe we could add a flag to
hack-local-variables
to skip major modes. (It currently has a
flag that does the opposite - major modes only.)
I’m pretty happy with the built-in emacs modeline in terms of information, but it doesn’t look flattering. Could use some customization. Matching improvements for frame title would also be appropriate.
- https://www.reddit.com/r/emacs/comments/6ftm3x/
- telephone-line
- powerline/spaceline
- smart-mode-line
- moody
Automatic pair insertion saves a lot of time and generally reduces the cognitive load of keeping parentheses matched. As emacs is a lisp-heavy environment, a number of specialized packages exist specifically for lisp’s uniquely paren-intensive requirements. An interesting overview was written here. Much ink has been shed on this topic, such as here.
- paredit
- parinfer
- smartparens
- paxedit
- lispyville
- evil-cleverparens
- xah also has some interesting thoughts here
While we’re on the subject of lisp, it would be nice to fix
indentation of keyword blocks, as described here. One example of
this in my config is in the :general
sections of my use-package
forms.
Outside of lisp, it’s still useful to have automatic pairs, but you don’t really need anything else. Besides smartparens, there’s also the built-in electric-pair-mode.
It would also be nice if evil’s %
motion worked with arbitrary
pairs, like in vim. That functionality can be achieved with
evil-matchit.
emacs has two built-in commenting functions, comment-dwim
and
comment-line
. There are some packages as well:
Autocompletion is a huge time saver and can eliminate a lot of “whoops I forgot that argument’s type” brain cycles. Unfortunately, the situation in emacs is not great. There are two main implentations, company and auto-complete. Some other interesting thoughts here.
Obviously the elephant in this room is magit, with support from other packages like magithub and evil-magit. Some other important considerations:
I also want good gist support, which I believe is built into magit, but there are also some interesting third-party alternatives, like webpaste.
My goal is to have window arrangements segregated by project, like persp-projectile. However, you need to have desktop management first to implement that, so I’m looking at using eyebrowse with some hand-rolled projectile integration. It’s also worth exploring wconf, or the built-in winner-mode. Also: zoom, purpose.
I’m pretty comfortable with emacs’s default scrolling behavior, but here are some packages to investigate:
I use ranger as my file manager these days. Theoretically, there’s no reason I couldn’t do that in emacs instead. However, vanilla dired is not fun. It’s a pain to teach dired to open things in their native programs rather than in emacs. So there’s a lot of work that needs to be added here:
- wdired (built-in, similar to ranger’s bulkrename)
- ztree
- ranger.el
- dired-hacks
In practice, I vastly prefer navigating projects with recursive fuzzy search, as already provided by counsel-projectile. But there’s something to be said for an interactive file tree when exploring a project whose structure you don’t yet know. emacs has a number of options here:
- neotree
- direx
- treemacs
- project-explorer (appears unmaintained)
I grudgingly use ncmpcpp as my mpd client right now, but its interface is not customizable enough for my tastes. I would like a tree by genre/album/track/artist in that order (cmus has a tree, but it’s artist/album only with no other options). What better place to implement a highly customizable text-based UI than emacs?
- rich-minority (I currently use the diminish integration in use-package)
- when hydra is not enough: transient
- crux
- vlf
- super-save
- yasnippet
- keyfreq
- hungry-delete and/or smart-hungry-delete
- mwim
- zenburn (I should actually implement pallor in emacs)
- autothemer
- auto close minibuffer
- tools for fontification debugging: font-lock-studio, font-lock-profiler, highlight-refontification, face-explorer, faceup
- more text objects: exato, targets
- so-long-mode
- highlight-indent-guides