Published on 12 March 2025 by Jing Huang.
For reasons that felt arbitrary at the time, I decided to document my Emacs configuration, written specifically for the NeXTStep Cocoa implementation. Refactored in May 2025, with major update in November 2025.
Somewhat trivial, basically about correcting Emacs’s behavior before init.el is loaded. This toggles
off the defaults that might cause clutter or affect performance, and disables unnecessary components for the
interface.
|(setq frame-inhibit-implied-resize t|frame-resize-pixelwise t|window-resize-pixelwise t|inhibit-startup-screen t5 |use-dialog-box nil)||(setopt menu-bar-mode nil|scroll-bar-mode nil|tool-bar-mode nil10 |tooltip-mode nil)
There are no redundant GC tweaks, as I compile Emacs with the incremental garbage collector.
Set the load-path explicitly when portable dumped. Portable dump related details are documented in
Chapter 5.
|(defvar dumped-load-path)||(when (boundp 'dumped-load-path)|(setq load-path dumped-load-path))
The handlers for file names are stripped locally during the very early stages.
|(setq-local file-name-handler-alist nil)
Then add core to the load path. The dolist is intended for more fine-grained division of
load paths in the future.
|(dolist (site '("core"))|(add-to-list 'load-path|(expand-file-name site user-emacs-directory)))
Tell Emacs not to litter on the file system, prevent custom.el from modifying init.el,
unlock disabled commands, use short answers, and enable refined CJK line-break which respects kinsoku.
|(setq auto-save-file-name-transforms `((".*" ,temporary-file-directory t))|backup-directory-alist `((".*" . ,temporary-file-directory))|custom-file (make-temp-file "custom" nil ".el")|disabled-command-function nil5 |use-short-answers t|word-wrap-by-category t)
Font configuration without mixing fonts, as they may lead to unexpected line height related issues. Since we don’t
actually use any sans serif fonts, we should’t scale the variable-pitch-text face as well.
|(when (display-graphic-p)|(dolist (face '(default fixed-pitch fixed-pitch-serif variable-pitch))|(set-face-attribute face nil :family "SF Mono")))|5 |(set-face-attribute 'variable-pitch-text nil :height 1.0)
Optimizations for large files, based on LdBeth’s approach.
|(setq bidi-display-reordering nil|bidi-inhibit-bpa t|large-hscroll-threshold 1000|long-line-threshold 10005 |syntax-wholeline-max 1000)
Unbind the annoying key binding to mouse-wheel-text-scale, to prevent accidental unintended text
scaling.
|(keymap-global-set "C-<wheel-up>" 'ignore)|(keymap-global-set "C-<wheel-down>" 'ignore)
Supply our own mode line, which is by design robust, with only necessary elements displayed. The lighter for minor modes are replaced with condensed Unicode glyphs.
|(setq mode-line-right-align-edge 'right-margin|mode-line-space (propertize " " 'display '(space :height 1.4))|mode-line-minor-mode '("ⓐ")|mode-line-minor-mode-lighter '((eldoc-mode . "ⓔ")5 |(flymake-mode . "ⓜ")|(flyspell-mode . "ⓢ")|(visual-line-mode . "ⓥ")|(whitespace-mode . "ⓦ")|(yas-minor-mode . "ⓨ")))10 ||(dolist (mapping mode-line-minor-mode-lighter)|(let ((mode (intern (symbol-name (car mapping))))|(lighter (cdr mapping)))|(push `(,mode ,lighter) mode-line-minor-mode)))15 ||(setq-default mode-line-format `(,mode-line-space|,mode-line-mule-info|,mode-line-modified|,mode-line-space20 |(:propertize "%b" face bold)|mode-line-format-right-align|(:propertize mode-name face bold)|,mode-line-space|,mode-line-minor-mode25 |,mode-line-space))
Activate some handy builtin modes. We use setopt for modes simply because they look better than
numbers preceded by mode name as functions. Note that indent-tabs-mode is disabled, meaning that spaces
will be used instead.
|(setq pixel-scroll-precision-use-momentum t|pixel-scroll-precision-interpolate-page t|window-divider-default-right-width 1)|5 |(setopt indent-tabs-mode nil|delete-selection-mode t|electric-pair-mode t|global-auto-revert-mode t|pixel-scroll-precision-mode t10 |repeat-mode t|save-place-mode t|window-divider-mode t)
Way to insert backslashs on JIS keyboard.
|(define-key key-translation-map (kbd "¥") (kbd "\\"))
Spawn a shell process to update environment variables inside Emacs. Without this, our wrapper around
use-package which checks for appropriate executables won’t work right.
|(defun environment-flush ()|(with-temp-buffer|(call-process-shell-command|(format "%s -l -c 'env'" (getenv "SHELL")) nil t)5 |(goto-char (point-min))|(while (re-search-forward "^\\([^=]+\\)=\\(.*\\)$" nil t)|(when-let* ((name (match-string 1))|(value (match-string 2)))|(when (string= name "PATH")10 |(setenv name value)|(setq exec-path (split-string value path-separator)))))))
Macro for revealing my deepest secrets.
|(defmacro secret-get (host)|`(funcall (plist-get (car (auth-source-search :host ,host)) :secret)))
We then gather the cookies in third-party extension packages that are not managed by package.el and
process them. The heavy lifting of generating autoloads is taken care of by loaddefs-generate.
|(let* ((site (expand-file-name "core" user-emacs-directory))|(cookie (expand-file-name "core-autoloads.el" site)))|(loaddefs-generate site cookie))|5 |(require 'core-autoloads)
Set up Milkypostman’s Emacs Lisp package archive. I’m happy with the bleeding-edge repository.
|(with-eval-after-load 'package|(add-to-list 'package-archives|'("melpa" . "https://melpa.org/packages/")))
Finally we setup use-package, an abstraction layer for configuration and loading of packages. It is
further extended with the ability to conceal package declarations unless specific binary exists.
|(setq use-package-always-defer t|use-package-always-ensure t)||(eval-when-compile5 |(require 'use-package))||(define-advice use-package|(:around (orig package &rest body) use-with-binary)|(let ((executable (plist-get body :with)))10 |(when executable|(setq body (seq-difference body `(:with ,executable))))|(if (or (not executable) (executable-find executable))|(apply orig package body))))|15 |(with-eval-after-load 'use-package|(environment-flush))
Here contains the configuration and loading of extension packages, which are either shipped with Emacs or available in any of the repositories. They are declared in alphabetical order, which is not generally recommended considering loading priorities, etc. However it’s applicable here since I dump all of them.
|(use-package auctex|:with "luatex"|:init|(setq-default TeX-engine 'luatex)5 |(setq TeX-check-TeX nil|TeX-parse-self t|TeX-view-program-list '(("Preview" "open -a Preview %o"))))||(use-package avy10 |:bind (("C-: c" . avy-goto-char)|("C-: x" . avy-goto-char-timer)|("C-: l" . avy-goto-line)))||(use-package corfu15 |:hook (after-init . global-corfu-mode)|:init (setq corfu-auto t|corfu-cycle t|corfu-preselect 'prompt|corfu-quit-no-match 'separator)20 |:bind (:map corfu-map|([tab] . corfu-next)|([backtab] . corfu-previous)|([return] . corfu-send)|([escape] . corfu-quit)))25 ||(use-package dired|:ensure nil|:init (setq dired-use-ls-dired nil))|30 |(use-package ef-themes|:defer nil|:config (ef-themes-select 'ef-kassio))||(use-package eglot35 |:ensure nil|:init|(setq eglot-code-action-indications '(eldoc-hint))|(defun eglot-for-tab-command ()|(interactive)40 |(cond ((yas-active-snippets)|(yas-next-field-or-maybe-expand))|((save-excursion|(beginning-of-line)|(looking-at-p "[ \t]*$"))45 |(indent-for-tab-command))|(t (eglot-format)|(when (use-region-p)|(deactivate-mark)))))|:bind (:map eglot-mode-map50 |([tab] . eglot-for-tab-command)))||(use-package eldoc|:ensure nil|:init (setq eldoc-echo-area-display-truncation-message nil55 |eldoc-echo-area-use-multiline-p nil|eldoc-echo-area-prefer-doc-buffer 'maybe))||(use-package eww|:ensure nil60 |:hook (eww-after-render . eww-render-xslt)|:init|(defun eww-extract-xslt ()|(save-excursion|(goto-char (point-min))65 |(when (re-search-forward "<\\?xml-stylesheet [^>]*href=['\"]\\([^'\"]+\\)['\"]" nil t)|(let ((xslt (match-string 1))|(link (url-generic-parse-url (eww-current-url))))|(if (file-name-absolute-p xslt)|(progn70 |(setf (url-filename link) xslt)|(url-recreate-url link))|(let* ((path (file-name-directory (url-filename link)))|(xslt (expand-file-name xslt path)))|(setf (url-filename link) xslt)75 |(url-recreate-url link)))))))|(defun eww-render-xslt ()|(when (or (string-match "\\.xml$" (eww-current-url))|(save-excursion|(goto-char (point-min))80 |(re-search-forward "<\\?xml" nil t)))|(when-let* ((link (eww-extract-xslt))|(xslt (make-temp-file "eww" nil ".xsl"))|(xml (make-temp-file "eww" nil ".xml"))|(html (make-temp-file "eww" nil ".html"))85 |(command (format "xsltproc '%s' '%s' > '%s'" xslt xml html)))|(url-copy-file link xslt t)|(append-to-file nil nil xml)|(call-process-shell-command command nil nil)|(eww-open-file html)))))90 ||(use-package flymake|:ensure nil|:init (define-fringe-bitmap 'flymake-fringe-indicator|(vector #b0000000095 |#b00000000|#b00000000|#b00000000|#b00000000|#b00000000100 |#b00011100|#b00111110|#b00111110|#b00111110|#b00011100105 |#b00000000|#b00000000|#b00000000|#b00000000|#b00000000110 |#b00000000))|:config (setq flymake-indicator-type 'fringes|flymake-note-bitmap '(flymake-fringe-indicator compilation-info)|flymake-warning-bitmap '(flymake-fringe-indicator compilation-warning)|flymake-error-bitmap '(flymake-fringe-indicator compilation-error)))115 ||(use-package flyspell|:ensure nil|:init (setq ispell-program-name "aspell"))|120 |(use-package gptel|:config (setq gptel-backend (gptel-make-anthropic "claude"|:stream t|:key (secret-get "console.anthropic.com"))))|125 |(use-package magit)||(use-package marginalia|:hook (after-init . marginalia-mode))|130 |(use-package markdown-mode|:init (setq markdown-enable-math t|markdown-fontify-code-blocks-natively t|markdown-hide-urls t)|:config135 |(set-face-background 'markdown-code-face nil)|(set-face-underline 'markdown-line-break-face nil))||(use-package nxml-mode|:ensure nil140 |:config (add-to-list 'rng-schema-locating-files|(expand-file-name "schema/schemas.xml" user-emacs-directory)))||(use-package orderless|:init (setq completion-styles '(orderless basic)145 |orderless-matching-styles '(orderless-literal|orderless-prefixes|orderless-regexp)))||(use-package proof-general150 |:with "coqc"|:init (setq proof-splash-enable nil|proof-delete-empty-windows t))||(use-package sly155 |:with "sbcl"|:init (setq inferior-lisp-program "sbcl"))||(use-package swift-mode|:with "swift")160 ||(use-package treesit|:ensure nil|:defer nil|:init (setq treesit-language-unmask-alist '((c++ . cpp))165 |treesit-language-fallback-alist '((html-ts-mode . mhtml-mode))|treesit-language-source-alist '((bash . ("https://github.com/tree-sitter/tree-sitter-bash"))|(c . ("https://github.com/tree-sitter/tree-sitter-c"))|(cpp . ("https://github.com/tree-sitter/tree-sitter-cpp"))|(css . ("https://github.com/tree-sitter/tree-sitter-css"))170 |(html . ("https://github.com/tree-sitter/tree-sitter-html"))|(rnc . ("https://github.com/ldbeth/tree-sitter-rnc"))))|:config (dolist (grammar treesit-language-source-alist)|(let* ((language (or (car (rassq (car grammar) treesit-language-unmask-alist))|(car grammar)))175 |(derived (intern (concat (symbol-name language) "-ts-mode")))|(fallback (assq derived treesit-language-fallback-alist))|(default (or (cdr fallback)|(intern (concat (symbol-name language) "-mode")))))|(and (not (and fallback (not (cdr fallback))))180 |(fboundp derived)|(if (treesit-ready-p (car grammar) t)|(add-to-list 'major-mode-remap-alist|`(,default . ,derived))|(when (fboundp default)185 |(add-to-list 'major-mode-remap-alist|`(,derived . ,default))))))))||(use-package tuareg|:with "ocaml")190 ||(use-package vertico|:hook (after-init . vertico-mode))||(use-package wanderlust195 |:init|(define-mail-user-agent|'wl-user-agent|'wl-user-agent-compose|'wl-draft-send200 |'wl-draft-kill|'mail-send-hook)|(setq mail-user-agent 'wl-user-agent)|(with-eval-after-load 'wl-demo|(set-face-background 'wl-highlight-demo-face nil)))205 ||(use-package yasnippet|:hook (prog-mode . yas-minor-mode))
Wanderlust-specific settings are isolated in ~/.wl, which is automatically loaded during its start
up. The behavior of Mail.app is replicated, and the default summary interface is refined. To get X-Face
display working, you need to have x-face-e21.el installed and patched so it works on recent Emacs
versions.
|(setq user-mail-address "[email protected]"|user-full-name "Huang Jing")||(setq elmo-imap4-default-user user-mail-address5 |elmo-imap4-default-authenticate-type 'clear|elmo-imap4-default-server "imap.mail.me.com"|elmo-imap4-default-port 993|elmo-imap4-default-stream-type 'ssl|elmo-passwd-storage-type 'auth-source)10 ||(setq wl-expire-alist '(("^\\+trash$" (date 7) remove))|wl-from "Huang Jing <[email protected]>"|wl-fcc "%Sent Messages"|wl-fcc-force-as-read t15 |wl-local-domain "icloud.com"|wl-smtp-authenticate-type "plain"|wl-smtp-connection-type 'starttls|wl-smtp-posting-user user-mail-address|wl-smtp-posting-server "smtp.mail.me.com"20 |wl-smtp-posting-port 587|wl-temporary-file-directory "~/.wlt"|wl-summary-width nil|wl-summary-line-format "%n%T%P %W:%M/%D %h:%m %36(%t%[%c %f %]%) %s"|wl-thread-indent-level 225 |wl-thread-have-younger-brother-str "+"|wl-thread-youngest-child-str "+"|wl-thread-vertical-str " "|wl-thread-horizontal-str "-"|wl-thread-space-str " "30 |wl-message-id-domain "smtp.mail.me.com"|wl-message-ignored-field-list '(".")|wl-message-visible-field-list|'("^Subject:"|"^\\(To\\|Cc\\):"35 |"^\\(From\\|Reply-To\\):"|"^\\(Posted\\|Date\\):"|"^Organization:"|"^X-Face\\(-[0-9]+\\)?:")|wl-message-sort-field-list40 |'("^Subject"|"^\\(To\\|Cc\\)"|"^\\(From\\|Reply-To\\)"|"^\\(Posted\\|Date\\)"|"^Organization"45 |"^X-Face\\(-[0-9]+\\)?:")|wl-highlight-x-face-function 'x-face-decode-message-header)
The portable dumper is a subsystem that replaces the traditional unexec method of creating an Emacs
pre-loaded with Lisp code and data. This significantly improves Emacs startup and response time.
|(package-initialize)||(defconst dumped-load-mask nil)|(defconst dumped-load-path load-path)5 ||(with-temp-buffer|(insert-file-contents (concat user-emacs-directory "init.el"))|(goto-char (point-min))|(condition-case error10 |(while-let ((form (read (current-buffer))))|(pcase form|(`(use-package ,package . ,rest)|(unless (memq package dumped-load-mask)|(require package nil t)))))15 |(end-of-file nil)))||(load (concat user-emacs-directory "init.el"))||(defun dumped-init ()20 |(global-font-lock-mode t)|(transient-mark-mode t))||(add-hook 'emacs-startup-hook 'dumped-init)|25 |(dump-emacs-portable "Emacs.pdmp")