Emacs config in progress

Posted on Jan 12, 2024

My Emacs config is now literate; here’s the core.

Variable Definitions

;; TODO: use an actual variable
(setq-default my-sync-dir (expand-file-name "~/Sync"))

Package Management

straight handles package management.

Bootstrap straight

;;;;;
;; STRAIGHT
;; bootstrap the pkg manager
(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))

Configure straight

;; Fetch use-package from straight
(straight-use-package 'use-package)
;; Configure use-package to install pkgs using straight
(setq straight-use-package-by-default t)

Utility Packages

(use-package a)
;; file manipulation
(use-package f)
;; string manipulation
(use-package s)
;; other helpers
(use-package dash)

Editor Config

;; hide warnings, only let errors through
(setq warning-minimum-level :error)

;; auto-close delimiters
(electric-pair-mode 1)

;; no menu bar
(when (fboundp 'menu-bar-mode)
  (menu-bar-mode -1))
;; and no tool bar
(when (fboundp 'tool-bar-mode)
  (tool-bar-mode -1))

Backup Files

Backup and temporary files are stored in $XDG_CACHE_HOME/emacs/backups if the env variable $XDG_CACHE_HOME is defined, else they’re stored in ~/.cache/emacs/backups.

(let ((backup-dir (expand-file-name "emacs/backups"
                                    (or (getenv "XDG_CACHE_HOME") "~/.cache"))))
  (setq backup-directory-alist `(("." . ,backup-dir)))
  (setq auto-save-file-name-transforms `((".*" ,backup-dir t))))

Theme and Font

(use-package dracula-theme
  :config (load-theme 'dracula t))
;; WARNING: Depends on Fira Code being installed!
(set-frame-font "Fira Code" nil t)

Evil vim

(use-package evil
  :init
  (setq evil-want-keybinding nil)
  (setq evil-want-integration t)
  :config
  (evil-mode 1))

(use-package evil-collection
  :after (evil)
  :custom
  (evil-collection-setup-minibuffer t)
  (evil-collection-calendar-want-org-bindings t)
  :straight (evil-collection :type git
                             :host github
                             :repo "emacs-evil/evil-collection"))

;; Surround: wrap selections with delimiters
;; https://github.com/emacs-evil/evil-surround
(use-package evil-surround
  :ensure t
  :config
  (global-evil-surround-mode t))

General leader keybindings

;; helpers
(defmacro my-launchers (&rest args)
  "Add global app launchers defined by ARGS under `SPC-o`"
  `(general-nmap
     :prefix "SPC o" ,@args))

(use-package general
  :config
  (general-evil-setup)

  ;; global top-level bindings
  (general-nmap
    :prefix "SPC"
    "SPC" 'switch-to-buffer
    ":" 'counsel-M-x
    "u" 'universal-argument)

  ;; global app launchers
  (my-launchers
   "e" 'eshell)

  ;; global window keybindings
  (general-nmap
    :prefix "SPC w"
    "d" 'delete-window
    "s" 'split-window-below
    "v" 'split-window-right
    "h" 'windmove-left
    "j" 'windmove-down
    "k" 'windmove-up
    "l" 'windmove-right)

  ;; global buffer keybindings
  (general-nmap
    :prefix "SPC b"
    "b" 'switch-to-buffer
    "d" 'kill-this-buffer
    "D" 'kill-buffer)

  ;; global file keybindings
  (general-nmap
    :prefix "SPC f"
    "f" 'find-file
    ;; SPC-f-r: open recent files
    "r" 'recentf-open
    "s" 'save-buffer
    "d" 'delete-file)

  ;; global help keybindings
  (general-nmap
    :prefix "SPC h"
    "v" 'describe-variable
    "f" 'describe-function
    "k" 'describe-key)

  ;; TODO per-lang evals
  (general-nmap
    :prefix ", e"
    "b" 'eval-buffer
    "f" 'eval-defun
    "s" 'eval-last-sexp))

Command pallete

The command pallete selector is ivy with counsel shims:

(use-package ivy
  :custom
  (ivy-use-virtual-buffers t)
  (enable-recursive-minibuffer t)
  (ivy-count-format "(%d/%d) ")
  (ivy-wrap t)
  :config
  (define-key ivy-minibuffer-map (kbd "C-j") 'ivy-next-line)
  (define-key ivy-minibuffer-map (kbd "C-k") 'ivy-previous-line)
  (ivy-mode))

(use-package counsel
  :after (ivy)
  :config
  (counsel-mode))

Autocompletion

;; COMPLETION
(use-package corfu
  :init
  (setq tab-always-indent 'complete)
  :config
  (define-key corfu-map (kbd "C-j") 'corfu-next)
  (define-key corfu-map (kbd "C-k") 'corfu-previous)
  (corfu-mode 1))

Snippets

yasnippet provides snippets.

Use the global normal mode binding SPC i s to insert a snippet via yas-insert-snippet.

;; setup yasnippet
(use-package yasnippet
  :init
  (general-nmap
    :prefix "SPC i"
    "s" #'yas-insert-snippet)
  (yas-minor-mode 1))

;; and all the snippets
(use-package yasnippet-snippets
  :after (yasnippet))

Window Management

popper keeps popup windows like eshell or Warnings from getting out of hand.

(use-package popper
  :init
  (setq popper-reference-buffers
        '("\\*Messages\\*"
          "\\*eshell\\*"
          "\\*Deft\\*"
          "Output\\*$"
          "\\*Async Shell Command\\*"
          "\\*chatgpt\\*"
          "\\*Warnings\\*"
          help-mode
          compilation-mode))
  (popper-mode +1)
  (popper-echo-mode +1)
  :config
  (general-nmap
    :prefix "SPC"
    "~" 'popper-toggle)
  (general-nmap
    :prefix "SPC P"
    "P" 'popper-toggle
    "p" 'popper-cycle
    "t" 'popper-toggle-type))