dotfiles/emacs-config.org

1842 lines
69 KiB
Org Mode
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+PROPERTY: header-args:emacs-lisp :tangle "~/.config/emacs/init.el"
#+OPTIONS: toc:nil title:nil
#+EXPORT_FILE_NAME: ~/code/website/src/content/dotfiles/emacs.md
#+BEGIN_EXPORT markdown
+++
title = "Emacs Configuration"
author = ["Ian Keane"]
date = 2023-02-09
tags = ["Config", "Text editing"]
description = 'My emacs configuration.'
+++
#+END_EXPORT
* Preamble
My emacs configuration. It tangles to =.emacs.d/init.el= and exports to this markdown file on save. If you're viewing this in a repo, it's better to visit the website at <https://iankeane.srht.site/dotfiles>. If you are viewing this on the website and want the repo, you can find it at <https://sr.ht/~iankeane/dotfiles>. To see the hugo front-matter configuration, you'll have to view the raw code.
#+TOC: headlines 2
* First things first
** Literate Config Setup
To get started, run =C-c C-v t=
#+begin_src emacs-lisp
(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)))
#+end_src
#+RESULTS:
| 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
#+begin_src emacs-lisp
(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))
#+end_src
** Paths
#+begin_src emacs-lisp
;; (add-to-list 'exec-path "/opt/homebrew/bin")
;; (setenv "PATH" (concat "/opt/homebrew/bin:" (getenv "PATH")))
#+end_src
** 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.
#+begin_src emacs-lisp
(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)
#+end_src
** Password Retrieval
=password-store= integrations require you to have =pass= and =pinentry= configured on your local system outside of emacs.
#+begin_src emacs-lisp
(use-package pinentry)
(use-package pass)
(use-package password-store)
(pinentry-start)
(auth-source-pass-enable)
#+end_src
** Variables
Things referenced throughout the config which are specific to me
#+begin_src emacs-lisp
(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))
#+end_src
** 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.
#+begin_src emacs-lisp
(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)
#+end_src
** Helper Functions
#+begin_src emacs-lisp
(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))))
#+end_src
** 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
#+begin_src emacs-lisp
(setq evil-want-keybinding nil)
#+end_src
* Basic Appearance and Behaviors
** Font and Theme
#+begin_src emacs-lisp
(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 "FiraCode Nerd Font Mono"
: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))
#+end_src
** Cosmetic
#+begin_src emacs-lisp
(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 nerd-icons
:ensure t
:config
(unless (find-font (font-spec :name "Symbols Nerd Font"))
(nerd-icons-install-fonts t)))
(use-package doom-modeline
:hook (after-init . doom-modeline-mode)
:config
(setq doom-modeline-minor-modes t
doom-modeline-height 24
nerd-icons-font-family "Symbols Nerd Font Mono")
(set-face-attribute 'mode-line nil :height 0.9)
(set-face-attribute 'mode-line-inactive nil :height 0.9))
(use-package minions
:config (minions-mode 1))
;; 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)
#+end_src
** Buffers
=TODO:= Make this use currently selected buffer
The base display action tries to reuse an existing window showing the same
mode or buffer before opening a new one, falling back to the same window.
This is the default for buffers not covered by =display-buffer-alist= in the
Popper section. The goal is to avoid gratuitous window splits for ordinary
buffer switching — splits should be intentional.
#+begin_src emacs-lisp
;; Generate buffers in last window
(setq display-buffer-base-action
'(display-buffer-reuse-mode-window
display-buffer-reuse-window
display-buffer-same-window))
#+end_src
** Behavioral Tweaks
#+begin_src emacs-lisp
(setq ring-bell-function 'ignore)
(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
:ensure t
:bind
([remap describe-function] . helpful-callable)
([remap describe-variable] . helpful-variable)
([remap describe-key] . helpful-key)
([remap describe-command] . helpful-command))
;; 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)
#+end_src
** Tab bar
#+begin_src emacs-lisp
(use-package tab-bar-echo-area
:ensure
:config
(tab-bar-echo-area-mode 1)
(setq tab-bar-show nil))
#+end_src
** Tabspaces
Buffers, and much of buffer-selection UI (=consult-buffer=, ibuffer, etc.),
are scoped per tab via =tabspaces=. Each tab gets its own isolated buffer
list, similar to workspaces in other editors — switching tabs only shows you
the buffers relevant to that tab.
#+begin_src emacs-lisp
(use-package tabspaces
:ensure t
:hook (after-init . tabspaces-mode)
:commands (tabspaces-switch-or-create-workspace
tabspaces-open-or-create-project-and-workspace)
:config
(setq tabspaces-use-filtered-buffers-as-default t)
(setq tabspaces-default-tab "Default")
(setq tabspaces-remove-to-default t)
(setq tabspaces-include-buffers '("*scratch*"))
;; Keep consult-buffer scoped to the current tab's buffer list.
;; tabspaces doesn't ship a consult source itself, so define one here
;; using its own buffer-list function, and put it ahead of consult's
;; default (unfiltered) buffer source.
(with-eval-after-load 'consult
(defvar consult--source-tabspaces
`(:name "Workspace Buffers"
:narrow ?w
:category buffer
:face consult-buffer
:history buffer-name-history
:state ,#'consult--buffer-state
:items ,(lambda () (mapcar #'buffer-name (tabspaces--buffer-list)))))
(consult-customize consult--source-buffer :hidden t :default nil)
(add-to-list 'consult-buffer-sources 'consult--source-tabspaces)))
#+end_src
* Evil
#+begin_src emacs-lisp
(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))
#+end_src
* Navigation
** Helper Functions
#+begin_src emacs-lisp
(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))
#+end_src
** Windows and Workspaces
[[https://github.com/abo-abo/ace-window/issues/216][Credit]] for manual dispatch function.
General window management uses =ace-window= for jumping and splitting, with
=winner-mode= for undo/redo of window layouts. The philosophy here is to keep
manual window control lightweight — ace-window covers most navigation needs
with single keypresses, and winner catches mistakes. Workspaces are handled
at the tab level rather than with a dedicated workspace package.
- =SPC w= jumps to a window by label
- =SPC W= opens the full ace-window dispatch menu for splits, swaps, etc.
- =C-c <left>= / =C-c <right>= undo/redo window layouts via winner
=TODO:= Window width controls
#+begin_src emacs-lisp
(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)
))
#+end_src
*** StumpWM-aware window navigation
=s-h/j/k/l= (mod+hjkl) move between Emacs windows using =windmove=. When
there's no further Emacs window in that direction (i.e. we're at the edge of
Emacs's frame), the command instead shells out to StumpWM via =stumpish= to
move focus to the next tiled frame in that direction — so navigation flows
seamlessly out of Emacs and into the rest of the WM using the same keys
StumpWM itself binds to =s-h/j/k/l=.
#+begin_src emacs-lisp
(defvar stumpwm-stumpish-path
(expand-file-name "~/.stumpwm.d/modules/util/stumpish/stumpish")
"Path to the stumpish binary used to talk to a running StumpWM.")
(defun stumpwm-move-focus (direction)
"Ask StumpWM to move focus to the frame in DIRECTION (\"left\" \"right\" \"up\" \"down\").
No-op on macOS (or anywhere StumpWM isn't running/available)."
(when (and (not IS-MACOS)
(file-executable-p stumpwm-stumpish-path))
(call-process stumpwm-stumpish-path nil nil nil
(concat "move-focus " direction))))
(defun stumpwm-windmove-or-escape (windmove-fn direction)
"Call WINDMOVE-FN; if it errors (no Emacs window in DIRECTION),
fall through to StumpWM and move focus to the next tiled frame there."
(condition-case nil
(funcall windmove-fn)
(error (stumpwm-move-focus direction))))
(defun stumpwm-windmove-left () (interactive) (stumpwm-windmove-or-escape #'windmove-left "left"))
(defun stumpwm-windmove-right () (interactive) (stumpwm-windmove-or-escape #'windmove-right "right"))
(defun stumpwm-windmove-up () (interactive) (stumpwm-windmove-or-escape #'windmove-up "up"))
(defun stumpwm-windmove-down () (interactive) (stumpwm-windmove-or-escape #'windmove-down "down"))
(global-set-key (kbd "s-h") #'stumpwm-windmove-left)
(global-set-key (kbd "s-j") #'stumpwm-windmove-down)
(global-set-key (kbd "s-k") #'stumpwm-windmove-up)
(global-set-key (kbd "s-l") #'stumpwm-windmove-right)
#+end_src
** Popper
Transient/popup buffers — terminals, help, compilation, messages — are managed
by popper, which tracks them as a group and lets you cycle or dismiss them
together. Popper's display control is enabled, meaning it defers to
=display-buffer-alist= (below) for the actual placement logic rather than
using its own defaults. Buffers are grouped by projectile project so popup
state is workspace-local.
- Popper decides /what/ is a popup
- =display-buffer-alist= decides /where/ it goes
- Terminals always go to the bottom
- Help and compilation go to the bottom unless the window is too narrow (<65
columns), in which case they replace the current buffer instead
- Messages appear at the bottom without stealing focus
#+begin_src emacs-lisp
(use-package popper
:after projectile
:init
(setq popper-reference-buffers
'(("\\*Messages\\*" . hide)
"\\*Error\\*"
"Output\\*$"
("\\*Warnings\\*" . hide)
"\\*scratch\\*"
"\\*Async Shell Command\\*"
"^\\*eshell.*\\*$" eshell-mode
"^\\*shell.*\\*$" shell-mode
"^\\*term.*\\*$" term-mode
"^\\*vterm.*\\*$" vterm-mode
helpful-mode
help-mode
compilation-mode
inferior-python-mode
consult-flymake-mode))
:config
(setq popper-display-control t)
(setq popper-group-function #'popper-group-by-projectile)
(popper-mode +1)
(popper-echo-mode +1))
;; display-buffer-alist rules (replaces shackle)
(setq display-buffer-alist
`(;; Help buffers: bottom split, fall back to same window if narrow
((or (derived-mode . help-mode)
(derived-mode . helpful-mode))
(display-buffer-reuse-window
,(lambda (buf alist)
(when (< (window-width) 65)
(display-buffer-same-window buf alist)))
display-buffer-in-side-window)
(side . bottom)
(slot . 0)
(window-height . 0.35)
(select . t))
;; Compilation: bottom split
((derived-mode . compilation-mode)
(display-buffer-in-side-window)
(side . bottom)
(slot . 1)
(window-height . 0.35)
(select . t))
;; Messages: bottom, no focus steal
("\\*Messages\\*"
(display-buffer-in-side-window)
(side . bottom)
(slot . 2)
(window-height . 0.25)
(select . nil))
;; Terminals: always bottom
((or "^\\*eshell.*\\*$"
"^\\*shell.*\\*$"
"^\\*term.*\\*$"
"^\\*vterm.*\\*$")
(display-buffer-in-side-window)
(side . bottom)
(slot . 3)
(window-height . 0.35)
(select . t))))
#+end_src
** Dired
#+begin_src emacs-lisp
(setq dired-listing-switches "-alh --group-directories-first")
;; opening 'other window' on a subdir enters 2 window dired mode for copy/paste
(setq dired-dwim-target t)
;; refresh dired buffers automatically after file operations
(setq dired-auto-revert-buffer t)
(add-hook 'dired-mode-hook 'dired-hide-details-mode)
(use-package dired-open
:config
(setq dired-open-extensions '(("png" . "nsxiv")
("mkv" . "mpv")
("avi" . "mpv"))))
(use-package nerd-icons-dired
:hook (dired-mode . nerd-icons-dired-mode))
;; Reuse dired buffer when navigating directories
(put 'dired-find-alternate-file 'disabled nil)
(with-eval-after-load 'dired
(evil-collection-define-key 'normal 'dired-mode-map
(kbd "RET") 'dired-find-alternate-file
(kbd "^") 'dired-up-directory))
#+end_src
** Treemacs
#+begin_src emacs-lisp
(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)
#+end_src
** Which-key
#+begin_src emacs-lisp
(use-package which-key
:ensure nil
:init (which-key-mode)
:diminish which-key-mode
:config
(setq which-key-idle-delay 0.3
which-key-add-column-padding 1
which-key-max-description-length 32
which-key-min-column-description-width 20))
#+end_src
** 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
#+begin_src emacs-lisp
(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-jump :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" '(consult-lsp-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")
"g/" '(consult-git-log-grep :which-key "Search git log")
"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")
))
#+end_src
* Notes
#+begin_src emacs-lisp
(use-package deft)
(setq deft-recursive t
deft-extensions '("org" "txt"))
#+end_src
* AI
#+begin_src emacs-lisp
(use-package gptel)
(setq gptl-default-mode 'org-mode)
#+end_src
* Org Mode
#+begin_src emacs-lisp
(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)
#+end_src
** Agenda
#+begin_src emacs-lisp
(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@)")))
#+end_src
** Capture templates
#+begin_src emacs-lisp
(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"))))
#+end_src
#+RESULTS:
| d | Daily Routine | entry | * Scheduled Today |
* Project management
#+begin_src emacs-lisp
(use-package projectile) ; todo config inside use-package
(use-package treemacs-projectile)
#+end_src
* Autocomplete
** Vertico
#+begin_src emacs-lisp
(use-package vertico
:init
(vertico-mode)
:config
(setq vertico-cycle t)
:bind (:map vertico-map
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word)))
#+end_src
** Consult
#+begin_src emacs-lisp
(use-package consult
:bind (:map minibuffer-local-map ("C-r" . consult-history))
:init
(setq register-preview-delay 0.3
register-preview-function #'consult-register-format)
(advice-add #'register-preview :override #'consult-register-window)
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
:config
(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")
(consult-customize
consult-recent-file consult-buffer :preview-key "M-.")
(setq completion-in-region-function #'consult-completion-in-region))
#+end_src
** Generic completion
Idk I think this came from the vertico readme
#+begin_src emacs-lisp
(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))
;; Note: overridden below by orderless
#+end_src
** Orderless
#+begin_src emacs-lisp
(use-package orderless
:init
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion)))))
#+end_src
** Marginalia
#+begin_src emacs-lisp
(use-package marginalia
:bind (:map minibuffer-local-map ("M-A" . marginalia-cycle))
:init
(marginalia-mode))
#+end_src
** Company-mode
#+begin_src emacs-lisp
(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))
#+end_src
** Embark
#+begin_src emacs-lisp
(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)
(general-def :states '(normal visual) "C-." 'embark-act)
(general-def :states '(normal visual) "C-;" 'embark-dwim)
: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))
;; Use mixed indicator: minimal prompt that switches to completing-read for narrowing
(setq embark-indicators
'(embark-mixed-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
#+end_src
#+RESULTS:
** Snippets
#+begin_src emacs-lisp
(use-package yasnippet
:diminish yas-minor-mode)
(use-package yasnippet-snippets
:after lsp-mode)
(yas-global-mode)
#+end_src
* Languages
** LSP
#+begin_src emacs-lisp
(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))
#+end_src
** Rust
#+begin_src emacs-lisp
(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))
#+end_src
** Terraform
#+begin_src emacs-lisp
(use-package terraform-mode
:ensure t)
#+end_src
** Lisp
All lisp-family languages
#+begin_src emacs-lisp
; Required for some reason
;; symex's deps are all countvajhula packages not in any recipe
;; repo. Pre-register all recipes so straight can resolve
;; transitive deps regardless of build order.
(dolist (recipe '((pubsub :host github :repo "countvajhula/pubsub")
(virtual-ring :host github :repo "countvajhula/virtual-ring")
(mantra :host github :repo "countvajhula/mantra")
(repeat-ring :host github :repo "countvajhula/repeat-ring")
(lithium :host github :repo "countvajhula/lithium")))
(straight-register-package recipe))
(use-package pubsub :straight (pubsub :host github :repo "countvajhula/pubsub"))
(use-package virtual-ring :straight (virtual-ring :host github :repo "countvajhula/virtual-ring"))
(use-package mantra :straight (mantra :host github :repo "countvajhula/mantra"))
(use-package repeat-ring :straight (repeat-ring :host github :repo "countvajhula/repeat-ring"))
(use-package lithium :straight (lithium :host github :repo "countvajhula/lithium"))
;; rigpa and symex-rigpa disabled for now — rigpa pulls in a
;; newer transient that needs cond-let which straight can't resolve
;; (use-package rigpa
;; :straight
;; (rigpa
;; :host github
;; :repo "countvajhula/rigpa"))
(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)
;; RET in normal mode enters symex
(evil-define-key 'normal symex-lisp-mode-map
(kbd "RET") #'symex-mode-interface)
;; ESC in insert mode goes to symex, then normal on next ESC
(evil-define-key 'insert symex-lisp-mode-map
(kbd "<escape>") (lambda ()
(interactive)
(evil-normal-state)
(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 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))
#+end_src
*** Scheme
**** Guile
#+begin_src emacs-lisp
(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")
":"))
))
#+end_src
** Go
#+begin_src emacs-lisp
(use-package go-mode)
(add-hook 'go-mode-hook 'lsp-deferred)
#+end_src
* Markups
** Markdown
#+begin_src emacs-lisp
(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)
#+end_src
** Beancount
This is mostly lifted from the doom emacs config. Beancount-mode by default has a pretty borked tab function.
#+begin_src emacs-lisp
(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)
#+end_src
** Epub
#+begin_src emacs-lisp
(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))
#+end_src
* Containers
** Docker
#+begin_src emacs-lisp
(use-package dockerfile-mode)
(use-package docker-compose-mode)
;; (use-package docker)
;; Todo: lsp-docker? docker-tramp? docker-cli?
#+end_src
** Kubernetes
#+begin_src emacs-lisp
(use-package k8s-mode)
;; kubernetes
;; kubernetes-evil
;; kubernetes-helm
;; kubernetes-tramp
#+end_src
* Shells
** Enable fish completions
Uses the external fish program to provide fish-style completions to eshell etc
#+begin_src emacs-lisp
(use-package fish-completion)
(when (and (executable-find "fish")
(require 'fish-completion nil t))
(global-fish-completion-mode))
#+end_src
* Git
#+begin_src emacs-lisp
(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)
#+end_src
* Web browsing and youtube
#+begin_src emacs-lisp
;; 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")
#+end_src
* Email, rss, newsgroups
Oh, boy. Here we go.
[[https://www.gnu.org/software/emacs/manual/html_node/auth][This]] will come in handy.
(=TODO=: smtp header)
#+begin_src emacs-lisp
(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)
#+end_src
* Emacs Meta
** Explain-Pause
Scrutinize which processes are gumming up emacs
#+begin_src emacs-lisp
;; (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))
#+end_src
* Todo
** General
- Refactor to use use-package more effectively
- Use :mode and :config like [[https://ianyepan.github.io/posts/setting-up-use-package/][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
- [[https://www.youtube.com/watch?v=3-sLBaJAtew][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
- [[https://youtu.be/UtqE-lR2HCA?t=5027][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)
- [[https://old.reddit.com/r/emacs/comments/o04it0/share_your_prettifysymbolsalist/][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.
#+begin_src emacs-lisp :tangle no
(add-to-list 'display-buffer-alist
'("\\*ielm\\*"
(display-buffer-in-side-window)
(side . left)
(window-width . 50)))
#+end_src
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)
- [[https://youtu.be/UtqE-lR2HCA?t=5134][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
** System crafters videos
- [[https://www.youtube.com/watch?v=CUkuyW6hr18][5 Hacks to improve org roam]]
- [[https://www.youtube.com/watch?v=C1kwStlEick][Improving the IRC experience]]
- [[https://www.youtube.com/watch?v=J0OaRy85MOo][Streamlined completions with vertico]]
- [[https://www.youtube.com/watch?v=XZjyJG-sFZI][Keeping your folders clean while using emacs]]
- [[https://www.youtube.com/watch?v=zMzkorlfqLA][Static site generator from org mode]]
- [[https://www.youtube.com/watch?v=za99DwdZEyg][Automated website publishing w/ org and sourcehut]]
- [[https://www.youtube.com/watch?v=SCPoF1PTZpI][Emacs presentations]]
- [[https://www.youtube.com/watch?v=0C16LLHGYzk&t=1882s][Converting a literate config to guix home]]
- [[https://www.youtube.com/playlist?list=PLEoMzSkcN8oM-kA19xOQc8s0gr0PpFGJQ][Emacs mail playlist]]
- [[https://www.youtube.com/watch?v=qk2Is_sC8Lk][Embark]]
** 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
#+begin_src emacs-lisp
(setq ediff-window-setup-function #'ediff-setup-windows-plain)
#+end_src
- 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)
- [[https://elpa.gnu.org/packages/adaptive-wrap.html]]
- 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