Git hosting
Clone
git clone https://depp.brause.cc/dotemacs.git
Files
Size | Path |
---|---|
etc/ | |
theme/ | |
unpublished/ | |
225 | INSTALL |
README.org -> init.org | |
66622 | TODO.org |
1210 | UNLICENSE |
2808 | init.el |
133320 | init.org |
1721 | install.el |
README.org
Table of Contents
- 1. Preface
- 2. Init
- 2.1. User Interface
- 2.2. Emacs annoyances
- 2.2.1. Memory Management
- 2.2.2. Fix
line-number-mode
- 2.2.3. GnuTLS
- 2.2.4. Scratch
- 2.2.5. Initial buffer
- 2.2.6. Find C functions
- 2.2.7. Shorten Yes/No prompts
- 2.2.8. Open URLs with
xdg-open
- 2.2.9. Zero out default splitting tresholds
- 2.2.10. Unique buffer names
- 2.2.11. Inhibit
custom
littering my init file - 2.2.12. Display .nfo files with appropriate code page
- 2.2.13. Fix scrolling
- 2.2.14. Indent with spaces by default
- 2.2.15. Manage Backup, autosave and lock files
- 2.2.16. Allow for multiple Emacs daemons
- 2.2.17. Stop pasting at the mouse click point
- 2.2.18. Display buffer name in frame titles
- 2.2.19. Disable parentheses blinking on entering a match
- 2.2.20. Display fringe indicators and fix line movement in
visual-line-mode
- 2.2.21. Enable every deactivated command
- 2.2.22. Save clipboard data of other programs in the kill ring when possible
- 2.2.23. Make recentering behave more similiar to other programs
- 2.2.24. Make
kill -USR1
do something useful - 2.2.25. Don't use dialog boxes
- 2.2.26. Don't print integers in multiple formats
- 2.2.27. Inspect complicated data
- 2.2.28. Print backtraces in a lispier form
- 2.2.29. Display raw bytes as hex
- 2.2.30. Disable signal handler insanity
- 2.2.31.
comment-dwim
isn't DWIM enough - 2.2.32. Unconditionally kill subprocesses at exit
- 2.2.33. Sentence spacing
- 2.3. Packages bundled with Emacs
- 2.3.1.
recentf
- 2.3.2.
savehist
- 2.3.3.
save-place
- 2.3.4.
windmove
- 2.3.5.
bookmark
- 2.3.6.
ediff
- 2.3.7.
dired
- 2.3.8.
tramp
- 2.3.9. FFAP
- 2.3.10. Calendar
- 2.3.11.
org-mode
- 2.3.12.
comint
- 2.3.13.
shell
- 2.3.14.
eshell
- 2.3.15. CC-Mode
- 2.3.16.
eldoc-mode
- 2.3.17. Emacs Lisp
- 2.3.18. Scheme
- 2.3.19. Common Lisp
- 2.3.20. NXML
- 2.3.21. CSS
- 2.3.22. Python
- 2.3.23. Etags
- 2.3.24. Info
- 2.3.25. nroff
- 2.3.26. sql-mode
- 2.3.27. VC
- 2.3.28. JS
- 2.3.29. shell-script-mode
- 2.3.30. Edebug
- 2.3.31. Sieve
- 2.3.32. yow
- 2.3.33. rst-mode
- 2.3.34. calculator
- 2.3.35. Flymake
- 2.3.1.
- 2.4. Packages outside Emacs
- 2.4.1. smex
- 2.4.2. CSV
- 2.4.3. Quelpa
- 2.4.4. shackle
- 2.4.5. eyebrowse
- 2.4.6. company-mode
- 2.4.7. dash
- 2.4.8. helm
- 2.4.9. flycheck
- 2.4.10. Hydra
- 2.4.11. CIDER
- 2.4.12. web-mode
- 2.4.13.
company-tern
- 2.4.14. enh-ruby-mode
- 2.4.15. AUCTEX
- 2.4.16. smartparens
- 2.4.17. Evil
- 2.4.18. yasnippet
- 2.4.19. Circe
- 2.4.20.
hl-todo
- 2.4.21.
macrostep
- 2.4.22.
visual-fill-column
- 2.4.23.
magit
- 2.4.24. nov.el
- 2.4.25. alda-mode
- 2.4.26. rainbow-delimiters-mode
- 2.4.27. Elpher
- 2.4.28. explain-pause-mode
- 2.5. Keybinds
- 2.6. Hooks
- 2.7. Other software
- 2.8. Load unpublished packages
- 2.9. Set up unpublished packages
- 2.10. Post-Init
- 3. Stuff to do
- 4. Epilogue
1. Preface
This is an experiment to find out whether literate configurations in a single monolithic org-mode file actually have a perceivable benefit.
I'm using this configuration on a system running Arch Linux and Emacs
28.1, that's why this configuration will be using the latest and
greatest features and not check for the system it's running on.
While I do use emacsclient
for most of my editing needs, I
occasionally open emacs
instances for things like IRC or testing
purposes.
To debug it, I've added a properties drawer to the following subtree
that exports the code blocks to .emacs
with M-x org-babel-tangle
.
Why .emacs
? This file is kickstarted by init.el, so the other init
file location was chosen.
There's some more files of interest:
Please don't use this Emacs configuration as is. I suggest you to
study it instead and inspect the many variables involved with the
built-in help system by hitting F1 v
.
Here's a list of other literate Emacs configurations I've drawn inspiration from:
2. Init
2.1. User Interface
Emacs comes with a set of pretty wonky defaults, since the UI is what
we'll see first, we'll deal with it swiftly. Part of its
configuration can be set up in ~/.Xresources
and has the effect to
be used before any frame is displayed for the price of less
flexibility. For experimentation, reload such configuration with
xrdb ~/.Xresources
. Keep in mind that this hack might not do
anything for you on a sufficiently fast machine that loads themes
faster than you can notice.
2.1.1. Superfluous UI elements
I prefer not dealing with menu bars, tool bars and scroll bars and
deactivate them in ~/.Xresources
:
Emacs.menuBar: off Emacs.toolBar: off Emacs.verticalScrollBars: off
2.1.2. Stop cursor blinking
This used to give me nightmares, especially with the 24.4 addition that made the cursor blink ten times, then stops blinking until moving it again.
Emacs.cursorBlink: off
2.1.3. Less jarring background change
The default background color chosen is white, however I'm using a dark theme. Changing the background color after frame creation results in flashing, therefore I modify the background color to equal the one of the theme I'm going to load later.
Emacs.background: #002b36
The only other UI element left that stays visible throughout the
entire init time would be the mode line. It took me a bit of puzzling
to figure out the right format for making it look the same as in my
theme, but I eventually figured out from reading the last paragraph of
the (emacs) Table of Resources
info node that it does read in a list
from a string by studying the sources of faces.el
and xfaces.c
.
While this sounds kind of obvious, it means one can debug errors for
more complex values like the one for the box by simply invoking
read-from-string
on them.
Emacs.mode-line.attributeForeground: #93a1a1 Emacs.mode-line.attributeBackground: #002b36 Emacs.mode-line.attributeBox: (:line-width 1 :color "#073642")
2.1.4. Load theme
I wrote a custom theme and want it to apply on each created frame:
(add-to-list 'custom-theme-load-path "~/code/elisp/mine/punpun-theme") (add-to-list 'load-path "~/code/elisp/mine/punpun-theme") (defun my-load-theme (&optional frame) (with-selected-frame (or frame (selected-frame)) (load-theme 'punpun-light t))) (my-load-theme) (add-hook 'after-make-frame-functions 'my-load-theme)
Let's disable questions about theme loading while we're at it.
(setq custom-safe-themes t)
Tooltips can be themed as well.
(setq x-gtk-use-system-tooltips nil)
2.1.5. Improve the mode line
The mode line is essentially a huge nested list of mode line items you can customize to achieve your prefered look. The simplest way of getting a more useful mode line is using the smart-mode-line package which comes with more useful and color-coded items and a few interesting features like shortening file names and minor modes.
(setq sml/theme 'respectful sml/mode-width 'full sml/name-width '(0 . 20) sml/replacer-regexp-list '(("^~/notes/" ":N:") ("^~/\\.emacs\\.d/" ":ED:")))
However I prefer explicitly whitelisting the flycheck/flymake/flyspell family of minor modes.
(setq rm-whitelist "Fly.*")
2.1.6. Disable advertisements
The first obvious thing one notices upon launching an uncustomized Emacs is a rather fancy splash screen that informs you about the usage and advertises for the GNU project. I did eventually grow annoyed by it.
(setq inhibit-startup-screen t)
A less obvious one is the advertisement message displayed after
successful startup in the echo area. The culprit behind it is
display-startup-echo-area-message
and goes great lengths to make
sure it's seen by first checking whether the
inhibit-startup-echo-area-message
has been set by the customize
system to your user name, then scanning your init file with a regular
expression for it. Considering I dislike using the customize
system, don't have a conventional init file and find this pretty
silly, I disable this behaviour entirely by redefining the function to
display a bit more encouraging message instead.
(defun display-startup-echo-area-message () (message "Let the hacking begin!"))
2.1.7. Adjust keystroke echo timeout
This is a built-in feature I didn't expect to be useful. If you type part of keybind, Emacs will display this part in the echo area after a timeout. One second is a bit too long though for my taste.
(setq echo-keystrokes 0.5)
2.1.8. Customize font-specific faces
I can't put these into every theme I use, so it's time to change the
special user
theme:
(custom-theme-set-faces 'user '(default ((t :font "DejaVu Sans Mono:pixelsize=14"))) '(variable-pitch ((t :family "Cambria" :height 1.3))))
2.2. Emacs annoyances
Every file stating "This file is part of GNU Emacs." is more often than not a source of code that may be crufty, nausea-inducing or just having weird defaults that I need to correct.
2.2.1. Memory Management
800 KiB heap size makes for major pauses during init, so we'll temporarily increase it, then reset it after init is done to minimize GC pause times.
(setq gc-cons-threshold 10000000)
2.2.2. Fix line-number-mode
line-number-mode
displays the current line number in the mode line,
however it stops doing that in buffers when encountering at least one
overly long line and displays two question marks instead. This is
pretty unhelpful, the only workaround I've been able to find was to
increase line-number-display-limit-width
to a substantially higher value.
(setq line-number-display-limit-width 2000000)
See also this question on the Emacs SE.
2.2.3. GnuTLS
I have no idea why, but apparently you get nasty warnings by the GnuTLS library when using https with the default settings. Increasing the minimum prime bits size to something safer alleviates that.
Another fun thing is that it allows legacy algorithms like 3DES by default, so let's disable them as well.
(setq gnutls-min-prime-bits 2048) (setq gnutls-algorithm-priority "SECURE128")
2.2.4. Scratch
Since the *scratch*
buffer is pretty hard-wired into Emacs (see
buffer.c
), the least we could do is getting rid of its initial
message. No, it's using its own mode instead of emacs-lisp-mode
for
the questionable benefit of having a function inserting evaluation
values after a newline.
(setq initial-scratch-message "") (setq initial-major-mode 'emacs-lisp-mode)
2.2.5. Initial buffer
However I don't want to see the scratch buffer, let's display our notes file instead as daily reminder what's left to do.
(setq remember-notes-bury-on-kill nil) (setq remember-notes-initial-major-mode 'org-mode) (setq initial-buffer-choice 'remember-notes)
There is a bit of mismatch between the keybindings of
remember-notes-mode
and org-mode
, so let's fix that:
(with-eval-after-load 'remember (define-key remember-notes-mode-map (kbd "C-c C-c") nil))
2.2.6. Find C functions
There's a fair number of Emacs functions that aren't written in Emacs Lisp (see these statistics). To be able to locate them, it's necessary to grab a tarball of the sources at http://ftp.gnu.org/gnu/emacs/ and put it into your prefered location.
(setq find-function-C-source-directory "~/code/emacsen/emacs-27.1/src")
2.2.7. Shorten Yes/No prompts
Per default you're required to type out a full "yes" or "no" whenever
the function yes-or-no-p
is invoked. Fixing this used to require
swapping it out for y-or-n-p
, but no longer does as of Emacs 28.1:
(setq use-short-answers t)
2.2.8. Open URLs with xdg-open
I've set up xdg-open
to use my prefered browser for HTTP and HTTPS
URLs. Emacs claims to detect whether my system can use it, however
this fails because I don't have a popular DE up and running (I kid you
not, look at browse-url-can-use-xdg
and how it replicates that part
from the xdg-open
script).
(setq browse-url-browser-function 'browse-url-xdg-open)
2.2.9. Zero out default splitting tresholds
I have no idea how this actually works, but it seems to make Emacs prefer doing a horizontal split over a vertical split on wide screens.
(setq split-height-threshold 0
split-width-threshold 0)
2.2.10. Unique buffer names
This shouldn't be necessary since I'm already using smart-mode-line
,
however it's better to use a less confusing style than the default
that puts brackets around the buffer names shared in Emacs.
(setq uniquify-buffer-name-style 'forward)
2.2.11. Inhibit custom
littering my init file
For whatever reason the customization system will write into your init file which is especially annoying if you have it in version control like I do. It's reasonably simple to deactivate this behaviour by customizing customize into using a dedicated file, however you'll need to both delete the lines it wrote and load it later to make it aware it has already been loaded successfully.
(setq custom-file "~/.config/emacs/etc/custom.el")
2.2.12. Display .nfo files with appropriate code page
Since Emacs auto-detection of encodings is quite good, but not omniscient, we'll give it a nudge to display these files the way they're supposed to be.
(add-to-list 'auto-coding-alist '("\\.nfo\\'" . ibm437))
2.2.13. Fix scrolling
Half-page scrolling is great at reducing bandwidth, but is very jarring when done automatically. The following settings will make Emacs scroll line by line, without scrolloff and try to keep point at the same visual place when scrolling by page.
I used to have scrolloff enabled here with the scroll-margin
variable, but it introduced pretty nasty scrolling behaviour for large
files, so I no longer do.
(setq scroll-conservatively 10000
scroll-preserve-screen-position t)
2.2.14. Indent with spaces by default
Most programming languages I work with prefer spaces over tabs.
Note how this is not a mode, but a buffer-local variable.
(setq-default indent-tabs-mode nil)
2.2.15. Manage Backup, autosave and lock files
Backup files are created on save in the same directory as the file and
end in ~
. They can be numbered which makes most sense combined with
a different save location and automatic pruning.
(setq backup-directory-alist '(("." . "~/.config/emacs/backup"))) (setq version-control t) (setq delete-old-versions t)
Autosave files are created between saves after a sufficient timeout in
the current directory for crash detection, they begin and end with
#
. Let's change their save location as well.
(setq auto-save-list-file-prefix "~/.config/emacs/autosave/") (setq auto-save-file-name-transforms '((".*" "~/.config/emacs/autosave/" t)))
Lock files are put in the same directory to detect multiple users trying to edit the same file. Given that I'm on a single-user system, I can migrate them elsewhere as of Emacs 28.1.
(setq lock-file-name-transforms '((".*" "~/.config/emacs/lock/" t)))
2.2.16. Allow for multiple Emacs daemons
Although I'm pretty sure I won't make use of this, I prefer using
local TCP connections over socket files. Another benefit of this
setting is that it would allow me to make use of emacsclient
to access a remote Emacs daemon.
(setq server-use-tcp t)
2.2.17. Stop pasting at the mouse click point
Middle-clicking is nice to paste, however it should not adjust point and paste at the then adjusted point.
(setq mouse-yank-at-point t)
2.2.18. Display buffer name in frame titles
The default is to display the invocation name and host. Changing that
to use a different separator and the buffer name is trivial, however
there's still an annoying space in front when using M-:
. Regular
expressions to the rescue!
(setq frame-title-format '("" invocation-name ": " (:eval (replace-regexp-in-string "^ +" "" (buffer-name)))))
2.2.19. Disable parentheses blinking on entering a match
This will be done by a different package anyways, therefore we don't need it.
(setq blink-matching-paren nil)
2.2.20. Display fringe indicators and fix line movement in visual-line-mode
It's pretty nice to have the option to display words in a buffer as if they were hardwrapped around the word boundaries, however it's confusing to not have any fringe indicators.
(setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))
I don't like the remappings done to operate on visual lines (for
C-a
, C-e
and C-k
), so I'm just undefining them.
(setcdr visual-line-mode-map nil)
2.2.21. Enable every deactivated command
The rationale for this default seems to be to avoid confusion for beginners, I personally find it kind of annoying that Emacs of all editors does this kind of thing and doesn't offer a straightforward option to disable it even.
(setq disabled-command-function nil)
2.2.22. Save clipboard data of other programs in the kill ring when possible
I hope the necessity of this will be gone once Wayland is a viable option for me.
(setq save-interprogram-paste-before-kill t)
2.2.23. Make recentering behave more similiar to other programs
Try it out yourself by hitting C-l
, it will start with the top
instead of the middle row.
(setq recenter-positions '(top middle bottom))
2.2.24. Make kill -USR1
do something useful
A lesser known fact is that sending the USR2
signal to an Emacs
process makes it proceed as soon as possible to a debug window.
USR1
is ignored however, so let's bind it to an alternative
desirable function that can be used on an Emacs instance that has
locked up.
(defun my-quit-emacs-unconditionally () (interactive) (my-quit-emacs '(4))) (define-key special-event-map (kbd "<sigusr1>") 'my-quit-emacs-unconditionally)
2.2.25. Don't use dialog boxes
Clicking on an install button for instance makes Emacs spawn dialog boxes from that point on.
(setq use-dialog-box nil)
2.2.26. Don't print integers in multiple formats
I'm not sure why this is handled this way in the first place. Turns
out that eval-expression-print-format
is responsible and signals a
non-integer by returning nil
.
(fset 'eval-expression-print-format 'ignore)
2.2.27. Inspect complicated data
When using F1 v
or friends to view complicated data (such as an Org
parse buffer), Emacs can take a long time to finish. The data-debug
library offers a replacement for M-:
for that:
(require 'data-debug) (global-set-key (kbd "C-:") 'data-debug-eval-expression)
2.2.28. Print backtraces in a lispier form
I wrote a patch to fix the oversight of function calls in a backtrace not being a list. The following will only work in Emacs 26:
(setq debugger-stack-frame-as-list t)
2.2.29. Display raw bytes as hex
Another patch of mine that will have effect in Emacs 26. Name says it all.
(setq display-raw-bytes-as-hex t)
2.2.30. Disable signal handler insanity
Emacs tries saving your buffers if it receives a fatal signal (including module segfaults). This is batshit insane, I prefer a clean exit over silent corruption. The following setting is supposed to make it so:
(setq attempt-stack-overflow-recovery nil) (setq attempt-orderly-shutdown-on-fatal-signal nil)
2.2.31. comment-dwim
isn't DWIM enough
All IDEs I've tried have a comment toggle command (typically mapped to
C-/
), however it only does operate on full lines instead of adding
an in-line comment if no region is selected. While Emacs already has
the comment-line
command, it doesn't work correctly with Evil, so I
wrote my own version of it:
(defun my-comment-dwim () (interactive) (if (use-region-p) (comment-or-uncomment-region (region-beginning) (region-end)) (comment-or-uncomment-region (line-beginning-position) (line-end-position)))) (global-set-key (kbd "M-;") 'my-comment-dwim)
2.2.32. Unconditionally kill subprocesses at exit
(setq confirm-kill-processes nil)
2.2.33. Sentence spacing
RIP double-spacing sentences.
(setq sentence-end-double-space nil)
2.3. Packages bundled with Emacs
This includes stuff that is bundled with Emacs and can be obtained
from a more recent source as well, such as org-mode
. I'm mostly
refering to smaller packages though.
2.3.1. recentf
recentf-mode
allows you to access the list of recent files which can
be used by ido
and helm
. Let's save its file somewhere else and
change the size of its history while we're at it.
(setq recentf-save-file "~/.config/emacs/etc/recentf" recentf-max-saved-items 50)
Editing with emacsclient
records files I'd rather not have there:
(setq recentf-exclude '("^/dev/shm/.*$"))
2.3.2. savehist
The history of prompts like M-:
can be saved, but let's change its
save file and history length first.
(setq savehist-file "~/.config/emacs/etc/savehist" history-length 150)
2.3.3. save-place
I didn't expect to like this functionality, but it's pretty neat to start from the last place you were in a file the next time you visit it. Asides from putting the save file somewhere else, I have to enable this behaviour for every buffer since it's buffer-local.
(setq save-place-file "~/.config/emacs/etc/saveplace")
2.3.4. windmove
The windmove
provides useful commands for moving window focus by
direction, I prefer having wraparound instead of getting errors
though.
(setq windmove-wrap-around t)
2.3.5. bookmark
Yet another file that I prefer being saved somewhere else.
(setq bookmark-default-file "~/.config/emacs/etc/bookmarks")
2.3.6. ediff
Anything else than emacsclient
spawning frames is pretty much
useless for me with i3
. I assume the vertical split is not done
because I've customized horizontal splits to be prefered. The name of
the alternative splitting function is not a mistake, what Emacs calls
"horizontal" in window.el
is called vertical in anything else.
(setq ediff-window-setup-function 'ediff-setup-windows-plain
ediff-split-window-function 'split-window-horizontally)
2.3.7. dired
For the few times I'm using Dired, I prefer it not spawning an endless amount of buffers. In fact, I'd prefer it using one buffer unless another one is explicitly created, but you can't have everything.
(with-eval-after-load 'dired (define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file))
2.3.8. tramp
If TRAMP makes backup files, they should better be kept locally than remote.
(setq tramp-backup-directory-alist backup-directory-alist)
As usual I want to fix up the file it's storing its history in.
(with-eval-after-load 'tramp-cache (setq tramp-persistency-file-name "~/.config/emacs/etc/tramp"))
But to be honest, I prefer it not automatically interfering with everything. Unloading it entirely causes packages to break that assume it's enabled, therefore I'm going for its main entry point and dike it out.
(defun my-disable-tramp-file-handlers () (setq file-name-handler-alist (--remove (string-match-p "^tramp" (symbol-name (cdr it))) file-name-handler-alist)))
It discovers SSH options by launching a process which is rather slow, so customize it to the discovered value to disable auto-detection.
(setq tramp-ssh-controlmaster-options "")
2.3.9. FFAP
A great idea, but it's rather annoying in practice. At times it can happen that it pings something looking like a domain name.
(setq ffap-machine-p-known 'accept)
2.3.10. Calendar
General functionality for calendars inside Emacs, split up in a lot of
files. Customizing it will affect other packages, including calfw and
M-x calendar
. The following customizations add German holidays
(since I happen to live in Germany, d'uh).
(setq calendar-week-start-day 1) (setq holiday-general-holidays '((holiday-fixed 1 1 "Neujahr") (holiday-fixed 5 1 "1. Mai") (holiday-fixed 10 3 "Tag der Deutschen Einheit"))) (setq holiday-christian-holidays '((holiday-float 12 0 -4 "1. Advent" 24) (holiday-float 12 0 -3 "2. Advent" 24) (holiday-float 12 0 -2 "3. Advent" 24) (holiday-float 12 0 -1 "4. Advent" 24) (holiday-fixed 12 24 "Weihnachten") (holiday-fixed 12 25 "1. Weihnachtstag") (holiday-fixed 12 26 "2. Weihnachtstag") (holiday-fixed 1 6 "Heilige Drei Könige") (holiday-easter-etc -48 "Rosenmontag") (holiday-easter-etc -3 "Gründonnerstag") (holiday-easter-etc -2 "Karfreitag") (holiday-easter-etc 0 "Ostersonntag") (holiday-easter-etc +1 "Ostermontag") (holiday-easter-etc +39 "Christi Himmelfahrt") (holiday-easter-etc +49 "Pfingstsonntag") (holiday-easter-etc +50 "Pfingstmontag") (holiday-easter-etc +60 "Fronleichnam") (holiday-fixed 8 15 "Mariae Himmelfahrt") (holiday-fixed 11 1 "Allerheiligen") (holiday-float 11 0 1 "Totensonntag" 20))) (setq holiday-oriental-holidays nil holiday-bahai-holidays nil holiday-islamic-holidays nil holiday-hebrew-holidays nil)
2.3.11. org-mode
First some UI and editing tweaks.
(setq org-catch-invisible-edits 'error
org-startup-indented t
org-cycle-include-plain-lists 'integrate
org-return-follows-link t
org-M-RET-may-split-line nil
org-src-fontify-natively t
org-src-preserve-indentation t
org-enforce-todo-dependencies t
org-enforce-todo-checkbox-dependencies t
org-link-frame-setup '((file . find-file)))
I like taking notes.
(setq org-directory "~/notes/" org-default-notes-file "~/notes/notes.org" org-capture-templates '(("j" "Journal" entry (file+olp+datetree "~/notes/journal.org.gpg") "* %<%H:%M>\n\n%?" :empty-lines 1)))
An Org patch from an upcoming release that makes datetree journal entries less annoying:
(with-eval-after-load 'org-datetree (defun org-datetree-insert-line (year &optional month day text) (delete-region (save-excursion (skip-chars-backward " \t\n") (point)) (point)) (org-insert-heading) (while (> (org-current-level) 1) (org-do-promote)) (when month (org-do-demote)) (when day (org-do-demote)) (if text (insert text) (insert (format "%d" year)) (when month (insert (if day (format-time-string "-%m-%d %A" (encode-time 0 0 0 day month year)) (format-time-string "-%m %B" (encode-time 0 0 0 1 month year)))))) (when (and day org-datetree-add-timestamp) (save-excursion (insert "\n") (org-indent-line) (org-insert-time-stamp (encode-time 0 0 0 day month year) nil (eq org-datetree-add-timestamp 'inactive)))) (beginning-of-line)))
The export functionality is very handy, but some of the stuff I like using is deactivated by default :<
(setq org-export-backends '(ascii beamer html latex md))
It's a bit tricky to color code listings and permit more flexible tables:
(setq org-latex-listings 'minted org-latex-packages-alist '(("" "tabu") ("" "minted") ("english" "babel")) org-latex-pdf-process '("pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f" "pdflatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
Using the HTML exporter I've discovered that underscores are interpreted as subscripts. Considering I only use these in LaTeX formulae, I'll just deactivate that markup completely:
(setq org-export-with-sub-superscripts '{})
When clocking in changes, the default accuracy of five minutes is a bit too fine for me. I don't know why, but Org has a customizable that adjusts this and rounding when adding clocking entries…
(setq org-time-stamp-rounding-minutes '(0 15))
Org's exporter has a rather weird way of figuring out how to open an
exported file, so we'll give it a nudge. Note that since Emacs
handles PDFs with docview
and its mailcap implementation prefers
Lisp viewers over system viewers, it ends up using docview
in both
cases, so we'll just tell it not to by placing the PDF entry at the
top while still defining that we'd rather want to use xdg-open
instead of that piece of rubbish.
(setq org-file-apps '(("pdf" . system) (auto-mode . emacs) (system . "xdg-open %s") (t . system)))
Org changed a default about clocking and drawers which has the annoying side effect of always putting clocks into a drawer and surrounding the existing clocks with one if necessary. This can take a significant amount of time if there are many clocking entries present.
(setq org-clock-into-drawer nil)
Enable additional Babel languages:
(with-eval-after-load 'ob
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(python . t))))
2.3.12. comint
Here comes another particularly interesting Emacs package. It allows
one to define major modes interacting with a REPL-style process. In
other words, it gives you all kinds of shell and interpreter
interaction with common keybindings, be it for SQL, your favourite
programming language or your shell. Even Emacs itself can be used,
try out M-x ielm
.
It's trivial to clear the entire comint
buffer by temporarily
binding comint-buffer-maximum-size
to zero and calling
comint-truncate-buffer
, however that's not what I really want.
Usually it's just the output of the last expression that's been
faulty and needs to be cleared by replacing it with a comment. The
idea itself is taken from CIDER.
(defun my-comint-last-output-beg () (save-excursion (comint-goto-process-mark) (while (not (or (eq (get-char-property (point) 'field) 'boundary) (= (point) (point-min)))) (goto-char (previous-char-property-change (point) (point-min)))) (if (= (point) (point-min)) (point) (1+ (point))))) (defun my-comint-last-output-end () (save-excursion (comint-goto-process-mark) (while (not (or (eq (get-char-property (point) 'font-lock-face) 'comint-highlight-prompt) (= (point) (point-min)))) (goto-char (previous-char-property-change (point) (point-min)))) (let ((overlay (car (overlays-at (point))))) (when (and overlay (eq (overlay-get overlay 'font-lock-face) 'comint-highlight-prompt)) (goto-char (overlay-start overlay)))) (1- (point)))) (defun my-comint-clear-last-output () (interactive) (let ((start (my-comint-last-output-beg)) (end (my-comint-last-output-end))) (let ((inhibit-read-only t)) (delete-region start end) (save-excursion (goto-char start) (insert (propertize "output cleared" 'font-lock-face 'font-lock-comment-face))))))
Killed comint
processes tend to leave an useless buffer around.
Let's kill it after noticing such an event with a process sentinel.
(defun my-shell-kill-buffer-sentinel (process event) (when (and (memq (process-status process) '(exit signal)) (buffer-live-p (process-buffer process))) (kill-buffer))) (defun my-kill-process-buffer-on-exit () (set-process-sentinel (get-buffer-process (current-buffer)) #'my-shell-kill-buffer-sentinel)) (dolist (hook '(ielm-mode-hook term-exec-hook comint-exec-hook)) (add-hook hook 'my-kill-process-buffer-on-exit))
Recentering feels a bit unintuitive since it goes by the middle first. I only need top and bottom commands, for that I'll define my own command and bind it later.
(defun my-recenter-top-bottom () (interactive) (goto-char (point-max)) (let ((recenter-positions '(top bottom))) (recenter-top-bottom)))
Another thing annoying me in comint buffers is that when text is read-only, both cursor movement and appending to kill ring still happen. This is less useful since if you keep holding the keys to delete words, you end up traversing the entire buffer instead of stopping at the read-only boundaries and pollute the kill ring. To remedy that I'll write my own word killing commands in the typical Emacs user fashion, however I'll not advise the built-ins since who knows what might possibly be relying on this default behaviour.
(defun my-kill-word (arg) (interactive "p") (unless buffer-read-only (let ((beg (point)) (end (save-excursion (forward-word arg) (point))) (point (save-excursion (goto-char (if (> arg 0) (next-single-char-property-change (point) 'read-only) (previous-single-char-property-change (point) 'read-only))) (point)))) (unless (get-char-property (point) 'read-only) (if (if (> arg 0) (< point end) (> point end)) (kill-region beg point) (kill-region beg end)))))) (defun my-backward-kill-word (arg) (interactive "p") (my-kill-word (- arg)))
The new functionality introduced has to be bound to keys for convenient use. Note the remapping of commands.
(with-eval-after-load 'comint (define-key comint-mode-map (kbd "<remap> <kill-word>") 'my-kill-word) (define-key comint-mode-map (kbd "<remap> <backward-kill-word>") 'my-backward-kill-word) (define-key comint-mode-map (kbd "C-S-l") 'my-comint-clear-last-output) (define-key comint-mode-map (kbd "C-l") 'my-recenter-top-bottom))
2.3.13. shell
For unknown reasons I get my input echoed back to me. In other words,
sending ls
to shell
echoes my input twice, then the output.
comint
has a setting that can filter these echoes.
(defun my-shell-turn-echo-off () (setq comint-process-echoes t)) (add-hook 'shell-mode-hook 'my-shell-turn-echo-off)
2.3.14. eshell
I want C-d
to not unconditionally delete the character, but to quit
on an empty prompt, too.
(defun my-eshell-quit-or-delete-char (arg) (interactive "p") (if (and (eolp) (looking-back eshell-prompt-regexp)) (eshell-life-is-too-much) ;; http://emacshorrors.com/post/life-is-too-much (delete-forward-char arg))) (defun my-eshell-setup () (define-key eshell-mode-map (kbd "C-d") 'my-eshell-quit-or-delete-char)) (add-hook 'eshell-mode-hook 'my-eshell-setup)
For silly reasons I like having a rainbow-colored prompt.
(add-hook 'eshell-load-hook 'nyan-prompt-enable)
2.3.15. CC-Mode
In their ingenuity the Emacs developers decided to make the GNU style the default style for C code written with it. While this is a decision that helps making contribution to GNU projects still adhering to this style (including Emacs itself) a fair bit easier, I'd hate using it for anything else. I don't know my exact preferences yet, but for the time being the "user" style is good enough and can still be customized into something more sophisticated.
(setq c-default-style '((java-mode . "java") (awk-mode . "awk") (c-mode . "user") (c++-mode . "user")))
Electric indentation of things like opening braces on their own line
is linked to electric-indent-mode
, but that can be worked around
easily:
(setq-default c-electric-flag t)
Debugging indentation issues is hard enough as it is, this setting echoes what construct you've just indented.
;; (setq c-echo-syntactic-information-p t)
Backspace can untabify tabs which is yucky. Hungry delete is overkill, so I'll just revert to normal behavior there:
(setq c-backspace-function 'delete-backward-char)
Smartparens screws up the handling of single quote literals and escapes them for some reason. Let's work around this by disabling its delimiter escaping in cc-mode:
(defun my-disable-sp-escape-quotes () (setq-local sp-escape-quotes-after-insert nil)) (add-hook 'c-mode-common-hook 'my-disable-sp-escape-quotes)
2.3.16. eldoc-mode
The default idle delay is way too long. Also, avoid displaying overly long function signatures.
(setq eldoc-idle-delay 0.1
eldoc-echo-area-use-multiline-p nil)
2.3.17. Emacs Lisp
Cask files are just Emacs Lisp.
(add-to-list 'auto-mode-alist '("Cask\\'" . emacs-lisp-mode))
Additionally to the F1
keybindings I'd like to have two extra
keybinds for evaluation and a REPL.
(defun my-eval-region-or-buffer () (interactive) (if (region-active-p) (eval-region (region-beginning) (region-end)) (eval-buffer))) (with-eval-after-load 'lisp-mode (define-key emacs-lisp-mode-map (kbd "C-c C-c") 'my-eval-region-or-buffer) (define-key emacs-lisp-mode-map (kbd "C-c C-z") 'ielm))
eldoc
is a nice helper to avoid looking up function signatures in
function documentation.
(add-hook 'emacs-lisp-mode-hook 'turn-on-eldoc-mode) (add-hook 'ielm-mode-hook 'turn-on-eldoc-mode)
I quite like CIDER-style display of evaluation results, so I'm glad there's a package providing this for Emacs Lisp code.
(add-hook 'emacs-lisp-mode-hook 'eros-mode)
Experiment: My biggest gripe with writing Emacs Lisp is having to type
out a package prefix every time. This hack will lessen the pain by
only having to hit C-.
for inserting the prefix at point. The
prefix is guessed if not set by searching for a (defgroup <prefix>
...)
form, but can be set manually as well by using the prefix
argument.
(defvar-local my-current-package-prefix nil) (defun my-ensure-trailing-dash (string) (if (and (not (zerop (length string))) (not (= (aref string (1- (length string))) ?-))) (concat string "-") string)) (defun my-guess-current-package-prefix (arg) (save-excursion (goto-char (point-min)) (if (and (not arg) (re-search-forward "^(defgroup " nil t)) (let ((form (read (thing-at-point 'defun))) rest) (if (setq rest (memq :prefix form)) (setq my-current-package-prefix (cadr rest)) (setq my-current-package-prefix (format "%s-" (cadr form))))) (setq my-current-package-prefix (my-ensure-trailing-dash (read-string "Package prefix: " my-current-package-prefix)))))) (defun my-insert-current-package-prefix (arg) (interactive "P") (when (or (not my-current-package-prefix) arg) (my-guess-current-package-prefix arg)) (insert my-current-package-prefix)) (with-eval-after-load 'elisp-mode (define-key emacs-lisp-mode-map (kbd "C-.") 'my-insert-current-package-prefix))
2.3.18. Scheme
I like CHICKEN.
(setq scheme-program-name "csi") (add-to-list 'interpreter-mode-alist '("chicken-scheme" . scheme-mode))
To avoid typing M-x run-scheme
, I define another useful keybinding.
(with-eval-after-load 'scheme (define-key scheme-mode-map (kbd "C-c C-z") 'run-scheme))
The binding is replaced though after launching the REPL, I should eventually fix this. Perhaps with my very own major mode.
I wrote an integration for chicken-doc
:
(with-eval-after-load 'scheme (define-key scheme-mode-map (kbd "C-c C-d") 'chicken-doc-describe))
Indentation hints fortunately seem to work for other languages than Emacs Lisp.
(put 'match 'scheme-indent-function 1) (put 'match-let 'scheme-indent-function 1) (put 'match-let* 'scheme-indent-function 1) (put 'when 'scheme-indent-function 1) (put 'and-let* 'scheme-indent-function 1) (put 'if-let 'scheme-indent-function 1) (put 'let-location 'scheme-indent-function 1) (put 'select 'scheme-indent-function 1) (put 'bitmatch 'scheme-indent-function 1) (put 'bitpacket 'scheme-indent-function 1) (put 'with-transaction 'scheme-indent-function 1) (put 'foreign-lambda* 'scheme-indent-function 2) (put 'test-group 'scheme-indent-function 1) (put 'call-with-database 'scheme-indent-function 1) (put 'call-with-input-string 'scheme-indent-function 1) (put 'parameterize 'scheme-indent-function 1) (put 'hash-table-for-each 'scheme-indent-function 1) (put 'module 'scheme-indent-function (lambda (&rest args) 0))
There's a few schemey file formats I'd like to automatically recognize:
(add-to-list 'auto-mode-alist '("\\.sld\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.sxml\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.scss\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.setup\\'" . scheme-mode)) (add-to-list 'auto-mode-alist '("\\.meta\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.release-info\\'" . lisp-data-mode)) (add-to-list 'auto-mode-alist '("\\.egg\\'" . lisp-data-mode))
CHICKEN has special syntax for embedded C code and heredocs that messes up syntax highlighting:
(with-eval-after-load 'scheme (defun my-scheme-region-extend-function () (when (not (get-text-property (point) 'font-lock-multiline)) (let* ((heredoc nil) (new-beg (save-excursion (when (and (re-search-backward "#>\\|<#\\|#<[<#]\\(.*\\)$" nil t) (not (get-text-property (point) 'font-lock-multiline))) (let ((match (match-string 0)) (tag (match-string 1))) (cond ((equal match "#>") (point)) ((string-match-p "^#<[<#]" match) (setq heredoc tag) (point))))))) (new-end (save-excursion (if heredoc (when (and (re-search-forward (concat "^" (regexp-quote heredoc) "$") nil t) (not (get-text-property (point) 'font-lock-multiline))) (point)) (when (and (re-search-forward "#>\\|<#" nil t) (not (get-text-property (point) 'font-lock-multiline)) (equal (match-string 0) "<#")) (point)))))) (when (and new-beg new-end) (setq font-lock-beg new-beg) (setq font-lock-end new-end) (with-silent-modifications (put-text-property new-beg new-end 'font-lock-multiline t)) (cons new-beg new-end))))) (defun my-scheme-syntax-propertize-foreign (_ end) (save-match-data (when (search-forward "<#" end t) (with-silent-modifications (put-text-property (1- (point)) (point) 'syntax-table (string-to-syntax "> cn")))))) (defun my-scheme-syntax-propertize-heredoc (_ end) (save-match-data (let ((tag (match-string 2))) (when (and tag (re-search-forward (concat "^" (regexp-quote tag) "$") nil t)) (with-silent-modifications (put-text-property (1- (point)) (point) 'syntax-table (string-to-syntax "> cn"))))))) (defun scheme-syntax-propertize (beg end) (goto-char beg) (scheme-syntax-propertize-sexp-comment (point) end) (funcall (syntax-propertize-rules ("\\(#\\);" (1 (prog1 "< cn" (scheme-syntax-propertize-sexp-comment (point) end)))) ("\\(#\\)>" (1 (prog1 "< cn" (my-scheme-syntax-propertize-foreign (point) end)))) ("\\(#\\)<[<#]\\(.*\\)$" (1 (prog1 "< cn" (my-scheme-syntax-propertize-heredoc (point) end))))) (point) end))) (defun my-scheme-mode-setup () (setq font-lock-extend-region-functions (cons 'my-scheme-region-extend-function font-lock-extend-region-functions))) (add-hook 'scheme-mode-hook 'my-scheme-mode-setup)
Flymake integration for linting:
(defun flymake-chicken-init () (add-hook 'flymake-diagnostic-functions #'flymake-chicken-backend nil t)) (add-hook 'scheme-mode-hook #'flymake-chicken-init)
2.3.19. Common Lisp
I like SBCL.
(setq inferior-lisp-program "/usr/bin/sbcl")
2.3.20. NXML
Let's automatically complete closing tags.
(setq nxml-slash-auto-complete-flag t)
2.3.21. CSS
Indentation could be a bit more narrow.
(setq css-indent-offset 2)
2.3.22. Python
Emacs is not aware of version-dependent shebangs.
(add-to-list 'interpreter-mode-alist '("python2" . python-mode)) (add-to-list 'interpreter-mode-alist '("python3" . python-mode))
For some reason guessing the indentation offset is on by default although nearly all Python code I've worked with did use 4 spaces. I wouldn't even care weren't it for the message displayed after it's done.
(setq python-indent-guess-indent-offset nil)
2.3.23. Etags
I've created a TAGS
file for finding the definitions to the C
sources quickly. To avoid prompting for its name, one can customize
the following:
(setq tags-file-name "TAGS")
2.3.24. Info
Make copying use the lispy syntax by default and with a normal syntax argument copy the HTML link.
(defun my-info-copy-current-node-name (arg) "Copy the lispy form of the current node. With a prefix argument, copy the link to the online manual instead." (interactive "P") (let* ((manual (file-name-sans-extension (file-name-nondirectory Info-current-file))) (node Info-current-node) (link (if (not arg) (format "(info \"(%s) %s\")" manual node) ;; NOTE this will only work with emacs-related nodes... (format "https://www.gnu.org/software/emacs/manual/html_node/%s/%s.html" manual (if (string= node "Top") "index" (replace-regexp-in-string " " "-" node)))))) (kill-new link) (message link))) (with-eval-after-load 'info (define-key Info-mode-map (kbd "c") 'my-info-copy-current-node-name))
2.3.25. nroff
I'll just pretend that mdoc is the same as nroff:
(add-to-list 'auto-mode-alist '("\\.mdoc\\'" . nroff-mode))
2.3.26. sql-mode
I'm using PostgreSQL most of the time:
(setq sql-product 'postgres)
2.3.27. VC
I rarely if ever do non-Git projects and if I do, I dislike the potentially very long VC indicator in the modeline. It looks as if VC is enabled by merely requiring the feature, so I'll have to disable it differently:
(setq vc-handled-backends nil)
2.3.28. JS
The whole world appears to be using two-space indentation:
(setq js-indent-level 2)
Some people use weird extensions for Javascript files:
(add-to-list 'auto-mode-alist '("\\.es6\\'" . js-mode))
2.3.29. shell-script-mode
I don't see why using zsh as my shell means I wouldn't want to write bash scripts.
(setq sh-shell-file "/bin/bash")
PKGBUILDs are shell scripts, really:
(add-to-list 'auto-mode-alist '("PKGBUILD\\'" . shell-script-mode))
2.3.30. Edebug
Evaluating expressions in Edebug gives you no completion whatsoever. https://emacs.stackexchange.com/questions/39353/completion-in-edebug-eval-expression shows how to do better.
(defun my-edebug-eval-expression-completion (expr) "Replace interactive-spec of `edebug-eval-expression' with `read--expression' from `eval-expression' to get completion working." (interactive (list (read--expression "Eval: ")))) (advice-add 'edebug-eval-expression :before 'my-edebug-eval-expression-completion)
2.3.31. Sieve
Neat to have, but it's annoying to do auth over and over again.
(defun my-sieve-manage () (interactive) (let ((host "imap.mailbox.org") (user "mail@vasilij.de") (secret (lambda () (car (process-lines "pass" "mail/mailbox.org"))))) (password-cache-add `(auth-source :host ,host :port "sieve" :max 1 :create t) `((:host ,host :user ,user :secret ,secret))) (sieve-manage "imap.mailbox.org")))
2.3.32. yow
(autoload 'yow "yow") (autoload 'psychoanalyze-pinhead "yow") (setq yow-file "~/.config/emacs/etc/yow.lines")
2.3.33. rst-mode
One convenience command for updating the date in my blog posts:
(defun my-update-blog-timestamp () (interactive) (save-excursion (save-restriction (goto-char (point-min)) (when (char-equal (char-after) ?\() (narrow-to-region (point-min) (save-excursion (forward-sexp) (point))) (when (or (re-search-forward "updated \\. \"\\([^\"]+\\)\"" nil t) (re-search-forward "date \\. \"\\([^\"]+\\)\"" nil t)) (replace-match (format-time-string "%F %T %z") nil t nil 1)))))) (with-eval-after-load 'rst (define-key rst-mode-map (kbd "C-c u") 'my-update-blog-timestamp))
2.3.34. calculator
The display precision defaults to 3 for some reason:
(setq calculator-number-digits 8)
I find scientific display of small numbers plain unreadable, so display of all digits with grouping it is.
(setq calculator-displayer '(std ?f t))
Division behaves like integer division for integers. I want float division by default (calc cleans up trailing zeroes anyway).
(setq calculator-user-operators '(("/" / (/ (float X) Y) 2 5)))
2.3.35. Flymake
In an ironic turn of events, flycheck development stalled and flymake got a long overdue rewrite, making it the better choice for linting source code. Therefore I'm slowly migrating my bits of flycheck configuration to flymake.
For some reason, flymake doesn't choose any key bindings for its commands.
(with-eval-after-load 'flymake (define-key flymake-mode-map (kbd "C-c ! n") 'flymake-goto-next-error) (define-key flymake-mode-map (kbd "C-c ! p") 'flymake-goto-prev-error))
I don't want it to check before I save the file.
(setq flymake-no-changes-timeout nil)
2.4. Packages outside Emacs
Welcome to the blind spot of emacs-devel
. Unlike the people on
there, I'll not pretend external packages are something to speak of in
hushed tones.
2.4.1. smex
Nice improvement over vanilla M-x
that gives you persistency and
better matching. Let's give it more history and a different file.
(setq smex-save-file (concat user-emacs-directory "etc/smex") smex-history-length 50)
2.4.2. CSV
After installing csv-mode from GNU ELPA, I found out it's using a
:set
form in its customization option for the separators, therefore
I had to figure out what "internal" variables they were setting and
customized them.
(setq csv-separators '(";" " " ",") csv-separator-chars '(?\; ? ?,) csv--skip-regexp "^ ; ," csv-separator-regexp "[; ,]" csv-font-lock-keywords '(("[; ,]" (0 'csv-separator-face))))
2.4.3. Quelpa
A client-side MELPA. Hugely useful for development, also useful to obtain packages that are not there or need to be built differently from what it offers. vim-plug comes close, but the closest equivalent to it would be the makepkg utility.
This customization is necessary to have updates of packages happen, even if they already exist.
(setq quelpa-upgrade-p t)
2.4.4. shackle
Declarative popup window rules.
(setq shackle-rules '(((svg-2048-mode circe-query-mode clojure-mode) :same t) ("*Help*" :align t :select t) ("\\`\\*helm.*?\\*\\'" :regexp t :align t) ((compilation-mode "\\`\\*firestarter\\*\\'" "\\`magit-diff:") :regexp t :noselect t) ((inferior-scheme-mode inferior-lisp-mode "*shell*" "*eshell*") :popup t)) shackle-default-rule '(:select t) shackle-default-size 0.4 shackle-inhibit-window-quit-on-same-windows t)
2.4.5. eyebrowse
Less clumsy management of window configurations.
Switch back and forth just like my i3wm configuration, wrap around, too.
(setq eyebrowse-switch-back-and-forth t
eyebrowse-wrap-around t)
2.4.6. company-mode
The best auto-completion mode we have out there.
The following sets up a good amount of UI tweaks and everything necessary for the global backends.
(setq company-idle-delay 0.1
company-minimum-prefix-length 2
company-selection-wrap-around t
company-show-numbers t
company-require-match 'never
company-dabbrev-downcase nil
company-dabbrev-ignore-case t
company-dabbrev-other-buffers nil
company-backends '(company-nxml company-css
company-capf
company-dabbrev-code
company-files
company-dabbrev))
Hitting ESC
does exit Evil's insert state (which is where I'm
usually in when typing completable text), but still keeps the popup
open. A similiar problem applies to the candidate search, so here's a
workaround for both:
(with-eval-after-load 'company (define-key company-search-map (kbd "<escape>") 'company-search-abort))
I've transitioned from auto-complete-mode so I'm missing its selection
behaviour. Company is not quite there yet, but
company-tng-configure-default
helps. It needs to be called after
this, but before Company is enabled, so I've deferred it to the end
of this init file.
2.4.7. dash
Very useful library, too bad I don't know how to properly use it yet. Since it's sprinkled all over in code I'd like to have extra syntax highlighting for it.
(with-eval-after-load 'dash
(dash-enable-font-lock))
2.4.8. helm
A polarizing package to say the least. The good part of it is that it actually tries enabling abstractions over complex selection UI. The bad part is that it's overly complex, hard to debug and prone to bizarre behaviour. I've handed in ten bugs for it already and don't expect those to be the last. With that being said I find it essential to quickly find your way through Emacs, I just wish it were less idiosyncratic and with developer documentation.
- Navigation
The default navigation isn't as fast as it could be. Automatically switching directories is a must for me. Note the hack with
helm-ff--auto-update-state
, it's supposedly internal, but only set after usinghelm-find-files
which essentially means that everything using the file selector won't get the auto-switching goodies unless a file has been found before. With this hack however it will. The other hack goes beyond thehelm-ff-ido-style-backspace
customization and unconditionally enables backspace going up one level in both kinds of file selectors.(setq helm-ff-ido-style-backspace 'always helm-ff-auto-update-initial-value t helm-ff--auto-update-state t) (with-eval-after-load 'helm-files (define-key helm-read-file-map (kbd "<backspace>") 'helm-find-files-up-one-level) (define-key helm-find-files-map (kbd "<backspace>") 'helm-find-files-up-one-level))
There are more idiosyncracies to be resolved with file selection. I don't want to see boring files and not get prompted for creating a new file either. The creation of a new directory however is kept as is.
(setq helm-ff-newfile-prompt-p nil helm-ff-skip-boring-files t)
- Search
grep is very fast, but not the best tool for code search, especially not within compressed files. That's why I'll go for ag instead, its
-z
option enables the usage of the very great libarchive.Here's two commands for pretty common queries, one going through the official Emacs Lisp sources, the other through the C parts. Note that while the second argument of
helm-grep-ag-1
is intended to be used for the file type switches, other switches such as-z
can be passed just fine:(defun my-grep-emacs-elisp () (interactive) (helm-grep-ag-1 (format "/usr/share/emacs/%s/lisp" emacs-version) '("-z"))) (defun my-grep-emacs-C () (interactive) (helm-grep-ag-1 find-function-C-source-directory '("--cc")))
Integration for comby's search:
(defun my-comby-command (query dir ext &optional ext-override) (concat (format "comby %s '' %s -d %s -o" (shell-quote-argument query) (shell-quote-argument ext) (shell-quote-argument (expand-file-name dir))) (if ext-override (format " -m %s" (shell-quote-argument ext-override)) ""))) (defun my-comby-search-C (query dir) (compilation-start (my-comby-command query dir ".c") 'grep-mode)) (defun my-comby-search-elisp (query dir) (compilation-start (my-comby-command query dir ".el" ".lisp") 'grep-mode)) (defun my-comby-emacs-C (query) (interactive "sQuery: ") (my-comby-search-C "~/code/emacsen/emacs-git/src")) (defun my-comby-emacs-lisp (query) (interactive "sQuery: ") (my-comby-search-elisp query "~/code/emacsen/emacs-git/lisp")) (defun my-comby-elpa (query) (interactive "sQuery: ") (my-comby-search-elisp "~/.config/emacs/elpa"))
completing-read
behaviour
I dislike
helm
taking over tab-completion in my IRC client.(setq helm-mode-no-completion-in-region-in-modes '(circe-channel-mode circe-query-mode circe-server-mode))
- Other
Highlighting of token matches is a tad slow, let's speed it up.
(setq helm-mp-highlight-delay 0.3)
I like having my dotfiles repo as default when using
helm-cmd-t
on a directory that's not under version-control.(setq helm-cmd-t-default-repo "~/code/misc/dotfiles")
I don't know why, but helm tries doing window management. Please stop:
(setq helm-display-function 'pop-to-buffer)
This may or may not avoid a memory leak:
(setq helm-ff-keep-cached-candidates nil) (with-eval-after-load 'helm-files (helm-ff-cache-mode -1))
- Custom commands
(with-eval-after-load 'helm (defun my-helm-rdictcc () (interactive) (helm :sources 'my-helm-rdictcc-source :buffer "*helm rdictcc*")) (defvar my-helm-rdictcc-source (helm-build-async-source "rdictcc" :candidates-process 'my-helm-rdictcc-process :candidate-number-limit 99 :filtered-candidate-transformer 'my-helm-rdictcc-transformer :requires-pattern 3)) (defun my-helm-rdictcc-process () (let* ((pattern (car (split-string helm-pattern))) (proc (start-process "rdictcc" helm-buffer "rdictcc" "-c" pattern))) (set-process-sentinel proc (lambda (process event) (helm-process-deferred-sentinel-hook process event default-directory))) proc)) (defun my-helm-rdictcc-transformer (candidates _source) (let* ((extra-patterns (cdr (split-string helm-pattern))) (extra-patterns (and extra-patterns (regexp-opt extra-patterns))) result) (dolist (candidate candidates) (when (string-match-p "=\\{20\\}\\[ [AB] => [AB] \\]=\\{20\\}" candidate) (add-face-text-property 0 (length candidate) 'font-lock-comment-face nil candidate)) (if extra-patterns (when (string-match-p extra-patterns candidate) (push candidate result)) (push candidate result))) (nreverse result))))
2.4.9. flycheck
There's a few languages I like having linting for, see 2.6.
Additionally to that there's few things to tweak. For one I prever
the tex-lacheck
linter over the default tex-chktex
linter and
don't want to use the emacs-lisp-checkdoc
one at all, another thing
is that I don't want linting to start on an idle timer, but rather on
opening the buffer and saving it to disk.
(setq flycheck-disabled-checkers '(tex-chktex emacs-lisp-checkdoc)
flycheck-check-syntax-automatically '(mode-enabled save))
For whatever reason the emacs-lisp
checker stopped unconditionally
initializing packages before doing the check, the following avoids
errors for dependencies in packages I write:
(setq flycheck-emacs-lisp-initialize-packages t)
Automatic linting for Emacs Lisp files is nice, but doesn't make any sense for non-packages. For simplicity's sake, I define everything in a few specific directories to be a package:
(defun my-enable-flycheck-for-elisp-packages () (when (and buffer-file-name (string-match-p "/home/wasa/code/elisp/" buffer-file-name)) (flycheck-mode))) (add-hook 'emacs-lisp-mode-hook 'my-enable-flycheck-for-elisp-packages)
2.4.10. Hydra
Not sure how to describe it. A library for defining key-centric interfaces? You use it to execute commands with single-key presses first and foremost, I have only come to define repetition-free ones.
- Define utility functions
(defun my-zsh () (interactive) (ansi-term "zsh")) (defun my-info-emacs-lisp-intro () (interactive) (info "eintr")) (defun my-info-emacs-lisp-manual () (interactive) (info "elisp")) (defun my-info-cl () (interactive) (info "cl")) (defun my-info-cl-loop () (interactive) (info "(cl) Loop facility")) (defun my-capture-journal () (interactive) (org-capture nil "j")) (defun my-open-journal () (interactive) (find-file "~/notes/journal.org.gpg")) (defun my-open-tracking () (interactive) (find-file "~/notes/tracking.org")) (autoload 'cfw:open-org-calendar "calfw-org" "Open Org calendar" t)
- Define setup function
This is used in
after-init-hook
.(defun my-setup-hydra () (global-set-key (kbd "<f1>") (defhydra hydra-help (:color blue) "Help" ("a" helm-apropos "Apropos") ("c" describe-char "Describe Char") ("f" find-function "Find Function") ("F" describe-function "Describe Function") ("k" describe-key "Describe Key") ("K" find-function-on-key "Find Key") ("m" describe-mode "Describe Modes") ("t" list-timers "List timers") ("v" find-variable "Find Variable") ("V" describe-variable "Describe Variable"))) (global-set-key (kbd "<f2>") (defhydra hydra-packages (:color blue) "Packages" ("c" helm-colors "Colors") ("f" find-library "Find Library") ("g" customize-group "Customize Group") ("p" list-packages "Package List") ("q" quelpa "Quelpa"))) (global-set-key (kbd "<f3>") (defhydra hydra-search (:color blue) "Search" ("e" my-grep-emacs-elisp "Grep Emacs Elisp") ("E" my-grep-emacs-C "Grep Emacs C") ("g" helm-do-grep "Grep") ("i" helm-imenu "Imenu") ("o" helm-occur "Occur") ("r" my-helm-rdictcc "rdictcc"))) (global-set-key (kbd "<f4>") (defhydra hydra-find (:color blue) "Find" ("f" helm-find "Find") ("l" helm-locate "Locate") ("t" helm-cmd-t "Cmd-T"))) (global-set-key (kbd "<f5>") (defhydra hydra-eval (:color blue) "Eval" ("c" calc "Calc") ("e" eshell "Eshell") ("g" magit-status "Magit") ("i" ielm "IELM") ("s" shell "Shell") ("t" my-zsh "Term"))) (global-set-key (kbd "<f6>") (defhydra hydra-doc (:color blue) "Doc" ("e" info-emacs-manual "Emacs manual") ("i" info "Info") ("l" my-info-emacs-lisp-manual "Emacs Lisp manual"))) (global-set-key (kbd "<f7>") (defhydra hydra-insert (:color blue) "insert" ("u" insert-char "Unicode input"))) (global-set-key (kbd "<f8>") (defhydra hydra-misc (:color blue) "Misc" ("g" helm-google-suggest "Google Suggest") ("p" helm-list-emacs-process "Emacs Process List") ("t" helm-top "Top"))) (global-set-key (kbd "<f9>") (defhydra hydra-distractions (:color blue) "Distractions" ("i" my-irc "IRC") ("s" my-sieve-manage "Sieve"))) (global-set-key (kbd "<f11>") (defhydra hydra-capture (:color blue) "Org Capture" ("j" my-capture-journal "Journal"))) (global-set-key (kbd "<f12>") (defhydra hydra-lookup (:color blue) "Org Lookup" ("c" cfw:open-org-calendar "Calendar") ("j" my-open-journal "Journal") ("t" my-open-tracking "Tracking"))))
2.4.11. CIDER
Clojure Interactive Development Environment that Rocks.
Please don't display a splash screen in my REPL:
(setq cider-repl-display-help-banner nil)
I like eldoc
for function signatures in both code and REPL buffers.
(add-hook 'cider-repl-mode-hook 'eldoc-mode) (add-hook 'clojure-mode-hook 'eldoc-mode)
The REPL binds a key for clearing the last evaluated output, but I prefer nuking all output (which is unbound for some reason):
(with-eval-after-load 'cider-repl (define-key cider-repl-mode-map (kbd "C-c C-k") 'cider-repl-clear-buffer))
Joker is a light-weight Clojure implementation that can be used as linter for Clojure and ClojureScript files.
(defun my-flycheck-joker-setup () (require 'flycheck-joker)) (add-hook 'clojure-mode-hook 'my-flycheck-joker-setup) (add-hook 'clojurescript-mode-hook 'my-flycheck-joker-setup)
2.4.12. web-mode
Deals with all kinds of templates and other files with multiple modes one encounters in web development. Other than templates, I prefer using it for HTML these days.
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tm?pl\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) (setq web-mode-markup-indent-offset 2) (setq web-mode-css-indent-offset 2) (setq web-mode-code-indent-offset 2)
2.4.13. company-tern
Tern is kind of cool. Don't forget installing it via npm
, then
adding a .tern-project
file to your project root.
(add-hook 'js-mode-hook 'tern-mode) (add-to-list 'company-backends 'company-tern)
2.4.14. enh-ruby-mode
I'm using this mode for everything the stock ruby-mode
would be used
for because it provides better syntax highlighting and indentation by
using an external process.
(add-to-list 'auto-mode-alist '("\\.rb\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("Gemfile\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("Rakefile\\'" . enh-ruby-mode)) (add-to-list 'auto-mode-alist '("\\.rake\\'" . enh-ruby-mode))
For deep indentation, allow bouncing towards a less deep level.
(setq enh-ruby-bounce-deep-indent t)
2.4.15. AUCTEX
- Usage tweaks
It's 2015 and I prefer a TeX engine that can deal with Unicode and use any font I like.
(setq-default TeX-engine 'luatex)
This isn't enough though to allow overriding the used command. I used to put
LaTeX-command
into a file-local variable, but that's not picked up ifTeX-engine
has been customized…(setq LuaTeX-command "luatex" LuaLaTeX-command "lualatex --jobname=%s" TeX-engine-alist '((luatex "LuaTeX" LuaTeX-command LuaLaTeX-command LuaTeX-command)))
Set up viewers and a few other things.
(setq TeX-quote-after-quote t TeX-auto-save t TeX-parse-self t TeX-view-program-list '(("llpp" "llpp %o")) TeX-view-program-selection '(((output-dvi style-pstricks) "dvips and gv") (output-dvi "xdvi") (output-pdf "llpp") (output-html "xdg-open")))
There's more verbatim environments than
verbatim
:(setq LaTeX-verbatim-environments '("minted" "verbatim" "verbatim*"))
Enable PDF mode, enable folding and add a few convenience keybinds (like
C-c C-a
to run every command until the document can be viewed).(defun my-extend-hs-modes-alist () (add-to-list 'hs-special-modes-alist `(latex-mode ,(latex/section-regexp) nil "%" (lambda (arg) (latex/next-section 1) (skip-chars-backward " \t\n")) nil))) (autoload 'latex/section-regexp "latex-extra" "LaTeX section regexp" t) (defun my-latex-setup () (TeX-PDF-mode) (latex/setup-keybinds) (my-extend-hs-modes-alist)) (add-hook 'LaTeX-mode-hook 'my-latex-setup)
completing-read
behaviour
helm-mode
enables more convenientcompleting-read
, however it's a bit silly that candidates for common AUCTEX functions aren't required matches.(with-eval-after-load 'tex (defun TeX-command-master (&optional override-confirm) "Run command on the current document. If a prefix argument OVERRIDE-CONFIRM is given, confirmation will depend on it being positive instead of the entry in `TeX-command-list'." (interactive "P") (TeX-command (my-TeX-command-query (TeX-master-file)) 'TeX-master-file override-confirm)) (defun TeX-command-query (name) "Query the user for what TeX command to use." (let* ((default (cond ((if (string-equal name TeX-region) (TeX-check-files (concat name "." (TeX-output-extension)) (list name) TeX-file-extensions) (TeX-save-document (TeX-master-file))) TeX-command-default) ((and (memq major-mode '(doctex-mode latex-mode)) ;; Want to know if bib file is newer than .bbl ;; We don't care whether the bib files are open in emacs (TeX-check-files (concat name ".bbl") (mapcar 'car (LaTeX-bibliography-list)) (append BibTeX-file-extensions TeX-Biber-file-extensions))) ;; We should check for bst files here as well. (if LaTeX-using-Biber TeX-command-Biber TeX-command-BibTeX)) ((TeX-process-get-variable name 'TeX-command-next TeX-command-Show)) (TeX-command-Show))) (completion-ignore-case t) (answer (or TeX-command-force (completing-read (concat "Command: (default " default ") ") (TeX-mode-specific-command-list major-mode) nil t default 'TeX-command-history)))) ;; If the answer is "latex" it will not be expanded to "LaTeX" (setq answer (car-safe (TeX-assoc answer TeX-command-list))) (if (and answer (not (string-equal answer ""))) answer default)))) (with-eval-after-load 'latex (defun LaTeX-section-heading () "Hook to prompt for LaTeX section name. Insert this hook into `LaTeX-section-hook' to allow the user to change the name of the sectioning command inserted with `\\[LaTeX-section]'." (let ((string (completing-read (concat "Level: (default " name ") ") LaTeX-section-list nil nil name))) ; Update name (if (not (zerop (length string))) (setq name string)) ; Update level (setq level (LaTeX-section-level name)))) (defun LaTeX-environment (arg) "Make LaTeX environment (\\begin{...}-\\end{...} pair). With optional ARG, modify current environment. It may be customized with the following variables: `LaTeX-default-environment' Your favorite environment. `LaTeX-default-style' Your favorite document class. `LaTeX-default-options' Your favorite document class options. `LaTeX-float' Where you want figures and tables to float. `LaTeX-table-label' Your prefix to labels in tables. `LaTeX-figure-label' Your prefix to labels in figures. `LaTeX-default-format' Format for array and tabular. `LaTeX-default-width' Width for minipage and tabular*. `LaTeX-default-position' Position for array and tabular." (interactive "*P") (let ((environment (completing-read (concat "Environment type: (default " (if (TeX-near-bobp) "document" LaTeX-default-environment) ") ") (LaTeX-environment-list) nil t nil 'LaTeX-environment-history LaTeX-default-environment))) ;; Get default (cond ((and (zerop (length environment)) (TeX-near-bobp)) (setq environment "document")) ((zerop (length environment)) (setq environment LaTeX-default-environment)) (t (setq LaTeX-default-environment environment))) (let ((entry (assoc environment (LaTeX-environment-list)))) (if (null entry) (LaTeX-add-environments (list environment))) (if arg (LaTeX-modify-environment environment) (LaTeX-environment-menu environment))))))
2.4.16. smartparens
Promises to go beyond paredit (which is structured editing for Lisp code) by supporting other languages than Lisp-likes with arbitrary kinds of pairs. I only use its autopairing feature, pair highlighting and a bit of auto-indent though.
- Disable some default pairs
The following wall of code disables pairs for Lisp- and TeX-like modes that make absolutely no sense.
(with-eval-after-load 'smartparens (sp-local-pair 'minibuffer-inactive-mode "'" nil :actions nil) (sp-local-pair 'minibuffer-inactive-mode "`" nil :actions nil) (sp-local-pair 'emacs-lisp-mode "'" nil :actions nil) (sp-local-pair 'emacs-lisp-mode "`" nil :actions nil) (sp-local-pair 'lisp-interaction-mode "'" nil :actions nil) (sp-local-pair 'lisp-interaction-mode "`" nil :actions nil) (sp-local-pair 'scheme-mode "'" nil :actions nil) (sp-local-pair 'scheme-mode "`" nil :actions nil) (sp-local-pair 'inferior-scheme-mode "'" nil :actions nil) (sp-local-pair 'inferior-scheme-mode "`" nil :actions nil) (sp-local-pair 'LaTeX-mode "\"" nil :actions nil) (sp-local-pair 'LaTeX-mode "'" nil :actions nil) (sp-local-pair 'LaTeX-mode "`" nil :actions nil) (sp-local-pair 'latex-mode "\"" nil :actions nil) (sp-local-pair 'latex-mode "'" nil :actions nil) (sp-local-pair 'latex-mode "`" nil :actions nil) (sp-local-pair 'TeX-mode "\"" nil :actions nil) (sp-local-pair 'TeX-mode "'" nil :actions nil) (sp-local-pair 'TeX-mode "`" nil :actions nil) (sp-local-pair 'tex-mode "\"" nil :actions nil) (sp-local-pair 'tex-mode "'" nil :actions nil) (sp-local-pair 'tex-mode "`" nil :actions nil))
- Add IDE-like auto-insertion for braces
Working on college assignments in both C and Java made me wish for an interesting feature I've seen in IDEs: Automatic insertion of a correctly indented newline before the closing brace which allows you to enter its content right away. The following is stolen from its wiki.
(defun my-create-newline-and-enter-sexp (&rest _ignored) "Open a new brace or bracket expression, with relevant newlines and indent." (newline) (indent-according-to-mode) (forward-line -1) (indent-according-to-mode)) (with-eval-after-load 'smartparens (sp-local-pair 'c-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'js-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'rjsx-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))) (sp-local-pair 'java-mode "{" nil :post-handlers '((my-create-newline-and-enter-sexp "RET"))))
- Other
First of all, no long pair mismatch messages please, they're reserved for debugging purposes.
(setq sp-message-width nil)
Because I'm using
evil
, funny things are happening with my cursor, like it not going beyond the end of the line in normal state. To emulate a bit more Vim-like paren highlighting, pairs should be shown from inside, too.(setq sp-show-pair-from-inside t)
Automatic quote escaping feels like a mistake to me (and to its author as well ._.).
(setq sp-autoescape-string-quote nil)
This curiously named variable controls whether the overlay spanning the pair's content disappears on backwards motions, something entirely different than its name suggests.
(setq sp-cancel-autoskip-on-backward-movement nil)
2.4.17. Evil
Here comes the set of sane text editing keybindings I can't live without. Both implementation and execution are excellent and reuse as much from Emacs as possible, resulting in very high compatibility and feature coverage. The only thing I can complain about is that its sources are pretty much incomprehensible to me.
Despite that weakness I've managed writing my own additions to improve integration a good bit more according to my own tastes.
- Initial state
First of all, there are plenty of special modes where neither insert state nor motion state suffice. I've instead decided to do away with motion state and going for Emacs state whenever it makes sense.
(setq evil-default-state 'emacs evil-emacs-state-modes nil evil-insert-state-modes nil evil-motion-state-modes nil evil-normal-state-modes '(text-mode prog-mode fundamental-mode css-mode conf-mode TeX-mode LaTeX-mode diff-mode))
org-capture-mode
is a minor mode, that's why I need to use its hook instead. Same goes forview-mode
.(add-hook 'org-capture-mode-hook 'evil-insert-state) (add-hook 'with-editor-mode-hook 'evil-insert-state) (add-hook 'view-mode-hook 'evil-emacs-state)
Allow quitting
M-x magit-blame
withq
by toggling Evil's current state.(defun my-evil-toggle () (interactive) (cond ((memq evil-state '(insert normal)) (evil-emacs-state)) ((eq evil-state 'emacs) (evil-exit-emacs-state)))) (add-hook 'magit-blame-mode-hook 'my-evil-toggle)
- More Emacs-like feel
These make movement, undo and search feel a bit less weird.
(setq evil-cross-lines t evil-move-beyond-eol t evil-want-fine-undo t evil-symbol-word-search t)
However, I want
C-w
to still be the window map prefix in Emacs state (instead of the standardkill-region
command). As the customization setting for that is applied inevil-maps.el
which is loaded byevil.el
, I need to load it before enablingevil-mode
.(with-eval-after-load 'evil-vars (setq evil-want-C-w-in-emacs-state t))
(with-eval-after-load 'evil-common (evil-declare-motion 'recenter-top-bottom))
- Keymap hacking
I want
Y
to yank to the end of line.(setq evil-want-Y-yank-to-eol t)
Some minor modes come with keymaps reminiscent of special major modes, these get overridden by Evil. These can be fixed by using
evil-normalize-keymaps
, at least foredebug-mode
.(add-hook 'edebug-mode-hook 'evil-normalize-keymaps)
macrostep-mode
requires a bit more effort, see evil#511 for the code involved and further explanation.(defun my-macrostep-setup () (evil-make-overriding-map macrostep-keymap 'normal) (evil-normalize-keymaps)) (add-hook 'macrostep-mode-hook 'my-macrostep-setup)
Same goes for
cider-debug
:(defun my-cider-debug-setup () (evil-make-overriding-map cider--debug-mode-map 'normal) (evil-normalize-keymaps)) (add-hook 'cider--debug-mode-hook 'my-cider-debug-setup)
Let's poke some holes into its keymaps. Anything not bound will be passed through to Emacs other keymaps. Because
SPC
,RET
andTAB
are bound to rather silly commands in Vim I'm unbinding them to allow for much more useful Emacs commands (such as context-aware indentation, following links, scrolling a page down, etc.).(with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "SPC") nil) (define-key evil-motion-state-map (kbd "RET") nil) (define-key evil-motion-state-map (kbd "TAB") nil))
Same story with
C-.
andM-.
, the latter is usually bound to lookup of symbol at point. The former is unbound because I'm fat-fingering often.(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-.") nil) (define-key evil-normal-state-map (kbd "M-.") nil))
The hole poking continues, this time for the ex completion keymap. Everything with a modifier (except for the toggle key for Emacs state and other useful keys) has to go.
(with-eval-after-load 'evil-maps (setcdr evil-ex-completion-map (--reject (and (memq 'control (event-modifiers (car-safe it))) ;; abort prompt (/= (car-safe it) ?\C-c) (/= (car-safe it) ?\C-g) ;; exit (/= (car-safe it) ?\C-m) ;; previous/next input (/= (car-safe it) ?\C-p) (/= (car-safe it) ?\C-n)) (cdr evil-ex-completion-map))))
There is a relatively new customizable for Evil's insert state keymap that disables setting up anything vimmish in it:
(setq evil-disable-insert-state-bindings t)
C-w
C-w
works in Emacs state, but not in insert state. Let's fix that.(with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "C-w") 'evil-window-map))
C-i
C-i
is used in Vim as counterpart toC-o
for going back and forth in the jump list. It also happens to be interpreted asTAB
, simply because terminals are a nightmare. Fortunately GUI Emacs can be told to not resolveC-i
to indentation by defining a function inkey-translation-map
that returns the desired key. That way I'm sending a custom<C-i>
when Evil is active, in normal state andC-i
(as opposed to theTAB
key) has been pressed, otherwiseTAB
is passed through.(defun my-translate-C-i (_prompt) (if (and (= (length (this-single-command-raw-keys)) 1) (eql (aref (this-single-command-raw-keys) 0) ?\C-i) (bound-and-true-p evil-mode) (eq evil-state 'normal)) (kbd "<C-i>") (kbd "TAB"))) (define-key key-translation-map (kbd "TAB") 'my-translate-C-i) (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "<C-i>") 'evil-jump-forward))
C-u
C-u
is bound to a scroll up command in Vim, in Emacs however it's used for the prefix argument. This feels pretty weird to me after having bothered learningC-u
as command for killing a whole line in everything using the readline library. I considerM-u
as a good replacement considering it's bound to the rather uselessupcase-word
command by default which I most definitely will not miss.(define-key global-map (kbd "C-u") 'kill-whole-line) (define-key global-map (kbd "M-u") 'universal-argument) (define-key universal-argument-map (kbd "C-u") nil) (define-key universal-argument-map (kbd "M-u") 'universal-argument-more) (with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "C-u") 'evil-scroll-up))
- Extra keybindings
Emacs 24.4 introduced
electric-indent-mode
as default which happens to be a global mode. I'm not particularly fond of it (and anything starting withelectric-
), that's why I disable it later after initialization is done and instead bindnewline-and-indent
in insert state.(with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "RET") 'newline-and-indent))
Let's get rid of
;
for the questionable benefit of having a modifier less to hit for entering ex state.(with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd ";") 'evil-ex) (define-key evil-visual-state-map (kbd ";") 'evil-ex))
U
is a much more fit key for redoing thanC-r
.(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "U") 'redo))
The evil-numbers package is pretty nice, but I don't want to use the standard Vim keybinds (
C-a
andC-x
) for its commands. Instead I'm going for the much more mnemonic+
and-
.(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "-") 'evil-numbers/dec-at-pt) (define-key evil-normal-state-map (kbd "+") 'evil-numbers/inc-at-pt) (define-key evil-visual-state-map (kbd "-") 'evil-numbers/dec-at-pt) (define-key evil-visual-state-map (kbd "+") 'evil-numbers/inc-at-pt) (define-key evil-visual-state-map (kbd "g -") 'evil-numbers/dec-at-pt-incremental) (define-key evil-visual-state-map (kbd "g +") 'evil-numbers/inc-at-pt-incremental))
The
z
map is full of keybindings I can never remember for dealing with code folding. First of all, get rid of them.(with-eval-after-load 'evil-maps (setcdr evil-normal-state-map (--reject (eq (car-safe it) ?z) (cdr evil-normal-state-map))) (setcdr evil-motion-state-map (--reject (eq (car-safe it) ?z) (cdr evil-motion-state-map))))
Next, define a few toggling commands and bind them.
(defvar my-hs-hide nil "Current state of hideshow for toggling all.") (with-eval-after-load 'evil-common (evil-define-command my-evil-toggle-folds () "Open or close all folds." (setq my-hs-hide (not my-hs-hide)) (if my-hs-hide (hs-hide-all) (hs-show-all)))) (defun my-toggle-mode-line-minor-modes () (interactive) (if rm-blacklist (setq rm-blacklist nil) (setq rm-blacklist ".*")) (force-mode-line-update t)) (defun my-narrow-to-region-with-mode (beg end mode) (interactive (list (region-beginning) (region-end) (completing-read "Major mode: " (mapcar 'cdr auto-mode-alist) nil t))) (unless (region-active-p) (error "No region for narrowing selected")) (narrow-to-region beg end) (deactivate-mark) (funcall (intern mode))) (defun my-revert-buffer () (interactive) (revert-buffer nil t)) (defun my-theme-toggle () (interactive) (cond ((memq 'punpun-light custom-enabled-themes) (disable-theme 'punpun-light) (load-theme 'punpun-dark t)) ((memq 'punpun-dark custom-enabled-themes) (disable-theme 'punpun-dark) (load-theme 'punpun-light t)))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "z r") 'my-revert-buffer) (define-key evil-normal-state-map (kbd "z b") 'magit-blame) (define-key evil-normal-state-map (kbd "z s") 'describe-char) (define-key evil-normal-state-map (kbd "z e") 'toggle-debug-on-error) (define-key evil-normal-state-map (kbd "z q") 'toggle-debug-on-quit) (define-key evil-normal-state-map (kbd "z t") 'my-theme-toggle) (define-key evil-normal-state-map (kbd "z m") 'my-toggle-mode-line-minor-modes) (define-key evil-normal-state-map (kbd "z n") 'my-narrow-to-region-with-mode) (define-key evil-normal-state-map (kbd "z TAB") 'evil-toggle-fold) (define-key evil-normal-state-map (kbd "z <backtab>") 'my-evil-toggle-folds))
Define my most-used helpers (stolen from unimpaired.vim) next.
(defun my-evil-unimpaired-insert-newline-above (count) "Insert an empty line below point" (interactive "p") (save-excursion (dotimes (i count) (evil-insert-newline-above)))) (defun my-evil-unimpaired-insert-newline-below (count) "Insert an empty line below point" (interactive "p") (save-excursion (dotimes (i count) (evil-insert-newline-below)))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "[ SPC") 'my-evil-unimpaired-insert-newline-above) (define-key evil-normal-state-map (kbd "] SPC") 'my-evil-unimpaired-insert-newline-below))
Add a few convenience bindings to the window map on
C-w
.(defun my-work-on-scratch () (interactive) (switch-to-buffer (get-buffer-create "*scratch*"))) (with-eval-after-load 'evil-maps (define-key evil-window-map (kbd "n") 'my-work-on-scratch) (define-key evil-window-map (kbd "u") 'winner-undo) (define-key evil-window-map (kbd "b") 'helm-mini) (define-key evil-window-map (kbd "d") 'kill-buffer) (define-key evil-window-map (kbd "D") 'kill-buffer-and-window) (define-key evil-window-map (kbd "C-d") 'kill-buffer-and-window))
Then some "leader" bindings.
(defun my-switch-to-last-buffer () (interactive) (switch-to-buffer (other-buffer))) (defun my-find-file-with-root-privileges (filename) (interactive "F") (let ((pw (concat (password-read "Enter password: ") "\n")) (sudo-process (start-process "Sudo" "*sudo*" "sudo" "-Se" filename))) (process-send-string sudo-process pw))) (with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd ", ,") 'my-switch-to-last-buffer) (define-key evil-normal-state-map (kbd ", .") 'helm-mini) (define-key evil-normal-state-map (kbd ", /") 'helm-find-files) (define-key evil-normal-state-map (kbd ", ?") 'my-find-file-with-root-privileges))
As
calc
keeps bewildering me, butcalculator
doesn't, I'll bind the latter for one-off calculations:(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "=") 'calculator))
- Load supplementary modes
Finally, there's a few minor modes depending on Evil being loaded before they are.
(defun my-after-evil () (global-evil-surround-mode) (eyebrowse-mode) (eyebrowse-setup-opinionated-keys) (require 'evil-cleverparens-text-objects)) (add-hook 'evil-mode-hook 'my-after-evil)
- evil-cleverparens
I'm not a fan of it rebinding either the small or big word movements. There's unfortunately no option to do neither, so we'll have to undo all those bindings.
(with-eval-after-load 'evil-cleverparens (evil-cp--populate-mode-bindings-for-state evil-cp-regular-movement-keys 'normal nil))
Its insertion replacement inserting spaces isn't terribly helpful.
(setq evil-cleverparens-use-regular-insert t)
Why did they change
M-w
from copying the region to pasting something? I like the other bindings though, so I'll leave them as is.(with-eval-after-load 'evil-cleverparens (evil-define-key 'normal evil-cleverparens-mode-map (kbd "M-w") nil))
It's useful to have slurping/barfing in insert mode so that you can quickly update things while typing.
(with-eval-after-load 'evil-maps (define-key evil-insert-state-map (kbd "M-<") 'evil-cp-<) (define-key evil-insert-state-map (kbd "M->") 'evil-cp->))
This resolves a bug in the change command eating a space when applied on a word:
(with-eval-after-load 'evil-vars (push 'evil-cp-change evil-change-commands))
- undo-redo
As of Emacs 28.1, there's a built-in redo, so I'm enabling that instead of undo-tree.
(setq evil-undo-system 'undo-redo)
2.4.18. yasnippet
Snippets are quite useful for boilerplatey languages. Like, Java.
Although, if you take it far enough, even something as org-mode
qualifies considering I can never remember the proper syntax for code
blocks. The following sets up a single directory for snippets.
(setq yas-snippet-dirs '("~/.config/emacs/snippets")) (with-eval-after-load 'yasnippet (yas-reload-all))
This package does way too chatty logging by default:
(setq yas-verbosity 2)
2.4.19. Circe
rcirc
is too small, ERC is too large. So I chose Circe as my IRC
client living inside Emacs. As for why IRC in Emacs in the first
place, I wanted to leave irssi behind and didn't really like Weechat.
So, why not try something extensible?
(defun my-face-at-point-p (face) (let ((face-or-faces (get-text-property (point) 'face))) (cond ((consp face-or-faces) (memq face face-or-faces)) ((symbolp face-or-faces) (eq face face-or-faces))))) (defun my-circe-inhibit-nick-highlight-function () (or (eq major-mode 'circe-server-mode) (my-face-at-point-p 'circe-server-face) (my-face-at-point-p 'circe-my-message-face))) (setq circe-inhibit-nick-highlight-function 'my-circe-inhibit-nick-highlight-function) (setq circe-track-faces-priorities '(circe-highlight-nick-face))
- Authentication and identification
First of all, let's define who I am and change the quit/part message to something less advertising.
(setq circe-default-nick "wasamasa" circe-default-user "wasamasa" circe-default-realname "wasamasa" circe-default-part-message "Bye" circe-default-quit-message "Bye")
I'm using ZNC to connect to Freenode. Passwords for the Nickserv service are kept in pass.
(defun my-pass-password* (name) (car (process-lines "pass" name))) (fset 'my-pass-password (my-memoize 'my-pass-password*)) (defun my-retrieve-irc-password (_) (cond ((string= circe-server-network "ZNC/freenode") (my-pass-password "irc/znc")) (t (error "Unknown network")))) (setq circe-network-options '(("ZNC/freenode" :host "brause.cc" :port 30832 :user "wasamasa/freenode" :pass my-retrieve-irc-password :use-tls t :reduce-lurker-spam t) ("Freenode" :nick "not_wasamasa")))
- Basic usability tweaks
I cannot imagine why I wouldn't want to use in-line tab-completion with cycling just as it exists in other IRC clients.
(setq circe-use-cycle-completion t)
Highlighting the last read position in buffer is invaluable:
(setq lui-track-behavior 'before-switch-to-buffer)
Let's customize a few format strings.
(setq circe-format-self-say "<{nick}> {body}" circe-format-server-topic "*** Topic Change by {userhost}: {topic-diff}" circe-server-buffer-name "{network}" circe-prompt-string (propertize ">>> " 'face 'circe-prompt-face))
Other entities using my nickname are not ghosted by default, that's why I enable it, but only after authenticating in some way.
(setq circe-nickserv-ghost-style 'after-auth)
ZNC handles autojoins for me, but Circe does not recognize these. So, instead I'll just ignore all buffers that are opened implicitly.
(setq circe-new-buffer-behavior 'ignore)
General highlight settings.
(setq circe-track-faces-priorities '(circe-highlight-nick-face lui-highlight-face)) (setq circe-extra-nicks '("wasa" "bagelmasa") lui-formatting-list '(("\\(?:^\\|[[:space:]]\\)\\(\\*[^*[:space:]]+?\\*\\)\\(?:$\\|[[:space:]]\\)" 1 lui-strong-face) ("\\(?:^\\|[[:space:]]\\)\\(_[^_[:space:]]+?_\\)\\(?:$\\|[[:space:]]\\)" 1 underline)) circe-highlight-nick-in-server-messages-p nil circe-highlight-nick-type 'all)
When using the
circe-color-nicks
contrib module, please color nicknames inside messages as well.(setq circe-color-nicks-everywhere t)
Additionally to that, make use of colors more compatible with my theme.
(setq circe-color-nicks-pool-type '("#ffaf00" "#d75f00" "#d70000" "#00af00" "#5f00ff" "#0087ff" "#ff005f" "#8700d7"))
Sometimes I get spurious tracking buffers in the modeline, this seems to happen if the frame loses focus. Here's a workaround:
(autoload 'tracking-remove-visible-buffers "tracking") (add-hook 'focus-in-hook 'tracking-remove-visible-buffers)
- Add extra keybinds
Let's add a few extra keybindings common in all buffers Circe spawns. I want word killing to behave the same as for
comint
,C-l
to redraw and reposition andC-u
to kill the whole line since there's a more appropriate command than the default one bound toC-u
.(defun my-window-C-l () (interactive) (goto-char (point-max)) (recenter-top-bottom -1)) (with-eval-after-load 'lui (define-key lui-mode-map (kbd "<remap> <kill-word>") 'my-kill-word) (define-key lui-mode-map (kbd "<remap> <backward-kill-word>") 'my-backward-kill-word) (define-key lui-mode-map (kbd "C-l") 'my-window-C-l) (define-key lui-mode-map (kbd "C-u") 'lui-kill-to-beginning-of-line))
Copy-pasting from other sources (like, browsers) can leave more than one line of text in the input area. Directly sending it would be annoying as this would result in either multiple messages or autopaste detection. To avoid resorting to joining lines manually, I've written a command doing the opposite of
M-q
, but named it similiarly as the intent is the same (making the given text conform to a more suitable form).(defun my-trim-surrounding-lui-whitespace () (save-excursion (let ((bol (line-beginning-position))) (goto-char bol) (skip-syntax-forward "-") (when (/= (point) bol) (delete-region bol (point))) (let ((eol (point-max))) (goto-char eol) (skip-syntax-backward "-" bol) (when (/= (point) eol) (delete-region (point) eol)))))) (defun my-fill-lui-input () (interactive) (fill-delete-newlines lui-input-marker (point-max) nil nil nil) (my-trim-surrounding-lui-whitespace) (goto-char (point-max))) (with-eval-after-load 'lui (define-key lui-mode-map (kbd "M-q") 'my-fill-lui-input))
Sometimes I like knowing just how many people are online.
(defun my-circe-count-nicks () (interactive) (when (eq major-mode 'circe-channel-mode) (message "%i entities are online on %s." (length (circe-channel-nicks)) (buffer-name)))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c n") 'my-circe-count-nicks))
There's some occasional pastebin spamming going on at
#chicken
, for that I wrote me a small helper to report it instantly:(defvar my-vandusen-paste-re (rx bol (? space) ; margin property hides here "<vandusen>" (? (+ any)) "\"" (group (+ any)) "\" pasted \"" (group (+ any)) "\" " (group (+ any)) eol)) (defun my-report-spam () (interactive) (save-excursion (re-search-backward my-vandusen-paste-re nil t)) (let ((url (match-string-no-properties 3))) (when url (let ((id (cadr (split-string url "paste\\?id=")))) (insert "vandusen: spam " id))))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c s") 'my-report-spam))
I keep forgetting how to send people a memo:
(defun my-circe-send-memo (target message) (interactive "sTarget: \nsMessage: ") (cond ((equal circe-chat-target "#chicken") (insert (format "vandusen: tell %s: %s" target message))) ((member circe-chat-target '("#emacs" "#scheme")) (insert (format "rudybot: later tell %s %s" target message))) (t (user-error "Unsupported channel")))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c m") 'my-circe-send-memo))
I've registered a few nicks, but need to use them once in a while for them not to expire. The following command makes this task a bit easier:
(defun my-next-nick () (interactive) (when (not circe-server-buffer) (user-error "Used outside of a Circe buffer")) (let* ((nicks '("wasamasa" "wasa" "{{{" "}}}")) (current-nick (circe-nick)) (index (--find-index (equal it current-nick) nicks)) (new-nick (nth (mod (1+ index) (length nicks)) nicks))) (insert (format "/nick %s" new-nick)))) (with-eval-after-load 'circe (define-key circe-channel-mode-map (kbd "C-c >") 'my-next-nick))
I like irssi's key binding to switch to the next active buffer:
(with-eval-after-load 'circe (define-key circe-mode-map (kbd "M-a") 'tracking-next-buffer))
- Hacks
The standard nickname switching function is a bit silly. I own a bunch of nicknames and will use the
wasa
one for switching.(defun my-circe-nick-next (oldnick) (cond ((string= oldnick "wasamasa") "wasa") ((string= oldnick "wasa" "wasamasa")))) (setq circe-nick-next-function 'my-circe-nick-next)
There isn't a highlighting function yet that could do something useful like setting a X urgency hint (taken from the wiki), asides from that I want a bit more of control to treat highlights in private queries different from channel highlights. The following code yanks out the default one and replaces it with something slightly better.
(defun my-x-urgency-hint (&rest _) (let* ((wm-hints (append (x-window-property "WM_HINTS" nil "WM_HINTS" nil nil t) nil)) (flags (car wm-hints))) (setcar wm-hints (logior flags #x00000100)) (x-change-window-property "WM_HINTS" wm-hints nil "WM_HINTS" 32 t))) (defvar my-circe-hl-function 'my-x-urgency-hint) (defvar my-bot-nicks '("fsbot" "phrik")) (defun my-circe-message-regex-handler (nick userhost command &rest args) (let* ((irc-message (cadr args)) (nick-regexp (regexp-opt (append (list (circe-server-nick)) circe-extra-nicks) 'symbols)) (highlight-match (string-match-p nick-regexp irc-message))) (when (and irc-message (not (member nick my-bot-nicks)) (not (circe--ignored-p nick userhost irc-message)) (not (string-match-p "LAGMON" irc-message)) (or highlight-match (string= (car args) (circe-server-nick)))) (message "HL: %s!%s %s %S" nick userhost command args) (funcall my-circe-hl-function nick userhost command args))) 'noop) (with-eval-after-load 'circe (circe-set-display-handler "PRIVMSG" 'my-circe-message-regex-handler))
Highlight quoted text in a green color for fun and profit (or to be honest, to discern 4chan people from the rest).
(defface my-circe-greentext-face '((t (:foreground "spring green"))) "Face for greentext detected in circe.") (defun my-circe-color-greentext () (when (memq major-mode '(circe-channel-mode circe-query-mode)) (let ((body-beg (text-property-any (point-min) (point-max) 'lui-format-argument 'body)) (greentext-regex "\\([^[:space:]]+?: \\)?\\(>[[:word:][:space:]]\\)")) (when body-beg (goto-char body-beg) (when (looking-at greentext-regex) (add-text-properties (match-beginning 2) (point-max) '(face my-circe-greentext-face))))))) (add-hook 'lui-pre-output-hook 'my-circe-color-greentext)
I dislike
custom
, but want persistent fools and ignore. So, advice it is!(defvar my-circe-fool-file "~/.config/emacs/etc/fools") (defvar my-circe-fool-list nil) (defvar my-circe-ignore-file "~/.config/emacs/etc/ignore") (defvar my-circe-ignore-list nil) (defun my-serialize (path data) (with-temp-file path (let (print-length print-level) (insert (prin1-to-string data)) (pp-buffer)))) (defun my-deserialize (path) (when (file-exists-p path) (with-temp-buffer (insert-file-contents-literally path) (goto-char (point-min)) (read (current-buffer))))) (defun my-circe-command-FOOL (line) (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line))) (reason (read-string "Why? "))) (push (list regex reason) my-circe-fool-list)) (my-serialize my-circe-fool-file my-circe-fool-list)) (defun my-circe-command-UNFOOL (line) (let ((regex (and (string-match "\\S-+" line) (match-string 0 line)))) (setq my-circe-fool-list (--remove (equal (car it) regex) my-circe-fool-list))) (my-serialize my-circe-fool-file my-circe-fool-list)) (defun my-circe-command-IGNORE (line) (let* ((regex (and (string-match "\\S-+" line) (match-string 0 line))) (reason (read-string "Why? "))) (push (list regex reason) my-circe-ignore-list)) (my-serialize my-circe-ignore-file my-circe-ignore-list)) (defun my-circe-command-UNIGNORE (line) (let ((regex (and (string-match "\\S-+" line) (match-string 0 line)))) (setq my-circe-ignore-list (--remove (equal (car it) regex) my-circe-ignore-list))) (my-serialize my-circe-ignore-file my-circe-ignore-list)) (advice-add 'circe-command-FOOL :after 'my-circe-command-FOOL) (advice-add 'circe-command-UNFOOL :after 'my-circe-command-UNFOOL) (advice-add 'circe-command-IGNORE :after 'my-circe-command-IGNORE) (advice-add 'circe-command-UNIGNORE :after 'my-circe-command-UNIGNORE) (with-eval-after-load 'circe (setq my-circe-fool-list (my-deserialize my-circe-fool-file)) (setq my-circe-ignore-list (my-deserialize my-circe-ignore-file)) (setq circe-fool-list (mapcar 'car my-circe-fool-list)) (setq circe-ignore-list (mapcar 'car my-circe-ignore-list)))
Let's test having fluid-width windows, now that both Circe and Emacs 24.4 seem to be less wonky about it. Adapted from the wiki, extended to avoid the rather annoying behaviour of the cursor jumping into the fringe when reaching the full text width.
(setq lui-time-stamp-position 'right-margin lui-fill-type nil) (defun my-no-fill-lui-setup () (setq fringes-outside-margins t right-margin-width 10 visual-fill-column-extra-text-width '(0 . -10) fill-column 80 wrap-prefix " ") (visual-line-mode) (setf (cdr (assoc 'continuation fringe-indicator-alist)) nil) (make-local-variable 'overflow-newline-into-fringe) (setq overflow-newline-into-fringe nil)) (add-hook 'circe-chat-mode-hook 'my-no-fill-lui-setup)
Port of the pangotext irssi script:
(defvar my-pango-color '(("white" . 0) ("black" . 1) ("blue" . 2) ("green" . 3) ("lightred" . 4) ("red" . 5) ("purple" . 6) ("orange" . 7) ("yellow" . 8) ("lightgreen" . 9) ("cyan" . 10) ("lightcyan" . 11) ("lightblue" . 12) ("lightpurple" . 13) ("gray" . 14) ("lightgray" . 15))) (defvar my-pango-color-order ["white" "lightgray" "lightcyan" "lightblue" "lightgreen" "lightpurple" "yellow" "lightred" "orange" "red" "purple" "cyan" "blue" "green" "gray" "black"]) (defvar my-pango-color-ordermap nil) (dotimes (i (length my-pango-color-order)) (push (cons (aref my-pango-color-order i) i) my-pango-color-ordermap)) (defvar my-pango-tag-registry '(("rb" . my-pango-rainbow) ("rainbow" . my-pango-rainbow) ("checker" . my-pango-checker) ("checkers" . my-pango-checker) ("gradient" . my-pango-gradient) ("gradiant" . my-pango-gradient) ("grad" . my-pango-gradient) ("ul" . my-pango-underline) ("underline" . my-pango-underline) ("b" . my-pango-bold) ("bold" . my-pango-bold) ("inv" . my-pango-inverse) ("inverse" . my-pango-inverse))) (defun my-pango-palettize (text palette) (with-temp-buffer (let ((count 0)) (dotimes (i (length text)) (let ((char (aref text i))) (when (not (= (char-syntax char) ?\s)) (insert ?\003 (format "%02d" (aref palette (% count (length palette))))) (setq count (1+ count))) (when (= char ?,) (insert ?,)) (insert char)))) (insert ?\003) (buffer-string))) (defun my-pango-rainbow (text &optional _attribs) (let ((palette [2 3 4 5 6 7 8 9 10 11 12 13])) (my-pango-palettize text palette))) (defun my-pango-gradient (text &optional attribs) (let* ((start (or (cdr (assoc "start" attribs)) "white")) (end (or (cdr (assoc "end" attribs)) "lightpurple")) (start (cdr (assoc start my-pango-color-ordermap))) (end (cdr (assoc end my-pango-color-ordermap))) (min (min start end)) (max (max start end)) (i min) palette) (while (<= i max) (push (cdr (assoc (aref my-pango-color-order (% i (length my-pango-color-order))) my-pango-color)) palette) (setq i (1+ i))) (my-pango-palettize text (vconcat (nreverse palette))))) (defun my-pango-checker (text &optional _attribs) (with-temp-buffer (let ((count 0) (palette ["01,04" "04,01"])) (dotimes (i (length text)) (let ((char (aref text i))) (when (not (= (char-syntax char) ?\s)) (insert ?\003 (aref palette (% count (length palette)))) (setq count (1+ count))) (when (= char ?,) (insert ?,)) (insert char)))) (insert ?\003) (buffer-string))) (defun my-pango-bold (text &optional _attribs) (format "\002%s\002" text)) (defun my-pango-underline (text &optional _attribs) (format "\037%s\037" text)) (defun my-pango-inverse (text &optional _attribs) (format "\026%s\026" text)) (defvar my-pango-tag-rx (rx "<" (* space) (group (+ (not (any ">" space)))) (* space) (group (* (not (any ">")))) ">" (group (+? any)) "<" (? "/") (backref 1) ">")) (defvar my-pango-tag-attr-rx (rx (group (+ (not space))) (* space) "=" (* space) (group (+ (not space))))) (defun my-pango-render (text) (while (string-match my-pango-tag-rx text) (let* ((action (match-string 1 text)) (extra (match-string 2 text)) (msg (match-string 3 text)) (op (cdr (assoc action my-pango-tag-registry))) attribs) (save-match-data (while (string-match my-pango-tag-attr-rx extra) (push (cons (match-string 1 extra) (match-string 2 extra)) attribs) (setq extra (replace-match "" t t extra)))) (when (not op) (user-error "unknown action: %s" action)) (setq text (replace-match (funcall op msg attribs) t t text)))) text) (defun circe-command-PANGO (line) "Send LINE using Pango markup." (interactive "sLine: ") (if (not circe-chat-target) (circe-display-server-message "No target for current buffer") (let* ((colored (my-pango-render line)) (preview (with-temp-buffer (insert colored) (lui-irc-colors) (buffer-string)))) (when (yes-or-no-p (format "Send message? %s" preview)) (circe-command-SAY colored)) (message ""))))
- Entry point
And finally, the function for entering IRC.
(defun my-irc () "Connect to all my IRC servers after enabling contrib modules." (interactive) (circe-lagmon-mode) (enable-circe-color-nicks) (enable-lui-autopaste) (enable-lui-track) (require 'circe-chanop) (circe "ZNC/freenode"))
2.4.20. hl-todo
Minor mode for coloring TODO
, NOTE
, FIXME
and many more keywords
of that sort prevalent in comments and strings.
(setq hl-todo-keyword-faces '(("TODO" . hl-todo) ("NOTE" . hl-todo) ("HACK" . hl-todo) ("FIXME" . hl-todo) ("KLUDGE" . hl-todo)))
2.4.21. macrostep
Expand macros interactively.
I'll go with the recommended keybinding for it.
(define-key emacs-lisp-mode-map (kbd "C-x e") 'macrostep-expand)
2.4.22. visual-fill-column
There used to be longlines-mode
which did display a file
soft-wrapped without breaking words and using the value of
fill-column
. However it got deprecated for visual-line-mode
which
does the same except it doesn't take fill-column
into account. The
visual-fill-column
package fixes that, I want to enable its mode
automatically when enabling visual-line-mode
.
(add-hook 'visual-line-mode-hook 'visual-fill-column-mode)
2.4.23. magit
The very best.
For some reason they've started nagging me to opt-in for default behaviour to be used without confirmation.
(setq magit-revert-buffers 'silent
magit-push-always-verify nil)
Enable inline highlighting of differences for all hunks:
(setq magit-diff-refine-hunk 'all)
Insert a helpful reminder about commit message rules if I happen to hack on Emacs:
(defun my-git-commit-emacs-setup () (let ((repo-path (expand-file-name "~/code/emacsen/emacs-git"))) (when (and (string-match-p (format "^%s" repo-path) default-directory) (looking-at "^$")) ; don't do this when there's a merge message (insert "Press C in the diff buffer and end each changelog line with a period") (goto-char (point-min))))) (add-hook 'git-commit-setup-hook 'my-git-commit-emacs-setup)
Experimental:
(setq magit-display-buffer-function 'display-buffer
magit-display-file-buffer-function 'display-buffer)
2.4.24. nov.el
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
2.4.25. alda-mode
Make :
and indentation less magic, then use this mode for waka.
(with-eval-after-load 'alda-mode (define-key alda-mode-map (kbd ":") 'self-insert-command)) (defun my-alda-mode-setup () (setq indent-line-function 'indent-relative)) (add-hook 'alda-mode-hook 'my-alda-mode-setup) (add-to-list 'auto-mode-alist '("\\.waka\\'" . alda-mode))
2.4.26. rainbow-delimiters-mode
I like highlighting the outermost parentheses, but keep the inner ones in a lighter color. This is done by the theme and a customization option:
(setq rainbow-delimiters-outermost-only-face-count 1)
2.4.27. Elpher
Finally a usable Gopher client. I'd like it to open gopher:// URLs for
me, yet keep using xdg-open
for everything else.
(defun my-browse-url-elpher (url &rest args) (require 'elpher) (elpher-go url)) ;; emacs 28.1 (setq browse-url-handlers '(("^gopher://" . my-browse-url-elpher) ("." . browse-url-xdg-open)))
Another odd choice is how it customizes Evil to use motion state for its buffers.
(with-eval-after-load 'elpher
(evil-set-initial-state 'elpher-mode 'emacs))
2.4.28. explain-pause-mode
This might help with debugging UX issues. It's a bit too noisy for my taste though, so let's make it more quiet and increase the threshold:
(setq explain-pause-alert-via-message nil) (setq explain-pause-blocking-too-long-ms 100)
2.5. Keybinds
We already have F1
for help, so let's turn C-h
and M-h
more
readline-like.
(global-set-key (kbd "C-h") 'delete-backward-char) (global-set-key (kbd "M-h") 'backward-kill-word)
Deactivate all other uses of insert than Shift-Insert
.
(global-set-key (kbd "<insert>") nil) (global-set-key (kbd "<C-insert>") nil)
FWIW, yanking is what Emacs calls pasting text. Unlike Vim where it
stands for copying text. Anyways, I want to rectify the curious
default of making S-insert
paste from the same place as C-y
.
(defun my-yank-primary () (interactive) (let ((primary (or (x-get-selection-value) (x-get-selection)))) (unless primary (error "No selection is available")) (push-mark (point)) (insert-for-yank primary))) (global-set-key (kbd "<S-insert>") 'my-yank-primary)
Install a keybind that saves all buffers with asking (use a prefix
argument to inhibit the questions), then kills Emacs (including the
daemon) on M-<f4>
.
(defun my-quit-emacs (arg) (interactive "P") (save-some-buffers (when (consp arg) t) t) (kill-emacs)) (global-set-key (kbd "M-<f4>") 'my-quit-emacs)
Make M-x
more useful, put its original functionality on C-c M-x
instead.
(global-set-key (kbd "M-x") 'helm-smex) (global-set-key (kbd "C-c M-x") 'execute-extended-command)
Helm stuff
(global-set-key (kbd "C-x C-f") 'helm-find-files) (global-set-key (kbd "C-x b") 'helm-buffers-list) (global-set-key (kbd "<f10>") 'helm-resume)
C-c C-+
and C-c C--
are pretty useful, but only resize the current
buffer. Here's a hack using set-frame-font
and altering the font
size only:
(defun my-alter-frame-font-size (fn) (let* ((current-font-name (frame-parameter nil 'font)) (decomposed-font-name (x-decompose-font-name current-font-name)) (font-size (string-to-number (aref decomposed-font-name 5)))) (aset decomposed-font-name 5 (number-to-string (funcall fn font-size))) (set-frame-font (x-compose-font-name decomposed-font-name)))) (defun my-inc-frame-font-size () (interactive) (my-alter-frame-font-size '1+)) (defun my-dec-frame-font-size () (interactive) (my-alter-frame-font-size '1-)) (global-set-key (kbd "C-+") 'my-inc-frame-font-size) (global-set-key (kbd "C-=") 'my-inc-frame-font-size) (global-set-key (kbd "C--") 'my-dec-frame-font-size)
2.6. Hooks
First of all, let's define a helper function that does the boilerplate for us.
(defun my-add-function-to-hooks (function hooks) (dolist (hook hooks) (add-hook hook function)))
2.6.1. Basic setup
I'll start with a list of hooks for everything that's not a special mode or in other words, related to programming and text editing. This will inevitably contain modes that have not been properly derived, might be worth reporting those.
(defun my-non-special-modes-setup () (setq indicate-empty-lines t) (setq indicate-buffer-boundaries '((top . left) (bottom . left))) (setq show-trailing-whitespace t) (modify-syntax-entry ?_ "w") (goto-address-mode) (smartparens-mode) (show-smartparens-mode) (yas-minor-mode)) (my-add-function-to-hooks 'my-non-special-modes-setup '(text-mode-hook prog-mode-hook css-mode-hook diff-mode-hook))
Same deal with programming-related hooks and text-related hooks.
(my-add-function-to-hooks 'auto-fill-mode '(text-mode-hook css-mode-hook))
(defun my-prog-modes-setup () (make-local-variable 'comment-auto-fill-only-comments) (setq comment-auto-fill-only-comments t) (auto-fill-mode) (column-enforce-mode)) (my-add-function-to-hooks 'my-prog-modes-setup '(prog-mode-hook))
If a file contains a shebang, mark it as executable after saving.
(add-hook 'after-save-hook 'executable-make-buffer-file-executable-if-script-p)
2.6.2. Mode-specific setup
To fine tune completion behavior, I'll prefer company-mode
over
global-company-mode
. I should add more hooks for REPLs, too.
(my-add-function-to-hooks 'company-mode '(prog-mode-hook css-mode-hook nxml-mode-hook sgml-mode-hook css-mode-hook ielm-mode-hook))
Enable linting for a few select modes by default. The rest is
programming languages I don't happen to use, languages I don't have a
linter installed for or that would be too annoying to always check.
Like, the default emacs-lisp
(and emacs-lisp-checkdoc
, too) linter
which always assumes I'm writing an Emacs package.
(my-add-function-to-hooks 'flycheck-mode '(python-mode-hook ruby-mode-hook enh-ruby-mode-hook LaTeX-mode-hook clojure-mode-hook clojurescript-mode-hook)) (my-add-function-to-hooks 'flymake-mode '(scheme-mode-hook))
Lisp-specific setup goes here:
(defun my-lisp-modes-setup () (rainbow-delimiters-mode) (evil-cleverparens-mode)) (my-add-function-to-hooks 'my-lisp-modes-setup '(emacs-lisp-mode-hook ielm-mode-hook scheme-mode-hook inferior-scheme-mode-hook lisp-mode-hook lisp-interaction-mode-hook clojure-mode-hook nrepl-interaction-mode-hook))
Colored color codes however only make sense for CSS, HTML and XML.
(my-add-function-to-hooks 'rainbow-mode '(css-mode-hook sgml-mode-hook nxml-mode-hook web-mode-hook))
smartparens
ought to be enabled in the minibuffer, but only for M-:
.
(defun my-smartparens-minibuffer-setup () (when (eq this-command 'eval-expression) (smartparens-mode) (show-smartparens-mode))) (add-hook 'minibuffer-setup-hook 'my-smartparens-minibuffer-setup)
I don't want .l or .lsp files to be considered CL files, so I'll use
lisp-data-mode
for them.
(add-to-list 'auto-mode-alist (cons (rx "." (or "l" "lsp") eos) 'lisp-data-mode))
Likewise, any .sexp file should be considered Lisp data.
(add-to-list 'auto-mode-alist (cons (rx ".sexp" eos) 'lisp-data-mode))
2.7. Other software
I'm using qutebrowser which provides a C-e
keybind for editing the
contents of the current text box inside my editor. As it creates a
file at /tmp/qutebrowser-editor-<random>.txt
, I'm changing some of
the text-mode defaults for nicer editing:
(defun my-qutebrowser-edit () (markdown-mode) (auto-fill-mode -1) (setq require-final-newline nil)) (add-to-list 'auto-mode-alist '("\\`/tmp/qutebrowser-editor-" . my-qutebrowser-edit))
MAL requires me to use modes for lesser known languages, so their config goes there:
(defvar inhibit-first-line-modes-regexps nil) ; HACK (add-to-list 'load-path "/usr/local/share/emacs/site-lisp/") (autoload 'smalltalk-mode "smalltalk-mode" "Smalltalk mode" t) (add-to-list 'auto-mode-alist '("\\.st\\'" . smalltalk-mode)) (push 'smalltalk-mode evil-normal-state-modes) (add-hook 'smalltalk-mode-hook 'my-non-special-modes-setup) (add-hook 'smalltalk-mode-hook 'my-prog-modes-setup) (with-eval-after-load 'smalltalk-mode (define-key smalltalk-mode-map "!" nil) (define-key smalltalk-mode-map ":" nil)) (add-to-list 'auto-mode-alist '("\\.cfml?\\'" . js-mode)) (add-to-list 'auto-mode-alist '("\\.cfc\\'" . js-mode)) (defun my-sclang-mode () (require 'sclang) (sclang-mode)) (autoload 'sclang-mode "sclang-mode" "SCLang mode" t) (add-to-list 'auto-mode-alist '("\\.sc\\'" . my-sclang-mode)) (push 'sclang-mode evil-normal-state-modes) (setq sclang-indent-level 4)
I use keychain to manage passphrases for SSH, the following integrates it into Emacs:
(defun my-keychain-setup () (let* ((output (shell-command-to-string "keychain --query --quiet id_rsa id_ed25519")) (lines (split-string output "\n" t))) (dolist (line lines) (when (string-match "^\\([^=]+\\)=\\(.*\\)$" line) (setenv (match-string 1 line) (match-string 2 line))))))
cloc
replacement:
(defun my-cloc () (interactive) (let ((blanks 0) (comments 0) (code 0)) (font-lock-ensure) (save-excursion (save-restriction (when (region-active-p) (narrow-to-region (region-beginning) (region-end))) (goto-char (point-min)) (while (not (eobp)) (skip-syntax-forward " ") (cond ((eolp) (setq blanks (1+ blanks))) ((memq (get-text-property (point) 'face) '(font-lock-comment-face font-lock-comment-delimiter-face)) (setq comments (1+ comments))) (t (setq code (1+ code)))) (forward-line 1)))) (message "blank: %d, comment: %d, code: %d" blanks comments code)))
2.8. Load unpublished packages
Emacs Lisp files can be easily turned into packages one can load and
install via package.el
. While initialization is done, it only
happens after successful startup.
My unpublished packages reside in ~/.config/emacs/unpublished
and need to
be added to the load-path
.
(add-to-list 'load-path "~/.config/emacs/unpublished") (load "~/.config/emacs/unpublished/private.el")
2.9. Set up unpublished packages
I have a bunch of stuff I'm working on.
(require 'helm-smex)
2.10. Post-Init
While packages were prematurely initialized, the following is unconditionally run after init has finished.
(defun my-after-init () (setq my-init-time 0) (add-to-list 'load-path "~/code/elisp/mine/evil") (add-to-list 'load-path "~/code/elisp/mine/circe") (autoload 'circe-lagmon-mode "circe-lagmon" "Enable lagmon" t) (autoload 'enable-circe-color-nicks "circe-color-nicks" "Enable coloring nicks" t) (autoload 'enable-lui-autopaste "lui-autopaste" "Enable lui autopaste" t) (autoload 'enable-lui-track "lui-track" "Enable lui track bar" t) (autoload 'circe "circe" "Enable circe" t) (autoload 'nov-mode "nov" nil t) (autoload 'chicken-doc-describe "chicken-doc" nil t) (autoload 'flymake-chicken-backend "flymake-chicken" nil t) (add-to-list 'load-path "~/code/elisp/mine/shackle") (add-to-list 'load-path "~/code/elisp/mine/eyebrowse") (add-to-list 'load-path "~/code/elisp/mine/form-feed") (add-to-list 'load-path "~/code/elisp/mine/firestarter") (add-to-list 'load-path "~/code/elisp/mine/esxml") (add-to-list 'load-path "~/code/elisp/mine/nov.el") (add-to-list 'load-path "~/code/elisp/mine/goto-chg") (add-to-list 'load-path "~/code/elisp/mine/chip8") (add-to-list 'load-path "~/code/elisp/mine/chicken-doc.el") (add-to-list 'load-path "~/code/elisp/mine/flymake-chicken") (add-to-list 'load-path "~/code/elisp/mine/yaml-mode") (add-to-list 'load-path "~/code/elisp/mine/svnwiki-mode") (with-timer (global-so-long-mode)) ;; (with-timer (explain-pause-mode)) (with-timer (sml/setup)) (with-timer (recentf-mode)) (with-timer (savehist-mode)) (with-timer (save-place-mode)) (with-timer (require 'saveplace)) (with-timer (winner-mode)) (with-timer (shackle-mode)) (with-timer (firestarter-mode)) (with-timer (smex-initialize)) (with-timer (helm-mode)) (with-timer (electric-indent-mode -1)) (with-timer (my-setup-hydra)) (with-timer (require 'evil)) (with-timer (evil-mode)) (with-timer (my-disable-tramp-file-handlers)) (with-timer (cl-lib-highlight-initialize)) (with-timer (cl-lib-highlight-warn-cl-initialize)) (with-timer (line-number-mode)) (with-timer (column-number-mode)) (with-timer (my-keychain-setup)) (with-timer (company-tng-configure-default)) (with-timer (global-form-feed-mode)) (with-timer (global-hl-todo-mode)) (load custom-file) (setq gc-cons-threshold 800000) (message "post-init: (%.3fs)" my-init-time)) (add-hook 'after-init-hook 'my-after-init)
3. Stuff to do
- Learn Yasnippet and write snippets
- Check company backends and reconsider their uses
- Mix in Yasnippet
- Configure smartparens properly, contribute defaults
- Fix show-smartparens on that configuration block
- Get modes not working with prog- or text-mode-hook fixed
- Use nadvice for
my-kill-process-buffer-on-exit
- https://emacs.stackexchange.com/questions/21205/flycheck-with-file-relative-eslint-executable https://github.com/ananthakumaran/tide
4. Epilogue
I consider this experiment successful. I've managed to uncruftify my previous setup, have a highly readable init file and get recommendations and Github favourites from an reasonable amount of people. It went better than expected because explaining my reasoning and hacks in a config file fits me better than doing the same for a program's code.
Debugging has suffered a bit due to the indirection. While C-x C-e
works globally, I no longer have C-M-x
and the prefix argument
version for instrumenting, so I need to copy the piece of code over to
a scratch buffer. esup reports an artificially low time, unlike what
M-x emacs-init-time
does.