Skip to content

SeanMooney/emacs

Repository files navigation

Emacs: A Literate Configuration

This is a literate Emacs configuration file written in Org mode. The primary goal is to create a setup that is well-documented, modular, and easy to maintain. All configuration is tangled into `init.el` upon saving this file.

Early Init

This section contains critical setup that must run at the very beginning of the Emacs startup process, before any packages are loaded.

Early UI Tweaks

These settings disable distracting UI elements and set up fundamental frame behavior to ensure a clean and predictable launch.

;; Set frame behavior early
(setq frame-resize-pixelwise t
      frame-inhibit-implied-resize t)

;; Disable UI elements for a minimal appearance
(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(menu-bar-mode -1)
(set-fringe-mode 10) ; A little breathing room

;; Inhibit startup screens and messages for a faster, quieter launch
(setq inhibit-splash-screen t
      inhibit-startup-screen t
      inhibit-x-resources t
      inhibit-startup-echo-area-message user-login-name
      inhibit-startup-buffer-menu t
      inhibit-startup-message t)

;; Set up the visible bell instead of an audible one
(setq visible-bell t)

;; Use short 'y/n' answers instead of 'yes/no'
(defalias 'yes-or-no-p 'y-or-n-p)

;; Reduce native compilation noise
(when (featurep 'native-compile)
  (setq native-comp-async-report-warnings-errors nil))

;; Ensure Emacs uses the proper PATH from bash shell
(defun update-path-from-bash ()
  "Update PATH and exec-path from bash shell environment."
  (let ((path-from-bash (shell-command-to-string "bash -i -c 'echo $PATH'")))
    (setenv "PATH" (string-trim path-from-bash))
    (setq exec-path (split-string (getenv "PATH") path-separator))))

(update-path-from-bash)

Package Management (straight.el)

I use `straight.el` for package management, which works directly with Git repositories. It is paired with `use-package` for declarative configuration. This block bootstraps `straight.el` if it’s not already installed.

(setq straight-use-package-by-default t)
;; Ensure we can get packages from nongnu.org
(custom-set-variables
 '(straight-recipe-overrides '((nil
                              (nongnu-elpa :type git
                                           :repo "https://github.com/emacsmirror/nongnu_elpa"
                                           :depth (full single-branch)
                                           :local-repo "nongnu-elpa"
                                           :build nil)))))

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el"
                         (or (bound-and-true-p straight-base-dir)
                             user-emacs-directory)))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
        (goto-char (point-max))
        (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-use-package 'use-package)
(setq use-package-always-ensure t)

;; Diminish hides or shortens minor mode names in the mode line
(straight-use-package 'diminish)

;; GCMH - Garbage Collector Magic Hack for better performance
(use-package gcmh
  :config
  (gcmh-mode 1))

Early Org Mode Setup

This configures Org mode basics, especially the function to automatically tangle this configuration file (`Emacs.org` -> `init.el`) every time it is saved.

(setq org-support-shift-select t)

;; Automatically tangle our Emacs.org config file when we save it
(defun efs/org-babel-tangle-config ()
  "Auto-tangle config file on save."
  (condition-case err
      (when (and (buffer-file-name)
                 (string-equal (file-name-directory (buffer-file-name))
                               (expand-file-name user-emacs-directory)))
        ;; Dynamic scoping to the rescue
        (let ((org-confirm-babel-evaluate nil))
          (org-babel-tangle)))
    (error
     (message "Error tangling config: %s" (error-message-string err)))))

(add-hook 'org-mode-hook
          (lambda ()
            (add-hook 'after-save-hook #'efs/org-babel-tangle-config nil 'local)))


(with-eval-after-load 'org
  (require 'org-tempo) ;; Needed as of Org 9.2 for structure templates
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python")))

Core Emacs Behavior

This section configures the fundamental, non-UI behavior of Emacs, from user information to editing enhancements and file handling.

Core Emacs Configuration

Consolidated configuration for user settings, file handling, editing behavior, and built-in features.

(use-package emacs
  :ensure nil
  :bind (("M-o" . other-window)
         ("M-j" . duplicate-dwim)
         ("RET" . newline-and-indent)
         ;; Unbind some keys to use for other purposes
         ("C-z" . nil)
         ("C-x C-z" . nil)
         ("C-x C-k RET" . nil))
  :custom
  ;; User information
  (user-full-name "Sean Mooney")
  (user-mail-address "sean@seanmooney.info")
  ;; Use UTF-8 everywhere
  (coding-system-for-read 'utf-8)
  (coding-system-for-write 'utf-8)
  (ad-redefinition-action 'accept)
  ;; Don't create lockfiles
  (create-lockfiles nil)
  ;; Disable backup files
  (make-backup-files nil)
  (backup-inhibited t)
  ;; Disable auto-save files (#filename#)
  (auto-save-default nil)
  (auto-save-mode nil)
  ;; Editing behavior
  (completion-ignore-case t)
  (completions-detailed t)
  ;; Highlight the current line in programming, text, and org modes.
  (global-hl-line-mode t)
  ;; When pasting, overwrite the currently selected region.
  (delete-selection-mode 1)
  (help-window-select t)
  ;; Don't store duplicate entries in the kill ring
  (kill-do-not-save-duplicates t)
  ;; Default width for text wrapping
  (fill-column 80)
  (column-number-mode 1)
  ;; Completion settings
  (completions-max-height 15)
  (tab-always-indent 'complete)
  :config
  ;; Font lock (syntax highlighting)
  (global-font-lock-mode 1)
  (setq font-lock-maximum-decoration t))

Additional Editing Packages

Additional packages that enhance the text editing experience beyond built-in functionality.

;; Enable line numbers for modes where it's most useful.
(dolist (mode '(text-mode-hook
                prog-mode-hook
                conf-mode-hook))
  (add-hook mode #'display-line-numbers-mode))

;; But disable them for modes where they are distracting.
(dolist (mode '(org-mode-hook
                term-mode-hook
                shell-mode-hook
                treemacs-mode-hook
                eshell-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode -1))))


;; Automatically pair delimiters like parentheses and quotes.
(use-package elec-pair
  :ensure nil
  :hook (after-init . electric-pair-mode)
  :config
  ;; A handy command for deleting a pair of surrounding delimiters.
  (global-set-key (kbd "C-c d") #'delete-pair)
  (setq delete-pair-blink-delay 0.0))

;; Visually highlight matching parentheses.
(use-package paren
  :ensure nil
  :hook (after-init . show-paren-mode)
  :custom
  (show-paren-style 'mixed)
  (show-paren-context-when-offscreen t))

;; Allows repeating commands with C-x z.
(use-package repeat
  :config
  (repeat-mode 1))

;; Color-code matching delimiters for better code readability
(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

;; Move text (lines or regions) up and down
(use-package move-text
  :bind (("M-<up>" . move-text-up)
         ("M-<down>" . move-text-down))
  :config
  (move-text-default-bindings))

;; Multiple cursors for simultaneous editing
(use-package multiple-cursors
  :bind (("C-S-c C-S-c" . mc/edit-lines)                    ; Add cursor to each line in region
         ("C->" . mc/mark-next-like-this)                   ; Mark next occurrence
         ("C-<" . mc/mark-previous-like-this)               ; Mark previous occurrence
         ("C-c C-<" . mc/mark-all-like-this)                ; Mark all occurrences
         ("C-S-<mouse-1>" . mc/add-cursor-on-click))        ; Add cursor with mouse
  :config
  ;; Don't warn about commands that haven't been used with multiple cursors
  (setq mc/always-run-for-all t))

File Handling & Saving

This configures how Emacs handles files, symlinks, and saving state.

(use-package files
  :ensure nil
  :straight (:type built-in)
  :custom
  ;; Prefer newer versions of files when loading Lisp code.
  (load-prefer-newer t)
  ;; Don't warn me about large files. I know what I'm doing.
  (large-file-warning-threshold nil)
  ;; When visiting a file, resolve symlinks to the true path.
  (find-file-visit-truename t))

;; Remember the cursor position in files between sessions.
(use-package saveplace
  :ensure nil
  :hook (after-init . save-place-mode))

;; Remember minibuffer history between sessions.
(use-package savehist
  :ensure nil
  :hook (after-init . savehist-mode)
  :custom (history-length 300))

;; Remember recently opened files.
(use-package recentf
  :ensure nil
  :hook (after-init . recentf-mode)
  :custom
  (recentf-max-saved-items 100)
  (recentf-exclude '("/tmp/" "/ssh:" "/sudo:" "\\.git/")))

;; Automatically revert file buffers when they change on disk.
(use-package autorevert
  :ensure nil
  :custom
  (auto-revert-interval 1)                    ; Check every second
  (auto-revert-check-vc-info t)              ; Also check version control info
  (auto-revert-verbose t)                    ; Show messages when reverting
  (global-auto-revert-non-file-buffers t)   ; Also revert non-file buffers like Dired
  (auto-revert-avoid-polling nil)            ; Use file notifications when available
  :config
  (global-auto-revert-mode 1))

Persistent Undo

This setup enables undo-tree-mode, a more powerful way of handling undo/redo that visualizes the history as a tree. More importantly, it configures Emacs to save the undo history of files to a dedicated directory (~/.config/emacs/undo/), so you can undo changes even after closing and reopening a file.

(use-package undo-tree
  :hook (after-init . global-undo-tree-mode)
  :bind (("C-z" . undo-tree-undo)
         ("C-S-z" . undo-tree-redo))
  :custom
  ;; Save undo history across sessions
  (undo-tree-auto-save-history t)
  ;; Create the undo directory if it doesn't exist
  (undo-tree-history-directory-alist
   `(("." . ,(expand-file-name "undo/" user-emacs-directory))))
  ;; Increase the amount of history stored
  (undo-tree-buffer-size-limit (* 1024 1024 8)) ; 8MB
  (undo-tree-max-history-size 1000)
  :config
  ;; Unbind C-/ from undo-tree to allow our comment binding
  (define-key undo-tree-map (kbd "C-/") nil))

Version Control

Settings for Emacs’s built-in version control integration.

(use-package vc
  :ensure nil
  :custom
  ;; VC should follow symbolic links.
  (vc-follow-symlinks t))

Version Control (Magit)

settings for magit for more powerful git integration

(use-package magit
  :bind (("C-x g" . magit-status)
         ("C-x M-g" . magit-dispatch))
  :custom
  (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))

;; Show git diff indicators in the fringe
(use-package diff-hl
  :hook ((prog-mode . diff-hl-mode)
         (dired-mode . diff-hl-dired-mode))
  :config
  ;; Integration with magit - refresh diff-hl when magit updates
  (with-eval-after-load 'magit
    (add-hook 'magit-pre-refresh-hook #'diff-hl-magit-pre-refresh)
    (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)))

User Interface

This section covers all visual aspects of Emacs, from fonts and colors to window layouts and completion UIs.

Fonts (Fontaine)

I use the `fontaine` package to easily switch between predefined font configurations. My default is `Source Code Pro` for code and `FiraGO` for proportional text.

(use-package fontaine
  :demand t
  :init
  (setq fontaine-latest-state-file
        (locate-user-emacs-file "fontaine-latest-state.eld"))
  (setq fontaine-presets
        '((small
           :default-height 90)
          (regular
           :default-height 120)
          (medium
           :default-weight semilight
           :default-height 140)
          (large
           :default-weight semilight
           :default-height 180
           :bold-weight extrabold)
          (dyslexia-friendly
           :default-family "OpenDyslexic"
           :variable-pitch-family "OpenDyslexic"
           :default-height 130
           :variable-pitch-height 1.1)
          (t ; our shared fallback properties
           :default-family "Source Code Pro"
           :default-weight semilight
           :default-height 100
           :variable-pitch-family "FiraGO"
           :variable-pitch-weight normal
           :variable-pitch-height 1.05
           :bold-weight bold
           :italic-slant italic)))
  :bind ("C-c f" . fontaine-set-preset))

;; Improve line spacing for better readability
(setq-default line-spacing 0.2)

;; Pulsar briefly highlights the current line after certain commands
;; Excellent accessibility feature for tracking cursor movement
(use-package pulsar
  :config
  (pulsar-global-mode 1)
  :custom
  ;; Highlight line after these commands for better cursor tracking
  (pulsar-pulse-functions '(isearch-repeat-forward
                            isearch-repeat-backward
                            recenter-top-bottom
                            move-to-window-line-top-bottom
                            reposition-window
                            bookmark-jump
                            other-window
                            delete-window
                            delete-other-windows
                            forward-page
                            backward-page
                            scroll-up-command
                            scroll-down-command
                            windmove-right
                            windmove-left
                            windmove-up
                            windmove-down)))

Theming (ef-themes)

I use the `ef-themes` collection by Protesilaos Stavrou for its excellent contrast and beautiful color palettes. I define a dark (`ef-cherie`) and light (`ef-summer`) theme to toggle between.

(use-package ef-themes
  :config
  ;; Define the pair of themes to toggle between.
  (setq ef-themes-to-toggle '(ef-cherie ef-summer))
  ;; Disable all other themes to avoid awkward blending.
  (mapc #'disable-theme custom-enabled-themes)
  ;; Load the default dark theme.
  (load-theme 'ef-cherie :no-confirm))

Frame and Window Management

These settings control the appearance of the Emacs frame, windows, and how they are split.

;; Enable smooth, pixel-based scrolling.
(setq pixel-scroll-precision-mode t)
(setq pixel-scroll-precision-use-momentum nil)

;; Add a hint of transparency and maximize the frame on startup.
(set-frame-parameter (selected-frame) 'alpha-background 93)
(add-to-list 'default-frame-alist '(alpha-background . 93))
(set-frame-parameter (selected-frame) 'fullscreen 'maximized)
(add-to-list 'default-frame-alist '(fullscreen . maximized))

;; Improve display characters in terminal mode.
(set-display-table-slot standard-display-table 'vertical-border ?\u2502)
(set-display-table-slot standard-display-table 'truncation ?\u2192)

;; Custom function to toggle a 2-window split between vertical and horizontal.
(defun toggle-window-split ()
  "Switch between horizontal and vertical split window layout."
  (interactive)
  (if (= (count-windows) 2)
      (let* ((other-win (next-window))
             ;; Is the split vertical? (i.e. do windows share a left edge)
             (is-vertical-split (= (nth 0 (window-edges))
                                   (nth 0 (window-edges other-win)))))
        ;; Delete the other window, which collapses the split
        (delete-other-windows)
        ;; And re-split in the other direction
        (if is-vertical-split
            (split-window-horizontally)
          (split-window-vertically)))
    (message "This command only works when there are exactly two windows.")))
(global-set-key (kbd "C-c j") #'toggle-window-split)

Minibuffer & Completion Framework

I use a modern completion system composed of several packages that work together.

  • vertico provides the core vertical minibuffer UI.
  • marginalia adds rich annotations (file permissions, command docs) to completions.
  • orderless enables powerful out-of-order matching.
  • consult enhances built-in commands like `find-file` and `switch-to-buffer` with previews.
  • corfu provides an in-buffer completion popup.
(use-package vertico
  :init (vertico-mode)
  :custom
  (vertico-cycle t)
  (vertico-resize nil))

(use-package marginalia
  :after vertico
  :init (marginalia-mode))

(use-package orderless
  :custom
  (completion-styles '(orderless flex basic))
  (completion-category-overrides '((file (styles basic partial-completion)))))

(use-package corfu
    :hook (prog-mode . corfu-mode)
    :custom
    (corfu-auto nil)
    (corfu-auto-delay 0.1)
    (corfu-quit-no-match 'separator)
    ;; Disable corfu in modes where it's disruptive
    (corfu-mode-modes '(not eshell-mode shell-mode term-mode))
    :init
    (global-corfu-mode))

;; Adds more completion sources (backends) for Corfu
(use-package cape
  :init
  (add-to-list 'completion-at-point-functions #'cape-file))

(use-package consult
  :bind (("C-x f" . consult-find)
         ("M-s M-o" . consult-outline)
         ("C-f" . consult-line)
         ("C-x b" . consult-buffer) ; a powerful switch-to-buffer
         ("C-j" . consult-imenu)
         ("C-x p b" . consult-project-buffer)
         ("M-y" . consult-yank-pop)
         ("M-g g" . consult-goto-line)
         ("C-c m" . consult-man)
         ("C-c i" . consult-info)
         ("C-c h" . consult-history)
         ("M-s c" . consult-locate)
         ("M-s g" . consult-grep)
         ("M-s G" . consult-git-grep)
         ("M-s r" . consult-ripgrep)
         ;; Isearch integration
         ("M-s e" . consult-isearch-history)
         :map isearch-mode-map
         ("M-e" . consult-isearch-history)
         ("M-s e" . consult-isearch-history)
         ("M-s l" . consult-line)
         ("M-s L" . consult-line-multi))
  :init
  ;; Add consult bindings to org-mode and org-agenda
  (with-eval-after-load "org"
    (keymap-set org-mode-map "C-j" #'consult-org-heading))
  (with-eval-after-load "org-agenda"
    (keymap-set org-agenda-mode-map "C-j" #'consult-org-agenda))
  :config
  (setq consult-line-start-from-top nil)
  ;; Integrate with xref for "find definitions/references"
  (with-eval-after-load "xref"
    (require 'consult-xref)
    (setq xref-show-xrefs-function #'consult-xref)
    (setq xref-show-definitions-function #'consult-xref)))

Dired (File Manager)

Configuration for Dired, Emacs’s built-in file manager.

(use-package dired
  :straight (:type built-in)
  :ensure nil
  :hook ((dired-mode . hl-line-mode)
         (dired-mode . dired-hide-details-mode))
  :custom
  (dired-listing-switches "-alFh") ; ls-like output
  (dired-dwim-target t)            ; Smart target for copying/renaming
  (dired-recursive-copies 'always)
  (dired-recursive-deletes 'always))

;; dired-x provides extra functionality for dired
(use-package dired-x
  :ensure nil
  :straight (:type built-in)
  :after dired
  :bind (("C-x C-j" . dired-jump))         ; Jump to dired of current file
  :custom
  ;; Only omit system/backup files, not regular dot files
  (dired-omit-files "^\\.\\.$\\|\\.DS_Store$\\|\\.localized$\\|~$\\|#.*#$")
  (dired-guess-shell-gnutar "tar"))


;; Ranger-style file browser with three-pane layout and previews
(use-package ranger
  :bind (("C-x r d" . ranger)
         ("C-x r j" . deer))          ; Minimal ranger mode
  :custom
  (ranger-cleanup-eagerly t)          ; Clean up ranger buffers
  (ranger-cleanup-on-disable t)
  (ranger-show-dotfiles t)
  (ranger-preview-file t)             ; Show file previews
  (ranger-max-preview-size 10)        ; Limit preview to 10MB files
  :config
  ;; Don't show hidden files by default (toggle with zh)
  (setq ranger-show-hidden nil))

;; Modern icons for dired
(use-package nerd-icons-dired
  :after (dired nerd-icons)
  :hook (dired-mode . nerd-icons-dired-mode))

Ibuffer (Buffer Manager)

I use Ibuffer to manage open buffers, with custom groups to keep things organized.

(use-package ibuffer
  :ensure nil
  :bind ("C-x C-b" . ibuffer)
  :custom
  (ibuffer-show-empty-filter-groups nil)
  (ibuffer-saved-filter-groups
   '(("default"
      ("org" (or (mode . org-mode) (name . "^\\*Org Src")))
      ("emacs" (or (name . "^\\*scratch\\*$") (name . "^\\*Messages\\*$")))
      ("dired" (mode . dired-mode))
      ("terminal" (or (mode . term-mode) (mode . shell-mode)))
      ("help" (or (name . "^\\*Help\\*$") (name . "^\\*helpful"))))))
  :config
  (add-hook 'ibuffer-mode-hook
            (lambda () (ibuffer-switch-to-saved-filter-groups "default"))))

Helper UI (which-key, helpful, treemacs)

Additional UI packages that help with discoverability and navigation.

;; `which-key` displays available keybindings in a popup.
(use-package which-key
  :config
  (which-key-mode))

;; Enhanced help system with more detailed information and better formatting
(use-package helpful
  :bind (("C-h f" . helpful-callable)   ; Enhanced function help
         ("C-h v" . helpful-variable)   ; Enhanced variable help
         ("C-h k" . helpful-key)        ; Enhanced key help
         ("C-h x" . helpful-command))   ; Enhanced command help
  :custom
  ;; Show source code for elisp functions
  (helpful-switch-buffer-function #'helpful-switch-to-buffer))

;; Transient menu framework (required for claude-code)
(use-package transient
  :straight t)

;; Clean up mode line by hiding/shortening minor mode names
(use-package diminish
  :ensure nil  ; Already installed above
  :config
  ;; Hide these minor modes from the mode line
  (diminish 'which-key-mode)
  (diminish 'eldoc-mode)
  (diminish 'auto-revert-mode)
  (diminish 'visual-line-mode)
  (diminish 'subword-mode)
  (diminish 'rainbow-delimiters-mode)
  (diminish 'flyspell-mode)
  (diminish 'writegood-mode))

;; `treemacs` provides a file tree sidebar.
(use-package treemacs
  :defer t
  :bind (("C-x t w"   . treemacs-select-window)
         ("C-x t 1"   . treemacs-delete-other-windows)
         ("C-x t t"   . treemacs)
         ("C-x t d"   . treemacs-select-directory))
  :config
  (setq treemacs-collapse-dirs (if treemacs-python-executable 3 0)
        treemacs-display-in-side-window t
        treemacs-follow-after-init t
        treemacs-expand-after-init t
        treemacs-git-command-pipe ""
        treemacs-hide-dot-git-directory t
        treemacs-indentation 2
        treemacs-litter-directories '("/node_modules" "/.venv" "/.cask")
        treemacs-position 'left
        treemacs-show-hidden-files t
        treemacs-width 35)
  (treemacs-follow-mode t)
  (treemacs-filewatch-mode t)
  (treemacs-fringe-indicator-mode 'always)
  ;; Enable automatic project following
  (treemacs-project-follow-mode t))

;; Custom integration with project.el for single-project display
(defun my/treemacs-display-current-project-exclusively ()
  "Display only the current project in treemacs, removing all others."
  (when-let* ((project (project-current))
              (project-root (project-root project)))
    (treemacs-block-refresh
      ;; Remove all projects from workspace
      (treemacs-remove-project-from-workspace
       (treemacs-workspace->projects (treemacs-current-workspace)))
      ;; Add and display only the current project
      (treemacs-add-and-display-current-project-exclusively))))

;; Advice to automatically update treemacs when switching projects
(defun my/treemacs-project-switch-advice (&rest _args)
  "Advice function to update treemacs when switching projects."
  (when (and (featurep 'treemacs)
             (treemacs-current-workspace))
    (run-with-idle-timer 0.1 nil #'my/treemacs-display-current-project-exclusively)))

;; Add advice to project-switch-project
(with-eval-after-load 'project
  (advice-add 'project-switch-project :after #'my/treemacs-project-switch-advice))

;; Git integration for treemacs
(use-package treemacs-magit
  :after (treemacs magit)
  :defer t)

;; Modern icon support for better readability
(use-package nerd-icons
  :config
  ;; Run M-x nerd-icons-install-fonts after first install
  (unless (find-font (font-spec :name "Symbols Nerd Font Mono"))
    (when (y-or-n-p "Nerd fonts not found. Install them? ")
      (nerd-icons-install-fonts))))

(use-package treemacs-nerd-icons
  :after (treemacs nerd-icons)
  :config
  (treemacs-load-theme "nerd-icons"))

Reading and Writing Support

Configuration for packages that enhance reading comprehension and writing quality, particularly beneficial for dyslexic users.

Distraction-Free Writing (Olivetti)

Creates a focused writing environment with comfortable margins and reduced visual clutter.

(use-package olivetti
  :bind ("C-c o" . olivetti-mode)
  :custom
  (olivetti-body-width 80)
  (olivetti-minimum-body-width 60)
  (olivetti-recall-visual-line-mode-entry-state t))

Enhanced Writing Analysis (Writegood)

Helps improve writing clarity and catch common errors beyond spell-checking.

(use-package writegood-mode
  :hook (text-mode . writegood-mode)
  :custom
  (writegood-weasel-words-length 5))

Code Spell Checking (Codespell)

Codespell catches common spelling errors in code, comments, and documentation. Particularly useful for catching typos in variable names and comments.

;; Codespell integration for catching spelling errors in code
(defun my-codespell-buffer ()
  "Run codespell on the current buffer."
  (interactive)
  (if (executable-find "codespell")
      (let ((temp-file (make-temp-file "codespell-")))
        (write-region (point-min) (point-max) temp-file)
        (with-temp-buffer
          (call-process "codespell" nil t nil temp-file)
          (if (> (buffer-size) 0)
              (progn
                (display-buffer (current-buffer))
                (message "Codespell found issues - see *codespell* buffer"))
            (message "No spelling errors found by codespell")))
        (delete-file temp-file))
    (message "Codespell not found. Install with: pip install codespell")))

(defun my-codespell-region (start end)
  "Run codespell on the selected region."
  (interactive "r")
  (if (executable-find "codespell")
      (let ((temp-file (make-temp-file "codespell-region-")))
        (write-region start end temp-file)
        (with-temp-buffer
          (call-process "codespell" nil t nil temp-file)
          (if (> (buffer-size) 0)
              (progn
                (display-buffer (current-buffer))
                (message "Codespell found issues in region"))
            (message "No spelling errors found in region")))
        (delete-file temp-file))
    (message "Codespell not found. Install with: pip install codespell")))

(defun my-codespell-project ()
  "Run codespell on the current project."
  (interactive)
  (if (executable-find "codespell")
      (if-let ((project-root (project-root (project-current))))
          (let ((default-directory project-root))
            (compile "codespell --skip=.git,*.lock,*.json"))
        (message "Not in a project"))
    (message "Codespell not found. Install with: pip install codespell")))

Development Environment

This section configures Emacs for software development, including linters, language servers, and language-specific setups.

General Tooling (LSP, Linters, Compilation)

These are language-agnostic tools that form the foundation of the IDE experience.

;; `flymake` is the built-in alternative.
;; I bind keys for navigating its diagnostics.
(use-package flymake
  :ensure nil
  :bind (:map flymake-mode-map
         ("C-c n" . flymake-goto-next-error)
         ("C-c p" . flymake-goto-prev-error)))

;; `eglot` is a minimal, built-in LSP client.
(use-package eglot
  :hook ((python-mode . eglot-ensure)
         (python-ts-mode . eglot-ensure)
         (js-mode . eglot-ensure)
         (typescript-mode . eglot-ensure)
         (zig-mode . eglot-ensure))
  :bind (("C-c l c" . eglot-reconnect)
         ("C-c l d" . flymake-show-buffer-diagnostics)
         ("C-c l f f" . eglot-format)
         ("C-c l f b" . eglot-format-buffer)
         ("C-c l l" . eglot)
         ("C-c l r n" . eglot-rename)
         ("C-c l s" . eglot-shutdown)
         ("C-c l i" . eglot-inlay-hints-mode))
  :custom
  ;; Shutdown LSP server when the last managed buffer is killed.
  (eglot-autoshutdown t))


;; Configuration for Emacs's compilation interface.
(use-package compile
  :ensure nil
  :bind (("C-c b" . compile)
         ("C-c B" . recompile)) ; Removed C-c t conflict
  :custom
  (compilation-scroll-output 'first-error)
  (compilation-skip-threshold 2)) ; Skip warnings

Spell Checking (Flyspell)

Traditional spell checker that’s reliable and doesn’t interfere with syntax highlighting.

;; Flyspell for spell checking
(use-package flyspell
  :ensure nil
  :hook ((text-mode . flyspell-mode)
         (org-mode . flyspell-mode)
         (markdown-mode . flyspell-mode)
         (prog-mode . flyspell-prog-mode))  ; Only check comments/strings in code
  :bind (("M-$" . flyspell-correct-word-before-point)
         ("C-M-$" . ispell-change-dictionary))
  :custom
  (flyspell-issue-message-flag nil)  ; Don't show messages for every word
  (flyspell-issue-welcome-flag nil)  ; Don't show welcome message
  :config
  ;; Better visual feedback
  (set-face-attribute 'flyspell-incorrect nil :underline '(:color "red" :style wave))
  (set-face-attribute 'flyspell-duplicate nil :underline '(:color "orange" :style wave)))

Tree-sitter

Tree-sitter provides faster and more accurate syntax parsing, which improves highlighting and code analysis. `treesit-auto` manages the installation of parsers.

(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  :config
  ;; Only add tree-sitter modes for languages that benefit from it
  (treesit-auto-add-to-auto-mode-alist '(python bash javascript typescript json yaml zig))
  (global-treesit-auto-mode))

Language: Python

This section configures the Python development environment, including virtual environment management with `pyvenv` and linting with `ruff`.

(add-to-list 'vc-directory-exclusion-list ".venv")

(use-package pyvenv
  :config
  (pyvenv-mode 1)
  ;; Set correct Python interpreter when a virtual env is activated/deactivated.
  (setq pyvenv-post-activate-hooks
        (list (lambda ()
                (setq python-shell-interpreter (concat pyvenv-virtual-env "bin/python3")))))
  (setq pyvenv-post-deactivate-hooks
        (list (lambda ()
                (setq python-shell-interpreter "python3")))))

(use-package python
  :ensure nil
  :hook (python-mode . (lambda ()
                        (setq-local tab-width 4)
                        (setq-local python-indent-offset 4)))
  :custom
  ;; Use the fast and powerful `ruff` linter for checking Python code.
  (python-check-command "ruff check --ignore-noqa")
  ;; Ensure syntax highlighting works properly
  (python-font-lock-keywords-level 2))

Language: Markdown

This section configures the Markdown syntax highlighting.

(use-package markdown-mode
  :ensure t
  :mode ("README\\.md\\'" . gfm-mode)
  :init (setq markdown-command "multimarkdown")
  :bind (:map markdown-mode-map
         ("C-c C-e" . markdown-do)))

Language: Zig

This section configures Zig development support with syntax highlighting, LSP integration, and Tree-sitter parsing.

(use-package zig-mode
  :hook (zig-mode . (lambda ()
                     (setq-local tab-width 4)
                     (setq-local indent-tabs-mode nil))))

Project-Specific Environment (direnv)

`direnv` is a tool that loads and unloads environment variables depending on the current directory. This package integrates it with Emacs.

(use-package direnv
  :config
  (direnv-mode))

Project Configuration (editorconfig)

EditorConfig helps maintain consistent coding styles across different editors and IDEs.

(use-package editorconfig
  :config
  (editorconfig-mode 1))

Enhanced Project Management

Enhanced project.el integration with useful keybindings for project-based workflows. Custom helper functions provide integrated workflows leveraging treemacs, eat, magit, and consult.

;; Custom project helper functions
(defun my/project-workspace-setup ()
  "Setup 2-pane workspace: dired on left, eat terminal on right (50:50 split)."
  (interactive)
  (let ((project-root (project-root (project-current))))
    ;; Close treemacs if it's loaded and has a workspace
    (when (and (featurep 'treemacs)
               (fboundp 'treemacs-current-workspace)
               (treemacs-current-workspace))
      (treemacs-kill-buffer))
    ;; Start with a clean slate
    (delete-other-windows)
    ;; Open dired in project root
    (dired project-root)
    ;; Split window vertically (50:50)
    (split-window-right)
    ;; Move to right pane and open terminal
    (other-window 1)
    (eat-project)
    ;; Terminal should be the active buffer (already is from other-window)
    ))

(defun my/project-dev-setup ()
  "Open terminal + magit for development workflow."
  (interactive)
  (let ((project-root (project-root (project-current))))
    (eat-project)
    (magit-status project-root)))

(defun my/project-smart-compile ()
  "Compile using project-appropriate command based on detected project type."
  (interactive)
  (let* ((project-root (project-root (project-current)))
         (compile-cmd (cond
                      ((file-exists-p (expand-file-name "Makefile" project-root)) "make")
                      ((file-exists-p (expand-file-name "package.json" project-root)) "npm run build")
                      ((file-exists-p (expand-file-name "Cargo.toml" project-root)) "cargo build")
                      ((file-exists-p (expand-file-name "pyproject.toml" project-root)) "python -m build")
                      ((file-exists-p (expand-file-name "CMakeLists.txt" project-root)) "cmake --build build")
                      (t "make"))))
    (compile compile-cmd)))

(defun my/consult-project-ripgrep ()
  "Ripgrep in current project with better defaults."
  (interactive)
  (consult-ripgrep (project-root (project-current))))

(defun my/project-show-treemacs ()
  "Show treemacs for current project."
  (interactive)
  (if (treemacs-current-workspace)
      (treemacs-select-window)
    (treemacs)))

(defun my/project-ranger ()
  "Open ranger in current project root."
  (interactive)
  (let ((project-root (project-root (project-current))))
    (ranger project-root)))

(defun my/project-recent-files ()
  "Show recent files in current project using consult."
  (interactive)
  (unless (bound-and-true-p recentf-mode)
    (recentf-mode 1))
  (let* ((project-root (project-root (project-current)))
         (project-files (when (and project-root
                                  (bound-and-true-p recentf-list))
                         (seq-filter
                          (lambda (file)
                            (string-prefix-p project-root file))
                          recentf-list))))
    (if project-files
        (find-file (completing-read "Recent project files: " project-files))
      (message "No recent files found in this project"))))

(use-package project
  :ensure nil
  :bind (("C-x p p" . project-switch-project)
         ("C-x p f" . project-find-file)
         ("C-x p g" . project-find-regexp)
         ("C-x p d" . project-find-dir)
         ("C-x p t" . eat-project)      ; Modern terminal for project
         ("C-x p a" . ansi-term))       ; Alternative terminal option
  :custom
  ;; Enhanced project switching menu with streamlined, logically grouped actions
  (project-switch-commands
   '(;; Core File Operations (most frequent)
     (project-find-file "Find file" ?f)
     (consult-find "Find externally" ?F)
     (my/project-recent-files "Recent files" ?r)
     (consult-project-buffer "Project buffers" ?b)
     ;; Navigation & Browsing
     (my/project-ranger "Browse (ranger)" ?d)
     (my/consult-project-ripgrep "Search project" ?s)
     ;; Development Workflow
     (eat-project "Terminal" ?t)
     (magit-status "Git status" ?g)
     (my/project-smart-compile "Compile" ?c)
     ;; Layout & Cleanup
     (my/project-workspace-setup "Workspace setup" ?w)
     (my/project-show-treemacs "Treemacs" ?T)
     (project-kill-buffers "Kill buffers" ?k))))

Shell & Terminals

Configuration for various terminal emulators inside Emacs. I use `eat`, a modern term-mode replacement. Terminal commands are bound under the `C-c t` prefix to avoid conflicts with spell-checking commands.

(straight-use-package
 '(eat :type git
       :host codeberg
       :repo "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 eat
  :ensure nil ; It's installed by `straight-use-package` above
  :bind (("C-c t t" . eat)
         ("C-c t a" . ansi-term)     ; Fallback terminal
         ("C-c t p" . eat-project))  ; Project-specific terminal
  :hook (eat-mode . (lambda () (setq-local global-hl-line-mode nil))))

GPT & AI

gptel

Configuration for `gptel`, a client for interacting with Large Language Models.

(use-package gptel
  :custom
  (gptel-default-mode 'org-mode)
  :config
  ;; Configure to use a local Ollama instance with configurable host
  (let ((ollama-host (or (getenv "OLLAMA_HOST") "192.168.16.172:11434")))
    (setq gptel-backend (gptel-make-ollama "Ollama"
                          :host ollama-host
                          :stream t
                          :models '(
				   "hf.co/unsloth/gemma-3-4b-it-qat-GGUF:UD-Q8_K_XL"
				   "hf.co/unsloth/DeepSeek-R1-0528-Qwen3-8B-GGUF:UD-Q4_K_XL"
				   "hf.co/unsloth/Magistral-Small-2506-GGUF:Q3_K_XL"
                                   "omaciel/ticketeer-granite3.3"
                                   "hf.co/unsloth/GLM-Z1-9B-0414-GGUF:Q5_K_XL"))))
  ;; Add error handling for unavailable backends
  :init
  (defun my/gptel-check-backend ()
    "Check if gptel backend is available and provide feedback."
    (condition-case err
        (gptel--model-capable-p 'stream)
      (error
       (message "GPTel backend unavailable: %s" (error-message-string err))
       nil)))
  (add-hook 'gptel-pre-request-hook #'my/gptel-check-backend))
(require 'gptel-integrations)

Integrating the Model Context Protocol (MCP)

This configures Emacs as a client for the Model Context Protocol (MCP), allowing gptel to automatically pull in context from external sources like the project’s file system (a simple RAG setup). This provides the language model with relevant information about the project you’re working on.

(use-package mcp
  :straight (mcp :type git :host github :repo "lizqwerscott/mcp.el")
  :after gptel
  :custom
  (mcp-hub-servers
   `(;; 1. A Filesystem Server (with error checking)
     ;; This server exposes the root directory of your current project to the LLM.
     ;; Only enabled if npx is available.
     ,@(when (executable-find "npx")
         `(("filesystem" . (:command "npx"
                           :args ("-y" "@modelcontextprotocol/server-filesystem"
                                  ,(or (ignore-errors (project-root (project-current)))
                                       default-directory))))))

     ;; 2. A Fetch Server (with error checking)
     ;; This server can fetch content from URLs.
     ;; Only enabled if uvx is available.
     ,@(when (executable-find "uvx")
         `(("fetch" . (:command "uvx" :args ("mcp-server-fetch")))))))
  :config
  ;; Load the hub functionality and tell gptel to use MCP as a context provider.
  (require 'mcp-hub)
  :hook (after-init . (lambda ()
                        (when mcp-hub-servers
                          (condition-case err
                              (mcp-hub-start-all-server)
                            (error
                             (message "MCP servers failed to start: %s" (error-message-string err))))))))

claude-code

(use-package claude-code
  :straight (:type git :host github :repo "stevemolitor/claude-code.el" :branch "main"
	       :files ("*.el" (:exclude "demo.gif")))
  :bind-keymap
  ("C-c a" . claude-code-command-map)  ; Choose your preferred prefix
  :custom
  ;; Configure to use claude installation from PATH
  (claude-code-program (or (executable-find "claude")
                           (executable-find "claude-code")
                           "claude"))
  :config
  (claude-code-mode)
  ;; Configure for your wide screen setup
  (add-to-list 'display-buffer-alist
		 '("^\\*claude"
		   (display-buffer-in-side-window)
		   (side . right)
		   (window-width . 0.33))))

Custom Commands & Bindings

This section is for custom functions and global keybindings that don’t belong to a specific package.

;; AI-enhanced development commands
(defun my/explain-code-with-ai ()
  "Explain selected code using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Explain this code:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select code to explain"))
    (error
     (message "Error with AI explain: %s" (error-message-string err)))))

(defun my/review-code-with-ai ()
  "Review selected code for improvements."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Review this code for best practices and suggest improvements:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select code to review"))
    (error
     (message "Error with AI review: %s" (error-message-string err)))))

(defun my/document-code-with-ai ()
  "Generate documentation for selected code using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Generate documentation for this code:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select code to document"))
    (error
     (message "Error with AI documentation: %s" (error-message-string err)))))

;; AI-enhanced writing assistance functions
(defun my/improve-writing-with-ai ()
  "Improve selected text for clarity, grammar, and style using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Improve this text for clarity, grammar, and style. "
                             "Keep the meaning and intent intact:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select text to improve"))
    (error
     (message "Error with AI writing improvement: %s" (error-message-string err)))))

(defun my/adjust-tone-with-ai (tone)
  "Adjust the tone of selected text (professional, casual, academic, friendly)."
  (interactive "sTone (professional/casual/academic/friendly): ")
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat (format "Rewrite this text in a %s tone while keeping the core message:\n\n" tone)
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select text to adjust tone"))
    (error
     (message "Error with AI tone adjustment: %s" (error-message-string err)))))

(defun my/summarize-text-with-ai ()
  "Summarize selected text using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Provide a clear, concise summary of this text:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select text to summarize"))
    (error
     (message "Error with AI summarization: %s" (error-message-string err)))))

(defun my/expand-text-with-ai ()
  "Expand selected text with more detail using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Expand this text with more detail and examples while maintaining clarity:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select text to expand"))
    (error
     (message "Error with AI text expansion: %s" (error-message-string err)))))

