#+PROPERTY: header-args:emacs-lisp :tangle yes #+TITLE: Emacs Configuration * Literate emacs configuration I can never rembember what various configs do. Jumping on this literate emacs config to see if it helps! ** Start the emacs server Starting as a server lets me connect externally to do things like change themes, save buffers via cron and other such dumbary! #+begin_src emacs-lisp (require 'bind-key) (load "server") (unless (server-running-p) (server-start)) #+end_src ** Interface and Behavior *** Interface Global font #+begin_src emacs-lisp (set-frame-font "Go Mono 11") #+end_src Use 80 columns, this helps keep things readable when windows are split #+begin_src emacs-lisp (setq whitespace-style '(trailing lines space-before-tab) whitespace-line-column 80) (setq-default fill-column 80) #+end_src I know I am in emacs, don't need to see the startup screen. #+begin_src emacs-lisp (setq inhibit-startup-screen t) #+end_src **** Use UTF8 where ever possible #+begin_src emacs-lisp (prefer-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-keyboard-coding-system 'utf-8) #+end_src **** Change various UI bits #+begin_src emacs-lisp (tool-bar-mode -1) (menu-bar-mode -1) (scroll-bar-mode -1) (column-number-mode +1) (global-font-lock-mode 1) #+end_src **** direnv #+begin_src emacs-lisp (use-package direnv :config (direnv-mode)) #+end_src **** project.el #+begin_src emacs-lisp (setq project-list-file (expand-file-name "~/.emacs.d/projects")) #+end_src **** transient #+begin_src emacs-lisp (setq transient-history-file (expand-file-name "~/.emacs.d/transient")) #+end_src *** Behavior Switch various defaults to be more comfortable for myself. #+begin_src emacs-lisp (fset 'yes-or-no-p 'y-or-n-p) (show-paren-mode t) (setq desktop-dirname "~/.emacs.d/" desktop-base-file-name "emacs.desktop" desktop-base-lock-name "lock" desktop-path (list desktop-dirname) desktop-save t desktop-files-not-to-save "^$" ;reload tramp paths desktop-load-locked-desktop nil desktop-auto-save-timeout 30) (desktop-save-mode 1) (setq backup-directory-alist '(("." . "~/.emacs-saves"))) (setq auto-mode-alist (append (list '("\\.gpg$" . sensitive-minor-mode) ) auto-mode-alist)) (setq auth-sources '("/run/secrets/netrc")) #+end_src Use spelling and auto-fill when we are in text mode. #+begin_src emacs-lisp (add-hook 'text-mode-hook (lambda () (auto-fill-mode 1) (turn-on-flyspell))) #+end_src #+begin_src emacs-lisp (setq eshell-history-file-name (expand-file-name "~/.emacs.d/eshell/history")) #+end_src ** Unset custom-file The customization file mostly just causes churn in the SCM so we disable it here. #+begin_src emacs-lisp (setq custom-file (make-temp-file "")) #+end_src * Packages ** rust-mode #+begin_src emacs-lisp (use-package rust-mode) #+end_src ** uxntal #+begin_src emacs-lisp (use-package uxntal-mode) #+end_src ** breadcrumb Handy breadcrumbs for seeing where things are in the LSP. #+begin_src emacs-lisp (use-package breadcrumb) #+end_src ** ollama Simple wrapper for ~ollama~ that lets me pipe regions and what not into various buffers. #+begin_src emacs-lisp (use-package ollama :init (setopt ollama:endpoint "https://ollama.otter-alligator.ts.net/api/generate" ollama:language "English" ollama:model "llama3.1")) #+end_src ** web-mode #+begin_src emacs-lisp (use-package web-mode :config (setq web-mode-markup-indent-offset 2) (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.php?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.ts?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.js?\\'" . web-mode))) #+end_src ** scpaste Post pastes to https://paste.suah.dev #+begin_src emacs-lisp (use-package scpaste :config (setq scpaste-scp-destination "suah.dev:/var/www/paste" scpaste-http-destination "https://paste.suah.dev")) #+end_src ** htmlize This is needed for publishing org stuff #+begin_src emacs-lisp (use-package htmlize) #+end_src ** parchment-theme This is a nice theme that resembles acme in plan9. Minimal. #+begin_src emacs-lisp (use-package parchment-theme :config (load-theme 'parchment t)) #+end_src ** keychain-environment I make heavy use of ~ssh-agent~ this lets emacs pickup / use the existing agents I have running. #+begin_src emacs-lisp (use-package keychain-environment ;;:pin "melpa" :init (keychain-refresh-environment)) #+end_src ** ivy ~ivy~ is fantastic. It gives me nice visual search for buffers, code.. etc. Combined with ~smex~ for sorting (shows last used things first) and ~counsel~ (extends ivy into various areas like the help stuff). #+begin_src emacs-lisp (use-package counsel) (setq smex-save-file (expand-file-name "~/.emacs.d/smex.save")) (use-package smex) (use-package ivy :hook (after-init . ivy-mode) :bind ("C-s" . swiper-isearch) ("M-x" . counsel-M-x) ("C-x C-f" . counsel-find-file) ("C-x b" . ivy-switch-buffer)) #+end_src ** magit Magit is a awesome. Not sure what else to say about it. :P #+begin_src emacs-lisp (use-package magit :bind ("C-c m" . magit-status) :init (setq magit-completing-read-function 'ivy-completing-read)) #+end_src ** lsp Use ~eglot~ for lsp stuff. It's built in and shows a bit more information for auto-completion stuff. #+begin_src emacs-lisp (use-package eglot :config (add-hook 'elm-mode-hook 'eglot-ensure) (add-hook 'go-mode-hook 'eglot-ensure) (add-hook 'haskell-mode-hook 'eglot-ensure) (add-hook 'nix-mode-hook 'eglot-ensure) (add-hook 'perl-mode-hook 'eglot-ensure) (add-hook 'ruby-mode-hook 'eglot-ensure) (add-hook 'rust-mode-hook 'eglot-ensure) (add-hook 'typescript-mode-hook 'eglot-ensure) (add-to-list 'eglot-server-programs '(c-mode . ("clangd"))) (add-to-list 'eglot-server-programs '(c++-mode . ("clangd"))) (add-to-list 'eglot-server-programs '(rust-mode . ("rust-analyzer"))) (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename) (define-key eglot-mode-map (kbd "C-c f") 'eglot-format) :hook (eglot-managed-mode . (lambda() (add-hook 'before-save-hook 'eglot-format-buffer nil 'local)))) #+end_src ** company and friends ~company~ allows for auto-completion of various things. It can interface with ~lsp-mode~ to complete things like Go. #+begin_src emacs-lisp (use-package company :config (setq company-tooltip-limit 20 company-minimum-prefix-length 1 company-idle-delay .3 company-echo-delay 0) :hook (prog-mode . company-mode)) #+end_src ** gitgutter This gives me a nice in-ui way to see modifications and what not. #+begin_src emacs-lisp (use-package git-gutter :hook (after-init . global-git-gutter-mode) :config (global-set-key (kbd "C-x g r") 'git-gutter:revert-hunk) (global-set-key (kbd "C-x g p") 'git-gutter:previous-hunk) (global-set-key (kbd "C-x g n") 'git-gutter:next-hunk)) #+end_src ** shell I don't often use the shell from emacs, but when I do these bits make it easier for me to treat it like a regular shell. #+begin_src emacs-lisp ;; Kill terminal buffers on exit so I din't have to kill the buffer after I exit. (defadvice term-handle-exit (after term-kill-buffer-on-exit activate) (kill-buffer)) #+end_src * Language Configurations ** Shell #+begin_src emacs-lisp (use-package shfmt) (add-hook 'sh-mode-hook 'shfmt-on-save-mode) #+end_src ** Typescript #+begin_src emacs-lisp (use-package typescript-mode) #+end_src ** Nix #+begin_src emacs-lisp (use-package nix-mode :mode "\\.nix\\'") #+end_src ** Elm #+begin_src emacs-lisp (use-package elm-mode) #+end_src ** Haskell #+begin_src emacs-lisp (use-package haskell-mode) #+end_src ** Go *** go-add-tags This lets one select a ~struct~ or similar and auto add the ~`json:"NAME"`~ bits. #+begin_src emacs-lisp (use-package go-add-tags) #+end_src *** go-mode This allows for things like ~gofmt~ and auto adding / removing of imports. #+begin_src emacs-lisp (use-package go-mode :bind ("C-c t" . go-add-tags)) (defun xin-eglot-organize-imports () (interactive) (eglot-code-actions nil nil "source.organizeImports" t)) (defun lsp-go-install-save-hooks () (add-hook 'before-save-hook 'xin-eglot-organize-imports nil t)) (add-hook 'go-mode-hook #'lsp-go-install-save-hooks) #+end_src *** go-eldoc This extends eldoc to be able to speak Go - quite handy for quickly looking up what things do. #+begin_src emacs-lisp (use-package go-eldoc :hook (go-mode . go-eldoc-setup)) #+end_src * org-mode Oh ~org-mode~. It's the reason I started using emacs.. and it's the reason I can't quit! ** Config #+begin_src emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t) (dot . t) (shell . t) (latex . t))) #+end_src ** Publish bits I publish some of my notes [[https://suah.dev/p][on suah.dev/p]]. Also some recipes. #+begin_src emacs-lisp (setq org-export-with-broken-links t) (defun exo-touch-and-publish (plist filename dir) (progn (set-file-times "~/org-roam/index.org") (org-html-publish-to-html plist filename dir))) (setq my-org-publish-alist '(("exo" :components ("org-roam" "org-roam-static" "org-roam-org")) ("bolddaemon" :components ("bolddaemon-web" "bolddaemon-static")) ("notes" :components ("org-notes" "notes-static" "notes-rss")) ("deftly" :components ("deftly-blog" "deftly-static")) ("ohmyksh" :components ("ohmy-web" "ohmy-static")) ("org-roam" :publishing-directory "/ssh:suah.dev:/var/www/exo.suah.dev/" :recursive t :html-link-home "http://exo.suah.dev/" :html-link-up "../" :html-head "" :publishing-function exo-touch-and-publish :base-directory "~/org-roam") ("org-roam-org" :publishing-directory "/ssh:suah.dev:/var/www/exo.suah.dev/" :publishing-function org-org-publish-to-org :recursive t :base-directory "~/org-roam") ("org-roam-static" :base-directory "~/org-roam" :recursive t :publishing-directory "/ssh:suah.dev:/var/www/exo.suah.dev/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|svg" :publishing-function org-publish-attachment) ("org-roam-rss" :publishing-directory "/ssh:suah.dev:/var/www/exo.suah.dev/" :publishing-function org-rss-publish-to-rss :rss-extension "xml" :base-directory "~/org-roam") ("org-notes" :auto-preamble t :auto-sitemap t :headline-levels 4 :publishing-directory "/ssh:suah.dev:/var/www/suah.dev/p/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :html-head "" :html-link-home "http://suah.dev/p/" :html-link-up "../" :style-include-default nil :sitemap-filename "index.org" :sitemap-title "Notes" :with-title t :author-info nil :creator-info nil :base-directory "~/org/notes") ("deftly-blog" :auto-preamble t :auto-sitemap t :headline-levels 1 :publishing-directory "/ssh:suah.dev:/var/www/deftly.net/new/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :html-head "" :html-link-home "http://deftly.net/new" :html-link-up "../" :style-include-default nil :sitemap-title "Deftly.net" :with-title t :author-info t :creator-info nil :base-directory "~/org/deftly") ("ohmy-web" :auto-preamble t :auto-sitemap nil :headline-levels 2 :publishing-directory "/ssh:suah.dev:/var/www/deftly.net/ohmyksh/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :html-head "" :html-link-home "http://deftly.net/ohmyksh" :html-link-up "../" :style-include-default nil :with-title t :author-info t :creator-info nil :base-directory "~/src/ohmyksh") ("notes-static" :base-directory "~/org/notes" :publishing-directory "/ssh:suah.dev:/var/www/suah.dev/p/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|svg" :recursive t :publishing-function org-publish-attachment) ("deftly-static" :base-directory "~/org/deftly" :publishing-directory "/ssh:suah.dev:/var/www/deftly.net/new/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg" :recursive t :publishing-function org-publish-attachment) ("ohmy-static" :base-directory "~/src/ohmyksh" :publishing-directory "/ssh:suah.dev:/var/www/deftly.net/ohmyksh/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg" :recursive t :publishing-function org-publish-attachment) ("notes-rss" :publishing-directory "/ssh:suah.dev:/var/www/suah.dev/p/" :publishing-function org-rss-publish-to-rss :recursive t :rss-extension "xml" :section-numbers nil :exclude ".*" :include ("index.org") :table-of-contents nil :base-directory "~/org/notes") ("recipes" :auto-preamble t :auto-sitemap t :headline-levels 4 :publishing-directory "/ssh:suah.dev:/var/www/suah.dev/recipes/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :html-head "" :html-link-home "http://suah.dev/recipes/" :html-link-up "../" :style-include-default nil :sitemap-filename "index.org" :sitemap-title "Recipes" :with-title t :author-info nil :creator-info nil :base-directory "~/org/recipes") ("bolddaemon-web" :auto-preamble t :auto-sitemap t :headline-levels 4 :publishing-directory "/ssh:suah.dev:/var/www/bolddaemon.com/" :publishing-function org-html-publish-to-html :recursive t :section-numbers nil :html-link-home "http://bolddaemon.com" :html-link-up "../" :style-include-default nil :with-title t :author-info nil :creator-info nil :base-directory "~/org/bold.daemon") ("bolddaemon-static" :base-directory "~/org/bold.daemon" :publishing-directory "/ssh:suah.dev:/var/www/bolddaemon.com/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg" :recursive t :publishing-function org-publish-attachment) )) #+end_src ** Capture templates #+begin_src emacs-lisp (setq my-org-capture-templates `(("t" "TODO" entry (file+headline "~/org/todo.org" "TODOs") ,(concat "* TODO %?\n" ":PROPERTIES:\n" ":LOGGING: TODO(!) WAIT(!) DONE(!) CANCELED(!)\n" ":END:\n") :prepend t) ("f" "TODO with File" entry (file+headline "~/org/todo.org" "TODOs") ,(concat "* TODO %?\n" ":PROPERTIES:\n" ":LOGGING: TODO(!) WAIT(!) DONE(!) CANCELED(!)\n" ":END:\n" "%i\n %a") :prepend t) ("b" "Bug" entry (file+olp+datetree "~/org/bugs.org" "Bugs") "* BUG %?\nEntered on %U\n :PROPERTIES:\n :FILE: %a\n :END:\n" :prepend t) ("p" "Protocol" entry (file+headline "~/org/links.org" "Links") "* %^{Title}\nSource: %u, %c\n #+BEGIN_QUOTE\n%i\n#+END_QUOTE\n\n\n%?") ("L" "Protocol Link" entry (file+headline "~/org/links.org" "Links") "* %? %:link\n%:description\n") ("j" "Journal" entry (file+olp+datetree "~/org/journal.org") "* %?\nEntered on %U\n %i\n"))) #+end_src ** org #+begin_src emacs-lisp (use-package org :hook (org-mode . (lambda () (turn-on-flyspell) (auto-revert-mode) (auto-fill-mode 1))) :bind ("C-c c" . org-capture) ("C-c p" . org-publish) ("C-c l" . org-store-link) ("C-c a" . org-agenda) ("C-c b" . org-iswitchb) :config (load-library "find-lisp") (setq org-directory "~/org" org-agenda-files (find-lisp-find-files "~/org" "\.org$") org-startup-indented t org-log-done 'time org-export-with-sub-superscripts nil org-html-inline-images t org-log-into-drawer t org-src-tab-acts-natively t org-agenda-skip-scheduled-if-deadline-is-shown t org-todo-keywords '((sequence "TODO(t)" "|" "DONE(d)") (sequence "REPORT(r)" "BUG(b)" "KNOWNCAUSE(k)" "|" "FIXED(f)") (sequence "|" "CANCELED(c)"))) (setq org-publish-project-alist my-org-publish-alist) (setq org-capture-templates my-org-capture-templates)) (use-package org-contrib) (use-package ox-rss) #+end_src ** Extra bits #+begin_src emacs-lisp (use-package org-journal :defer t :config (setq org-journal-dir "~/org/journal/" org-journal-file-format "%Y/%m-%d" org-journal-date-format "%A, %d %B %Y")) #+end_src Add in some org-mode helpers: - ~org-habit~ lets me keep track of TODOs and other things. - ~org-checklist~ lets me reset checklists for reoccurring tasks. - This requires one to ~pkg_add a2ps~. - ~RESET_CHECK_BOXES~ property to be set to ~t~ on a task headline. (properties can be set via ~C-c C-x d~ #+begin_src emacs-lisp (require 'org-habit) (require 'org-checklist) #+end_src Custom agenda commands for various things. - ~Daily habits~ shows how well I am keeping track of daily things. #+begin_src emacs-lisp (setq org-agenda-custom-commands '(("h" "Daily habits" ((agenda "")) ((org-agenda-show-log t) (org-agenda-ndays 7) (org-agenda-log-mode-items '(state)))))) #+end_src #+begin_src emacs-lisp (use-package org-roam :after org :custom (org-roam-directory (file-truename "~/org-roam/")) (org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+date: %U\n") :unnarrowed t) ("b" "book" plain "- Author: /%^{Author}/\n- Year: /%^{Year}/\n\n* Highlights / Notes\n" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+date: %U\n#+filetags: :Book:\n") :unnarrowed t) )) :bind (("C-c n l" . org-roam-buffer-toggle) ("C-c n f" . org-roam-node-find) ("C-c n g" . org-roam-graph) ("C-c n r" . org-roam-ref-add) ("C-c n s" . org-roam-db-sync) ("C-c n t" . org-roam-tag-add) ("C-c n i" . org-roam-node-insert) ("C-c n c" . org-roam-capture) ("C-c n j" . org-roam-dailies-capture-today)) :config (setq org-roam-completion-everywhere t) (setq org-roam-node-display-template (concat "${title:40} " (propertize "${tags:40}" 'face 'org-tag) "${file}")) (require 'org-roam-protocol)) #+end_src Extending org with the ability to transclude makes for a powerhouse! #+begin_src emacs-lisp (use-package org-transclusion :after org) #+end_src * gnus [2024-08-22 Thu] Might need to switch go gnus. mu4e has been not showing new mail for some things and having to sync state between two sources is a pita. #+begin_src emacs-lisp (setq gnus-use-full-window nil gnus-fetch-old-headers t gnus-select-method '(nnimap "imap.fastmail.com") gnus-secondary-select-methods '((nntp "news.gwene.org")) gnus-sum-thread-tree-false-root "" gnus-sum-thread-tree-indent " " gnus-sum-thread-tree-leaf-with-other "├► " gnus-sum-thread-tree-root "" gnus-sum-thread-tree-single-leaf "╰► " gnus-sum-thread-tree-vertical "│" gnus-summary-line-format "%U%R%z %(%&user-date>; %-15,15f %B%s%)\n" gnus-summary-thread-gathering-function 'gnus-gather-threads-by-references gnus-thread-sort-functions '(gnus-thread-sort-by-date) gnus-user-date-format-alist '((t . "%Y-%m-%d %H:%M"))) (with-eval-after-load 'gnus (setq gnus-select-method '(nnimap "imap.fastmail.com") gnus-secondary-select-methods '((nntp "news.gwene.org")))) #+end_src * mu4e ~mu~ has been the best mail client for me on emacs. ** Initializing mu The defaults ~mu~ uses make no sense. ~~/.cache~ is for .. caching data, not persistent databases.. So we init things with sane defaults: #+begin_src shell mu init --muhome=/home/qbit/.mu -m /home/qbit/Maildir/fastmail/ --my-address="aaron@bolddaemon.com" #+end_src ** General mail configuration #+begin_src emacs-lisp (require 'smtpmail) (setq user-mail-address "aaron@bolddaemon.com" user-full-name "Aaron Bieber" message-send-mail-function 'smtpmail-send-it message-kill-buffer-on-exit t smtpmail-smtp-user "qbit@fastmail.com" smtpmail-smtp-server "smtp.fastmail.com" smtpmail-smtp-service 465 smtpmail-default-smtp-server "smtp.fastmail.com" smtpmail-stream-type 'ssl) #+end_src ** mu4e specific configs #+begin_src emacs-lisp (use-package mu4e :init (setq mail-user-agent 'mu4e-user-agent read-mail-command 'mu4e mu4e-get-mail-command "mbsync fastmail" mu4e-update-interval 420 mu4e-compose-context-policy nil mu4e-context-policy 'pick-first mu4e-drafts-folder "/Drafts" mu4e-sent-folder "/Sent Items" mu4e-trash-folder "/Trash" mu4e-maildir-shortcuts '( ("/INBOX" . ?i) ("/Archive" . ?a) ("/Sent Items" . ?s)) org-mu4e-link-query-in-headers-mode nil mu4e-attachment-dir (lambda (fname mtype) (cond ((and fname (string-match "\\.diff$" fname)) "~/patches") ((and fname (string-match "\\.patch$" fname)) "~/patches") ((and fname (string-match "\\.diff.gz$" fname)) "~/patches") (t "~/Downloads"))) mu4e-bookmarks `(( :name "Inbox" :query "maildir:/Inbox AND NOT flag:trashed" :key ?i) ( :name "TODO" :query "maildir:/TODO AND NOT flag:trashed" :key ?T) ( :name "Unread messages" :query "flag:unread AND NOT flag:trashed AND NOT list:ports-changes.openbsd.org AND NOT list:source-changes.openbsd.org" :key ?u) ( :name "Today's messages" :query "date:today..now" :key ?d) ( :name "Hackers" :query "list:hackers.openbsd.org AND NOT flag:trashed" :key ?h) ( :name "Bugs" :query "list:bugs.openbsd.org AND NOT flag:trashed" :key ?b) ( :name "Tech" :query "list:tech.openbsd.org AND NOT flag:trashed" :key ?t) ( :name "Ports" :query "list:ports.openbsd.org AND NOT flag:trashed" :key ?p) ( :name "Misc" :query "list:misc.openbsd.org AND NOT flag:trashed" :key ?m) ( :name "9front" :query "list:9front.9front.org AND NOT flag:trashed" :key ?9) ( :name "GOT" :query "list:gameoftrees.openbsd.org AND NOT flag:trashed" :key ?g)))) #+end_src