Skip to content

Latest commit

 

History

84 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

My Emacs Configuration

Hello. This is my Emacs configuration. It’s both documentation and code in one place, a paradigm referred to as Literate programming.

How it works

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")

Set up a package management system

Bootstrap use-package

(require 'package)

(add-to-list 'package-archives
    '("MELPA" .
      "http://melpa.org/packages/"))

(setq package-enable-at-startup nil)
(package-initialize)

(eval-when-compile
  (require 'use-package))

(setq use-package-always-ensure t)

(setenv "COPYFILE_DISABLE" "1")
(setq quelpa-update-melpa-p nil)
(use-package quelpa)
(use-package quelpa-use-package)

Emacs server (local + daemon)

Start a local Unix-socket server in interactive sessions so emacsclient (and agents) can open files / eval Lisp in the running Emacs. The macOS daemon path still uses TCP with EMACS_SERVER_AUTH_KEY via launchctl.

(defun shanemcd/start-emacs-server ()
  "Start Emacs server: TCP when daemonized, local socket otherwise."
  (require 'server)
  (unless (server-running-p)
    (if (daemonp)
        (progn
          (setq server-use-tcp t
                server-auth-key (getenv "EMACS_SERVER_AUTH_KEY")
                server-port 52698)
          (server-start)
          ;; Adjust "mac" if needed for your display configuration.
          (make-frame '((display . "mac"))))
      ;; Interactive / Linux GUI: default local socket under
      ;; /run/user/$UID/emacs/server (or /tmp/emacs$UID/server).
      (setq server-use-tcp nil)
      (server-start))))

(add-hook 'after-init-hook #'shanemcd/start-emacs-server)

;; For agents / emacsclient --eval: open FILE in another window and leave
;; the selected window (e.g. agent-shell) alone.
(defun shanemcd/display-file-other-window (file)
  "Visit FILE in another window without selecting it."
  (let ((buffer (find-file-noselect (expand-file-name file))))
    (display-buffer buffer
                    '((display-buffer-reuse-window
                       display-buffer-pop-up-window)
                      (inhibit-same-window . t)
                      (reusable-frames . nil)))
    nil))

Why am I doing this?

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
}

Other stuff that should run early

delight

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)

Install some packages

Text and programming modes

python-mode

(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")

markdown-mode

(use-package markdown-mode
  :config
  (setq markdown-fontify-code-blocks-natively t))

grip-mode (GitHub-flavored Markdown preview)

Uses go-grip for local offline GFM preview (no GitHub API rate limits). Toggle with C-c C-c g in Markdown buffers; preview opens in the browser and refreshes on save.

(use-package grip-mode
  :after markdown-mode
  :bind (:map markdown-mode-command-map
         ("g" . grip-mode))
  :config
  ;; Ensure ~/.local/bin (go-grip) is visible even if Emacs was started
  ;; before the binary was installed.
  (let ((local-bin (expand-file-name "~/.local/bin")))
    (add-to-list 'exec-path local-bin)
    (setenv "PATH" (concat local-bin path-separator (getenv "PATH"))))
  (setopt grip-command 'go-grip
          grip-preview-in-webkit nil
          grip-real-time-refresh nil))

show-paren-mode

(show-paren-mode)

yaml-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)

groovy-mode

(use-package groovy-mode
  :config
  (setq groovy-indent-offset 2))

swift

(use-package swift-mode)
(use-package ob-swift)

go

(use-package go-mode
  :config
  (add-hook 'go-mode-hook
            (lambda ()
              (setq indent-tabs-mode 1)
              (setq tab-width 8))))
(use-package ob-go)

rust-mode

(use-package rust-mode
  :mode "\\.rs\\'")

dockerfile-mode

(use-package dockerfile-mode)

ob-mermaid

(use-package ob-mermaid)

Org Mode

org-mode

(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))

ox-gfm

https://github.com/larstvei/ox-gfm

(use-package ox-gfm)

Terminal Emulation

vterm

(use-package vterm
  ;; :config
  ;; (setq vterm-tramp-shells '(("sshx" "/bin/zsh")))

)

eat

Terminal emulator from NonGNU/MELPA. The package ships Linux-compiled terminfo databases; on macOS/BSD those make Backspace insert spaces (and other keys misbehave). Recompile once per OS with eat-compile-terminfo; Linux can keep the prebuilt files.

(use-package eat
  :commands (eat eat-other-window)
  :config
  ;; MELPA terminfo is Linux-built; Darwin/BSD need a local recompile.
  ;; Stamp is per-system-type so a shared emacs.d across machines is fine.
  (when (memq system-type '(darwin berkeley-unix))
    (let ((stamp (expand-file-name
                  (format ".terminfo-compiled-%s" system-type)
                  eat-term-terminfo-directory)))
      (unless (file-exists-p stamp)
        (eat-compile-terminfo)
        (with-temp-file stamp (insert "ok\n"))))))

Interface Customizations

Smooth scrolling

(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))

Columns

(setq-default fill-column 80)

Disable Native UI Controls

(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)

Font

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)

Hide Splash Screen

(setq inhibit-splash-screen t)

Show column number in modeline

(setq column-number-mode t)

Show Line Numbers

;; (global-display-line-numbers-mode)

Theme

(use-package base16-theme)

Auto Dark Mode

Automatically switch between light and dark themes based on system appearance.

(use-package auto-dark
  :custom
  (auto-dark-themes '((base16-tomorrow-night) (base16-one-light)))
  (setq auto-dark-allow-osascript t)
  :init
  (auto-dark-mode))

Wind Move

https://www.emacswiki.org/emacs/WindMove

(when (fboundp 'windmove-default-keybindings)
  (windmove-default-keybindings))

Enable clicking in the fringe to toggle bookmarks

(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)

Increase spacing / padding of windows and frames

(use-package spacious-padding
  :config
  (spacious-padding-mode))

Start maximized

(toggle-frame-maximized)

Writing environment

(use-package olivetti)

Whitespace

(setq-default indent-tabs-mode nil tab-width 4)

(use-package sh-script
  :mode "\\.sh\\'"
  :config (setq sh-indentation 4 sh-basic-offset 4))

Require newlines at end of files

(setq require-final-newline t)

LLM

gptel

(use-package gptel
  :bind ("C-c a" . gptel-send)
  :init
  (eval-when-compile
    (require 'gptel nil t)
    (require 'gptel-anthropic nil t))
  :config
  (require 'gptel-anthropic)

  ;; Define a custom Vertex AI backend that doesn't send model in request body
  (cl-defstruct (gptel-vertex-anthropic (:constructor gptel--make-vertex-anthropic)
                                        (:copier nil)
                                        (:include gptel-anthropic)))

  ;; Override request building to exclude :model field
  (cl-defmethod gptel--request-data ((backend gptel-vertex-anthropic) prompts)
    (let ((data (cl-call-next-method)))
      ;; Remove :model from the plist since Vertex has it in the URL
      (cl-remf data :model)
      data))

  ;; Map Claude Code model aliases to Vertex AI model names
  (let* ((model-alias (or (getenv "ANTHROPIC_MODEL") "claude-sonnet-4-5@20250929"))
         (model-map '(("opusplan" . "claude-sonnet-4-5@20250929")
                      ("opus" . "claude-opus-4-6@20250514")
                      ("sonnet" . "claude-sonnet-4-5@20250929")
                      ("haiku" . "claude-3-5-haiku@20241022")))
         (model (or (cdr (assoc model-alias model-map)) model-alias))
         (region (or (getenv "CLOUD_ML_REGION") "us-east5"))
         (project (getenv "ANTHROPIC_VERTEX_PROJECT_ID")))
    (setq-default gptel-backend
                  (gptel--make-vertex-anthropic
                   :curl-args nil
                   :name "Claude via Vertex"
                   :host (format "%s-aiplatform.googleapis.com" region)
                   :header (lambda (_)
                             `(("Authorization" . ,(concat "Bearer " (gptel--get-api-key)))))
                   :key (lambda ()
                          (with-temp-buffer
                            (call-process "gcloud" nil t nil "auth" "print-access-token")
                            (string-trim (buffer-string))))
                   :models (list (intern model))
                   :protocol "https"
                   :endpoint (format "/v1/projects/%s/locations/%s/publishers/anthropic/models/%s:streamRawPredict"
                                     project region model)
                   :stream t
                   :request-params '(:anthropic_version "vertex-2023-10-16")
                   :url (format "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/anthropic/models/%s:streamRawPredict"
                                region project region model)))
    (setq-default gptel-model (intern model))))

claude

;; (quelpa '(claudemacs :fetcher github :repo "cpoile/claudemacs"))

gptel (Cursor models via open-cursor proxy)

Talks to a local OpenAI-compatible proxy on 127.0.0.1:32124 that shells out to the Cursor agent CLI (same approach as Nomadcxx/opencode-cursor).

Prerequisites:

  • agent login (Cursor Agent CLI)
  • systemctl --user enable --now open-cursor-proxy (serves /v1/chat/completions)
  • Optional: CURSOR_ACP_SESSION_RESUME=1 for incremental multi-turn via --resume
(use-package gptel
  :commands (gptel gptel-send gptel-menu)
  :bind ("C-c RET" . gptel-send)
  :config
  (setq gptel-model 'composer-2.5-fast
        gptel-backend
        (gptel-make-openai "Cursor"
          :host "127.0.0.1:32124"
          :protocol "http"
          :endpoint "/v1/chat/completions"
          :stream t
          :key "unused"
          :models '(composer-2.5-fast
                    composer-2.5
                    claude-4.6-sonnet-medium
                    gpt-5.4-medium-fast
                    gemini-3-flash
                    cursor-grok-4.5-high-fast
                    cursor-grok-4.5-high
                    cursor-grok-4.5-medium-fast
                    claude-4.6-opus-high
                    auto))))

agent-shell (Cursor + Claude/Vertex + Gemini via ACP)

Native Emacs shell for LLM agents over ACP. Loaded from a local checkout for development (xenodium/agent-shell); acp.el and shell-maker still come from MELPA.

Providers:

  • Cursoragent acp --approve-mcps (existing agent login)
  • Claudeclaude-agent-acp over Vertex AI (CLAUDE_CODE_USE_VERTEX + gcloud ADC; project/region from ~/.zshrc via chezmoi)
  • Geminigemini --acp over Vertex AI (GOOGLE_CLOUD_* / GEMINI_MODEL from /.zshrc via chezmoi). Antigravity (~agy) has no ACP yet, so we keep Gemini CLI for agent-shell.

Prerequisites on PATH (via exec-path-from-shell): agent, claude-agent-acp (npm i -g @agentclientprotocol/claude-agent-acp), gemini (npm i -g @google/gemini-cli).

Past conversations live as Markdown under .agent-shell/transcripts/. agent-recall indexes them so you can search, browse, and resume — no desktop-save-mode / agent-shell-desktop required. Prefer session/load so resumed buffers get prior messages back.

;; Dependencies still from MELPA (not vendored in the local checkout).
(use-package acp :ensure t)
(use-package shell-maker :ensure t)

(use-package agent-shell
  :ensure nil
  :load-path "~/github/xenodium/agent-shell"
  :demand t
  :bind (("C-c A" . agent-shell-cursor-start-agent)
         ("C-c C" . agent-shell-anthropic-start-claude-code)
         ("C-c G" . agent-shell-google-start-gemini))
  :config
  ;; Cursor — `agent login` outside Emacs; no ACP authenticate request.
  (setq agent-shell-cursor-authentication
        (agent-shell-cursor-make-authentication :none t)
        agent-shell-cursor-acp-command '("agent" "acp" "--approve-mcps"))

  ;; Claude — Vertex via env from ~/.zshrc (CLAUDE_CODE_USE_VERTEX, etc.).
  (setq agent-shell-anthropic-authentication
        (agent-shell-anthropic-make-authentication :login t))

  ;; Gemini — Vertex via env from ~/.zshrc (chezmoi); model via GEMINI_MODEL.
  (setq agent-shell-google-authentication
        (agent-shell-google-make-authentication :vertex-ai t)
        agent-shell-google-gemini-acp-command '("gemini" "--acp"))

  ;; Environments are set after exec-path-from-shell (later in this file).

  (setq agent-shell-preferred-agent-config '(preselect . cursor)
        agent-shell-session-strategy 'prompt
        ;; Use session/load (not session/resume) so history is restored.
        agent-shell-prefer-session-resume nil)

  ;; Typing anywhere in the transcript jumps to the prompt (like chat UIs).
  ;; Note: n/p/r stay navigation/quote when not at the prompt.
  (defun shanemcd/agent-shell-self-insert (&optional n)
    "Insert N characters at the prompt, moving point there if needed."
    (interactive "p")
    (unless (shell-maker-point-at-last-prompt-p)
      (goto-char (point-max)))
    (self-insert-command (or n 1)))
  (define-key agent-shell-mode-map [remap self-insert-command]
              #'shanemcd/agent-shell-self-insert))

;; Search / browse / resume agent-shell transcripts.
;; Local checkout of https://github.com/shanemcd/agent-recall
;; (branch fix/session-id-and-resume-loop) until upstream merges.
;; Keys: C-c B browse, C-c R resume, C-c / live search.
(use-package agent-recall
  :ensure nil
  :load-path "~/github/shanemcd/agent-recall"
  :after agent-shell
  :hook (agent-shell-mode . agent-recall-track-sessions)
  :bind (("C-c B" . agent-recall-browse)
         ("C-c R" . agent-recall-resume)
         ("C-c /" . agent-recall-search-live))
  :config
  (setq agent-recall-search-paths '("~/github" "~/projects" "~/.emacs.d")
        agent-recall-extra-transcript-dirs
        '((:dir "~/.agent-shell/transcripts/"))
        agent-recall-search-function 'consult-ripgrep
        agent-recall-browse-sort 'modified-desc))

;; Persistent project-aware sidebar for agent-shell.
;; https://github.com/cmacrae/agent-shell-sidebar
;; Keys: C-c s toggle, C-c f focus (C-c a is already ai-code-menu).
(use-package agent-shell-sidebar
  :ensure nil
  :quelpa (agent-shell-sidebar :fetcher github
                               :repo "cmacrae/agent-shell-sidebar")
  :after agent-shell
  :bind (("C-c s" . agent-shell-sidebar-toggle)
         ("C-c f" . agent-shell-sidebar-toggle-focus))
  :custom
  (agent-shell-sidebar-width "30%")
  (agent-shell-sidebar-minimum-width 80)
  (agent-shell-sidebar-maximum-width "50%")
  (agent-shell-sidebar-position 'right)
  (agent-shell-sidebar-locked t)
  :config
  (setq agent-shell-sidebar-default-config
        (agent-shell-cursor-make-agent-config)))

ai-code (unified AI coding menu)

ai-code-interface.el — transient menu for context-aware coding actions. Uses agent-shell as the backend. Menu is C-c a; start Cursor/Claude/Gemini shells with C-c A / C-c C / C-c G.

(use-package ai-code
  :ensure t
  :after agent-shell
  :bind ("C-c a" . ai-code-menu)
  :config
  (ai-code-set-backend 'agent-shell)
  (with-eval-after-load 'magit
    (ai-code-magit-setup-transients)))

To be filed or tossed away at some point…

browse-at-remote

(use-package browse-at-remote
  :commands browse-at-remote
  :bind ("C-c g g" . browse-at-remote))

company

(use-package company
  :delight
  :config
  (setq company-minimum-prefix-length 1)
  (global-company-mode t)
  (setq company-global-modes '(not org-mode)))

dired

  • 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 "\\|^\\..+$"))
dired-subtree
(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))

drag-stuff

(use-package drag-stuff
  :config
  (drag-stuff-define-keys))

dumb-jump

(use-package dumb-jump
  :config
  ;(setq dumb-jump-selector 'helm)
  (add-hook 'xref-backend-functions #'dumb-jump-xref-activate))

fill-column-indicator

(use-package fill-column-indicator
  :config
  (setq-default fci-rule-column 80)
  (setq fci-rule-color (face-attribute 'highlight :background)))

flycheck

(use-package flycheck
  :delight
  :config
  ;; Silence the once-per-mode "no syntax checker can run here" notice in
  ;; lisp-interaction-mode buffers. Their only checker (the emacs-lisp byte
  ;; compiler) is disabled there by Emacs 30+ trusted-content rules
  ;; (CVE-2024-53920), so Flycheck has nothing to do in those buffers.
  (setq flycheck-global-modes '(not lisp-interaction-mode))
  (global-flycheck-mode))

flyspell

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))

htmlize

(use-package htmlize
  :commands (htmlize-buffer
	     htmlize-file
	     htmlize-many-files
	     htmlize-many-files-dired
	     htmlize-region))

imenu-list

(use-package imenu-list
  :bind
  (("C-." . imenu-list-smart-toggle))
  :config
  (setq imenu-list-focus-after-activation t))

tramp

(use-package tramp
  :defer t
  :config
  (setopt tramp-remote-path '(tramp-own-remote-path))
  (setq tramp-histfile-override nil))

magit

(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))

mwim

“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))

nerd-icons

(use-package nerd-icons)

(use-package nerd-icons-dired
  :delight
  :hook
  (dired-mode . nerd-icons-dired-mode))

rainbow-delimeters

(use-package rainbow-delimiters
  :config
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode))

reveal-in-osx-finder

(use-package reveal-in-osx-finder)

vundo

(use-package vundo
  :commands (vundo)
  :config
  (setq vundo-compact-display t))

ws-butler

(use-package ws-butler
  :delight
  :config
  (ws-butler-global-mode t))

wttrin

(use-package wttrin
  :config
  (setq wttrin-default-cities '("New York NY" "Winchester VA" "Durham NC" "Kaohsiung City")))

ztree

(use-package ztree)

yassnippet

(use-package yasnippet
  :commands yas-minor-mode
  :hook (go-mode . yas-minor-mode))

winum

(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))
  )

pandoc-mode

(use-package pandoc-mode
  :config
  (add-hook 'markdown-mode-hook 'pandoc-mode))

marp-mode

;; Use Emacs 30's built-in :vc support instead of quelpa
(use-package marp-mode
  :vc (:url "https://github.com/shanemcd/marp-mode"
       :branch "main")
  :hook (markdown-mode . marp-mode))

hide-mode-line

(use-package hide-mode-line)
(setq backup-directory-alist `(("." . "~/.emacs.bak")))

(setenv "PINENTRY_USER_DATA" "USE_CURSES=0")

Set up the PATH

(use-package exec-path-from-shell
  :config
  (setq exec-path-from-shell-arguments '("-li"))
  (setenv "SHELL" "/bin/zsh")
  (exec-path-from-shell-copy-envs '("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"
                                    "GOOGLE_CLOUD_PROJECT"
                                    "GOOGLE_CLOUD_LOCATION"
                                    "GOOGLE_GENAI_USE_VERTEXAI"
                                    "GEMINI_MODEL"
                                    "GEMINI_SANDBOX"
                                    "SANDBOX_FLAGS"
                                    "CURSOR_ACP_SESSION_RESUME"))
  ;; agent-shell is loaded earlier; snapshot env now that PATH/Vertex vars exist.
  (when (fboundp 'agent-shell-make-environment-variables)
    (setq agent-shell-cursor-environment
          (agent-shell-make-environment-variables :inherit-env t)
          agent-shell-anthropic-claude-environment
          (agent-shell-make-environment-variables :inherit-env t)
          agent-shell-google-gemini-environment
          (agent-shell-make-environment-variables :inherit-env t))))

Proving ground

Trying to piece together a more modern completion system

marginalia

;; 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)))))

consult

;; 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)

embark

;; 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))

vertico

(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)))

savehist

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

emacs

;; 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))

orderless

;; 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)))))

wgrep

(use-package wgrep)

eglot

(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))

smart-mode-line

(use-package smart-mode-line
  :init
  (sml/setup))

Misc

sudo-edit

(use-package sudo-edit)

Miscellaneous configuration

Swap super and meta keys on macOS

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))

Kill all buffers except the current one

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))))

Increase resolution of rendered PDFs

(setq doc-view-resolution 300)

Custom font utilities

(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)))

Ensure HOME is not a symlink

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)))

About

My emacs config

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages