emacs-files/configuration.org

1549 lines
44 KiB
Org Mode

#+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-<arrow>= to navigate between windows. (N.B.: This overrides the
default behavior of moving forward or backward by word using =C-<right>=
nad =C-<left>=, 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-<left>")
#'(lambda ()
(interactive)
(ignore-errors (windmove-left))))
(global-set-key (kbd "C-<right>")
#'(lambda ()
(interactive)
(ignore-errors (windmove-right))))
(global-set-key (kbd "C-<up>")
#'(lambda ()
(interactive)
(ignore-errors (windmove-up))))
(global-set-key (kbd "C-<down>")
#'(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
=<original-file-name>.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-<left>= and =C-<right>= to navigate between
windows in Emacs, so I don't want to use them for Paredit. Instead,
I remap these to =C-S-<left>= and =C-S-<right>=, 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-<left>") nil)
(define-key paredit-mode-map (kbd "C-<right>") nil)
;; Map new keys
(define-key paredit-mode-map (kbd "C-S-<left>")
'paredit-forward-barf-sexp)
(define-key paredit-mode-map (kbd "C-S-<right>")
'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-<return>" . 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