Skip to content
My life is dope and I have dope emacs configs
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
screenshots
signatures
snippets
.gitignore
README.org
init.el

README.org

Emacs Configuration

screenshots/scratch.png

It’s been said “there are many ways to skin a cat”. The same can be said of Emacs. Probably.

Personal

Don’t be shy - introduce yourself to emacs. If you are copying this config, make sure you use your name. We don’t want to confuse my mother.

(setq user-full-name "Alex Recker"
      user-mail-address "alex@reckerfamily.com")

Packages

All packages are installed with the use-package library. Sadly, this needs to load before org can tangle anything, so all the action is in init.el.

Startup

Path

Ensure that the system PATH is the one used by emacs.

(use-package exec-path-from-shell
  :ensure t
  :config (exec-path-from-shell-initialize))

Personal directories.

(defun recker/path (dir &optional subpath)
  (let* ((macos-p (string-equal system-type "darwin"))
	 (dir (pcase dir
		('home "~")
		('desktop (if macos-p "~/Desktop" "~/desktop"))
		('docs (if macos-p "~/Documents" "~/docs"))
		('pics (if macos-p "~/Pictures" "~/pics"))
		('emacs user-emacs-directory)
		(_ (error (format "no %s directory!" dir)))))
	 (subdir (or subpath "")))
    (expand-file-name (concat (file-name-as-directory dir) subpath))))

Scratch

The slash screen displayed on startup is a little too noisy for me. The *scratch* buffer is a lot more low key.

(setq inhibit-startup-message 't)

Here is a collection of pithy quotes I like to display on my scratch screen.

QuoteAttribution
Sanity and happiness are an impossible combination.Mark Twain
Trust thyself only, and another shall not betray thee.Thomas Fuller
Fear has its uses but cowardice has none.Mahatma Ghandi
Happiness can exist only in acceptance.George Orwell
Seek respect mainly from thyself, for it comes first from within.Steven H. Coogler
Conscience is the dog that can’t bite, but never stops barking.Proverb
In general, pride is at the bottom of all great mistakes.Steven H. Coogler
Anger as soon as fed is dead – tis starving makes it fat.Emily Dickinson
Make no judgements where you have no compassion.Anne McCaffrey
Isolation is a self-defeating dream.Carlos Salinas de Gortari
Doubt must be no more than vigilance, otherwise it can become dangerous.George C. Lichtenberg
Love is a willingless to sacrifice.Michael Novak
The value of identity is that so often with it comes purpose.Richard R. Grant
Discontent is the first necessity of progress.Thomas Edison
Some of us think holding on makes us strong, but sometimes it is letting go.Herman Hesse
Let not a man guard his dignity but let his dignity guard him.Ralph Waldo Emerson
Guilt: the gift that keeps on giving.Erma Bombeck
Be here now.Ram Dass
The master understands that the universe is forever out of control.Lao Tzu
Our biggest problems arise from the avoidance of smaller ones.Jeremy Caulfield
The truth will set you free, but first it will make you miserableJames A. Garfield
The thing that lies at the foundation of positive change is service to a fellow human beingLee Iacocca
Honesty and transparency make you vulnerable. Be honest and transparent anywayMother Teresa
If you do not ask the right questions, you do not get the right answers.Edward Hodnett
Resentment is like taking poison and waiting for the other person to die.Malachy McCourt
If we knew each other’s secrets, what comfort should we find.John Churton Collins
The mistake is thinking that there can be an antidote to the uncertainty.David Levithan
Cure sometimes, treat often, comfort always.Hippocrates
Suspicion is a heavy armor and with its weight it impedes more than it protects.Robert Burns
Sincerity, even if it speaks with a stutter, will sound eloquent when inspired.Eiji Yoshikawa
I have little shame, no dignity - all in the name of a better cause.A.J. Jacobs
Truth may sometimes hurt, but delusion harms.Vanna Bonta
Intuition is more important to discovery than logic.Henri Poincare
How weird was it to drive streets I knew so well. What a different perspective.Suzanne Vega
There can be no progress without head-on confrontation.Christopher Hitchens
Sometimes it’s necessary to go a long distance out of the way to come back a short distance correctly.Edward Albea
Stagnation is death. If you don’t change, you die. It’s that simple. It’s that scary.Leonard Sweet
In my opinion, actual heroism, like actual love, is a messy, painful, vulnerable business.John Green
Maybe all one can do is hope to end up with the right regrets.Arthur Miller
If you have behaved badly, repent, make what amends you can and address yourself to the task of behaving better next time.Aldous Huxley
Sooner or later everyone sits down to a banquet of consequences.Robert Louis Stevenson
We are all in the same boat, in a stormy sea, and we owe each other a terrible loyalty.G.K. Chesterton
In our quest for the answers of life we tend to make order out of chaos, and chaos out of order.Jeffrey Fry
There are many ways of going forward, but only one way of standing still.Franklin D. Roosevelt
Truth is outside of all patterns.Bruce Lee
By imposing too great a responsibility, or rather, all responsibility, on yourself, you crush yourself.Franz Kafka
How few there are who have courage enough to own their faults, or resolution enough to mend them.Benjamin Franklin
Resistance is useless.Doctor Who
Happiness does not depend on outward things, but on the way we see them.Leo Tolstoy

Pick a random one on startup, wrap it in a lisp comment box, and assign it to the scratch message variable.

(setq initial-scratch-message (let* ((choice (nth (random (length quotes)) quotes))
				     (text (car choice))
				     (attribution (car (cdr choice))))
				(with-temp-buffer
				  (lisp-mode)
				  (newline)
				  (insert (format "\"%s\"\n" text))
				  (fill-region (point-min) (point-max))
				  (insert (format "-- %s" attribution))
				  (comment-region (point-min) (point-max))
				  (dotimes (_ 2) (newline))
				  (buffer-string))))

Make the *scratch* buffer unkillable.

(use-package unkillable-scratch
  :ensure t
  :init (unkillable-scratch))

Interface

Better Defaults

Emacs comes with some obnixious defaults. “Not on my watch!”, yelled Alex as he disabled them.

(setq make-backup-files nil
      auto-save-default nil
      indent-tabs-mode nil
      ns-confirm-quit 1)

(global-auto-revert-mode 1)
(menu-bar-mode 0)
(delete-selection-mode t)
(scroll-bar-mode -1)
(tool-bar-mode -1)

Because the command C-x C-c is easier to type by accident than you’d think, enable this so Emacs says “are you sure?”

(setq confirm-kill-emacs #'yes-or-no-p)

Better Comments

I overwrite the build-in comment-dwim with its superior sequel.

(use-package comment-dwim-2
  :ensure t
  :bind ("M-;" . comment-dwim-2))

Better Modeline

Hide all minor modes from the modeline (since there are usually like a hundred).

(use-package rich-minority
  :ensure t
  :init (rich-minority-mode 1)
  :config (setq rm-blacklist ""))

Better Bookmarks

Automatically save the bookmark file each time it is modified. This prevents losing bookmarks created in separate emacs clients.

(setq bookmark-save-flag 1
      bookmark-default-file (recker/path 'docs "emacs/bookmarks.el"))

This is just a custom implementation of bookmark-jump that displays file paths instead of just the “name”.

(defun recker/bookmark-jump ()
  (interactive)
  (let ((bookmark-list
	 (mapcar (lambda (b) (cdr (assoc 'filename b)))
		 (read (with-temp-buffer
			 (insert-file-contents-literally bookmark-default-file)
			 (buffer-string))))))
    (find-file (completing-read "Jump to Bookmark: " bookmark-list nil t))))

(global-set-key (kbd "C-x r b") 'recker/bookmark-jump)

Better File Manager

By default, hide dot files. They can be shown by disabling dired-omit-mode with C-x M-o.

Another nice side effect of dired-x is suddenly gaining the ability of jumping to the current file in dired with C-x C-j.

(require 'dired-x)
(setq-default dired-omit-files-p t)
(setq dired-omit-files (concat dired-omit-files "\\|^\\..+$"))

Add the -h switch to the dired output to show prettier filenames.

(setq dired-listing-switches "-alh")

Don’t ask permission to delete the buffer of a deleted file.

(setq dired-clean-confirm-killing-deleted-buffers nil)

Better Music

OK, so there’s no music in Emacs to begin with. But check out mingus, it’s pretty awesome. This works, assuming you have an local mpd server running on the default port.

(use-package mingus
  :ensure t
  :bind (("C-x m" . mingus))
  :config
  (setq mingus-mode-always-modeline nil)
  (setq mingus-mode-line-separator " // ")
  (setq mingus-mode-line-show-consume-and-single-status nil)
  (setq mingus-mode-line-show-elapsed-time nil)
  (setq mingus-mode-line-show-volume nil)
  (setq mingus-mode-line-show-elapsed-percentage nil)
  (setq mingus-mode-line-show-random-and-repeat-status nil)
  (setq mingus-mode-line-show-status nil)
  (setq mingus-mode-line-string-max 50))

Better Text Selection

I use expand-region to incrementally grab larger portions of text based on where the cursor is. It’s a brilliant tool.

(use-package expand-region
  :ensure t
  :bind ("C-=" . er/expand-region))

Better Completion

Company mode.

(use-package company
  :ensure t
  :config (global-company-mode))

Yasnippet - I don’t use this nearly as much as I should be.

(use-package yasnippet
  :ensure t
  :init (yas-global-mode 1))

Completion and filtering with ivy, supported by counsel.

(use-package ivy
  :ensure t
  :config (setq ivy-use-selectable-prompt t)
  :init (ivy-mode 1))

(use-package counsel
  :ensure t
  :bind
  ("C-c i" . counsel-imenu)
  ("C-c s" . swiper)
  ("C-c g" . counsel-git-grep)
  ("C-x C-y" . counsel-yank-pop))

Better Git

Magit. Seriously. Just try it you heathen.

(use-package magit
  :ensure t
  :bind
  ("C-x g" . magit-status)
  ("C-c m" . magit-blame)
  :config (magit-add-section-hook 'magit-status-sections-hook
                                  'magit-insert-unpushed-to-upstream
                                  'magit-insert-unpushed-to-upstream-or-recent
                                  'replace))

Modes

These are the settings for various editing modes - the top level being text-mode, which is for “editing text written for humans to read”.

(defun recker/text-mode-hook ()
  (auto-fill-mode 1)
  (flyspell-mode 1)
  (flymake-mode-off))
(add-hook 'text-mode-hook 'recker/text-mode-hook)

Use personal dictionary from docs for ispell.

(setq ispell-personal-dictionary (recker/path 'docs "emacs/ispell.dict"))

Disable goal column warning.

(put 'set-goal-column 'disabled nil)

Flycheck mode.

(use-package flycheck
  :ensure t
  :init
  (global-flycheck-mode))

Support for editorconfig.

(use-package editorconfig
  :ensure t
  :config (editorconfig-mode 1))

C

Taken from The Linux Kernel Coding Style, which was a way better read than you’d think.

I slightly modified the provided snippet so that all of my C would obey these rules by default.

(defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
         (column (c-langelem-2nd-pos c-syntactic-element))
         (offset (- (1+ column) anchor))
         (steps (floor offset c-basic-offset)))
    (* (max steps 1)
       c-basic-offset)))

(add-hook 'c-mode-common-hook
          (lambda ()
            ;; Add kernel style
            (c-add-style
             "linux-tabs-only"
             '("linux" (c-offsets-alist
                        (arglist-cont-nonempty
                         c-lineup-gcc-asm-reg
                         c-lineup-arglist-tabs-only))))))

(add-hook 'c-mode-hook (lambda ()
                         (setq indent-tabs-mode t)
                         (setq show-trailing-whitespace t)
                         (c-set-style "linux-tabs-only")))

Clojure

(use-package cider
  :ensure t)

(use-package clojure-mode
  :ensure t)

Commmon Lisp

For this to work, sbcl should be installed and in PATH.

(use-package slime
  :ensure t
  :config (setq inferior-lisp-program (executable-find "sbcl")))

(use-package slime-company
  :ensure t
  :init (slime-setup '(slime-fancy slime-company)))

Csv

(use-package csv-mode
  :ensure t
  :defer t
  :mode "\\.csv\\'")

D

(use-package d-mode
  :ensure t
  :defer t
  :mode "\\.d\\'")

Dockerfile

(use-package dockerfile-mode
  :ensure t
  :defer t
  :mode "\\Dockerfile\\'")

Elisp

Disable those silly docstring warnings when editing elisp.

(with-eval-after-load 'flycheck
  (add-to-list 'flycheck-disabled-checkers 'emacs-lisp-checkdoc))

Go

Here is the really trendy part of my config.

(defun recker/go-mode-hook ()
  (setenv "GOPATH" (recker/path 'home))
  (set (make-local-variable 'company-backends) '(company-go))
  (if (not (string-match "go" compile-command))
      (set (make-local-variable 'compile-command)
	   "go build -v && go test -v && go vet")))

(use-package go-mode
  :ensure t
  :defer t
  :mode "\\*.go\\'"
  :init (progn (add-hook 'before-save-hook #'gofmt-before-save)
	       (add-hook 'go-mode-hook #'recker/go-mode-hook))
  :config (setq gofmt-command "goimports"))

(use-package company-go
  :ensure t
  :defer t)
			  
(with-eval-after-load 'flycheck
  (add-to-list 'flycheck-disabled-checkers 'go-vet))

Groovy

Pretty much just for Jenkins files.

(use-package groovy-mode
  :ensure t
  :defer t
  :mode "\\Jenkinsfile\\'")

Haskell

(use-package haskell-mode
  :ensure t
  :defer t
  :mode "\\.hs\\'")

HTML

(use-package web-mode
  :ensure t
  :defer t
  :mode ("\\.html\\'" "\\.jinja\\'")
  :config (setq web-mode-markup-indent-offset 2
                web-mode-code-indent-offset 2))

(use-package emmet-mode
  :ensure t
  :config (add-hook 'web-mode-hook 'emmet-mode))

JavaScript

This is the web-scale portion of my config.

(setq js-indent-level 2)

Jsonnet

(use-package jsonnet-mode
  :ensure t
  :defer t
  :mode ("\\.jsonnet\\'"))

Log

Taken from Working with Log Files in Emacs.

(use-package vlf :ensure t)

(use-package log4j-mode
  :ensure t
  :defer t
  :mode "\\.log\\'")

Lua

(use-package lua-mode
  :ensure t
  :defer t
  :mode ("\\.lua\\'" "\\.p8\\'"))

Markdown

(use-package markdown-mode
  :ensure t
  :commands (gfm-mode)
  :mode (("\\.md\\'" . gfm-mode)
         ("\\.gfm\\'" . gfm-mode))
  :config (setq markdown-command "multimarkdown"
		markdown-fontify-code-blocks-natively t))

Nginx

(use-package nginx-mode
  :ensure t
  :defer t)

Python

Install these dependencies

pip install rope flake8 importmagic autopep8 yapf ipdb ipython virtualenv virtualenvwrapper

Install virtualenvwrapper support.

(use-package virtualenvwrapper
  :ensure t)

Use ipython for running the code in a shell. Evidently, it’s still experimental. I have issues with some of the tab completion, so I’ll end up using *ansi-term* instead.

(setq python-shell-interpreter "ipython"
      python-shell-interpreter-args "-i --simple-prompt")

Let elpy do its thing.

(use-package elpy
  :ensure t
  :init (elpy-enable))

Ruby

These are very much a work in progress. I know about as much about ruby as I know about scented candles and professional football.

(setq ruby-deep-indent-paren nil)

Rust

(use-package rust-mode
  :ensure t
  :defer t
  :mode "\\.rs'")

Terraform

(use-package terraform-mode
  :ensure t
  :defer t
  :mode "\\.tf\\'")

Terminal

I’m a simple man, and I use a simple shell.

(defun recker/ansi-term ()
  (interactive)
  (ansi-term "/bin/bash"))
(global-set-key (kbd "C-c e") 'eshell)
(global-set-key (kbd "C-x t") 'recker/ansi-term)

The terminal buffer should be killed on exit.

(defadvice term-handle-exit
    (after term-kill-buffer-on-exit activate)
  (kill-buffer))

Aliases for eshell

(defalias 'ff #'find-file)

Typescript

(use-package typescript-mode
  :ensure t
  :defer t
  :mode "\\.ts\\'")

YAML

(use-package indent-guide
  :ensure t
  :init (add-hook 'yaml-mode-hook 'indent-guide-mode))

(use-package yaml-mode
  :ensure t
  :defer t
  :mode ("\\.yml\\'" "\\.sls\\'")
  :init
  (add-hook 'yaml-mode-hook 'turn-off-auto-fill))

Org

Org is love. Org is life.

(use-package org
  :ensure t
  :config (progn (custom-set-faces      ;Get rid of the different font sizes on headers
		  '(org-document-title ((t (:inherit outline-1 :height 1.0 :underline nil))))
		  '(org-level-1 ((t (:inherit outline-1 :height 1.0))))
		  '(org-level-2 ((t (:inherit outline-2 :height 1.0))))
		  '(org-level-3 ((t (:inherit outline-3 :height 1.0))))
		  '(org-level-4 ((t (:inherit outline-4 :height 1.0))))
		  '(org-level-5 ((t (:inherit outline-5 :height 1.0)))))
		 (setq org-confirm-babel-evaluate nil
		       org-log-into-drawer t
		       org-agenda-start-with-follow-mode t
		       org-cycle-separator-lines 1))
  :bind (("C-c a" . org-agenda)
	 ("C-c c" . org-capture))
  :init (require 'org-habit))

Custom before-save hook to fix up formatting.

(defun recker/org-before-save-hook ()
  (when (eq major-mode 'org-mode)
    ;; todo: fix up format
    ))

(add-hook 'before-save-hook #'recker/org-before-save-hook)

Inject blank lines between headings.

(setq org-blank-before-new-entry '((heading . t) (plain-list-item . auto)))

Define some simple capture templates.

(setq org-capture-templates
      `(("j" "journal" plain (file+olp+datetree ,(recker/path 'docs "journal.org"))
	 "%?" :empty-lines 1)
	("t" "task" entry (file ,(recker/path 'docs "opsat.org"))
	 "* TODO %?" :prepend t :empty-lines 1)
	("e" "emacs" entry (file+headline ,(recker/path 'docs "opsat.org") "Emacs")
	 "* TODO %?" :prepend t :empty-lines 1)
	("p" "plex" entry (file+headline ,(recker/path 'docs "opsat.org") "Plex")
	 "* TODO %?" :prepend t :empty-lines 1)
	("w" "work task" entry (file ,(recker/path 'docs "work.org"))
	 "* TODO %?" :prepend t :empty-lines 1)
	("r" "recipe" entry (file ,(recker/path 'docs "recipes.org"))
	 "* %?" :empty-lines 1)
	("i" "interview" entry (file+headline ,(recker/path 'docs "work.org") "Interviews")
	 "* TODO Evaluation: %?" :empty-lines 1)))

Babel

Load a bunch of fancy languages for a fancy man like me.

(org-babel-do-load-languages
 'org-babel-load-languages
 '((awk . t)
   (C . t)
   (calc . t)
   (clojure . t)
   (css . t)
   (plantuml . t)
   (ditaa . t)
   (ditaa . t)
   (haskell . t)
   (java . t)
   (js . t)
   (latex . t)
   (lisp . t)
   (gnuplot . t)
   (makefile . t)
   (perl . t)
   (python . t)
   (ruby . t)
   (screen . t)
   (shell . t)
   (sql . t)
   (sqlite . t)))

Don’t touch the indentation of code blocks.

(setq org-src-preserve-indentation t)

Set up some exporting backends.

(setq org-export-backends '(ascii latex md confluence))

Archive options.

(setq org-attach-directory (recker/path 'docs "attachments/"))
(setq org-attach-archive-delete 't)

Agenda

Set files read in as part of agenda.

(setq org-agenda-files (mapcar #'(lambda (f) (recker/path 'docs f)) '("opsat.org" "work.org")))

Start agenda with follow mode turned on.

(setq org-agenda-start-with-follow-mode t)

Custom Agenda Views

(setq org-agenda-custom-commands
      '(("w" "Work View"
	 ((agenda "")
	  (tags-todo "work"))
	 ((org-agenda-tag-filter-preset (quote ("+work")))))
	("h" "Habit View"
	 ((agenda "")
	  (tags-todo "habits"))
	 ((org-agenda-tag-filter-preset (quote ("+habits")))))
	("c" "Chores View"
	 ((agenda "")
	  (tags-todo "chores"))
	 ((org-agenda-tag-filter-preset (quote ("+chores")))))
	("e" "Emacs"
	 ((tags-todo "emacs")))))

Blog

My blog.

(setq org-publish-project-alist '(("blog-html"
				   :html-link-home "/"
				   :base-directory "~/src/blog"
				   :base-extension "org"
				   :publishing-directory "~/public/www.alexrecker.com"
				   :publishing-function org-html-publish-to-html
				   :recursive t
				   :section-numbers nil
				   :with-toc nil)
				  ("blog-static"
				   :base-directory "~/src/blog"
				   :base-extension "css\\|pdf\\|jpg\\|jpeg\\|gif\\|png\\|txt\\|ogg\\|js\\|webm"
				   :publishing-directory "~/public/www.alexrecker.com"
				   :publishing-function org-publish-attachment
				   :recursive t)
				  ("blog" :components ("blog-html" "blog-static"))))

Tables

Integrate gnuplot with org mode tables. Example:

#+PLOT: title:"Trial 2" ind:1 deps:(2 3) type:2d
(use-package gnuplot
  :ensure t)

(use-package gnuplot-mode
  :ensure t
  :bind ("M-C-g" . org-plot/gnuplot))

Gnus

screenshots/gnus.png

Gnus has a steep learning curve, and learning to incorporate this mysterious program has proven to be an emotional roller coaster. I’m not even sure I know enough about it to say “it’s worth it”, but hopefully this will help you with your own journey.

Better Startup

Gnus requires a “primary method” from which you obtain news. Unfortunately, the program kind of explodes if this isn’t set, which proves to be kind of a pain when you want to poke around and set up things interactively.

Here’s my workaround - set the primary method to a dummy protocol that will immediately come back. In our case, this is a blank nnml stream.

(setq gnus-select-method '(nnml ""))

Default on topic mode, since it’s more helpful.

(add-hook 'gnus-group-mode-hook 'gnus-topic-mode)

Change path to newsrc config file.

(setq gnus-startup-file (recker/path 'docs "emacs/newsrc"))

Read the auto save file on startup without asking.

(setq gnus-always-read-dribble-file t)

Enable the asynchronous flag.

(setq gnus-asynchronous t)

More possible placebo code to make gnus feel faster - use the cache.

(setq gnus-use-cache t)

Better Folders

Gnus creates a bunch of folders in your home directory that, as far as I can tell, are not needed outside of gnus. I’ve finally managed to wrangle enough variables to tell gnus to save everything in the gnus folder. I save mine off in a version controlled “docs” directory.

(setq gnus-home-directory (recker/path 'docs "emacs/gnus")
      nnfolder-directory (recker/path 'docs "emacs/gnus/Mail/archive")
      message-directory (recker/path 'docs "emacs/gnus/Mail")
      nndraft-directory (recker/path 'docs "emacs/gnus/Drafts")
      gnus-cache-directory (recker/path 'docs "emacs/gnus/cache"))

Reading News

Use gmane and gwene to follow news, mailers, and tons of other syndicated things. There are even comics.

(setq gnus-secondary-select-methods '((nntp "news.gmane.org")
                                      (nntp "news.gwene.org")))

Reading Mail

Add a personal IMAP account.

(add-to-list 'gnus-secondary-select-methods
             '(nnimap "personal"
                      (nnimap-address "imap.gmail.com")
                      (nnimap-server-port "imaps")
                      (nnimap-stream ssl)
                      (nnmail-expiry-target "nnimap+gmail:[Gmail]/Trash")
                      (nnmail-expiry-wait immediate)))

Sending Mail

Posting styles for a personal email.

(setq gnus-posting-styles '((".*" (signature (string-join '("Alex Recker" "alex@reckerfamily.com") "\n")))))

Don’t attempt to archive outbound emails to groups.

(setq gnus-message-archive-group nil)

Keep addresses locally using bbdb.

(use-package bbdb
  :ensure t
  :config (setq bbdb-file (recker/path 'docs "emacs/bbdb.el"))
  :init
  (bbdb-mua-auto-update-init 'message)
  (setq bbdb-mua-auto-update-p 'query)
  (add-hook 'gnus-startup-hook 'bbdb-insinuate-gnus))

SMTP settings.

(setq smtpmail-smtp-service 587
      smtpmail-smtp-user "alex@reckerfamily.com"
      smtpmail-smtp-server "smtp.gmail.com"
      send-mail-function 'smtpmail-send-it)

I keep an encrypted authinfo in my docs under version control.

(add-to-list 'auth-sources (recker/path 'docs "emacs/authinfo.gpg"))

Here’s what it looks like.

machine imap.gmail.com login alex@reckerfamily.com password <password> port imaps
machine smtp.gmail.com login alex@reckerfamily.com password <password> port 587

Miscellaneous

Tools

(use-package pass
  :ensure t)

(use-package request
  :ensure t)

Games

(setq tetris-score-file (recker/path 'docs "emacs/tetris-scores"))

Functions

These are miscellaneous functions that I’ve written (or plagiarized).

(defun recker/purge-buffers ()
  "Delete all buffers, except for *scratch*."
  (interactive)
  (mapc #'(lambda (b) (unless (string= (buffer-name b) "*scratch*") (kill-buffer b))) (buffer-list)))

(defun recker/unfill-region (beg end)
  "Unfill the region, joining text paragraphs into a single logical line."
  (interactive "*r")
  (let ((fill-column (point-max)))
    (fill-region beg end)))

(defun recker/org-scratch ()
  "Open a org mode *scratch* pad."
  (interactive)
  (switch-to-buffer "*org scratch*")
  (org-mode)
  (insert "#+TITLE: Org Scratch\n\n"))

(defun recker/sudo (file-name)
  "find-file, as sudo."
  (interactive "Fsudo Find file:")
  (let ((tramp-file-name (concat "/sudo::" (expand-file-name file-name))))
    (find-file tramp-file-name)))

(defun recker/do-fancy-equal-thingy (beg end)
  (interactive "r")
  (align-regexp beg end "\\(\\s-*\\)\\ =" 1 0 t))

(defun recker/pass-to-string (entry)
  "Read an entry from `pass` as a string."
  (with-temp-buffer
    (password-store-copy entry)
    (progn (yank) (buffer-string))))

(defun recker/password-store-copy-work ()
  (interactive)
  (setenv "PASSWORD_STORE_DIR" (expand-file-name "~/.password-store-work"))
  (funcall-interactively #'password-store-copy (password-store--completing-read)))

(defun recker/password-store-copy ()
  (interactive)
  (setenv "PASSWORD_STORE_DIR" (expand-file-name "~/.password-store"))
  (funcall-interactively #'password-store-copy (password-store--completing-read)))

(defun recker/encrypt-with-ssh (public-key-path)
  (interactive "fPublic Key Path: ")
  (let* ((pem (shell-command-to-string (format "ssh-keygen -f %s -e -m PKCS8" public-key-path)))
	 (secret (read-passwd "Secret String: "))
	 (encrypt-command
	  (format "openssl rsautl -ssl -encrypt -pubin -inkey <(echo \"%s\") -ssl -in <(echo \"%s\") | base64" pem secret))
	 (hash (shell-command-to-string encrypt-command))
	 (decrypt-command
	  (format "echo \"%s\" | base64 -D | openssl rsautl -decrypt -inkey ~/.ssh/id_rsa" hash)))
    (kill-new decrypt-command nil)
    (message "Decrypt command added to kill ring.")))

(defun recker/send-list-at-point-to-wunderlist ()
  "Sends the org mode list at point to wunderlist.  Any item not
  already captured in wunderlist (by title) is added."
  (interactive)
  (setenv "PASSWORD_STORE_DIR" (expand-file-name "~/.password-store"))
  (let* ((these-items (if (member (first (org-element-at-point)) '(plain-list item))
			  (mapcar #'(lambda (i) (first i)) (cdr (org-list-to-lisp)))
			(error "pointer not on a list")))
	 (headers `(("Content-Type" . "application/json")
		    ("X-Access-Token" . ,(password-store-get "wundercron/client-secret"))
		    ("X-Client-ID" . ,(password-store-get "wundercron/client-id"))))
	 (url "https://a.wunderlist.com/api/v1")
	 (list-name "groceries")	;TODO: completing-read?
	 (list-obj (seq-find
		    #'(lambda (i) (string-equal list-name (cdr (assoc 'title i))))
		    (request-response-data
		     (request (concat url "/lists") :sync t :parser 'json-read :headers headers))))
	 (list-id (cdr (assoc 'id list-obj)))
	 (current-items (mapcar
			 #'(lambda (o) (cdr (assoc 'title o)))
			 (request-response-data
			  (request (concat url "/tasks")
				   :sync t :parser 'json-read :headers headers
				   :params `(("list_id" . ,list-id))))))
	 (new-items (or (remove-if #'(lambda (i) (member (format "%s" i) current-items)) these-items)
			(error "nothing to add!"))))
    (dolist (item new-items)
      (request (concat url "/tasks")
	       :parser 'json-read :headers headers :type "POST"
	       :data (json-encode-alist `(("list_id" . ,list-id)
					  ("title" . ,(format "%s" item))))))
    (message "Added to groceries: %s" new-items)))

Keybindings

(global-set-key (kbd "C-c b") 'browse-url)
(global-set-key (kbd "C-c d") #'(lambda () (interactive) (find-file (recker/path 'desktop))))
(global-set-key (kbd "C-c f") 'project-find-file)
(global-set-key (kbd "C-c j") #'(lambda () (interactive) (find-file (recker/path 'docs "journal.org"))))
(global-set-key (kbd "C-c l") 'sort-lines)
(global-set-key (kbd "C-c n") 'recker/org-scratch)
(global-set-key (kbd "C-c o") #'(lambda () (interactive) (find-file (recker/path 'docs "opsat.org"))))
(global-set-key (kbd "C-c r") 'replace-string)
(global-set-key (kbd "C-c w") 'recker/send-list-at-point-to-wunderlist)
(global-set-key (kbd "C-x C-k k") 'kill-buffer)
(global-set-key (kbd "C-x P") 'recker/purge-buffers)
(global-set-key (kbd "C-x k") 'kill-this-buffer)
(global-set-key (kbd "C-x p") 'password-store-copy)
(global-set-key (kbd "C-x p") 'recker/password-store-copy)
(global-set-key (kbd "C-x w") 'recker/password-store-copy-work)
(global-set-key (kbd "C-x |") 'recker/do-fancy-equal-thingy)

Local

Emacs sometimes dumps things in init.el. It means well, but I would rather this be in a different file ignored by git.

(let ((custom (recker/path 'emacs "custom.el")))
  (unless (file-exists-p custom)
    (with-temp-buffer
      (write-file custom)))
  (setq custom-file custom))

I also like to keep a file around for miscellaneous elisp that should run on startup. This is for machine specific settings or things I am still tinkering with.

(let ((local (recker/path 'emacs "local.el")))
  (unless (file-exists-p local)
    (with-temp-buffer
      (insert ";; This file is for local changes")
      (write-file local)))
  (load local))
You can’t perform that action at this time.