dotfiles/emacs-config.org

62 KiB
Raw Blame History

First things first

Literate Config Setup

To get started, run C-c C-v t

  (defun dotfiles/org-babel-tangle-config ()
    (when (string-equal (buffer-file-name)
                        (expand-file-name "~/.dotfiles/emacs-config.org"))
      (let ((org-confirm-babel-evaluate nil))
        (org-babel-tangle)
        (load-file (expand-file-name "~/.config/emacs/init.el")))))

  (add-hook 'org-mode-hook
            (lambda ()
              (add-hook 'after-save-hook
                        #'dotfiles/org-babel-tangle-config
                        nil
                        t)))
org-appear-mode org-tempo-setup yas-minor-mode #[nil (evil-org-mode) nil] #[0 \300\301\302\303\304$\207 [add-hook change-major-mode-hook org-fold-show-all append local] 5] #[0 \300\301\302\303\304$\207 [add-hook change-major-mode-hook org-babel-show-result-all append local] 5] org-babel-result-hide-spec org-babel-hide-all-hashes #[0 \301\211\207 [imenu-create-index-function org-imenu-get-tree] 2] #[nil ((display-line-numbers-mode 0)) nil] #[nil ((add-hook 'after-save-hook #'dotfiles/org-babel-tangle-config nil t)) nil]

Package initialization

