This org-mode file gets built into an elisp file, and we want this comment at the top for performance reasons.
;; -*- lexical-binding: t; -*-
(setq user-full-name "Robin Schroer"
user-login-name "sulami"
user-mail-address "[email protected]"
sulami/source-directory "~/src")
custom.el
is hidden state, all config is declarative.
(setq custom-file (make-temp-file ""))
This way we lose everything backups if the whole machine crashes, but we don’t accidentally leave backups around.
(setq backup-directory-alist '(("." . "~/emacs-backup/"))
auto-save-file-name-transforms '((".*" "~/emacs-auto-save/" t))
create-lockfiles nil)
This reloads changed files which are open in Emacs but not edited.
(setq revert-without-query '(".*"))
(setq delete-by-moving-to-trash nil)
(use-package recentf
:straight nil
:custom
(recentf-max-saved-items 1024)
:hook
(after-init . recentf-mode))
(setq inhibit-splash-screen t
inhibit-startup-screen t
inhibit-startup-message t)
(setq suggest-key-bindings nil)
These are just fixes to make TRAMP work and be reasonably fast. Mostly sourced from the internet, I don’t pretend to know how this actually works.
(setq tramp-default-method "ssh"
tramp-ssh-controlmaster-options
"-o ControlMaster=auto -o ControlPath='tramp.%%C'")
;; Various speedups
;; from https://www.gnu.org/software/emacs/manual/html_node/tramp/Frequently-Asked-Questions.html
(setq remote-file-name-inhibit-cache 3600
tramp-completion-reread-directory-timeout nil
vc-ignore-dir-regexp (format "%s\\|%s"
vc-ignore-dir-regexp
tramp-file-name-regexp)
tramp-verbose 0)
;; Disable the history file on remote hosts
(setq tramp-histfile-override t)
;; Save backup files locally
;; from https://stackoverflow.com/a/47021266
(add-to-list 'backup-directory-alist
(cons tramp-file-name-regexp "/tmp/emacs-backup/"))
(require 'saveplace)
(save-place-mode 1)
(setq vc-follow-symlinks t)
(setq ring-bell-function 'ignore)
I find this mostly distracting.
(blink-cursor-mode -1)
(setq initial-scratch-message "")
This allows me to use winner-undo
if I accidentally delete a window.
(add-hook 'after-init-hook 'winner-mode)
(setq initial-major-mode 'emacs-lisp-mode)
(setq help-window-select t)
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq sentence-end-double-space nil)
It’s disabled by default, and then gets enabled for all file-based buffer modes, so not for REPLS and shells.
(setq-default show-trailing-whitespace nil)
(defun sulami/show-trailing-whitespace ()
"Just sets `show-trailing-whitespace'."
(setq show-trailing-whitespace t))
(add-hook 'prog-mode-hook 'sulami/show-trailing-whitespace)
(add-hook 'text-mode-hook 'sulami/show-trailing-whitespace)
This shows vim-style tildes on the left fringe.
(when (display-graphic-p)
(setq-default indicate-empty-lines t)
(define-fringe-bitmap 'tilde [0 0 0 113 219 142 0 0] nil nil 'center)
(setcdr (assq 'empty-line fringe-indicator-alist) 'tilde))
This disables expensive modes when a buffer has very long lines to prevent performance issues.
(if (version<= "27.1" emacs-version)
(global-so-long-mode 1))
I prefer this over using rainbow parentheses, which make it difficult to see what’s actually happening.
(show-paren-mode 1)
These settings were lifted off the internet™ and make scrolling with pointing devices feel more reasonable.
(setq mouse-wheel-progressive-speed nil
mouse-wheel-scroll-amount '(1 ((shift) . 1) ((control) . nil)))
At least as a default, much nicer when resizing windows.
(set-default 'truncate-lines t)
(setq line-move-visual nil)
(setq-default buffer-file-coding-system 'utf-8)
(setenv "LANG" "en_US.UTF-8")
(setenv "LC_ALL" "en_US.UTF-8")
(prefer-coding-system 'utf-8)
Use aspell
with British English.
(use-package flyspell
:straight nil
:custom
(ispell-program-name "aspell")
(ispell-extra-args (quote ("--sug-mode=ultra" "--lang=en_GB-ise")))
(flyspell-sort-corrections nil)
(flyspell-issue-message-flag nil)
:hook
(prog-mode . flyspell-prog-mode))
(put 'erase-buffer 'disabled nil)
(fset 'yes-or-no-p 'y-or-n-p)
(setq confirm-kill-emacs 'yes-or-no-p)
Set the frame title to the current project name. This is useful if I have several frames/Emacsen open and want to switch between them.
(setq frame-title-format
(list :eval '(let ((p-name (projectile-project-name)))
(if (string-equal p-name "-")
"Emacs"
(concat "Emacs - " p-name)))))
(if (and (fboundp 'tool-bar-mode)
tool-bar-mode)
(tool-bar-mode -1))
(if (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(if (fboundp 'tooltip-mode) (tooltip-mode -1))
This assumes GPG-Agent is already running. Otherwise start it with
gpg-agent --daemon
.
(setenv "SSH_AUTH_SOCK" (string-trim (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))
For example to yank while entering into the minibuffer.
(setq enable-recursive-minibuffers t)
Everything in here relates to macOS in some way.
The MacPorts build I’m using swaps the modifiers from what I’m used to, so I’m swapping them back.
(setq mac-command-modifier 'super
mac-option-modifier 'meta)
Especially Alfred likes to paste with ⌘-v
, so that needs to work.
(define-key global-map (kbd "s-v") 'yank)
(define-key global-map (kbd "<s-return>") 'toggle-frame-maximized)
(define-key global-map (kbd "s-f") #'mac-font-panel-mode)
The MacPorts Emacs version I’m using has the peculiar behaviour that
requires menu-bar-mode
to be enabled in order to focus the current
frame when switching workspaces.
;; Use `mac-font-panel-mode' as a proxy to find out if this is the
;; MacPorts version.
(when (fboundp 'mac-font-panel-mode)
(menu-bar-mode 1))
(defun sulami/macos-dark-theme-p ()
"Return non-nil if on macOS and currently in dark theme."
(when (fboundp 'mac-application-state)
(equal "NSAppearanceNameDarkAqua"
(plist-get (mac-application-state) :appearance))))
Default straight
to install anything use-package
defines.
(setq straight-use-package-by-default t)
Allows for patching functions in packages.
(use-package el-patch)
List library that comes in handy.
(use-package dash)
(defun sulami/update-packages ()
"Prunes and updates packages, revalidates patches."
(straight-prune-build-directory)
(straight-pull-all)
(el-patch-validate-all)
(straight-freeze-versions)
(byte-recompile-directory "~/.emacs.d/straight/build" nil 'force))
Set the font to Fira Code and enable ligatures.
(let ((font "PragmataPro Mono 14"))
(set-face-attribute 'default nil :font font)
(set-frame-font font nil t))
;; (when (boundp 'mac-auto-operator-composition-mode)
;; (mac-auto-operator-composition-mode))
I use doom-themes
, mostly doom-solarized-light
& doom-gruvbox
.
There are some fixes to prevent themes from clashing, and I also disable most backgrounds as I find them distracting.
;; I like to live dangerously
(setq custom-safe-themes t)
(defconst sulami/light-theme 'doom-solarized-light)
(defconst sulami/dark-theme 'doom-gruvbox)
(defun sulami/disable-all-themes ()
"Disables all custom themes."
(interactive)
(mapc #'disable-theme custom-enabled-themes))
(defun sulami/before-load-theme-advice (theme &optional no-confirm no-enable)
"Disable all themes before loading a new one.
Prevents mixing of themes, where one theme doesn't override all faces
of another theme."
(sulami/disable-all-themes))
(advice-add 'load-theme
:before
#'sulami/before-load-theme-advice)
(defun sulami/set-face-straight-underline (face)
"Remove FACE's :underline style, if it's set"
(when (display-graphic-p)
(let ((old-attr (face-attribute face :underline)))
(when (eq 'cons (type-of old-attr))
(set-face-attribute face nil :underline (nth 3 old-attr))))))
(defun sulami/after-load-theme-advice (theme &optional no-confirm no-enable)
"Unsets backgrounds for some org-mode faces.
Also changes squiggly underlines to straight ones."
(require 'flycheck)
(set-face-background 'outline-1 nil)
(set-face-background 'org-block nil)
(set-face-background 'org-block-begin-line nil)
(set-face-background 'org-block-end-line nil)
(set-face-background 'org-quote nil)
(cl-loop for face in '(flyspell-incorrect
flyspell-duplicate
flycheck-error
flycheck-warning
flycheck-info)
do (sulami/set-face-straight-underline face)))
(advice-add 'load-theme
:after
#'sulami/after-load-theme-advice)
(use-package doom-themes
:after (dash)
:init
(setq doom-themes-enable-bold t
doom-themes-enable-italic t)
:config
(doom-themes-org-config)
;; Set the default colourscheme according to the time of day
:hook
(after-init . (lambda ()
(when (display-graphic-p)
(let* ((hour-of-day (read (format-time-string "%H")))
(theme (if (or (not (sulami/macos-dark-theme-p))
(<= 8 hour-of-day 17))
sulami/light-theme
sulami/dark-theme)))
(load-theme theme t)
(sulami/after-load-theme-advice theme))))))
(use-package mac-auto-theme
:straight nil
:no-require t
:if (fboundp 'mac-application-state)
:after (doom-themes)
:hook
(mac-effective-appearance-change . (lambda ()
(when (display-graphic-p)
(load-theme
(if (sulami/macos-dark-theme-p)
sulami/dark-theme
sulami/light-theme)
t)))))
I use doom-modeline
, without any icons, and patched to be regular
height.
(use-package doom-modeline
:hook (after-init . doom-modeline-mode)
:custom
(doom-modeline-icon nil)
(doom-modeline-height 10)
(doom-modeline-buffer-file-name-style 'relative-to-project)
(doom-modeline-buffer-encoding nil)
(doom-modeline-persp-name nil)
(doom-modeline-vcs-max-length 36)
:config/el-patch
(defun doom-modeline--font-height ()
"Calculate the actual char height of the mode-line."
(let ((height (face-attribute 'mode-line :height)))
;; WORKAROUND: Fix tall issue of 27 on Linux
;; @see https://github.com/seagle0128/doom-modeline/issues/271
(round
(* (if (and (>= emacs-major-version 27)
(not (eq system-type 'darwin)))
1.0
(if doom-modeline-icon
(el-patch-swap 1.68 1.0)
(el-patch-swap 1.25 1.0)))
(cond ((integerp height) (/ height 10))
((floatp height) (* height (frame-char-height)))
(t (frame-char-height))))))))
(defun sulami/open-emacs-config ()
"Opens the config file for our favourite OS."
(interactive)
(find-file sulami/emacs-config-file))
(defun sulami/reload-emacs-config ()
"Loads the config file for our favourite OS."
(interactive)
(org-babel-load-file sulami/emacs-config-file))
(defun sulami/rename-file-and-buffer ()
"Rename the current buffer and file it is visiting."
(interactive)
(let ((filename (buffer-file-name)))
(if (not (and filename (file-exists-p filename)))
(message "Buffer is not visiting a file!")
(let ((new-name (read-file-name "New name: " filename)))
(cond
((vc-backend filename) (vc-rename-file filename new-name))
(t
(rename-file filename new-name t)
(set-visited-file-name new-name t t)))))))
(defun sulami/open-scratch-buffer ()
"Opens the scratch buffer."
(interactive)
(switch-to-buffer "*scratch*"))
(defun sulami/open-message-buffer ()
"Opens the message buffer."
(interactive)
(switch-to-buffer "*Messages*"))
(defun sulami/open-minibuffer ()
"Focusses the minibuffer, if active."
(interactive)
(when (active-minibuffer-window)
(select-window (minibuffer-window))))
(defun sulami/buffer-line-count ()
"Get the number of lines in the active buffer."
(count-lines 1 (point-max)))
(defun sulami/delete-file-and-buffer ()
"Deletes a buffer and the file it's visiting."
(interactive)
(when-let* ((file-name (buffer-file-name))
(really (yes-or-no-p (format "Delete %s? "
file-name))))
(delete-file file-name)
(kill-buffer)))
(defun sulami/copy-buffer ()
"Copies the entire buffer to the kill-ring."
(interactive)
(copy-region-as-kill 1 (point-max)))
(defun sulami/open-source-dir ()
(interactive)
(find-file sulami/source-directory))
(defun sulami/toggle-term ()
"Opens global vterm, or switches to last buffer."
(interactive)
(let ((buf-name "*vterm*"))
(cond
((eq major-mode 'vterm-mode)
(evil-switch-to-windows-last-buffer))
((get-buffer buf-name)
(switch-to-buffer buf-name))
((vterm buf-name)))))
(defun sulami/toggle-maximise-window ()
"Toggles maximising the current window.
From: https://gist.github.com/mads-hartmann/3402786"
(interactive)
(if (and (= 1 (length (window-list)))
(assoc ?_ register-alist))
(jump-to-register ?_)
(progn
(window-configuration-to-register ?_)
(delete-other-windows))))
(defun sulami/shell-command-on-region (beg end cmd)
(interactive "r\nsCommand: ")
(shell-command-on-region beg end cmd t t))
(defun sulami/sort-words (beg end)
"Sorts words in region."
(interactive "r")
(sort-regexp-fields nil "\\w+" "\\&" beg end))
(defun sulami/kill-matching-lines (regexp &optional rstart rend interactive)
"Kill lines containing matches for REGEXP.
See `flush-lines' or `keep-lines' for behavior of this command.
If the buffer is read-only, Emacs will beep and refrain from deleting
the line, but put the line in the kill ring anyway. This means that
you can use this command to copy text from a read-only buffer.
\(If the variable `kill-read-only-ok' is non-nil, then this won't
even beep.)"
(interactive
(keep-lines-read-args "Kill lines containing match for regexp"))
(let ((buffer-file-name nil)) ;; HACK for `clone-buffer'
(with-current-buffer (clone-buffer nil nil)
(let ((inhibit-read-only t))
(keep-lines regexp rstart rend interactive)
(kill-region (or rstart (line-beginning-position))
(or rend (point-max))))
(kill-buffer)))
(unless (and buffer-read-only kill-read-only-ok)
;; Delete lines or make the "Buffer is read-only" error.
(flush-lines regexp rstart rend interactive)))
(defun sulami/toggle-narrow ()
"Toggles `narrow-to-defun' or `org-narrow-to-subtree'."
(interactive)
(if (buffer-narrowed-p)
(widen)
(if (eq major-mode 'org-mode)
(org-narrow-to-subtree)
(narrow-to-defun))))
This one is faster than linum-mode
.
(defun sulami/toggle-line-numbers ()
"Toggles buffer line number display."
(interactive)
(setq display-line-numbers (not display-line-numbers)))
This one is quite useful for debugging syntax highlighting. It’s adapted from here.
(defun sulami/what-face (pos)
(interactive "d")
(let ((face (or (get-char-property pos 'read-face-name)
(get-char-property pos 'face))))
(if face
(message "Face: %s" face)
(message "No face at %d" pos))))
I need random UUIDs all the time. This generates one and places it in the clipboard, ready for pasting. Heavily dependent on macOS.
(defun sulami/random-uuid ()
(interactive)
(let ((uuid (s-trim (shell-command-to-string "uuidgen | tr '[:upper:]' '[:lower:]'"))))
(kill-new uuid)
(message "Generated UUID: %s" uuid)))
This is useful to run Rspec tests.
(defun sulami/rspec-path ()
(interactive)
(let ((fp (f-relative buffer-file-name (projectile-project-root)))
(ln (1+ (line-number-at-pos))))
(-> (format "%s:%d" fp ln)
(message)
(kill-new))))
Fills the current paragraph. If used again, “unfills” it, for pasting in places that doesn’t like hard line breaks, such as GitHub. “Taken from here.
(defun sulami/fill-or-unfill ()
"Like `fill-paragraph', but unfill if used twice."
(interactive)
(let ((fill-column
(if (eq last-command #'sulami/fill-or-unfill)
(progn (setq this-command nil)
(point-max))
fill-column)))
(if (eq major-mode 'org-mode)
(call-interactively #' org-fill-paragraph)
(call-interactively #'fill-paragraph))))
General allows me to use fancy prefix keybindings.
I’m using a spacemacs-inspired system of a global leader key and a local leader
key for major modes. Bindings are setup in the respective use-package
declarations.
(use-package general
:config
(general-auto-unbind-keys)
(general-evil-setup)
(defconst leader-key "SPC")
(general-create-definer leader-def
:prefix leader-key
:keymaps 'override
:states '(normal visual))
(defconst local-leader-key ",")
(general-create-definer local-leader-def
:prefix local-leader-key
:keymaps 'override
:states '(normal visual))
(leader-def
"" '(nil :wk "my lieutenant general prefix")
;; Prefixes
"a" '(:ignore t :wk "app")
"b" '(:ignore t :wk "buffer")
"d" '(:ignore t :wk "dired")
"f" '(:ignore t :wk "file")
"f e" '(:ignore t :wk "emacs")
"g" '(:ignore t :wk "git")
"h" '(:ignore t :wk "help")
"j" '(:ignore t :wk "jump")
"k" '(:ignore t :wk "lisp")
"m" '(:ignore t :wk "mail")
"o" '(:ignore t :wk "org")
"p" '(:ignore t :wk "project/perspective")
"s" '(:ignore t :wk "search/spell")
"t" '(:ignore t :wk "toggle")
"w" '(:ignore t :wk "window")
;; General keybinds
"\\" 'indent-region
"|" 'sulami/shell-command-on-region
"a c" 'calc
"a s" 'shell
"b e" 'erase-buffer
"b d" 'kill-this-buffer
"b D" 'kill-buffer-and-window
"b m" 'sulami/open-message-buffer
"b ." 'sulami/open-minibuffer
"b r" 'sulami/rename-file-and-buffer
"b s" 'sulami/open-scratch-buffer
"b y" 'sulami/copy-buffer
"d d" #'dired
"d s" #'sulami/open-source-dir
"f f" 'find-file
"f e e" 'sulami/open-emacs-config
"f e r" 'sulami/reload-emacs-config
"f D" 'sulami/delete-file-and-buffer
"f R" 'sulami/rename-file-and-buffer
"h e" 'info-display-manual
"h g" 'general-describe-keybindings
"h l" 'view-lossage
"h m" 'woman
"h v" 'describe-variable
"t a" 'auto-fill-mode
"t l" 'toggle-truncate-lines
"t r" 'refill-mode
"t s" 'flyspell-mode
"t n" 'sulami/toggle-line-numbers
"t N" 'sulami/toggle-narrow
"t w" 'whitespace-mode
"w =" 'balance-windows
"w f" 'make-frame
"w m" 'sulami/toggle-maximise-window
"w u" 'winner-undo)
(general-define-key
"s-m" #'suspend-frame
"s-t" #'sulami/toggle-term
"s-u" #'universal-argument
"s-=" (lambda () (interactive) (text-scale-increase 0.5))
"s--" (lambda () (interactive) (text-scale-decrease 0.5))
"s-0" (lambda () (interactive) (text-scale-increase 0))
"M-q" #'sulami/fill-or-unfill)
(general-nmap "g r" #'xref-find-references)
;; Dired
(general-define-key
:keymaps 'dired-mode-map
"<return>" 'dired-find-alternate-file))
This provides vim-style modal editing. There is quite a bit of boilerplate to make it work with the various components, but I really can’t stand the default Emacs keybindings.
(use-package evil
:init
(setq evil-want-C-u-scroll t
evil-want-C-i-jump t
evil-want-Y-yank-to-eol t
evil-want-keybinding nil
evil-ex-visual-char-range t
evil-move-beyond-eol t
evil-disable-insert-state-bindings t)
:custom
(evil-undo-system 'undo-fu)
:config
;; This conflicts with the local leader
(unbind-key "," evil-motion-state-map)
(defun sulami/evil-shift-left-visual ()
"`evil-shift-left`, but keeps the selection."
(interactive)
(call-interactively 'evil-shift-left)
(evil-normal-state)
(evil-visual-restore))
(defun sulami/evil-shift-right-visual ()
"`evil-shift-right`, but keeps the selection."
(interactive)
(call-interactively 'evil-shift-right)
(evil-normal-state)
(evil-visual-restore))
:general
(leader-def
"TAB" #'evil-switch-to-windows-last-buffer
"<tab>" #'evil-switch-to-windows-last-buffer
"w d" #'evil-window-delete
"w h" #'evil-window-move-far-left
"w j" #'evil-window-move-very-bottom
"w k" #'evil-window-move-very-top
"w l" #'evil-window-move-far-right
"w /" #'evil-window-vsplit
"w -" #'evil-window-split)
(general-imap
"C-w" #'evil-delete-backward-word
"C-k" #'evil-insert-digraph)
(general-vmap
">" #'sulami/evil-shift-right-visual
"<" #'sulami/evil-shift-left-visual)
:hook (after-init . evil-mode))
This adds evil-keybindings for a lot of popular modes.
I have to disable some because they clash with my own.
(use-package evil-collection
:after (evil)
:config
(setq evil-collection-mode-list
(->> evil-collection-mode-list
(delete 'company)
(delete 'gnus)
(delete 'lispy)))
(evil-collection-init))
Evil-keybindings for org/agenda.
(use-package evil-org
:after (org)
:config
(require 'evil-org-agenda)
:hook ((org-mode . evil-org-mode)
(org-agenda-mode . evil-org-agenda-set-keys)))
vim-commentary
but for evil.
(use-package evil-commentary
:hook (evil-mode . evil-commentary-mode))
vim-surround
but for evil.
(use-package evil-surround
:hook (evil-mode . global-evil-surround-mode))
(use-package evil-numbers
:defer t
:general
(general-nvmap
"C-a" 'evil-numbers/inc-at-pt
"C-z" 'evil-numbers/dec-at-pt))
This just provides linear undo/redo. As an added bonus, it also does “undo in region”.
(use-package undo-fu
:defer t
:custom
(undo-fu-allow-undo-in-region t)
(undo-fu-ignore-keyboard-quit t))
This shows all available keybindings when I hit a key. Sometimes useful.
(use-package which-key
:hook (after-init . which-key-mode))
Vertico, the successor to Selectrum, is a great narrowing and selection tool, intended to replace Ivy & Helm.
(use-package vertico
:custom
(completion-in-region-function #'consult-completion-in-region)
:general
(leader-def
"b b" #'consult-buffer
"p b" #'consult-project-buffer)
:hook
(after-init . vertico-mode))
(use-package marginalia
:hook
(vertico-mode . marginalia-mode))
Consult is counsel for vertico, in that it adds vertico support
for various commands that do not use read-string
normally.
consult-flycheck
cannot be autoloaded via consult
, so it needs to
be loaded separately.
(use-package consult
:defer t
:straight
'(consult
:type git
:host github
:repo "minad/consult"
:branch "main")
:custom
(consult-preview-key nil)
(xref-show-xrefs-function #'consult-xref)
(xref-show-definitions-function #'consult-xref)
:config
;; Eshell only defines its locally keymap when you launch it, so we
;; have to add bindings with a hook.
(defun sulami/consult-setup-eshell-bindings ()
(general-imap
:keymaps 'eshell-mode-map
"C-r" 'consult-history))
:general
(leader-def
"f r" 'consult-recent-file
"j i" 'consult-imenu
"j I" 'consult-project-imenu
"j o" 'consult-outline
"s s" 'consult-line)
(general-imap
"M-y" 'consult-yank-pop)
(general-nmap
"M-y" 'consult-yank-pop)
(general-imap
:keymaps 'shell-mode-map
"C-r" 'consult-history)
:hook
((eshell-mode . sulami/consult-setup-eshell-bindings)))
(use-package consult-flycheck
:general
(leader-def
"j e" 'consult-flycheck))
Orderless provides a minibuffer completion style that is “whitespace separated words in any order.” Very useful if you don’t know exactly what you’re looking for.
(use-package orderless
:defer t
:custom
(completion-styles '(orderless basic)))
(use-package flyspell-correct
:defer t
:general
(leader-def
"s c" 'flyspell-correct-at-point))
Corfu does completion via a dropdown that automatically pops up while typing. I can select a match if I want to, but ignore the dropdown if I don’t.
(use-package corfu
:custom
(corfu-auto t)
(corfu-quit-no-match 'separator)
:hook (after-init . global-corfu-mode))
Snippets. I have a few custom ones. yasnippet-snippets
is a huge
bundle of useful snippets for all kinds of modes.
(use-package yasnippet
:general
(general-imap
"C-y" 'yas-insert-snippet)
(:keymaps 'yas-minor-mode-map
"<tab>" nil
"TAB" nil
"<ret>" nil
"RET" nil)
:config
(yas-reload-all)
:hook
((text-mode . yas-minor-mode)
(prog-mode . yas-minor-mode)))
(use-package yasnippet-snippets
:defer t
:after (yasnippet))
Popper deals with all those temporary popup windows.
(use-package popper
:after (projectile)
:init
(setq popper-reference-buffers
'("\\*Messages\\*"
"\\*Async Shell Command\\*"
help-mode
helpful-mode
backtrace-mode
compilation-mode
vterm-mode
rustic-compilation-mode
rustic-cargo-run-mode
rustic-cargo-test-mode
rustic-rustfmt-mode
rspec-compilation-mode))
(setq popper-display-control t
popper-display-function #'display-buffer-use-least-recent-window)
(popper-mode +1)
(popper-echo-mode +1)
:custom
(popper-group-function #'popper-group-by-projectile)
:general
("s-p" #'popper-toggle-latest
"s-g" #'popper-cycle))
Keeps my parentheses balanced.
I use LispyVille for all Lisp major modes, as it does some additional magic around spacing, comments, and more.
N.B. This currently pulls in a lot of dependencies for no good reason, including swiper & hydra.
(use-package lispyville
:defer t
:custom
(lispy-close-quotes-at-end-p t)
:config
(lispyville-set-key-theme '(operators
c-w
additional-motions
commentary
slurp/barf-lispy
additional-wrap))
:general
(general-imap
:keymaps 'lispyville-mode-map
"(" 'lispy-parens
"[" 'lispy-brackets
"{" 'lispy-braces
"\"" 'lispy-quotes
")" 'lispy-right-nostring
"]" 'lispy-right-nostring
"}" 'lispy-right-nostring
"DEL" 'lispy-delete-backward-or-splice-or-slurp)
:hook
((emacs-lisp-mode . lispyville-mode)
(lisp-mode . lispyville-mode)
(scheme-mode . lispyville-mode)
(clojure-mode . lispyville-mode)
(cider-repl-mode . lispyville-mode)
(monroe-mode . lispyville-mode)
(racket-mode . lispyville-mode)))
All other modes just use electric-pair-mode
, which is built into
Emacs already, for automatically matching parentheses. The main reason
for this divide being the whitespace changes done by LispyVille
interfering with non-lisp syntax.
(use-package electric-pair-mode
:straight nil
:hook
((text-mode . electric-pair-local-mode)
(prog-mode . electric-pair-local-mode)))
(use-package code-folding
:straight nil
:config
(defun sulami/close-defun-fold ()
"Hide the top-level definition at point."
(interactive)
(beginning-of-defun)
(hs-hide-block))
:general
(general-nmap
"z C" #'sulami/close-defun-fold)
:hook
(prog-mode . hs-minor-mode))
(use-package exec-path-from-shell
:init
(exec-path-from-shell-initialize))
Uses rg
to jump to definition. Zero setup. Not always correct, but
usually good enough. Much less of a hassle than LSP.
(use-package dumb-jump
:after (evil)
:custom
(dumb-jump-prefer-searcher 'rg)
(dumb-jump-selector 'completing-read)
:config
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))
(use-package deadgrep
:defer t)
This allows running rgrep
and then writing to the result buffer,
modifying the files matched in place. Quite useful for sweeping
changes.
(use-package wgrep
:defer t
:commands (wgrep-change-to-wgrep-mode)
:config
(setq wgrep-auto-save-buffer t)
:general
(local-leader-def
:keymaps 'grep-mode-map
"w" 'wgrep-change-to-wgrep-mode))
Highlights certain keywords in comments, like TODO
and FIXME
.
(use-package hl-todo
:defer t
:config
(add-to-list 'hl-todo-keyword-faces
`("N\.?B\.?" . ,(cdr (assoc "NOTE" hl-todo-keyword-faces))))
:hook (after-init . global-hl-todo-mode))
I only enable this every now and then.
(use-package auto-highlight-symbol
:custom
(ahs-idle-interval 0.1)
:general
(leader-def "t h" 'auto-highlight-symbol-mode))
Manages projects (usually git repositories, but flexible).
(use-package projectile
:custom
(projectile-switch-project-action 'projectile-find-file)
(projectile-completion-system 'default)
:config
(defun sulami/projectile-rg ()
(interactive)
(let ((vertico-count 30))
(consult-ripgrep (projectile-project-root))))
(defun sulami/projectile-rg-thing-at-point ()
(interactive)
(let ((vertico-count 30))
(consult-ripgrep (projectile-project-root)
(thing-at-point 'symbol t))))
(defun sulami/toggle-project-root-eshell ()
"Opens eshell, if possible in the project root."
(interactive)
(cond
((eq major-mode 'eshell-mode)
(evil-switch-to-windows-last-buffer))
((projectile-project-p (f-dirname (or (buffer-file-name)
"")))
(call-interactively #'projectile-run-eshell))
((eshell))))
(defun sulami/toggle-project-root-shell ()
"Opens shell, if possible in the project root."
(interactive)
(cond
((eq major-mode 'shell-mode)
(evil-switch-to-windows-last-buffer))
((projectile-project-p (f-dirname (or (buffer-file-name)
"")))
(call-interactively #'projectile-run-shell))
((shell))))
(defun sulami/toggle-project-root-term ()
"Opens vterm, if possible in the project root."
(interactive)
(cond
((eq major-mode 'vterm-mode)
(evil-switch-to-windows-last-buffer))
((projectile-project-p (f-dirname (or (buffer-file-name)
"")))
(let ((default-directory (projectile-project-root))
(buf-name (concat "*vterm " (projectile-project-name) "*")))
(if (get-buffer buf-name)
(switch-to-buffer buf-name)
(vterm buf-name))))
((vterm))))
;; Don't do projectile stuff on remote files
;; from https://github.com/syl20bnr/spacemacs/issues/11381#issuecomment-481239700
(defadvice projectile-project-root (around ignore-remote first activate)
(unless (file-remote-p default-directory) ad-do-it))
:general
(leader-def
"d p" #'projectile-dired
"p c" #'projectile-compile-project
"p p" #'projectile-switch-project
"p f" #'projectile-find-file
"p k" #'projectile-kill-buffers
"p s" #'sulami/toggle-project-root-shell
"p t" #'sulami/toggle-project-root-term
"s p" #'sulami/projectile-rg
"s P" #'sulami/projectile-rg-thing-at-point)
("s-'" #'sulami/toggle-project-root-eshell)
:hook (after-init . projectile-global-mode))
Number windows and allow me to switch to them.
(use-package winum
:general
("s-1" 'winum-select-window-1
"s-2" 'winum-select-window-2
"s-3" 'winum-select-window-3
"s-4" 'winum-select-window-4
"s-5" 'winum-select-window-5
"s-6" 'winum-select-window-6
"s-7" 'winum-select-window-7
"s-8" 'winum-select-window-8
"s-9" 'winum-select-window-9)
(leader-def
"w 1" 'winum-select-window-1
"w 2" 'winum-select-window-2
"w 3" 'winum-select-window-3
"w 4" 'winum-select-window-4
"w 5" 'winum-select-window-5
"w 6" 'winum-select-window-6
"w 7" 'winum-select-window-7
"w 8" 'winum-select-window-8
"w 9" 'winum-select-window-9)
:hook (after-init . winum-mode))
(use-package org
:custom
(calendar-date-style 'iso "YYYY-MM-DD")
(org-directory "~/Documents/Notes")
(calendar-week-start-day 1 "Weeks start on Monday")
(org-complete-tags-always-offer-all-agenda-tags t "Autocomplete tags")
(org-edit-src-content-indentation 0 "Do not indent src blocks")
(org-export-initial-scope 'subtree "Default export to the current subtree")
(org-export-with-toc nil "Omit the TOC when exporting")
(org-export-with-author nil "Omit the author when exporting")
(org-footnote-auto-adjust t "Automatically renumber footnotes")
(org-footnote-section nil "Footnotes go into the section they are referenced in")
(org-hide-emphasis-markers t "Hide emphasis markers")
(org-log-into-drawer t "Log workflow changes into a drawer")
(org-src-preserve-indentation nil "Remove leading whitespace when editing src blocks")
(org-src-window-setup 'current-window "Show src editing in the current window")
(org-use-fast-todo-selection t "Use fast selection for workflow states")
(org-preview-latex-image-directory "~/.cache/org-latex/" "Store latex previews in cache")
:config
;; Open file links in same window.
(setf (cdr (assoc 'file org-link-frame-setup)) 'find-file)
(defun sulami/org-return ()
"`org-return' with better plain list handling.
If inside a plain list, insert a new list item. If the current list
item is empty, remove it instead. Essentially imitating Google Docs."
(interactive)
(if (org-at-item-p)
(let* ((begin (org-in-item-p))
(struct (org-list-struct))
(end (org-list-get-item-end begin struct))
(indent (org-list-get-ind begin struct))
(bullet (org-list-get-bullet begin struct))
(checkbox (org-list-get-checkbox begin struct))
(type (org-list-get-list-type begin
struct
(org-list-prevs-alist struct)))
;; Different factors alter the amount of whitespace.
(whitespace (+ 1
(if (= end (point-max)) -1 0)
(if (= end (point-max) (1+ (point)))
1 0)
(if checkbox 1 0)
(if (equal 'descriptive type) 3 0)
(if (and checkbox
(equal 'descriptive type))
1 0))))
(if (zerop (- end
begin
indent
(length bullet)
(length checkbox)
whitespace))
(progn (org-list-delete-item begin struct)
(insert "\n")
(backward-char))
(evil-org-open-below 0)))
(org-return)))
:config/el-patch
(defun org-link-escape (link)
"Backslash-escape sensitive characters in string LINK."
(el-patch-wrap 3 0
(replace-regexp-in-string
" "
"%20"
(replace-regexp-in-string
(rx (seq (group (zero-or-more "\\")) (group (or string-end (any "[]")))))
(lambda (m)
(concat (match-string 1 m)
(match-string 1 m)
(and (/= (match-beginning 2) (match-end 2)) "\\")))
link nil t 1))))
:general
(local-leader-def
:keymaps 'org-mode-map
:states '(normal)
"a" 'org-archive-subtree
"d" 'org-deadline
"e" '(org-export-dispatch :wk "org-export-dispatch")
"f" 'org-fill-paragraph
"l" 'org-insert-link
"L" 'org-store-link
"R" 'sulami/org-refile-in-current-file
"s" 'org-schedule
"S" 'org-babel-switch-to-session
"T" 'org-babel-tangle
"w" 'org-todo
"W" '((lambda ()
(interactive)
(org-todo '(4)))
:wk "org-todo (with note)"))
;; Fix plain lists.
(general-imap
:keymaps 'org-mode-map
"RET" 'sulami/org-return)
:hook
((org-mode . auto-fill-mode)
(org-mode . flyspell-mode)
(org-mode . org-indent-mode)))
(setq org-todo-keywords '((sequence "TODO(t)"
"WIP(p)"
"WAITING(w)"
"|"
"DONE(d)"
"CANCELLED(c)")))
(add-hook 'org-trigger-hook 'save-buffer)
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)
(python . t)))
(add-to-list 'org-babel-default-header-args '(:results . "replace drawer"))
(add-hook 'org-mode-hook
(lambda ()
(auto-composition-mode -1)))
- archive into one shared file
- auto-save
(setq org-archive-location "~/Documents/Notes/archive.org::"
org-archive-subtree-add-inherited-tags t)
My capture templates. Also reload buffers before attempting to capture to avoid overwriting iCloud changes.
(setq org-capture-templates
'(("b" "Blog idea" entry
(file "blog.org")
"* %^{title}\n%u\n%?"
:prepend t)
("f" "File link" entry
(file "inbox.org")
"* %^{title}\n%a\n%?")
("n" "Note" entry
(file "inbox.org")
"* %^{title}\n%u\n%?")
("t" "Thought" entry
(file "thoughts.org")
"* %^{title}\n%u\n%?")))
This gives me org-mode->github flavoured markdown export.
(use-package ox-gfm
:defer 3
:after org)
This is probably the single best interface for Git out there.
(use-package magit
:custom
(magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)
:config
(defun sulami/clone-repo (url)
"Clone the repo at URL into `sulami/source-directory'"
(interactive "sURL: ")
(let* ((repo-name (magit-clone--url-to-name url))
(target-dir (concat sulami/source-directory "/" repo-name)))
(magit-clone-regular url target-dir nil)))
(defun sulami/reset-repo ()
"Reset the current repo to its default state"
(interactive)
(magit-checkout (magit-main-branch))
(magit-pull-from-upstream nil))
:general
(leader-def
"g b" #'magit-blame-addition
"g h" #'magit-log
"g n" #'sulami/reset-repo
"g s" #'magit-status)
:hook
((shell-mode . with-editor-export-editor)
(term-mode . with-editor-export-editor)
(eshell-mode . with-editor-export-editor)
(git-commit-setup . git-commit-turn-on-flyspell)))
This package gets me the link to a git repository or line in a file.
(use-package git-link
:init
(defun open-git-link-in-browser ()
(interactive)
(let ((git-link-open-in-browser t))
(git-link "origin" (line-number-at-pos) (line-number-at-pos))))
(defun open-git-repo-in-browser ()
(interactive)
(let ((git-link-open-in-browser t))
(git-link-homepage "origin")))
:general
(leader-def
"g l" 'git-link
"g L" 'open-git-link-in-browser
"g r" 'git-link-homepage
"g R" 'open-git-repo-in-browser))
This is a convenience function to generate a changelog for a PR.
(defun sulami/magit-changelog ()
"Generate a git changelog and copy it to the kill ring"
(interactive)
(kill-new
(format "Best reviewed commit-by-commit:\n\n%s"
(shell-command-to-string
(format "git log %s..HEAD --reverse --pretty=format:'%s'"
(magit-main-branch)
"### %h %s%n%n%b")))))
Flycheck does automatic linting. I enable it mostly manually, as I don’t have a lot of linters setup.
(use-package flycheck
:after (nix-sandbox)
:config
;; Disable flycheck on-the-fly-checking if the line count exceeds 2000.
(setq flycheck-check-syntax-automatically
(if (> (sulami/buffer-line-count) 2000)
(delete 'idle-change flycheck-check-syntax-automatically)
(add-to-list 'flycheck-check-syntax-automatically 'idle-change))
;; Resolve checker commands in Nix environments.
flycheck-command-wrapper-function
(lambda (command) (apply 'nix-shell-command (nix-current-sandbox) command))
flycheck-executable-find
(lambda (cmd) (nix-executable-find (nix-current-sandbox) cmd)))
:custom
(flycheck-emacs-lisp-load-path 'inherit)
:general
(leader-def "t c" 'flycheck-mode)
:hook
((clojure-mode . flycheck-mode)
(rust-mode . flycheck-mode)
(go-mode . flycheck-mode)))
Just some bindings for interacting with Emacs Lisp.
(local-leader-def
:keymaps 'emacs-lisp-mode-map
"e" '(:ignore t :wk "eval")
"e b" 'eval-buffer
"e e" 'eval-last-sexp
"e f" 'eval-defun
"e r" 'eval-region)
Eshell is my main shell these days, mostly because it integrates so well with Emacs. On rare occasions I use a terminal emulator (usually also inside Emacs) with zsh.
(setq eshell-prompt-function
(lambda ()
(concat
(when (not (eshell-exit-success-p))
(concat
"<"
(propertize (number-to-string eshell-last-command-status)
'face `(:foreground "red"))
"> "))
(abbreviate-file-name (eshell/pwd))
" λ "))
eshell-prompt-regexp
(rx (opt "<" (1+ digit) "> ")
(1+ anything)
" λ "))
This just loads my aliases. They are auto-generated from my zsh aliases, so that the two are always in sync.
(setq eshell-aliases-file "~/.emacs.d/aliases")
Eshell doesn’t do context-aware autocompletion by default and defaults to completing filenames instead. Luckily we can easily define custom completion handlers for commands.
This swaps the terrible popup buffer that eshell opens when I hit
TAB
for a read-string
completion.
The binding has to happen here in a hook because eshell-mode-map
isn’t available before eshell is started.
(add-hook
'eshell-mode-hook
(lambda ()
(setq completion-at-point-functions '(comint-completion-at-point t))
(define-key eshell-mode-map (kbd "TAB") 'completion-at-point)
(define-key eshell-mode-map (kbd "<tab>") 'completion-at-point)))
(defun pcomplete/sudo ()
"Completion rules for the `sudo' command."
(let ((pcomplete-ignore-case t))
(pcomplete-here (funcall pcomplete-command-completion-function))
(while (pcomplete-here (pcomplete-entries)))))
I like having timestamps in my shell history, so that I can use the history for logs later on.
(defun sulami/eshell-insert-timestamp ()
(when eshell-mode
(save-excursion
(goto-char eshell-last-input-start)
(let ((ts (format-time-string "[%FT%TZ] " nil t)))
(insert ts)
(put-text-property eshell-last-input-start
(+ eshell-last-input-start
(length ts))
'face
'eshell-prompt)))))
(add-hook 'eshell-pre-command-hook #'sulami/eshell-insert-timestamp)
Just diffing. I don’t have any strong opinions on it, ediff does the job. I’m sure there are cool features I’m not aware of.
(setq ediff-diff-options "-w")
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
(setq ediff-split-window-function 'split-window-horizontally)
Stolen from Stig.
(defun sulami/ediff-copy-both-to-C ()
(interactive)
(ediff-copy-diff ediff-current-difference nil 'C nil
(concat
(ediff-get-region-contents ediff-current-difference 'A ediff-control-buffer)
(ediff-get-region-contents
ediff-current-difference 'B
ediff-control-buffer))))
(defun sulami/add-d-to-ediff-mode-map ()
(define-key ediff-mode-map "d" #'sulami/ediff-copy-both-to-C))
(add-hook 'ediff-keymap-setup-hook #'sulami/add-d-to-ediff-mode-map)
This brings some nice quality of life extensions to dired.
(use-package dired+
:custom
(diredp-hide-details-initially-flag nil))
This causes RET
to open the target in the current buffer, instead of
a new buffer. As a result, when traversing directories I don’t get one
buffer per step, which quickly gets annoying.
At some point I need to rebind ^
as well.
(put 'dired-find-alternate-file 'disabled nil)
(add-hook 'dired-mode-hook 'auto-revert-mode)
(setq-default dired-listing-switches "-alh")
(setq dired-recursive-copies 'always)
man
and woman
are manual page readers for emacs. man
just calls
the system man
and shows the results in a buffer, while woman
is a
full man page parser in Emacs Lisp. woman
is much faster, but does
not support all formats of man pages. To solve this issue, I’m
patching woman
to fall back to man
if it fails to render, which is
still fast because it doesn’t require man
to build a man page index,
as we just pass it the correct file already.
(use-package man
:straight nil
:after (woman)
:defer t
:commands (woman)
:config/el-patch
(defun woman (&optional topic re-cache)
"Browse UN*X man page for TOPIC (Without using external Man program).
The major browsing mode used is essentially the standard Man mode.
Choose the filename for the man page using completion, based on the
topic selected from the directories specified in `woman-manpath' and
`woman-path'. The directory expansions and topics are cached for
speed. With a prefix argument, force the caches to be
updated (e.g. to re-interpret the current directory).
Used non-interactively, arguments are optional: if given then TOPIC
should be a topic string and non-nil RE-CACHE forces re-caching."
(interactive (list nil current-prefix-arg))
;; The following test is for non-interactive calls via gnudoit etc.
(if (or (not (stringp topic)) (string-match-p "\\S " topic))
(let ((file-name (woman-file-name topic re-cache)))
(if file-name
(el-patch-swap
(woman-find-file file-name)
(condition-case nil
(woman-find-file file-name)
(error (progn
(message "WoMan failed to format %s, falling back to `man'..." file-name)
(kill-buffer (alist-get file-name woman-buffer-alist))
(pop-to-buffer (man file-name))))))
(message
"WoMan Error: No matching manual files found in search path")
(ding)))
(message "WoMan Error: No topic specified in non-interactive call")
(ding))))
Helpful provides better *Help*
buffers, with niceties such as “where
is this symbol referenced”, and the full source code.
(use-package helpful
:commands (helpful-symbol helpful-key)
:config/el-patch
(defun helpful-symbol (symbol)
"Show help for SYMBOL, a variable, function or macro.
See also `helpful-callable' and `helpful-variable'."
(interactive
(list (helpful--read-symbol
"Symbol: "
(el-patch-wrap 2 1
(condition-case nil
(helpful--symbol-at-point)
(error nil)))
#'helpful--bound-p)))
(let ((c-var-sym (helpful--convert-c-name symbol t))
(c-fn-sym (helpful--convert-c-name symbol nil)))
(cond
((and (boundp symbol) (fboundp symbol))
(if (y-or-n-p
(format "%s is a both a variable and a callable, show variable?"
symbol))
(helpful-variable symbol)
(helpful-callable symbol)))
((fboundp symbol)
(helpful-callable symbol))
((boundp symbol)
(helpful-variable symbol))
((and c-fn-sym (fboundp c-fn-sym))
(helpful-callable c-fn-sym))
((and c-var-sym (boundp c-var-sym))
(helpful-variable c-var-sym))
(t
(user-error "Not bound: %S" symbol)))))
:general
(leader-def
"h d" 'helpful-symbol
"h f" 'helpful-function
"h k" 'helpful-key))
This allows me to benchmark Emacs startup.
(use-package esup
:defer t
:commands (esup))
IRC. I don’t use IRC a lot, but every now and then. As such I like ERC because it works reasonably well out of the box, and I don’t need to yak shave an awful lot.
(use-package erc
:straight nil
:defer t
:commands (erc sulami/erc)
:custom
(erc-nick "sulami")
(erc-join-buffer 'bury)
(erc-hide-list '("JOIN" "PART" "QUIT"))
(erc-lurker-hide-list '("JOIN" "PART" "QUIT"))
(erc-rename-buffers t)
(erc-interpret-mirc-color t)
(erc-timestamp-only-if-changed-flag nil)
(erc-timestamp-format "%H:%M ")
(erc-fill-prefix nil)
(erc-fill-function 'erc-fill-variable)
(erc-insert-timestamp-function 'erc-insert-timestamp-left)
(erc-autojoin-channels-alist '(("irc.libera.chat" "#nixers_net")
("irc.circleci.com" "#general")))
:config
(add-to-list 'erc-modules 'keep-place)
(add-to-list 'erc-modules 'spelling)
(defun sulami/erc ()
(interactive)
(erc :server "irc.libera.chat"
:port 6667
:nick "sulami"
:password (string-trim (shell-command-to-string "pass libera/password")))
(erc :server "irc.circleci.com"
:port 6667
:nick "robins"
:password (string-trim (shell-command-to-string "pass circleci/irc/password"))))
:general
(leader-def
"a i" 'erc-track-switch-buffer))
This is my email setup. I fetch email into a local maildir, which is available offline, and also much faster.
(setq message-directory "~/.mail"
message-kill-buffer-on-exit t
message-send-mail-function 'message-send-mail-with-sendmail
message-sendmail-envelope-from 'header
sendmail-program "msmtp"
mail-specify-envelope-from t
mail-envelope-from 'header)
This is my own package, which does live inline calculations. Quite handy, if you ask me.
(use-package literate-calc-mode
:defer t)
Use the builtin bug-reference-mode
to automatically convert JIRA
ticket references into clickable links. Adapted from monotux.
(use-package bug-reference
:straight nil
:config
(defun sulami/bug-reference-setup ()
(setq bug-reference-bug-regexp (rx (group
(group
(or "CIRCLE"
"BACKPLANE"
"SRE"
"SERVER"
"INFRA"
"PIPE"
"ORCH"
"PER"
"API"
"TASKS")
"-"
(repeat 3 5 digit))))
bug-reference-url-format "https://circleci.atlassian.net/browse/%s"))
:hook
((bug-reference-mode . sulami/bug-reference-setup)
(org-mode . bug-reference-mode)
(clojure-mode . bug-reference-mode)))
vterm is a terminal emulator which is way faster than anything built into Emacs. It copes well with huge amounts of output and weird escape sequences. Prior to installation run:
brew install cmake libtool
(use-package vterm
:defer t
:commands (vterm)
:general
(leader-def
"a t" 'vterm))
LSP (Language Server Protocol) is a system which enables static analysis of code and provides various IDE-like features.
(defun sulami/eldoc-documentation-adaptive ()
"An `eldoc-documentation-functions' function which uses the
-default variant by default to keep the minibuffer small by only
showing signatures, but uses -enthusiast if the *eldoc* buffer is
open."
(if (eldoc--echo-area-prefer-doc-buffer-p t)
(eldoc-documentation-enthusiast)
(eldoc-documentation-default)))
(use-package eglot
:custom
(eldoc-echo-area-display-truncation-message nil)
(eldoc-echo-area-prefer-doc-buffer t)
(eldoc-documentation-strategy sulami/eldoc-documentation-adaptive)
:general
(local-leader-def 'eglot-mode-map
"l" '(:ignore t :wk "lsp")
"l r" #'eglot-rename))
Tree-sitter is a library for parsing source code, which can be used to generate faster and better syntax highlighting than the traditional regexp-based approach.
(use-package tree-sitter
:hook
((after-init . global-tree-sitter-mode)
(tree-sitter-after-on . tree-sitter-hl-mode)))
(use-package tree-sitter-langs)
Clojure is the language I work with at $DAYJOB
, so it has quite a
lot of configuration.
(use-package clojure-mode
:defer t
:general
(local-leader-def
:keymaps 'clojure-mode-map
"R" '(:ignore t :wk "refactor")
"R a" 'clojure-align
"R l" 'clojure-move-to-let
"R t" 'clojure-thread-first-all
"R T" 'clojure-thread-last-all
"R u" 'clojure-unwind-all)
:config
(define-clojure-indent
(database/speculate 1)
(car/wcar 1)))
(use-package flycheck-clj-kondo
:defer t
:hook (clojure-mode . (lambda () (require 'flycheck-clj-kondo))))
The big IDE-like integration for Clojure.
(use-package cider
:defer t
:config
(defun sulami/cider-debug-defun-at-point ()
"Set an implicit breakpoint and load the function at point."
(interactive)
(let ((current-prefix-arg '(4)))
(call-interactively 'cider-eval-defun-at-point)))
:custom
(cider-show-error-buffer nil)
(cider-repl-display-help-banner nil)
(cider-redirect-server-output-to-repl nil)
:general
(local-leader-def
:keymaps 'cider-mode-map
"c" 'cider-connect
"j" 'cider-jack-in
"q" 'cider-quit
"s" 'cider-scratch
"x" 'cider-ns-reload-all
"e" '(:ignore t :wk "eval")
"e b" 'cider-eval-buffer
"e d" 'sulami/cider-debug-defun-at-point
"e e" 'cider-eval-last-sexp
"e f" 'cider-eval-defun-at-point
"e r" 'cider-eval-region
"h" '(:ignore t :wk "help")
"h a" 'cider-apropos
"h A" 'cider-apropos-documentation
"h d" 'cider-doc
"h i" 'cider-inspect-last-result
"h w" 'cider-clojuredocs
"h W" 'cider-clojuredocs-web
"r" '(:ignore t :wk "repl")
"r f" 'cider-insert-defun-in-repl
"r n" 'cider-repl-set-ns
"r r" 'cider-switch-to-repl-buffer
"t" '(:ignore t :wk "test")
"t b" 'cider-test-show-report
"t f" 'cider-test-rerun-failed-tests
"t l" 'cider-test-run-loaded-tests
"t n" 'cider-test-run-ns-tests
"t p" 'cider-test-run-project-tests
"t t" 'cider-test-run-test)
:hook
((clojure-mode . cider-mode)))
HugSQL is a Clojure ORM which adds some special syntax to SQL.
This declaration is only here for config encapsulation, it doesn’t actually install a package.
In this case we install a fix to make imenu work in HugSQL files.
(use-package hugsql
:straight nil
:defer t
:init
(defun sulami/init-hugsql-imenu ()
(when (string-suffix-p ".hug.sql" (buffer-file-name))
(setq
imenu-generic-expression
'((nil "^--[[:space:]]:name[[:space:]]+\\([[:alnum:]-]+\\)" 1)))))
:hook
(sql-mode . sulami/init-hugsql-imenu))
One of my favourite recreational languages. I use sly
instead of
slime
because it’s supposedly better. I don’t have much of an
opinion, it works.
(use-package sly
:defer t
:commands (sly)
:custom
(inferior-lisp-program "sbcl")
:general
(local-leader-def
:keymaps 'sly-mode-map
"s" 'sly
"q" 'sly-quit-lisp
"e" '(:ignore t :wk "eval")
"e b" 'sly-eval-buffer
"e e" 'sly-eval-last-expression
"e f" 'sly-eval-defun
"e r" 'sly-eval-region
"h" '(:ignore t :wk "help")
"h a" 'sly-apropos
"h d" 'sly-documentation
"h i" 'sly-inspect))
I use Racket for quite a bit of scripting, due to its rich standard
library. I don’t need any fancy configuration though, the major mode
comes with reasonable defaults. This does not have the usual eval
bindings, because I usually don’t use Racket REPLs.
(use-package racket-mode
:defer t)
I don’t actually really use Rust beyond experiments, so not much config here.
(use-package rustic
:defer t
:after (rust-mode flycheck)
:init
(add-to-list 'flycheck-checkers 'rustic-clippy)
:config
(setq rustic-format-on-save t)
(setq rustic-lsp-client 'eglot)
(setq rustic-rustfmt-args "--edition 2021")
:general
(local-leader-def
:keymaps 'rustic-mode-map
"c" #'rustic-compile
"r" #'rustic-cargo-run
"t" #'rustic-cargo-test-dwim
"T" #'rustic-cargo-run-nextest))
(use-package flycheck-rust
:defer t
:after (flycheck)
:hook
(flycheck-mode . flycheck-rust-setup))
Every now and then I do have to look at some Go code, sadly.
(use-package go-mode
:defer t
:custom
(gofmt-show-errors 'echo)
:hook
(before-save . gofmt-before-save))
(use-package web-mode
:defer t
:custom
(js-indent-level 2)
(web-mode-code-indent-offset 2)
(web-mode-markup-indent-offset 2)
(web-mode-script-padding 0)
(web-mode-style-padding 0)
:mode
(("\\.tsx" . web-mode)
("\\.vue" . web-mode)))
(use-package typescript-mode
:defer t
:custom
(typescript-indent-level 2))
(use-package tide
:defer t
:config
(defun sulami/tide-setup ()
(when (equal "tsx"
(file-name-extension buffer-file-name))
(tide-setup)
(flycheck-mode +1)
(eldoc-mode +1)))
:hook
((typescript-mode . sulami/tide-setup)
(web-mode . sulami/tide-setup)))
(use-package prettier-js
:defer t
:after (nix-sandbox)
:config
(defun sulami/prettier-js-setup ()
(setq prettier-js-command
(nix-executable-find (nix-current-sandbox)
"prettier"))
(prettier-js-mode +1))
:hook
((typescript-mode . sulami/prettier-js-setup)
(web-mode . sulami/prettier-js-setup)))
The primary language at $DAYJOB
. rspec-mode
adds imenu
support
for RSpec.
(use-package rspec-mode
:general
(local-leader-def
:keymaps 'rspec-mode-map
"t" #'rspec-verify-single
"T" #'rspec-verify-all))
(use-package csv-mode
:defer t)
I’m using the builtin SQL-mode, but I want it set to highlight for PostgreSQL specifically.
(use-package sql
:straight nil
:custom
(sql-product 'postgres))
(use-package dockerfile-mode
:defer t)
(use-package terraform-mode
:defer t)
I think it’s vastly inferior to org-mode, but I still have to use it. Mostly just try to make it look & work like org-mode.
(use-package markdown-mode
:defer t
:custom
(markdown-fontify-code-blocks-natively t)
:hook
((markdown-mode . orgtbl-mode)
(markdown-mode . flyspell-mode))
:general
(local-leader-def
:keymaps 'markdown-mode-map
"l" 'markdown-insert-link
"m" 'markdown-toggle-markup-hiding)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)))
(use-package edit-indirect
:defer t)
Sometimes I’m a YAML-engineer, too.
(use-package yaml-mode
:defer t
:config
(defun sulami/yaml-setup ()
(setq evil-shift-width 2))
:hook
(yaml-mode . sulami/yaml-setup))
There are two distinct parts here:
nix-mode
- Provides support for editing Nix expressions
nix-sandbox
- Used to make other commands Nix-aware
(use-package nix-mode
:defer t)
(use-package nix-sandbox
:disabled)
We use quite a lot of protobuffers at $DAYJOB
, so the mode is
useful.
(use-package protobuf-mode
:defer t
:init
(defun sulami/init-protobuf-imenu ()
"Sets up imenu support for Protobuf.
Stolen from Spacemacs."
(setq
imenu-generic-expression
'((nil "^[[:space:]]*\\(message\\|service\\|enum\\)[[:space:]]+\\([[:alnum:]]+\\)" 2))))
:hook
(protobuf-mode . sulami/init-protobuf-imenu))