31 KiB
Emacs Configuration
- Initialization
- Package management
- Common
- Sensible defaults
- A good starting point: `better-defaults`
- Load custom.el
- Revert buffers when files on disk change
- Remove trailing whitespace on save
- Kill the current buffer without confirmation
- Enable some commands that are disabled by default
- Unify the way Emacs is asking for confirmation
- recentf
- Prevent suspending
- Don't save duplicates in kill-ring
- Navigation and editing
- Project management
- Org
- Magit
- UI
- Programming
- Language support
- Misc
- Performance shenanigans
- Playground
- Meta
Initialization
Lexical scope
;;; init.el --- user-init-file -*- lexical-binding: t -*-
;;;
;;; DO NOT EDIT THIS FILE!
;;; This file was automatically generated by tangling `init.org`.
Package management
I use Nix with the emacs overlay to build Emacs including packages directly
from use-package
declarations in this file. The packages are already added to
load-path
, so we can disable Emacs' own package manager entirely.
Disable Emacs' own package manager
;;; early-init.el --- user-init-file -*- lexical-binding: t -*-
;;;
;;; DO NOT EDIT THIS FILE!
;;; This file was automatically generated by tangling `init.org`.
(setq package-archives nil
package-enable-at-startup nil)
Common
These packages and functions are used by various other subsections.
Keep ~/.emacs.d/ clean
no-littering
needs to be loaded as early as possible, see
https://github.com/emacscollective/no-littering#usage for details.
(use-package no-littering
:ensure t)
(when (fboundp 'startup-redirect-eln-cache)
(startup-redirect-eln-cache
(convert-standard-filename
(expand-file-name "var/eln-cache/" user-emacs-directory))))
diminish
(use-package diminish
:ensure t)
hydra
(use-package hydra
:ensure t)
Run commands in an external terminal
(defun dkellner/term-command (cmd &rest args)
(let* ((cmd-with-args (s-join " " (cons cmd args)))
(name (format "alacritty: %s" cmd-with-args)))
(apply #'start-process
name
(format "*%s*" name)
"alacritty"
"--title"
cmd-with-args
"--command"
cmd
args)))
Run functions in dedicated frames
We set the title to "emacs-floating", so we can distinguish them from regular emacs frames in our sway configuration.
(defun dkellner/run-in-dedicated-frame (fn)
(let ((frame (make-frame '((title . "emacs-floating")))))
(select-frame frame)
(funcall fn)))
(defun dkellner/run-in-minibuffer-frame (fn)
(let ((frame (make-frame '((minibuffer . only)
(title . "emacs-floating")))))
(select-frame frame)
(unwind-protect
(funcall fn)
(delete-frame frame))))
(defun dkellner/dedicated-frame-p ()
(equal "emacs-floating" (frame-parameter nil 'title)))
Sensible defaults
A good starting point: `better-defaults`
From https://git.sr.ht/~technomancy/better-defaults: "[…] this package attempts to address the most obvious of deficiencies in uncontroversial ways that nearly everyone can agree upon."
(use-package better-defaults
:ensure t
:config
(ido-mode -1)) ; I prefer vertico
Load custom.el
(when (file-exists-p custom-file)
(load custom-file))
Revert buffers when files on disk change
(setq global-auto-revert-non-file-buffers t)
(global-auto-revert-mode 1)
Remove trailing whitespace on save
(add-hook 'before-save-hook #'delete-trailing-whitespace)
Kill the current buffer without confirmation
(bind-key "C-x k" #'kill-current-buffer)
Enable some commands that are disabled by default
(put 'upcase-region 'disabled nil)
(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'narrow-to-defun 'disabled nil)
Unify the way Emacs is asking for confirmation
(setq use-short-answers t)
recentf
Auto-cleanup of recently used files is disabled, because it causes freezes when
remote files are not accessible anymore. Call recentf-cleanup
for manual
cleanup.
(use-package recentf
:demand t
:config
(setq recentf-max-saved-items 250
recentf-auto-cleanup 'never)
(add-to-list 'recentf-exclude no-littering-etc-directory)
(add-to-list 'recentf-exclude no-littering-var-directory)
(add-to-list 'recentf-exclude "^/\\(?:ssh\\|su\\|sudo\\)?:")
(recentf-mode 1))
Prevent suspending
(global-unset-key (kbd "C-z"))
(global-unset-key (kbd "C-x C-z"))
Don't save duplicates in kill-ring
(setq kill-do-not-save-duplicates t)
Navigation and editing
Boon: modal editing
(use-package boon
:ensure t
:demand t
:diminish boon-local-mode
:config
(setq boon-insert-conditions
'((eq major-mode 'message-mode)
(eq major-mode 'eshell-mode)))
;; Core commands
(define-key boon-command-map "x" 'boon-x-map)
(define-key boon-command-map "c" 'boon-c-god)
(define-key boon-command-map "g" '("goto" . boon-goto-map))
;; Switch to insert mode
(define-key boon-command-map "n" '("insert" . boon-set-insert-like-state))
(define-key boon-command-map "N" 'boon-substitute-region)
(define-key boon-command-map "r" '("open" . boon-open-next-line-and-insert))
(define-key boon-command-map "R" 'boon-open-line-and-insert)
;; Other commands
(define-key boon-command-map "z" 'boon-repeat-command)
(define-key boon-command-map "t" 'boon-replace-by-character)
(define-key boon-command-map "T" 'boon-enclose)
(define-key boon-command-map "\"" 'boon-quote-character)
(define-key boon-command-map "/" 'occur)
(define-key boon-command-map "d" 'boon-take-region)
(define-key boon-command-map "D" 'boon-treasure-region)
(define-key boon-command-map "y" '("yank" . boon-splice))
(define-key boon-command-map "Y" 'yank-pop)
(define-key boon-command-map "q" '("quit" . quit-window))
(define-key boon-command-map "\\" 'indent-region)
(define-key boon-command-map "^" 'join-line)
(define-key boon-command-map "." 'dkellner/eglot-actions/body)
;; Neo2-like movement
(define-key boon-moves-map "l" '("previous" . previous-line))
(define-key boon-moves-map "L" 'boon-smarter-upward)
(define-key boon-moves-map "a" '("next" . next-line))
(define-key boon-moves-map "A" 'boon-smarter-downward)
(define-key boon-moves-map "i" '("backward" . backward-char))
(define-key boon-moves-map "I" 'boon-smarter-backward)
(define-key boon-moves-map "e" '("forward" . forward-char))
(define-key boon-moves-map "E" 'boon-smarter-forward)
(define-key boon-moves-map "u" 'boon-beginning-of-line)
(define-key boon-moves-map "o" 'boon-end-of-line)
;; Other movements
(define-key boon-moves-map "s" 'boon-forward-search-map)
(define-key boon-moves-map "S" 'boon-backward-search-map)
(define-key boon-moves-map "(" 'boon-beginning-of-expression)
(define-key boon-moves-map ")" 'boon-end-of-expression)
(define-key boon-moves-map "{" 'backward-paragraph)
(define-key boon-moves-map "}" 'forward-paragraph)
(define-key boon-moves-map "<" 'beginning-of-buffer)
(define-key boon-moves-map ">" 'end-of-buffer)
(define-key boon-moves-map "@" 'boon-switch-mark)
;; Selections
(define-key boon-select-map "w" 'boon-select-word)
(define-key boon-select-map "h" 'boon-select-paragraph)
(define-key boon-select-map "H" 'boon-select-document)
(define-key boon-select-map "s" 'boon-select-wim)
(define-key boon-select-map "S" 'boon-select-sentence)
(define-key boon-select-map "r" 'boon-select-justline)
(define-key boon-select-map "g" 'boon-select-block)
(define-key boon-select-map "q" 'boon-select-outside-quotes)
(define-key boon-select-map "x" 'boon-select-outside-pairs)
(define-key boon-select-map "c" 'boon-select-inside-pairs)
(define-key boon-select-map "C" 'boon-select-comment)
(define-key boon-select-map "V" 'boon-select-blanks)
(define-key boon-select-map "v" 'boon-select-with-spaces)
(define-key boon-select-map "z" 'boon-select-content)
(define-key boon-select-map "t" 'boon-select-borders)
(define-key boon-select-map "T" 'boon-select-org-tree)
(define-key boon-select-map "G" 'boon-select-org-table-cell)
;; Extensions of the goto map
(define-key boon-goto-map "w" 'avy-goto-subword-1)
(define-key boon-forward-search-map "m" 'dkellner/flymake-goto-next-error)
(define-key boon-backward-search-map "m" 'dkellner/flymake-goto-prev-error)
(boon-mode))
Avy
(use-package avy
:ensure t
:bind (("M-g g" . avy-goto-line)
("M-g M-g" . avy-goto-line)))
yasnippet
(use-package yasnippet
:ensure t
:demand t
:config
(yas-global-mode)
:diminish yas-minor-mode)
(use-package yasnippet-snippets
:ensure t)
(Auto-)Filling
(setq-default fill-column 79)
Vertico and friends
(use-package vertico
:ensure t
:config
(setq vertico-cycle t)
(vertico-mode))
(use-package marginalia
:ensure t
:config
(marginalia-mode))
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
Consult
(use-package consult
:ensure t
:init
(require 'consult-imenu)
:bind (("C-x b" . consult-buffer)
("C-x p b" . consult-project-buffer)
("M-y" . consult-yank-pop)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
("M-g o" . consult-outline)
("M-g f" . consult-flymake)
("M-s g" . consult-ripgrep)))
Completion
(use-package emacs
:bind ("C-." . completion-at-point))
(use-package cape
:ensure t
:config
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev))
(use-package corfu
:ensure t
:custom
(corfu-cycle t)
:config
;; Silence the pcomplete capf, no errors or messages!
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
;; Ensure that pcomplete does not write to the buffer
;; and behaves as a pure `completion-at-point-function'.
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify)
(global-corfu-mode 1))
multiple-cursors
(use-package multiple-cursors
:ensure t
:bind (("C-<" . mc/mark-all-like-this)
("C->" . mc/mark-next-like-this)))
unfill
(use-package unfill
:ensure t
:bind ("M-q" . unfill-toggle))
Project management
project.el
(use-package project
:config
(setq project-switch-commands
'((project-find-file "Find file")
(project-find-regexp "Find regexp")
(project-find-dir "Find directory")
(project-eshell "Eshell")
(magit-project-status "Magit" ?m))))
direnv integration
(use-package envrc
:ensure t
:diminish
:config
(envrc-global-mode 1))
Org
Use current version of org
and org-contrib
(use-package org
:ensure t)
(use-package org-contrib
:ensure t)
Basic configuration
(setq org-directory "~/org/"
org-agenda-files '("~/org/main.org" "~/org/tickler.org" "~/org/areas/")
org-refile-use-outline-path 'file
org-outline-path-complete-in-steps nil
org-refile-targets '((nil . (:maxlevel . 2))
("~/org/inbox.org" . (:level . 0))
("~/org/cookbook.org" . (:level . 0))
("~/org/pap.org" . (:maxlevel . 1))
(org-agenda-files . (:maxlevel . 2))
("~/org/calendars/personal.org" . (:level . 0))
("~/org/calendars/puzzleandplay.org" . (:level . 0))
("~/org/bookmarks.org" . (:maxlevel . 1)))
org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w)" "|" "DONE(d)" "CANCELLED(c)")))
;; This list contains tags I want to use in almost any file as they are tied to
;; actionable items (e.g. GTD contexts).
(setq org-tag-alist `((:startgroup)
("@laptop" . ,(string-to-char "l"))
("@phone" . ,(string-to-char "p"))
("@home" . ,(string-to-char "h"))
("@errands" . ,(string-to-char "e"))
(:endgroup)))
(setq org-startup-folded 'content
org-log-done 'time
org-log-into-drawer t
org-agenda-todo-ignore-scheduled 'all
org-agenda-todo-ignore-deadlines 'all
org-agenda-tags-todo-honor-ignore-options t
org-agenda-window-setup 'current-window
org-agenda-restore-windows-after-quit nil
org-time-clocksum-format "%d:%02d"
org-duration-format 'h:mm
org-enforce-todo-dependencies t
org-columns-default-format "%40ITEM(Task) %3Priority(Pr.) %16Effort(Estimated Effort){:} %CLOCKSUM{:}"
org-export-with-sub-superscripts nil
org-export-allow-bind-keywords t
org-default-priority ?C
org-insert-heading-respect-content t)
Capturing
Templates
(setq org-capture-templates
'(("i" "Inbox" entry (file "~/org/inbox.org")
"* %?\nCreated: %U")
("I" "Inbox (with link)" entry (file "~/org/inbox.org")
"* %?\n%a\nCreated: %U")))
(use-package ol-notmuch
:ensure t)
Use the same window
(use-package org-capture
:config
(defun dkellner/org-pop-to-buffer (&rest args)
"Use `pop-to-buffer' instead of `switch-to-buffer' to open buffer.'"
(let ((buf (car args)))
(switch-to-buffer
(cond ((stringp buf) (get-buffer-create buf))
((bufferp buf) buf)
(t (error "Invalid buffer %s" buf))))))
(advice-add #'org-switch-to-buffer-other-window
:override #'dkellner/org-pop-to-buffer)
(defun dkellner/org-capture-place-template (oldfun &rest args)
"Don't delete other windows in `org-capture-place-template'."
(cl-letf (((symbol-function #'delete-other-windows) #'ignore))
(apply oldfun args)))
(advice-add #'org-capture-place-template
:around #'dkellner/org-capture-place-template))
Delete dedicated frame after capture
See https://www.reddit.com/r/orgmode/comments/uycc8m/comment/ia422x6/ .
(defun dkellner/delete-frame-after-org-capture (&optional oldfun &rest args)
(when (and (dkellner/dedicated-frame-p)
(not (eq this-command #'org-capture-refile)))
(delete-frame)))
(advice-add #'org-capture-finalize
:after #'dkellner/delete-frame-after-org-capture)
(advice-add #'org-capture-refile
:after #'dkellner/delete-frame-after-org-capture)
Capture buffers should start in insert state
(use-package org
:after boon
:hook (org-capture-mode . boon-set-insert-like-state))
Agenda
Customizing the agenda view
(setq org-agenda-use-time-grid nil
org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-custom-commands
'(("h" "Home"
((agenda "" ((org-agenda-span 'day)))
(todo "TODO"
((org-agenda-sorting-strategy
'(priority-down tag-up))))))
("w" "Work"
((agenda "" ((org-agenda-span 'day)))
(todo "TODO"
((org-agenda-sorting-strategy
'(priority-down tag-up)))))
((org-agenda-files
(append org-agenda-files '("~/org/pap.org" "~/org/calendars/puzzleandplay.org")))
(org-super-agenda-groups
(append org-super-agenda-groups '((:name "@work" :tag "@work"))))))))
(use-package org-super-agenda
:ensure t
:config
(setq org-super-agenda-groups
'((:name "@laptop"
:tag "@laptop")
(:name "@phone"
:tag "@phone")
(:name "@home"
:tag "@home")
(:name "@errands"
:tag "@errands")))
(org-super-agenda-mode 1))
Habits
(require 'org-habit)
Keybindings
(bind-key "C-c a" #'org-agenda)
(bind-key "C-c c" #'org-capture)
(bind-key "C-c l" #'org-store-link)
Literate Programming
(setq org-src-tab-acts-natively t
org-edit-src-content-indentation 0
org-confirm-babel-evaluate nil)
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(shell . t)
(python . t)))
Expand snippets like "<s"
(require 'org-tempo)
Prettification
(setq
org-ellipsis " ⤵"
org-agenda-block-separator 9472)
(use-package org-bullets
:ensure t
:hook (org-mode . org-bullets-mode)
:config
(setq org-bullets-bullet-list '("◉" "❃" "✿" "✤")))
Use org-mode for *scratch*
(setq initial-major-mode 'org-mode
initial-scratch-message nil)
Visual indentation instead of actual spaces
(use-package org-indent
:hook (org-mode . org-indent-mode)
:diminish)
Save org-mode buffers after refiling
(use-package org
:config
(advice-add #'org-refile :after #'org-save-all-org-buffers))
org-ql
(use-package org-ql
:ensure t)
(defun dkellner/list-tasks-done-today ()
(interactive)
(let* ((today-str (format-time-string "%Y-%m-%d"))
(pattern (concat "- State \"DONE\" *from \"TODO\" *\\[" today-str)))
(org-ql-search (org-agenda-files)
`(or (closed :on today)
(regexp ,pattern)))))
org-auto-tangle
(use-package org-auto-tangle
:ensure t
:hook (org-mode . org-auto-tangle-mode)
:diminish)
Magit
(use-package magit
:ensure t
:config
(setq magit-display-buffer-function
#'magit-display-buffer-same-window-except-diff-v1
magit-section-initial-visibility-alist '((stashes . hide)
(unpushed . show))))
(use-package notmuch
:ensure t
:config
(setq mail-host-address (system-name)
sendmail-program "msmtp"
message-kill-buffer-on-exit t
message-send-mail-function 'message-send-mail-with-sendmail
message-sendmail-extra-arguments '("--read-envelope-from")
message-sendmail-f-is-evil t
notmuch-fcc-dirs '(("dominik.kellner@puzzleyou.de"
. "puzzleandplay/.sent")
(".*" . "dkellner/.sent"))))
UI
Themes
Everybody's got one: their favorite theme. In my case I've always configured at least a dark and a light one, and I switch between them based on lighting conditions (e.g. when I'm working outside I'm likely to use the light theme).
This is another area where going "all-in" Emacs really shines: Switching your theme will conveniently affect all of your computing.
(setq custom--inhibit-theme-enable nil)
(use-package color-theme-sanityinc-tomorrow
:ensure t
:config
(defun dkellner/load-light-theme ()
(interactive)
(disable-theme 'sanityinc-tomorrow-night)
(load-theme 'sanityinc-tomorrow-day t)
(custom-theme-set-faces
'sanityinc-tomorrow-night
'(fringe ((t (:background unspecified))))
'(org-block ((t (:background unspecified))))))
(defun dkellner/load-dark-theme ()
(interactive)
(disable-theme 'sanityinc-tomorrow-day)
(load-theme 'sanityinc-tomorrow-night t)
(custom-theme-set-faces
'sanityinc-tomorrow-night
'(fringe ((t (:background unspecified))))
'(org-block ((t (:background unspecified))))))
(defun dkellner/toggle-theme ()
(interactive)
(if (-contains? custom-enabled-themes 'sanityinc-tomorrow-night)
(dkellner/load-light-theme)
(dkellner/load-dark-theme)))
(dkellner/load-dark-theme))
Font
(add-to-list 'default-frame-alist '(font . "Meslo LG M 12"))
Mode-line
(use-package all-the-icons
:ensure t)
(column-number-mode 1)
(setq mode-line-position
'((line-number-mode ("%l" (column-number-mode ":%c"))))
eol-mnemonic-unix nil)
(setq-default mode-line-format
'("%e"
mode-line-front-space
(:eval (when current-input-method-title
(format "%s " current-input-method-title)))
mode-line-client
(:eval
(let* ((props (-concat `(:height ,(/ all-the-icons-scale-factor 1.6)
:v-adjust 0)
(cond
(buffer-read-only '(:face (:foreground "gray85")))
((buffer-modified-p) '(:face (:foreground "red"))))))
(icon (apply #'all-the-icons-icon-for-mode
(-concat (list major-mode) props))))
(if (not (eq icon major-mode)) icon
(apply #'all-the-icons-icon-for-mode 'text-mode props))))
" "
mode-line-buffer-identification
" "
mode-line-position
" "
mode-line-modes
mode-line-misc-info
mode-line-end-spaces))
Remove distractions
When you're using unclutter
or similar to hide the mouse pointer, then setting
mouse-highlight
to nil
is a must. Without, e.g. the agenda buffer will still
keep highlighting the line the now invisible pointer is on.
(diminish 'auto-revert-mode)
(setq mouse-highlight nil
ring-bell-function 'ignore)
Fringe and internal borders
(add-to-list 'default-frame-alist '(internal-border-width . 7))
(use-package fringe
:config
(fringe-mode '(7 . 1)))
Scrolling
(setq auto-window-vscroll nil
fast-but-imprecise-scrolling t
scroll-conservatively 101
scroll-margin 3
scroll-preserve-screen-position t)
Customize startup
(setq inhibit-startup-screen t
inhibit-startup-echo-area-message t
inhibit-startup-message t)
which-key
(use-package which-key
:ensure t
:diminish
:config
(which-key-mode))
Programming
Common
(use-package eldoc
:diminish
:config
(setq eldoc-echo-area-use-multiline-p nil))
Flymake
(use-package flymake-diagnostic-at-point
:ensure t
:after flymake
:hook (flymake-mode . flymake-diagnostic-at-point-mode))
(defun dkellner/flymake-goto-next-error ()
"Call `flymake-goto-next-error' non-interactively to suppress
printing the error."
(interactive)
(flymake-goto-next-error))
(defun dkellner/flymake-goto-prev-error ()
"Call `flymake-goto-prev-error' non-interactively to suppress
printing the error."
(interactive)
(flymake-goto-prev-error))
LSP (eglot)
(use-package eglot
:after yasnippet
:config
(add-to-list 'eglot-server-programs '(rust-ts-mode "rust-analyzer"))
(defhydra dkellner/eglot-actions (:exit t)
"Eglot"
("a" #'eglot-code-actions "code actions")
("f" #'eglot-format "format")
("r" #'eglot-rename "rename")))
(use-package consult-eglot
:ensure t)
Language support
Docker
(use-package dockerfile-mode
:ensure t)
Justfile
(use-package just-mode
:ensure t)
Lisp
(use-package rainbow-delimiters
:ensure t
:hook ((emacs-lisp-mode . rainbow-delimiters-mode)))
Emacs Lisp
(use-package macrostep
:ensure t
:bind (:map emacs-lisp-mode-map
("C-c e" . macrostep-expand)))
;; Make the use of sharp-quote more convenient.
;; See http://endlessparentheses.com/get-in-the-habit-of-using-sharp-quote.html
(defun endless/sharp ()
"Insert #' unless in a string or comment."
(interactive)
(call-interactively #'self-insert-command)
(let ((ppss (syntax-ppss)))
(unless (or (elt ppss 3)
(elt ppss 4)
(eq (char-after) ?'))
(insert "'"))))
(bind-key "#" #'endless/sharp emacs-lisp-mode-map)
Go
(use-package go-mode
:ensure t)
Markdown
(use-package markdown-mode
:ensure t)
Nix
(use-package nix-mode
:ensure t
:mode ("\\.nix\\'" . nix-mode))
PHP, HTML
(use-package web-mode
:ensure t
:config
(add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
(setq-default web-mode-markup-indent-offset 2)
(setq-default web-mode-css-indent-offset 2)
(setq-default web-mode-code-indent-offset 2))
Rust
(use-package rust-ts-mode
:hook (rust-ts-mode . eglot-ensure))
YAML
(use-package yaml-mode
:ensure t)
(use-package highlight-indentation
:ensure t
:hook (yaml-mode . highlight-indentation-current-column-mode)
:diminish highlight-indentation-current-column-mode)
Misc
Shutdown and reboot
Simply running shutdown -h now
in a terminal will cause Emacs to not shutdown
properly. For example, the list of recently used files will not be persisted.
dkellner/prepare-kill-and-run
solves this by placing the actual shutdown
command at the end of kill-emacs-hook
. This way it is executed just before
Emacs would exit normally.
(defhydra dkellner/shutdown-or-reboot (:exit t)
"Shutdown/reboot/exit?"
("s" #'dkellner/shutdown "shutdown")
("r" #'dkellner/reboot "reboot")
("x" #'dkellner/exit-sway "exit sway"))
(defun dkellner/shutdown ()
"Kills emacs properly and shutdown."
(interactive)
(dkellner/prepare-kill-and-run "shutdown -h now"))
(defun dkellner/reboot ()
"Kill emacs properly and reboot."
(interactive)
(dkellner/prepare-kill-and-run "shutdown -r now"))
(defun dkellner/exit-sway ()
"Kill emacs properly and exit sway."
(interactive)
(dkellner/prepare-kill-and-run "swaymsg exit"))
(defun dkellner/prepare-kill-and-run (command)
"Prepare to kill Emacs properly and execute COMMAND.
This allows us to shutting down or rebooting the whole system and still
saving recently used files, bookmarks, places etc."
(when (org-clock-is-active)
(org-clock-out))
(let ((kill-emacs-hook (append (remove #'server-force-stop kill-emacs-hook)
(list (lambda () (shell-command command))))))
(save-buffers-kill-emacs)))
Helpful
(use-package helpful
:ensure t
:config
(global-set-key (kbd "C-h f") #'helpful-callable)
(global-set-key (kbd "C-h F") #'helpful-function)
(global-set-key (kbd "C-h v") #'helpful-variable)
(global-set-key (kbd "C-h k") #'helpful-key)
(global-set-key (kbd "C-h C") #'helpful-command))
pdf-tools
(use-package pdf-tools
:ensure t
:config
(require 'pdf-occur)
(pdf-tools-install-noverify))
diff-hl
(use-package diff-hl
:ensure t
:hook (((prog-mode conf-mode) . turn-on-diff-hl-mode)
(magit-post-refresh . diff-hl-magit-post-refresh))
:config
(setq diff-hl-draw-borders t))
Dired
(use-package dired
:config
(require 'dired-x)
(setq dired-listing-switches "-ahl"
dired-omit-files "^\\.")
(add-hook 'dired-mode-hook
(lambda () (dired-omit-mode))))
consult-ssh
(defun dkellner/open-ssh-term ()
"Run `ssh` for a hosts configured in ~/.ssh/config.
INITIAL-INPUT can be given as the initial minibuffer input."
(interactive)
(let ((host (completing-read "ssh " (pcmpl-ssh-hosts))))
(dkellner/term-command "ssh" host)))
Make shebang (#!) file executable when saved
(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)
Better completions for Eshell
(use-package pcmpl-args
:ensure t)
Performance shenanigans
Startup
Inhibit implied frame resizing
(setq frame-inhibit-implied-resize t)
Better support for files with long lines
(setq-default bidi-paragraph-direction 'left-to-right
bidi-inhibit-bpa t)
(global-so-long-mode 1)
GC-Tuning
(setq gc-cons-threshold (* 16 1024 1024))
Read bigger chunks from external processes
(setq read-process-output-max (* 1024 1024))
Playground
Often I get quite excited about all the great new packages out there and try them out immediately. Sometimes only to find myself forgetting about these new additions to my config and then they go unnoticed until I stumple upon them again months later.
This section is there to prevent it: I'm adding new packages, snippets etc. here for the purpose of reevaluating their usefulness after some time. If I don't use it as often as I thought I would, I just discard it again. Otherwise, I will move the entire section to a better place.
vlf
(use-package vlf
:ensure t)
hledger-mode
(use-package hledger-mode
:ensure t
:demand t
:mode ("\\.journal\\'" "\\.hledger\\'")
:hook (hledger-mode . (lambda () (setq-local tab-width 4)))
:config
(setq hledger-currency-string "EUR"))
org-roam
(use-package org-roam
:ensure t
:hook (after-init . org-roam-setup)
:diminish
:init (setq org-roam-v2-ack t)
:config
(setq org-roam-directory "~/org/roam"
emacsql-sqlite3-executable (executable-find "sqlite3")
org-roam-capture-templates
'(("d" "default" plain "%?" :if-new
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t
:immediate-finish t)))
(defhydra dkellner/org-roam (:exit t)
"org-roam"
("f" #'org-roam-node-find "find")
("i" #'org-roam-node-insert "insert")
("b" #'org-roam-buffer-toggle "backlinks"))
(bind-key* "C-c r" #'dkellner/org-roam/body))
org-tree-slide
(use-package org-tree-slide
:ensure t)
Meta
Private configuration
(load "~/.emacs.d/private.el")