Skip to content
My life is dope and I have dope emacs configs
Emacs Lisp YASnippet
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.


Type Name Latest commit message Commit time
Failed to load latest commit information.

Emacs Configuration

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


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 "")


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.


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))

Helper function for sniffing OS.

(defun recker/macos-p ()
  "Returns T if running on MacOS."
  (string-equal system-type "darwin"))

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"))
		  ('public (if macos-p "~/Public" "~/public"))
		  ('src "~/src")
		  ('emacs user-emacs-directory)
		  (_ (error (format "no %s directory!" dir)))))
	   (subdir (or subpath "")))
    (expand-file-name (concat (file-name-as-directory dir) subpath))))

Register some files I visit often.

(set-register ?b '(file . "/ssh:bedroom:/home/alex"))
(set-register ?d `(file . ,(recker/path 'desktop)))
(set-register ?e `(file . ,(recker/path 'emacs "")))
(set-register ?f '(file . "/ssh:alex@archive.local:/mnt/"))
(set-register ?l `(file . ,(recker/path 'emacs "local.el")))
(set-register ?o `(file . ,(recker/path 'docs "")))


Better Look

On MacOS, I like my emacs extra thicc.

(when (and (recker/macos-p) (display-graphic-p))
  (setq initial-frame-alist '((width . 99) (height . 53))
	  default-frame-alist '((width . 99) (height . 53))))

Set the font to monaco if I’m on a mac. Otherwise, use inconsolata.

 (when (display-graphic-p)
   (if (recker/macos-p)
	(set-default-font "Monaco 16" nil t)
     (set-default-font "Inconsolata 13" nil t)))

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)
 (tool-bar-mode -1)

 (when (display-graphic-p)
   (scroll-bar-mode -1))

 (setq vc-follow-symlinks t)

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)

Disable goal column warning.

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


(put 'upcase-region 'disabled nil)

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"))

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 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
  :init (global-company-mode)
  :config (setq company-tooltip-align-annotations t
		  company-dabbrev-downcase nil))

(use-package company-quickhelp
  :ensure t
  :init (company-quickhelp-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
  ("C-c i" . counsel-imenu)
  ("C-c s" . swiper)
  ("C-c g" . counsel-git-grep)
  ("C-x C-y" . counsel-yank-pop))

Use projectile for finding things within projects.

(use-package projectile
  :ensure t
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
  (setq projectile-completion-system 'ivy)
  (projectile-mode t))

Better Git

Magit. Seriously. Just try it you heathen.

(use-package magit
  :ensure t
  ("C-x g" . magit-status)
  ("C-c m" . magit-blame)
  :config (magit-add-section-hook 'magit-status-sections-hook

Better Spellcheck.

Use personal dictionary from docs for ispell.

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

Use ispell explicitly, so it doesn’t accidentally pick up a different program like aspell on a mac.

(setq ispell-program-name (executable-find "ispell"))

Flycheck mode.

(use-package flycheck
  :ensure t

Better 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.

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
Being president is like being a jackass in a hailstorm. There’s nothing to do but to stand there and take it.Lyndon Johnson

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))))
				    (insert (format "\"%s\"\n" text))
				    (fill-region (point-min) (point-max))
				    (insert (format "-- %s" attribution))
				    (comment-region (point-min) (point-max))
				    (dotimes (_ 2) (newline))

Make the *scratch* buffer unkillable.

(defun recker/dont-kill-scratch ()
  "Return NIL if the current buffer is the *scratch* buffer."
  (not (equal (buffer-name (current-buffer)) "*scratch*")))

(add-hook 'kill-buffer-query-functions 'recker/dont-kill-scratch)

Better Passwords

My own functions for pass.

 (defun recker/pass-directory ()
   (or (bound-and-true-p recker/active-pass-directory)
	(expand-file-name "~/.password-store")))

 (defun recker/pass--file-to-entry (path)
    (file-relative-name path (recker/pass-directory))))

 (defun recker/pass-list-entries ()
    (directory-files-recursively (recker/pass-directory) ".gpg")))

 (defun recker/pass-to-string (path)
      (format "PASSWORD_STORE_DIR=\"%s\" pass \"%s\" | head -1" (recker/pass-directory) path))

 (defun recker/pass-to-clip (path)
   (interactive (list (completing-read "Password: " (recker/pass-list-entries) nil t)))
    (format "PASSWORD_STORE_DIR=\"%s\" pass -c \"%s\"" (recker/pass-directory) path)))

 (defun recker/pass-to-clip-work ()
   (let ((recker/active-pass-directory (expand-file-name "~/.password-store-work")))
     (funcall-interactively #'recker/pass-to-clip (completing-read "Password: " (recker/pass-list-entries) nil t))))


Support for editorconfig, no matter what the mode is.

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


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)

(add-hook 'c-mode-common-hook
	    (lambda ()
	      ;; Add kernel style
	       '("linux" (c-offsets-alist

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


;; (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)))


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


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


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


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


Disable those silly docstring warnings when editing elisp.

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


This is the really trendy part of my config.

(use-package go-mode
  :ensure t
  :defer t
  :mode "\\*.go\\'"
  (add-hook 'before-save-hook 'gofmt-before-save)
  (local-set-key (kbd "M-.") 'godef-jump)
  (add-hook 'go-mode-hook (lambda ()
			      (set (make-local-variable 'company-backends) '(company-go))

(use-package company-go
  :ensure t)


Pretty much just for Jenkins files.

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


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


(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))


This is the web-scale portion of my config.

(setq js-indent-level 2)


Install jsonnet mode.

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

Add my own shim for formatting jsonnet because the language is in shambles and this doesn’t work out of the box.

(defun jsonnet-reformat-buffer ()
  "Reformat entire buffer using the Jsonnet format utility, except it actually works."
  (call-process-region (point-min) (point-max) "jsonnetfmt" t t nil (buffer-file-name)))


Taken from Working with Log Files in Emacs.

(use-package vlf :ensure t)

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


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


(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))


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


Install virtualenvwrapper support.

(use-package virtualenvwrapper
  :ensure t)

Let elpy do its thing.

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


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)


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


Automatically “fill” text while editing.

(add-hook 'text-mode-hook 'turn-on-auto-fill)

Turn on spell check.

(add-hook 'text-mode-hook #'(lambda () (flyspell-mode t)))


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

(use-package company-terraform
  :ensure t
  :init (company-terraform-init))


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

(defun recker/ansi-term ()
  (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)

Aliases for eshell

(defalias 'ff #'find-file)


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


(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\\'" "\\.yml.j2\\'")
  (add-hook 'yaml-mode-hook 'turn-off-auto-fill))


(use-package org-mode
  :mode "\\.org$")


Render blank lines between collapsed headings.

(setq org-cycle-separator-lines 1)

Insert blank lines between headings by default.

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

Set attachments directory.

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

Delete attachments when archiving something, since everything is stored in git anyway.

(setq org-attach-archive-delete 't)


Set-up org capture for quickly adding text to notes.

(setq org-capture-templates '())

Bind org-capture to a fast, slick keybinding.

(global-set-key (kbd "C-c c") 'org-capture)


Use the local documents folder as the agenda root.

(setq org-agenda-files (list (recker/path 'docs)))

Give org-agenda a slick keybinding so I can quickly check it while working on something else.

(global-set-key (kbd "C-c a") 'org-agenda)

While browsing the agenda, default to follow mode - which higlights the item in your notes while you scan through the agenda.

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

By default, exclude anything that was archived.

(setq org-agenda-tag-filter-preset '("-ARCHIVE"))

Set up some custom agenda views.

(setq org-agenda-custom-commands '())


Live dangerously. Tell org to run code blocks without confirmation.

(setq org-confirm-babel-evaluate nil)

Add some languages!

 '((python . t)
   (ruby . t)
   (shell . t)))


Set up some publishing projects.

(setq org-publish-project-alist '())


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"))

Don’t keep a dribble file.

(setq gnus-use-dribble-file nil)

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 '())

Reading Mail

Add a personal IMAP account.

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

Sending Mail

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"))
  (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 ""
	smtpmail-smtp-server ""
	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 login password <password> port imaps
machine login password <password> port 587



(use-package dictionary :ensure t)

(use-package request :ensure t)

(use-package transmission :ensure t)

Write good.

(use-package writegood-mode
  :ensure t
  :init (add-hook 'org-mode-hook 'writegood-mode))

Start a server to listen for emacsclient.

(require 'server)
(unless (server-running-p)

Mutt, for work email.

(add-to-list 'auto-mode-alist '("/mutt" . mail-mode))


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


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

 (defun recker/purge-buffers ()
   "Delete all buffers, except for *scratch*."
   (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."
   (switch-to-buffer "*org scratch*")
   (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/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: "))
	    (format "openssl rsautl -ssl -encrypt -pubin -inkey <(echo \"%s\") -ssl -in <(echo \"%s\") | base64" pem secret))
	   (hash (shell-command-to-string encrypt-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."
   (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" . ,(recker/pass-to-string "wundercron/client-secret"))
		      ("X-Client-ID" . ,(recker/pass-to-string "wundercron/client-id"))))
	   (url "")
	   (list-name "groceries")	;TODO: completing-read?
	   (list-obj (seq-find
		      #'(lambda (i) (string-equal list-name (cdr (assoc 'title i))))
		       (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 (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)))

 (defun recker/docs-sync ()
     (async-shell-command (format "git-sync -d %s" (recker/path 'docs)) nil nil)))

 (defun find-first-non-ascii-char ()
   "Find the first non-ascii character from point onwards."
   (let (point)
	(setq point
	      (catch 'non-ascii
		(while (not (eobp))
		  (or (eq (char-charset (following-char))
		      (throw 'non-ascii (point)))
		  (forward-char 1)))))
     (if point
	  (goto-char point)
	(message "No non-ascii characters."))))

 (defun recker/today ()
   "Open today's journal entry."
   (let ((target
	   (recker/path 'src (format-time-string "blog/_posts/"))))
     (find-file target)))


(global-set-key (kbd "C-c b") 'browse-url)
(global-set-key (kbd "C-c d") 'recker/docs-sync)
(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 r") 'replace-string)
(global-set-key (kbd "C-c t") 'recker/today)
(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") 'recker/pass-to-clip)
(global-set-key (kbd "C-x w") 'recker/pass-to-clip-work)
(global-set-key (kbd "C-x |") 'recker/do-fancy-equal-thingy)


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)
	(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)
	(insert ";; This file is for local changes")
	(write-file local)))
   (load local))
You can’t perform that action at this time.