This is my emacs, there are many like it, but this one is mine…
I run Emacs 30.1 exclusively in the terminal (emacs -nw) on both
Linux and Mac. Startup speed matters. This configuration uses
use-package with global deferred loading and native compilation.
Remap Caps Lock to Control. Your pinkies will thank you.
git clone git@github.com:trevorbernard/emacs.d.git ~/.emacs.d cd ~/.emacs.d make setup
make setup installs all ELPA packages, downloads tree-sitter grammars,
and compiles the configuration. Requires Emacs 29+.
Start Emacs with emacs -nw. After editing configuration.org, run make
to tangle and recompile.
Everything here runs before the main configuration loads. The GC threshold is cranked up during init and restored at the end.
;;; -*- lexical-binding: t -*-
(setq process-adaptive-read-buffering nil
read-process-output-max (* 10 1024 1024))
(add-hook 'emacs-startup-hook
(lambda ()
(setq gc-cons-threshold 16777216
gc-cons-percentage 0.1)
(message "Emacs ready in %s with %d garbage collections."
(format "%.2f seconds" (float-time (time-subtract after-init-time before-init-time)))
gcs-done)))
(setq gc-cons-threshold most-positive-fixnum
gc-cons-percentage 0.9)
(set-face-attribute 'mode-line nil :background 'unspecified)
(set-face-attribute 'header-line nil :background 'unspecified)
(menu-bar-mode -1)
(defun display-startup-echo-area-message ())
(setq inhibit-startup-message t)
(when (native-comp-available-p)
(startup-redirect-eln-cache (expand-file-name "eln-cache/" user-emacs-directory))
(setq native-comp-speed 2
native-comp-jit-compilation t
native-comp-async-jobs-number (min 4 (max 1 (/ (num-processors) 2)))
native-comp-async-report-warnings-errors nil))
(setenv "LSP_USE_PLISTS" "true")
;; Bootstrap package system and use-package for better startup performance
(require 'package)
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")))
;; pin projectile to melpa-stable
(setq package-pinned-packages
'((projectile . "melpa-stable")))
(setq package-enable-at-startup nil)
(package-initialize)
(setq use-package-always-defer t
use-package-verbose nil ; Set to t for debugging, nil for performance
use-package-minimum-reported-time 0.1)
(require 'use-package)
(provide 'early-init);;; -*- lexical-binding: t -*-
(setq load-prefer-newer t)
(setq user-full-name "Trevor Bernard"
user-mail-address "trevor.bernard@pm.me")(use-package timu-spacegrey-theme
:vc (:url "https://github.com/trevorbernard/timu-spacegrey-theme" :rev :newest)
:demand t
:init
(setq timu-spacegrey-transparent-background t)
(add-to-list 'custom-theme-load-path
(expand-file-name "elpa/timu-spacegrey-theme" user-emacs-directory))
:hook
(after-init . (lambda () (load-theme 'timu-spacegrey t))))(use-package doom-modeline
:ensure t
:hook (after-init . doom-modeline-mode))(use-package rainbow-delimiters
:ensure t
:hook ((prog-mode . rainbow-delimiters-mode)))(when (eq system-type 'darwin)
(setq delete-by-moving-to-trash t)
(setq trash-directory "~/.Trash/emacs")
(setq dired-use-ls-dired nil))(unless (display-graphic-p)
(xterm-mouse-mode t)
(global-set-key (kbd "<wheel-up>") 'scroll-down-line)
(global-set-key (kbd "<wheel-down>") 'scroll-up-line)
(set-face-inverse-video 'vertical-border nil)
(set-face-background 'vertical-border (face-background 'default))
(set-display-table-slot standard-display-table 'vertical-border (make-glyph-code ?│))
(unless (fboundp 'x-hide-tip)
(defun x-hide-tip () nil))
(send-string-to-terminal "\e[1 q")
(add-hook 'kill-emacs-hook (lambda () (send-string-to-terminal "\e[5 q"))))Wayland needs explicit clipboard plumbing for copy/paste between Emacs and other applications. For terminal sessions over SSH, OSC-52 escape sequences let yanked text reach the host clipboard.
(when (getenv "WAYLAND_DISPLAY")
(setq tb/wl-copy-process nil)
(defun tb/wl-copy (text)
(setq tb/wl-copy-process (make-process :name "wl-copy"
:buffer nil
:command '("wl-copy" "-f" "-n")
:connection-type 'pipe
:noquery t))
(process-send-string tb/wl-copy-process text)
(process-send-eof tb/wl-copy-process))
(defun tb/wl-paste ()
(unless (and tb/wl-copy-process (process-live-p tb/wl-copy-process))
(shell-command-to-string "wl-paste -n | tr -d \r")))
(setq interprogram-cut-function 'tb/wl-copy)
(setq interprogram-paste-function 'tb/wl-paste))(use-package clipetty
:ensure t
:bind ("M-w" . clipetty-kill-ring-save))(setq
use-short-answers t
scroll-preserve-screen-position t
ring-bell-function 'ignore)
(global-auto-revert-mode t)
(column-number-mode 1)
(delete-selection-mode 1)
(show-paren-mode 1)(setq
make-backup-files nil
auto-save-default nil
create-lockfiles nil)(setq-default indent-tabs-mode nil)
(setq-default c-basic-offset 4)
(setq-default tab-width 8)
(setq-default fill-column 80)
(setq-default truncate-lines nil)(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'upcase-region 'disabled nil)(use-package ivy
:ensure t
:commands ivy-mode
:hook (after-init . ivy-mode))
(use-package counsel
:ensure t
:commands counsel-mode
:hook (ivy-mode . counsel-mode))(use-package projectile
:ensure t
:diminish projectile-mode
:custom
(projectile-project-search-path '("~/p/" "~/code/" "~/.emacs.d/"))
(projectile-completion-system 'ivy)
(projectile-enable-caching t)
(projectile-indexing-method 'alien)
(projectile-sort-order 'recently-active)
:bind-keymap ("C-c p" . projectile-command-map)
:bind (:map projectile-command-map
("C" . projectile-invalidate-cache))
:commands (projectile-find-file projectile-switch-project projectile-mode)
:config
(projectile-mode))(use-package company
:ensure t
:bind
(:map company-active-map
("C-n". company-select-next)
("C-p". company-select-previous)
("M-<". company-select-first)
("M->". company-select-last))
:hook (prog-mode . company-mode))M-x via C-x C-m per Steve Yegge’s Effective Emacs. Keeps your
fingers on the home row when Caps Lock is mapped to Control.
(keymap-global-set "C-x C-m" 'execute-extended-command)
(keymap-global-set "C-c C-m" 'execute-extended-command)(keymap-global-set "C-x g" 'magit-status)
(keymap-global-set "C-c g" 'magit-file-dispatch)(use-package prog-mode
:custom
(display-line-numbers-type 'relative)
:hook (prog-mode . (lambda ()
(setq-local show-trailing-whitespace t)
(display-line-numbers-mode))))(use-package magit
:ensure t
:commands (magit-status magit-file-dispatch))On Mac, ^-left and ^-right conflict with Mission Control. Disable
them in System Preferences > Keyboard > Shortcuts > Mission Control.
(use-package paredit
:ensure t
:bind
(:map paredit-mode-map
("C-<right>" . paredit-forward-slurp-sexp)
("C-<left>" . paredit-forward-barf-sexp)
("C-<backspace>" . paredit-backward-kill-word)
("RET" . nil))
:hook ((cider-repl-mode
clojure-mode
emacs-lisp-mode
eval-expression-minibuffer-setup
ielm-mode
inf-clojure-mode-hook
lisp-interaction-mode
lisp-mode
scheme-mode) . paredit-mode))(use-package evil
:ensure t
:commands evil-mode)(defun tb/python-ruff-setup ()
"Configure ruff as the Python checker unless in org-src-mode."
(unless (bound-and-true-p org-src-mode)
(when (buffer-file-name)
(flycheck-mode)
(setq-local flycheck-checkers '(python-ruff)))))
(use-package flycheck
:ensure t
:config
(flycheck-define-checker python-ruff
"A Python syntax and style checker using the ruff utility.
To override the path to the ruff executable, set
`flycheck-python-ruff-executable'.
See URL `http://pypi.python.org/pypi/ruff'."
:command ("ruff"
"check"
"--output-format=text"
(eval (when buffer-file-name
(concat "--stdin-filename=" buffer-file-name)))
"-")
:standard-input t
:error-filter (lambda (errors)
(let ((errors (flycheck-sanitize-errors errors)))
(seq-map #'flycheck-flake8-fix-error-level errors)))
:error-patterns
((warning line-start
(file-name) ":" line ":" (optional column ":") " "
(id (one-or-more (any alpha)) (one-or-more digit)) " "
(message (one-or-more not-newline))
line-end))
:modes (python-mode python-ts-mode))
:hook ((python-mode . tb/python-ruff-setup)
(rust-mode . flycheck-mode))
:bind (:map flycheck-mode-map
("M-n" . flycheck-next-error)
("M-p" . flycheck-previous-error)))(use-package flyspell
:commands (flyspell-mode flyspell-prog-mode)
:bind (:map flyspell-mouse-map
([down-mouse-3] . flyspell-correct-word)
([mouse-3] . undefined))
:hook (((org-mode markdown-mode) . flyspell-mode))
:config
(setq flyspell-issue-welcome-flag nil
flyspell-issue-message-flag nil
flyspell-mark-duplications-flag nil
ispell-program-name "aspell"
ispell-list-command "list"))Emacs 29+ has built-in tree-sitter support. This configures grammars for all languages and remaps traditional major modes to their tree-sitter equivalents.
(use-package treesit
:mode (("\\.tsx\\'" . tsx-ts-mode)
("\\.js\\'" . typescript-ts-mode)
("\\.mjs\\'" . typescript-ts-mode)
("\\.mts\\'" . typescript-ts-mode)
("\\.cjs\\'" . typescript-ts-mode)
("\\.ts\\'" . typescript-ts-mode)
("\\.jsx\\'" . tsx-ts-mode)
("\\.json\\'" . json-ts-mode)
("\\.yaml\\'" . yaml-ts-mode)
("\\.Dockerfile\\'" . dockerfile-ts-mode))
:preface
(defvar os/treesit-grammars-installed nil
"Cache variable to track if tree-sitter grammars have been checked/installed.")
(defvar os/treesit-grammar-cache-file
(expand-file-name "treesit-grammars-installed" user-emacs-directory)
"File to persist tree-sitter grammar installation status.")
(defun os/setup-install-grammars ()
"Install Tree-sitter grammars if they are absent.
Uses caching to avoid checking on every startup - only runs once per session
or when explicitly called interactively."
(interactive)
(when (and (fboundp 'treesit-available-p)
(treesit-available-p)
(or (called-interactively-p 'any)
(not os/treesit-grammars-installed)
(not (file-exists-p os/treesit-grammar-cache-file))))
(unless (boundp 'treesit-language-source-alist)
(setq treesit-language-source-alist nil))
(let ((grammars-to-install '())
(grammar-sources '((css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.20.0"))
(scss . ("https://github.com/serenadeai/tree-sitter-scss"))
(bash "https://github.com/tree-sitter/tree-sitter-bash")
(html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.20.1"))
(javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.21.2" "src"))
(java . ("https://github.com/tree-sitter/tree-sitter-java"))
(json . ("https://github.com/tree-sitter/tree-sitter-json" "v0.20.2"))
(python . ("https://github.com/tree-sitter/tree-sitter-python" "v0.20.4"))
(go "https://github.com/tree-sitter/tree-sitter-go" "v0.20.0")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
(make "https://github.com/alemuller/tree-sitter-make")
(elisp "https://github.com/Wilfred/tree-sitter-elisp")
(cmake "https://github.com/uyha/tree-sitter-cmake")
(c . ("https://github.com/tree-sitter/tree-sitter-c" "v0.20.7"))
(cpp "https://github.com/tree-sitter/tree-sitter-cpp")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
(tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src"))
(typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src"))
(yaml . ("https://github.com/ikatyang/tree-sitter-yaml" "v0.5.0"))
(rust . ("https://github.com/tree-sitter/tree-sitter-rust" "v0.24.1" "src"))
(just "https://github.com/IndianBoy42/tree-sitter-just")
(ruby "https://github.com/tree-sitter/tree-sitter-ruby"))))
(dolist (grammar grammar-sources)
(add-to-list 'treesit-language-source-alist grammar)
(unless (treesit-language-available-p (car grammar))
(push grammar grammars-to-install)))
(when grammars-to-install
(message "Installing %d missing tree-sitter grammars..." (length grammars-to-install))
(dolist (grammar grammars-to-install)
(condition-case err
(treesit-install-language-grammar (car grammar))
(error (message "Failed to install grammar %s: %s" (car grammar) err)))))
(setq os/treesit-grammars-installed t)
(with-temp-file os/treesit-grammar-cache-file
(insert (format "Last checked: %s\n" (current-time-string))))
(when (called-interactively-p 'any)
(message "Tree-sitter grammar check completed.")))))
(dolist (mapping
'((bash-mode . bash-ts-mode)
(c++-mode . c++-ts-mode)
(c-mode . c-ts-mode)
(c-or-c++-mode . c-or-c++-ts-mode)
(css-mode . css-ts-mode)
(java-mode . java-ts-mode)
(js-json-mode . json-ts-mode)
(js-mode . typescript-ts-mode)
(js2-mode . typescript-ts-mode)
(json-mode . json-ts-mode)
(python-mode . python-ts-mode)
(scss-mode . scss-ts-mode)
(sh-base-mode . bash-ts-mode)
(sh-mode . bash-ts-mode)
(ruby-mode . ruby-ts-mode)
(typescript-mode . typescript-ts-mode)))
(add-to-list 'major-mode-remap-alist mapping))
:config
(add-hook 'after-init-hook
(lambda () (run-with-idle-timer 2.0 nil #'os/setup-install-grammars))))Uses emacs-lsp-booster when available to speed up JSON parsing.
(use-package lsp-ivy
:ensure t
:after (lsp-mode ivy)
:commands lsp-ivy-workspace-symbol)
(use-package lsp-ui
:ensure t
:after lsp-mode
:commands lsp-ui-mode
:hook (lsp-mode . lsp-ui-mode)
:config
(setq lsp-ui-doc-enable nil))
(use-package lsp-mode
:ensure t
:commands (lsp lsp-deferred)
:hook ((tsx-ts-mode typescript-ts-mode js-ts-mode python-ts-mode java-ts-mode) . lsp-deferred)
:init
(setq lsp-log-io nil
lsp-use-plists t)
:config
(defun lsp-booster--advice-json-parse (old-fn &rest args)
(or
(when (equal (following-char) ?#)
(let ((bytecode (read (current-buffer))))
(when (byte-code-function-p bytecode)
(funcall bytecode))))
(apply old-fn args)))
(defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
(let ((orig-result (funcall old-fn cmd test?)))
(if (and (not test?)
(not (file-remote-p default-directory))
lsp-use-plists
(not (functionp 'json-rpc-connection))
(executable-find "emacs-lsp-booster"))
(progn
(when-let ((resolved (executable-find (car orig-result))))
(setcar orig-result resolved))
(message "Using emacs-lsp-booster for %s!" orig-result)
(cons "emacs-lsp-booster" orig-result))
orig-result)))
(advice-add (if (fboundp 'json-parse-buffer) 'json-parse-buffer 'json-read)
:around #'lsp-booster--advice-json-parse)
(advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command))(use-package combobulate
:vc (:url "https://github.com/mickeynp/combobulate" :rev :newest)
:custom
(combobulate-key-prefix "C-c o")
:hook ((python-ts-mode
tsx-ts-mode
typescript-ts-mode
js-ts-mode
css-ts-mode
yaml-ts-mode
json-ts-mode
go-ts-mode
html-ts-mode
toml-ts-mode) . combobulate-mode))
(use-package indent-bars
:vc (:url "https://github.com/jdtsmith/indent-bars" :rev :newest)
:hook ((python-ts-mode
tsx-ts-mode
typescript-ts-mode
js-ts-mode
css-ts-mode
yaml-ts-mode
json-ts-mode
go-ts-mode
html-ts-mode
toml-ts-mode
rust-ts-mode
java-ts-mode
c-ts-mode
c++-ts-mode
bash-ts-mode) . indent-bars-mode))(use-package yasnippet
:ensure t
:diminish yas-minor-mode
:commands (yas-minor-mode yas-global-mode)
:hook ((prog-mode . yas-minor-mode)
(org-mode . yas-minor-mode)))
(use-package ag
:ensure t
:commands (ag ag-project ag-regexp))
(use-package string-inflection
:ensure t
:commands (string-inflection-all-cycle))(use-package ielm
:hook ((ielm-mode . paredit-mode))
:bind
(:map ielm-map
("C-m" . ielm-return)
("<return>" . ielm-return)))Cider is pinned to melpa-stable to avoid bleeding-edge breakage.
(use-package clojure-mode
:ensure t
:hook (clojure-mode . subword-mode)
:custom
(clojure-align-forms-automatically t)
:config
(eldoc-add-command 'paredit-backward-delete 'paredit-close-round))
(use-package cider
:ensure t
:commands cider-jack-in
:bind ("C-c C-j" . cider-jack-in)
:custom
(nrepl-log-messages t)
(cider-repl-use-clojure-font-lock t)
(cider-repl-display-help-banner nil))(use-package rust-mode
:ensure t
:init
(setq rust-mode-treesitter-derive t))
(use-package rustic
:ensure t
:after (rust-mode)
:bind (:map rustic-mode-map
("M-j" . lsp-ui-imenu)
("M-?" . lsp-find-references)
("C-c C-c l" . flycheck-list-errors)
("C-c C-c a" . lsp-execute-code-action)
("C-c C-c r" . lsp-rename)
("C-c C-c q" . lsp-workspace-restart)
("C-c C-c Q" . lsp-workspace-shutdown)
("C-c C-c s" . lsp-rust-analyzer-status))
:custom
(rustic-compile-command "cargo b --release")
(rustic-default-clippy-arguments "--all-targets --all-features -- -D warnings")
(rust-format-on-save t)
(rustic-ansi-faces ["black" "#bf616a" "#a3be8c" "#ecbe7b" "#2257a0" "#b48ead" "#4db5bd" "white"]))C-j is rebound from org-return-indent to org-return because I
use C-j in place of the enter key everywhere.
(use-package ob-rust
:ensure t
:after org)
(use-package org
:bind
(:map
org-mode-map
("C-j" . org-return)
("C-c ]" . org-ref-insert-link)
("C-c l" . org-store-link)
("C-c a" . org-agenda)
("C-c c" . org-capture))
:hook (org-mode . auto-fill-mode)
:config
(set-face-attribute 'org-block nil :background 'unspecified)
(set-face-attribute 'org-block-begin-line nil :background 'unspecified)
(set-face-attribute 'org-block-end-line nil :background 'unspecified)
(org-babel-do-load-languages
'org-babel-load-languages '((rust . t)
(shell . t))))Exporting to PDF requires basictex:
brew reinstall --cask basictex
sudo tlmgr update --self
sudo tlmgr install wrapfig
sudo tlmgr install capt-of(use-package lsp-nix
:ensure lsp-mode
:after (lsp-mode)
:custom
(lsp-nix-nil-formatter ["nixfmt"]))
(use-package nix-mode
:ensure t
:hook (nix-mode . lsp-deferred))
(use-package nixfmt
:ensure t
:bind
(:map
nix-mode-map
("C-c C-f" . nixpkgs-fmt-buffer)))(use-package ruby-ts-mode
:mode "\\.rb\\'"
:mode "Rakefile\\'"
:mode "Gemfile\\'"
:custom
(ruby-indent-level 2)
(ruby-indent-tabs-mode nil))(use-package lsp-java
:ensure t
:after lsp-mode
:hook (java-ts-mode . lsp))
(use-package dap-java :after (lsp-java))(use-package terraform-mode
:ensure t
:hook (terraform-mode . lsp-deferred))(use-package js
:custom
(js-indent-level 2))
(use-package css-mode
:custom
(css-indent-level 2))
(use-package markdown-mode
:ensure t
:mode (("\\.md\\'" . gfm-mode)
("\\.markdown\\'" . gfm-mode))
:config
(set-face-attribute 'markdown-code-face nil :background 'unspecified)
(set-face-attribute 'markdown-pre-face nil :background 'unspecified)
(set-face-attribute 'markdown-inline-code-face nil :background 'unspecified))
(use-package csv-mode
:ensure t
:mode "\\.csv\\'")
(use-package dockerfile-mode
:ensure t
:commands dockerfile-mode)
(use-package yaml-mode
:ensure t
:commands yaml-mode)
(use-package bnf-mode
:ensure t
:mode "\\.bnf\\'")
(use-package dotenv-mode
:ensure t
:mode "\\.env\\'")
(use-package just-ts-mode
:ensure t
:hook (just-ts-mode . (lambda ()
(setq-local just-ts-indent-offset 2
tab-width 2))))
(use-package pest-mode
:ensure t
:mode "\\.pest\\'"
:hook (pest-mode . flycheck-mode))
(use-package flycheck-pest
:ensure t
:after pest-mode)The GC threshold is restored by emacs-startup-hook in early-init.