Hello. This is my Emacs configuration. It’s both documentation and code in one place, a paradigm referred to as Literate programming.
When Emacs starts it runs the code in init.el. Rather than storing the configuration here directly, we have this line:
(org-babel-load-file "~/.emacs.d/readme.org")That then loads this file, which will tangle and then evaluate every emacs-lisp
code block in here, unless it is explicitly marked with :tangle no, as is true
with the example above.
Note that evaluating this file with org-babel-load-file will result in some
variables being set unless we set the variable below. Instead of cluttering up
this file, we will write the generated code to a new file that can be easily
ignored:
(setq custom-file "~/.emacs.d/generated.el")(require 'package)
(add-to-list 'package-archives
'("MELPA" .
"http://melpa.org/packages/"))
(setq package-enable-at-startup nil)
(eval-when-compile
(require 'use-package))
(setq use-package-always-ensure t)
(setq quelpa-update-melpa-p nil)
(use-package quelpa)
(use-package quelpa-use-package)Note that I set EMACS_SERVER_AUTH_KEY and launch emacs via launchctl. Since I
currently only run emacs as a daemon on macOS I do not have any conditional
logic here.
(defun open-initial-frame-when-daemonized ()
(when (daemonp)
(setq server-use-tcp t)
(setq server-auth-key (getenv "EMACS_SERVER_AUTH_KEY"))
(setq server-port 52698)
(server-start)
(make-frame '((display . "mac"))))) ;; Adjust "mac" if needed, depending on your display configuration
;; Run the function after the daemon starts
(add-hook 'after-init-hook 'open-initial-frame-when-daemonized)My workflow is built around the assumption that the code I am working on may or may not be local to the machine I am working on.
For cases where I need to work remotely, I use tramp. This mostly works fine, but there are times where I am SSH’d into a remote machine where my code is hosted via a terminal, and I would like a way to open a file or directory in Emacs without having to switch contexts to copy / paste a path between the applications.
By starting the server below listening via TCP, we are able to open reverse emacsclient connections back to ourselves using this ZSH function:
#!/bin/env zsh
function re {
# Get the number of arguments
local num_args=$#
# If no arguments are provided, exit the function
if [ $num_args -eq 0 ]; then
echo "🤖 re (remote? reverse? emacsclient) 🤖"
echo "Usage: re [emacsclient options] <path>"
return 1
fi
# Extract the last argument as the path
local tramppath="$1"
# Check if a valid path was provided (should be the first arg)
if [ -z "$tramppath" ]; then
echo "Error: No path provided."
return 1
fi
# Run the emacsclient command with -cn option and --server-file and /sshx options
emacsclient -cn --server-file ~/server /sshx:$(hostname):$tramppath
}https://elpa.gnu.org/packages/delight.html
This package makes the mode line less noisy by allowing us to hide certain minor
modes. We’ll call this early so we can use as-needed, by using the delight
keyword when calling use-package.
(use-package delight)(setq python-shell-interpreter "/Users/shanemcd/.pyenv/shims/python"
flycheck-python-pycompile-executable "/Users/shanemcd/.pyenv/shims/python"
python-shell-exec-path "/Users/shanemcd/.pyenv/shims/python")(use-package markdown-mode
:config
(setq markdown-fontify-code-blocks-natively t))(show-paren-mode)(use-package yaml-mode
:config
(setq yaml-indent-offset 2))
(defun my/yaml-outline-setup ()
"Configure outline-minor-mode for YAML to fold both keys and list items."
;; Match lines like:
;; topKey:
;; - listItem:
;; - listItem
(setq-local outline-regexp "^\\([[:space:]]*\\(-\\|\\w\\)\\)")
(outline-minor-mode 1))
(add-hook 'yaml-mode-hook #'my/yaml-outline-setup)(use-package groovy-mode
:config
(setq groovy-indent-offset 2))(use-package swift-mode)
(use-package ob-swift)(use-package go-mode
:config
(add-hook 'go-mode-hook
(lambda ()
(setq indent-tabs-mode 1)
(setq tab-width 8))))
(use-package ob-go)(use-package dockerfile-mode)(use-package ob-mermaid)(use-package org
:mode ("\\.org\\'" . org-mode)
:bind (("C-c l" . org-store-link)
("C-c c" . org-capture)
("C-c C-w" . org-refile))
:config
(require 'org-tempo)
(setq org-directory "~/org")
(setq org-agenda-skip-unavailable-files t)
(setq org-agenda-files (directory-files-recursively "~/org/" "\\.org$"))
(setq org-use-speed-commands t)
(setq org-startup-with-inline-images t)
(setq org-capture-templates '(
("t" "TODO" entry (file+headline "~/org/incoming.org" "TODOs") "** TODO %?\n %i\n %a")
("e" "Log entry" entry (file+headline "~/org/log.org" "Log") "** %?\n %i\n %a")
("p" "Private note" entry (file+headline "~/org/journal.org" "Notes") "** %?\n %i\n %a")
))
(setq org-refile-targets '((org-agenda-files :maxlevel . 3)))
(setq org-imenu-depth 3)
(org-babel-do-load-languages
'org-babel-load-languages
'((shell . t)
(emacs-lisp . t)
(js . t)
(ruby . t)
(swift . t)
(mermaid . t)
(python . t)))
(setq org-babel-python-command "python3")
(setq org-startup-folded t)
(setq org-odt-preferred-output-format "docx")
(setq org-src-fontify-natively t
org-src-window-setup 'current-window
org-src-strip-leading-and-trailing-blank-lines t
org-src-preserve-indentation t
org-hide-emphasis-markers t
org-src-tab-acts-natively t))
(use-package org-appear
:after org
:hook (org-mode . org-appear-mode)
:config
(setq org-appear-autolinks t))
(defun my/org-roam-capture-finished ()
"Function to run after an Org-roam capture is finalized."
(setq org-agenda-files (directory-files-recursively "~/org/" "\\.org$")))
(use-package org-roam
:after org
:config
(setq org-roam-directory (file-truename "~/org/roam"))
(org-roam-db-autosync-mode)
(setq org-roam-node-display-template "${title:*} ${tags:20}")
(add-hook 'org-capture-after-finalize-hook 'my/org-roam-capture-finished)
:bind (("C-c r b" . org-roam-buffer-toggle)
("C-c r f" . org-roam-node-find)))
(use-package org-modern
:after org
:hook (org-mode . global-org-modern-mode))https://github.com/larstvei/ox-gfm
(use-package ox-gfm)(use-package vterm
;; :config
;; (setq vterm-tramp-shells '(("sshx" "/bin/zsh")))
)(quelpa '(eat :fetcher git
:url "https://codeberg.org/akib/emacs-eat"
:files ("*.el" ("term" "term/*.el") "*.texi"
"*.ti" ("terminfo/e" "terminfo/e/*")
("terminfo/65" "terminfo/65/*")
("integration" "integration/*")
(:exclude ".dir-locals.el" "*-tests.el"))))(use-package ultra-scroll
;:vc (:url "https://github.com/jdtsmith/ultra-scroll") ; if desired (emacs>=v30)
:init
(setq scroll-conservatively 3 ; or whatever value you prefer, since v0.4
scroll-margin 0) ; important: scroll-margin>0 not yet supported
:config
(ultra-scroll-mode 1))(setq-default fill-column 80)(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)https://github.com/tonsky/FiraCode
;; Set Iosevka SS05 Expanded using XLFD names for each OS
(set-frame-font
(cond
((eq system-type 'darwin)
"-*-Iosevka SS05-medium-normal-expanded-*-16-*-*-*-m-0-iso10646-1")
((eq system-type 'gnu/linux)
"-UKWN-Iosevka SS05-medium-normal-expanded-*-16-*-*-*-d-0-iso10646-1"))
t t)(setq inhibit-splash-screen t)(setq column-number-mode t);; (global-display-line-numbers-mode)(use-package base16-theme)Automatically switch between light and dark themes based on system appearance.
(use-package auto-dark
:custom
(auto-dark-themes '((base16-tomorrow-night) (base16-tomorrow)))
:init
(auto-dark-mode))https://www.emacswiki.org/emacs/WindMove
(when (fboundp 'windmove-default-keybindings)
(windmove-default-keybindings))(defun my-toggle-emacs-bookmark (event)
"Toggle an Emacs bookmark at the line clicked."
(interactive "e")
(let* ((pos (posn-point (event-start event)))
(line (line-number-at-pos pos))
(bookmark-name (format "Line %d in %s" line (buffer-name))))
(save-excursion
(goto-char pos)
(if (bookmark-get-bookmark bookmark-name t)
(progn
(bookmark-delete bookmark-name)
(message "Deleted bookmark: %s" bookmark-name))
(bookmark-set bookmark-name)
(message "Set bookmark: %s" bookmark-name)))))
(global-set-key [left-fringe mouse-1] #'my-toggle-emacs-bookmark)(use-package spacious-padding
:config
(spacious-padding-mode))(toggle-frame-maximized)(use-package olivetti)(setq-default indent-tabs-mode nil tab-width 4)
(use-package sh-script
:mode "\\.sh\\'"
:config (setq sh-indentation 4 sh-basic-offset 4))(setq require-final-newline t)(use-package ellama
:bind ("C-c e" . ellama-transient-main-menu)
:init
(require 'llm-ollama)
(setopt ellama-provider
(make-llm-ollama
:host "tot"
:port 11434
:chat-model "granite3.2:8b"
:embedding-model "granite3.2:8b")))(quelpa '(claudemacs :fetcher github :repo "cpoile/claudemacs"))(use-package browse-at-remote
:commands browse-at-remote
:bind ("C-c g g" . browse-at-remote))(use-package company
:delight
:config
(setq company-minimum-prefix-length 1)
(global-company-mode t)
(setq company-global-modes '(not org-mode)))- Set up key binding for
dired-jump.- If you enter a file from dired and press this shortcut, you will pop back into the dired buffer
- Only show filenames in dired by default, hiding the permissions and other
details. Press
(to show details.
(global-set-key (kbd "C-x C-j") 'dired-jump)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
;(use-package dired-single)
(require 'dired-x)
(add-hook 'dired-mode-hook 'dired-omit-mode)
(setq-default dired-omit-files-p t) ; Buffer-local variable
(setq dired-omit-files (concat dired-omit-files "\\|^\\..+$"))(use-package dired-subtree
:after dired
:config
(bind-key "<tab>" #'dired-subtree-toggle dired-mode-map)
(bind-key "<backtab>" #'dired-subtree-cycle dired-mode-map)
(setq dired-subtree-use-backgrounds nil))(use-package drag-stuff
:config
(drag-stuff-define-keys))(use-package dumb-jump
:config
;(setq dumb-jump-selector 'helm)
(add-hook 'xref-backend-functions #'dumb-jump-xref-activate))(use-package fill-column-indicator
:config
(setq-default fci-rule-column 80)
(setq fci-rule-color (face-attribute 'highlight :background)))(use-package flycheck
:delight
:config
(global-flycheck-mode))I unbind flyspell-mode-map because I use C-. for embark-act.
(use-package flyspell
:delight
:config
(define-key flyspell-mode-map (kbd "C-.") nil)
(add-hook 'prog-mode-hook 'flyspell-prog-mode)
(add-hook 'text-mode-hook 'flyspell-mode)
(add-hook 'markdown-mode-hook 'flyspell-mode))(use-package htmlize
:commands (htmlize-buffer
htmlize-file
htmlize-many-files
htmlize-many-files-dired
htmlize-region))(use-package imenu-list
:bind
(("C-." . imenu-list-smart-toggle))
:config
(setq imenu-list-focus-after-activation t))(use-package tramp
:defer t
:config
(setopt tramp-remote-path '(tramp-own-remote-path))
(setq tramp-histfile-override nil))(use-package magit
:bind ("C-x g" . magit)
:commands magit-project-status)
(use-package project
:bind
(:map project-prefix-map
("m" . magit-project-status))
:config
(add-to-list 'project-switch-commands '(magit-project-status "Magit") t))“Move where I mean” - C-a takes you to the first character on the line.
(use-package mwim
:config
(global-set-key (kbd "C-a") 'mwim-beginning)
(global-set-key (kbd "C-e") 'mwim-end))(use-package nerd-icons)
(use-package nerd-icons-dired
:delight
:hook
(dired-mode . nerd-icons-dired-mode))(use-package rainbow-delimiters
:config
(add-hook 'prog-mode-hook #'rainbow-delimiters-mode))(use-package reveal-in-osx-finder)(use-package vundo
:commands (vundo)
:config
(setq vundo-compact-display t))(use-package ws-butler
:delight
:config
(ws-butler-global-mode t))(use-package wttrin
:config
(setq wttrin-default-cities '("New York NY" "Winchester VA" "Durham NC" "Kaohsiung City")))(use-package ztree)(use-package yasnippet
:commands yas-minor-mode
:hook (go-mode . yas-minor-mode))(use-package winum
:config
(winum-mode)
:bind
(("M-0" . 'winum-select-window-0-or-10)
("M-1" . 'winum-select-window-1)
("M-2" . 'winum-select-window-2)
("M-3" . 'winum-select-window-3)
("M-4" . 'winum-select-window-4)
("M-5" . 'winum-select-window-5)
("M-6" . 'winum-select-window-6)
("M-7" . 'winum-select-window-7)
("M-8" . 'winum-select-window-8))
)(use-package pandoc-mode
:config
(add-hook 'markdown-mode-hook 'pandoc-mode));; (use-package marp-mode
;; :quelpa (marp-mode :fetcher github :repo "shanemcd/marp-mode")
;; :hook (markdown-mode . marp-mode))
(use-package marp-mode
:load-path "/var/home/shanemcd/github/shanemcd/marp-mode"
:hook (markdown-mode . marp-mode))(use-package hide-mode-line)(setq backup-directory-alist `(("." . "~/.emacs.bak")))
(setenv "PINENTRY_USER_DATA" "USE_CURSES=0")(use-package exec-path-from-shell
:config
(setq exec-path-from-shell-arguments '("-li"))
(setenv "SHELL" "/bin/zsh")
(exec-path-from-shell-initialize)
(dolist (var '("GOPATH"
"PATH"
"LC_ALL"
"LANG"
"LC_TYPE"
"SSH_AGENT_PID"
"SSH_AUTH_SOCK"
"SHELL"
"CLAUDE_CODE_USE_VERTEX"
"CLOUD_ML_REGION"
"ANTHROPIC_VERTEX_PROJECT_ID"
"ANTHROPIC_MODEL"
"ANTHROPIC_DEFAULT_HAIKU_MODEL"))
(exec-path-from-shell-copy-env var)))
Trying to piece together a more modern completion system
;; Enable rich annotations using the Marginalia package
(use-package marginalia
;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding
;; available in the *Completions* buffer, add it to the
;; `completion-list-mode-map'.
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
;; The :init section is always executed.
:init
;; Marginalia must be activated in the :init section of use-package such that
;; the mode gets enabled right away. Note that this forces loading the
;; package.
(marginalia-mode))(use-package embark
:ensure t
:bind
(("C-." . embark-act) ;; pick some comfortable binding
("C-;" . embark-dwim) ;; good alternative: M-.
("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
;; Show the Embark target at point via Eldoc. You may adjust the
;; Eldoc strategy, if you want to see the documentation from
;; multiple providers. Beware that using this can be a little
;; jarring since the message shown in the minibuffer can be more
;; than one line, causing the modeline to move up and down:
;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))));; Example configuration for Consult
(use-package consult
;; Replace bindings. Lazily loaded by `use-package'.
:bind (;; C-c bindings in `mode-specific-map'
("C-c M-x" . consult-mode-command)
("C-c h" . consult-history)
("C-c k" . consult-kmacro)
("C-c m" . consult-man)
("C-c i" . consult-info)
([remap Info-search] . consult-info)
;; C-x bindings in `ctl-x-map'
("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
("C-x b" . consult-buffer) ;; orig. switch-to-buffer
("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame
("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab
("C-x r b" . consult-bookmark) ;; orig. bookmark-jump
("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
;; Custom M-# bindings for fast register access
("M-#" . consult-register-load)
("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
("C-M-#" . consult-register)
;; Other custom bindings
("M-y" . consult-yank-pop) ;; orig. yank-pop
;; M-g bindings in `goto-map'
("M-g e" . consult-compile-error)
("M-g f" . consult-flymake) ;; Alternative: consult-flycheck
("M-g g" . consult-goto-line) ;; orig. goto-line
("M-g M-g" . consult-goto-line) ;; orig. goto-line
("M-g o" . consult-outline) ;; Alternative: 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 in `search-map'
("M-s d" . consult-find) ;; Alternative: consult-fd
("M-s c" . consult-locate)
("M-s g" . consult-grep)
("M-s G" . consult-git-grep)
("M-s r" . consult-ripgrep)
("M-s l" . consult-line)
("M-s L" . consult-line-multi)
("M-s k" . consult-keep-lines)
("M-s u" . consult-focus-lines)
;; Isearch integration
("M-s e" . consult-isearch-history)
:map isearch-mode-map
("M-e" . consult-isearch-history) ;; orig. isearch-edit-string
("M-s e" . consult-isearch-history) ;; orig. 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) ;; orig. next-matching-history-element
("M-r" . consult-history)) ;; orig. previous-matching-history-element
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
:hook (completion-list-mode . consult-preview-at-point-mode)
;; The :init configuration is always executed (Not lazy)
:init
;; Tweak the register preview for `consult-register-load',
;; `consult-register-store' and the built-in commands. This improves the
;; register formatting, adds thin separator lines, register sorting and hides
;; the window mode line.
(advice-add #'register-preview :override #'consult-register-window)
(setq register-preview-delay 0.5)
;; Use Consult to select xref locations with preview
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
;; Configure other variables and modes in the :config section,
;; after lazily loading the package.
:config
;; Optionally configure preview. The default value
;; is 'any, such that any key triggers the preview.
;; (setq consult-preview-key 'any)
;; (setq consult-preview-key "M-.")
;; (setq consult-preview-key '("S-<down>" "S-<up>"))
;; For some commands and buffer sources it is useful to configure the
;; :preview-key on a per-command basis using the `consult-customize' macro.
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
;; :preview-key "M-."
:preview-key '(:debounce 0.4 any))
;; Optionally configure the narrowing key.
;; Both < and C-+ work reasonably well.
(setq consult-narrow-key "<") ;; "C-+"
;; Optionally make narrowing help available in the minibuffer.
;; You may want to use `embark-prefix-help-command' or which-key instead.
;; (keymap-set consult-narrow-map (concat consult-narrow-key " ?") #'consult-narrow-help)
)
(use-package consult-eglot
:after eglot);; Pulls in embark as a dependency
(use-package embark-consult
:ensure t ; only need to install it, embark loads it after consult if found
:hook
(embark-collect-mode . consult-preview-at-point-mode))(use-package vertico
:custom
(vertico-resize t) ;; Grow and shrink the Vertico minibuffer
(vertico-cycle t) ;; Enable cycling for `vertico-next/previous'
:init
(vertico-mode))
(use-package vertico-directory
:ensure f
:bind (:map vertico-map
("C-l" . vertico-directory-up)
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)));; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
:init
(savehist-mode));; A few more useful configurations...
(use-package emacs
:custom
;; Hide commands in M-x which do not work in the current mode. Vertico
;; commands are hidden in normal buffers. This setting is useful beyond
;; Vertico.
(read-extended-command-predicate #'command-completion-default-include-p)
:init
;; Add prompt indicator to `completing-read-multiple'.
;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
(defun crm-indicator (args)
(cons (format "[CRM%s] %s"
(replace-regexp-in-string
"\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
crm-separator)
(car args))
(cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator));; Optionally use the `orderless' completion style.
(use-package orderless
:custom
;; Configure a custom style dispatcher (see the Consult wiki)
;; (orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch))
;; (orderless-component-separator #'orderless-escapable-split-on-space)
(completion-styles '(orderless ))
(completion-category-defaults nil)
(completion-category-overrides '((file (styles partial-completion)))))(use-package wgrep)(use-package eglot)
(use-package dape
:after eglot
:config
;; Turn on global bindings for setting breakpoints with mouse
(dape-breakpoint-global-mode)
(setq dape-inlay-hints t))(use-package smart-mode-line
:init
(sml/setup))(use-package sudo-edit)This makes it consistent with Linux.
(when (eq system-type 'darwin)
(setq mac-command-modifier 'meta)
(setq mac-option-modifier 'super)
(setq mac-control-modifier 'control))Pulled from https://www.emacswiki.org/emacs/KillingBuffers#h5o-2
(defun kill-other-buffers ()
"Kill all other buffers."
(interactive)
(mapc 'kill-buffer (delq (current-buffer) (buffer-list))))(setq doc-view-resolution 300)(defun preview-font ()
"Interactively preview available fonts."
(interactive)
(let ((font (completing-read "Preview font: " (font-family-list))))
(set-frame-font font t t)))
(defun preview-font-full ()
"Preview full font faces including variants."
(interactive)
(let* ((fonts (delete-dups (sort (x-list-fonts "*" nil) #'string<)))
(font (completing-read "Preview font face: " fonts)))
(set-frame-font font t t)))
(defun print-current-font ()
"Print the current frame font in the minibuffer and echo area."
(interactive)
(message "Current font: %s" (frame-parameter nil 'font)))On Fedora Kinoite and other Atomic Desktops, /home is symlinked to
/var/home. This causes issues with breakpoints not working in certain debuggers.
(when-let ((real-home (file-truename (getenv "HOME"))))
(when (not (string= (getenv "HOME") real-home))
(setenv "HOME" real-home)))