#+AUTHOR: Seth Morabito #+EMAIL: web@loomcom.com #+TITLE: GNU Emacs Configuration File #+OPTIONS: toc:nil ':nil #+STARTUP: showall * Table of Contents - [[#introduction][Introduction]] - [[#basic-setup][Basic Setup]] - [[#behavior][Behavior]] - [[#appearance][Appearance]] - [[#package-management][Package Management]] - [[#development][Development and Languages]] - [[#email][Email]] * Introduction :PROPERTIES: :CUSTOM_ID: introduction :END: This document contains my entire GNU Emacs configuration file in /Literate Programming/ style. Literate Programming strives to invert the normal programming paradigm; Instead of source code scattered with comments, Literate Programming encourages code to be written as a human readable document, in prose, with source code blocks embedded inside of it. The source code is later interpreted, while the human readable prose is ignored by the interpreter or compiler. The magic here is provided by =org-babel=, which provides a method for extracting and evaluating Emacs Lisp expressions inside an =org-mode= file. The only thing needed in the main Emacs init file is this line: #+BEGIN_EXAMPLE (org-babel-load-file "~/.emacs.d/configuration.org") #+END_EXAMPLE * Basic Setup :PROPERTIES: :CUSTOM_ID: basic-setup :END: ** Lexical Scoping By default, Emacs uses dynamic binding. Some aspects of my configuration rely on /lexical binding/, so that gets turned on first! #+BEGIN_SRC emacs-lisp ;; -*- lexical-binding: t; -*- #+END_SRC ** Where to Store Customization First things first. Because I use =org-babel= and literate programming style for my config, I want my =init.el= file to be as small as possible. For that reason, I want Emacs to put all of its generated fluff into a separate file that is actually outside the Emacs directory. #+BEGIN_SRC emacs-lisp (let ((local-custom-file "~/.emacs-custom.el")) (when (not (file-exists-p local-custom-file)) (write-region "" nil local-custom-file)) (setq custom-file local-custom-file) (load custom-file)) #+END_SRC ** Default Directory Some builds of emacs on exotic operating systems don't automatically know that I want my home directory to be the default find-file directory. #+BEGIN_SRC emacs-lisp (setq default-directory (expand-file-name "~/")) #+END_SRC ** Reducing Clutter Next, I like to immediately reduce what I consider to be visual clutter. Your milage may vary, but for me this means turning off startup messages, the splash screen, tool bar, and tooltips. #+BEGIN_SRC emacs-lisp (setq warning-minimum-level :emergency inhibit-startup-message t inhibit-splash-screen t) (when window-system (progn (tool-bar-mode -1) (tooltip-mode -1))) #+END_SRC That said, I do often want a menu and a scroll bar, so those can stay on. #+BEGIN_SRC emacs-lisp (menu-bar-mode t) (when (fboundp #'scroll-bar-mode) (scroll-bar-mode t)) #+END_SRC ** Local lisp directory Some packages are kept as submodules under =~/.emacs.d/lisp/=, so I add this to my load path. #+BEGIN_SRC emacs-lisp (let ((default-directory "~/.emacs.d/lisp/")) (normal-top-level-add-subdirs-to-load-path)) #+END_SRC * Behavior :PROPERTIES: :CUSTOM_ID: behavior :END: ** Better Defaults A few minor tweaks that I enjoy. First, I like to take space from all windows whenever I split a window #+BEGIN_SRC emacs-lisp (setq-default window-combination-resize t) #+END_SRC Next, stretch the cursor to fill a full glyph cell #+BEGIN_SRC emacs-lisp (setq-default x-stretch-cursor t) #+END_SRC On macOS, I turn off =--dired=, because =ls= does not support it there! #+BEGIN_SRC emacs-lisp (when (string= system-type "darwin") (setq dired-use-ls-dired nil)) #+END_SRC I completely disable lockfiles, which I don't need, and which only cause trouble for me. #+BEGIN_SRC emacs-lisp (setq create-lockfiles nil) #+END_SRC ** Turn Off Suspending Emacs with Control-Z I disable the default "Control-Z" behavior of suspending emacs, because I find that I accidentally hit this key combo way too often when my clumsy fingers are trying to hit "Control-X" #+BEGIN_SRC emacs-lisp (global-unset-key [(control z)]) (global-unset-key [(control x)(control z)]) #+END_SRC ** Long line improvements Here are a few settings that help improve Emacs performance when editing very long lines. These tips are taken from [[https://200ok.ch/posts/2020-09-29_comprehensive_guide_on_handling_long_lines_in_emacs.html][200ok.ch]]. First, we tell Emacs that we're really only using left-to-right text. #+BEGIN_SRC emacs-lisp (setq-default bidi-paragraph-direction 'left-to-right) (if (version<= "27.1" emacs-version) (setq bidi-inhibit-bpa t)) #+END_SRC Next we set global "so-long-mode", which tries to tell Emacs to be smarter about opening files with long lines. #+BEGIN_SRC emacs-lisp (if (version<= "27.1" emacs-version) (global-so-long-mode 1)) #+END_SRC ** Tidying Up the Working Directory Emacs, by default, keeps backup files in the current working directory. I much prefer to keep all backup files together in one place. This will put them all into the directory =~/.emacs.d/backups/=, creating the directory if it does not exist. #+BEGIN_SRC emacs-lisp (if (not (file-exists-p "~/.emacs.d/backups/")) (make-directory "~/.emacs.d/backups/" t)) (setq backup-directory-alist '(("." . "~/.emacs.d/backups/"))) (setq auto-save-file-name-transforms '((".*" "~/.emacs.d/backups/" t))) (setq backup-by-copying t) (setq auto-save-default t) #+END_SRC Next, these settings control how many backup versions to keep, and specify that older versions should be silently deleted (don't warn me). #+BEGIN_SRC emacs-lisp (setq kept-old-versions 2) (setq kept-new-versions 5) (setq delete-old-versions t) #+END_SRC ** Spelling Spelling is important (I'm terrible at spelling). This block configures =ispell=. It relies on an external dictionary program; first it will check to see if =aspell= is available, and if so, set it as the default spell checking program. If that's not found, it will search for =hunspell= and set that. If neither is found, oh well, no spell checking program for us. #+BEGIN_SRC emacs-lisp (setq ispell-local-dictionary "en_US" ispell-local-dictionary-alist '(("en_US" "[[:alpha]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8))) (cond ((executable-find "aspell") (setq ispell-program-name "aspell")) ((executable-find "hunspell") (setq ispell-program-name "hunspell")) (t (setq ispell-program-name nil))) #+END_SRC ** Scrolling =scroll-step= controls the number of lines that the window will scroll automatically when the cursor moves off the screen. By default, it will jump you so that the cursor is centered (vertically) after scrolling. I really don't like this behavior, so I set it to =1= so the window will only move by a single line. #+BEGIN_SRC emacs-lisp (setq scroll-step 1) #+END_SRC Next, setting =scroll-conservatively= to a very large number will further prevent automatic centering. The value =10,000= comes from a suggestion on the [[https://www.emacswiki.org/emacs/SmoothScrolling][Emacs Wiki]]. #+BEGIN_SRC emacs-lisp (setq scroll-conservatively 10000) #+END_SRC ** Indentation I always prefer 4 spaces for indents. #+BEGIN_SRC emacs-lisp (setq-default c-basic-offset 4) (setq-default sh-basic-offset 4) (setq-default tab-width 4) (setq-default indent-tabs-mode nil) #+END_SRC And next, I want to fix how multi-line initialization in C-like languages is handled (for example, when initializing an array or a struct). By default, elements after the =brace-list-intro= character get lined up directly below it, like this: #+BEGIN_EXAMPLE int array[3] = { 0, 1, 2, }; #+END_EXAMPLE By setting the correct value for =c-set-offset 'brace-list-intro=, I can get what I consider to be a much better offset that looks like this: #+BEGIN_EXAMPLE int array[3] = { 0, 1, 2, }; #+END_EXAMPLE Here's the setting: #+BEGIN_SRC emacs-lisp (c-set-offset 'brace-list-intro '+) #+END_SRC ** Tramp /Tramp/ is a useful mode that allows editing files remotely. The first thing I like to do is set the default connection method. #+BEGIN_SRC emacs-lisp (setq tramp-default-method "ssh") #+END_SRC Then, I up some default values to make editing large directories happy. #+BEGIN_SRC emacs-lisp (setq max-lisp-eval-depth 10000) ; default is 400 (setq max-specpdl-size 10000) ; default is 1000 #+END_SRC ** Recent Files Keep a list of recently opened files #+BEGIN_SRC emacs-lisp (recentf-mode 1) (setq-default recent-save-file "~/.emacs.d/recentf") #+END_SRC ** Exec Path If certain directories exist, they should be added to =exec-path= and the =PATH= environment variable. #+BEGIN_SRC emacs-lisp (setq loomcom-append-to-path '("/usr/local/bin" "/opt/homebrew/bin" "/opt/homebrew/opt/llvm/bin" "~/bin" "~/.local/bin" "/Library/TeX/texbin" "~/.cargo/bin")) (mapc #'(lambda (dir) (when (file-exists-p (expand-file-name dir)) ;; Add the directory to exec-path (add-to-list 'exec-path (expand-file-name dir)) ;; Add the directory to the PATH environment variable, but ;; replace `~' with `$HOME' (setenv "PATH" (concat (getenv "PATH") (concat ":" (replace-regexp-in-string "^~" "$HOME" dir)))))) loomcom-append-to-path) #+END_SRC ** Encryption Enable integration between Emacs and GPG. #+BEGIN_SRC emacs-lisp (setenv "GPG_AGENT_INFO" nil) (require 'epa-file) (require 'password-cache) (setq epg-pgp-program "gpg") (setq password-cache-expiry (* 15 60)) (setq epa-file-cache-passphrase-for-symmetric-encryption t) (setq epa-pinentry-mode 'loopback) #+END_SRC ** Window Navigation I frequently split my Emacs windows both horizontally and vertically. Navigation between windows with =C-x o= is tedious, so I use =C-= to navigate between windows. (N.B.: This overrides the default behavior of moving forward or backward by word using =C-= nad =C-=, so keep that in mind) The typical way of doing this would be just to set the following in your config: #+BEGIN_EXAMPLE (windmove-default-keybindings 'ctrl) #+END_EXAMPLE However, there's one downside here: If you accidentally try to navigate to a window that doesn't exist, it raises an error and/or traps into the debugger (if ~debug-on-error~ is enabled). No good! So instead, I wrap in a lambda that ignores errors (Inspired by: [[https://www.emacswiki.org/emacs/WindMove][EmacsWiki WindMove]]) #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-") #'(lambda () (interactive) (ignore-errors (windmove-left)))) (global-set-key (kbd "C-") #'(lambda () (interactive) (ignore-errors (windmove-right)))) (global-set-key (kbd "C-") #'(lambda () (interactive) (ignore-errors (windmove-up)))) (global-set-key (kbd "C-") #'(lambda () (interactive) (ignore-errors (windmove-down)))) #+END_SRC ** A Resize Helper I like a standard editor size of 88 by 66 characters (If you know why, you win a cookie!) This helper will set that size automatically. #+BEGIN_SRC emacs-lisp (defun set-frame-standard-size () (interactive) (set-frame-size (selected-frame) 88 66)) (defun set-frame-double-size () (interactive) (set-frame-size (selected-frame) 176 66)) #+END_SRC ** Other Key Bindings *** Shortcut for "Goto Line" #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-x l") #'goto-line) #+END_SRC *** Shortcut for "Delete Trailing Whitespace" #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-c C-x w") #'delete-trailing-whitespace) #+END_SRC ** Miscellaneous Settings Turn off the infernal bell, both visual and audible. #+BEGIN_SRC emacs-lisp (setq ring-bell-function 'ignore) #+END_SRC Enable the =upcase-region= function. I still have no idea why this is disabled by default. #+BEGIN_SRC emacs-lisp (put 'upcase-region 'disabled nil) #+END_SRC Whenever we visit a buffer that has no active edits, but the file has changed on disk, automatically reload it. #+BEGIN_SRC emacs-lisp (global-auto-revert-mode t) #+END_SRC I'm really not smart sometimes, so I need emacs to warn me when I try to quit it. #+BEGIN_SRC emacs-lisp (setq confirm-kill-emacs 'yes-or-no-p) #+END_SRC Remote X11 seems to have problems with delete for me (mostly XQuartz, I believe), so I force erase to be backspace. #+BEGIN_SRC emacs-lisp (when (eq window-system 'x) (normal-erase-is-backspace-mode 1)) #+END_SRC When functions are redefined with =defadvice=, a warning is emitted. This is annoying, so I disable these warnings. #+BEGIN_SRC emacs-lisp (setq ad-redefinition-action 'accept) #+END_SRC Tell Python mode to use Python 3 #+BEGIN_SRC emacs-lisp (setq python-shell-interpreter "python3") #+END_SRC * Appearance :PROPERTIES: :CUSTOM_ID: appearance :END: ** Default Face Not all fonts are installed on all systems where I use Emacs. This code will iterate over a list of fonts, in order of my personal preference, and set the default face to the first one available. Of course, if Emacs is not running in a windowing system, this is ignored. #+BEGIN_SRC emacs-lisp (when window-system (let* ((families '("Hack" "Roboto Mono" "Input Mono" "Inconsolata" "Dejavu" "Menlo" "Monaco" "Courier New" "Courier" "fixed")) (selected-family (cl-dolist (fam families) (when (member fam (font-family-list)) (cl-return fam))))) (set-face-attribute 'default nil :family selected-family :height 120) (set-face-attribute 'fixed-pitch nil :family selected-family :height 120))) #+END_SRC ** Window Frame *** Title By default, the Emacs frame (what you or I would call a window) title is *user@host*. I much prefer the frame title to show the actual name of the currently selected buffer. #+BEGIN_SRC emacs-lisp (setq-default frame-title-format "%b") (setq frame-title-format "%b") #+END_SRC ** Changing Font Size on the Fly By default, you can increase or decrease the font face size in a single window with =C-x C-+= or =C-x C--=, respectively. This is fine, but it applies to the /current window only/ (*note*: In Emacs, a /window/ is what you or I would probably call a frame or a pane... yes, I know, just work with it). I like to map =C-+= and =C--= to functions that will change the height of the default face in ALL windows. First, I create a base function to do the change by a certain amount in a certain direction. #+BEGIN_SRC emacs-lisp (defun change-face-size (dir-func &optional delta) "Increase or decrease font size in all frames and windows. ,* DIR-FUNC is a direction function (embiggen-default-face) or (ensmallen-default-face) ,* DELTA is an amount to increase. By default, the value is 10." (progn (set-face-attribute 'default nil :height (funcall dir-func (face-attribute 'default :height) delta)))) #+END_SRC Then, I create two little helper functions to bump the size up or down. #+BEGIN_SRC emacs-lisp (defun embiggen-default-face (&optional delta) "Increase the default font. ,* DELTA is the amount (in point units) to increase the font size. If not specified, the dfault is 10." (interactive) (let ((incr (or delta 10))) (change-face-size '+ incr))) (defun ensmallen-default-face (&optional delta) "Decrease the default font. ,* DELTA is the amount (in point units) to decrease the font size. If not specified, the default is 10." (interactive) (let ((incr (or delta 10))) (change-face-size '- incr))) #+END_SRC And, finally, bind those functions to the right keys. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-+") 'embiggen-default-face) (global-set-key (kbd "C--") 'ensmallen-default-face) #+END_SRC ** Shell Colors Turn on ANSI colors in the shell. #+BEGIN_SRC emacs-lisp (autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t) (add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on) #+END_SRC ** Assembly Mode hack Tabs are all wrong in assembly mode, so here's a fix. #+BEGIN_SRC emacs-lisp (add-hook 'asm-mode-hook (lambda () (setq indent-tabs mode nil) (electric-indent-mode) (setq tab-stop-list (number-sequence 8 60 8)))) #+END_SRC ** Line Numbers I like to see /(Line,Column)/ displayed in the modeline. #+BEGIN_SRC emacs-lisp (setq line-number-mode t) (setq column-number-mode t) #+END_SRC I also like seeing line numbers in the gutter, but I want them to be /relative/, such that the lines are numbered /1,2,3,4.../ both decreasing and increasing from the current line. #+BEGIN_SRC emacs-lisp (global-display-line-numbers-mode t) (setq display-line-numbers 'relative) #+END_SRC ** Show the Time I like having the day, date, and time displayed in my modeline. (Note that it's pointless to display seconds here, since the modeline does not automatically update every second, for efficiency purposes) #+BEGIN_SRC emacs-lisp (setq display-time-day-and-date t) (display-time-mode 1) #+END_SRC ** Line Wrapping By default, if a frame has been split horizontally, partial windows will not wrap. #+BEGIN_SRC emacs-lisp (setq truncate-partial-width-windows nil) #+END_SRC I also prefer my fill-column to be at 74, not the default of 70 #+BEGIN_SRC emacs-lisp (setq-default fill-column 74) #+END_SRC ** Parentheses Whenever the cursor is on a paren, highlight the matching paren. #+BEGIN_SRC emacs-lisp (show-paren-mode t) #+END_SRC I like automatic pair matching, but you might want to turn this off if you find it annoying. #+BEGIN_SRC emacs-lisp (electric-pair-mode) #+END_SRC ** Mac OS X Specific Tweaks GNU Emacs running on recent versions of MacOS in particular exhibit some pretty ugly UI elements. Further, I don't like having to use the /Option/ key for /Meta/, so I switch things around on the keyboard. Note, though, that this block is only evaluated when the windowing system is ='ns=, so this won't do anything at all on Linux. #+BEGIN_SRC emacs-lisp (when (eq window-system 'ns) (add-to-list 'frameset-filter-alist '(ns-transparent-titlebar . :never)) (add-to-list 'frameset-filter-alist '(ns-appearance . :never)) (setq mac-option-modifier 'super mac-command-modifier 'meta mac-function-modifier 'hyper mac-right-option-modifier 'super)) #+END_SRC * Package Management :PROPERTIES: :CUSTOM_ID: package-management :END: ** Basic Setup We'll begin by requiring =package= mode and setting up URLs to the package archives. #+BEGIN_SRC emacs-lisp (require 'package) (setq package-enable-at-startup t) (setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/") ("melpa" . "https://melpa.org/packages/"))) #+END_SRC Then, actually initialize things. #+BEGIN_SRC emacs-lisp (package-initialize) #+END_SRC And then, if the =use-package= package is not installed, install it immediately. #+BEGIN_SRC emacs-lisp (unless (package-installed-p 'use-package) (package-refresh-contents) (package-install 'use-package)) (require 'use-package) #+END_SRC ** Theme I never tire of experimenting with themes. This section changes pretty often. #+BEGIN_SRC emacs-lisp (use-package modus-themes :ensure t :config (setq modus-themes-org-blocks 'gray-background modus-themes-mixed-fonts nil modus-themes-subtle-line-numbers t modus-themes-region '(bg-only) modus-themes-bold-constructs t modus-themes-italic-constructs t modus-themes-completions '((matches . (extrabold)) (selection . (semibold accented)) (popup . (accented intense))) modus-themes-mode-line '(accented borderless padded)) (load-theme 'modus-vivendi t)) (use-package olivetti :ensure t :config (setq olivetti-body-width 90)) #+END_SRC ** Completion UI - Vertico I recently switched to Vertico and Consult for completion and completion, which replaces Helm / Ivy. #+BEGIN_SRC emacs-lisp (use-package vertico :ensure t :init (vertico-mode)) (use-package savehist :ensure t :init (savehist-mode)) (use-package orderless :ensure t :init (setq completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion))))) (use-package consult :ensure t :hook (completion-list-mode . consult-preview-at-point-mode) :bind (("C-c M-x" . consult-mode-command) ("C-c h" . consult-history) ("C-c k" . consult-kmacro) ("C-c m" . consult-man) ("C-c i" . consult-info) ([remap Info-search] . consult-info) ("C-x b" . consult-buffer) ("C-x 4 b" . consult-buffer-other-window) ("C-x 5 b" . consult-buffer-other-frame) ("C-x r b" . consult-bookmark) ("C-x p b" . consult-project-buffer) ("M-#" . consult-register-load) ("M-'" . consult-register-store) ("C-M-#" . consult-register) ("M-y" . consult-yank-pop) ("M-g e" . consult-compile-error) ("M-g f" . consult-flymake) ("M-g g" . consult-goto-line) ("M-g M-g" . consult-goto-line) ("M-g o" . consult-outline) ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) ("M-s d" . consult-find) ("M-s D" . consult-locate) ("M-s g" . consult-grep) ("M-s G" . consult-git-grep) ("M-s r" . consult-ripgrep) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) ("M-s e" . consult-isearch-history) :map isearch-mode-map ("M-e" . consult-isearch-history) ("M-s e" . consult-isearch-history) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) :map minibuffer-local-map ("M-s" . consult-history) ("M-r" . consult-history)) :init (setq register-preview-delay 0.5 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 (consult-customize consult-theme :preview-key '(:debounce 0.2 any) consult-ripgrep consult-git-grep consult-grep consult-bookmark consult-recent-file consult-xref consult--source-bookmark consult--source-file-register consult--source-recent-file consult--source-project-recent-file :preview-key '(:debounce 0.4 any)) (setq consult-narrow-key "<")) #+END_SRC ** Git Integration Magit and git-gutter are both essential, I can't live without them. #+BEGIN_SRC emacs-lisp (use-package magit :ensure t) (use-package git-gutter :ensure t) #+END_SRC ** Org Mode Next is =org-mode=, which I use constantly, day in and day out. #+BEGIN_SRC emacs-lisp (defun my-org-agenda-format-date-aligned (date) "Format a DATE string for display in the daily/weekly agenda, or timeline. This function makes sure that dates are aligned for easy reading." (require 'cal-iso) (let* ((dayname (calendar-day-name date 1 nil)) (day (cadr date)) (day-of-week (calendar-day-of-week date)) (month (car date)) (monthname (calendar-month-name month 1)) (year (nth 2 date)) (iso-week (org-days-to-iso-week (calendar-absolute-from-gregorian date))) (weekyear (cond ((and (= month 1) (>= iso-week 52)) (1- year)) ((and (= month 12) (<= iso-week 1)) (1+ year)) (t year))) (weekstring (if (= day-of-week 1) (format " W%02d" iso-week) ""))) (format "%-2s. %2d %s" dayname day monthname))) (use-package org :ensure t ;; I like to have visual-line-mode enabled in org buffers :init (add-hook 'org-mode-hook #'visual-line-mode) :config (setq org-hide-emphasis-markers t org-pretty-entities t org-tags-column -65 org-latex-listings 't org-export-default-language "en" org-export-with-smart-quotes t org-agenda-tags-column -65 org-deadline-warning-days 14 org-table-shrunk-column-indicator "" org-agenda-block-separator (string-to-char " ") org-adapt-indentation nil org-confirm-babel-evaluate nil org-fontify-whole-heading-line t org-agenda-format-date 'my-org-agenda-format-date-aligned ;; Use CSS for htmlizing HTML output org-html-htmlize-output-type 'css ;; Open up org-mode links in the same buffer org-link-frame-setup '((file . find-file)))) #+END_SRC I have a lot of custom configuration for =org-mode=. *** Timestamp Helpers When I keep a long-running notes file, I like each top level entry to have a ~DATE:~ property set. This function automatically inserts the current timestamp as a property. #+BEGIN_SRC emacs-lisp (defun timestamp-notes-entry () "Insert a DATE property in the current heading with the current timestamp." (interactive) (org-set-property "DATE" (format-time-string "<%F %a %H:%M>" (current-time)))) (define-key org-mode-map (kbd "C-c C-x t") #'timestamp-notes-entry) #+END_SRC *** Org Agenda Org Agenda is a great way of tracking time and progress on various projects and repeatable tasks. It's built into org-mode. I add a quick and easy way to get into =org-agenda= from any =org-mode= buffer by pressing =C-c a=. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-c a") 'org-agenda) #+END_SRC Next, I add a custom =org-agenda= command to show the next three weeks. #+BEGIN_SRC emacs-lisp (setq org-agenda-custom-commands '(("n" "Agenda / INTR / PROG / NEXT" ((agenda "" nil) (todo "INTR" nil) (todo "PROG" nil) (todo "NEXT" nil))) ("W" "Next Week" agenda "" ((org-agenda-span 7) (org-agenda-start-on-weekday 0))) ("N" "Next Three Weeks" agenda "" ((org-agenda-span 21) (org-agenda-start-on-weekday 0))))) #+END_SRC Then, I define some faces and use them for deadlines in =org-agenda=. #+BEGIN_SRC emacs-lisp (defface deadline-soon-face '((t (:foreground "#ff0000" :weight bold :slant italic :underline t))) "Soon deadlines") (defface deadline-near-face '((t (:foreground "#ffa500" :weight bold :slant italic))) "Near deadlines") (defface deadline-distant-face '((t (:foreground "#ffff00" :weight bold :slant italic))) "Distant deadlines") (setq org-agenda-deadline-faces '((0.75 . deadline-soon-face) (0.5 . deadline-near-face) (0.25 . deadline-distant-face) (0.0 . deadline-distant-face))) #+END_SRC Then I set my =org-todo-keywords= so that I can manage my workflow states the way I like to. Although my own list is very linear and simple, they can become quite complex if need be! #+BEGIN_SRC emacs-lisp (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "PROG(p)" "INTR(i)" "DONE(d)"))) #+END_SRC And finally, I set some file locations. This is a bit convoluted because I use Agenda both for work and for home. At work, I keep a file called =~/.org-agenda-setup.el= that contains my agenda files and archive location information. At home, I just use what's baked into this file. Also note that I like to keep archived Agenda items in a separate directory, rather than the default behavior of renaming them to =.org_archive=. #+BEGIN_SRC emacs-lisp (if (file-exists-p "~/.org-agenda-setup.el") (load "~/.org-agenda-setup.el") (progn (global-set-key (kbd "C-c o") (lambda () (interactive) (find-file "~/Nextcloud/agenda/agenda.org"))) (setq org-directory (expand-file-name "~/Nextcloud/Notes")) (setq org-default-notes-file (concat org-directory "/notes.org") org-capture-templates '(("t" "Task" entry (file+olp+datetree "~/Nextcloud/Notes/tasks.org") "* TODO %?\n%i" :empty-lines 1) ("j" "Journal" entry (file+datetree "~/Nextcloud/Notes/journal.org") "* %?\nAdded: %U\n%i" :empty-lines 1) ("n" "Note" entry (file "~/Nextcloud/Notes/notes.org") "* %^{Headline}\nAdded: %U\n\n%?" :empty-lines 1))) (setq org-habit-show-habits-only-for-today nil org-agenda-files (file-expand-wildcards "~/Nextcloud/agenda/*.org") org-default-notes-file "~/Nextcloud/agenda/agenda.org"))) #+END_SRC *** Org Super Agenda #+BEGIN_SRC emacs-lisp (use-package org-super-agenda :ensure t :after org-agenda :init (setq org-super-agenda-groups '((:name "Next" :time-grid t :todo "NEXT" :order 1) (:name "Language" :time-grid t :tag "language" :order 2) (:name "Study" :time-grid t :tag "study" :order 3) (:discard (:not (:todo "TODO"))))) :config (org-super-agenda-mode) (setq org-agenda-compact-blocks nil org-agenda-span 'day org-agenda-todo-ignore-scheduled 'future org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled org-super-agenda-header-separator "" org-columns-default-format "%35ITEM %TODO %3PRIORITY %TAGS") (set-face-attribute 'org-super-agenda-header nil :weight 'bold)) #+END_SRC *** Org Capture To capture new notes, I configure Org Capture with a quick key binding of =C-c c=. #+BEGIN_SRC emacs-lisp (global-set-key (kbd "C-c c") 'org-capture) #+END_SRC *** Org-Babel Language Integration I want to be able to support C, Emacs Lisp, shell, python, and GraphViz blocks in org-babel. #+BEGIN_SRC emacs-lisp (org-babel-do-load-languages 'org-babel-load-languages '((python . t) (C . t) (shell . t) (emacs-lisp . t) (dot . t))) (setq org-babel-python-command "python3") #+END_SRC Next I want output header-args to be =:results output :wrap EXAMPLE= by default. This horrid mess accomplishes that by removing the old =:results= and =:wrap= keys from the association list =org-babel-default-header-args= and then replacing them. #+BEGIN_SRC emacs-lisp (cons '(:results . "output") (cons '(:wrap . "EXAMPLE") (assq-delete-all :wrap (assq-delete-all :results org-babel-default-header-args)))) #+END_SRC *** Display Options I turn on Pretty Entities, which allows Emacs, in graphics mode, to render unicode symbols, math symbols, and so on. I also set a custom ellipsis character that will be shown when sections or blocks are collapsed. #+BEGIN_SRC emacs-lisp (setq org-pretty-entities t org-ellipsis " ↴") #+END_SRC ** Org Roam Choose the best directory for org-roam: In my Nextcloud directory, if it exists, otherwise in Documents. #+BEGIN_SRC emacs-lisp (setq my-org-roam-directory (if (file-directory-p (expand-file-name "~/Nextcloud/org-roam/")) "~/Nextcloud/org-roam/" "~/Documents/org-roam/")) #+END_SRC Next, ensure ~org-roam~ is installed and configured. #+BEGIN_SRC emacs-lisp (use-package org-roam :ensure t :init (setq org-roam-v2-ack t org-roam-dailies-directory "journal/") :custom (org-roam-directory (expand-file-name my-org-roam-directory)) (org-roam-completion-everywhere t) (org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d>-${slug}.org" "#+TITLE: ${title}\n") :unnarrowed t))) (org-roam-dailies-capture-templates '(("d" "default" entry "\n* %?" :target (file+head "%<%Y-%m-%d>.org" "#+TITLE: %u\n#+STARTUP: showall\n\n") :unnarrowed nil ; Show only the current note on entry :empty-lines 1))) :bind (("C-c n l" . org-roam-buffer-toggle) ("C-c n f" . org-roam-node-find) ("C-c n i" . org-roam-node-insert) :map org-mode-map ("C-M-i" . completion-at-point) :map org-roam-dailies-map ("Y" . org-roam-dailies-capture-yesterday) ("T" . org-roam-dailies-capture-tomorrow)) :bind-keymap ("C-c n d" . org-roam-dailies-map) :config (require 'org-roam-dailies) (org-roam-db-autosync-mode) (org-roam-setup)) #+END_SRC ** Org Superstar Org Superstar replaces the default asterisk style Org-Mode headers with nicer looking defaults using Unicode. #+BEGIN_SRC emacs-lisp (use-package org-superstar :ensure t :hook (org-mode . org-superstar-mode) :config (setq org-superstar-leading-bullet " ")) #+END_SRC ** Perspective =perspective.el= is a tool that allows grouping of buffers into separate "perspectives", like workgroups in other editors. #+BEGIN_SRC emacs-lisp (use-package perspective :ensure t :bind (("C-x k" . persp-kill-buffer*)) :custom (persp-mode-prefix-key (kbd "C-x M-p")) :init (persp-mode)) #+END_SRC ** Support for Encrypted Authinfo Various workflows require accessing authentication credentials stored in an encrypted file named =~/.authinfo.gpg=. The =auth-source= package lets me get quick access to those credentials. #+BEGIN_SRC emacs-lisp (use-package auth-source :ensure t :config (setq auth-sources '("~/.authinfo.gpg"))) #+END_SRC ** Tera Mode Tera is a templating language used by [[https://www.getzola.org/][Zola]], which I use for my website and blog. This mode helps with editing those templates. #+BEGIN_SRC emacs-lisp (use-package tera-mode :load-path "lisp/tera-mode") #+END_SRC ** Sly Sly is a Common Lisp IDE that is a fork of SLIME, with some additional features. #+BEGIN_SRC emacs-lisp (use-package sly :ensure t :config (setq inferior-lisp-program "sbcl")) (use-package sly-quicklisp :ensure t) #+END_SRC ** GraphViz (dot) Mode If you don't know GraphViz, you should. It's a very useful language for generating visual flowcharts and graphs. #+BEGIN_SRC emacs-lisp (use-package graphviz-dot-mode :ensure t) #+END_SRC ** YAML YAML mode is useful for editing Docker files, among many other things. #+BEGIN_SRC emacs-lisp (use-package yaml-mode :ensure t) #+END_SRC ** Snow This is just a bit of fun. See: [[https://github.com/alphapapa/snow.el]["Let It Snow" on GitHub]]. #+BEGIN_SRC emacs-lisp (use-package snow :ensure t) #+END_SRC ** Fireplace Sometimes you want a roaring fire. #+BEGIN_SRC emacs-lisp (use-package fireplace :ensure t) #+END_SRC ** Snippets Snippets build in support for typing a few keys, pressing tab, and getting a complete template inserted into your buffer. I use these heavily. In addition to the built-in snippets that come from the =yasnippet-snippets= package, I have some custom snippets defined in the =snippets= directory. #+BEGIN_SRC emacs-lisp (use-package yasnippet :ensure t :diminish yas-minor-mode :config (setq yas-snippet-dirs (append yas-snippet-dirs '("~/.emacs.d/snippets"))) (yas-global-mode)) (use-package yasnippet-snippets :ensure t :after yasnippet :config (yasnippet-snippets-initialize)) #+END_SRC ** Markdown #+BEGIN_SRC emacs-lisp (use-package markdown-mode :ensure t :commands (markdown-mode gfm-mode) :mode (("README\\.md\\'" . gfm-mode) ("\\.md\\'" . markdown-mode) ("\\.markdown\\'" . markdown-mode)) :init (setq markdown-command "markdown") :config (use-package edit-indirect :ensure t)) #+END_SRC ** Gemini [[https://gemini.circumlunar.space/][Gemini]] is a new project I'm kind of interested in. These packages will help support my interest in it. #+BEGIN_SRC emacs-lisp (use-package elpher :ensure t) (use-package gemini-mode :ensure t) (use-package ox-gemini :ensure t) #+END_SRC * Development and Languages :PROPERTIES: :CUSTOM_ID: development :END: Much of this section, especially with regards to Rust development, is stolen verbatim from [[https://robert.kra.hn/posts/2021-02-07_rust-with-emacs/][Robert Krahn]]. Thank you! ** Python Development #+BEGIN_SRC emacs-lisp (use-package elpy :ensure t :init (elpy-enable)) (add-hook 'python-mode-hook (lambda () (setq-default indent-tabs-mode nil) (setq-default tab-width 4) (setq-default py-indent-tabs-mode nil))) #+END_SRC ** Web Mode #+BEGIN_SRC emacs-lisp (use-package web-mode :ensure t :config (setq web-mode-markup-indent-offset 2 web-mode-css-indent-offset 2) :init (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.php\\'" . web-mode))) #+END_SRC ** SQL Indent Mode #+BEGIN_SRC emacs-lisp (use-package sql-indent :ensure t :config (add-hook 'sql-mode-hook #'sqlind-minor-mode)) #+END_SRC ** Lisp Editing I really like paredit, especially for Lisp, but I don't like the default key bindings, so I tweak them heavily. Primarily, the problem is that I use =C-= and =C-= to navigate between windows in Emacs, so I don't want to use them for Paredit. Instead, I remap these to =C-S-= and =C-S-=, respectively. #+BEGIN_SRC emacs-lisp (use-package paredit :ensure t :init (autoload 'enable-paredit-mode "paredit" "Structural editing of Lisp") (add-hook 'emacs-lisp-mode-hook #'enable-paredit-mode) (add-hook 'eval-expression-minibuffer-setup-hook #'enable-paredit-mode) (add-hook 'lisp-mode-hook #'enable-paredit-mode) (add-hook 'lisp-interaction-mode-hook #'enable-paredit-mode) (add-hook 'scheme-mode-hook #'enable-paredit-mode) :config ;; Unmap defaults (define-key paredit-mode-map (kbd "C-") nil) (define-key paredit-mode-map (kbd "C-") nil) ;; Map new keys (define-key paredit-mode-map (kbd "C-S-") 'paredit-forward-barf-sexp) (define-key paredit-mode-map (kbd "C-S-") 'paredit-forward-slurp-sexp)) #+END_SRC ** Haskell I've recently been playing more with Haskell. #+BEGIN_SRC emacs-lisp (use-package haskell-mode :ensure t :init (progn (add-hook 'haskell-mode-hook 'turn-on-haskell-doc-mode) (add-hook 'haskell-mode-hook 'turn-on-haskell-indent) (add-hook 'haskell-mode-hook 'interactive-haskell-mode) (setq haskell-process-args-cabal-new-repl '("--ghc-options=-ferror-spans -fshow-loaded-modules")) (setq haskell-process-type 'cabal-new-repl) (setq haskell-stylish-on-save 't) (setq haskell-tags-on-save 't))) (use-package flycheck-haskell :ensure t :config (add-hook 'flycheck-mode-hook #'flycheck-haskell-setup) (eval-after-load 'haskell-mode-hook 'flycheck-mode)) (use-package flymake-hlint :ensure t :config (add-hook 'haskell-mode-hook 'flymake-hlint-load)) #+END_SRC ** Rustic Support for the Rust Programming Language. #+BEGIN_SRC emacs-lisp (use-package rustic :ensure t :bind (:map rustic-mode-map ("M-j" . lsp-ui-imenu) ("M-?" . lsp-find-references) ("C-c C-c l" . flycheck-list-errors) ("C-c C-c a" . lsp-execute-code-action) ("C-c C-c r" . lsp-rename) ("C-c C-c q" . lsp-workspace-restart) ("C-c C-c Q" . lsp-workspace-shutdown) ("C-c C-c s" . lsp-rust-analyzer-status)) :config ;; comment to disable rustfmt on save (setq rustic-format-on-save t) (add-hook 'rustic-mode-hook 'loomcom/rustic-mode-hook)) (defun loomcom/rustic-mode-hook () ;; so that run C-c C-c C-r works without having to confirm (setq-local buffer-save-without-query t)) #+END_SRC ** CCLS #+BEGIN_SRC emacs-lisp (use-package ccls :ensure t :config (setq ccls-executable "ccls") (setq lsp-prefer-flymake nil) (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc)) :hook ((c-mode c++-mode objc-mode) . (lambda () (require 'ccls) (lsp)))) #+END_SRC ** LSP Mode LSP is a language server protocol mode to allow working with various LSP daemons. Note that I've disabled lsp-ui-mode because I've discovered I'm finding it to be very distracting. If you want to turn it back on, just add =:config (add-hook 'lsp-mode-hook 'lsp-ui-mode)= to lsp-mode. #+BEGIN_SRC emacs-lisp (use-package lsp-mode :ensure t :commands lsp :config ;; Improve performance and enable features (progn (setq vc-ignore-dir-regexp (format "\\(%s\\)\\|\\(%s\\)" vc-ignore-dir-regexp tramp-file-name-regexp) read-process-output-max (* 1024 1024) gc-cons-threshold 100000000 vc-handled-backends '(Git) ;; Only support git lsp-rust-analyzer-proc-macro-enable t lsp-rust-analyzer-cargo-watch-command "clippy" lsp-rust-analyzer-cargo-load-out-dirs-from-check t ;; These three make things significantly less flashy... lsp-eldoc-render-all nil lsp-eldoc-hook nil lsp-idle-delay 0.5 lsp-eldoc-hook nil lsp-enable-symbol-highlighting nil lsp-signature-auto-activate nil ;; Do not automatically include headers for me! lsp-clients-clangd-args '("--header-insertion=never") ;; Do not auto-format for me! lsp-enable-indentation nil lsp-enable-on-type-formatting nil) (lsp-register-client (make-lsp-client :new-connection (lsp-tramp-connection "/usr/bin/clangd") :major-modes '(c-mode) :remote? t :server-id 'clangd-remote)))) (use-package lsp-ui :ensure t :commands lsp-ui-mode :custom (lsp-ui-peek-always-show t) (lsp-ui-sideline-show-hover t) (lsp-ui-doc-enable t) (lsp-ui-doc-delay 2)) (add-hook 'c-mode-hook 'lsp) (add-hook 'c++-mode-hook 'lsp) #+END_SRC ** Corfu #+BEGIN_SRC emacs-lisp (use-package corfu :ensure t :custom (corfu-cycle t) (corfu-auto t) (corfu-auto-prefix 2) (corfu-auto-delay 2.0) (corfu-quit-at-boundary 'separator) (corfu-echo-documentation 0.25) (corfu-preview-current 'separator) (corfu-preselect-first t) :bind (:map corfu-map ("M-SPC" . corfu-insert-separator) ("TAB" . corfu-next) ([tab] . corfu-next) ("S-TAB" . corfu-previous) ([backtab] . corfu-previous) ("S-" . corfu-insert)) :init (global-corfu-mode)) #+END_SRC ** Flycheck #+BEGIN_SRC emacs-lisp (use-package flycheck :ensure t) #+END_SRC * Email :PROPERTIES: :CUSTOM_ID: email :END: Email configuration is all in an external, optional file. It's not checked in here for privacy reasons, which I'm sure you'll understand! #+BEGIN_SRC emacs-lisp (let ((mail-conf (expand-file-name "~/.emacs-mail.el"))) (when (file-exists-p mail-conf) (load-file mail-conf))) #+END_SRC