(defun my/proofread-with-ai ()
  "Proofread selected text for grammar, spelling, and style issues using AI."
  (interactive)
  (condition-case err
      (if (region-active-p)
          (gptel-send (concat "Proofread this text for grammar, spelling, punctuation, and style issues. "
                             "Provide specific corrections and explanations:\n\n"
                             (buffer-substring-no-properties
                              (region-beginning) (region-end))))
        (message "Please select text to proofread"))
    (error
     (message "Error with AI proofreading: %s" (error-message-string err)))))

;; Bury the current buffer instead of killing it.
(global-set-key (kbd "C-c k") #'bury-buffer)

;; A convenient key for replacing text via regexp.
(global-set-key (kbd "C-c r") #'replace-regexp)

;; Toggles whitespace visibility.
(global-set-key (kbd "C-c w") #'whitespace-mode)

;; Custom function to comment/uncomment line or region without moving cursor
(defun my/comment-dwim-line-or-region ()
  "Comment or uncomment current line or region without moving cursor."
  (interactive)
  (condition-case err
      (if (region-active-p)
          ;; If region is selected, comment/uncomment the region
          (comment-or-uncomment-region (region-beginning) (region-end))
        ;; If no region, comment/uncomment current line without moving cursor
        (save-excursion
          (comment-line 1)))
    (error
     (message "Error commenting: %s" (error-message-string err)))))

;; Toggle comment/uncomment for region or line
;; Clear any existing binding for C-/ first
(global-unset-key (kbd "C-/"))
(global-set-key (kbd "C-/") #'my/comment-dwim-line-or-region)

;; Keybinding for the restart command.
(global-set-key (kbd "C-c x r") #'restart-emacs)

;; AI-enhanced development keybindings (C-c g prefix for all AI functions)
(global-set-key (kbd "C-c g e") #'my/explain-code-with-ai)
(global-set-key (kbd "C-c g r") #'my/review-code-with-ai)
(global-set-key (kbd "C-c g d") #'my/document-code-with-ai)

;; AI-enhanced writing keybindings (C-c g prefix)
(global-set-key (kbd "C-c g i") #'my/improve-writing-with-ai)     ; Improve text
(global-set-key (kbd "C-c g t") #'my/adjust-tone-with-ai)        ; Adjust tone
(global-set-key (kbd "C-c g s") #'my/summarize-text-with-ai)     ; Summarize
(global-set-key (kbd "C-c g x") #'my/expand-text-with-ai)        ; Expand text
(global-set-key (kbd "C-c g p") #'my/proofread-with-ai)          ; Proofread

;; Writing assistance and accessibility keybindings
(global-set-key (kbd "C-c o") #'olivetti-mode)          ; Focus mode
(global-set-key (kbd "C-c W") #'writegood-mode)         ; Toggle writing analysis

;; Spell checking keybindings (C-c s prefix)
(global-set-key (kbd "C-c s c") #'flyspell-correct-word-before-point)  ; Quick spell correction
(global-set-key (kbd "C-c s n") #'flyspell-goto-next-error)            ; Next spelling error
(global-set-key (kbd "C-c s l") #'ispell-change-dictionary)            ; Change dictionary

;; Codespell keybindings (C-c s prefix)
(global-set-key (kbd "C-c s b") #'my-codespell-buffer)   ; Check buffer
(global-set-key (kbd "C-c s r") #'my-codespell-region)   ; Check region
(global-set-key (kbd "C-c s p") #'my-codespell-project)  ; Check project

Package Management

This section contains advanced package management functions for straight.el, including health checking, version control integration, and a transient menu interface.

Core Package Operations

Functions for basic package management operations like freezing versions and updating packages.

;; Core package management functions
(defun my/straight-freeze-versions ()
  "Freeze current package versions to lock file."
  (interactive)
  (condition-case err
      (progn
        (message "Freezing package versions...")
        (straight-freeze-versions)
        (message "Package versions frozen to straight/versions/default.el"))
    (error
     (message "Error freezing packages: %s" (error-message-string err)))))

(defun my/straight-update-all ()
  "Update and rebuild all straight packages."
  (interactive)
  (condition-case err
      (progn
        (message "Pulling all packages...")
        (straight-pull-all)
        (message "Rebuilding all packages...")
        (straight-rebuild-all)
        (message "All packages updated and rebuilt successfully"))
    (error
     (message "Error updating packages: %s" (error-message-string err)))))

Package Health & Maintenance

Functions for checking package health, cleaning up build artifacts, and maintaining package repositories.

;; Package health and maintenance functions
(defun my/straight-check-all ()
  "Check for broken or problematic packages."
  (interactive)
  (condition-case err
      (progn
        (message "Checking package health...")
        (let ((issues '()))
          ;; Check for missing repositories
          (dolist (package (hash-table-keys straight--recipe-cache))
            (let ((repo-dir (straight--repos-dir (symbol-name package))))
              (unless (file-directory-p repo-dir)
                (push (format "Missing repository: %s" package) issues))))

          ;; Check for build issues
          (dolist (package (hash-table-keys straight--recipe-cache))
            (let ((build-dir (straight--build-dir (symbol-name package))))
              (when (and (file-directory-p build-dir)
                        (= 0 (length (directory-files build-dir nil "\\.el\\'"))))
                (push (format "Empty build directory: %s" package) issues))))

          (if issues
              (with-current-buffer (get-buffer-create "*Package Health*")
                (erase-buffer)
                (insert "Package Health Issues:\n\n")
                (dolist (issue issues)
                  (insert "" issue "\n"))
                (display-buffer (current-buffer))
                (message "Found %d package issues - see *Package Health* buffer" (length issues)))
            (message "All packages appear healthy"))))
    (error
     (message "Error checking package health: %s" (error-message-string err)))))

(defun my/straight-prune-build ()
  "Clean up unused build artifacts."
  (interactive)
  (condition-case err
      (progn
        (message "Pruning build artifacts...")
        (let ((pruned 0))
          (dolist (build-dir (directory-files (straight--build-dir) t))
            (when (and (file-directory-p build-dir)
                      (not (member (file-name-nondirectory build-dir) '("." "..")))
                      (not (gethash (intern (file-name-nondirectory build-dir))
                                   straight--recipe-cache)))
              (delete-directory build-dir t)
              (setq pruned (1+ pruned))))
          (message "Pruned %d unused build directories" pruned)))
    (error
     (message "Error pruning build artifacts: %s" (error-message-string err)))))

(defun my/straight-normalize-all ()
  "Fix repository states by normalizing all packages."
  (interactive)
  (condition-case err
      (when (yes-or-no-p "This will reset all package repositories. Continue? ")
        (message "Normalizing all package repositories...")
        (straight-normalize-all)
        (message "All packages normalized successfully"))
    (error
     (message "Error normalizing packages: %s" (error-message-string err)))))

(defun my/straight-rebuild-package ()
  "Rebuild a specific package interactively."
  (interactive)
  (condition-case err
      (let* ((packages (hash-table-keys straight--recipe-cache))
             (package (intern (completing-read "Rebuild package: "
                                             (mapcar #'symbol-name packages)))))
        (message "Rebuilding package: %s" package)
        (straight-rebuild-package package)
        (message "Package %s rebuilt successfully" package))
    (error
     (message "Error rebuilding package: %s" (error-message-string err)))))

Version Control Integration

Functions for managing lockfile versions, including backup, restore, commit, and diff operations.

;; Version control integration functions
(defun my/straight-backup-lockfile ()
  "Create a timestamped backup of the current lockfile."
  (interactive)
  (condition-case err
      (let* ((lockfile (straight--versions-lockfile))
             (backup-dir (expand-file-name "versions/backups" (straight--dir)))
             (timestamp (format-time-string "%Y%m%d-%H%M%S"))
             (backup-file (expand-file-name (format "lockfile-%s.el" timestamp) backup-dir)))
        (unless (file-directory-p backup-dir)
          (make-directory backup-dir t))
        (if (file-exists-p lockfile)
            (progn
              (copy-file lockfile backup-file)
              (message "Lockfile backed up to: %s" backup-file))
          (message "No lockfile found to backup")))
    (error
     (message "Error backing up lockfile: %s" (error-message-string err)))))

(defun my/straight-restore-lockfile ()
  "Restore from a previous lockfile backup."
  (interactive)
  (condition-case err
      (let* ((backup-dir (expand-file-name "versions/backups" (straight--dir)))
             (lockfile (straight--versions-lockfile)))
        (if (file-directory-p backup-dir)
            (let* ((backups (directory-files backup-dir nil "lockfile-.*\\.el$"))
                   (backup (when backups
                            (completing-read "Restore from backup: " backups))))
              (when backup
                (let ((backup-path (expand-file-name backup backup-dir)))
                  (when (yes-or-no-p (format "Restore lockfile from %s? This will overwrite current lockfile." backup))
                    (copy-file backup-path lockfile t)
                    (message "Lockfile restored from: %s" backup)))))
          (message "No backup directory found")))
    (error
     (message "Error restoring lockfile: %s" (error-message-string err)))))

(defun my/straight-commit-lockfile ()
  "Commit lockfile changes with a descriptive message."
  (interactive)
  (condition-case err
      (let* ((lockfile (straight--versions-lockfile))
             (default-directory user-emacs-directory))
        (if (and (file-exists-p lockfile)
                 (vc-backend lockfile))
            (let ((commit-msg (format "Pin package versions (%s)"
                                    (format-time-string "%Y-%m-%d %H:%M"))))
              (vc-checkin (list lockfile) nil commit-msg)
              (message "Lockfile committed: %s" commit-msg))
          (message "Lockfile not under version control or doesn't exist")))
    (error
     (message "Error committing lockfile: %s" (error-message-string err)))))

(defun my/straight-diff-lockfile ()
  "View changes in the lockfile compared to the last commit."
  (interactive)
  (condition-case err
      (let* ((lockfile (straight--versions-lockfile))
             (default-directory user-emacs-directory))
        (if (and (file-exists-p lockfile)
                 (vc-backend lockfile))
            (vc-diff nil t (list lockfile))
          (message "Lockfile not under version control or doesn't exist")))
    (error
     (message "Error viewing lockfile diff: %s" (error-message-string err)))))

Transient Menu Interface

A magit-style transient menu that provides organized access to all package management functions.

;; Transient menu for package management
(defun my/straight-transient ()
  "Open the package management transient menu."
  (interactive)
  (transient-setup 'my/straight-transient))

(transient-define-prefix my/straight-transient ()
  "Package management with straight.el"
  :info-manual "(straight) Top"
  [["Actions"
    ("f" "Freeze versions" my/straight-freeze-versions)
    ("u" "Update all packages" my/straight-update-all)
    ("r" "Rebuild package" my/straight-rebuild-package)]
   ["Maintenance"
    ("c" "Check package health" my/straight-check-all)
    ("p" "Prune build artifacts" my/straight-prune-build)
    ("n" "Normalize repositories" my/straight-normalize-all)]
   ["Version Control"
    ("b" "Backup lockfile" my/straight-backup-lockfile)
    ("B" "Restore lockfile" my/straight-restore-lockfile)
    ("C" "Commit lockfile" my/straight-commit-lockfile)
    ("d" "Diff lockfile" my/straight-diff-lockfile)]
   ["Quit"
    ("q" "Quit" transient-quit-one)]])

Keybindings

All package management functions are bound under the `C-c p` prefix for easy access.

;; Package management keybindings (C-c p prefix)
(global-set-key (kbd "C-c p m") #'my/straight-transient)        ; Main transient menu
(global-set-key (kbd "C-c p f") #'my/straight-freeze-versions)  ; Freeze versions
(global-set-key (kbd "C-c p u") #'my/straight-update-all)       ; Update all packages
(global-set-key (kbd "C-c p c") #'my/straight-check-all)        ; Check package health
(global-set-key (kbd "C-c p p") #'my/straight-prune-build)      ; Prune build artifacts
(global-set-key (kbd "C-c p r") #'my/straight-rebuild-package)  ; Rebuild package
(global-set-key (kbd "C-c p b") #'my/straight-backup-lockfile)  ; Backup lockfile
(global-set-key (kbd "C-c p R") #'my/straight-restore-lockfile) ; Restore lockfile
(global-set-key (kbd "C-c p C") #'my/straight-commit-lockfile)  ; Commit lockfile
(global-set-key (kbd "C-c p d") #'my/straight-diff-lockfile)    ; Diff lockfile

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Contributors 2

  •  
  •