TODO: savehist mode doesn't seem to save the history correctly

  (require 'package)

  (setq package-archives
        '(("melpa" . "https://melpa.org/packages/")
          ("org" . "https://orgmode.org/elpa/")
          ("elpa" . "https://elpa.gnu.org/packages/")))
  (package-initialize)

  ;; Initialize use-package on non-linux platforms
  (unless (package-installed-p 'use-package)
    (package-install 'use-package))

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

  (package-refresh-contents)

  (use-package savehist
    :init
    (savehist-mode))

Paths

  ;; (add-to-list 'exec-path "/opt/homebrew/bin")
  ;; (setenv "PATH" (concat "/opt/homebrew/bin:" (getenv "PATH")))

Straight

I don't use straight for much currently, but needed it in order to install beancount-mode and intend to migrate all my packages to it eventually.

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

  (setq package-enable-at-startup nil)

Password Retrieval

password-store integrations require you to have pass and pinentry configured on your local system outside of emacs.

  (use-package pinentry)
  (use-package pass)
  (use-package password-store)
  (pinentry-start)
  (auth-source-pass-enable)

Variables

Things referenced throughout the config which are specific to me

  (setq user-init-file (expand-file-name "~/.config/emacs/init.el"))

  (setq IS-MACOS (eq system-type 'darwin))

  (setq openai-key-string
	(if IS-MACOS "self/openai-alt" "openai-alt"))

  (setq gptel-api-key (password-store-get openai-key-string))

Required directories for emacs setup

Eventually I'll set this up so that all these directories are set in the config of their respective packages; I'll still run ensure-directory on all of them beforehand though.

  (defun ian/ensure-directory (dir)
    "Ceate a dir if it doesn't exist."
    (unless (file-directory-p dir)
      (make-directory dir t)))

  (ian/ensure-directory "~/.config/emacs/backups/")
  (ian/ensure-directory "~/.config/emacs/autosaves")
  (ian/ensure-directory "~/notes/roam")
  (ian/ensure-directory "~/notes/deft")
  (ian/ensure-directory "~/notes/code")
  (ian/ensure-directory "~/notes/work")
  (ian/ensure-directory "~/.config/emacs/video")
  (ian/ensure-directory "~/.config/emacs/audio")

  (setq projectile-project-search-path '("~/code/")
        empv-video-dir "~/.config/emacs/video"
        empv-audio-dir "~/.config/emacs/audio"
        backup-directory-alist `(("." . "~/.config/emacs/backups/"))
        auto-save-file-name-transforms `((".*" "~/.config/emacs/autosaves/" t)))

  ;; (projectile-mode)

Helper Functions

  (defun ian/eval-region-or-buffer ()
    "Eval the region if anything is selected, otherwise eval the whole buffer."
    (interactive)
    (if mark-active
        (eval-region (region-beginning) (region-end))
      (eval-buffer)))

  (defun ian/conditional-imenu ()
    "Call `consult-org-heading` in Org mode, otherwise `consult-imenu`."
    (interactive)
    (if (eq major-mode 'org-mode)
        (consult-org-heading)
      (consult-imenu)))

  (defun ian/reload-config ()
    "Reload the Emacs configuration."
    (interactive)
    (load-file user-init-file))

  (defun ian/open-from-project-root (filename)
  "Open a file from the root of the current Projectile project."
  (interactive)
  (find-file (expand-file-name filename (projectile-project-root))))

Evil-want-keybinding

Setting this in the :init of evil doesn't work for some reason. It has to be early in the file TODO: find a better solution

  (setq evil-want-keybinding nil)

Basic Appearance and Behaviors

Font and Theme

  (use-package doom-themes
    :config
    ;; Global settings (defaults)
    (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
          doom-themes-enable-italic t) ; if nil, italics is universally disabled
    ;; Enable flashing mode-line on errors
    (setq doom-themes-treemacs-theme "doom-atom") ; use "doom-colors" for less minimal icon theme
    (doom-themes-treemacs-config)
    ;; Corrects (and improves) org-mode's native fontification.
    (doom-themes-org-config))

  ;; (set-frame-font "DejaVu Sans Mono 16")
  ;; (add-to-list 'default-frame-alist
  ;;              '(font . "Fira Code Mono 16"))

  (set-face-attribute 'default nil
                    :family "Fira Code"
                    :height 160)

  ;; (load-theme 'doom-oceanic-next t)

  (use-package base16-theme)
  (load-theme 'base16-oceanicnext t)

  (use-package default-text-scale
    :defer 1
    :config
    (default-text-scale-mode))

Cosmetic

  (setq fancy-splash-image "~/.dotfiles/sink.png")  ;; the best emacs logo
  (setq lsp-lens-enable nil)  ;; gets rid of the '0 references' note from lsp

  ;; remove visual noise
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (recentf-mode 1)
  (savehist-mode 1)  ; remember previous commands in minibuffer
  (save-place-mode 1)  ; put cursor in last place when opening a file

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

  (use-package rainbow-delimiters
    :hook (prog-mode . rainbow-delimiters-mode))

  (use-package solaire-mode
    :config (solaire-global-mode +1))

  (use-package simple-modeline
    :hook (after-init . simple-modeline-mode))

  ;; turn off number mode in some contexts
  (dolist (mode
           '(org-mode-hook
             term-mode-hook
             eshell-mode-hook
             treemacs-mode-hook
             vterm-mode-hook
             nov-mode-hook))
    (add-hook mode (lambda () (display-line-numbers-mode 0))))
  (column-number-mode)

Buffers

TODO: Make this use currently selected buffer

  ;; Generate buffers in last window
  (setq display-buffer-base-action
	'(display-buffer-reuse-mode-window
	  display-buffer-reuse-window
	  display-buffer-same-window))

Behavioral Tweaks

  (when IS-MACOS
    (setq mac-pass-command-to-system nil)) 

  ;; Avoid customization UI auto-variables in init.el
  (setq custom-file (locate-user-emacs-file "custom-vars.el"))
  (load custom-file 'noerror 'nomessage)

  ;; Don't show popup UI when prompting
  (setq use-dialog-box nil)

  ;; Update files when changed on disk
  (global-auto-revert-mode 1)

  (use-package helpful)  ; Slightly more helpful help

  ;; If a popup does happen, don't resize windows to be equal-sized
  (setq even-window-sizes nil)
  (setq whitespace-style '(trailing tabs newline tab-mark )) ; show tab characters
  (setq tab-always-indent 'complete)
  (setq tab-first-completion 'word)
  (setq indent-tabs-mode nil)  ; tabs not spaces

  (visual-line-mode)  ; wrap long lines visually
  (whitespace-mode)
  (setq-default indent-tabs-mode nil)

Tab bar

  (use-package tab-bar-echo-area
    :ensure
    :config
    (tab-bar-echo-area-mode 1)
    (setq tab-bar-show nil))

Evil

  (use-package evil
    :ensure t
    :config
    (global-set-key (kbd "<escape>") 'keyboard-escape-quit)
    (define-key evil-normal-state-map (kbd "j") 'evil-next-visual-line)
    (define-key evil-normal-state-map (kbd "k") 'evil-previous-visual-line)
    (define-key evil-normal-state-map (kbd "ghp") 'git-gutter-previous-hunk)
    (define-key evil-normal-state-map (kbd "ghn") 'git-gutter-next-hunk)
    (define-key evil-normal-state-map (kbd "ghr") 'git-gutter-revert-hunk)
    (define-key evil-normal-state-map (kbd "ghs") 'git-gutter-popup-hunk)
    (define-key evil-normal-state-map (kbd "ghS") 'git-gutter-stage-hunk)
    (define-key evil-normal-state-map (kbd "gp") nil) 
    (define-key evil-normal-state-map (kbd "gpn") 'popper-cycle)
    (define-key evil-normal-state-map (kbd "gpp") 'popper-cycle-backwards)
    (define-key evil-normal-state-map (kbd "gpq") 'popper-kill-latest-popup)
    (define-key evil-normal-state-map (kbd "gpt") 'popper-toggle-type)
    (define-key evil-normal-state-map (kbd "gbn") 'projectile-next-project-buffer)
    (define-key evil-normal-state-map (kbd "gbp") 'projectile-previous-project-buffer)
    (evil-mode 1))

  (use-package undo-tree
    :ensure t
    :after evil
    :diminish
    :config
    (evil-set-undo-system 'undo-tree)
    (setq undo-tree-history-directory-alist '(("." . "~/.config/emacs/undo")))
    (global-undo-tree-mode 1))


  (use-package evil-org
    :ensure t
    :after org
    :hook (org-mode . (lambda () evil-org-mode)))


  (use-package evil-collection
    :after evil
    :ensure t
    :diminish evil-collection-unimpaired-mode
    :init
    :config
    (setq evil-respect-visual-line-mode t)
    (setq evil-collection-repl-submit-state 'insert)
    (evil-collection-init))


  (use-package evil-commentary
    :config
    (evil-commentary-mode))

Navigation

Helper Functions

  (defun split-and-follow-horizontally ()
    (interactive)
    (split-window-below)
    (balance-windows)
    (other-window 1))

  (defun split-and-follow-vertically ()
    (interactive)
    (split-window-right)
    (balance-windows)
    (other-window 1))

Windows and Workspaces

Credit for manual dispatch function.

TODO: Window width controls

  (use-package winner
    :after evil
    :config
    (winner-mode))

  (use-package ace-window)
  (setq aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l))
  (setq aw-dispatch-alist
	'(
	  (?x aw-delete-window "Delete Window")
	  (?m aw-swap-window "Swap Windows")
	  (?M aw-move-window "Move Window")
	  ;; (?c aw-copy-window "Copy Window")
	  ;; (?j aw-switch-buffer-in-window "Select Buffer")
	  ;; (?n aw-flip-window)
	  ;; ;;
	  (?b aw-switch-buffer-other-window "Switch Buffer Other Window")
	  (?u winner-undo)
	  (?U winner-redo)
	  (?c aw-split-window-fair "Split Fair Window")
	  (?v aw-split-window-vert "Split Vert Window")
	  (?H aw-split-window-horz "Split Horz Window")
	  (?o delete-other-windows "Delete Other Windows")
	  (?? aw-show-dispatch-help)))
  (setq aw-scope 'frame)

  (defun ace-window-manual-dispatch (arg)
    "Calls `ace-window' with ARG asking for the dispatch, even if `aw-dispatch-always' is nil.
    This could be useful to use the advanced commands"
    (interactive "p")
    (let ((aw-dispatch-always t))
      (ace-window arg)
      ))

Shackle

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

  (setq shackle-rules
	'(
	  (helpful-mode          :align right :select t  :size 0.3  :popup t)
	  (help-mode             :align right :select t  :size 0.3  :popup t)
	  (compilation-mode      :align right :select t  :size 0.3  :popup t)
	  ("*shell*"             :align below :select t  :size 0.3  :popup t)
	  ("*eshell*"            :align below :select t  :size 0.3  :popup t)
	  ("*vterm*"             :align below :select t  :size 0.3  :popup t)
	  ("*scratch*"           :align left  :select t  :size 0.25 :popup t)
	  (treemacs-mode         :align left  :select t  :size 0.40 :popup t)
	  ("*Messages*"          :align right :select t  :size 0.25 :popup t))
	default-shackle-rule '(:select t))  ;; Always select newly created windows

Popper

  (use-package popper
    :after (shackle projectile)
    :init
    (setq popper-reference-buffers
	  '(("\\*Messages\\*" . hide)
	    "\\*Error\\*"
	    "Output\\*$"
	    ("\\*Warnings\\*" . hide)
	    "\\*scratch*\\*"
	    ;; "\\*lsp-bridge\\*"
	    "\\*Async Shell Command\\*"
	    "^\\*eshell.*\\*$" eshell-mode ;eshell as a popup
	    "^\\*shell.*\\*$"  shell-mode  ;shell as a popup
	    "^\\*term.*\\*$"   term-mode   ;term as a popup
	    "^\\*vterm.*\\*$"  vterm-mode  ;vterm as a popup
	    helpful-mode
	    compilation-mode
	    inferior-python-mode
	    consult-flymake-mode))
    :config
    (setq popper-display-control nil)
    (popper-mode +1)
    (popper-echo-mode +1))
  (setq popper-group-function #'popper-group-by-projectile)

Dired

  (setq ring-bell-function 'ignore)
  (setq dired-listing-switches "-al")

  ;; opening 'other window' on a subdir enters 2 window dired mode for copy/paste
  (setq dired-dwim-target t) 

  ;; ;; if dired's already loaded, then the keymap will be bound
  ;; (if (boundp 'dired-mode-map)
  ;;     (my-dired-init)
  ;;   (add-hook 'dired-load-hook 'my-dired-init))

  (use-package dired-open
    :config
    (setq dired-open-extensions '(("png" . "sxiv")
                                  ("mkv" . "mpv")
                                  ("avi" . "mpv"))))
  ;; (eval-after-load 'evil
  ;;   '(evil-define-key 'normal dired-mode-map (kbd "SPC") nil))

  (use-package all-the-icons-dired
    :hook (dired-mode . all-the-icons-dired-mode))

Treemacs

  (use-package treemacs
    :config
    (setq treemacs-width  45))

  ;; Follow project buffers instantly
  (setq treemacs--project-follow-delay 0)

  (defun ian/treemacs-open-current-or-jump ()
    "Find the current file and open treemacs if it's not open, otherwise jump
      to the treemacs window"
    (interactive)
    (if
        (not(eq (treemacs-current-visibility) 'visible))
        (treemacs-find-file))
    (treemacs-select-window))

  (use-package all-the-icons)
  (use-package treemacs-all-the-icons)
  (use-package treemacs-evil)

Which-key

  (use-package which-key
    :init (which-key-mode)
    :diminish which-key-mode
    :config
    (setq which-key-idle-delay 0.3))

General

I know these could be bound on a per-package basis, but since this is my primary interface with emacs I'd rather keep all keymaps in one place, both for reference and to discourage myself from organizing my top-level bindings on a per-plugin basis rather than keeping the focus on functionality. TODO:

  • bind lispy for colemak-dh
  • bind lispy doc features and ide-like features
  • bind embark
  (use-package general
    :ensure t
    :after nov evil evil-collection treemacs
    :config

    (general-create-definer general-definition
      ;; :keymaps '(normal insert visual emacs nov-mode-map nov-button-map dired-mode-map)
      :states '(normal visual symex)
      :keymaps 'override
      :prefix "SPC"
      :global-prefix "C-SPC")

    (general-definition

      ;; Top level bindings
      "SPC" '(projectile-find-file                      :which-key "Find project file")
      "<backspace>" '(ian/treemacs-open-current-or-jump :which-key "Find current file in sidebar")
      "TAB" '(popper-toggle                             :which-key "Toggle popup")
      "," '(projectile-ibuffer                          :which-key "Buffers (workspace)")
      "/" '(consult-ripgrep                             :which-key "Search project")
      ":" '(execute-extended-command                    :which-key "M-x")
      ";" '(eval-expression                             :which-key "Eval")
      "<" '(consult-buffer                              :which-key "Buffers (all)")
      "-" '(dired                                       :which-key "Find Directory")
      "d" '(docker                                      :which-key "Docker")
      "i" '(ian/conditional-imenu                       :which-key "Imenu")
      "w" '(ace-window                                  :which-key "Window")  
      "W" '(ace-window-manual-dispatch                  :which-key "Window (dispatch)")  

      ;; Agenda
      "a" '(:ignore t                     :which-key "Agenda")
      "aa" '(org-agenda                   :which-key "Open Agenda")
      "a/" '(consult-org-agenda           :which-key "Search agenda headlines")

      "ac" '(:ignore t                    :which-key "Clock")
      "acc" '(org-clock-goto              :which-key "Active clock")
      "ack" '(org-clock-cancel            :which-key "Cancel current clock")
      "act" '(+org/toggle-last-clock      :which-key "Toggle last clock")


      ;; Buffers / bookmarks
      "b" '(:ignore t                 :which-key "Buffers")
      "bb" '(consult-bookmark         :which-key "Goto Bookmark")  ;; consult-bookmark
      "bk" '(kill-current-buffer      :which-key "Kill buffer")
      "bm" '(bookmark-set             :which-key "Set bookmark")
      "bM" '(bookmark-delete          :which-key "Delete bookmark")
      ;; "bo" '(NEEDS CUSTOM FUNCTION :which-key "Kill other buffers")
      "br" '(revert-buffer            :which-key "Revert buffer")
      "bR" '(rename-buffer            :which-key "Rename buffer")
      "bz" '(bury-buffer              :which-key "Bury buffer")
      ;; "bZ" '(NEEDS CUSTOM FUNCTION :which-key "Kill buried buffers")
      "bj" '(+ivy/jump-list           :which-key "Jump list")
      "bu" '(vundo                    :which-key "Undo history")

      ;; Code
      "c" '(:ignore t                         :which-key "Code")
      "cc" '(compile                          :which-key "Compile")
      "cC" '(recompile                        :which-key "Recompile")
      "cd" '(+lookup/definition               :which-key "Goto definition")
      "cr" '(+lookup/references               :which-key "Goto references")
      "ce" '(ian/eval-region-or-buffer        :which-key "Eval buffer/region")
      "cE" '(consult-flymake                  :which-key "Errors")
      ;; "cf" '(NOT A COMMAND (re-do)         :which-key "Format buffer/region")
      "ci" '(+lookup/implementations          :which-key "Find implementations")
      "cj" '(lsp-ivy-workspace-symbol         :which-key "Goto symbol (curr. workspace)")
      "cJ" '(lsp-ivy-global-workspace-symbol  :which-key "Goto symbol (any workspace)")
      "ck" '(+lookup/documentation            :which-key "Jump to documentation")
      "cl" '(+default/lsp-command-map         :which-key "LSP")
      "co" '(lsp-organize-imports             :which-key "LSP organize imports")
      "cp" '(completion-at-point              :which-key "Completion at point")
      "cR" '(lsp-rename                       :which-key "LSP rename")
      "cs" '(+eval/send-region-to-repl        :which-key "Send to repl")
      "ct" '(+lookup/type-definition          :which-key "Find type definition")
      "cw" '(delete-trailing-whitespace       :which-key "Delete trailing whitespace")
      ;; "cn" '(doom/delete-trailing-newlines :which-key "Delete trailing newlines")
      "cx" '(+default/diagnostics             :which-key "List errors")

      ;; File
      "f" '(:ignore t                                      :which-key "File")
      ;; "fp" '(doom/open-private-config                   :which-key "Browse private config")
      "fr" '(projectile-recentf                            :which-key "Recent files")
      "fR" '(consult-recent-file                           :which-key "Recent files")
      "fc" '(ian/reload-config                             :which-key "Reload config")
      ;; "fm" '(doom/move-this-file                        :which-key "Move/rename file")
      ;; "fS" '(doom/sudo-find-file                        :which-key "Sudo find file")
      ;; "fs" '(doom/sudo-this-file                        :which-key "Sudo this file")
      "fy" '(+default/yank-buffer-path                     :which-key "yank file path")
      "fY" '(+default/yank-buffer-path-relative-to-project :which-key "Yank file path from project")

      ;; Git
      "g"   '(:ignore t :which-key "git")
      "gb"  '(magit-branch-checkout         :which-key "magit switch branch")
      "gB"  '(magit-blame-addition          :which-key "magit blame")
      "gF"  '(magit-fetch                   :which-key "magit fetch")
      "gg"  '(magit-status                  :which-key "magit status")
      "gG"  '(magit-status-here             :which-key "magit status here")
      "gL"  '(magit-log-buffer-file         :which-key "magit buffer log")
      "gt"  '(git-timemachine-toggle        :which-key "git time machine")

      "gd"   '(:ignore t :which-key "dispatch")
      "gdF" '(forge-dispatch                :which-key "Forge dispatch")
      "gdd" '(magit-dispatch                :which-key "Magit dispatch")
      "gdf" '(magit-file-dispatch           :which-key "Magit file dispatch")

      "gf"   '(:ignore t :which-key "find")
      "gfc" '(magit-show-commit             :which-key "find commit")
      "gff" '(magit-find-file               :which-key "find file")
      "gfg" '(magit-find-git-config-file    :which-key "find gitconfig file")
      "gfi" '(forge-visit-issue             :which-key "find issue")
      "gfp" '(forge-visit-pullreq           :which-key "find pull request")

      "gh"   '(:ignore t :which-key "hunk")

      "ghn" '(git-gutter-previous-hunk     :which-key "jump to previous hunk")
      "ghp" '(git-gutter-next-hunk         :which-key "jump to next hunk")
      "ghr" '(git-gutter-revert-hunk       :which-key "revert hunk at point")
      "ghs" '(git-gutter-popup-hunk        :which-key "show hunk")
      "ghS" '(git-gutter-stage-hunk        :which-key "stage hunk at point")

      "gl"   '(:ignore t :which-key "list")
      "gli" '(forge-list-issues             :which-key "list issues")
      "gln" '(forge-list-notifications      :which-key "list notifications")
      "glp" '(forge-list-pullreqs           :which-key "list pull requests")
      "glr" '(forge-list-issues             :which-key "list repositories")
      "gls" '(magit-list-submodules         :which-key "list submodules")

      "go"   '(:ignore t :which-key "open")
      "goI" '(forge-browse-issues           :which-key "Browse issues")
      "goP" '(forge-browse-pullreqs         :which-key "Browse pull requests")
      "goc" '(forge-browse-commit           :which-key "Browse commit")
      "goh" '(+vc/browse-at-remote-homepage :which-key "Browse homepage")
      "goi" '(forge-browse-issue            :which-key "Browse an issue")
      "goo" '(+vc/browse-at-remote          :which-key "Browse file or region")
      "gop" '(forge-browse-pullreq          :which-key "Browse a pull request")
      "gor" '(forge-browse-remote           :which-key "Browse remote")

      ;; Help
      "h"   '(:ignore t :which-key "help")
      "h<return>" '(info-emacs-manual                :which-key "Emacs manual")
      "h'"        '(describe-char                    :which-key "Describe character")
      "ha"        '(apropos                          :which-key "Apropos")
      "hb"        '(embark-bindings                  :which-key "Describe bindings")
      "he"        '(view-echo-area-messages          :which-key "View echo area messages")
      "hf"        '(helpful-function                 :which-key "Describe function")
      "hi"        '(info                             :which-key "Info")
      "hk"        '(helpful-key                      :which-key "Describe key")
      "hm"        '(describe-mode                    :which-key "Describe mode")
      "ho"        '(helpful-symbol                   :which-key "Describe symbol")
      "hp"        '(explain-pause-top                :which-key "Processes")
      "hs"        '(yas-describe-tables              :which-key "Describe symbol")
      "hv"        '(helpful-variable                 :which-key "Describe variable")
      "hx"        '(helpful-command                  :which-key "Describe command")
      "hA"        '(apropos-documentation            :which-key "Apropos(documentation)")
      "hF"        '(describe-face                    :which-key "Describe Face")
      "hM"        '(popwin:messages                  :which-key "View system messages")

      "hw"        '(:ignore t :which-key "which-key")
      "hwf"       '(which-key-show-full-keymap       :which-key "Full keymap")
      "hwi"       '(which-key-show-minor-mode-keymap :which-key "Minor mode keymap")
      "hwk"       '(which-key-show-keymap            :which-key "Show keymap")
      "hwm"       '(which-key-show-major-mode        :which-key "Show major mode")
      "hwt"       '(which-key-show-top-level         :which-key "Show top-level")

      "m"       '(:ignore t :which-key "media")
      "my"      '(empv-youtube          :which-key "youtube")
      "mt"      '(empv-toggle           :which-key "toggle")
      "mT"      '(empv-toggle-video     :which-key "toggle-video")
      "mq"      '(empv-exit             :which-key "quit")
      "ms"      '(empv-display-current  :which-key "show currently playing")

      "mr"      '(:ignore t :which-key "radio")
      "mrr"     '(empv-play-radio                    :which-key "play")
      "mrR"     '(empv-play-random-channel           :which-key "play random")
      "mrl"     '(empv-log-current-radio-song-name   :which-key "log song name")

      "mp"      '(:ignore t :which-key "playlist")
      "mpP"     '(empv-playlist                 :which-key "playlist")
      "mps"     '(empv-playlist-select  :which-key "select")
      "mpn"     '(empv-playlist-next    :which-key "next")
      "mpp"     '(empv-playlist-prev    :which-key "prev")
      "mpc"     '(empv-playlist-clear   :which-key "clear")
      "mpS"     '(empv-playlist-shuffle :which-key "shuffle")

      "mf"      '(:ignore t :which-key "file")
      "mff"     '(empv-play-file        :which-key "play file")
      "mfd"     '(empv-play-directory   :which-key "play directory")

      "mv"      '(:ignore t :which-key "volume")
      "mvd"     '(empv-volume-down      :which-key "down")
      "mvu"     '(empv-volume-up        :which-key "up")

      "mc"      '(:ignore t :which-key "chapter")
      "mcp"     '(empv-chapter-prev     :which-key "prev")
      "mcn"     '(empv-chapter-next     :which-key "next")
      "mcs"     '(empv-chapter-select   :which-key "select")

      ;; Notes
      "nd" '(deft                              :which-key "Deft")
      "nt" '(org-todo-list                     :which-key "Todo list")
      "nf" '(org-roam-node-find                :which-key "Find node")
      "ng" '(org-roam-ui-open                  :which-key "Show graph")
      "nr" '(org-roam-buffer-toggle            :which-key "Toggle roam buffer")
      "nb" '(org-roam-buffer-display-dedicated :which-key "Launch roam buffer")
      "ns" '(org-roam-db-sync                  :which-key "Sync database")
      "nc" '(org-roam-capture                  :which-key "Capture to node")
      "ni" '(org-roam-node-insert                   :which-key "Insert node at point")

      ;; Org mode
      "o" '(:ignore t :which-key "org")
      "ol"  '(org-store-link                        :which-key "Store link")
      "oC"  '(org-capture-goto-target               :which-key "Goto Capture")
      "oo"  '(org-open-at-point                     :which-key "Open link")
      "ot"  '(org-babel-tangle                      :which-key "Tangle")
      "oy"  '(+org/export-to-clipboard              :which-key "Export to clipboard")
      "oY"  '(+org/export-to-clipboard-as-rich-text :which-key "Export to clipboard (rich)")
      "oc"  '(:ignore t :which-key "Capture")
      "ocn" '(ian/capture-to-notes-org              :which-key "Capture to local notes")
      "oct" '(ian/capture-to-todo-org               :which-key "Capture to local todo")
      "oe" '(:ignore t :which-key "export")
      "oeh" '(org-html-export-to-html               :which-key "Export Org to HTML")
      "oa" '(:ignore t :which-key "attach")
      "oaa" '(org-attach                            :which-key "Attach")
      "oac" '(org-download-clipboard                :which-key "Org download clipboard")
      "oad" '(org-attach-delete-one                 :which-key "Delete one attachment")
      "oaD" '(org-attach-delete-all                 :which-key "Delete all attachments")
      "oas" '(org-attach-sync                       :which-key "Sync attachments")
      "oay" '(org-download-yank                     :which-key "Org download yank")

      ;; Project
      "p"  '(:ignore t :which-key "project")
      "ps" '(+neotree/open                     :which-key "Open project sidebar")
      "pa" '(projectile-add-known-project      :which-key "Add new project")
      "pc" '(+ivy/project-compile              :which-key "Compile in project")
      "pd" '(projectile-remove-known-project   :which-key "Remove known project")
      "ps" '(projectile-switch-project         :which-key "Switch projects")
      "pC" '(projectile-configure-project      :which-key "Configure project")
      "pr" '(projectile-run-project            :which-key "Run project")
      "pt" '(magit-todos-list                  :which-key "List project todos")
      "pT" '(projectile-test-project           :which-key "Test project")
      "pz" '(+taskrunner/project-tasks         :which-key "List  project tasks")

      ;; Reference buffers
      "r"  '(:ignore t :which-key "reference buffers")
      "rs" '(scratch-buffer                                                       :which-key "Scratch")
      "rm" '(view-echo-area-messages                                              :which-key "Messages")
      "rc" '(view-lossage                                                         :which-key "Commands History")
      "rn" '((lambda () (interactive) (ian/open-from-project-root "notes.org"))   :which-key "Notes")
      "rt" '((lambda () (interactive) (ian/open-from-project-root "todo.org"))    :which-key "Todo")
      "rl" '((lambda () (interactive) (find-file "~/notes/work/time.org"))        :which-key "Time Log")

      ;; Shell
      "s"  '(:ignore t :which-key "shell")
      "se" '(+eshell/toggle               :which-key "Toggle eshell popup")
      "sE" '(+eshell/here                 :which-key "Open eshell here")
      "sr" '(+eval/open-repl-other-window :which-key "Repl")
      "st" '(+vterm/toggle                :which-key "Toggle vterm popup")
      "sT" '(+vterm/here                  :which-key "Open vterm here")

      ;; Toggle
      "t"  '(:ignore t :which-key "toggle")
      "tc" '(global-display-fill-column-indicator-mode :which-key "Fill Column Indicator")
      "tf" '(flycheck-mode                             :which-key "Flycheck")
      "tm" '(consult-minor-mode-menu                   :which-key "Minor modes")
      "tp" '(org-tree-slide-mode                       :which-key "Org-tree-slide-mode")
      "tw" '(+word-wrap-mode                           :which-key "Soft line wrapping")
      "tz" '(+zen/toggle                               :which-key "Zen mode")
      ;; "tl" '(doom/toggle-line-numbers                  :which-key "Line numbers")

      ;; Visual / appearance
      "v"  '(:ignore t  :which-key "visual/appearance")
      "vt" '(load-theme :which-key "load theme")

      ;; TODO: maybe make this mode-specific stuff, not using workspaces
      ;; Workspaces
      ;; "TAB TAB" '(+workspace/display              :which-key "Display tab bar")
      ;; "TAB d"   '(+workspace/delete               :which-key "Delete this workspace")
      ;; "TAB l"   '(+workspace/load                 :which-key "Load workspace from file")
      ;; "TAB n"   '(+workspace/new                  :which-key "New workspace")
      ;; "TAB N"   '(+workspace/new-named            :which-key "New named workspace")
      ;; "TAB r"   '(+workspace/rename               :which-key "Rename workspace")
      ;; "TAB R"   '(+workspace/restore-last-session :which-key "Restore last session")
      ;; "TAB s"   '(+workspace/save                 :which-key "Save workspace")
      ;; "TAB x"   '(+workspace/kill-session         :which-key "Delete session")
      ))

Notes

  (use-package deft)
  (setq deft-recursive t
        deft-extensions '("org" "txt"))

AI

  (use-package gptel)
  (setq gptl-default-mode 'org-mode)

Org Mode

  (use-package org)

  (add-hook 'org-mode-hook 'yas-minor-mode)

  (org-babel-do-load-languages
   'org-babel-load-languages
   '((emacs-lisp . t)
     (python . t)))

  (setq org-confirm-babel-evaluate nil)

  (require 'org-tempo)
  (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"))  

  (setq org-hide-emphasis-markers t)

  (use-package org-appear
    :hook (org-mode . org-appear-mode))

  (use-package org-roam
    :custom
    (org-roam-directory "~/notes/roam")
    :config
    (org-roam-db-autosync-mode)
    (setq org-link-frame-setup '((file . find-file))))

  (use-package deft
    :commands (deft)
    :config 
    (setq deft-directory "~/notes/deft"
          deft-extensions '("org" "txt")
          deft-recursive t))

  (use-package ox-hugo
    :after ox)

  (use-package org-roam-ui
    :after org-roam
    :config
    (setq org-roam-ui-sync-theme t
          org-roam-ui-follow t
          org-roam-ui-update-on-save t
          org-roam-ui-open-on-start t))

  (use-package ox-gfm)  ; github-flavored-markdown exporter

  (use-package org-download)

Agenda

  (setq org-agenda-start-with-log-mode t)
  (setq org-agenda-files '("~/notes/deft/tasks.org" "~/notes/roam/daily"))
  
  (setq org-agenda-span 10)  
  

  (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "EVENT(e)" "|" "DONE(d!)" "CANCELLED(c@)")))

Capture templates

  (defun ian/capture-to-notes-org ()
    "Capture the current region (if selected) or a link to the point into 'notes.org' at the top of the Projectile project directory."
    (interactive)
    (let* ((selected-text (if (use-region-p)
                              (buffer-substring-no-properties (mark) (point))
                            nil))
           (file-path (buffer-file-name))
           (project-root (projectile-project-root))
           (notes-file (expand-file-name "notes.org" project-root))
           (link (format "[[file:%s::%d][Link to %s::%d]]\n\n"
                         file-path
                         (line-number-at-pos)
                         (file-name-nondirectory file-path)
                         (line-number-at-pos)))
           (template (if selected-text
                         (concat "* %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n#+BEGIN_SRC\n" selected-text "\n#+END_SRC\n\n" link)
                       (concat "* %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n" link)))
           (org-capture-entry `("r" "Capture" entry
                                (file ,notes-file)
                                ,template)))
      (org-capture nil "r")
      (with-current-buffer (find-file-noselect notes-file)
        (save-buffer))))

  (defun ian/capture-to-todo-org ()
    "Capture the current region (if selected) or a link to the point into 'todo.org' at the top of the Projectile project directory."
    (interactive)
    (let* ((selected-text (if (use-region-p)
                              (buffer-substring-no-properties (mark) (point))
                            nil))
           (file-path (buffer-file-name))
           (project-root (projectile-project-root))
           (notes-file (expand-file-name "todo.org" project-root))
           (link (format "[[file:%s::%d][Link to %s::%d]]\n\n"
                         file-path
                         (line-number-at-pos)
                         (file-name-nondirectory file-path)
                         (line-number-at-pos)))
           (template (if selected-text
                         (concat "* %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n#+BEGIN_SRC\n" selected-text "\n#+END_SRC\n\n" link)
                       (concat "* %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n" link)))
           (org-capture-entry `("r" "Capture" entry
                                (file ,notes-file)
                                ,template)))
      (org-capture nil "r")
      (with-current-buffer (find-file-noselect notes-file)
        (save-buffer))))

  (defun checklist-daily-capture ()
  "Generates daily checklist content."
  (concat "* Daily Checklist\n"
          "- [ ] Brush teeth\n"
          "- [ ] Stretch\n"
          "- [ ] Clean 20m\n"))

  (defun weekday-specific-daily-capture (day-of-week)
  "Generate checklist content which is specific to the weekday"
  (cond
      ((string= day-of-week "Monday")
      (concat ""))
      ((string= day-of-week "Tuesday")
      (concat "* Tuesday Checklist\n"
          "- [ ] Rollout trash\n"))
      ((string= day-of-week "Thursday")
      (concat 
          "- [ ] Water Houseplants\n"))
      ((string= day-of-week "Friday")
      (concat ""))
      ((string= day-of-week "Saturday")
      (concat ""))
      ((string= day-of-week "Sunday")
      (concat "* Sunday Checklist\n"
          "- [ ] Start Sourdough\n"))
      ))

  (defun workout-daily-capture (day-of-week)
  "Generate workout table content based on DAY-OF-WEEK."
  (concat
          "* Workout\n"
          "| Exercise | Weight | Sets | Reps | Notes |\n"
          "|----------+--------+------+------+-------|\n"
          (cond
              ((string= day-of-week "Monday")
              (concat
              "| VSQ      |        |      |      |       |\n"
              "| CGB      |        |      |      |       |\n"
              "| BTN      |        |      |      |       |\n"
              "| OHP      |        |      |      |       |\n"
              "| SC       |        |      |      |       |\n"))
              ((string= day-of-week "Tuesday")
              (concat
              "| RDL      |        |      |      |       |\n"
              "| ROW      |        |      |      |       |\n"
              "| WCHP     |        |      |      |       |\n"
              "| CU       |        |      |      |       |\n"))
              ((string= day-of-week "Thursday")
              (concat
              "| BP       |        |      |      |       |\n"
              "| JMP      |        |      |      |       |\n"
              "| SQ       |        |      |      |       |\n"
              "| SC       |        |      |      |       |\n"))
              ((string= day-of-week "Friday")
              (concat
              "| DL       |        |      |      |       |\n"
              "| WPU      |        |      |      |       |\n"
              "| CU       |        |      |      |       |\n"))
              (t "Rest Day\n"))))

  (defun diet-daily-capture ()
      "Capture for calorie tracking"
      (concat
       "* Diet\n"
       "- Weight :: \n"
       "| Food | Calories | Total |\n"
       "|------|----------|-------|\n"
       "|      |          |       |\n"))

  (defun spending-daily-capture ()
      "Capture for spending"
      (concat
       "* Spending\n"
       "| Expense | Price | Total |\n"
       "|---------|-------|-------|\n"
       "|         |       |       |\n"))

  (setq org-roam-dailies-capture-templates
        `(("d" "Daily Routine" entry
           ,(concat 
             "* Scheduled Today\n\n"
             "%(weekday-specific-daily-capture \"%<%A>\")\n"
             "%(checklist-daily-capture)\n"
             "%(workout-daily-capture \"%<%A>\")\n"
             "%(diet-daily-capture)\n"
             "%(spending-daily-capture)\n")
           :target (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n"))))
d Daily Routine entry * Scheduled Today

Project management

  (use-package projectile)  ; todo config inside use-package
  (use-package treemacs-projectile)

Autocomplete

Vertico

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

Consult

  (use-package consult
    :bind (:map minibuffer-local-map ("C-r" . consult-history)))
  (setq consult-ripgrep-args "rg --null --line-buffered --color=never --max-columns=1000 --path-separator / --smart-case --no-heading --with-filename --line-number --search-zip --hidden")

Generic completion

Idk I think this came from the vertico readme

  (use-package emacs
  :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)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
          '(read-only t cursor-intangible t face minibuffer-prompt))
  
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  (setq enable-recursive-minibuffers t))
  (setq completion-styles '(basic substring partial-completion flex))

Orderless

  (use-package orderless
    :init
    (setq completion-styles '(orderless basic)
          completion-category-defaults nil
          completion-category-overrides '((file (styles partial-completion)))))

Marginalia

  (use-package marginalia
    :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle))
    :init
    (marginalia-mode))

Company-mode

  (use-package company
    :ensure t
    :hook (prog-mode . company-mode)
    :diminish
    :bind (:map company-active-map
                ("<tab>" . company-select-next)
                ("TAB" . company-select-next)
                ("<backtab>" . company-select-previous)
                ("<S-tab>" . company-select-previous))
    (:map lsp-mode-map  ; more general mode than this?
          ("<tab>" . company-indent-or-complete-common))
    :config
    (global-company-mode 1)
    :init
    (setq company-idle-delay nil))

Embark

  (use-package embark
    :ensure t
    :after evil evil-collection evil-org
    :diminish eldoc-mode
    :bind
    (("C-." . embark-act)         ;; pick some comfortable binding
     ("C-;" . embark-dwim)        ;; good alternative: M-.
     ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
    :init

    ;; Show the Embark target at point via Eldoc.  You may adjust the Eldoc
    ;; strategy, if you want to see the documentation from multiple providers.
    ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
    (define-key evil-normal-state-map (kbd "C-.") 'embark-act)
    (evil-define-key 'normal evil-normal-state-map (kbd "C-.") embark-act)


    :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 users will also want the embark-consult package.
  (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))

  ;; https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt
  (defun embark-which-key-indicator ()
    "An embark indicator that displays keymaps using which-key.
  The which-key help message will show the type and value of the
  current target followed by an ellipsis if there are further
  targets."
    (lambda (&optional keymap targets prefix)
      (if (null keymap)
          (which-key--hide-popup-ignore-command)
        (which-key--show-keymap
         (if (eq (plist-get (car targets) :type) 'embark-become)
             "Become"
           (format "Act on %s '%s'%s"
                   (plist-get (car targets) :type)
                   (embark--truncate-target (plist-get (car targets) :target))
                   (if (cdr targets) "…" "")))
         (if prefix
             (pcase (lookup-key keymap prefix 'accept-default)
               ((and (pred keymapp) km) km)
               (_ (key-binding prefix 'accept-default)))
           keymap)
         nil nil t (lambda (binding)
                     (not (string-suffix-p "-argument" (cdr binding))))))))

  (setq embark-indicators
        '(embark-which-key-indicator
          embark-highlight-indicator
          embark-isearch-highlight-indicator))

  (defun embark-hide-which-key-indicator (fn &rest args)
    "Hide the which-key indicator immediately when using the completing-read prompter."
    (which-key--hide-popup-ignore-command)
    (let ((embark-indicators
           (remq #'embark-which-key-indicator embark-indicators)))
      (apply fn args)))

  (advice-add #'embark-completing-read-prompter
              :around #'embark-hide-which-key-indicator)

Snippets

  (use-package yasnippet
    :diminish yas-minor-mode)
  (use-package yasnippet-snippets
    :after lsp-mode)
  (yas-global-mode)

Languages

LSP

  (use-package lsp-mode
    :commands (lsp lsp-deferred)
    :init
    (setq lsp-keymap-prefix "C-c l")
    :config
    (lsp-enable-which-key-integration t)
    (setq lsp-headerline-breadcrumb-enable nil)
    :hook (terraform-mode . lsp-deferred)
    :hook (rust-mode . lsp-deferred))

  (use-package lsp-ui)
  (use-package lsp-treemacs)
  (use-package flycheck
    :ensure t
    :diminish
    :init (global-flycheck-mode))

Rust

  (use-package rust-mode
    :ensure t
    :mode "\\.rs\\'"
    :config
    (setq rust-format-on-save t))

  (use-package cargo
    :ensure t
    :hook (rust-mode . cargo-minor-mode))

Terraform

  (use-package terraform-mode
    :ensure t)

Lisp

All lisp-family languages

                                              ; Required for some reason
      (use-package symex-core
        :straight
        (symex-core
         :host github
         :repo "drym-org/symex.el"
         :files ("symex-core/symex*.el")))

      (use-package symex
        :after (symex-core)
        :straight
        (symex
         :host github
         :repo "drym-org/symex.el"
         :files ("symex/symex*.el" "symex/doc/*.texi" "symex/doc/figures"))
        :config
        (symex-mode 1)
        (global-set-key (kbd "s-;") #'symex-mode-interface))

      (use-package symex-evil
        :after (symex evil)
        :straight
        (symex-evil
         :host github
         :repo "drym-org/symex.el"
         :files ("symex-evil/symex*.el"))
        :config
        (symex-evil-mode 1))

      (use-package symex-ide
        :after (symex)
        :straight
        (symex-ide
         :host github
         :repo "drym-org/symex.el"
         :files ("symex-ide/symex*.el"))
        :config
          (define-key symex-editing-mode-map
              (kbd "?")
              #'symex-describe)
          (define-key symex-editing-mode-map
              (kbd "g d")
              #'geiser-edit-symbol-at-point)
        (symex-ide-mode 1))

    (use-package lithium
    :straight t
    :config
    (lithium-mode 1))

  (use-package rigpa
    :straight
    (rigpa
     :type git
     :host github
     :repo "countvajhula/rigpa")
    :config
    (rigpa-mode 1))

    (use-package symex-rigpa
      :after (symex rigpa symex-evil)
      :straight
      (symex-rigpa
       :host github
       :repo "drym-org/symex.el"
       :files ("symex-rigpa/symex*.el"))
      :config
      (symex-rigpa-mode 1))

Scheme

Guile
  (use-package geiser)
  (use-package geiser-guile)

  ;; Guile debug mode uses ',' to bind a bunch of useful command
  ;; None of my evil packages seem to account for this
  (with-eval-after-load 'geiser-debug
    (evil-define-key 'motion geiser-debug-mode-map
      (kbd ",") #'geiser-guile-debug-menu))

  ;; Why why why doesn't homebrew just handle this?
  (when IS-MACOS
    (let ((brew-guile-share "/opt/homebrew/share/guile/site/3.0")
          (brew-guile-cache "/opt/homebrew/lib/guile/3.0/site-ccache")
          (brew-guile-ext   "/opt/homebrew/lib/guile/3.0/extensions"))
      (setenv "GUILE_LOAD_PATH" brew-guile-share)
      (setenv "GUILE_LOAD_COMPILED_PATH" brew-guile-cache)
      (setenv "GUILE_SYSTEM_EXTENSIONS_PATH" brew-guile-ext)
      (setenv "GUILE_TLS_CERTIFICATE_DIRECTORY"
              "/opt/homebrew/etc/gnutls/")
      (setenv "GUILE_LOAD_PATH"
              (string-join
               '("/opt/homebrew/share/guile/site/3.0"
                 "/opt/homebrew/share/guile-lib")
               ":"))
      ))

Go

  (use-package go-mode)
  (add-hook 'go-mode-hook 'lsp-deferred)

Markups

Markdown

  (use-package markdown-mode)
  (use-package evil-markdown
    :after evil
    :straight (evil-markdown :type git :host github :repo "somelauw/evil-markdown")
    :mode ("\\.md\\'" . evil-markdown-mode))
  (use-package json-mode)
  (use-package yaml-mode)

Beancount

This is mostly lifted from the doom emacs config. Beancount-mode by default has a pretty borked tab function.

  (defun beancount-tab-function ()
    "Improved beancount tab ripped off from doom emacs"
    (interactive)
    (if (and outline-minor-mode (outline-on-heading-p))
	(beancount-outline-cycle)
      (indent-according-to-mode)))

  (use-package beancount
    :straight (beancount :type git :host github :repo "beancount/beancount-mode")
    :mode ("\\.beancount\\'" . beancount-mode)
    :hook (beancount-mode . outline-minor-mode)
    :config
    (evil-define-key 'normal 'beancount-mode-map
      (kbd "TAB") 'beancount-tab-function)) ;; Bind the TAB key

  (add-hook 'beancount-mode-hook #'flymake-bean-check-enable)

Epub

  (use-package nov
    :after evil evil-collection
    :config
    ;; (define-key nov-button-map (kbd "SPC") nil)
    ;; (evil-define-key 'normal nov-mode-map (kbd "SPC") nil)
    ;; (evil-define-key 'normal nov-button-map (kbd "SPC") nil)
    ;; (define-key nov-mode-map (kbd "SPC") nil)
    (setq nov-text-width 80))

  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))

Containers

Docker

  (use-package dockerfile-mode)
  (use-package docker-compose-mode)
  ;; (use-package docker)
  ;; Todo: lsp-docker? docker-tramp? docker-cli?

Kubernetes

  (use-package k8s-mode)
  ;; kubernetes
  ;; kubernetes-evil
  ;; kubernetes-helm
  ;; kubernetes-tramp

Shells

Enable fish completions

Uses the external fish program to provide fish-style completions to eshell etc

  (use-package fish-completion)
  (when (and (executable-find "fish")
             (require 'fish-completion nil t))
    (global-fish-completion-mode))

Git

  (use-package magit)
  (use-package magit-todos)
  (use-package git-gutter
    :diminish)
  (global-git-gutter-mode t)
  (setq magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1)

  (use-package git-timemachine)

Web browsing and youtube

  ;; Using straight:
  ;;(use-package empv
  ;;  :straight (:host github :repo "isamert/empv.el"))

  (use-package empv)

  (setq empv-radio-channels
        '(("SomaFM - Groove Salad" . "http://www.somafm.com/groovesalad.pls")
          ("SomaFM - Drone Zone" . "http://www.somafm.com/dronezone.pls")
          ("SomaFM - Sonic Universe" . "https://somafm.com/sonicuniverse.pls")
          ("SomaFM - Metal" . "https://somafm.com/metal.pls")
          ("SomaFM - Vaporwaves" . "https://somafm.com/vaporwaves.pls")
          ("ADHD 1" . "https://www.youtube.com/watch?v=CmMrm4BpQHU")
          ("WREK FM" . "http://streaming.wrek.org:8000/wrek_live-128kb.m3u")
          ))

  (with-eval-after-load 'embark (empv-embark-initialize-extra-actions))
  (add-to-list 'empv-mpv-args "--ytdl-format=best")
  (add-to-list 'empv-mpv-args "--save-position-on-quit")
  (add-hook 'empv-init-hook #'empv-override-quit-key)

  (setq empv-invidious-instance "https://invidious.projectsegfau.lt/api/v1")

Email, rss, newsgroups

Oh, boy. Here we go. This will come in handy. (TODO: smtp header)

  (setq user-mail-address "ian@keane.sh"
	user-full-name "Ian Keane")

  ;; Assuming your passwords are stored in Password Store with names like:
  ;; "email/fastmail" and "email/gmail"

  ;; IMAP configuration for Fastmail

  (setq gnus-select-method
	'(nnimap "Fastmail-emacs"
		 (nnimap-user "ian@keane.sh")
		 (nnimap-address "imap.fastmail.com")
		 (nnimap-server-port 993)
		 (nnimap-stream ssl)))


  ;; Additional secondary IMAP account for Gmail
  ;; (add-to-list 'gnus-secondary-select-methods
  ;; 	     '(nnimap "Gmail"
  ;; 		      (nnimap-address "imap.gmail.com")
  ;; 		      (nnimap-server-port 993)
  ;; 		      (nnimap-stream ssl)
  ;; 		      (nnimap-authinfo-file nil)  ; Not using .authinfo for Gmail
  ;; 		      (nnir-search-engine imap)
  ;; 		      (nnimap-authenticator login)
  ;; 		      (nnimap-login "your-gmail-username") ; Replace with your Gmail username
  ;; 		      (nnimap-password (password-store-get "email/gmail"))))

  (setq send-mail-function 'smtpmail-send-it
	message-send-mail-function 'smtpmail-send-it
	smtpmail-default-smtp-server "smtp.fastmail.com"
	smtpmail-smtp-server "smtp.fastmail.com"
	smtpmail-smtp-service 587
	smtpmail-stream-type 'starttls
	smtpmail-smtp-user "ian@keane.sh"
	smtpmail-debug-info t)

  ;; You may also want to add a conditional to choose the SMTP server based on the current 'From' address.

  ;; RSS Feeds
  ;; (add-to-list 'gnus-secondary-select-methods '(nnrss ""))

  ;; Configure RSS feed subscriptions
  ;; (setq gnus-secondary-sources
  ;;       '((nnrss "http://example.com/rss") ; replace with actual RSS URL
  ;;         ;; Add more RSS feeds here
  ;;         ))


  ;; Use Gnus for composing email
  (setq mail-user-agent 'gnus-user-agent)

  ;; Set up HTML email rendering
  (setq mm-text-html-renderer 'shr)

Emacs Meta

Explain-Pause

Scrutinize which processes are gumming up emacs

  ;; (use-package explain-pause-mode
  ;;   :straight (explain-pause-mode :type git :host github :repo "lastquestion/explain-pause-mode")
  ;;   :diminish explain-pause-mode
  ;;   :config
  ;;   (explain-pause-mode))

Todo

General

  • Refactor to use use-package more effectively

    • Use :mode and :config like here
    • Set things which should be set before package install in :init
  • Disable source editing issue

    • Goes away when I run (defun org-edit-src-code nil)
  • Go through AWB's notes about using gnus
  • TODO aggregation
  • Suppress evilmarkdown mode and org-roam-ui minor mode

Terminals

  • More terminals
  • emacs-fish-completions
  • setup terminals to complete to vertico
  • starship prompts

New functions

  • Git-time-machine-or-vc-region-history

Note taking

  • deft
  • org : roam2, hugo, pretty, dragndrop, bullet
  • meetlog reference buffer
  • work roam directory
  • Dalies for scheduling things
  • Fix org indentation stuff

Lisp stuff

  • paredit
  • lispy
  • no tabs for yaml-mode (wtf?)

One-offs

  • 'ex' snippet for the most common example of a thing I do per filetype

    • docker-compose example
  • hl-todo
  • persistent undo
  • commentary
  • highlight tabs and newlines
  • Vertico backwards delete
  • alphabetical keybindings
  • brighter modeline
  • surpress global minor modes

    • Undo-Tree, company, flycheck, which-key
  • ignore case in imenu
  • improve which-key visibility
  • gpt popup
  • org-roam-buffer popup
  • conflicts with global popup buffers
  • init popup buffer binding? spc r i?
  • org-roam buffer in popup (follow)
  • prettify-symbols-mode
  • create 'context' keybinding

    • map smerge mode

Embark

  • open file in other window

NFS stuff

  • map music from nfs
  • map books from nfs with nov.el

LSP stuff

  • look into treemacs integrations
  • consider eglot
  • dap-mode

Completion

  • dabbrev-expand or company on c-n c-p
  • c-N c-P for expand full line
  • yasnippet
  • company-mode to corfu (or vertico completions? or cofu-in-vertico?)
  • get vertico as completion box

Keybindings

  • bind (custom?) git-view-from-other-branch

    • want to be able to view a file as it is in another branch
  • bind (custom?) git diff from other branch
  • bind capture to init todo
  • bind capture to project todo
  • bind org-set-property
  • treemacs-open-other-window
  • treemacs | to open in split
  • create-tab-with-name function
  • buffer to spc b b local, spc b B global
  • do somthing useful with ','
  • custom function for toggle between last 2 windows
  • custom function to scroll-other-window using last 2 windows
  • evil-define-key instead of define-key everywhere
  • bind something to create file in dired / treemacs
  • fix general prefix in treemacs mode
  • fix vim-surround
  • snippets help spc h y, helpful-symbol spc h s
  • put general bindings in separate keymaps in separate sections
  • map hyper bindings for buffer nav and jumping
  • evil-org
  • map alignment functions in 'code'section
  • fix c-u in evil mode
  • fix tab key
  • window size functions, winner undo/redo
  • map default-text-scale operations
  • org-capture from string and org-ripgrep
  • move spc p s back to spc p p
  • org eval block
  • explain-pause spc h p

    • Want this for org-roam, don't want to rebind tab here
  • empv

    • map
    • configure

      • empv-radio-log-file
      • empv-radio-log-format
      • spc m y(outube)
      • spc m l(ocal)
      • spc m r(adio)
      • empv-video-dir empv-audio-dir?
      • empv-play-file
  • Set display-buffer-alist values for how I want windows treated (integrates with popper), e.g.
  (add-to-list 'display-buffer-alist
	       '("\\*ielm\\*"
		 (display-buffer-in-side-window)
		 (side . left)
		 (window-width . 50)))

selected radio (See empv-radio-log-file and empv-radio-log-format variables and their documentations).

New plugins

  • tramp
  • k8s
  • docker
  • terraform
  • straight?
  • embark?
  • emms, emms-play-url binding (youtube view)
  • consult-lsp (better for errors than treemacs integrations probably)
  • srht.el
  • affe
  • gnus

Appearance switches

  • fonts
  • random theme
  • org mode bullets / pretty / etc

Research

  • What does C-d do when in a minibuffer for vertico?
  • embark
  • ace-window / embark integration

Annoyances

  • set require-final-newline = nil
  • fix gitgutter behavior
  • remove extra line file modifying behavior
  • fix missing snippets in note-taking mode
  • always open treemacs from most recent window
  • prevent :wq from killing frame

Ediff

  • vertical
  • no new frame
  • bind magit-ediff-compare

      (setq ediff-window-setup-function #'ediff-setup-windows-plain)
  • bind ediff two files
  • fix fuzzy searching for functions
  • bind SPC - to dired-jump
  • fix markdown-mode (doesn't start with .md files automatically)
  • bind magit-log-buffer-file (handles selection-based history)
  • adaptive wrap (plus fix visual-line-mode to always load)

  • install / configure consult-org-roam
  • map default-text-scale-increase/decrease (better than regular text scaling)

    • set default-text-scale-amount to 5 instead of 10
  • map outline-toggle-children in tf mode
  • map ripgrep in parent dir
  • map ripgrep in (dired select)
  • add .git to ripgrep ignore
  • fix vterm

    • make the popup frame-wide
    • fix spc v t
    • maybe just make spc tab open shell and exclude it from ace managed windows
  • make eshell pretty
  • when toggling shell, enter insert mode
  • migrate to wezterm, impelement popup term
  • look into letting org fold blocks or folding bracket blocks in general cases
  • try (add-hook 'magit-post-refresh-hook 'git-gutter:refresh)
  • make treemacs enter always open in most recent buffer
  • add meeting log to reference reaches
  • install ini-mode plugin
  • install lua-mode plugin
  • install fish-mode plugin