Emacs Configuration
Introduction
This is my Emacs configuration. It is available online in these forms:
- Org mode source file in a Git repository.
- HTML article on my personal web page.
Inspirations for this Emacs configuration include:
- Harry R. Schwartz' Emacs configuration and sensible-defaults.el.
- Phil Hagelberg's better-defaults.el.
- EmacsCast.
- Protesilaos Stavrou's blog and videos.
Some interesting properties of this configuration:
- Almost all of the configuration is kept in an Org file.
- The startup time is quite fast. There are some tweaks in init.el and early-init.el that makes helps with this, together with prudent use of autoloading, and in general trying to keep the number of add-on packages low.
- Add-on packages are managed manually as Git submodules.
The configuration is organized so that basic configuration of Emacs comes first, then built-in packages that are bundles with Emacs, followed by the add-on packages that are kept in ~/.emacs.d/site-lisp/.
Here is my home page on EmacsWiki.
Set personal information
(customize-set-variable 'user-full-name "Björn Lindström")
(customize-set-variable 'user-mail-address "bkhl@elektrubadur.se")
Built-in functionality
Configuration of built-in Emacs behaviour and packages bundled with Emacs.
Start-up
use-package
The use-package
macro makes it easier to configure add-on packages without generally having to remember how to set up autoloading &c.
(require 'use-package)
This section contains extensions to this macro to enable using packages from Git submodules without involving a package manager.
Provide default :load-path
pointing to location of submodules
This makes the :load-package
keyword in use-package
calls default to a directory under /.emacs.d/site-lisp/
named the same as the package to be imported, if such a directory exists. For packages where for some reason the package name doesn't match the directory name, or that has its Lisp files in a subdirectory, an explicit :load-path
needs to be added.
(setf (alist-get :load-path use-package-defaults)
'((lambda (name args)
(let ((path (expand-file-name (concat user-emacs-directory
"site-lisp/"
(symbol-name name)))))
(when (file-exists-p path)
(list path))))
t))
Add :make-autoloads
keyword extension to generate autoload files
Some packages rely on package.el
(or equivalent) to generate and register an autoloads file. This custom keyword will do that for packages included without a package manager.
This is the function that actually generates the autoload file (if needed) and loads it.
Since make-directory-autoloads
has no logic to work out if the file needs to be regenerated, this function will skip calling it the file exists already, and is newer than all the other .el
files in the directory.
If a package has additional files in subdirectories and such, this function would not handle it, but then neither would package-generate-autoloads
, as far as I can tell, so this should work for any packages supported by package.el
.
(defun my/use-package-autoload-package (name package-directory)
"Set up autoloading for package NAME in directory PACKAGE-DIRECTORY."
(let* ((name (symbol-name name))
(auto-file (expand-file-name (format "%s/%s-autoloads.el"
package-directory
name))))
(when (or (not (file-exists-p auto-file))
(let* ((autoloads-attributes
(file-attributes auto-file))
(autoloads-age
(file-attribute-modification-time
autoloads-attributes))
(autoloads-inode-number
(file-attribute-inode-number autoloads-attributes)))
(seq-find (lambda (attributes)
(time-less-p autoloads-age
(file-attribute-modification-time
attributes)))
(mapcar #'cdr
(directory-files-and-attributes
package-directory
nil
(rx ".el" eos))))))
(make-directory-autoloads package-directory auto-file))
(load auto-file package-directory)))
Register the custom keyword. This is added first in the list, so that it will have access to the :load-path
parameter, and so that it will load before :defer
or other keywords that might cause this to run after the package is loaded.
(add-to-list 'use-package-keywords :make-autoloads)
This makes the keyword take boolean parameters similar to other keywords like :defer
.
(defalias 'use-package-normalize/:make-autoloads
'use-package-normalize-predicate)
The handler function is what injects the call to the function to generate the autoloads file when the use-package
macro is expanded.
(defun use-package-handler/:make-autoloads (name _keyword arg rest state)
(use-package-concat
(mapcar #'(lambda (path)
`(my/use-package-autoload-package ',name ,path))
(plist-get rest :load-path))
(use-package-process-keywords name rest state)))
Keep customizations in separate file
This makes the Emacs customization interface store values in a separate file, instead of in init.el
.
(customize-set-variable 'custom-file (concat user-emacs-directory "custom.el"))
(load custom-file :noerror)
Show init time on startup
(advice-add 'display-startup-echo-area-message
:after
(defun my/display-startup-echo-area-message ()
(message "Emacs init time: %s" (emacs-init-time))))
Disable garbage collection when in minibuffer
Disable GC while minibuffer is open, and enabled again when it is closed. This helps prevent hanging while working in the minibuffer.
(add-hook 'minibuffer-setup-hook
(defun my/disable-gc ()
(setq gc-cons-threshold most-positive-fixnum)))
(add-hook 'minibuffer-exit-hook
(defun my/default-gc ()
(setq gc-cons-threshold my/default-gc-cons-threshold)))
Interface
Show line and column number in mode line
(line-number-mode)
(column-number-mode)
Emoji font
Enable Emoji font if available. ☃
This is run as a hook after the first graphical frame is created, as this will otherwise not work when Emacs is started in daemon mode, or by emacsclient
.
(defun my/set-fontset-fonts (frame)
(when (display-graphic-p frame)
(dolist (font-spec '((#x2600 . #x26ff)
emoji))
(set-fontset-font t font-spec
"Noto Color Emoji"))
(remove-hook 'after-make-frame-functions
'my/set-fontset-fonts)))
(add-hook 'after-make-frame-functions #'my/set-fontset-fonts)
(my/set-fontset-fonts (selected-frame))
Disable bell
Disable warning bell, both the default audio one and the visual one.
(customize-set-variable 'ring-bell-function 'ignore)
Set window title
Set window title including current buffer or filename, along with system name. Use a straight or squiggly line to show if the buffer has modifications.
(setq frame-title-format
'(
"%b"
(:eval (if (buffer-modified-p) " ⁓ " " — "))
(:eval (system-name))))
Enable restoring exact window size
Setting this variable allows resizing window by pixels, rather than rounding to an exact number of lines or columns. This is needed to be able to restore back from fullscreen to original frame size in Gnome.
(customize-set-variable 'frame-resize-pixelwise t)
Disable message on new emacsclient
frames
(customize-set-variable 'server-client-instructions nil)
Prompt before closing Emacs
(customize-set-variable 'confirm-kill-emacs 'y-or-n-p)
Set preferred dateformat
(calendar-set-date-style 'iso)
Allow undo of window layout changes
(winner-mode)
Preserve M-x
command history between sessions
(savehist-mode)
Use saved point position in previously opened files
(save-place-mode)
Scrolling behaviour when moving cursor
When the cursor moves close to the edge of the screen, scroll only one line at time, but try to keep 5 rows within view.
(customize-set-variable 'scroll-conservatively 101)
(customize-set-variable 'scroll-margin 5)
Smooth scrolling with scroll wheel
(pixel-scroll-precision-mode)
Highlight error messages
In next-error
buffers, highligt the currently visited error.
(customize-set-variable 'next-error-message-highlight t)
Make yes/no prompts shorter
(customize-set-variable 'use-short-answers t)
Don't show bookmarks in fringe
(customize-set-variable 'bookmark-set-fringe-mark nil)
Use bar cursor
(customize-set-variable 'cursor-type 'bar)
Show matching parenthesis context when offscreen
(customize-set-variable 'show-paren-context-when-offscreen 'overlay)
Switch windows with M-o
Bind M-o
(by default bound to a rarely used command) to other-window
.
(global-set-key (kbd "M-o") #'other-window)
Switch between windows with S-<direction>
(windmove-default-keybindings)
Key bindings
Disable C-z
Disabling C-z
, which normally minimizes the window, which is rather distracting.
(keymap-global-unset "C-z")
Enable repeat maps for commands that have them
This adds ability to repat some common commands by repeating the last key in its binding.
(repeat-mode)
Mouse behaviour
Make middle-clicking mouse yank at point
(customize-set-variable 'mouse-yank-at-point t)
Save to kill ring when adjusting region with mouse
Setting this to non-empty
means this won't happen for empty strings, like when accidentally dragging for less than a character's width.
(customize-set-variable 'mouse-drag-copy-region 'non-empty)
Documentation and help
Make apropos
search more extensively
(customize-set-variable 'apropos-do-all t)
Use variable pitch in Info reader
(add-hook 'Info-mode-hook 'variable-pitch-mode)
Autoload if documentation is missing from autoload objects
(customize-set-variable 'help-enable-symbol-autoload t)
Show outlines in bindings description
(customize-set-variable 'describe-bindings-outline t)
Reuse help window if already shown
(customize-set-variable 'help-window-keep-selected t)
Buffers
Start with an empty scratch buffer.
(customize-set-variable 'inhibit-startup-screen t)
(customize-set-variable 'initial-scratch-message nil)
Use directory name in buffer names for files with same name
(customize-set-variable 'uniquify-buffer-name-style 'forward)
Allow remembering risky local variables
This overrides the Emacs settings that enforces having to accept local variables matching certain patterns every time they are used.
(advice-add 'risky-local-variable-p :override #'ignore)
Load .dir-locals.el
files on remote hosts
(customize-set-variable 'enable-remote-dir-locals t)
Key bindings to kill/bury current buffer
Change the default keybinding for killing a buffer, C-x k
, so that it kills the current buffer rather than prompting for a buffer. Instead C-x K
is used for the previous default.
Also binds C-x M-k
to bury the current buffer, a command that's not bound to any key by default.
(defun my/kill-this-buffer ()
"Kill current buffer, prompting if there are unsaved changes."
(interactive)
(kill-buffer (current-buffer)))
(global-set-key (kbd "C-x k") #'my/kill-this-buffer)
(global-set-key (kbd "C-x K") #'kill-buffer)
(global-set-key (kbd "C-x M-k") #'bury-buffer)
Files
Start opening files from home directory
Unless overridden by a buffer, when prompting to open a file, start in the home directory.
(setq default-directory "~/")
Backup by copying
The default method here can break hardlinks.
(customize-set-variable 'backup-by-copying t)
Store backups in tmp directory
Store backups and autosaves in temporary-file-directory
. This risks losing some data on a system crash, but I am not very concerned about that as generally my important files are in some kind of version control.
(customize-set-variable 'backup-directory-alist
`((".*" . ,temporary-file-directory)))
(customize-set-variable 'auto-save-file-name-transforms
`((".*" ,temporary-file-directory t)))
Offer to create parent directories on save
When saving a file to a directory that doesn't exist, offer to create it.
(add-hook
'before-save-hook
(defun my/ask-create-directory ()
(when buffer-file-name
(let ((dir (file-name-directory buffer-file-name)))
(when
(and
(not (file-exists-p dir))
(y-or-n-p
(format
"Directory %s does not exist. Create it?"
dir)))
(make-directory dir t))))))
Disable message when saving files
(customize-set-variable 'save-silently t)
Automatically sync updated files
If a file changes, automatically refresh buffers containing the file, so that it doesn't get out of sync.
(global-auto-revert-mode t)
Disable Emacs lock files
Disable use of those lock files with a .#
prefix that Emacs by default creates. Since my ways of using Emacs rarely involves multiple Emacs instances opening the same file, they cause me more problems than they solve.
(customize-set-variable 'create-lockfiles nil)
dired
Make file sizes shown in dired human readable.
(customize-set-variable 'dired-listing-switches
"-l --all --human-readable --group-directories-first")
tramp
remote editing
Allow Tramp to write backups of root-owned files in /tmp
, and ensure that Tramp uses path of remote shell on remote hosts.
(use-package tramp
:custom
(tramp-allow-unsafe-temporary-files t)
:config
(add-to-list 'tramp-remote-path 'tramp-own-remote-path))
Text editing
Bind Home/End to move to start/end of line
(global-set-key (kbd "<home>") #'move-beginning-of-line)
(global-set-key (kbd "<end>") #'move-end-of-line)
Change behaviour of M-z
for zapping to character
Make M-z
kill characters up to the character before the next occurrence of the selected character, instead of including it, which is generally more useful.
(global-set-key (kbd "M-z") #'zap-up-to-char)
Bind cycle-spacing
to M-S-SPC
This edits whitespace around point by cycling between leaving only one space, deleting the space, and going back to what was there before.
(global-set-key (kbd "M-S-SPC") #'cycle-spacing)
Bind duplicate-dwim
to M-R
Duplicates current line or active region.
(global-set-key (kbd "M-R") #'duplicate-dwim)
Use single space to delimit sentences
(customize-set-variable 'sentence-end-double-space nil)
Highlight selected region and apply changes to it
Highlight the region when the mark is active.
(transient-mark-mode t)
Set it so that if a selection is active, typed text will replace the selection.
(delete-selection-mode t)
Disable indentation using tabs.
(customize-set-variable 'indent-tabs-mode nil)
Set default line length to 80
(customize-set-variable 'fill-column 80)
Show character name in character description
When using C-x =
to look up the character under the point, also show Unicode character name.
(customize-set-variable 'what-cursor-show-names t)
Automatically pair matching characters like parenthesis
Enable electric-pair-mode
, which enables automatic insert of matching characters for example for parentheses.
(electric-pair-mode)
Save existing clipboard text into kill ring before replacing it
Prevents killing text in Emacs from irrevocably deleting things from the system clipboard.
(customize-set-variable 'save-interprogram-paste-before-kill t)
Enable downcase-region
and upcase-region
(put 'downcase-region 'disabled nil)
(put 'upcase-region 'disabled nil)
Make replacements and completions preserve case
This also affects dabbrev
completions.
(customize-set-variable 'case-replace nil)
Completion
dabbrev
dynamic abbreviations
Swap dabbrev-completion
and dabbrev-expand
, which works nicer with Corfu.
Also make it ignore some buffers where it does not make sense.
(use-package dabbrev
:bind (("M-/" . dabbrev-completion)
("C-M-/" . dabbrev-expand))
:config
(add-to-list 'dabbrev-ignored-buffer-regexps "\\` ")
(add-to-list 'dabbrev-ignored-buffer-modes 'doc-view-mode)
(add-to-list 'dabbrev-ignored-buffer-modes 'pdf-view-mode)
(add-to-list 'dabbrev-ignored-buffer-modes 'tags-table-mode))
Programming
Render some keywords and operators as symbols
I use this to make lambda
get rendered as λ
in Emacs Lisp, and similar replacements in other languages.
(global-prettify-symbols-mode)
Some reusable character compositions.
Most of these are double-wide characters in the font that I use, meaning that for example →
will for me occupy the space of two normal characters.
In principle I should then be able to replace two characters with that one and everything should line up, but I've sometimes seen subtle alignment issues when doing that, so now use this method where you first draw the correct number of spaces, and then the character on top of them.
This also works when the replacement character is not actually the same width as the characters I'm replacing, like with ∷
and …
in my case.
This also incidentally means this should work regardless of the width of the glyph in your font.
(setq my/prettify-right-arrow
'(?\s (Br . Bl) ?\s (Bc . Bc) ?→)
my/prettify-double-right-arrow
'(?\s (Br . Bl) ?\s (Bc . Bc) ?⇒)
my/prettify-left-arrow
'(?\s (Br . Bl) ?\s (Bc . Bc) ?←)
my/prettify-double-colon
'(?\s (Br . Bl) ?\s (Bc . Bc) ?∷)
my/prettify-ellipsis
'(?\s (Br . Bl) ?\s (Br . Bl) ?\s (Bc . Bc) ?…))
Simplified predicate to determine if a substitution should be applied, which makes them apply everywhere except for in strings. prettify-symbols-compose-predicate
can be set to this to apply substitutions more liberally.
(defun my/prettify-symbols-compose-p (_start _end _match)
(not (nth 3 (syntax-ppss))))
In programming modes, treat words in camel case symbols as separate.
(add-hook 'prog-mode-hook 'subword-mode)
Bind key to trigger compilation/recompilation
(define-key prog-mode-map (kbd "C-c b") #'compile)
(define-key prog-mode-map (kbd "C-c r") #'recompile)
flymake
Package for showing diagnostics from linters and similar interactively.
(autoload #'flymake-goto-next-error "flymake" nil t)
(autoload #'flymake-goto-prev-error "flymake" nil t)
(eval-after-load 'flymake
'(progn
(define-key flymake-mode-map (kbd "M-n") 'flymake-goto-next-error)
(define-key flymake-mode-map (kbd "M-p") 'flymake-goto-prev-error)))
Eglot for language server protocol support
Languages
C
(use-package cc-mode
:custom
(c-default-style '((java-mode . "java")
(awk-mode . "awk")
(other . "my")))
:hook
(c-mode . my/config-c-mode)
:config
(c-add-style "my"
'((c-basic-offset . 2)
(c-comment-only-line-offset . 0)
(c-hanging-braces-alist . ((brace-list-open)
(brace-entry-open)
(substatement-open after)
(block-close . c-snug-do-while)
(arglist-cont-nonempty)))
(c-cleanup-list . (brace-else-brace))
(c-offsets-alist . ((statement-block-intro . +)
(knr-argdecl-intro . 0)
(substatement-open . 0)
(substatement-label . 0)
(label . 0)
(statement-cont . +)))))
(defun my/config-c-mode ()
(setq-local prettify-symbols-alist
`(("->" . ,my/prettify-right-arrow)))))
To use a language server through Eglot for C, you can create a .dir-locals.el
file to run it in a container for C mode buffers, with the project directory mounted to the same path within the container to ensure paths sent to the LSP server matches those on the host system, and enable Eglot automatically when opening C mode buffers.
((c-mode . ((eval . (let ((root (project-root (project-current)))) (setq-local eglot-server-programs `((c-mode "podman" "run" "--rm" "--interactive" ,(concat "--volume=" root ":" root ":z") ,(concat "--workdir=" root) "ghcr.io/bkhl/lsp-containers/ccls:latest"))) (eglot-ensure))))))
Containerfile/Dockerfile
(use-package dockerfile-ts-mode
:mode (rx (or "/" bos)
(or "Containerfile" "Dockerfile")
(opt "." (* (not (any "/"))))
eos))
Go
(use-package go-ts-mode
:init
(defalias 'go-mode 'go-ts-mode)
(defalias 'go-mod-mode 'go-mod-ts-mode)
:mode
((rx ".go" eos) . go-mode)
((rx (or "/" bos) "go.mod" eos) . go-mod-mode)
:custom (go-ts-mode-indent-offset 4)
:config (defun my/config-go-ts-mode ()
(setq-local tab-width 4)
(setq-local prettify-symbols-alist
`(("<-" . ,my/prettify-left-arrow)
("..." . ,my/prettify-ellipsis))))
:hook (go-ts-mode . my/config-go-ts-mode))
Example .dir-locals.el
to use the gopls language server with Eglot, also using it for code formatting on save.
((go-ts-mode . ((eval . (let ((root (project-root (project-current)))) (setq-local eglot-server-programs `((go-ts-mode "podman" "run" "--rm" "--interactive" ,(concat "--volume=" root ":" root ":z") ,(concat "--workdir=" root) "docker.io/lspcontainers/gopls:latest"))) (add-hook 'before-save-hook #'eglot-format-buffer nil t) (eglot-ensure))))))
Perl
(use-package cperl-mode
:custom
(cperl-file-style "PBP")
:init
(add-to-list 'major-mode-remap-alist '(perl-mode . cperl-mode))
:config
(defun my/config-cperl-mode ()
(setq-local prettify-symbols-compose-predicate
#'my/prettify-symbols-compose-p
prettify-symbols-alist
`(("->" . ,my/prettify-right-arrow)
("=>" . ,my/prettify-double-right-arrow)
("::" . ,my/prettify-double-colon))))
:hook
(cperl-mode . my/config-cperl-mode))
Prolog
(use-package prolog
:hook
(prolog-mode . my/config-prolog-mode)
:config
(defun my/config-prolog-mode ()
(setq-local prettify-symbols-alist
`((":-" ,my/prettify-left-arrow)
("->" ,my/prettify-right-arrow)))))
Version control
vc-diff
Make vc-diff
imitate the diff format of Magit.
(customize-set-variable 'diff-font-lock-prettify t)
ediff
Make ediff use existing frame instead of creating new one
(customize-set-variable 'ediff-window-setup-function
'ediff-setup-windows-plain)
Project management
Detect Exercism exercises as projects.
This will make e.g. project-compile
run commands with the appropriate working directory for Exercism excercises.
(add-hook 'project-find-functions
(defun my/project-try-exercism (path)
(when-let ((root (locate-dominating-file path ".exercism")))
(cons 'transient (expand-file-name root)))))
Bug reference mode
Enable bug reference mode, and in Org mode override the keybinding to open links in the bug reference overlays.
(use-package bug-reference
:custom
(bug-reference-bug-regexp nil)
(bug-reference-url-format nil)
:hook
(text-mode . bug-reference-mode)
(prog-mode . bug-reference-prog-mode)
:bind
(:map bug-reference-map
("C-c C-o" . bug-reference-push-button))
:config
(setq bug-reference-auto-setup-functions nil))
To make this work in a project, a couple of variables need to be set, for example in .dir-locals.el
like this:
((nil . ((bug-reference-bug-regexp . "\\<\\(\\(\\(?:PROJECTA\\|PROJECTB\\)-[[:digit:]]+\\)\\)\\>") (bug-reference-url-format . "https://tracker.company.example/issue/%s"))))
Org
Default org-mode
directory
Set a custom variable for the notes directory, so that it can be referred to later.
(customize-set-variable 'org-directory "~/Documents/Notes/")
Make initial scratch buffer use org-mode
(customize-set-variable 'initial-major-mode 'org-mode)
Editing
Edit src blocks in current window.
(customize-set-variable 'org-src-window-setup 'current-window)
Make indentation and fonts in code blocks work according to mode for the language in the block.
(customize-set-variable 'org-src-tab-acts-natively t)
(customize-set-variable 'org-src-fontify-natively t)
Disable the extra indentation in src blocks.
(customize-set-variable 'org-edit-src-content-indentation 0)
This prevents accidental editing in invisible regions.
(customize-set-variable 'org-catch-invisible-edits 'error)
Shortcut for inserting a block of Elisp.
(add-to-list 'org-structure-template-alist
'("el" . "src emacs-lisp"))
When trying to edit in an hidden area, expand it before throwing an error.
(customize-set-variable 'org-catch-invisible-edits 'show-and-error)
Display
Enable org-indent
mode, which makes org-mode indent sections visually, but not in the saved files.
(customize-set-variable 'org-startup-indented t)
Use variable fonts in org-mode
buffers.
(add-hook 'org-mode-hook 'variable-pitch-mode)
Hide the characters surrounding emphasized phrases
(customize-set-variable 'org-hide-emphasis-markers t)
Use real ellipsis character for collapsed subtrees, and prefix it with a space.
(customize-set-variable 'org-ellipsis "…")
Put tags right after headline. This causes fewer conflicts with add-on packages affecting Org-mode style.
(customize-set-variable 'org-tags-column 0)
(customize-set-variable 'org-auto-align-tags nil)
Show Latex-style entities as Unicode characters.
(customize-set-variable 'org-pretty-entities t)
Key bindings
Editing of headers
When point is on a headline, make C-a
and C-e
go to beginning/end of headline text.
(customize-set-variable 'org-special-ctrl-a/e t)
Insert new headlines after current subtree.
(customize-set-variable 'org-insert-heading-respect-content t)
Global key binding to store links for org-mode
(global-set-key (kbd "C-c l") #'org-store-link)
Copy link location
Function to copy the destination of a link in Org.
I'd like to extend this to work more generally for example for bug-reference-mode
.
(defun my/org-copy-link (&optional arg)
(interactive "P")
(let* ((link (org-element-lineage (org-element-context) '(link) t))
(type (org-element-property :type link))
(path (org-element-property :path link)))
(if (and type path)
(let ((url (concat type ":" path)))
(kill-new url)
(message url))
(message "Not a link"))))
(define-key org-mode-map (kbd "C-c y") #'my/org-copy-link)
Capturing
Add templates for use by org-capture
.
(customize-set-variable 'org-capture-templates
`(("i"
"Inbox"
entry
(file ,(concat org-directory "Inbox.org"))
"* TODO %?")))
Bind C-c c
to org-capture
to quickly add notes.
(global-set-key (kbd "C-c c") #'org-capture)
Refiling
This allows refiling within the current buffer, or any agenda files.
(customize-set-variable 'org-refile-targets
'((nil :maxlevel . 9)
(org-agenda-files :maxlevel . 9)))
(customize-set-variable 'org-outline-path-complete-in-steps nil)
(customize-set-variable 'org-refile-use-outline-path 'file)
Agendas
Search all files in the notes directory when creating agendas.
(customize-set-variable 'org-agenda-files `(,org-directory))
Key binding to open an agenda view.
(global-set-key (kbd "C-c a") #'org-agenda)
Hide done tasks from the agenda.
(customize-set-variable 'org-agenda-skip-scheduled-if-done t)
(customize-set-variable 'org-agenda-skip-deadline-if-done t)
Hide already scheduled tasks from the agenda.
(customize-set-variable 'org-agenda-todo-ignore-scheduled 'all)
Show tags right after headline. Reduces conflicts with packages that affect Org agenda style.
(customize-set-variable 'org-agenda-tags-column 0)
Some agenda visual styling.
(customize-set-variable 'org-agenda-block-separator ?-)
(customize-set-variable 'org-agenda-time-grid
'((daily today require-timed)
(800 1000 1200 1400 1600 1800)
" ┄┄┄" ""))
(customize-set-variable 'org-agenda-current-time-string
"🠨")
Allow opening links to anchors with org-open-at-point
org-ctags
otherwise breaks this functionality if it's loaded, which happens if you open some other filetype that uses ctags.
This is caused by a bug discussed on mailing list here.
(with-eval-after-load 'org-ctags
(customize-set-variable 'org-open-link-functions nil))
Allow evaluating Shell code blocks in Org
Loading ob-shell
will implicitly enable using source blocks in languages supported by shell-mode
.
(use-package ob-shell)
Eshell
Disable banner
(customize-set-variable 'eshell-banner-message "")
Aliases
Alias to open file for editing
(defun eshell/e (f) (find-file f))
Add-on packages
Configuration of add-on packages.
Dependencies
These are add-on packages that are dependencies of other packages further down, as listed under each one.
compat
(use-package compat
:defer)
with-editor
(use-package with-editor
:load-path "site-lisp/with-editor/lisp")
Interface
fontaine font configuration
This package provides a concise way to define multiple font configurations and switch between them.
(use-package fontaine
:custom
(fontaine-presets
'((light :default-family "Iosevka BKHL Sans Normal"
:variable-pitch-family "Charis SIL"
:fixed-pitch-family "Iosevka BKHL Serif Normal")
(dark :default-family "Iosevka BKHL Sans Normal"
:variable-pitch-family "Inter BKHL Variable"
:variable-pitch-weight medium
:fixed-pitch-family "Iosevka BKHL Sans Normal")
(t :default-height 130
:default-weight normal
:variable-pitch-weight normal
:fixed-pitch-weight normal))))
modus-themes accessible themes
(use-package modus-themes
:custom
(modus-themes-bold-constructs t)
(modus-themes-italic-constructs t)
(modus-themes-mixed-fonts t)
(modus-themes-common-palette-overrides '((fringe unspecified))))
auto-dark to follow desktop dark mode setting
This mode switches Emacs theme triggered by dark style setting of the desktop.
To get this to also change menu bar theme in Fedora, I need to install the gnome-themes-extra package, and the Legacy (GTK3) Theme Scheme Auto Switcher Gnome extension.
(use-package auto-dark
:demand t
:custom
(auto-dark-dark-theme 'modus-vivendi-tritanopia)
(auto-dark-light-theme 'modus-operandi)
:config
(add-hook 'auto-dark-dark-mode-hook
(defun my/config-dark-mode ()
(fontaine-set-preset 'dark)))
(add-hook 'auto-dark-light-mode-hook
(defun my/config-light-mode ()
(fontaine-set-preset 'light)))
(auto-dark-mode))
minions mode line minor mode listing improvements
Hides minor modes in a popup menu to preserve space and make the mode line less noisy.
(use-package minions
:custom
(minions-prominent-modes '(trimspace-mode))
:config
(minions-mode))
lin mode for highlight of current line.
Enable higlight of current line in selected modes.
(use-package lin
:custom
(lin-face 'lin-yellow)
:config
(lin-global-mode))
edit-server to edit Firefox text areas
This module provides the server allowing the Edit with Emacs Firefox add-on to open Emacs buffers where you can edit the content of text areas.
(use-package edit-server
:load-path "site-lisp/edit-server/servers"
:custom
(edit-server-new-frame nil)
:config
(when (and (daemonp)
(not (process-status "edit-server")))
(edit-server-start)))
sv-kalender Swedish calendar localization
(use-package sv-kalender)
vertico for minibuffer completion
This is a library for completion in the minibuffer, which integrates with the emacs completing-read
functionality.
(use-package vertico
:config
(vertico-mode))
Do not allow the cursor in the minibuffer prompt.
(customize-set-variable 'minibuffer-prompt-properties
'(read-only t
cursor-intangible t
face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
Add prompt indicator to completing-read-multiple
.
(defun my/crm-indicator (args)
(cons (format "[CRM %s] %s"
(replace-regexp-in-string
(rx (or (seq bos "["
(*? nonl)
"]*")
(seq "["
(*? nonl)
"]*" eos)))
""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'my/crm-indicator)
(customize-set-variable 'read-extended-command-predicate
#'command-completion-default-include-p)
Allow minibuffer commands while in the minibuffer.
(customize-set-variable 'enable-recursive-minibuffers t)
marginalia minibuffer annotations
(use-package marginalia
:config
(marginalia-mode))
consult search and navigation commands
(use-package consult
:make-autoloads
:bind (;; C-c bindings (mode-specific-map)
("C-c h" . consult-history)
("C-c m" . consult-mode-command)
("C-c k" . consult-kmacro)
;; C-x bindings (ctl-x-map)
("C-x M-:" . consult-complex-command) ;; replaces `nrepeat-complex-command'
("C-x b" . consult-buffer) ;; replaces `switch-to-buffer'
("C-x 4 b" . consult-buffer-other-window) ;; replaces `switch-to-buffer-other-window'
("C-x 5 b" . consult-buffer-other-frame) ;; replaces `switch-to-buffer-other-frame'
("C-x r b" . consult-bookmark) ;; replaces `bookmark-jump'
("C-x p b" . consult-project-buffer) ;; replaces `project-switch-to-buffer'
;; Custom bindings for quick register access
("M-\"" . consult-register-store)
("M-'" . consult-register-load) ;; replaces `abbrev-prefix-mark' (unrelated)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; replaces `yank-pop'
;; M-g bindings (goto-map)
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake)
("M-g g" . consult-goto-line) ;; replaces `goto-line'
("M-g M-g" . consult-goto-line) ;; replaces `goto-line'
("M-g o" . consult-outline)
("M-g a" . consult-org-agenda)
("M-g h" . consult-org-heading)
("M-g m" . consult-mark)
("M-g k" . consult-global-mark)
("M-g i" . consult-imenu)
("M-g I" . consult-imenu-multi)
;; M-s bindings (search-map)
("M-s d" . consult-find)
("M-s D" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
("M-s '" . consult-register)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; replaces isearch-edit-string
("M-s e" . consult-isearch-history) ;; replaces isearch-edit-string
("M-s l" . consult-line) ;; needed by consult-line to detect isearch
("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch
;; Minibuffer history
:map minibuffer-local-map
("M-s" . consult-history) ;; replaces next-matching-history-element
("M-r" . consult-history)) ;; replaces previous-matching-history-element
:init
(customize-set-variable 'register-preview-delay 0.5)
(customize-set-variable 'register-preview-function
#'consult-register-format)
(advice-add #'register-preview :override #'consult-register-window)
(customize-set-variable 'xref-show-xrefs-function #'consult-xref)
(customize-set-variable 'xref-show-definitions-function #'consult-xref)
:config
(customize-set-variable 'consult-narrow-key "<"))
embark contextual actions
Embark provides ways to trigger commands based on the entity at point or the region, in both regular buffers and minibuffers.
(use-package embark
:bind
(("C-." . embark-act)
("C-;" . embark-dwim)
("C-h B" . embark-bindings)) ;; replaces `describe-bindings'
:commands embark-prefix-help-command
:init
(setq prefix-help-command #'embark-prefix-help-command)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
`(,(rx bos
"*Embark Collect "
(or "Live" "Completions")
"*")
nil
(window-parameters (mode-line-format . none)))))
This adds some extra integration between Embark and Consult.
(use-package embark-consult
:after (embark consult)
:demand t
:hook
(embark-collect-mode . consult-preview-at-point-mode))
pulsar to temporarily highlight current line
Press to flash line of point where the currently active cursor is.
The M-p
binding conflicts with my custom binding for flymake-mode
, but we can then use M-P
as long as that is free.
(use-package pulsar
:demand t
:custom
(pulsar-face 'pulsar-yellow)
:bind
(("M-p" . pulsar-pulse-line))
:hook
(next-error . pulsar-pulse-line)
:config
(push 'next-error pulsar-pulse-functions)
(pulsar-global-mode))
free-keys to show free bindings
This package can show available bindings in the current buffer.
(use-package free-keys
:commands free-keys)
Text editing
substitute text replacement commands
(use-package substitute
:custom
(substitute-highlight t)
:bind
(("M-# s" . substitute-target-below-point)
("M-# r" . substitute-target-above-point)
("M-# d" . substitute-target-in-defun)
("M-# b" . substitute-target-in-buffer)))
trimspace-mode for trimming trailing spaces and newlines
trimspace-mode
sets things up so that when a file is opened, it enables deleting trailing whitespace and newlines before saving the file, unless the file when first opened already has traling whitespace of each type.
(use-package trimspace-mode
:hook
(prog-mode . trimspace-mode-maybe)
(text-mode . trimspace-mode-maybe))
whole-line-or-region
This module allows a number of functions to operate on the current line if no region is selected.
(use-package whole-line-or-region
:config
(whole-line-or-region-global-mode))
Completion
corfu for completion at point
(use-package corfu
:demand t
:bind
(("C-<tab>" . complete-symbol))
:config
(global-corfu-mode))
cape completion at point extensions
(use-package cape
:make-autoloads
:bind (("C-c p p" . completion-at-point)
("C-c p t" . complete-tag)
("C-c p d" . cape-dabbrev)
("C-c p h" . cape-hist)
("C-c p f" . cape-file)
("C-c p k" . cape-keyword)
("C-c p s" . cape-symbol)
("C-c p a" . cape-abbrev)
("C-c p i" . cape-ispell)
("C-c p l" . cape-line)
("C-c p w" . cape-dict)
("C-c p \\" . cape-tex)
("C-c p _" . cape-tex)
("C-c p ^" . cape-tex)
("C-c p &" . cape-sgml)
("C-c p r" . cape-rfc1345))
:init
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-file))
orderless completion style
Orderless provides a completion style that allows typing components of a canditate out of order.
(use-package orderless
:custom
(completition-styles '(orderless basic))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles partial-completion))))
:config
(let ((hook (defun my/minibuffer-setup ()
(setq-local completion-styles '(orderless basic)))))
(remove-hook 'minibuffer-setup-hook hook)
(add-hook 'minibuffer-setup-hook hook 1)))
Programming
fancy-compilation to improve compilation output buffers
(use-package fancy-compilation
:custom
(fancy-compilation-override-colors nil)
:config
(fancy-compilation-mode))
Languages
Emacs Lisp
package-lint Emacs package linter
(use-package package-lint
:commands (package-lint-buffer
package-lint-current-buffer))
xr reverse rx
Helpful functions for refactoring regular expressions to rx
expressions.
(use-package xr
:commands (xr
xr-pp
xr-lint
xr-skip-set
xr-skip-set-pp
xr-skip-set-lint
xr-pp-rx-to-str))
lua-mode
(use-package lua-mode
:mode (rx ".lua" eos)
:custom
(lua-indent-level 4))
Writing
Jinx spell checking
(use-package jinx
:custom
(jinx-languages "sv_SE en_GB en_US th_TH")
:config
(add-to-list 'jinx--syntax-overrides '(?: . "."))
:bind
("M-$" . jinx-correct)
:commands (jinx-mode
global-jinx-mode
jinx-correct))
markdown-mode for Markdown support
(use-package markdown-mode
:mode (rx ".md" eos))
olivetti to adjust margins of text
A minor mode that automatically adjusts margins &c. for reading and writing prose.
(use-package olivetti
:custom
(olivetti-style nil)
:hook
(Info-mode . olivetti-mode)
(org-mode . olivetti-mode)
(ewww . olivetti-mode))
File formats
YAML
yaml-mode
(use-package yaml-mode
:mode (rx ".y" (opt "a") "ml" eos))
flymake-yamllint
(use-package flymake-yamllint)
To use this in a project a .dir-locals.el
file is needed, looking something like this:
((yaml-mode . ((flymake-yamllint-program . "podman") (flymake-yamllint-arguments . ("run" "--rm" "--interactive" "docker.io/cytopia/yamllint")) (eval . (progn (flymake-yamllint-setup) (flymake-mode))))))
Version control
Magit Git interface
(use-package magit
:load-path "site-lisp/magit/lisp"
:bind
("C-x g" . magit-status)
:commands
magit-call-git
:custom
(magit-push-always-verify nil)
(git-commit-summary-max-length 50)
:config
(transient-append-suffix 'magit-push "-t"
'(4
"-s"
"Skip pipeline"
"-o ci.skip"))
(transient-append-suffix 'magit-push "-s"
'(4
"-m"
"Create merge request"
"-o merge_request.create"))
(transient-append-suffix 'magit-push "-m"
'(4
"-M"
"Create merge request with target"
"-o merge_request.create -o merge_request.target="))
(transient-append-suffix 'magit-push "-M"
'(4
"-l"
"Set all tests label"
"-o merge_request.unlabel=test::skip -o merge_request.label=test::all"))
(transient-append-suffix 'magit-push "-l"
'(4
"-L"
"Set skip tests label"
"-o merge_request.unlabel=test::all \
-o merge_request.label=test::skip")))
Automatic commit on save
Function to do automatic commit on save in certain repos. This is for use with for example org-mode
, to enable finding things after accidental changes.
(defun my/magic-commit-current-buffer ()
(magit-call-git "add" buffer-file-name)
(magit-call-git "commit"
"-m"
(format "Automatic commit on save of %s"
buffer-file-name))
(magit-refresh))
To use this as an after-save-hook
in a project, create a .dir-locals.el
with something like this:
((org-mode . ((eval . (add-hook 'after-save-hook 'my/magic-commit-current-buffer nil t)))))
diff-hl to show uncommitted changes in gutter
Shows changes that are not committed to the version control system for the file open in a buffer in the gutter.
(use-package diff-hl
:custom
(diff-hl-draw-borders nil)
:config
(global-diff-hl-mode)
(add-hook 'magit-pre-refresh-hook
'diff-hl-magit-pre-refresh)
(add-hook 'magit-post-refresh-hook
'diff-hl-magit-post-refresh))
(use-package diff-hl-flydiff
:config
(diff-hl-flydiff-mode))
git-timemachine file history browsing
(use-package git-timemachine
:commands git-timemachine)
git-link to generate links to Git forges
(use-package git-link
:bind
("C-c g l" . git-link))
Org
org-modern styling for Org mode
(use-package org-modern
:config
(dolist (face '(org-modern-symbol org-modern-label))
(set-face-attribute face nil :family "Iosevka BKHL Sans Normal"))
(global-org-modern-mode))